From 4058ad63692c19b3e9645b8676282e136200f76b Mon Sep 17 00:00:00 2001 From: Michael Arnold Date: Mon, 14 Sep 2020 11:20:30 -0400 Subject: [PATCH 1/8] Do not use data.aws_ssm_parameter as it leaks secrets. --- main.tf | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/main.tf b/main.tf index d01f561..9f5fbdd 100644 --- a/main.tf +++ b/main.tf @@ -24,18 +24,16 @@ locals { # Datasources ############################################################# +data "aws_partition" "current" {} +data "aws_region" "current" {} +data "aws_caller_identity" "current" {} + data "aws_db_instance" "default" { count = var.enabled ? 1 : 0 db_instance_identifier = var.db_instance_id } -data "aws_ssm_parameter" "master_password" { - count = var.enabled && local.master_password_in_ssm_param ? 1 : 0 - - name = var.db_master_password_ssm_param -} - data "aws_secretsmanager_secret" "master_password" { count = var.enabled && local.master_password_in_secretsmanager ? 1 : 0 @@ -48,12 +46,6 @@ data "aws_kms_key" "master_password" { key_id = var.db_master_password_ssm_param_kms_key } -data "aws_ssm_parameter" "user_password" { - count = var.enabled && local.user_password_in_ssm_param ? 1 : 0 - - name = var.db_user_password_ssm_param -} - data "aws_secretsmanager_secret" "user_password" { count = var.enabled && local.user_password_in_secretsmanager ? 1 : 0 @@ -267,7 +259,7 @@ data "aws_iam_policy_document" "master_password_ssm_permissions" { actions = [ "ssm:GetParameter", ] - resources = [join("", data.aws_ssm_parameter.master_password.*.arn)] + resources = ["arn:${data.aws_partition.current.partition}:ssm:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:parameter${var.db_master_password_ssm_param}"] } } @@ -303,7 +295,7 @@ data "aws_iam_policy_document" "user_password_ssm_permissions" { actions = [ "ssm:GetParameter", ] - resources = [join("", data.aws_ssm_parameter.user_password.*.arn)] + resources = ["arn:${data.aws_partition.current.partition}:ssm:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:parameter${var.db_user_password_ssm_param}"] } } From 4986005090f650c0f7cc15f921a4dab2b94e5cbf Mon Sep 17 00:00:00 2001 From: Michael Arnold Date: Mon, 11 Jan 2021 14:30:02 -0500 Subject: [PATCH 2/8] Update to support Terraform 0.13. --- main.tf | 4 ++-- versions.tf | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/main.tf b/main.tf index d01f561..6370f14 100644 --- a/main.tf +++ b/main.tf @@ -79,7 +79,7 @@ data "aws_kms_key" "lambda" { module "default_label" { enabled = var.enabled - source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.16.0" + source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.22.1" attributes = compact(concat(var.attributes, ["db", "provisioner"])) delimiter = var.delimiter name = var.name @@ -332,7 +332,7 @@ data "aws_iam_policy_document" "user_password_kms_permissions" { } module "aggregated_policy" { - source = "git::https://github.com/cloudposse/terraform-aws-iam-policy-document-aggregator.git?ref=tags/0.2.0" + source = "git::https://github.com/cloudposse/terraform-aws-iam-policy-document-aggregator.git?ref=tags/0.7.0" source_documents = compact([ join("", data.aws_iam_policy_document.default_permissions.*.json), diff --git a/versions.tf b/versions.tf index cf4d633..e37df6f 100644 --- a/versions.tf +++ b/versions.tf @@ -1,9 +1,9 @@ terraform { - required_version = "~> 0.12.0" + required_version = ">= 0.12.0" required_providers { - aws = "~> 2.31" - archive = "~> 1.3" - local = "~> 1.2" + aws = ">= 2.31" + archive = ">= 1.3" + local = ">= 1.2" } } From c9ff6d71fa08312946756815fa58af140c31c1be Mon Sep 17 00:00:00 2001 From: root Date: Mon, 26 Apr 2021 07:14:11 +0000 Subject: [PATCH 3/8] Added read-only access feature --- main.tf | 1 + source-code/main.py | 71 +++++++++++++++++++++++++++++++-------------- variables.tf | 6 ++++ 3 files changed, 56 insertions(+), 22 deletions(-) diff --git a/main.tf b/main.tf index d01f561..5bbc4eb 100644 --- a/main.tf +++ b/main.tf @@ -136,6 +136,7 @@ resource "aws_lambda_function" "default" { PROVISION_USER = var.db_user PROVISION_USER_PASSWORD = var.db_user_password PROVISION_USER_PASSWORD_SSM_PARAM = var.db_user_password_ssm_param + GRANT_ALL_PRIVILEGES = var.grant_all_privileges } } diff --git a/source-code/main.py b/source-code/main.py index 2365a67..cf746fb 100644 --- a/source-code/main.py +++ b/source-code/main.py @@ -36,6 +36,7 @@ class DBInfo: provision_db_name: str provision_user: str provision_user_password: str + grant_all_privileges: str class DBProvisioner(object): @@ -210,28 +211,53 @@ def provision_mysql_db(self, info: DBInfo): cursor.execute(query) if info.provision_user: - self.logger.info("Granting all privileges on database '{}' to '{}'".format( - info.provision_db_name, - info.provision_user, - )) - - query = "GRANT ALL PRIVILEGES ON {} . * TO '{}'@'localhost';".format( - info.provision_db_name, - info.provision_user, - ) - cursor.execute(query) - query = "GRANT ALL PRIVILEGES ON {} . * TO '{}'@'%';".format( - info.provision_db_name, - info.provision_user, - ) - cursor.execute(query) - query = "FLUSH PRIVILEGES;" - cursor.execute(query) + if info.grant_all_privileges == "true": + self.logger.info("Granting all privileges on database '{}' to '{}'".format( + info.provision_db_name, + info.provision_user, + )) + + query = "GRANT ALL PRIVILEGES ON {} . * TO '{}'@'localhost';".format( + info.provision_db_name, + info.provision_user, + ) + cursor.execute(query) + query = "GRANT ALL PRIVILEGES ON {} . * TO '{}'@'%';".format( + info.provision_db_name, + info.provision_user, + ) + cursor.execute(query) + query = "FLUSH PRIVILEGES;" + cursor.execute(query) + + self.logger.info("All privileges on database '{}' granted to '{}'.".format( + info.provision_db_name, + info.provision_user, + )) + else: + self.logger.info("Granting read-only privileges on database '{}' to '{}'".format( + info.provision_db_name, + info.provision_user, + )) + + query = "GRANT SELECT PRIVILEGES ON {} . * TO '{}'@'localhost';".format( + info.provision_db_name, + info.provision_user, + ) + cursor.execute(query) + query = "GRANT SELECT PRIVILEGES ON {} . * TO '{}'@'%';".format( + info.provision_db_name, + info.provision_user, + ) + cursor.execute(query) + query = "FLUSH PRIVILEGES;" + cursor.execute(query) + + self.logger.info("Read-only privileges on database '{}' granted to '{}'.".format( + info.provision_db_name, + info.provision_user, + )) - self.logger.info("All privileges on database '{}' granted to '{}'.".format( - info.provision_db_name, - info.provision_user, - )) self.logger.info("Database '{}' successfully created".format(info.provision_db_name)) @@ -257,7 +283,8 @@ def provision(self): connect_db_name=os.environ.get('CONNECT_DB_NAME', instance.get('DBName')), provision_db_name=os.environ.get('PROVISION_DB_NAME'), provision_user=os.environ.get('PROVISION_USER'), - provision_user_password=user_password + provision_user_password=user_password, + grant_all_privileges=os.environ.get('GRANT_ALL_PRIVILEGES') ) engine: str = instance.get('Engine') diff --git a/variables.tf b/variables.tf index 1d5cb88..3914391 100644 --- a/variables.tf +++ b/variables.tf @@ -149,3 +149,9 @@ variable "allowed_egress_cidr_blocks" { default = ["0.0.0.0/0"] } + +variable "grant_all_privileges" { + type = string + default = "true" + description = "Defines whether the user created by lambda function should be given all privileges" +} From 2f153a743c3e8f857c6f004ad3ac940146d509a6 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 16 Jun 2021 07:26:24 +0000 Subject: [PATCH 4/8] Fixed user privileges --- source-code/main.py | 95 ++++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 48 deletions(-) diff --git a/source-code/main.py b/source-code/main.py index cf746fb..2ccbfa6 100644 --- a/source-code/main.py +++ b/source-code/main.py @@ -209,57 +209,56 @@ def provision_mysql_db(self, info: DBInfo): query = "CREATE DATABASE {};".format(info.provision_db_name) cursor.execute(query) + self.logger.info("Database '{}' successfully created".format(info.provision_db_name)) - if info.provision_user: - if info.grant_all_privileges == "true": - self.logger.info("Granting all privileges on database '{}' to '{}'".format( - info.provision_db_name, - info.provision_user, - )) - - query = "GRANT ALL PRIVILEGES ON {} . * TO '{}'@'localhost';".format( - info.provision_db_name, - info.provision_user, - ) - cursor.execute(query) - query = "GRANT ALL PRIVILEGES ON {} . * TO '{}'@'%';".format( - info.provision_db_name, - info.provision_user, - ) - cursor.execute(query) - query = "FLUSH PRIVILEGES;" - cursor.execute(query) - - self.logger.info("All privileges on database '{}' granted to '{}'.".format( - info.provision_db_name, - info.provision_user, - )) - else: - self.logger.info("Granting read-only privileges on database '{}' to '{}'".format( - info.provision_db_name, - info.provision_user, - )) - - query = "GRANT SELECT PRIVILEGES ON {} . * TO '{}'@'localhost';".format( - info.provision_db_name, - info.provision_user, - ) - cursor.execute(query) - query = "GRANT SELECT PRIVILEGES ON {} . * TO '{}'@'%';".format( - info.provision_db_name, - info.provision_user, - ) - cursor.execute(query) - query = "FLUSH PRIVILEGES;" - cursor.execute(query) - - self.logger.info("Read-only privileges on database '{}' granted to '{}'.".format( - info.provision_db_name, - info.provision_user, - )) + if info.provision_user: + if info.grant_all_privileges == "true": + self.logger.info("Granting all privileges on database '{}' to '{}'".format( + info.provision_db_name, + info.provision_user, + )) + query = "GRANT ALL PRIVILEGES ON {} . * TO '{}'@'localhost';".format( + info.provision_db_name, + info.provision_user, + ) + cursor.execute(query) + query = "GRANT ALL PRIVILEGES ON {} . * TO '{}'@'%';".format( + info.provision_db_name, + info.provision_user, + ) + cursor.execute(query) + query = "FLUSH PRIVILEGES;" + cursor.execute(query) + + self.logger.info("All privileges on database '{}' granted to '{}'.".format( + info.provision_db_name, + info.provision_user, + )) + else: + self.logger.info("Granting read-only privileges on database '{}' to '{}'".format( + info.provision_db_name, + info.provision_user, + )) + + query = "GRANT SELECT PRIVILEGES ON {} . * TO '{}'@'localhost';".format( + info.provision_db_name, + info.provision_user, + ) + cursor.execute(query) + query = "GRANT SELECT PRIVILEGES ON {} . * TO '{}'@'%';".format( + info.provision_db_name, + info.provision_user, + ) + cursor.execute(query) + query = "FLUSH PRIVILEGES;" + cursor.execute(query) + + self.logger.info("Read-only privileges on database '{}' granted to '{}'.".format( + info.provision_db_name, + info.provision_user, + )) - self.logger.info("Database '{}' successfully created".format(info.provision_db_name)) cursor.close() connection.close() From d4b38ab1078a06ee61684150c74f1382f70e2d19 Mon Sep 17 00:00:00 2001 From: SKIMRAN0509 Date: Fri, 23 Feb 2024 13:37:06 +0530 Subject: [PATCH 5/8] modified aggregate aws iam policy --- main.tf | 36 +++++++++++++++++++++--------------- outputs.tf | 8 ++++++++ variables.tf | 6 ++++++ versions.tf | 10 +++++----- 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/main.tf b/main.tf index bdd6915..d72e85a 100644 --- a/main.tf +++ b/main.tf @@ -70,8 +70,8 @@ data "aws_kms_key" "lambda" { module "default_label" { enabled = var.enabled - - source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.22.1" + source = "cloudposse/label/null" + version = "0.25.0" attributes = compact(concat(var.attributes, ["db", "provisioner"])) delimiter = var.delimiter name = var.name @@ -324,21 +324,24 @@ data "aws_iam_policy_document" "user_password_kms_permissions" { } } -module "aggregated_policy" { - source = "git::https://github.com/cloudposse/terraform-aws-iam-policy-document-aggregator.git?ref=tags/0.7.0" +######################################################## +locals { - source_documents = compact([ - join("", data.aws_iam_policy_document.default_permissions.*.json), - join("", data.aws_iam_policy_document.lambda_kms_permissions.*.json), - join("", data.aws_iam_policy_document.master_password_ssm_permissions.*.json), - join("", data.aws_iam_policy_document.master_password_kms_permissions.*.json), - join("", data.aws_iam_policy_document.master_password_secretsmanager_permissions.*.json), - join("", data.aws_iam_policy_document.user_password_ssm_permissions.*.json), - join("", data.aws_iam_policy_document.user_password_kms_permissions.*.json), - join("", data.aws_iam_policy_document.user_password_secretsmanager_permissions.*.json), - ]) + policy_statement = concat( + jsondecode(data.aws_iam_policy_document.default_permissions[0].json).Statement, + jsondecode(data.aws_iam_policy_document.master_password_ssm_permissions[0].json).Statement, + jsondecode(data.aws_iam_policy_document.master_password_kms_permissions[0].json).Statement, + jsondecode(data.aws_iam_policy_document.master_password_secretsmanager_permissions[0].json).Statement + # jsondecode(data.aws_iam_policy_document.assume[0].json).Statement + # jsondecode(data.aws_iam_policy_document.lambda_kms_permissions[0].json).Statement + # jsondecode(data.aws_iam_policy_document.user_password_ssm_permissions[0].json).Statement, + # jsondecode(data.aws_iam_policy_document.user_password_secretsmanager_permissions[0].json).Statement, + # jsondecode(data.aws_iam_policy_document.user_password_kms_permissions[0].json).Statement + ) } +data "aws_iam_policy_document" "empty" {} + resource "aws_iam_role" "lambda" { count = var.enabled ? 1 : 0 @@ -355,7 +358,10 @@ resource "aws_iam_policy" "default" { path = "/" description = "IAM policy to control access of Lambda function to AWS resources" - policy = module.aggregated_policy.result_document + policy = jsonencode({ + Version = "2012-10-17", + Statement = local.policy_statement + }) } resource "aws_iam_role_policy_attachment" "default_permissions" { diff --git a/outputs.tf b/outputs.tf index 0d33ead..9bedac2 100644 --- a/outputs.tf +++ b/outputs.tf @@ -38,3 +38,11 @@ output "lambda_function_name" { value = join("", aws_lambda_function.default.*.function_name) } +output "result_document" { + # value = data.aws_iam_policy_document.default[*].json + # description = "Aggregated IAM policy" + value = jsonencode({ + Version = "2012-10-17", + Statement = local.policy_statement + }) +} diff --git a/variables.tf b/variables.tf index 3914391..b64df12 100644 --- a/variables.tf +++ b/variables.tf @@ -155,3 +155,9 @@ variable "grant_all_privileges" { default = "true" description = "Defines whether the user created by lambda function should be given all privileges" } + +variable "source_documents" { + type = list(string) + description = "List of JSON IAM policy documents.

Limits:
* List size max 10
* Statement can be overriden by the statement with the same sid from the latest policy." + default = [] +} \ No newline at end of file diff --git a/versions.tf b/versions.tf index e37df6f..ef57128 100644 --- a/versions.tf +++ b/versions.tf @@ -1,9 +1,9 @@ terraform { - required_version = ">= 0.12.0" + required_version = "~> 1.6.0, < 1.7.0" required_providers { - aws = ">= 2.31" - archive = ">= 1.3" - local = ">= 1.2" + aws = "~> 5.26.0" + archive = "~> 1.3" + local = "~> 1.2" } -} +} \ No newline at end of file From 933b2302dcee7bffcfb4f4f9d9b948db172f840b Mon Sep 17 00:00:00 2001 From: SKIMRAN0509 Date: Thu, 7 Mar 2024 13:22:15 +0530 Subject: [PATCH 6/8] updated aggrehate policy with leng builtin func --- main.tf | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/main.tf b/main.tf index d72e85a..1a2c5e6 100644 --- a/main.tf +++ b/main.tf @@ -328,15 +328,14 @@ data "aws_iam_policy_document" "user_password_kms_permissions" { locals { policy_statement = concat( - jsondecode(data.aws_iam_policy_document.default_permissions[0].json).Statement, - jsondecode(data.aws_iam_policy_document.master_password_ssm_permissions[0].json).Statement, - jsondecode(data.aws_iam_policy_document.master_password_kms_permissions[0].json).Statement, - jsondecode(data.aws_iam_policy_document.master_password_secretsmanager_permissions[0].json).Statement - # jsondecode(data.aws_iam_policy_document.assume[0].json).Statement - # jsondecode(data.aws_iam_policy_document.lambda_kms_permissions[0].json).Statement - # jsondecode(data.aws_iam_policy_document.user_password_ssm_permissions[0].json).Statement, - # jsondecode(data.aws_iam_policy_document.user_password_secretsmanager_permissions[0].json).Statement, - # jsondecode(data.aws_iam_policy_document.user_password_kms_permissions[0].json).Statement + length(data.aws_iam_policy_document.default_permissions[*].json) > 0 ? jsondecode(data.aws_iam_policy_document.default_permissions[0].json)["Statement"] : [], + length(data.aws_iam_policy_document.master_password_ssm_permissions[*].json) > 0 ? jsondecode(data.aws_iam_policy_document.master_password_ssm_permissions[0].json)["Statement"] : [], + length(data.aws_iam_policy_document.master_password_kms_permissions[*].json) > 0 ? jsondecode(data.aws_iam_policy_document.master_password_kms_permissions[0].json)["Statement"] : [], + length(data.aws_iam_policy_document.master_password_secretsmanager_permissions[*].json) > 0 ? jsondecode(data.aws_iam_policy_document.master_password_secretsmanager_permissions[0].json)["Statement"] : [], + length(data.aws_iam_policy_document.lambda_kms_permissions[*].json) > 0 ? jsondecode(data.aws_iam_policy_document.lambda_kms_permissions[0].json)["Statement"] : [], + length(data.aws_iam_policy_document.user_password_ssm_permissions[*].json) > 0 ? jsondecode(data.aws_iam_policy_document.user_password_ssm_permissions[0].json)["Statement"] : [], + length(data.aws_iam_policy_document.user_password_secretsmanager_permissions[*].json) > 0 ? jsondecode(data.aws_iam_policy_document.user_password_secretsmanager_permissions[0].json)["Statement"] : [], + length(data.aws_iam_policy_document.user_password_kms_permissions[*].json) > 0 ? jsondecode(data.aws_iam_policy_document.user_password_kms_permissions[0].json)["Statement"] : [], ) } From 54b3833b0af5cc72807ed424ffd78c240c5fa69c Mon Sep 17 00:00:00 2001 From: Satya Vadapalli Date: Thu, 7 Mar 2024 16:41:55 -0700 Subject: [PATCH 7/8] psycopg2 dependency --- source-code/psycopg2/LICENSE | 49 - .../_psycopg.cpython-37m-x86_64-linux-gnu.so | Bin 1046424 -> 0 bytes source-code/psycopg2/{ => lib}/__init__.py | 30 +- source-code/psycopg2/{ => lib}/_ipaddress.py | 3 +- source-code/psycopg2/{ => lib}/_json.py | 71 +- source-code/psycopg2/{ => lib}/_range.py | 101 +- source-code/psycopg2/{ => lib}/errorcodes.py | 47 +- source-code/psycopg2/lib/errors.py | 38 + source-code/psycopg2/{ => lib}/extensions.py | 44 +- source-code/psycopg2/{ => lib}/extras.py | 454 ++-- source-code/psycopg2/{ => lib}/pool.py | 72 +- source-code/psycopg2/{ => lib}/sql.py | 111 +- source-code/psycopg2/{ => lib}/tz.py | 53 +- .../psycopg/_psycopg.vc9.amd64.manifest | 15 + .../psycopg/_psycopg.vc9.x86.manifest | 15 + source-code/psycopg2/psycopg/adapter_asis.c | 195 ++ source-code/psycopg2/psycopg/adapter_asis.h | 48 + source-code/psycopg2/psycopg/adapter_binary.c | 281 +++ source-code/psycopg2/psycopg/adapter_binary.h | 48 + .../psycopg2/psycopg/adapter_datetime.c | 515 ++++ .../psycopg2/psycopg/adapter_datetime.h | 107 + source-code/psycopg2/psycopg/adapter_list.c | 342 +++ source-code/psycopg2/psycopg/adapter_list.h | 47 + .../psycopg2/psycopg/adapter_pboolean.c | 185 ++ .../psycopg2/psycopg/adapter_pboolean.h | 48 + .../psycopg2/psycopg/adapter_pdecimal.c | 248 ++ .../psycopg2/psycopg/adapter_pdecimal.h | 48 + source-code/psycopg2/psycopg/adapter_pfloat.c | 221 ++ source-code/psycopg2/psycopg/adapter_pfloat.h | 48 + source-code/psycopg2/psycopg/adapter_pint.c | 222 ++ source-code/psycopg2/psycopg/adapter_pint.h | 48 + .../psycopg2/psycopg/adapter_qstring.c | 307 +++ .../psycopg2/psycopg/adapter_qstring.h | 52 + source-code/psycopg2/psycopg/aix_support.c | 58 + source-code/psycopg2/psycopg/aix_support.h | 48 + source-code/psycopg2/psycopg/bytes_format.c | 309 +++ source-code/psycopg2/psycopg/column.h | 49 + source-code/psycopg2/psycopg/column_type.c | 420 ++++ source-code/psycopg2/psycopg/config.h | 216 ++ source-code/psycopg2/psycopg/connection.h | 229 ++ source-code/psycopg2/psycopg/connection_int.c | 1554 ++++++++++++ .../psycopg2/psycopg/connection_type.c | 1518 ++++++++++++ source-code/psycopg2/psycopg/conninfo.h | 41 + source-code/psycopg2/psycopg/conninfo_type.c | 648 +++++ source-code/psycopg2/psycopg/cursor.h | 147 ++ source-code/psycopg2/psycopg/cursor_int.c | 171 ++ source-code/psycopg2/psycopg/cursor_type.c | 2126 +++++++++++++++++ source-code/psycopg2/psycopg/diagnostics.h | 41 + .../psycopg2/psycopg/diagnostics_type.c | 208 ++ source-code/psycopg2/psycopg/error.h | 46 + source-code/psycopg2/psycopg/error_type.c | 376 +++ source-code/psycopg2/psycopg/green.c | 210 ++ source-code/psycopg2/psycopg/green.h | 76 + source-code/psycopg2/psycopg/libpq_support.c | 106 + source-code/psycopg2/psycopg/libpq_support.h | 49 + source-code/psycopg2/psycopg/lobject.h | 102 + source-code/psycopg2/psycopg/lobject_int.c | 486 ++++ source-code/psycopg2/psycopg/lobject_type.c | 471 ++++ source-code/psycopg2/psycopg/microprotocols.c | 277 +++ source-code/psycopg2/psycopg/microprotocols.h | 64 + .../psycopg2/psycopg/microprotocols_proto.c | 180 ++ .../psycopg2/psycopg/microprotocols_proto.h | 47 + source-code/psycopg2/psycopg/notify.h | 41 + source-code/psycopg2/psycopg/notify_type.c | 298 +++ source-code/psycopg2/psycopg/pgtypes.h | 65 + source-code/psycopg2/psycopg/pqpath.c | 1835 ++++++++++++++ source-code/psycopg2/psycopg/pqpath.h | 74 + source-code/psycopg2/psycopg/psycopg.h | 107 + source-code/psycopg2/psycopg/psycopgmodule.c | 1035 ++++++++ source-code/psycopg2/psycopg/python.h | 99 + .../psycopg2/psycopg/replication_connection.h | 53 + .../psycopg/replication_connection_type.c | 198 ++ .../psycopg2/psycopg/replication_cursor.h | 66 + .../psycopg/replication_cursor_type.c | 402 ++++ .../psycopg2/psycopg/replication_message.h | 58 + .../psycopg/replication_message_type.c | 195 ++ .../psycopg2/psycopg/solaris_support.c | 58 + .../psycopg2/psycopg/solaris_support.h | 48 + .../psycopg2/psycopg/sqlstate_errors.h | 337 +++ source-code/psycopg2/psycopg/typecast.c | 620 +++++ source-code/psycopg2/psycopg/typecast.h | 91 + source-code/psycopg2/psycopg/typecast_array.c | 298 +++ source-code/psycopg2/psycopg/typecast_basic.c | 150 ++ .../psycopg2/psycopg/typecast_binary.c | 275 +++ .../psycopg2/psycopg/typecast_binary.h | 50 + .../psycopg2/psycopg/typecast_builtins.c | 71 + .../psycopg2/psycopg/typecast_datetime.c | 463 ++++ source-code/psycopg2/psycopg/utils.c | 456 ++++ source-code/psycopg2/psycopg/utils.h | 65 + source-code/psycopg2/psycopg/win32_support.c | 90 + source-code/psycopg2/psycopg/win32_support.h | 56 + source-code/psycopg2/psycopg/xid.h | 52 + source-code/psycopg2/psycopg/xid_type.c | 665 ++++++ source-code/psycopg2/psycopg1.py | 96 - .../psycopg2_binary.egg-info/PKG-INFO | 112 + .../psycopg2_binary.egg-info/SOURCES.txt | 178 ++ .../dependency_links.txt | 1 + .../psycopg2_binary.egg-info/top_level.txt | 1 + 98 files changed, 22545 insertions(+), 600 deletions(-) delete mode 100644 source-code/psycopg2/LICENSE delete mode 100755 source-code/psycopg2/_psycopg.cpython-37m-x86_64-linux-gnu.so rename source-code/psycopg2/{ => lib}/__init__.py (87%) mode change 100755 => 100644 rename source-code/psycopg2/{ => lib}/_ipaddress.py (96%) mode change 100755 => 100644 rename source-code/psycopg2/{ => lib}/_json.py (79%) mode change 100755 => 100644 rename source-code/psycopg2/{ => lib}/_range.py (85%) mode change 100755 => 100644 rename source-code/psycopg2/{ => lib}/errorcodes.py (89%) mode change 100755 => 100644 create mode 100644 source-code/psycopg2/lib/errors.py rename source-code/psycopg2/{ => lib}/extensions.py (82%) mode change 100755 => 100644 rename source-code/psycopg2/{ => lib}/extras.py (76%) mode change 100755 => 100644 rename source-code/psycopg2/{ => lib}/pool.py (72%) mode change 100755 => 100644 rename source-code/psycopg2/{ => lib}/sql.py (79%) mode change 100755 => 100644 rename source-code/psycopg2/{ => lib}/tz.py (74%) mode change 100755 => 100644 create mode 100644 source-code/psycopg2/psycopg/_psycopg.vc9.amd64.manifest create mode 100644 source-code/psycopg2/psycopg/_psycopg.vc9.x86.manifest create mode 100644 source-code/psycopg2/psycopg/adapter_asis.c create mode 100644 source-code/psycopg2/psycopg/adapter_asis.h create mode 100644 source-code/psycopg2/psycopg/adapter_binary.c create mode 100644 source-code/psycopg2/psycopg/adapter_binary.h create mode 100644 source-code/psycopg2/psycopg/adapter_datetime.c create mode 100644 source-code/psycopg2/psycopg/adapter_datetime.h create mode 100644 source-code/psycopg2/psycopg/adapter_list.c create mode 100644 source-code/psycopg2/psycopg/adapter_list.h create mode 100644 source-code/psycopg2/psycopg/adapter_pboolean.c create mode 100644 source-code/psycopg2/psycopg/adapter_pboolean.h create mode 100644 source-code/psycopg2/psycopg/adapter_pdecimal.c create mode 100644 source-code/psycopg2/psycopg/adapter_pdecimal.h create mode 100644 source-code/psycopg2/psycopg/adapter_pfloat.c create mode 100644 source-code/psycopg2/psycopg/adapter_pfloat.h create mode 100644 source-code/psycopg2/psycopg/adapter_pint.c create mode 100644 source-code/psycopg2/psycopg/adapter_pint.h create mode 100644 source-code/psycopg2/psycopg/adapter_qstring.c create mode 100644 source-code/psycopg2/psycopg/adapter_qstring.h create mode 100644 source-code/psycopg2/psycopg/aix_support.c create mode 100644 source-code/psycopg2/psycopg/aix_support.h create mode 100644 source-code/psycopg2/psycopg/bytes_format.c create mode 100644 source-code/psycopg2/psycopg/column.h create mode 100644 source-code/psycopg2/psycopg/column_type.c create mode 100644 source-code/psycopg2/psycopg/config.h create mode 100644 source-code/psycopg2/psycopg/connection.h create mode 100644 source-code/psycopg2/psycopg/connection_int.c create mode 100644 source-code/psycopg2/psycopg/connection_type.c create mode 100644 source-code/psycopg2/psycopg/conninfo.h create mode 100644 source-code/psycopg2/psycopg/conninfo_type.c create mode 100644 source-code/psycopg2/psycopg/cursor.h create mode 100644 source-code/psycopg2/psycopg/cursor_int.c create mode 100644 source-code/psycopg2/psycopg/cursor_type.c create mode 100644 source-code/psycopg2/psycopg/diagnostics.h create mode 100644 source-code/psycopg2/psycopg/diagnostics_type.c create mode 100644 source-code/psycopg2/psycopg/error.h create mode 100644 source-code/psycopg2/psycopg/error_type.c create mode 100644 source-code/psycopg2/psycopg/green.c create mode 100644 source-code/psycopg2/psycopg/green.h create mode 100644 source-code/psycopg2/psycopg/libpq_support.c create mode 100644 source-code/psycopg2/psycopg/libpq_support.h create mode 100644 source-code/psycopg2/psycopg/lobject.h create mode 100644 source-code/psycopg2/psycopg/lobject_int.c create mode 100644 source-code/psycopg2/psycopg/lobject_type.c create mode 100644 source-code/psycopg2/psycopg/microprotocols.c create mode 100644 source-code/psycopg2/psycopg/microprotocols.h create mode 100644 source-code/psycopg2/psycopg/microprotocols_proto.c create mode 100644 source-code/psycopg2/psycopg/microprotocols_proto.h create mode 100644 source-code/psycopg2/psycopg/notify.h create mode 100644 source-code/psycopg2/psycopg/notify_type.c create mode 100644 source-code/psycopg2/psycopg/pgtypes.h create mode 100644 source-code/psycopg2/psycopg/pqpath.c create mode 100644 source-code/psycopg2/psycopg/pqpath.h create mode 100644 source-code/psycopg2/psycopg/psycopg.h create mode 100644 source-code/psycopg2/psycopg/psycopgmodule.c create mode 100644 source-code/psycopg2/psycopg/python.h create mode 100644 source-code/psycopg2/psycopg/replication_connection.h create mode 100644 source-code/psycopg2/psycopg/replication_connection_type.c create mode 100644 source-code/psycopg2/psycopg/replication_cursor.h create mode 100644 source-code/psycopg2/psycopg/replication_cursor_type.c create mode 100644 source-code/psycopg2/psycopg/replication_message.h create mode 100644 source-code/psycopg2/psycopg/replication_message_type.c create mode 100644 source-code/psycopg2/psycopg/solaris_support.c create mode 100644 source-code/psycopg2/psycopg/solaris_support.h create mode 100644 source-code/psycopg2/psycopg/sqlstate_errors.h create mode 100644 source-code/psycopg2/psycopg/typecast.c create mode 100644 source-code/psycopg2/psycopg/typecast.h create mode 100644 source-code/psycopg2/psycopg/typecast_array.c create mode 100644 source-code/psycopg2/psycopg/typecast_basic.c create mode 100644 source-code/psycopg2/psycopg/typecast_binary.c create mode 100644 source-code/psycopg2/psycopg/typecast_binary.h create mode 100644 source-code/psycopg2/psycopg/typecast_builtins.c create mode 100644 source-code/psycopg2/psycopg/typecast_datetime.c create mode 100644 source-code/psycopg2/psycopg/utils.c create mode 100644 source-code/psycopg2/psycopg/utils.h create mode 100644 source-code/psycopg2/psycopg/win32_support.c create mode 100644 source-code/psycopg2/psycopg/win32_support.h create mode 100644 source-code/psycopg2/psycopg/xid.h create mode 100644 source-code/psycopg2/psycopg/xid_type.c delete mode 100755 source-code/psycopg2/psycopg1.py create mode 100644 source-code/psycopg2/psycopg2_binary.egg-info/PKG-INFO create mode 100644 source-code/psycopg2/psycopg2_binary.egg-info/SOURCES.txt create mode 100644 source-code/psycopg2/psycopg2_binary.egg-info/dependency_links.txt create mode 100644 source-code/psycopg2/psycopg2_binary.egg-info/top_level.txt diff --git a/source-code/psycopg2/LICENSE b/source-code/psycopg2/LICENSE deleted file mode 100644 index 9029e70..0000000 --- a/source-code/psycopg2/LICENSE +++ /dev/null @@ -1,49 +0,0 @@ -psycopg2 and the LGPL ---------------------- - -psycopg2 is free software: you can redistribute it and/or modify it -under the terms of the GNU Lesser General Public License as published -by the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -psycopg2 is distributed in the hope that it will be useful, but WITHOUT -ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -License for more details. - -In addition, as a special exception, the copyright holders give -permission to link this program with the OpenSSL library (or with -modified versions of OpenSSL that use the same license as OpenSSL), -and distribute linked combinations including the two. - -You must obey the GNU Lesser General Public License in all respects for -all of the code used other than OpenSSL. If you modify file(s) with this -exception, you may extend this exception to your version of the file(s), -but you are not obligated to do so. If you do not wish to do so, delete -this exception statement from your version. If you delete this exception -statement from all source files in the program, then also delete it here. - -You should have received a copy of the GNU Lesser General Public License -along with psycopg2 (see the doc/ directory.) -If not, see . - - -Alternative licenses --------------------- - -The following BSD-like license applies (at your option) to the files following -the pattern ``psycopg/adapter*.{h,c}`` and ``psycopg/microprotocol*.{h,c}``: - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this - software in a product, an acknowledgment in the product documentation - would be appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not - be misrepresented as being the original software. - - 3. This notice may not be removed or altered from any source distribution. diff --git a/source-code/psycopg2/_psycopg.cpython-37m-x86_64-linux-gnu.so b/source-code/psycopg2/_psycopg.cpython-37m-x86_64-linux-gnu.so deleted file mode 100755 index 613c41982cad0afdadd481f61a1727b4905fbc89..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1046424 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�HUMAbou2VI!yPUK%&ERdBARTm@X}DyE67L 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�+i3|#LdlM`+i@DIE^mt%!5Kw8>s?(}IGleDA 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 Aj{pDw diff --git a/source-code/psycopg2/__init__.py b/source-code/psycopg2/lib/__init__.py old mode 100755 new mode 100644 similarity index 87% rename from source-code/psycopg2/__init__.py rename to source-code/psycopg2/lib/__init__.py index 492b924..59a8938 --- a/source-code/psycopg2/__init__.py +++ b/source-code/psycopg2/lib/__init__.py @@ -6,10 +6,10 @@ candies. Like the original, psycopg 2 was written with the aim of being very small and fast, and stable as a rock. -Homepage: http://initd.org/projects/psycopg2 +Homepage: https://psycopg.org/ -.. _PostgreSQL: http://www.postgresql.org/ -.. _Python: http://www.python.org/ +.. _PostgreSQL: https://www.postgresql.org/ +.. _Python: https://www.python.org/ :Groups: * `Connections creation`: connect @@ -18,7 +18,8 @@ """ # psycopg/__init__.py - initialization of the psycopg module # -# Copyright (C) 2003-2010 Federico Di Gregorio +# Copyright (C) 2003-2019 Federico Di Gregorio +# Copyright (C) 2020-2021 The Psycopg Team # # psycopg2 is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published @@ -43,7 +44,7 @@ # Note: the first internal import should be _psycopg, otherwise the real cause # of a failed loading of the C module may get hidden, see -# http://archives.postgresql.org/psycopg/2011-02/msg00044.php +# https://archives.postgresql.org/psycopg/2011-02/msg00044.php # Import the DBAPI-2.0 stuff into top-level module. @@ -60,26 +61,20 @@ __version__, __libpq_version__, ) -from psycopg2 import tz # noqa - # Register default adapters. -import psycopg2.extensions as _ext +from psycopg2 import extensions as _ext _ext.register_adapter(tuple, _ext.SQL_IN) _ext.register_adapter(type(None), _ext.NoneAdapter) # Register the Decimal adapter here instead of in the C layer. # This way a new class is registered for each sub-interpreter. # See ticket #52 -try: - from decimal import Decimal -except ImportError: - pass -else: - from psycopg2._psycopg import Decimal as Adapter - _ext.register_adapter(Decimal, Adapter) - del Decimal, Adapter +from decimal import Decimal # noqa +from psycopg2._psycopg import Decimal as Adapter # noqa +_ext.register_adapter(Decimal, Adapter) +del Decimal, Adapter def connect(dsn=None, connection_factory=None, cursor_factory=None, **kwargs): @@ -123,9 +118,6 @@ def connect(dsn=None, connection_factory=None, cursor_factory=None, **kwargs): if 'async_' in kwargs: kwasync['async_'] = kwargs.pop('async_') - if dsn is None and not kwargs: - raise TypeError('missing dsn and no parameters') - dsn = _ext.make_dsn(dsn, **kwargs) conn = _connect(dsn, connection_factory=connection_factory, **kwasync) if cursor_factory is not None: diff --git a/source-code/psycopg2/_ipaddress.py b/source-code/psycopg2/lib/_ipaddress.py old mode 100755 new mode 100644 similarity index 96% rename from source-code/psycopg2/_ipaddress.py rename to source-code/psycopg2/lib/_ipaddress.py index f2a6464..d38566c --- a/source-code/psycopg2/_ipaddress.py +++ b/source-code/psycopg2/lib/_ipaddress.py @@ -3,7 +3,8 @@ # psycopg/_ipaddress.py - Ipaddres-based network types adaptation # -# Copyright (C) 2016 Daniele Varrazzo +# Copyright (C) 2016-2019 Daniele Varrazzo +# Copyright (C) 2020-2021 The Psycopg Team # # psycopg2 is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published diff --git a/source-code/psycopg2/_json.py b/source-code/psycopg2/lib/_json.py old mode 100755 new mode 100644 similarity index 79% rename from source-code/psycopg2/_json.py rename to source-code/psycopg2/lib/_json.py index b137a2d..9502422 --- a/source-code/psycopg2/_json.py +++ b/source-code/psycopg2/lib/_json.py @@ -7,7 +7,8 @@ # psycopg/_json.py - Implementation of the JSON adaptation objects # -# Copyright (C) 2012 Daniele Varrazzo +# Copyright (C) 2012-2019 Daniele Varrazzo +# Copyright (C) 2020-2021 The Psycopg Team # # psycopg2 is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published @@ -27,22 +28,12 @@ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. -import sys +import json from psycopg2._psycopg import ISQLQuote, QuotedString from psycopg2._psycopg import new_type, new_array_type, register_type -# import the best json implementation available -if sys.version_info[:2] >= (2, 6): - import json -else: - try: - import simplejson as json - except ImportError: - json = None - - # oids from PostgreSQL 9.2 JSON_OID = 114 JSONARRAY_OID = 199 @@ -52,27 +43,20 @@ JSONBARRAY_OID = 3807 -class Json(object): +class Json: """ An `~psycopg2.extensions.ISQLQuote` wrapper to adapt a Python object to :sql:`json` data type. `!Json` can be used to wrap any object supported by the provided *dumps* - function. If none is provided, the standard :py:func:`json.dumps()` is - used (`!simplejson` for Python < 2.6; - `~psycopg2.extensions.ISQLQuote.getquoted()` will raise `!ImportError` if - the module is not available). + function. If none is provided, the standard :py:func:`json.dumps()` is + used. """ def __init__(self, adapted, dumps=None): self.adapted = adapted - - if dumps is not None: - self._dumps = dumps - elif json is not None: - self._dumps = json.dumps - else: - self._dumps = None + self._conn = None + self._dumps = dumps or json.dumps def __conform__(self, proto): if proto is ISQLQuote: @@ -85,25 +69,21 @@ def dumps(self, obj): provided in the constructor. You can override this method to create a customized JSON wrapper. """ - dumps = self._dumps - if dumps is not None: - return dumps(obj) - else: - raise ImportError( - "json module not available: " - "you should provide a dumps function") + return self._dumps(obj) + + def prepare(self, conn): + self._conn = conn def getquoted(self): s = self.dumps(self.adapted) - return QuotedString(s).getquoted() + qs = QuotedString(s) + if self._conn is not None: + qs.prepare(self._conn) + return qs.getquoted() - if sys.version_info < (3,): - def __str__(self): - return self.getquoted() - else: - def __str__(self): - # getquoted is binary in Py3 - return self.getquoted().decode('ascii', 'replace') + def __str__(self): + # getquoted is binary + return self.getquoted().decode('ascii', 'replace') def register_json(conn_or_curs=None, globally=False, loads=None, @@ -174,10 +154,7 @@ def register_default_jsonb(conn_or_curs=None, globally=False, loads=None): def _create_json_typecasters(oid, array_oid, loads=None, name='JSON'): """Create typecasters for json data type.""" if loads is None: - if json is None: - raise ImportError("no json module available") - else: - loads = json.loads + loads = json.loads def typecast_json(s, cur): if s is None: @@ -186,7 +163,7 @@ def typecast_json(s, cur): JSON = new_type((oid, ), name, typecast_json) if array_oid is not None: - JSONARRAY = new_array_type((array_oid, ), "%sARRAY" % name, JSON) + JSONARRAY = new_array_type((array_oid, ), f"{name}ARRAY", JSON) else: JSONARRAY = None @@ -204,7 +181,7 @@ def _get_json_oids(conn_or_curs, name='json'): conn_status = conn.status # column typarray not available before PG 8.3 - typarray = conn.server_version >= 80300 and "typarray" or "NULL" + typarray = conn.info.server_version >= 80300 and "typarray" or "NULL" # get the oid for the hstore curs.execute( @@ -213,10 +190,10 @@ def _get_json_oids(conn_or_curs, name='json'): r = curs.fetchone() # revert the status of the connection as before the command - if (conn_status != STATUS_IN_TRANSACTION and not conn.autocommit): + if conn_status != STATUS_IN_TRANSACTION and not conn.autocommit: conn.rollback() if not r: - raise conn.ProgrammingError("%s data type not found" % name) + raise conn.ProgrammingError(f"{name} data type not found") return r diff --git a/source-code/psycopg2/_range.py b/source-code/psycopg2/lib/_range.py old mode 100755 new mode 100644 similarity index 85% rename from source-code/psycopg2/_range.py rename to source-code/psycopg2/lib/_range.py index eadc657..64bae07 --- a/source-code/psycopg2/_range.py +++ b/source-code/psycopg2/lib/_range.py @@ -4,7 +4,8 @@ # psycopg/_range.py - Implementation of the Range type and adaptation # -# Copyright (C) 2012 Daniele Varrazzo +# Copyright (C) 2012-2019 Daniele Varrazzo +# Copyright (C) 2020-2021 The Psycopg Team # # psycopg2 is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published @@ -31,7 +32,7 @@ from psycopg2.extensions import new_type, new_array_type, register_type -class Range(object): +class Range: """Python representation for a PostgreSQL |range|_ type. :param lower: lower bound for the range. `!None` means unbound @@ -46,7 +47,7 @@ class Range(object): def __init__(self, lower=None, upper=None, bounds='[)', empty=False): if not empty: if bounds not in ('[)', '(]', '()', '[]'): - raise ValueError("bound flags not valid: %r" % bounds) + raise ValueError(f"bound flags not valid: {bounds!r}") self._lower = lower self._upper = upper @@ -56,11 +57,24 @@ def __init__(self, lower=None, upper=None, bounds='[)', empty=False): def __repr__(self): if self._bounds is None: - return "%s(empty=True)" % self.__class__.__name__ + return f"{self.__class__.__name__}(empty=True)" else: - return "%s(%r, %r, %r)" % (self.__class__.__name__, + return "{}({!r}, {!r}, {!r})".format(self.__class__.__name__, self._lower, self._upper, self._bounds) + def __str__(self): + if self._bounds is None: + return 'empty' + + items = [ + self._bounds[0], + str(self._lower), + ', ', + str(self._upper), + self._bounds[1] + ] + return ''.join(items) + @property def lower(self): """The lower bound of the range. `!None` if empty or unbound.""" @@ -181,14 +195,11 @@ def __ge__(self, other): return self.__gt__(other) def __getstate__(self): - return dict( - (slot, getattr(self, slot)) - for slot in self.__slots__ - if hasattr(self, slot) - ) + return {slot: getattr(self, slot) + for slot in self.__slots__ if hasattr(self, slot)} def __setstate__(self, state): - for slot, value in list(state.items()): + for slot, value in state.items(): setattr(self, slot, value) @@ -223,7 +234,7 @@ def register_range(pgrange, pyrange, conn_or_curs, globally=False): return caster -class RangeAdapter(object): +class RangeAdapter: """`ISQLQuote` adapter for `Range` subclasses. This is an abstract class: concrete classes must set a `name` class @@ -271,7 +282,7 @@ def getquoted(self): + b", '" + r._bounds.encode('utf8') + b"')" -class RangeCaster(object): +class RangeCaster: """Helper class to convert between `Range` and PostgreSQL range types. Objects of this class are usually created by `register_range()`. Manual @@ -337,9 +348,9 @@ def _from_db(self, name, pyrange, conn_or_curs): from psycopg2.extras import _solve_conn_curs conn, curs = _solve_conn_curs(conn_or_curs) - if conn.server_version < 90200: + if conn.info.server_version < 90200: raise ProgrammingError("range types not available in version %s" - % conn.server_version) + % conn.info.server_version) # Store the transaction status of the connection to revert it after use conn_status = conn.status @@ -352,33 +363,54 @@ def _from_db(self, name, pyrange, conn_or_curs): schema = 'public' # get the type oid and attributes - try: - curs.execute("""\ -select rngtypid, rngsubtype, - (select typarray from pg_type where oid = rngtypid) + curs.execute("""\ +select rngtypid, rngsubtype, typarray from pg_range r join pg_type t on t.oid = rngtypid join pg_namespace ns on ns.oid = typnamespace where typname = %s and ns.nspname = %s; """, (tname, schema)) + rec = curs.fetchone() - except ProgrammingError: - if not conn.autocommit: - conn.rollback() - raise - else: - rec = curs.fetchone() + if not rec: + # The above algorithm doesn't work for customized seach_path + # (#1487) The implementation below works better, but, to guarantee + # backwards compatibility, use it only if the original one failed. + try: + savepoint = False + # Because we executed statements earlier, we are either INTRANS + # or we are IDLE only if the transaction is autocommit, in + # which case we don't need the savepoint anyway. + if conn.status == STATUS_IN_TRANSACTION: + curs.execute("SAVEPOINT register_type") + savepoint = True + + curs.execute("""\ +SELECT rngtypid, rngsubtype, typarray, typname, nspname +from pg_range r +join pg_type t on t.oid = rngtypid +join pg_namespace ns on ns.oid = typnamespace +WHERE t.oid = %s::regtype +""", (name, )) + except ProgrammingError: + pass + else: + rec = curs.fetchone() + if rec: + tname, schema = rec[3:] + finally: + if savepoint: + curs.execute("ROLLBACK TO SAVEPOINT register_type") - # revert the status of the connection as before the command - if (conn_status != STATUS_IN_TRANSACTION - and not conn.autocommit): - conn.rollback() + # revert the status of the connection as before the command + if conn_status != STATUS_IN_TRANSACTION and not conn.autocommit: + conn.rollback() if not rec: raise ProgrammingError( - "PostgreSQL type '%s' not found" % name) + f"PostgreSQL range '{name}' not found") - type, subtype, array = rec + type, subtype, array = rec[:3] return RangeCaster(name, pyrange, oid=type, subtype_oid=subtype, array_oid=array) @@ -408,7 +440,7 @@ def parse(self, s, cur=None): m = self._re_range.match(s) if m is None: - raise InterfaceError("failed to parse range: '%s'" % s) + raise InterfaceError(f"failed to parse range: '{s}'") lower = m.group(3) if lower is None: @@ -488,13 +520,12 @@ def getquoted(self): else: upper = '' - return ("'%s%s,%s%s'" % ( - r._bounds[0], lower, upper, r._bounds[1])).encode('ascii') + return (f"'{r._bounds[0]}{lower},{upper}{r._bounds[1]}'").encode('ascii') + # TODO: probably won't work with infs, nans and other tricky cases. register_adapter(NumericRange, NumberRangeAdapter) - # Register globally typecasters and adapters for builtin range types. # note: the adapter is registered more than once, but this is harmless. diff --git a/source-code/psycopg2/errorcodes.py b/source-code/psycopg2/lib/errorcodes.py old mode 100755 new mode 100644 similarity index 89% rename from source-code/psycopg2/errorcodes.py rename to source-code/psycopg2/lib/errorcodes.py index 6b3e6d8..aa646c4 --- a/source-code/psycopg2/errorcodes.py +++ b/source-code/psycopg2/lib/errorcodes.py @@ -1,10 +1,11 @@ -"""Error codes for PostgresSQL +"""Error codes for PostgreSQL This module contains symbolic names for all PostgreSQL error codes. """ # psycopg2/errorcodes.py - PostgreSQL error codes # -# Copyright (C) 2006-2010 Johan Dahlin +# Copyright (C) 2006-2019 Johan Dahlin +# Copyright (C) 2020-2021 The Psycopg Team # # psycopg2 is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published @@ -26,7 +27,7 @@ # # Based on: # -# http://www.postgresql.org/docs/current/static/errcodes-appendix.html +# https://www.postgresql.org/docs/current/static/errcodes-appendix.html # @@ -42,7 +43,8 @@ def lookup(code, _cache={}): tmp = {} for k, v in globals().items(): if isinstance(v, str) and len(v) in (2, 5): - tmp[v] = k + # Strip trailing underscore used to disambiguate duplicate values + tmp[v] = k.rstrip("_") assert tmp @@ -93,6 +95,7 @@ def lookup(code, _cache={}): CLASS_OBJECT_NOT_IN_PREREQUISITE_STATE = '55' CLASS_OPERATOR_INTERVENTION = '57' CLASS_SYSTEM_ERROR = '58' +CLASS_SNAPSHOT_FAILURE = '72' CLASS_CONFIGURATION_FILE_ERROR = 'F0' CLASS_FOREIGN_DATA_WRAPPER_ERROR = 'HV' CLASS_PL_PGSQL_ERROR = 'P0' @@ -104,7 +107,7 @@ def lookup(code, _cache={}): # Class 01 - Warning WARNING = '01000' NULL_VALUE_ELIMINATED_IN_SET_FUNCTION = '01003' -STRING_DATA_RIGHT_TRUNCATION = '01004' +STRING_DATA_RIGHT_TRUNCATION_ = '01004' PRIVILEGE_NOT_REVOKED = '01006' PRIVILEGE_NOT_GRANTED = '01007' IMPLICIT_ZERO_BIT_PADDING = '01008' @@ -162,7 +165,7 @@ def lookup(code, _cache={}): STRING_DATA_RIGHT_TRUNCATION = '22001' NULL_VALUE_NO_INDICATOR_PARAMETER = '22002' NUMERIC_VALUE_OUT_OF_RANGE = '22003' -NULL_VALUE_NOT_ALLOWED = '22004' +NULL_VALUE_NOT_ALLOWED_ = '22004' ERROR_IN_ASSIGNMENT = '22005' INVALID_DATETIME_FORMAT = '22007' DATETIME_FIELD_OVERFLOW = '22008' @@ -172,6 +175,7 @@ def lookup(code, _cache={}): INVALID_ESCAPE_OCTET = '2200D' ZERO_LENGTH_CHARACTER_STRING = '2200F' MOST_SPECIFIC_TYPE_MISMATCH = '2200G' +SEQUENCE_GENERATOR_LIMIT_EXCEEDED = '2200H' NOT_AN_XML_DOCUMENT = '2200L' INVALID_XML_DOCUMENT = '2200M' INVALID_XML_CONTENT = '2200N' @@ -180,6 +184,7 @@ def lookup(code, _cache={}): INVALID_INDICATOR_PARAMETER_VALUE = '22010' SUBSTRING_ERROR = '22011' DIVISION_BY_ZERO = '22012' +INVALID_PRECEDING_OR_FOLLOWING_SIZE = '22013' INVALID_ARGUMENT_FOR_NTILE_FUNCTION = '22014' INTERVAL_FIELD_OVERFLOW = '22015' INVALID_ARGUMENT_FOR_NTH_VALUE_FUNCTION = '22016' @@ -202,6 +207,23 @@ def lookup(code, _cache={}): ARRAY_SUBSCRIPT_ERROR = '2202E' INVALID_TABLESAMPLE_REPEAT = '2202G' INVALID_TABLESAMPLE_ARGUMENT = '2202H' +DUPLICATE_JSON_OBJECT_KEY_VALUE = '22030' +INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION = '22031' +INVALID_JSON_TEXT = '22032' +INVALID_SQL_JSON_SUBSCRIPT = '22033' +MORE_THAN_ONE_SQL_JSON_ITEM = '22034' +NO_SQL_JSON_ITEM = '22035' +NON_NUMERIC_SQL_JSON_ITEM = '22036' +NON_UNIQUE_KEYS_IN_A_JSON_OBJECT = '22037' +SINGLETON_SQL_JSON_ITEM_REQUIRED = '22038' +SQL_JSON_ARRAY_NOT_FOUND = '22039' +SQL_JSON_MEMBER_NOT_FOUND = '2203A' +SQL_JSON_NUMBER_NOT_FOUND = '2203B' +SQL_JSON_OBJECT_NOT_FOUND = '2203C' +TOO_MANY_JSON_ARRAY_ELEMENTS = '2203D' +TOO_MANY_JSON_OBJECT_MEMBERS = '2203E' +SQL_JSON_SCALAR_REQUIRED = '2203F' +SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE = '2203G' FLOATING_POINT_EXCEPTION = '22P01' INVALID_TEXT_REPRESENTATION = '22P02' INVALID_BINARY_REPRESENTATION = '22P03' @@ -233,6 +255,7 @@ def lookup(code, _cache={}): HELD_CURSOR_REQUIRES_SAME_ISOLATION_LEVEL = '25008' NO_ACTIVE_SQL_TRANSACTION = '25P01' IN_FAILED_SQL_TRANSACTION = '25P02' +IDLE_IN_TRANSACTION_SESSION_TIMEOUT = '25P03' # Class 26 - Invalid SQL Statement Name INVALID_SQL_STATEMENT_NAME = '26000' @@ -253,9 +276,9 @@ def lookup(code, _cache={}): # Class 2F - SQL Routine Exception SQL_ROUTINE_EXCEPTION = '2F000' -MODIFYING_SQL_DATA_NOT_PERMITTED = '2F002' -PROHIBITED_SQL_STATEMENT_ATTEMPTED = '2F003' -READING_SQL_DATA_NOT_PERMITTED = '2F004' +MODIFYING_SQL_DATA_NOT_PERMITTED_ = '2F002' +PROHIBITED_SQL_STATEMENT_ATTEMPTED_ = '2F003' +READING_SQL_DATA_NOT_PERMITTED_ = '2F004' FUNCTION_EXECUTED_NO_RETURN_STATEMENT = '2F005' # Class 34 - Invalid Cursor Name @@ -314,6 +337,7 @@ def lookup(code, _cache={}): INVALID_FOREIGN_KEY = '42830' CANNOT_COERCE = '42846' UNDEFINED_FUNCTION = '42883' +GENERATED_ALWAYS = '428C9' RESERVED_NAME = '42939' UNDEFINED_TABLE = '42P01' UNDEFINED_PARAMETER = '42P02' @@ -359,6 +383,7 @@ def lookup(code, _cache={}): OBJECT_IN_USE = '55006' CANT_CHANGE_RUNTIME_PARAM = '55P02' LOCK_NOT_AVAILABLE = '55P03' +UNSAFE_NEW_ENUM_VALUE_USAGE = '55P04' # Class 57 - Operator Intervention OPERATOR_INTERVENTION = '57000' @@ -367,6 +392,7 @@ def lookup(code, _cache={}): CRASH_SHUTDOWN = '57P02' CANNOT_CONNECT_NOW = '57P03' DATABASE_DROPPED = '57P04' +IDLE_SESSION_TIMEOUT = '57P05' # Class 58 - System Error (errors external to PostgreSQL itself) SYSTEM_ERROR = '58000' @@ -374,6 +400,9 @@ def lookup(code, _cache={}): UNDEFINED_FILE = '58P01' DUPLICATE_FILE = '58P02' +# Class 72 - Snapshot Failure +SNAPSHOT_TOO_OLD = '72000' + # Class F0 - Configuration File Error CONFIG_FILE_ERROR = 'F0000' LOCK_FILE_EXISTS = 'F0001' diff --git a/source-code/psycopg2/lib/errors.py b/source-code/psycopg2/lib/errors.py new file mode 100644 index 0000000..e4e47f5 --- /dev/null +++ b/source-code/psycopg2/lib/errors.py @@ -0,0 +1,38 @@ +"""Error classes for PostgreSQL error codes +""" + +# psycopg/errors.py - SQLSTATE and DB-API exceptions +# +# Copyright (C) 2018-2019 Daniele Varrazzo +# Copyright (C) 2020-2021 The Psycopg Team +# +# psycopg2 is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# In addition, as a special exception, the copyright holders give +# permission to link this program with the OpenSSL library (or with +# modified versions of OpenSSL that use the same license as OpenSSL), +# and distribute linked combinations including the two. +# +# You must obey the GNU Lesser General Public License in all respects for +# all of the code used other than OpenSSL. +# +# psycopg2 is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +# License for more details. + +# +# NOTE: the exceptions are injected into this module by the C extention. +# + + +def lookup(code): + """Lookup an error code and return its exception class. + + Raise `!KeyError` if the code is not found. + """ + from psycopg2._psycopg import sqlstate_errors # avoid circular import + return sqlstate_errors[code] diff --git a/source-code/psycopg2/extensions.py b/source-code/psycopg2/lib/extensions.py old mode 100755 new mode 100644 similarity index 82% rename from source-code/psycopg2/extensions.py rename to source-code/psycopg2/lib/extensions.py index 0375d91..b938d0c --- a/source-code/psycopg2/extensions.py +++ b/source-code/psycopg2/lib/extensions.py @@ -8,11 +8,12 @@ - `adapt()` -- exposes the PEP-246_ compatible adapting mechanism used by psycopg to adapt Python types to PostgreSQL ones -.. _PEP-246: http://www.python.org/peps/pep-0246.html +.. _PEP-246: https://www.python.org/dev/peps/pep-0246/ """ # psycopg/extensions.py - DBAPI-2.0 extensions specific to psycopg # -# Copyright (C) 2003-2010 Federico Di Gregorio +# Copyright (C) 2003-2019 Federico Di Gregorio +# Copyright (C) 2020-2021 The Psycopg Team # # psycopg2 is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published @@ -35,35 +36,24 @@ import re as _re from psycopg2._psycopg import ( # noqa - BINARYARRAY, BOOLEAN, BOOLEANARRAY, DATE, DATEARRAY, DATETIMEARRAY, - DECIMAL, DECIMALARRAY, FLOAT, FLOATARRAY, INTEGER, INTEGERARRAY, - INTERVAL, INTERVALARRAY, LONGINTEGER, LONGINTEGERARRAY, ROWIDARRAY, - STRINGARRAY, TIME, TIMEARRAY, UNICODE, UNICODEARRAY, + BINARYARRAY, BOOLEAN, BOOLEANARRAY, BYTES, BYTESARRAY, DATE, DATEARRAY, + DATETIMEARRAY, DECIMAL, DECIMALARRAY, FLOAT, FLOATARRAY, INTEGER, + INTEGERARRAY, INTERVAL, INTERVALARRAY, LONGINTEGER, LONGINTEGERARRAY, + ROWIDARRAY, STRINGARRAY, TIME, TIMEARRAY, UNICODE, UNICODEARRAY, AsIs, Binary, Boolean, Float, Int, QuotedString, ) -try: - from psycopg2._psycopg import ( # noqa - MXDATE, MXDATETIME, MXINTERVAL, MXTIME, - MXDATEARRAY, MXDATETIMEARRAY, MXINTERVALARRAY, MXTIMEARRAY, - DateFromMx, TimeFromMx, TimestampFromMx, IntervalFromMx, ) -except ImportError: - pass - -try: - from psycopg2._psycopg import ( # noqa - PYDATE, PYDATETIME, PYINTERVAL, PYTIME, - PYDATEARRAY, PYDATETIMEARRAY, PYINTERVALARRAY, PYTIMEARRAY, - DateFromPy, TimeFromPy, TimestampFromPy, IntervalFromPy, ) -except ImportError: - pass +from psycopg2._psycopg import ( # noqa + PYDATE, PYDATETIME, PYDATETIMETZ, PYINTERVAL, PYTIME, PYDATEARRAY, + PYDATETIMEARRAY, PYDATETIMETZARRAY, PYINTERVALARRAY, PYTIMEARRAY, + DateFromPy, TimeFromPy, TimestampFromPy, IntervalFromPy, ) from psycopg2._psycopg import ( # noqa adapt, adapters, encodings, connection, cursor, lobject, Xid, libpq_version, parse_dsn, quote_ident, string_types, binary_types, new_type, new_array_type, register_type, - ISQLQuote, Notify, Diagnostics, Column, + ISQLQuote, Notify, Diagnostics, Column, ConnectionInfo, QueryCanceledError, TransactionRollbackError, - set_wait_callback, get_wait_callback, ) + set_wait_callback, get_wait_callback, encrypt_password, ) """Isolation level values.""" @@ -108,7 +98,7 @@ def register_adapter(typ, callable): # The SQL_IN class is the official adapter for tuples starting from 2.0.6. -class SQL_IN(object): +class SQL_IN: """Adapt any iterable to an SQL quotable object.""" def __init__(self, seq): self._seq = seq @@ -132,7 +122,7 @@ def __str__(self): return str(self.getquoted()) -class NoneAdapter(object): +class NoneAdapter: """Adapt None to NULL. This adapter is not used normally as a fast path in mogrify uses NULL, @@ -163,14 +153,14 @@ def make_dsn(dsn=None, **kwargs): kwargs['dbname'] = kwargs.pop('database') # Drop the None arguments - kwargs = dict((k, v) for (k, v) in kwargs.items() if v is not None) + kwargs = {k: v for (k, v) in kwargs.items() if v is not None} if dsn is not None: tmp = parse_dsn(dsn) tmp.update(kwargs) kwargs = tmp - dsn = " ".join(["%s=%s" % (k, _param_escape(str(v))) + dsn = " ".join(["{}={}".format(k, _param_escape(str(v))) for (k, v) in kwargs.items()]) # verify that the returned dsn is valid diff --git a/source-code/psycopg2/extras.py b/source-code/psycopg2/lib/extras.py old mode 100755 new mode 100644 similarity index 76% rename from source-code/psycopg2/extras.py rename to source-code/psycopg2/lib/extras.py index 3ecb4bf..36e8ef9 --- a/source-code/psycopg2/extras.py +++ b/source-code/psycopg2/lib/extras.py @@ -5,7 +5,8 @@ """ # psycopg/extras.py - miscellaneous extra goodies for psycopg # -# Copyright (C) 2003-2010 Federico Di Gregorio +# Copyright (C) 2003-2019 Federico Di Gregorio +# Copyright (C) 2020-2021 The Psycopg Team # # psycopg2 is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published @@ -26,20 +27,18 @@ # License for more details. import os as _os -import sys as _sys import time as _time import re as _re +from collections import namedtuple, OrderedDict -try: - import logging as _logging -except: - _logging = None +import logging as _logging import psycopg2 from psycopg2 import extensions as _ext -from psycopg2.extensions import cursor as _cursor -from psycopg2.extensions import connection as _connection -from psycopg2.extensions import adapt as _A, quote_ident +from .extensions import cursor as _cursor +from .extensions import connection as _connection +from .extensions import adapt as _A, quote_ident +from functools import lru_cache from psycopg2._psycopg import ( # noqa REPLICATION_PHYSICAL, REPLICATION_LOGICAL, @@ -73,51 +72,51 @@ def __init__(self, *args, **kwargs): else: raise NotImplementedError( "DictCursorBase can't be instantiated without a row factory.") - super(DictCursorBase, self).__init__(*args, **kwargs) - self._query_executed = 0 - self._prefetch = 0 + super().__init__(*args, **kwargs) + self._query_executed = False + self._prefetch = False self.row_factory = row_factory def fetchone(self): if self._prefetch: - res = super(DictCursorBase, self).fetchone() + res = super().fetchone() if self._query_executed: self._build_index() if not self._prefetch: - res = super(DictCursorBase, self).fetchone() + res = super().fetchone() return res def fetchmany(self, size=None): if self._prefetch: - res = super(DictCursorBase, self).fetchmany(size) + res = super().fetchmany(size) if self._query_executed: self._build_index() if not self._prefetch: - res = super(DictCursorBase, self).fetchmany(size) + res = super().fetchmany(size) return res def fetchall(self): if self._prefetch: - res = super(DictCursorBase, self).fetchall() + res = super().fetchall() if self._query_executed: self._build_index() if not self._prefetch: - res = super(DictCursorBase, self).fetchall() + res = super().fetchall() return res def __iter__(self): try: if self._prefetch: - res = super(DictCursorBase, self).__iter__() + res = super().__iter__() first = next(res) if self._query_executed: self._build_index() if not self._prefetch: - res = super(DictCursorBase, self).__iter__() + res = super().__iter__() first = next(res) yield first - while 1: + while True: yield next(res) except StopIteration: return @@ -126,33 +125,36 @@ def __iter__(self): class DictConnection(_connection): """A connection that uses `DictCursor` automatically.""" def cursor(self, *args, **kwargs): - kwargs.setdefault('cursor_factory', DictCursor) - return super(DictConnection, self).cursor(*args, **kwargs) + kwargs.setdefault('cursor_factory', self.cursor_factory or DictCursor) + return super().cursor(*args, **kwargs) class DictCursor(DictCursorBase): - """A cursor that keeps a list of column name -> index mappings.""" + """A cursor that keeps a list of column name -> index mappings__. + + .. __: https://docs.python.org/glossary.html#term-mapping + """ def __init__(self, *args, **kwargs): kwargs['row_factory'] = DictRow - super(DictCursor, self).__init__(*args, **kwargs) - self._prefetch = 1 + super().__init__(*args, **kwargs) + self._prefetch = True def execute(self, query, vars=None): - self.index = {} - self._query_executed = 1 - return super(DictCursor, self).execute(query, vars) + self.index = OrderedDict() + self._query_executed = True + return super().execute(query, vars) def callproc(self, procname, vars=None): - self.index = {} - self._query_executed = 1 - return super(DictCursor, self).callproc(procname, vars) + self.index = OrderedDict() + self._query_executed = True + return super().callproc(procname, vars) def _build_index(self): - if self._query_executed == 1 and self.description: + if self._query_executed and self.description: for i in range(len(self.description)): self.index[self.description[i][0]] = i - self._query_executed = 0 + self._query_executed = False class DictRow(list): @@ -167,47 +169,40 @@ def __init__(self, cursor): def __getitem__(self, x): if not isinstance(x, (int, slice)): x = self._index[x] - return list.__getitem__(self, x) + return super().__getitem__(x) def __setitem__(self, x, v): if not isinstance(x, (int, slice)): x = self._index[x] - list.__setitem__(self, x, v) + super().__setitem__(x, v) def items(self): - return list(self.items()) + g = super().__getitem__ + return ((n, g(self._index[n])) for n in self._index) def keys(self): - return list(self._index.keys()) + return iter(self._index) def values(self): - return tuple(self[:]) - - def has_key(self, x): - return x in self._index + g = super().__getitem__ + return (g(self._index[n]) for n in self._index) def get(self, x, default=None): try: return self[x] - except: + except Exception: return default - def iteritems(self): - for n, v in self._index.items(): - yield n, list.__getitem__(self, v) - - def iterkeys(self): - return iter(self._index.keys()) - - def itervalues(self): - return list.__iter__(self) - def copy(self): - return dict(iter(self.items())) + return OrderedDict(self.items()) def __contains__(self, x): return x in self._index + def __reduce__(self): + # this is apparently useless, but it fixes #1073 + return super().__reduce__() + def __getstate__(self): return self[:], self._index.copy() @@ -215,19 +210,12 @@ def __setstate__(self, data): self[:] = data[0] self._index = data[1] - # drop the crusty Py2 methods - if _sys.version_info[0] > 2: - items = iteritems # noqa - keys = iterkeys # noqa - values = itervalues # noqa - del iteritems, iterkeys, itervalues, has_key - class RealDictConnection(_connection): """A connection that uses `RealDictCursor` automatically.""" def cursor(self, *args, **kwargs): - kwargs.setdefault('cursor_factory', RealDictCursor) - return super(RealDictConnection, self).cursor(*args, **kwargs) + kwargs.setdefault('cursor_factory', self.cursor_factory or RealDictCursor) + return super().cursor(*args, **kwargs) class RealDictCursor(DictCursorBase): @@ -240,57 +228,64 @@ class RealDictCursor(DictCursorBase): """ def __init__(self, *args, **kwargs): kwargs['row_factory'] = RealDictRow - super(RealDictCursor, self).__init__(*args, **kwargs) - self._prefetch = 0 + super().__init__(*args, **kwargs) def execute(self, query, vars=None): self.column_mapping = [] - self._query_executed = 1 - return super(RealDictCursor, self).execute(query, vars) + self._query_executed = True + return super().execute(query, vars) def callproc(self, procname, vars=None): self.column_mapping = [] - self._query_executed = 1 - return super(RealDictCursor, self).callproc(procname, vars) + self._query_executed = True + return super().callproc(procname, vars) def _build_index(self): - if self._query_executed == 1 and self.description: - for i in range(len(self.description)): - self.column_mapping.append(self.description[i][0]) - self._query_executed = 0 + if self._query_executed and self.description: + self.column_mapping = [d[0] for d in self.description] + self._query_executed = False -class RealDictRow(dict): +class RealDictRow(OrderedDict): """A `!dict` subclass representing a data record.""" - __slots__ = ('_column_mapping') - - def __init__(self, cursor): - dict.__init__(self) - # Required for named cursors - if cursor.description and not cursor.column_mapping: - cursor._build_index() - - self._column_mapping = cursor.column_mapping - - def __setitem__(self, name, value): - if type(name) == int: - name = self._column_mapping[name] - return dict.__setitem__(self, name, value) - - def __getstate__(self): - return (self.copy(), self._column_mapping[:]) + def __init__(self, *args, **kwargs): + if args and isinstance(args[0], _cursor): + cursor = args[0] + args = args[1:] + else: + cursor = None + + super().__init__(*args, **kwargs) + + if cursor is not None: + # Required for named cursors + if cursor.description and not cursor.column_mapping: + cursor._build_index() + + # Store the cols mapping in the dict itself until the row is fully + # populated, so we don't need to add attributes to the class + # (hence keeping its maintenance, special pickle support, etc.) + self[RealDictRow] = cursor.column_mapping + + def __setitem__(self, key, value): + if RealDictRow in self: + # We are in the row building phase + mapping = self[RealDictRow] + super().__setitem__(mapping[key], value) + if key == len(mapping) - 1: + # Row building finished + del self[RealDictRow] + return - def __setstate__(self, data): - self.update(data[0]) - self._column_mapping = data[1] + super().__setitem__(key, value) class NamedTupleConnection(_connection): """A connection that uses `NamedTupleCursor` automatically.""" def cursor(self, *args, **kwargs): - kwargs.setdefault('cursor_factory', NamedTupleCursor) - return super(NamedTupleConnection, self).cursor(*args, **kwargs) + kwargs.setdefault('cursor_factory', self.cursor_factory or NamedTupleCursor) + return super().cursor(*args, **kwargs) class NamedTupleCursor(_cursor): @@ -310,21 +305,22 @@ class NamedTupleCursor(_cursor): "abc'def" """ Record = None + MAX_CACHE = 1024 def execute(self, query, vars=None): self.Record = None - return super(NamedTupleCursor, self).execute(query, vars) + return super().execute(query, vars) def executemany(self, query, vars): self.Record = None - return super(NamedTupleCursor, self).executemany(query, vars) + return super().executemany(query, vars) def callproc(self, procname, vars=None): self.Record = None - return super(NamedTupleCursor, self).callproc(procname, vars) + return super().callproc(procname, vars) def fetchone(self): - t = super(NamedTupleCursor, self).fetchone() + t = super().fetchone() if t is not None: nt = self.Record if nt is None: @@ -332,14 +328,14 @@ def fetchone(self): return nt._make(t) def fetchmany(self, size=None): - ts = super(NamedTupleCursor, self).fetchmany(size) + ts = super().fetchmany(size) nt = self.Record if nt is None: nt = self.Record = self._make_nt() return list(map(nt._make, ts)) def fetchall(self): - ts = super(NamedTupleCursor, self).fetchall() + ts = super().fetchall() nt = self.Record if nt is None: nt = self.Record = self._make_nt() @@ -347,7 +343,7 @@ def fetchall(self): def __iter__(self): try: - it = super(NamedTupleCursor, self).__iter__() + it = super().__iter__() t = next(it) nt = self.Record @@ -356,35 +352,55 @@ def __iter__(self): yield nt._make(t) - while 1: + while True: yield nt._make(next(it)) except StopIteration: return - try: - from collections import namedtuple - except ImportError as _exc: - def _make_nt(self): - raise self._exc - else: - def _make_nt(self, namedtuple=namedtuple): - return namedtuple("Record", [d[0] for d in self.description or ()]) + def _make_nt(self): + key = tuple(d[0] for d in self.description) if self.description else () + return self._cached_make_nt(key) + + @classmethod + def _do_make_nt(cls, key): + fields = [] + for s in key: + s = _re_clean.sub('_', s) + # Python identifier cannot start with numbers, namedtuple fields + # cannot start with underscore. So... + if s[0] == '_' or '0' <= s[0] <= '9': + s = 'f' + s + fields.append(s) + + nt = namedtuple("Record", fields) + return nt + + +@lru_cache(512) +def _cached_make_nt(cls, key): + return cls._do_make_nt(key) + + +# Exposed for testability, and if someone wants to monkeypatch to tweak +# the cache size. +NamedTupleCursor._cached_make_nt = classmethod(_cached_make_nt) class LoggingConnection(_connection): """A connection that logs all queries to a file or logger__ object. - .. __: http://docs.python.org/library/logging.html + .. __: https://docs.python.org/library/logging.html """ def initialize(self, logobj): """Initialize the connection to log to `!logobj`. - The `!logobj` parameter can be an open file object or a Logger + The `!logobj` parameter can be an open file object or a Logger/LoggerAdapter instance from the standard logging module. """ self._logobj = logobj - if _logging and isinstance(logobj, _logging.Logger): + if _logging and isinstance( + logobj, (_logging.Logger, _logging.LoggerAdapter)): self.log = self._logtologger else: self.log = self._logtofile @@ -401,7 +417,7 @@ def filter(self, msg, curs): def _logtofile(self, msg, curs): msg = self.filter(msg, curs) if msg: - if _sys.version_info[0] >= 3 and isinstance(msg, bytes): + if isinstance(msg, bytes): msg = msg.decode(_ext.encodings[self.encoding], 'replace') self._logobj.write(msg + _os.linesep) @@ -417,8 +433,8 @@ def _check(self): def cursor(self, *args, **kwargs): self._check() - kwargs.setdefault('cursor_factory', LoggingCursor) - return super(LoggingConnection, self).cursor(*args, **kwargs) + kwargs.setdefault('cursor_factory', self.cursor_factory or LoggingCursor) + return super().cursor(*args, **kwargs) class LoggingCursor(_cursor): @@ -426,13 +442,13 @@ class LoggingCursor(_cursor): def execute(self, query, vars=None): try: - return super(LoggingCursor, self).execute(query, vars) + return super().execute(query, vars) finally: self.connection.log(self.query, self) def callproc(self, procname, vars=None): try: - return super(LoggingCursor, self).callproc(procname, vars) + return super().callproc(procname, vars) finally: self.connection.log(self.query, self) @@ -455,10 +471,13 @@ def initialize(self, logobj, mintime=0): def filter(self, msg, curs): t = (_time.time() - curs.timestamp) * 1000 if t > self._mintime: - return msg + _os.linesep + " (execution time: %d ms)" % t + if isinstance(msg, bytes): + msg = msg.decode(_ext.encodings[self.encoding], 'replace') + return f"{msg}{_os.linesep} (execution time: {t} ms)" def cursor(self, *args, **kwargs): - kwargs.setdefault('cursor_factory', MinTimeLoggingCursor) + kwargs.setdefault('cursor_factory', + self.cursor_factory or MinTimeLoggingCursor) return LoggingConnection.cursor(self, *args, **kwargs) @@ -478,14 +497,14 @@ class LogicalReplicationConnection(_replicationConnection): def __init__(self, *args, **kwargs): kwargs['replication_type'] = REPLICATION_LOGICAL - super(LogicalReplicationConnection, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) class PhysicalReplicationConnection(_replicationConnection): def __init__(self, *args, **kwargs): kwargs['replication_type'] = REPLICATION_PHYSICAL - super(PhysicalReplicationConnection, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) class StopReplication(Exception): @@ -506,7 +525,7 @@ class ReplicationCursor(_replicationCursor): def create_replication_slot(self, slot_name, slot_type=None, output_plugin=None): """Create streaming replication slot.""" - command = "CREATE_REPLICATION_SLOT %s " % quote_ident(slot_name, self) + command = f"CREATE_REPLICATION_SLOT {quote_ident(slot_name, self)} " if slot_type is None: slot_type = self.connection.replication_type @@ -517,7 +536,7 @@ def create_replication_slot(self, slot_name, slot_type=None, output_plugin=None) "output plugin name is required to create " "logical replication slot") - command += "LOGICAL %s" % quote_ident(output_plugin, self) + command += f"LOGICAL {quote_ident(output_plugin, self)}" elif slot_type == REPLICATION_PHYSICAL: if output_plugin is not None: @@ -529,18 +548,19 @@ def create_replication_slot(self, slot_name, slot_type=None, output_plugin=None) else: raise psycopg2.ProgrammingError( - "unrecognized replication type: %s" % repr(slot_type)) + f"unrecognized replication type: {repr(slot_type)}") self.execute(command) def drop_replication_slot(self, slot_name): """Drop streaming replication slot.""" - command = "DROP_REPLICATION_SLOT %s" % quote_ident(slot_name, self) + command = f"DROP_REPLICATION_SLOT {quote_ident(slot_name, self)}" self.execute(command) - def start_replication(self, slot_name=None, slot_type=None, start_lsn=0, - timeline=0, options=None, decode=False): + def start_replication( + self, slot_name=None, slot_type=None, start_lsn=0, + timeline=0, options=None, decode=False, status_interval=10): """Start replication stream.""" command = "START_REPLICATION " @@ -550,7 +570,7 @@ def start_replication(self, slot_name=None, slot_type=None, start_lsn=0, if slot_type == REPLICATION_LOGICAL: if slot_name: - command += "SLOT %s " % quote_ident(slot_name, self) + command += f"SLOT {quote_ident(slot_name, self)} " else: raise psycopg2.ProgrammingError( "slot name is required for logical replication") @@ -559,19 +579,18 @@ def start_replication(self, slot_name=None, slot_type=None, start_lsn=0, elif slot_type == REPLICATION_PHYSICAL: if slot_name: - command += "SLOT %s " % quote_ident(slot_name, self) + command += f"SLOT {quote_ident(slot_name, self)} " # don't add "PHYSICAL", before 9.4 it was just START_REPLICATION XXX/XXX else: raise psycopg2.ProgrammingError( - "unrecognized replication type: %s" % repr(slot_type)) + f"unrecognized replication type: {repr(slot_type)}") if type(start_lsn) is str: lsn = start_lsn.split('/') - lsn = "%X/%08X" % (int(lsn[0], 16), int(lsn[1], 16)) + lsn = f"{int(lsn[0], 16):X}/{int(lsn[1], 16):08X}" else: - lsn = "%X/%08X" % ((start_lsn >> 32) & 0xFFFFFFFF, - start_lsn & 0xFFFFFFFF) + lsn = f"{start_lsn >> 32 & 4294967295:X}/{start_lsn & 4294967295:08X}" command += lsn @@ -580,7 +599,7 @@ def start_replication(self, slot_name=None, slot_type=None, start_lsn=0, raise psycopg2.ProgrammingError( "cannot specify timeline for logical replication") - command += " TIMELINE %d" % timeline + command += f" TIMELINE {timeline}" if options: if slot_type == REPLICATION_PHYSICAL: @@ -591,10 +610,11 @@ def start_replication(self, slot_name=None, slot_type=None, start_lsn=0, for k, v in options.items(): if not command.endswith('('): command += ", " - command += "%s %s" % (quote_ident(k, self), _A(str(v))) + command += f"{quote_ident(k, self)} {_A(str(v))}" command += ")" - self.start_replication_expert(command, decode=decode) + self.start_replication_expert( + command, decode=decode, status_interval=status_interval) # allows replication cursors to be used in select.select() directly def fileno(self): @@ -603,11 +623,11 @@ def fileno(self): # a dbtype and adapter for Python UUID type -class UUID_adapter(object): +class UUID_adapter: """Adapt Python's uuid.UUID__ type to PostgreSQL's uuid__. - .. __: http://docs.python.org/library/uuid.html - .. __: http://www.postgresql.org/docs/current/static/datatype-uuid.html + .. __: https://docs.python.org/library/uuid.html + .. __: https://www.postgresql.org/docs/current/static/datatype-uuid.html """ def __init__(self, uuid): @@ -618,10 +638,10 @@ def __conform__(self, proto): return self def getquoted(self): - return ("'%s'::uuid" % self._uuid).encode('utf8') + return (f"'{self._uuid}'::uuid").encode('utf8') def __str__(self): - return "'%s'::uuid" % self._uuid + return f"'{self._uuid}'::uuid" def register_uuid(oids=None, conn_or_curs=None): @@ -658,7 +678,7 @@ def register_uuid(oids=None, conn_or_curs=None): # a type, dbtype and adapter for PostgreSQL inet type -class Inet(object): +class Inet: """Wrap a string to allow for correct SQL-quoting of inet values. Note that this adapter does NOT check the passed value to make @@ -670,7 +690,7 @@ def __init__(self, addr): self.addr = addr def __repr__(self): - return "%s(%r)" % (self.__class__.__name__, self.addr) + return f"{self.__class__.__name__}({self.addr!r})" def prepare(self, conn): self._conn = conn @@ -722,30 +742,18 @@ def register_inet(oid=None, conn_or_curs=None): return _ext.INET -def register_tstz_w_secs(oids=None, conn_or_curs=None): - """The function used to register an alternate type caster for - :sql:`TIMESTAMP WITH TIME ZONE` to deal with historical time zones with - seconds in the UTC offset. - - These are now correctly handled by the default type caster, so currently - the function doesn't do anything. - """ - import warnings - warnings.warn("deprecated", DeprecationWarning) - - def wait_select(conn): """Wait until a connection or cursor has data available. The function is an example of a wait callback to be registered with `~psycopg2.extensions.set_wait_callback()`. This function uses - :py:func:`~select.select()` to wait for data available. - + :py:func:`~select.select()` to wait for data to become available, and + therefore is able to handle/receive SIGINT/KeyboardInterrupt. """ import select from psycopg2.extensions import POLL_OK, POLL_READ, POLL_WRITE - while 1: + while True: try: state = conn.poll() if state == POLL_OK: @@ -755,7 +763,7 @@ def wait_select(conn): elif state == POLL_WRITE: select.select([], [conn.fileno()], []) else: - raise conn.OperationalError("bad state from poll: %s" % state) + raise conn.OperationalError(f"bad state from poll: {state}") except KeyboardInterrupt: conn.cancel() # the loop will be broken by a server error @@ -777,7 +785,7 @@ def _solve_conn_curs(conn_or_curs): return conn, curs -class HstoreAdapter(object): +class HstoreAdapter: """Adapt a Python dict to the hstore syntax.""" def __init__(self, wrapped): self.wrapped = wrapped @@ -786,7 +794,7 @@ def prepare(self, conn): self.conn = conn # use an old-style getquoted implementation if required - if conn.server_version < 90000: + if conn.info.server_version < 90000: self.getquoted = self._getquoted_8 def _getquoted_8(self): @@ -857,7 +865,7 @@ def parse(self, s, cur, _bsdec=_re.compile(r"\\(.)")): for m in self._re_hstore.finditer(s): if m is None or m.start() != start: raise psycopg2.InterfaceError( - "error parsing hstore pair at char %d" % start) + f"error parsing hstore pair at char {start}") k = _bsdec.sub(r'\1', m.group(1)) v = m.group(2) if v is not None: @@ -868,7 +876,7 @@ def parse(self, s, cur, _bsdec=_re.compile(r"\\(.)")): if start < len(s): raise psycopg2.InterfaceError( - "error parsing hstore: unparsed data after char %d" % start) + f"error parsing hstore: unparsed data after char {start}") return rv @@ -891,17 +899,16 @@ def get_oids(self, conn_or_curs): conn_status = conn.status # column typarray not available before PG 8.3 - typarray = conn.server_version >= 80300 and "typarray" or "NULL" + typarray = conn.info.server_version >= 80300 and "typarray" or "NULL" rv0, rv1 = [], [] # get the oid for the hstore - curs.execute("""\ -SELECT t.oid, %s + curs.execute(f"""SELECT t.oid, {typarray} FROM pg_type t JOIN pg_namespace ns ON typnamespace = ns.oid WHERE typname = 'hstore'; -""" % typarray) +""") for oids in curs: rv0.append(oids[0]) rv1.append(oids[1]) @@ -914,7 +921,7 @@ def get_oids(self, conn_or_curs): return tuple(rv0), tuple(rv1) -def register_hstore(conn_or_curs, globally=False, str=False, +def register_hstore(conn_or_curs, globally=False, unicode=False, oid=None, array_oid=None): r"""Register adapter and typecaster for `!dict`\-\ |hstore| conversions. @@ -965,12 +972,7 @@ def register_hstore(conn_or_curs, globally=False, str=False, array_oid = tuple([x for x in array_oid if x]) # create and register the typecaster - if _sys.version_info[0] < 3 and str: - cast = HstoreAdapter.parse_unicode - else: - cast = HstoreAdapter.parse - - HSTORE = _ext.new_type(oid, "HSTORE", cast) + HSTORE = _ext.new_type(oid, "HSTORE", HstoreAdapter.parse) _ext.register_type(HSTORE, not globally and conn_or_curs or None) _ext.register_adapter(dict, HstoreAdapter) @@ -979,7 +981,7 @@ def register_hstore(conn_or_curs, globally=False, str=False, _ext.register_type(HSTOREARRAY, not globally and conn_or_curs or None) -class CompositeCaster(object): +class CompositeCaster: """Helps conversion of a PostgreSQL composite type into a Python object. The class is usually created by the `register_composite()` function. @@ -1000,7 +1002,7 @@ def __init__(self, name, oid, attrs, array_oid=None, schema=None): self.typecaster = _ext.new_type((oid,), name, self.parse) if array_oid: self.array_typecaster = _ext.new_array_type( - (array_oid,), "%sARRAY" % name, self.typecaster) + (array_oid,), f"{name}ARRAY", self.typecaster) else: self.array_typecaster = None @@ -1044,7 +1046,7 @@ def tokenize(self, s): rv = [] for m in self._re_tokenize.finditer(s): if m is None: - raise psycopg2.InterfaceError("can't parse type: %r" % s) + raise psycopg2.InterfaceError(f"can't parse type: {s!r}") if m.group(1) is not None: rv.append(None) elif m.group(2) is not None: @@ -1055,14 +1057,9 @@ def tokenize(self, s): return rv def _create_type(self, name, attnames): - try: - from collections import namedtuple - except ImportError: - self.type = tuple - self._ctor = self.type - else: - self.type = namedtuple(name, attnames) - self._ctor = self.type._make + name = _re_clean.sub('_', name) + self.type = namedtuple(name, attnames) + self._ctor = self.type._make @classmethod def _from_db(self, name, conn_or_curs): @@ -1083,7 +1080,7 @@ def _from_db(self, name, conn_or_curs): schema = 'public' # column typarray not available before PG 8.3 - typarray = conn.server_version >= 80300 and "typarray" or "NULL" + typarray = conn.info.server_version >= 80300 and "typarray" or "NULL" # get the type oid and attributes curs.execute("""\ @@ -1098,14 +1095,46 @@ def _from_db(self, name, conn_or_curs): recs = curs.fetchall() + if not recs: + # The above algorithm doesn't work for customized seach_path + # (#1487) The implementation below works better, but, to guarantee + # backwards compatibility, use it only if the original one failed. + try: + savepoint = False + # Because we executed statements earlier, we are either INTRANS + # or we are IDLE only if the transaction is autocommit, in + # which case we don't need the savepoint anyway. + if conn.status == _ext.STATUS_IN_TRANSACTION: + curs.execute("SAVEPOINT register_type") + savepoint = True + + curs.execute("""\ +SELECT t.oid, %s, attname, atttypid, typname, nspname +FROM pg_type t +JOIN pg_namespace ns ON typnamespace = ns.oid +JOIN pg_attribute a ON attrelid = typrelid +WHERE t.oid = %%s::regtype + AND attnum > 0 AND NOT attisdropped +ORDER BY attnum; +""" % typarray, (name, )) + except psycopg2.ProgrammingError: + pass + else: + recs = curs.fetchall() + if recs: + tname = recs[0][4] + schema = recs[0][5] + finally: + if savepoint: + curs.execute("ROLLBACK TO SAVEPOINT register_type") + # revert the status of the connection as before the command - if (conn_status != _ext.STATUS_IN_TRANSACTION - and not conn.autocommit): + if conn_status != _ext.STATUS_IN_TRANSACTION and not conn.autocommit: conn.rollback() if not recs: raise psycopg2.ProgrammingError( - "PostgreSQL type '%s' not found" % name) + f"PostgreSQL type '{name}' not found") type_oid = recs[0][0] array_oid = recs[0][1] @@ -1150,7 +1179,7 @@ def _paginate(seq, page_size): """ page = [] it = iter(seq) - while 1: + while True: try: for i in range(page_size): page.append(next(it)) @@ -1178,13 +1207,16 @@ def execute_batch(cur, sql, argslist, page_size=100): fewer multi-statement commands, each one containing at most *page_size* statements, resulting in a reduced number of server roundtrips. + After the execution of the function the `cursor.rowcount` property will + **not** contain a total result. + """ for page in _paginate(argslist, page_size=page_size): sqls = [cur.mogrify(sql, args) for args in page] cur.execute(b";".join(sqls)) -def execute_values(cur, sql, argslist, template=None, page_size=100): +def execute_values(cur, sql, argslist, template=None, page_size=100, fetch=False): '''Execute a statement using :sql:`VALUES` with a sequence of parameters. :param cur: the cursor to use to execute the query. @@ -1198,10 +1230,15 @@ def execute_values(cur, sql, argslist, template=None, page_size=100): *template*. :param template: the snippet to merge to every item in *argslist* to - compose the query. If *argslist* items are sequences it should contain - positional placeholders (e.g. ``"(%s, %s, %s)"``, or ``"(%s, %s, 42)``" - if there are constants value...); If *argslist* is items are mapping - it should contain named placeholders (e.g. ``"(%(id)s, %(f1)s, 42)"``). + compose the query. + + - If the *argslist* items are sequences it should contain positional + placeholders (e.g. ``"(%s, %s, %s)"``, or ``"(%s, %s, 42)``" if there + are constants value...). + + - If the *argslist* items are mappings it should contain named + placeholders (e.g. ``"(%(id)s, %(f1)s, 42)"``). + If not specified, assume the arguments are sequence and use a simple positional template (i.e. ``(%s, %s, ...)``), with the number of placeholders sniffed by the first element in *argslist*. @@ -1210,8 +1247,15 @@ def execute_values(cur, sql, argslist, template=None, page_size=100): statement. If there are more items the function will execute more than one statement. + :param fetch: if `!True` return the query results into a list (like in a + `~cursor.fetchall()`). Useful for queries with :sql:`RETURNING` + clause. + .. __: https://www.postgresql.org/docs/current/static/queries-values.html + After the execution of the function the `cursor.rowcount` property will + **not** contain a total result. + While :sql:`INSERT` is an obvious candidate for this function it is possible to use it with other statements, for example:: @@ -1232,6 +1276,10 @@ def execute_values(cur, sql, argslist, template=None, page_size=100): [(1, 20, 3), (4, 50, 6), (7, 8, 9)]) ''' + from psycopg2.sql import Composable + if isinstance(sql, Composable): + sql = sql.as_string(cur) + # we can't just use sql % vals because vals is bytes: if sql is bytes # there will be some decoding error because of stupid codec used, and Py3 # doesn't implement % on bytes. @@ -1239,6 +1287,7 @@ def execute_values(cur, sql, argslist, template=None, page_size=100): sql = sql.encode(_ext.encodings[cur.connection.encoding]) pre, post = _split_sql(sql) + result = [] if fetch else None for page in _paginate(argslist, page_size=page_size): if template is None: template = b'(' + b','.join([b'%s'] * len(page[0])) + b')' @@ -1248,6 +1297,10 @@ def execute_values(cur, sql, argslist, template=None, page_size=100): parts.append(b',') parts[-1:] = post cur.execute(b''.join(parts)) + if fetch: + result.extend(cur.fetchall()) + + return result def _split_sql(sql): @@ -1280,3 +1333,8 @@ def _split_sql(sql): raise ValueError("the query doesn't contain any '%s' placeholder") return pre, post + + +# ascii except alnum and underscore +_re_clean = _re.compile( + '[' + _re.escape(' !"#$%&\'()*+,-./:;<=>?@[\\]^`{|}~') + ']') diff --git a/source-code/psycopg2/pool.py b/source-code/psycopg2/lib/pool.py old mode 100755 new mode 100644 similarity index 72% rename from source-code/psycopg2/pool.py rename to source-code/psycopg2/lib/pool.py index 425e008..9d67d68 --- a/source-code/psycopg2/pool.py +++ b/source-code/psycopg2/lib/pool.py @@ -4,7 +4,8 @@ """ # psycopg/pool.py - pooling code for psycopg # -# Copyright (C) 2003-2010 Federico Di Gregorio +# Copyright (C) 2003-2019 Federico Di Gregorio +# Copyright (C) 2020-2021 The Psycopg Team # # psycopg2 is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published @@ -25,14 +26,14 @@ # License for more details. import psycopg2 -import psycopg2.extensions as _ext +from psycopg2 import extensions as _ext class PoolError(psycopg2.Error): pass -class AbstractConnectionPool(object): +class AbstractConnectionPool: """Generic key-based pooling code.""" def __init__(self, minconn, maxconn, *args, **kwargs): @@ -95,17 +96,17 @@ def _putconn(self, conn, key=None, close=False): """Put away a connection.""" if self.closed: raise PoolError("connection pool is closed") + if key is None: key = self._rused.get(id(conn)) - - if not key: - raise PoolError("trying to put unkeyed connection") + if key is None: + raise PoolError("trying to put unkeyed connection") if len(self._pool) < self.minconn and not close: # Return the connection into a consistent state before putting # it back into the pool if not conn.closed: - status = conn.get_transaction_status() + status = conn.info.transaction_status if status == _ext.TRANSACTION_STATUS_UNKNOWN: # server connection lost conn.close() @@ -138,7 +139,7 @@ def _closeall(self): for conn in self._pool + list(self._used.values()): try: conn.close() - except: + except Exception: pass self.closed = True @@ -184,58 +185,3 @@ def closeall(self): self._closeall() finally: self._lock.release() - - -class PersistentConnectionPool(AbstractConnectionPool): - """A pool that assigns persistent connections to different threads. - - Note that this connection pool generates by itself the required keys - using the current thread id. This means that until a thread puts away - a connection it will always get the same connection object by successive - `!getconn()` calls. This also means that a thread can't use more than one - single connection from the pool. - """ - - def __init__(self, minconn, maxconn, *args, **kwargs): - """Initialize the threading lock.""" - import warnings - warnings.warn("deprecated: use ZPsycopgDA.pool implementation", - DeprecationWarning) - - import threading - AbstractConnectionPool.__init__( - self, minconn, maxconn, *args, **kwargs) - self._lock = threading.Lock() - - # we we'll need the thread module, to determine thread ids, so we - # import it here and copy it in an instance variable - import _thread as _thread # work around for 2to3 bug - see ticket #348 - self.__thread = _thread - - def getconn(self): - """Generate thread id and return a connection.""" - key = self.__thread.get_ident() - self._lock.acquire() - try: - return self._getconn(key) - finally: - self._lock.release() - - def putconn(self, conn=None, close=False): - """Put away an unused connection.""" - key = self.__thread.get_ident() - self._lock.acquire() - try: - if not conn: - conn = self._used[key] - self._putconn(conn, key, close) - finally: - self._lock.release() - - def closeall(self): - """Close all connections (even the one currently in use.)""" - self._lock.acquire() - try: - self._closeall() - finally: - self._lock.release() diff --git a/source-code/psycopg2/sql.py b/source-code/psycopg2/lib/sql.py old mode 100755 new mode 100644 similarity index 79% rename from source-code/psycopg2/sql.py rename to source-code/psycopg2/lib/sql.py index 950b612..69b352b --- a/source-code/psycopg2/sql.py +++ b/source-code/psycopg2/lib/sql.py @@ -1,9 +1,10 @@ """SQL composition utility module """ -# psycopg/sql.py - Implementation of the JSON adaptation objects +# psycopg/sql.py - SQL composition utility module # -# Copyright (C) 2016 Daniele Varrazzo +# Copyright (C) 2016-2019 Daniele Varrazzo +# Copyright (C) 2020-2021 The Psycopg Team # # psycopg2 is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published @@ -23,7 +24,6 @@ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. -import sys import string from psycopg2 import extensions as ext @@ -32,12 +32,13 @@ _formatter = string.Formatter() -class Composable(object): +class Composable: """ Abstract base class for objects that can be used to compose an SQL string. - `!Composable` objects can be passed directly to `~cursor.execute()` and - `~cursor.executemany()` in place of the query string. + `!Composable` objects can be passed directly to `~cursor.execute()`, + `~cursor.executemany()`, `~cursor.copy_expert()` in place of the query + string. `!Composable` objects can be joined using the ``+`` operator: the result will be a `Composed` instance containing the objects joined. The operator @@ -49,7 +50,7 @@ def __init__(self, wrapped): self._wrapped = wrapped def __repr__(self): - return "%s(%r)" % (self.__class__.__name__, self._wrapped) + return f"{self.__class__.__name__}({self._wrapped!r})" def as_string(self, context): """ @@ -58,9 +59,9 @@ def as_string(self, context): :param context: the context to evaluate the string into. :type context: `connection` or `cursor` - The method is automatically invoked by `~cursor.execute()` and - `~cursor.executemany()` if a `!Composable` is passed instead of the - query string. + The method is automatically invoked by `~cursor.execute()`, + `~cursor.executemany()`, `~cursor.copy_expert()` if a `!Composable` is + passed instead of the query string. """ raise NotImplementedError @@ -84,11 +85,11 @@ def __ne__(self, other): class Composed(Composable): """ - A `Composable` object made of a sequence of `Composable`. + A `Composable` object made of a sequence of `!Composable`. - The object is usually created using `Composable` operators and methods. + The object is usually created using `!Composable` operators and methods. However it is possible to create a `!Composed` directly specifying a - sequence of `Composable` as arguments. + sequence of `!Composable` as arguments. Example:: @@ -105,10 +106,10 @@ def __init__(self, seq): for i in seq: if not isinstance(i, Composable): raise TypeError( - "Composed elements must be Composable, got %r instead" % i) + f"Composed elements must be Composable, got {i!r} instead") wrapped.append(i) - super(Composed, self).__init__(wrapped) + super().__init__(wrapped) @property def seq(self): @@ -180,7 +181,7 @@ class SQL(Composable): def __init__(self, string): if not isinstance(string, str): raise TypeError("SQL values must be strings") - super(SQL, self).__init__(string) + super().__init__(string) @property def string(self): @@ -202,12 +203,12 @@ def format(self, *args, **kwargs): :rtype: `Composed` The method is similar to the Python `str.format()` method: the string - template supports auto-numbered (``{}``, only available from Python - 2.7), numbered (``{0}``, ``{1}``...), and named placeholders - (``{name}``), with positional arguments replacing the numbered - placeholders and keywords replacing the named ones. However placeholder - modifiers (``{0!r}``, ``{0:<10}``) are not supported. Only - `!Composable` objects can be passed to the template. + template supports auto-numbered (``{}``), numbered (``{0}``, + ``{1}``...), and named placeholders (``{name}``), with positional + arguments replacing the numbered placeholders and keywords replacing + the named ones. However placeholder modifiers (``{0!r}``, ``{0:<10}``) + are not supported. Only `!Composable` objects can be passed to the + template. Example:: @@ -288,11 +289,11 @@ def join(self, seq): class Identifier(Composable): """ - A `Composable` representing an SQL identifer. + A `Composable` representing an SQL identifier or a dot-separated sequence. - Identifiers usually represent names of database objects, such as tables - or fields. They follow `different rules`__ than SQL string literals for - escaping (e.g. they use double quotes). + Identifiers usually represent names of database objects, such as tables or + fields. PostgreSQL identifiers follow `different rules`__ than SQL string + literals for escaping (e.g. they use double quotes instead of single). .. __: https://www.postgresql.org/docs/current/static/sql-syntax-lexical.html# \ SQL-SYNTAX-IDENTIFIERS @@ -305,20 +306,48 @@ class Identifier(Composable): >>> print(sql.SQL(', ').join([t1, t2, t3]).as_string(conn)) "foo", "ba'r", "ba""z" + Multiple strings can be passed to the object to represent a qualified name, + i.e. a dot-separated sequence of identifiers. + + Example:: + + >>> query = sql.SQL("select {} from {}").format( + ... sql.Identifier("table", "field"), + ... sql.Identifier("schema", "table")) + >>> print(query.as_string(conn)) + select "table"."field" from "schema"."table" + """ - def __init__(self, string): - if not isinstance(string, str): - raise TypeError("SQL identifiers must be strings") + def __init__(self, *strings): + if not strings: + raise TypeError("Identifier cannot be empty") - super(Identifier, self).__init__(string) + for s in strings: + if not isinstance(s, str): + raise TypeError("SQL identifier parts must be strings") + + super().__init__(strings) @property - def string(self): - """The string wrapped by the `Identifier`.""" + def strings(self): + """A tuple with the strings wrapped by the `Identifier`.""" return self._wrapped + @property + def string(self): + """The string wrapped by the `Identifier`. + """ + if len(self._wrapped) == 1: + return self._wrapped[0] + else: + raise AttributeError( + "the Identifier wraps more than one than one string") + + def __repr__(self): + return f"{self.__class__.__name__}({', '.join(map(repr, self._wrapped))})" + def as_string(self, context): - return ext.quote_ident(self._wrapped, context) + return '.'.join(ext.quote_ident(s, context) for s in self._wrapped) class Literal(Composable): @@ -360,7 +389,7 @@ def as_string(self, context): a.prepare(conn) rv = a.getquoted() - if sys.version_info[0] >= 3 and isinstance(rv, bytes): + if isinstance(rv, bytes): rv = rv.decode(ext.encodings[conn.encoding]) return rv @@ -396,12 +425,12 @@ class Placeholder(Composable): def __init__(self, name=None): if isinstance(name, str): if ')' in name: - raise ValueError("invalid name: %r" % name) + raise ValueError(f"invalid name: {name!r}") elif name is not None: - raise TypeError("expected string or None as name, got %r" % name) + raise TypeError(f"expected string or None as name, got {name!r}") - super(Placeholder, self).__init__(name) + super().__init__(name) @property def name(self): @@ -409,12 +438,14 @@ def name(self): return self._wrapped def __repr__(self): - return "Placeholder(%r)" % ( - self._wrapped if self._wrapped is not None else '',) + if self._wrapped is None: + return f"{self.__class__.__name__}()" + else: + return f"{self.__class__.__name__}({self._wrapped!r})" def as_string(self, context): if self._wrapped is not None: - return "%%(%s)s" % self._wrapped + return f"%({self._wrapped})s" else: return "%s" diff --git a/source-code/psycopg2/tz.py b/source-code/psycopg2/lib/tz.py old mode 100755 new mode 100644 similarity index 74% rename from source-code/psycopg2/tz.py rename to source-code/psycopg2/lib/tz.py index 92a1604..d88ca37 --- a/source-code/psycopg2/tz.py +++ b/source-code/psycopg2/lib/tz.py @@ -6,7 +6,8 @@ """ # psycopg/tz.py - tzinfo implementation # -# Copyright (C) 2003-2010 Federico Di Gregorio +# Copyright (C) 2003-2019 Federico Di Gregorio +# Copyright (C) 2020-2021 The Psycopg Team # # psycopg2 is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published @@ -44,7 +45,12 @@ class FixedOffsetTimezone(datetime.tzinfo): offset and name that instance will be returned. This saves memory and improves comparability. - .. __: http://docs.python.org/library/datetime.html#datetime-tzinfo + .. versionchanged:: 2.9 + + The constructor can take either a timedelta or a number of minutes of + offset. Previously only minutes were supported. + + .. __: https://docs.python.org/library/datetime.html """ _name = None _offset = ZERO @@ -53,7 +59,9 @@ class FixedOffsetTimezone(datetime.tzinfo): def __init__(self, offset=None, name=None): if offset is not None: - self._offset = datetime.timedelta(minutes=offset) + if not isinstance(offset, datetime.timedelta): + offset = datetime.timedelta(minutes=offset) + self._offset = offset if name is not None: self._name = name @@ -64,18 +72,28 @@ def __new__(cls, offset=None, name=None): try: return cls._cache[key] except KeyError: - tz = super(FixedOffsetTimezone, cls).__new__(cls, offset, name) + tz = super().__new__(cls, offset, name) cls._cache[key] = tz return tz def __repr__(self): - offset_mins = self._offset.seconds // 60 + self._offset.days * 24 * 60 return "psycopg2.tz.FixedOffsetTimezone(offset=%r, name=%r)" \ - % (offset_mins, self._name) + % (self._offset, self._name) + + def __eq__(self, other): + if isinstance(other, FixedOffsetTimezone): + return self._offset == other._offset + else: + return NotImplemented + + def __ne__(self, other): + if isinstance(other, FixedOffsetTimezone): + return self._offset != other._offset + else: + return NotImplemented def __getinitargs__(self): - offset_mins = self._offset.seconds // 60 + self._offset.days * 24 * 60 - return (offset_mins, self._name) + return self._offset, self._name def utcoffset(self, dt): return self._offset @@ -83,14 +101,16 @@ def utcoffset(self, dt): def tzname(self, dt): if self._name is not None: return self._name - else: - seconds = self._offset.seconds + self._offset.days * 86400 - hours, seconds = divmod(seconds, 3600) - minutes = seconds / 60 - if minutes: - return "%+03d:%d" % (hours, minutes) - else: - return "%+03d" % hours + + minutes, seconds = divmod(self._offset.total_seconds(), 60) + hours, minutes = divmod(minutes, 60) + rv = "%+03d" % hours + if minutes or seconds: + rv += ":%02d" % minutes + if seconds: + rv += ":%02d" % seconds + + return rv def dst(self, dt): return ZERO @@ -132,6 +152,7 @@ def _isdst(self, dt): tt = time.localtime(stamp) return tt.tm_isdst > 0 + LOCAL = LocalTimezone() # TODO: pre-generate some interesting time zones? diff --git a/source-code/psycopg2/psycopg/_psycopg.vc9.amd64.manifest b/source-code/psycopg2/psycopg/_psycopg.vc9.amd64.manifest new file mode 100644 index 0000000..e92d583 --- /dev/null +++ b/source-code/psycopg2/psycopg/_psycopg.vc9.amd64.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/source-code/psycopg2/psycopg/_psycopg.vc9.x86.manifest b/source-code/psycopg2/psycopg/_psycopg.vc9.x86.manifest new file mode 100644 index 0000000..9fc55da --- /dev/null +++ b/source-code/psycopg2/psycopg/_psycopg.vc9.x86.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/source-code/psycopg2/psycopg/adapter_asis.c b/source-code/psycopg2/psycopg/adapter_asis.c new file mode 100644 index 0000000..5c75786 --- /dev/null +++ b/source-code/psycopg2/psycopg/adapter_asis.c @@ -0,0 +1,195 @@ +/* adapter_asis.c - adapt types as they are + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/adapter_asis.h" +#include "psycopg/microprotocols_proto.h" + +#include + + +/** the AsIs object **/ + +static PyObject * +asis_getquoted(asisObject *self, PyObject *args) +{ + PyObject *rv; + if (self->wrapped == Py_None) { + Py_INCREF(psyco_null); + rv = psyco_null; + } + else { + rv = PyObject_Str(self->wrapped); + /* unicode to bytes */ + if (rv) { + PyObject *tmp = PyUnicode_AsUTF8String(rv); + Py_DECREF(rv); + rv = tmp; + } + } + + return rv; +} + +static PyObject * +asis_str(asisObject *self) +{ + return psyco_ensure_text(asis_getquoted(self, NULL)); +} + +static PyObject * +asis_conform(asisObject *self, PyObject *args) +{ + PyObject *res, *proto; + + if (!PyArg_ParseTuple(args, "O", &proto)) return NULL; + + if (proto == (PyObject*)&isqlquoteType) + res = (PyObject*)self; + else + res = Py_None; + + Py_INCREF(res); + return res; +} + +/** the AsIs object */ + +/* object member list */ + +static struct PyMemberDef asisObject_members[] = { + {"adapted", T_OBJECT, offsetof(asisObject, wrapped), READONLY}, + {NULL} +}; + +/* object method table */ + +static PyMethodDef asisObject_methods[] = { + {"getquoted", (PyCFunction)asis_getquoted, METH_NOARGS, + "getquoted() -> wrapped object value as SQL-quoted string"}, + {"__conform__", (PyCFunction)asis_conform, METH_VARARGS, NULL}, + {NULL} /* Sentinel */ +}; + +/* initialization and finalization methods */ + +static int +asis_setup(asisObject *self, PyObject *obj) +{ + Dprintf("asis_setup: init asis object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + self, Py_REFCNT(self) + ); + + Py_INCREF(obj); + self->wrapped = obj; + + Dprintf("asis_setup: good asis object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + self, Py_REFCNT(self) + ); + return 0; +} + +static void +asis_dealloc(PyObject* obj) +{ + asisObject *self = (asisObject *)obj; + + Py_CLEAR(self->wrapped); + + Dprintf("asis_dealloc: deleted asis object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + obj, Py_REFCNT(obj) + ); + + Py_TYPE(obj)->tp_free(obj); +} + +static int +asis_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + PyObject *o; + + if (!PyArg_ParseTuple(args, "O", &o)) + return -1; + + return asis_setup((asisObject *)obj, o); +} + +static PyObject * +asis_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + + +/* object type */ + +#define asisType_doc \ +"AsIs(str) -> new AsIs adapter object" + +PyTypeObject asisType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2.extensions.AsIs", + sizeof(asisObject), 0, + asis_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + (reprfunc)asis_str, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + asisType_doc, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + asisObject_methods, /*tp_methods*/ + asisObject_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + asis_init, /*tp_init*/ + 0, /*tp_alloc*/ + asis_new, /*tp_new*/ +}; diff --git a/source-code/psycopg2/psycopg/adapter_asis.h b/source-code/psycopg2/psycopg/adapter_asis.h new file mode 100644 index 0000000..b6c82b7 --- /dev/null +++ b/source-code/psycopg2/psycopg/adapter_asis.h @@ -0,0 +1,48 @@ +/* adapter_asis.h - definition for the psycopg AsIs type wrapper + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_ASIS_H +#define PSYCOPG_ASIS_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +extern HIDDEN PyTypeObject asisType; + +typedef struct { + PyObject_HEAD + + /* this is the real object we wrap */ + PyObject *wrapped; + +} asisObject; + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_ASIS_H) */ diff --git a/source-code/psycopg2/psycopg/adapter_binary.c b/source-code/psycopg2/psycopg/adapter_binary.c new file mode 100644 index 0000000..d6b110c --- /dev/null +++ b/source-code/psycopg2/psycopg/adapter_binary.c @@ -0,0 +1,281 @@ +/* adapter_binary.c - Binary objects + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/adapter_binary.h" +#include "psycopg/microprotocols_proto.h" +#include "psycopg/connection.h" + +#include + + +/** the quoting code */ + +static unsigned char * +binary_escape(unsigned char *from, size_t from_length, + size_t *to_length, PGconn *conn) +{ + if (conn) + return PQescapeByteaConn(conn, from, from_length, to_length); + else + return PQescapeBytea(from, from_length, to_length); +} + +/* binary_quote - do the quote process on plain and unicode strings */ + +static PyObject * +binary_quote(binaryObject *self) +{ + char *to = NULL; + const char *buffer = NULL; + Py_ssize_t buffer_len; + size_t len = 0; + PyObject *rv = NULL; + Py_buffer view; + int got_view = 0; + + /* Allow Binary(None) to work */ + if (self->wrapped == Py_None) { + Py_INCREF(psyco_null); + rv = psyco_null; + goto exit; + } + + /* if we got a plain string or a buffer we escape it and save the buffer */ + if (PyObject_CheckBuffer(self->wrapped)) { + if (0 > PyObject_GetBuffer(self->wrapped, &view, PyBUF_CONTIG_RO)) { + goto exit; + } + got_view = 1; + buffer = (const char *)(view.buf); + buffer_len = view.len; + } + + if (!buffer) { + goto exit; + } + + /* escape and build quoted buffer */ + + to = (char *)binary_escape((unsigned char*)buffer, (size_t)buffer_len, + &len, self->conn ? ((connectionObject*)self->conn)->pgconn : NULL); + if (to == NULL) { + PyErr_NoMemory(); + goto exit; + } + + if (len > 0) + rv = Bytes_FromFormat( + (self->conn && ((connectionObject*)self->conn)->equote) + ? "E'%s'::bytea" : "'%s'::bytea" , to); + else + rv = Bytes_FromString("''::bytea"); + +exit: + if (to) { PQfreemem(to); } + if (got_view) { PyBuffer_Release(&view); } + + /* if the wrapped object is not bytes or a buffer, this is an error */ + if (!rv && !PyErr_Occurred()) { + PyErr_Format(PyExc_TypeError, "can't escape %s to binary", + Py_TYPE(self->wrapped)->tp_name); + } + + return rv; +} + +/* binary_str, binary_getquoted - return result of quoting */ + +static PyObject * +binary_getquoted(binaryObject *self, PyObject *args) +{ + if (self->buffer == NULL) { + self->buffer = binary_quote(self); + } + Py_XINCREF(self->buffer); + return self->buffer; +} + +static PyObject * +binary_str(binaryObject *self) +{ + return psyco_ensure_text(binary_getquoted(self, NULL)); +} + +static PyObject * +binary_prepare(binaryObject *self, PyObject *args) +{ + PyObject *conn; + + if (!PyArg_ParseTuple(args, "O!", &connectionType, &conn)) + return NULL; + + Py_XDECREF(self->conn); + self->conn = conn; + Py_INCREF(self->conn); + + Py_RETURN_NONE; +} + +static PyObject * +binary_conform(binaryObject *self, PyObject *args) +{ + PyObject *res, *proto; + + if (!PyArg_ParseTuple(args, "O", &proto)) return NULL; + + if (proto == (PyObject*)&isqlquoteType) + res = (PyObject*)self; + else + res = Py_None; + + Py_INCREF(res); + return res; +} + +/** the Binary object **/ + +/* object member list */ + +static struct PyMemberDef binaryObject_members[] = { + {"adapted", T_OBJECT, offsetof(binaryObject, wrapped), READONLY}, + {"buffer", T_OBJECT, offsetof(binaryObject, buffer), READONLY}, + {NULL} +}; + +/* object method table */ + +static PyMethodDef binaryObject_methods[] = { + {"getquoted", (PyCFunction)binary_getquoted, METH_NOARGS, + "getquoted() -> wrapped object value as SQL-quoted binary string"}, + {"prepare", (PyCFunction)binary_prepare, METH_VARARGS, + "prepare(conn) -> prepare for binary encoding using conn"}, + {"__conform__", (PyCFunction)binary_conform, METH_VARARGS, NULL}, + {NULL} /* Sentinel */ +}; + +/* initialization and finalization methods */ + +static int +binary_setup(binaryObject *self, PyObject *str) +{ + Dprintf("binary_setup: init binary object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + self, Py_REFCNT(self) + ); + + self->buffer = NULL; + self->conn = NULL; + Py_INCREF(str); + self->wrapped = str; + + Dprintf("binary_setup: good binary object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + self, Py_REFCNT(self)); + return 0; +} + +static void +binary_dealloc(PyObject* obj) +{ + binaryObject *self = (binaryObject *)obj; + + Py_CLEAR(self->wrapped); + Py_CLEAR(self->buffer); + Py_CLEAR(self->conn); + + Dprintf("binary_dealloc: deleted binary object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + obj, Py_REFCNT(obj) + ); + + Py_TYPE(obj)->tp_free(obj); +} + +static int +binary_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + PyObject *str; + + if (!PyArg_ParseTuple(args, "O", &str)) + return -1; + + return binary_setup((binaryObject *)obj, str); +} + +static PyObject * +binary_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + + +/* object type */ + +#define binaryType_doc \ +"Binary(buffer) -> new binary object" + +PyTypeObject binaryType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2.extensions.Binary", + sizeof(binaryObject), 0, + binary_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + (reprfunc)binary_str, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + binaryType_doc, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + binaryObject_methods, /*tp_methods*/ + binaryObject_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + binary_init, /*tp_init*/ + 0, /*tp_alloc*/ + binary_new, /*tp_new*/ +}; diff --git a/source-code/psycopg2/psycopg/adapter_binary.h b/source-code/psycopg2/psycopg/adapter_binary.h new file mode 100644 index 0000000..54f9fb5 --- /dev/null +++ b/source-code/psycopg2/psycopg/adapter_binary.h @@ -0,0 +1,48 @@ +/* adapter_binary.h - definition for the Binary type + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_BINARY_H +#define PSYCOPG_BINARY_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +extern HIDDEN PyTypeObject binaryType; + +typedef struct { + PyObject_HEAD + + PyObject *wrapped; + PyObject *buffer; + PyObject *conn; +} binaryObject; + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_BINARY_H) */ diff --git a/source-code/psycopg2/psycopg/adapter_datetime.c b/source-code/psycopg2/psycopg/adapter_datetime.c new file mode 100644 index 0000000..9df26ad --- /dev/null +++ b/source-code/psycopg2/psycopg/adapter_datetime.c @@ -0,0 +1,515 @@ +/* adapter_datetime.c - python date/time objects + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/adapter_datetime.h" +#include "psycopg/microprotocols_proto.h" + +#include + +#include +#include + + +RAISES_NEG int +adapter_datetime_init(void) +{ + PyDateTime_IMPORT; + + if (!PyDateTimeAPI) { + PyErr_SetString(PyExc_ImportError, "datetime initialization failed"); + return -1; + } + return 0; +} + +/* datetime_str, datetime_getquoted - return result of quoting */ + +static PyObject * +_pydatetime_string_date_time(pydatetimeObject *self) +{ + PyObject *rv = NULL; + PyObject *iso = NULL; + PyObject *tz; + + /* Select the right PG type to cast into. */ + char *fmt = NULL; + switch (self->type) { + case PSYCO_DATETIME_TIME: + tz = PyObject_GetAttrString(self->wrapped, "tzinfo"); + if (!tz) { goto error; } + fmt = (tz == Py_None) ? "'%s'::time" : "'%s'::timetz"; + Py_DECREF(tz); + break; + case PSYCO_DATETIME_DATE: + fmt = "'%s'::date"; + break; + case PSYCO_DATETIME_TIMESTAMP: + tz = PyObject_GetAttrString(self->wrapped, "tzinfo"); + if (!tz) { goto error; } + fmt = (tz == Py_None) ? "'%s'::timestamp" : "'%s'::timestamptz"; + Py_DECREF(tz); + break; + } + + if (!(iso = psyco_ensure_bytes( + PyObject_CallMethod(self->wrapped, "isoformat", NULL)))) { + goto error; + } + + rv = Bytes_FromFormat(fmt, Bytes_AsString(iso)); + + Py_DECREF(iso); + return rv; + +error: + Py_XDECREF(iso); + return rv; +} + +static PyObject * +_pydatetime_string_delta(pydatetimeObject *self) +{ + PyDateTime_Delta *obj = (PyDateTime_Delta*)self->wrapped; + + char buffer[8]; + int i; + int a = PyDateTime_DELTA_GET_MICROSECONDS(obj); + + for (i=0; i < 6 ; i++) { + buffer[5-i] = '0' + (a % 10); + a /= 10; + } + buffer[6] = '\0'; + + return Bytes_FromFormat("'%d days %d.%s seconds'::interval", + PyDateTime_DELTA_GET_DAYS(obj), + PyDateTime_DELTA_GET_SECONDS(obj), + buffer); +} + +static PyObject * +pydatetime_getquoted(pydatetimeObject *self, PyObject *args) +{ + if (self->type <= PSYCO_DATETIME_TIMESTAMP) { + return _pydatetime_string_date_time(self); + } + else { + return _pydatetime_string_delta(self); + } +} + +static PyObject * +pydatetime_str(pydatetimeObject *self) +{ + return psyco_ensure_text(pydatetime_getquoted(self, NULL)); +} + +static PyObject * +pydatetime_conform(pydatetimeObject *self, PyObject *args) +{ + PyObject *res, *proto; + + if (!PyArg_ParseTuple(args, "O", &proto)) return NULL; + + if (proto == (PyObject*)&isqlquoteType) + res = (PyObject*)self; + else + res = Py_None; + + Py_INCREF(res); + return res; +} + +/** the DateTime wrapper object **/ + +/* object member list */ + +static struct PyMemberDef pydatetimeObject_members[] = { + {"adapted", T_OBJECT, offsetof(pydatetimeObject, wrapped), READONLY}, + {"type", T_INT, offsetof(pydatetimeObject, type), READONLY}, + {NULL} +}; + +/* object method table */ + +static PyMethodDef pydatetimeObject_methods[] = { + {"getquoted", (PyCFunction)pydatetime_getquoted, METH_NOARGS, + "getquoted() -> wrapped object value as SQL date/time"}, + {"__conform__", (PyCFunction)pydatetime_conform, METH_VARARGS, NULL}, + {NULL} /* Sentinel */ +}; + +/* initialization and finalization methods */ + +static int +pydatetime_setup(pydatetimeObject *self, PyObject *obj, int type) +{ + Dprintf("pydatetime_setup: init datetime object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + self, Py_REFCNT(self)); + + self->type = type; + Py_INCREF(obj); + self->wrapped = obj; + + Dprintf("pydatetime_setup: good pydatetime object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + self, Py_REFCNT(self)); + return 0; +} + +static void +pydatetime_dealloc(PyObject* obj) +{ + pydatetimeObject *self = (pydatetimeObject *)obj; + + Py_CLEAR(self->wrapped); + + Dprintf("mpydatetime_dealloc: deleted pydatetime object at %p, " + "refcnt = " FORMAT_CODE_PY_SSIZE_T, obj, Py_REFCNT(obj)); + + Py_TYPE(obj)->tp_free(obj); +} + +static int +pydatetime_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + PyObject *dt; + int type = -1; /* raise an error if type was not passed! */ + + if (!PyArg_ParseTuple(args, "O|i", &dt, &type)) + return -1; + + return pydatetime_setup((pydatetimeObject *)obj, dt, type); +} + +static PyObject * +pydatetime_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + + +/* object type */ + +#define pydatetimeType_doc \ +"datetime(datetime, type) -> new datetime wrapper object" + +PyTypeObject pydatetimeType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2._psycopg.datetime", + sizeof(pydatetimeObject), 0, + pydatetime_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + (reprfunc)pydatetime_str, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + pydatetimeType_doc, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + pydatetimeObject_methods, /*tp_methods*/ + pydatetimeObject_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + pydatetime_init, /*tp_init*/ + 0, /*tp_alloc*/ + pydatetime_new, /*tp_new*/ +}; + + +/** module-level functions **/ + +PyObject * +psyco_Date(PyObject *self, PyObject *args) +{ + PyObject *res = NULL; + int year, month, day; + + PyObject* obj = NULL; + + if (!PyArg_ParseTuple(args, "iii", &year, &month, &day)) + return NULL; + + obj = PyObject_CallFunction((PyObject*)PyDateTimeAPI->DateType, + "iii", year, month, day); + + if (obj) { + res = PyObject_CallFunction((PyObject *)&pydatetimeType, + "Oi", obj, PSYCO_DATETIME_DATE); + Py_DECREF(obj); + } + + return res; +} + +PyObject * +psyco_Time(PyObject *self, PyObject *args) +{ + PyObject *res = NULL; + PyObject *tzinfo = NULL; + int hours, minutes=0; + double micro, second=0.0; + + PyObject* obj = NULL; + + if (!PyArg_ParseTuple(args, "iid|O", &hours, &minutes, &second, + &tzinfo)) + return NULL; + + micro = (second - floor(second)) * 1000000.0; + second = floor(second); + + if (tzinfo == NULL) + obj = PyObject_CallFunction((PyObject*)PyDateTimeAPI->TimeType, "iiii", + hours, minutes, (int)second, (int)round(micro)); + else + obj = PyObject_CallFunction((PyObject*)PyDateTimeAPI->TimeType, "iiiiO", + hours, minutes, (int)second, (int)round(micro), tzinfo); + + if (obj) { + res = PyObject_CallFunction((PyObject *)&pydatetimeType, + "Oi", obj, PSYCO_DATETIME_TIME); + Py_DECREF(obj); + } + + return res; +} + +static PyObject * +_psyco_Timestamp(int year, int month, int day, + int hour, int minute, double second, PyObject *tzinfo) +{ + double micro; + PyObject *obj; + PyObject *res = NULL; + + micro = (second - floor(second)) * 1000000.0; + second = floor(second); + + if (tzinfo == NULL) + obj = PyObject_CallFunction((PyObject*)PyDateTimeAPI->DateTimeType, + "iiiiiii", + year, month, day, hour, minute, (int)second, + (int)round(micro)); + else + obj = PyObject_CallFunction((PyObject*)PyDateTimeAPI->DateTimeType, + "iiiiiiiO", + year, month, day, hour, minute, (int)second, + (int)round(micro), tzinfo); + + if (obj) { + res = PyObject_CallFunction((PyObject *)&pydatetimeType, + "Oi", obj, PSYCO_DATETIME_TIMESTAMP); + Py_DECREF(obj); + } + + return res; +} + +PyObject * +psyco_Timestamp(PyObject *self, PyObject *args) +{ + PyObject *tzinfo = NULL; + int year, month, day; + int hour=0, minute=0; /* default to midnight */ + double second=0.0; + + if (!PyArg_ParseTuple(args, "iii|iidO", &year, &month, &day, + &hour, &minute, &second, &tzinfo)) + return NULL; + + return _psyco_Timestamp(year, month, day, hour, minute, second, tzinfo); +} + +PyObject * +psyco_DateFromTicks(PyObject *self, PyObject *args) +{ + PyObject *res = NULL; + struct tm tm; + time_t t; + double ticks; + + if (!PyArg_ParseTuple(args, "d", &ticks)) + return NULL; + + t = (time_t)floor(ticks); + if (localtime_r(&t, &tm)) { + args = Py_BuildValue("iii", tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday); + if (args) { + res = psyco_Date(self, args); + Py_DECREF(args); + } + } + else { + PyErr_SetString(InterfaceError, "failed localtime call"); + } + + return res; +} + +PyObject * +psyco_TimeFromTicks(PyObject *self, PyObject *args) +{ + PyObject *res = NULL; + struct tm tm; + time_t t; + double ticks; + + if (!PyArg_ParseTuple(args,"d", &ticks)) + return NULL; + + t = (time_t)floor(ticks); + ticks -= (double)t; + if (localtime_r(&t, &tm)) { + args = Py_BuildValue("iid", tm.tm_hour, tm.tm_min, + (double)tm.tm_sec + ticks); + if (args) { + res = psyco_Time(self, args); + Py_DECREF(args); + } + } + else { + PyErr_SetString(InterfaceError, "failed localtime call"); + } + + return res; +} + +PyObject * +psyco_TimestampFromTicks(PyObject *self, PyObject *args) +{ + pydatetimeObject *wrapper = NULL; + PyObject *dt_aware = NULL; + PyObject *res = NULL; + struct tm tm; + time_t t; + double ticks; + + if (!PyArg_ParseTuple(args, "d", &ticks)) + return NULL; + + t = (time_t)floor(ticks); + ticks -= (double)t; + if (!localtime_r(&t, &tm)) { + PyErr_SetString(InterfaceError, "failed localtime call"); + goto exit; + } + + /* Convert the tm to a wrapper containing a naive datetime.datetime */ + if (!(wrapper = (pydatetimeObject *)_psyco_Timestamp( + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, (double)tm.tm_sec + ticks, NULL))) { + goto exit; + } + + /* Localize the datetime and assign it back to the wrapper */ + if (!(dt_aware = PyObject_CallMethod( + wrapper->wrapped, "astimezone", NULL))) { + goto exit; + } + Py_CLEAR(wrapper->wrapped); + wrapper->wrapped = dt_aware; + dt_aware = NULL; + + /* the wrapper is ready to be returned */ + res = (PyObject *)wrapper; + wrapper = NULL; + +exit: + Py_XDECREF(dt_aware); + Py_XDECREF(wrapper); + return res; +} + +PyObject * +psyco_DateFromPy(PyObject *self, PyObject *args) +{ + PyObject *obj; + + if (!PyArg_ParseTuple(args, "O!", PyDateTimeAPI->DateType, &obj)) + return NULL; + + return PyObject_CallFunction((PyObject *)&pydatetimeType, "Oi", obj, + PSYCO_DATETIME_DATE); +} + +PyObject * +psyco_TimeFromPy(PyObject *self, PyObject *args) +{ + PyObject *obj; + + if (!PyArg_ParseTuple(args, "O!", PyDateTimeAPI->TimeType, &obj)) + return NULL; + + return PyObject_CallFunction((PyObject *)&pydatetimeType, "Oi", obj, + PSYCO_DATETIME_TIME); +} + +PyObject * +psyco_TimestampFromPy(PyObject *self, PyObject *args) +{ + PyObject *obj; + + if (!PyArg_ParseTuple(args, "O!", PyDateTimeAPI->DateTimeType, &obj)) + return NULL; + + return PyObject_CallFunction((PyObject *)&pydatetimeType, "Oi", obj, + PSYCO_DATETIME_TIMESTAMP); +} + +PyObject * +psyco_IntervalFromPy(PyObject *self, PyObject *args) +{ + PyObject *obj; + + if (!PyArg_ParseTuple(args, "O!", PyDateTimeAPI->DeltaType, &obj)) + return NULL; + + return PyObject_CallFunction((PyObject *)&pydatetimeType, "Oi", obj, + PSYCO_DATETIME_INTERVAL); +} diff --git a/source-code/psycopg2/psycopg/adapter_datetime.h b/source-code/psycopg2/psycopg/adapter_datetime.h new file mode 100644 index 0000000..7705db3 --- /dev/null +++ b/source-code/psycopg2/psycopg/adapter_datetime.h @@ -0,0 +1,107 @@ +/* adapter_datetime.h - definition for the python date/time types + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_DATETIME_H +#define PSYCOPG_DATETIME_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +extern HIDDEN PyTypeObject pydatetimeType; + +typedef struct { + PyObject_HEAD + + PyObject *wrapped; + int type; +#define PSYCO_DATETIME_TIME 0 +#define PSYCO_DATETIME_DATE 1 +#define PSYCO_DATETIME_TIMESTAMP 2 +#define PSYCO_DATETIME_INTERVAL 3 + +} pydatetimeObject; + + +RAISES_NEG HIDDEN int adapter_datetime_init(void); + +HIDDEN PyObject *psyco_Date(PyObject *module, PyObject *args); +#define psyco_Date_doc \ + "Date(year, month, day) -> new date\n\n" \ + "Build an object holding a date value." + +HIDDEN PyObject *psyco_Time(PyObject *module, PyObject *args); +#define psyco_Time_doc \ + "Time(hour, minutes, seconds, tzinfo=None) -> new time\n\n" \ + "Build an object holding a time value." + +HIDDEN PyObject *psyco_Timestamp(PyObject *module, PyObject *args); +#define psyco_Timestamp_doc \ + "Timestamp(year, month, day, hour, minutes, seconds, tzinfo=None) -> new timestamp\n\n" \ + "Build an object holding a timestamp value." + +HIDDEN PyObject *psyco_DateFromTicks(PyObject *module, PyObject *args); +#define psyco_DateFromTicks_doc \ + "DateFromTicks(ticks) -> new date\n\n" \ + "Build an object holding a date value from the given ticks value.\n\n" \ + "Ticks are the number of seconds since the epoch; see the documentation " \ + "of the standard Python time module for details)." + +HIDDEN PyObject *psyco_TimeFromTicks(PyObject *module, PyObject *args); +#define psyco_TimeFromTicks_doc \ + "TimeFromTicks(ticks) -> new time\n\n" \ + "Build an object holding a time value from the given ticks value.\n\n" \ + "Ticks are the number of seconds since the epoch; see the documentation " \ + "of the standard Python time module for details)." + +HIDDEN PyObject *psyco_TimestampFromTicks(PyObject *module, PyObject *args); +#define psyco_TimestampFromTicks_doc \ + "TimestampFromTicks(ticks) -> new timestamp\n\n" \ + "Build an object holding a timestamp value from the given ticks value.\n\n" \ + "Ticks are the number of seconds since the epoch; see the documentation " \ + "of the standard Python time module for details)." + +HIDDEN PyObject *psyco_DateFromPy(PyObject *module, PyObject *args); +#define psyco_DateFromPy_doc \ + "DateFromPy(datetime.date) -> new wrapper" + +HIDDEN PyObject *psyco_TimeFromPy(PyObject *module, PyObject *args); +#define psyco_TimeFromPy_doc \ + "TimeFromPy(datetime.time) -> new wrapper" + +HIDDEN PyObject *psyco_TimestampFromPy(PyObject *module, PyObject *args); +#define psyco_TimestampFromPy_doc \ + "TimestampFromPy(datetime.datetime) -> new wrapper" + +HIDDEN PyObject *psyco_IntervalFromPy(PyObject *module, PyObject *args); +#define psyco_IntervalFromPy_doc \ + "IntervalFromPy(datetime.timedelta) -> new wrapper" + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_DATETIME_H) */ diff --git a/source-code/psycopg2/psycopg/adapter_list.c b/source-code/psycopg2/psycopg/adapter_list.c new file mode 100644 index 0000000..e22292b --- /dev/null +++ b/source-code/psycopg2/psycopg/adapter_list.c @@ -0,0 +1,342 @@ +/* adapter_list.c - python list objects + * + * Copyright (C) 2004-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/adapter_list.h" +#include "psycopg/microprotocols.h" +#include "psycopg/microprotocols_proto.h" + + +/* list_str, list_getquoted - return result of quoting */ + +static PyObject * +list_quote(listObject *self) +{ + /* adapt the list by calling adapt() recursively and then wrapping + everything into "ARRAY[]" */ + PyObject *res = NULL; + PyObject **qs = NULL; + Py_ssize_t bufsize = 0; + char *buf = NULL, *ptr; + + /* list consisting of only NULL don't work with the ARRAY[] construct + * so we use the {NULL,...} syntax. The same syntax is also necessary + * to convert array of arrays containing only nulls. */ + int all_nulls = 1; + + Py_ssize_t i, len; + + len = PyList_GET_SIZE(self->wrapped); + + /* empty arrays are converted to NULLs (still searching for a way to + insert an empty array in postgresql */ + if (len == 0) { + /* it cannot be ARRAY[] because it would make empty lists unusable + * in any() without a cast. But we may convert it into ARRAY[] below */ + res = Bytes_FromString("'{}'"); + goto exit; + } + + if (!(qs = PyMem_New(PyObject *, len))) { + PyErr_NoMemory(); + goto exit; + } + memset(qs, 0, len * sizeof(PyObject *)); + + for (i = 0; i < len; i++) { + PyObject *wrapped = PyList_GET_ITEM(self->wrapped, i); + if (wrapped == Py_None) { + Py_INCREF(psyco_null); + qs[i] = psyco_null; + } + else { + if (!(qs[i] = microprotocol_getquoted( + wrapped, (connectionObject*)self->connection))) { + goto exit; + } + + /* Lists of arrays containing only nulls are also not supported + * by the ARRAY construct so we should do some special casing */ + if (PyList_Check(wrapped)) { + if (Bytes_AS_STRING(qs[i])[0] == 'A') { + all_nulls = 0; + } + else if (0 == strcmp(Bytes_AS_STRING(qs[i]), "'{}'")) { + /* case of issue #788: '{{}}' is not supported but + * array[array[]] is */ + all_nulls = 0; + Py_CLEAR(qs[i]); + if (!(qs[i] = Bytes_FromString("ARRAY[]"))) { + goto exit; + } + } + } + else { + all_nulls = 0; + } + } + bufsize += Bytes_GET_SIZE(qs[i]) + 1; /* this, and a comma */ + } + + /* Create an array literal, usually ARRAY[...] but if the contents are + * all NULL or array of NULL we must use the '{...}' syntax + */ + if (!(ptr = buf = PyMem_Malloc(bufsize + 8))) { + PyErr_NoMemory(); + goto exit; + } + + if (!all_nulls) { + strcpy(ptr, "ARRAY["); + ptr += 6; + for (i = 0; i < len; i++) { + Py_ssize_t sl; + sl = Bytes_GET_SIZE(qs[i]); + memcpy(ptr, Bytes_AS_STRING(qs[i]), sl); + ptr += sl; + *ptr++ = ','; + } + *(ptr - 1) = ']'; + } + else { + *ptr++ = '\''; + *ptr++ = '{'; + for (i = 0; i < len; i++) { + /* in case all the adapted things are nulls (or array of nulls), + * the quoted string is either NULL or an array of the form + * '{NULL,...}', in which case we have to strip the extra quotes */ + char *s; + Py_ssize_t sl; + s = Bytes_AS_STRING(qs[i]); + sl = Bytes_GET_SIZE(qs[i]); + if (s[0] != '\'') { + memcpy(ptr, s, sl); + ptr += sl; + } + else { + memcpy(ptr, s + 1, sl - 2); + ptr += sl - 2; + } + *ptr++ = ','; + } + *(ptr - 1) = '}'; + *ptr++ = '\''; + } + + res = Bytes_FromStringAndSize(buf, ptr - buf); + +exit: + if (qs) { + for (i = 0; i < len; i++) { + PyObject *q = qs[i]; + Py_XDECREF(q); + } + PyMem_Free(qs); + } + PyMem_Free(buf); + + return res; +} + +static PyObject * +list_str(listObject *self) +{ + return psyco_ensure_text(list_quote(self)); +} + +static PyObject * +list_getquoted(listObject *self, PyObject *args) +{ + return list_quote(self); +} + +static PyObject * +list_prepare(listObject *self, PyObject *args) +{ + PyObject *conn; + + if (!PyArg_ParseTuple(args, "O!", &connectionType, &conn)) + return NULL; + + Py_CLEAR(self->connection); + Py_INCREF(conn); + self->connection = conn; + + Py_RETURN_NONE; +} + +static PyObject * +list_conform(listObject *self, PyObject *args) +{ + PyObject *res, *proto; + + if (!PyArg_ParseTuple(args, "O", &proto)) return NULL; + + if (proto == (PyObject*)&isqlquoteType) + res = (PyObject*)self; + else + res = Py_None; + + Py_INCREF(res); + return res; +} + +/** the DateTime wrapper object **/ + +/* object member list */ + +static struct PyMemberDef listObject_members[] = { + {"adapted", T_OBJECT, offsetof(listObject, wrapped), READONLY}, + {NULL} +}; + +/* object method table */ + +static PyMethodDef listObject_methods[] = { + {"getquoted", (PyCFunction)list_getquoted, METH_NOARGS, + "getquoted() -> wrapped object value as SQL date/time"}, + {"prepare", (PyCFunction)list_prepare, METH_VARARGS, + "prepare(conn) -> set encoding to conn->encoding"}, + {"__conform__", (PyCFunction)list_conform, METH_VARARGS, NULL}, + {NULL} /* Sentinel */ +}; + +/* initialization and finalization methods */ + +static int +list_setup(listObject *self, PyObject *obj) +{ + Dprintf("list_setup: init list object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + self, Py_REFCNT(self) + ); + + if (!PyList_Check(obj)) + return -1; + + self->connection = NULL; + Py_INCREF(obj); + self->wrapped = obj; + + Dprintf("list_setup: good list object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + self, Py_REFCNT(self) + ); + return 0; +} + +static int +list_traverse(listObject *self, visitproc visit, void *arg) +{ + Py_VISIT(self->wrapped); + Py_VISIT(self->connection); + return 0; +} + +static int +list_clear(listObject *self) +{ + Py_CLEAR(self->wrapped); + Py_CLEAR(self->connection); + return 0; +} + +static void +list_dealloc(listObject* self) +{ + PyObject_GC_UnTrack((PyObject *)self); + list_clear(self); + + Dprintf("list_dealloc: deleted list object at %p, " + "refcnt = " FORMAT_CODE_PY_SSIZE_T, self, Py_REFCNT(self)); + + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static int +list_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + PyObject *l; + + if (!PyArg_ParseTuple(args, "O", &l)) + return -1; + + return list_setup((listObject *)obj, l); +} + +static PyObject * +list_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + + +/* object type */ + +#define listType_doc \ +"List(list) -> new list wrapper object" + +PyTypeObject listType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2._psycopg.List", + sizeof(listObject), 0, + (destructor)list_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + (reprfunc)list_str, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/ + listType_doc, /*tp_doc*/ + (traverseproc)list_traverse, /*tp_traverse*/ + (inquiry)list_clear, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + listObject_methods, /*tp_methods*/ + listObject_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + list_init, /*tp_init*/ + 0, /*tp_alloc*/ + list_new, /*tp_new*/ +}; diff --git a/source-code/psycopg2/psycopg/adapter_list.h b/source-code/psycopg2/psycopg/adapter_list.h new file mode 100644 index 0000000..2e00b53 --- /dev/null +++ b/source-code/psycopg2/psycopg/adapter_list.h @@ -0,0 +1,47 @@ +/* adapter_list.h - definition for the python list types + * + * Copyright (C) 2004-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_LIST_H +#define PSYCOPG_LIST_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +extern HIDDEN PyTypeObject listType; + +typedef struct { + PyObject_HEAD + + PyObject *wrapped; + PyObject *connection; +} listObject; + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_LIST_H) */ diff --git a/source-code/psycopg2/psycopg/adapter_pboolean.c b/source-code/psycopg2/psycopg/adapter_pboolean.c new file mode 100644 index 0000000..6a28119 --- /dev/null +++ b/source-code/psycopg2/psycopg/adapter_pboolean.c @@ -0,0 +1,185 @@ +/* adapter_pboolean.c - psycopg boolean type wrapper implementation + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/adapter_pboolean.h" +#include "psycopg/microprotocols_proto.h" + +#include + + +/** the Boolean object **/ + +static PyObject * +pboolean_getquoted(pbooleanObject *self, PyObject *args) +{ + if (PyObject_IsTrue(self->wrapped)) { + return Bytes_FromString("true"); + } + else { + return Bytes_FromString("false"); + } +} + +static PyObject * +pboolean_str(pbooleanObject *self) +{ + return psyco_ensure_text(pboolean_getquoted(self, NULL)); +} + +static PyObject * +pboolean_conform(pbooleanObject *self, PyObject *args) +{ + PyObject *res, *proto; + + if (!PyArg_ParseTuple(args, "O", &proto)) return NULL; + + if (proto == (PyObject*)&isqlquoteType) + res = (PyObject*)self; + else + res = Py_None; + + Py_INCREF(res); + return res; +} + +/** the Boolean object */ + +/* object member list */ + +static struct PyMemberDef pbooleanObject_members[] = { + {"adapted", T_OBJECT, offsetof(pbooleanObject, wrapped), READONLY}, + {NULL} +}; + +/* object method table */ + +static PyMethodDef pbooleanObject_methods[] = { + {"getquoted", (PyCFunction)pboolean_getquoted, METH_NOARGS, + "getquoted() -> wrapped object value as SQL-quoted string"}, + {"__conform__", (PyCFunction)pboolean_conform, METH_VARARGS, NULL}, + {NULL} /* Sentinel */ +}; + +/* initialization and finalization methods */ + +static int +pboolean_setup(pbooleanObject *self, PyObject *obj) +{ + Dprintf("pboolean_setup: init pboolean object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + self, Py_REFCNT(self) + ); + + Py_INCREF(obj); + self->wrapped = obj; + + Dprintf("pboolean_setup: good pboolean object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + self, Py_REFCNT(self) + ); + return 0; +} + +static void +pboolean_dealloc(PyObject* obj) +{ + pbooleanObject *self = (pbooleanObject *)obj; + + Py_CLEAR(self->wrapped); + + Dprintf("pboolean_dealloc: deleted pboolean object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + obj, Py_REFCNT(obj) + ); + + Py_TYPE(obj)->tp_free(obj); +} + +static int +pboolean_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + PyObject *o; + + if (!PyArg_ParseTuple(args, "O", &o)) + return -1; + + return pboolean_setup((pbooleanObject *)obj, o); +} + +static PyObject * +pboolean_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + + +/* object type */ + +#define pbooleanType_doc \ +"Boolean(str) -> new Boolean adapter object" + +PyTypeObject pbooleanType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2.extensions.Boolean", + sizeof(pbooleanObject), 0, + pboolean_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + (reprfunc)pboolean_str, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + pbooleanType_doc, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + pbooleanObject_methods, /*tp_methods*/ + pbooleanObject_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + pboolean_init, /*tp_init*/ + 0, /*tp_alloc*/ + pboolean_new, /*tp_new*/ +}; diff --git a/source-code/psycopg2/psycopg/adapter_pboolean.h b/source-code/psycopg2/psycopg/adapter_pboolean.h new file mode 100644 index 0000000..562fedc --- /dev/null +++ b/source-code/psycopg2/psycopg/adapter_pboolean.h @@ -0,0 +1,48 @@ +/* adapter_pboolean.h - definition for the psycopg boolean type wrapper + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_PBOOLEAN_H +#define PSYCOPG_PBOOLEAN_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +extern HIDDEN PyTypeObject pbooleanType; + +typedef struct { + PyObject_HEAD + + /* this is the real object we wrap */ + PyObject *wrapped; + +} pbooleanObject; + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_PBOOLEAN_H) */ diff --git a/source-code/psycopg2/psycopg/adapter_pdecimal.c b/source-code/psycopg2/psycopg/adapter_pdecimal.c new file mode 100644 index 0000000..25a7212 --- /dev/null +++ b/source-code/psycopg2/psycopg/adapter_pdecimal.c @@ -0,0 +1,248 @@ +/* adapter_pdecimal.c - psycopg Decimal type wrapper implementation + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/adapter_pdecimal.h" +#include "psycopg/microprotocols_proto.h" + +#include +#include + + +/** the Decimal object **/ + +static PyObject * +pdecimal_getquoted(pdecimalObject *self, PyObject *args) +{ + PyObject *check, *res = NULL; + check = PyObject_CallMethod(self->wrapped, "is_finite", NULL); + if (check == Py_True) { + if (!(res = PyObject_Str(self->wrapped))) { + goto end; + } + goto output; + } + else if (check) { + res = Bytes_FromString("'NaN'::numeric"); + goto end; + } + + /* is_finite() was introduced 2.5.1 < somewhere <= 2.5.4. + * We assume we are here because we didn't find the method. */ + PyErr_Clear(); + + if (!(check = PyObject_CallMethod(self->wrapped, "_isnan", NULL))) { + goto end; + } + if (PyObject_IsTrue(check)) { + res = Bytes_FromString("'NaN'::numeric"); + goto end; + } + + Py_DECREF(check); + if (!(check = PyObject_CallMethod(self->wrapped, "_isinfinity", NULL))) { + goto end; + } + if (PyObject_IsTrue(check)) { + res = Bytes_FromString("'NaN'::numeric"); + goto end; + } + + /* wrapped is finite */ + if (!(res = PyObject_Str(self->wrapped))) { + goto end; + } + + /* res may be unicode and may suffer for issue #57 */ +output: + + /* unicode to bytes */ + { + PyObject *tmp = PyUnicode_AsUTF8String(res); + Py_DECREF(res); + if (!(res = tmp)) { + goto end; + } + } + + if ('-' == Bytes_AS_STRING(res)[0]) { + /* Prepend a space in front of negative numbers (ticket #57) */ + PyObject *tmp; + if (!(tmp = Bytes_FromString(" "))) { + Py_DECREF(res); + res = NULL; + goto end; + } + Bytes_ConcatAndDel(&tmp, res); + if (!(res = tmp)) { + goto end; + } + } + +end: + Py_XDECREF(check); + return res; +} + +static PyObject * +pdecimal_str(pdecimalObject *self) +{ + return psyco_ensure_text(pdecimal_getquoted(self, NULL)); +} + +static PyObject * +pdecimal_conform(pdecimalObject *self, PyObject *args) +{ + PyObject *res, *proto; + + if (!PyArg_ParseTuple(args, "O", &proto)) return NULL; + + if (proto == (PyObject*)&isqlquoteType) + res = (PyObject*)self; + else + res = Py_None; + + Py_INCREF(res); + return res; +} + +/** the Decimal object */ + +/* object member list */ + +static struct PyMemberDef pdecimalObject_members[] = { + {"adapted", T_OBJECT, offsetof(pdecimalObject, wrapped), READONLY}, + {NULL} +}; + +/* object method table */ + +static PyMethodDef pdecimalObject_methods[] = { + {"getquoted", (PyCFunction)pdecimal_getquoted, METH_NOARGS, + "getquoted() -> wrapped object value as SQL-quoted string"}, + {"__conform__", (PyCFunction)pdecimal_conform, METH_VARARGS, NULL}, + {NULL} /* Sentinel */ +}; + +/* initialization and finalization methods */ + +static int +pdecimal_setup(pdecimalObject *self, PyObject *obj) +{ + Dprintf("pdecimal_setup: init pdecimal object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + self, Py_REFCNT(self) + ); + + Py_INCREF(obj); + self->wrapped = obj; + + Dprintf("pdecimal_setup: good pdecimal object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + self, Py_REFCNT(self) + ); + return 0; +} + +static void +pdecimal_dealloc(PyObject* obj) +{ + pdecimalObject *self = (pdecimalObject *)obj; + + Py_CLEAR(self->wrapped); + + Dprintf("pdecimal_dealloc: deleted pdecimal object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + obj, Py_REFCNT(obj) + ); + + Py_TYPE(obj)->tp_free(obj); +} + +static int +pdecimal_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + PyObject *o; + + if (!PyArg_ParseTuple(args, "O", &o)) + return -1; + + return pdecimal_setup((pdecimalObject *)obj, o); +} + +static PyObject * +pdecimal_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + + +/* object type */ + +#define pdecimalType_doc \ +"Decimal(str) -> new Decimal adapter object" + +PyTypeObject pdecimalType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2._psycopg.Decimal", + sizeof(pdecimalObject), 0, + pdecimal_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + (reprfunc)pdecimal_str, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + pdecimalType_doc, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + pdecimalObject_methods, /*tp_methods*/ + pdecimalObject_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + pdecimal_init, /*tp_init*/ + 0, /*tp_alloc*/ + pdecimal_new, /*tp_new*/ +}; diff --git a/source-code/psycopg2/psycopg/adapter_pdecimal.h b/source-code/psycopg2/psycopg/adapter_pdecimal.h new file mode 100644 index 0000000..24b5ec5 --- /dev/null +++ b/source-code/psycopg2/psycopg/adapter_pdecimal.h @@ -0,0 +1,48 @@ +/* adapter_pdecimal.h - definition for the psycopg Decimal type wrapper + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_PDECIMAL_H +#define PSYCOPG_PDECIMAL_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +extern HIDDEN PyTypeObject pdecimalType; + +typedef struct { + PyObject_HEAD + + /* this is the real object we wrap */ + PyObject *wrapped; + +} pdecimalObject; + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_PDECIMAL_H) */ diff --git a/source-code/psycopg2/psycopg/adapter_pfloat.c b/source-code/psycopg2/psycopg/adapter_pfloat.c new file mode 100644 index 0000000..9893523 --- /dev/null +++ b/source-code/psycopg2/psycopg/adapter_pfloat.c @@ -0,0 +1,221 @@ +/* adapter_float.c - psycopg pfloat type wrapper implementation + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/adapter_pfloat.h" +#include "psycopg/microprotocols_proto.h" + +#include +#include + + +/** the Float object **/ + +static PyObject * +pfloat_getquoted(pfloatObject *self, PyObject *args) +{ + PyObject *rv; + double n = PyFloat_AsDouble(self->wrapped); + if (isnan(n)) + rv = Bytes_FromString("'NaN'::float"); + else if (isinf(n)) { + if (n > 0) + rv = Bytes_FromString("'Infinity'::float"); + else + rv = Bytes_FromString("'-Infinity'::float"); + } + else { + if (!(rv = PyObject_Repr(self->wrapped))) { + goto exit; + } + + /* unicode to bytes */ + { + PyObject *tmp = PyUnicode_AsUTF8String(rv); + Py_DECREF(rv); + if (!(rv = tmp)) { + goto exit; + } + } + + if ('-' == Bytes_AS_STRING(rv)[0]) { + /* Prepend a space in front of negative numbers (ticket #57) */ + PyObject *tmp; + if (!(tmp = Bytes_FromString(" "))) { + Py_DECREF(rv); + rv = NULL; + goto exit; + } + Bytes_ConcatAndDel(&tmp, rv); + if (!(rv = tmp)) { + goto exit; + } + } + } + +exit: + return rv; +} + +static PyObject * +pfloat_str(pfloatObject *self) +{ + return psyco_ensure_text(pfloat_getquoted(self, NULL)); +} + +static PyObject * +pfloat_conform(pfloatObject *self, PyObject *args) +{ + PyObject *res, *proto; + + if (!PyArg_ParseTuple(args, "O", &proto)) return NULL; + + if (proto == (PyObject*)&isqlquoteType) + res = (PyObject*)self; + else + res = Py_None; + + Py_INCREF(res); + return res; +} + +/** the Float object */ + +/* object member list */ + +static struct PyMemberDef pfloatObject_members[] = { + {"adapted", T_OBJECT, offsetof(pfloatObject, wrapped), READONLY}, + {NULL} +}; + +/* object method table */ + +static PyMethodDef pfloatObject_methods[] = { + {"getquoted", (PyCFunction)pfloat_getquoted, METH_NOARGS, + "getquoted() -> wrapped object value as SQL-quoted string"}, + {"__conform__", (PyCFunction)pfloat_conform, METH_VARARGS, NULL}, + {NULL} /* Sentinel */ +}; + +/* initialization and finalization methods */ + +static int +pfloat_setup(pfloatObject *self, PyObject *obj) +{ + Dprintf("pfloat_setup: init pfloat object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + self, Py_REFCNT(self) + ); + + Py_INCREF(obj); + self->wrapped = obj; + + Dprintf("pfloat_setup: good pfloat object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + self, Py_REFCNT(self) + ); + return 0; +} + +static void +pfloat_dealloc(PyObject* obj) +{ + pfloatObject *self = (pfloatObject *)obj; + + Py_CLEAR(self->wrapped); + + Dprintf("pfloat_dealloc: deleted pfloat object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + obj, Py_REFCNT(obj) + ); + + Py_TYPE(obj)->tp_free(obj); +} + +static int +pfloat_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + PyObject *o; + + if (!PyArg_ParseTuple(args, "O", &o)) + return -1; + + return pfloat_setup((pfloatObject *)obj, o); +} + +static PyObject * +pfloat_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + + +/* object type */ + +#define pfloatType_doc \ +"Float(str) -> new Float adapter object" + +PyTypeObject pfloatType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2.extensions.Float", + sizeof(pfloatObject), 0, + pfloat_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + (reprfunc)pfloat_str, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + pfloatType_doc, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + pfloatObject_methods, /*tp_methods*/ + pfloatObject_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + pfloat_init, /*tp_init*/ + 0, /*tp_alloc*/ + pfloat_new, /*tp_new*/ +}; diff --git a/source-code/psycopg2/psycopg/adapter_pfloat.h b/source-code/psycopg2/psycopg/adapter_pfloat.h new file mode 100644 index 0000000..8a12564 --- /dev/null +++ b/source-code/psycopg2/psycopg/adapter_pfloat.h @@ -0,0 +1,48 @@ +/* adapter_pfloat.h - definition for the psycopg float type wrapper + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_PFLOAT_H +#define PSYCOPG_PFLOAT_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +extern HIDDEN PyTypeObject pfloatType; + +typedef struct { + PyObject_HEAD + + /* this is the real object we wrap */ + PyObject *wrapped; + +} pfloatObject; + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_PFLOAT_H) */ diff --git a/source-code/psycopg2/psycopg/adapter_pint.c b/source-code/psycopg2/psycopg/adapter_pint.c new file mode 100644 index 0000000..d3cf508 --- /dev/null +++ b/source-code/psycopg2/psycopg/adapter_pint.c @@ -0,0 +1,222 @@ +/* adapter_int.c - psycopg pint type wrapper implementation + * + * Copyright (C) 2011-2019 Daniele Varrazzo + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/adapter_pint.h" +#include "psycopg/microprotocols_proto.h" + + +/** the Int object **/ + +static PyObject * +pint_getquoted(pintObject *self, PyObject *args) +{ + PyObject *res = NULL; + + /* Convert subclass to int to handle IntEnum and other subclasses + * whose str() is not the number. */ + if (PyLong_CheckExact(self->wrapped)) { + res = PyObject_Str(self->wrapped); + } else { + PyObject *tmp; + if (!(tmp = PyObject_CallFunctionObjArgs( + (PyObject *)&PyLong_Type, self->wrapped, NULL))) { + goto exit; + } + res = PyObject_Str(tmp); + Py_DECREF(tmp); + } + + if (!res) { + goto exit; + } + + /* unicode to bytes */ + { + PyObject *tmp = PyUnicode_AsUTF8String(res); + Py_DECREF(res); + if (!(res = tmp)) { + goto exit; + } + } + + if ('-' == Bytes_AS_STRING(res)[0]) { + /* Prepend a space in front of negative numbers (ticket #57) */ + PyObject *tmp; + if (!(tmp = Bytes_FromString(" "))) { + Py_DECREF(res); + res = NULL; + goto exit; + } + Bytes_ConcatAndDel(&tmp, res); + if (!(res = tmp)) { + goto exit; + } + } + +exit: + return res; +} + +static PyObject * +pint_str(pintObject *self) +{ + return psyco_ensure_text(pint_getquoted(self, NULL)); +} + +static PyObject * +pint_conform(pintObject *self, PyObject *args) +{ + PyObject *res, *proto; + + if (!PyArg_ParseTuple(args, "O", &proto)) return NULL; + + if (proto == (PyObject*)&isqlquoteType) + res = (PyObject*)self; + else + res = Py_None; + + Py_INCREF(res); + return res; +} + +/** the int object */ + +/* object member list */ + +static struct PyMemberDef pintObject_members[] = { + {"adapted", T_OBJECT, offsetof(pintObject, wrapped), READONLY}, + {NULL} +}; + +/* object method table */ + +static PyMethodDef pintObject_methods[] = { + {"getquoted", (PyCFunction)pint_getquoted, METH_NOARGS, + "getquoted() -> wrapped object value as SQL-quoted string"}, + {"__conform__", (PyCFunction)pint_conform, METH_VARARGS, NULL}, + {NULL} /* Sentinel */ +}; + +/* initialization and finalization methods */ + +static int +pint_setup(pintObject *self, PyObject *obj) +{ + Dprintf("pint_setup: init pint object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + self, Py_REFCNT(self) + ); + + Py_INCREF(obj); + self->wrapped = obj; + + Dprintf("pint_setup: good pint object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + self, Py_REFCNT(self) + ); + return 0; +} + +static void +pint_dealloc(PyObject* obj) +{ + pintObject *self = (pintObject *)obj; + + Py_CLEAR(self->wrapped); + + Dprintf("pint_dealloc: deleted pint object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + obj, Py_REFCNT(obj) + ); + + Py_TYPE(obj)->tp_free(obj); +} + +static int +pint_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + PyObject *o; + + if (!PyArg_ParseTuple(args, "O", &o)) + return -1; + + return pint_setup((pintObject *)obj, o); +} + +static PyObject * +pint_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + + +/* object type */ + +#define pintType_doc \ +"Int(str) -> new Int adapter object" + +PyTypeObject pintType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2.extensions.Int", + sizeof(pintObject), 0, + pint_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + (reprfunc)pint_str, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + pintType_doc, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + pintObject_methods, /*tp_methods*/ + pintObject_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + pint_init, /*tp_init*/ + 0, /*tp_alloc*/ + pint_new, /*tp_new*/ +}; diff --git a/source-code/psycopg2/psycopg/adapter_pint.h b/source-code/psycopg2/psycopg/adapter_pint.h new file mode 100644 index 0000000..49ad8b2 --- /dev/null +++ b/source-code/psycopg2/psycopg/adapter_pint.h @@ -0,0 +1,48 @@ +/* adapter_pint.h - definition for the psycopg int type wrapper + * + * Copyright (C) 2011-2019 Daniele Varrazzo + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_PINT_H +#define PSYCOPG_PINT_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +extern HIDDEN PyTypeObject pintType; + +typedef struct { + PyObject_HEAD + + /* this is the real object we wrap */ + PyObject *wrapped; + +} pintObject; + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_PINT_H) */ diff --git a/source-code/psycopg2/psycopg/adapter_qstring.c b/source-code/psycopg2/psycopg/adapter_qstring.c new file mode 100644 index 0000000..3a3ad63 --- /dev/null +++ b/source-code/psycopg2/psycopg/adapter_qstring.c @@ -0,0 +1,307 @@ +/* adapter_qstring.c - QuotedString objects + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/connection.h" +#include "psycopg/adapter_qstring.h" +#include "psycopg/microprotocols_proto.h" + +#include + +static const char *default_encoding = "latin1"; + +/* qstring_quote - do the quote process on plain and unicode strings */ + +static PyObject * +qstring_quote(qstringObject *self) +{ + PyObject *str = NULL; + char *s, *buffer = NULL; + Py_ssize_t len, qlen; + const char *encoding; + PyObject *rv = NULL; + + if (PyUnicode_Check(self->wrapped)) { + if (self->conn) { + if (!(str = conn_encode(self->conn, self->wrapped))) { goto exit; } + } + else { + encoding = self->encoding ? self->encoding : default_encoding; + if(!(str = PyUnicode_AsEncodedString(self->wrapped, encoding, NULL))) { + goto exit; + } + } + } + + /* if the wrapped object is a binary string, we don't know how to + (re)encode it, so we pass it as-is */ + else if (Bytes_Check(self->wrapped)) { + str = self->wrapped; + /* INCREF to make it ref-wise identical to unicode one */ + Py_INCREF(str); + } + + /* if the wrapped object is not a string, this is an error */ + else { + PyErr_SetString(PyExc_TypeError, "can't quote non-string object"); + goto exit; + } + + /* encode the string into buffer */ + Bytes_AsStringAndSize(str, &s, &len); + if (!(buffer = psyco_escape_string(self->conn, s, len, NULL, &qlen))) { + goto exit; + } + + if (qlen > PY_SSIZE_T_MAX) { + PyErr_SetString(PyExc_IndexError, + "PG buffer too large to fit in Python buffer."); + goto exit; + } + + rv = Bytes_FromStringAndSize(buffer, qlen); + +exit: + PyMem_Free(buffer); + Py_XDECREF(str); + + return rv; +} + +/* qstring_str, qstring_getquoted - return result of quoting */ + +static PyObject * +qstring_getquoted(qstringObject *self, PyObject *args) +{ + if (self->buffer == NULL) { + self->buffer = qstring_quote(self); + } + Py_XINCREF(self->buffer); + return self->buffer; +} + +static PyObject * +qstring_str(qstringObject *self) +{ + return psyco_ensure_text(qstring_getquoted(self, NULL)); +} + +static PyObject * +qstring_prepare(qstringObject *self, PyObject *args) +{ + PyObject *conn; + + if (!PyArg_ParseTuple(args, "O!", &connectionType, &conn)) + return NULL; + + Py_CLEAR(self->conn); + Py_INCREF(conn); + self->conn = (connectionObject *)conn; + + Py_RETURN_NONE; +} + +static PyObject * +qstring_conform(qstringObject *self, PyObject *args) +{ + PyObject *res, *proto; + + if (!PyArg_ParseTuple(args, "O", &proto)) return NULL; + + if (proto == (PyObject*)&isqlquoteType) + res = (PyObject*)self; + else + res = Py_None; + + Py_INCREF(res); + return res; +} + +static PyObject * +qstring_get_encoding(qstringObject *self) +{ + if (self->conn) { + return conn_pgenc_to_pyenc(self->conn->encoding, NULL); + } + else { + return Text_FromUTF8(self->encoding ? self->encoding : default_encoding); + } +} + +static int +qstring_set_encoding(qstringObject *self, PyObject *pyenc) +{ + int rv = -1; + const char *tmp; + char *cenc; + + /* get a C copy of the encoding (which may come from unicode) */ + Py_INCREF(pyenc); + if (!(pyenc = psyco_ensure_bytes(pyenc))) { goto exit; } + if (!(tmp = Bytes_AsString(pyenc))) { goto exit; } + if (0 > psyco_strdup(&cenc, tmp, -1)) { goto exit; } + + Dprintf("qstring_set_encoding: encoding set to %s", cenc); + PyMem_Free((void *)self->encoding); + self->encoding = cenc; + rv = 0; + +exit: + Py_XDECREF(pyenc); + return rv; +} + +/** the QuotedString object **/ + +/* object member list */ + +static struct PyMemberDef qstringObject_members[] = { + {"adapted", T_OBJECT, offsetof(qstringObject, wrapped), READONLY}, + {"buffer", T_OBJECT, offsetof(qstringObject, buffer), READONLY}, + {NULL} +}; + +/* object method table */ + +static PyMethodDef qstringObject_methods[] = { + {"getquoted", (PyCFunction)qstring_getquoted, METH_NOARGS, + "getquoted() -> wrapped object value as SQL-quoted string"}, + {"prepare", (PyCFunction)qstring_prepare, METH_VARARGS, + "prepare(conn) -> set encoding to conn->encoding and store conn"}, + {"__conform__", (PyCFunction)qstring_conform, METH_VARARGS, NULL}, + {NULL} /* Sentinel */ +}; + +static PyGetSetDef qstringObject_getsets[] = { + { "encoding", + (getter)qstring_get_encoding, + (setter)qstring_set_encoding, + "current encoding of the adapter" }, + {NULL} +}; + +/* initialization and finalization methods */ + +static int +qstring_setup(qstringObject *self, PyObject *str) +{ + Dprintf("qstring_setup: init qstring object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + self, Py_REFCNT(self) + ); + + Py_INCREF(str); + self->wrapped = str; + + Dprintf("qstring_setup: good qstring object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + self, Py_REFCNT(self) + ); + return 0; +} + +static void +qstring_dealloc(PyObject* obj) +{ + qstringObject *self = (qstringObject *)obj; + + Py_CLEAR(self->wrapped); + Py_CLEAR(self->buffer); + Py_CLEAR(self->conn); + PyMem_Free((void *)self->encoding); + + Dprintf("qstring_dealloc: deleted qstring object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + obj, Py_REFCNT(obj) + ); + + Py_TYPE(obj)->tp_free(obj); +} + +static int +qstring_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + PyObject *str; + + if (!PyArg_ParseTuple(args, "O", &str)) + return -1; + + return qstring_setup((qstringObject *)obj, str); +} + +static PyObject * +qstring_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + + +/* object type */ + +#define qstringType_doc \ +"QuotedString(str) -> new quoted object" + +PyTypeObject qstringType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2.extensions.QuotedString", + sizeof(qstringObject), 0, + qstring_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + (reprfunc)qstring_str, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + qstringType_doc, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + qstringObject_methods, /*tp_methods*/ + qstringObject_members, /*tp_members*/ + qstringObject_getsets, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + qstring_init, /*tp_init*/ + 0, /*tp_alloc*/ + qstring_new, /*tp_new*/ +}; diff --git a/source-code/psycopg2/psycopg/adapter_qstring.h b/source-code/psycopg2/psycopg/adapter_qstring.h new file mode 100644 index 0000000..7e139ba --- /dev/null +++ b/source-code/psycopg2/psycopg/adapter_qstring.h @@ -0,0 +1,52 @@ +/* adapter_qstring.h - definition for the QuotedString type + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_QSTRING_H +#define PSYCOPG_QSTRING_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +extern HIDDEN PyTypeObject qstringType; + +typedef struct { + PyObject_HEAD + + PyObject *wrapped; + PyObject *buffer; + + connectionObject *conn; + + const char *encoding; + +} qstringObject; + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_QSTRING_H) */ diff --git a/source-code/psycopg2/psycopg/aix_support.c b/source-code/psycopg2/psycopg/aix_support.c new file mode 100644 index 0000000..941bcab --- /dev/null +++ b/source-code/psycopg2/psycopg/aix_support.c @@ -0,0 +1,58 @@ +/* aix_support.c - emulate functions missing on AIX + * + * Copyright (C) 2017 My Karlsson + * Copyright (c) 2018, Joyent, Inc. + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" +#include "psycopg/aix_support.h" + +#if defined(_AIX) +/* timeradd is missing on AIX */ +#ifndef timeradd +void +timeradd(struct timeval *a, struct timeval *b, struct timeval *c) +{ + c->tv_sec = a->tv_sec + b->tv_sec; + c->tv_usec = a->tv_usec + b->tv_usec; + if (c->tv_usec >= 1000000) { + c->tv_usec -= 1000000; + c->tv_sec += 1; + } +} + +/* timersub is missing on AIX */ +void +timersub(struct timeval *a, struct timeval *b, struct timeval *c) +{ + c->tv_sec = a->tv_sec - b->tv_sec; + c->tv_usec = a->tv_usec - b->tv_usec; + if (c->tv_usec < 0) { + c->tv_usec += 1000000; + c->tv_sec -= 1; + } +} +#endif /* timeradd */ +#endif /* defined(_AIX)*/ diff --git a/source-code/psycopg2/psycopg/aix_support.h b/source-code/psycopg2/psycopg/aix_support.h new file mode 100644 index 0000000..14c1220 --- /dev/null +++ b/source-code/psycopg2/psycopg/aix_support.h @@ -0,0 +1,48 @@ +/* aix_support.h - definitions for aix_support.c + * + * Copyright (C) 2017 My Karlsson + * Copyright (c) 2018-2019, Joyent, Inc. + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ +#ifndef PSYCOPG_AIX_SUPPORT_H +#define PSYCOPG_AIX_SUPPORT_H + +#include "psycopg/config.h" + +#ifdef _AIX +#include + +#ifndef timeradd +extern HIDDEN void timeradd(struct timeval *a, struct timeval *b, struct timeval *c); +extern HIDDEN void timersub(struct timeval *a, struct timeval *b, struct timeval *c); +#endif + +#ifndef timercmp +#define timercmp(a, b, cmp) \ + (((a)->tv_sec == (b)->tv_sec) ? \ + ((a)->tv_usec cmp (b)->tv_usec) : \ + ((a)->tv_sec cmp (b)->tv_sec)) +#endif +#endif + +#endif /* !defined(PSYCOPG_AIX_SUPPORT_H) */ diff --git a/source-code/psycopg2/psycopg/bytes_format.c b/source-code/psycopg2/psycopg/bytes_format.c new file mode 100644 index 0000000..d34a017 --- /dev/null +++ b/source-code/psycopg2/psycopg/bytes_format.c @@ -0,0 +1,309 @@ +/* bytes_format.c - bytes-oriented version of PyString_Format + * + * Copyright (C) 2010-2019 Daniele Varrazzo + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +/* This implementation is based on the PyString_Format function available in + * Python 2.7.1. The function is altered to be used with both Python 2 strings + * and Python 3 bytes and is stripped of the support of formats different than + * 's'. Original license follows. + * + * PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 + * -------------------------------------------- + * + * 1. This LICENSE AGREEMENT is between the Python Software Foundation + * ("PSF"), and the Individual or Organization ("Licensee") accessing and + * otherwise using this software ("Python") in source or binary form and + * its associated documentation. + * + * 2. Subject to the terms and conditions of this License Agreement, PSF hereby + * grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, + * analyze, test, perform and/or display publicly, prepare derivative works, + * distribute, and otherwise use Python alone or in any derivative version, + * provided, however, that PSF's License Agreement and PSF's notice of copyright, + * i.e., "Copyright (c) 2001-2019, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Python Software Foundation; All Rights Reserved" are retained in Python alone or + * in any derivative version prepared by Licensee. + * + * 3. In the event Licensee prepares a derivative work that is based on + * or incorporates Python or any part thereof, and wants to make + * the derivative work available to others as provided herein, then + * Licensee hereby agrees to include in any such work a brief summary of + * the changes made to Python. + * + * 4. PSF is making Python available to Licensee on an "AS IS" + * basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR + * IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND + * DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS + * FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT + * INFRINGE ANY THIRD PARTY RIGHTS. + * + * 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON + * FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS + * A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, + * OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + * + * 6. This License Agreement will automatically terminate upon a material + * breach of its terms and conditions. + * + * 7. Nothing in this License Agreement shall be deemed to create any + * relationship of agency, partnership, or joint venture between PSF and + * Licensee. This License Agreement does not grant permission to use PSF + * trademarks or trade name in a trademark sense to endorse or promote + * products or services of Licensee, or any third party. + * + * 8. By copying, installing or otherwise using Python, Licensee + * agrees to be bound by the terms and conditions of this License + * Agreement. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" +#include "pyport.h" + +/* Helpers for formatstring */ + +BORROWED Py_LOCAL_INLINE(PyObject *) +getnextarg(PyObject *args, Py_ssize_t arglen, Py_ssize_t *p_argidx) +{ + Py_ssize_t argidx = *p_argidx; + if (argidx < arglen) { + (*p_argidx)++; + if (arglen < 0) + return args; + else + return PyTuple_GetItem(args, argidx); + } + PyErr_SetString(PyExc_TypeError, + "not enough arguments for format string"); + return NULL; +} + +/* wrapper around _Bytes_Resize offering normal Python call semantics */ + +STEALS(1) +Py_LOCAL_INLINE(PyObject *) +resize_bytes(PyObject *b, Py_ssize_t newsize) { + if (0 == _Bytes_Resize(&b, newsize)) { + return b; + } + else { + return NULL; + } +} + +/* fmt%(v1,v2,...) is roughly equivalent to sprintf(fmt, v1, v2, ...) */ + +PyObject * +Bytes_Format(PyObject *format, PyObject *args) +{ + char *fmt, *res; + Py_ssize_t arglen, argidx; + Py_ssize_t reslen, rescnt, fmtcnt; + int args_owned = 0; + PyObject *result; + PyObject *dict = NULL; + if (format == NULL || !Bytes_Check(format) || args == NULL) { + PyErr_SetString(PyExc_SystemError, "bad argument to internal function"); + return NULL; + } + fmt = Bytes_AS_STRING(format); + fmtcnt = Bytes_GET_SIZE(format); + reslen = rescnt = fmtcnt + 100; + result = Bytes_FromStringAndSize((char *)NULL, reslen); + if (result == NULL) + return NULL; + res = Bytes_AS_STRING(result); + if (PyTuple_Check(args)) { + arglen = PyTuple_GET_SIZE(args); + argidx = 0; + } + else { + arglen = -1; + argidx = -2; + } + if (Py_TYPE(args)->tp_as_mapping && !PyTuple_Check(args) && + !PyObject_TypeCheck(args, &Bytes_Type)) + dict = args; + while (--fmtcnt >= 0) { + if (*fmt != '%') { + if (--rescnt < 0) { + rescnt = fmtcnt + 100; + reslen += rescnt; + if (!(result = resize_bytes(result, reslen))) { + return NULL; + } + res = Bytes_AS_STRING(result) + reslen - rescnt; + --rescnt; + } + *res++ = *fmt++; + } + else { + /* Got a format specifier */ + Py_ssize_t width = -1; + int c = '\0'; + PyObject *v = NULL; + PyObject *temp = NULL; + char *pbuf; + Py_ssize_t len; + fmt++; + if (*fmt == '(') { + char *keystart; + Py_ssize_t keylen; + PyObject *key; + int pcount = 1; + + if (dict == NULL) { + PyErr_SetString(PyExc_TypeError, + "format requires a mapping"); + goto error; + } + ++fmt; + --fmtcnt; + keystart = fmt; + /* Skip over balanced parentheses */ + while (pcount > 0 && --fmtcnt >= 0) { + if (*fmt == ')') + --pcount; + else if (*fmt == '(') + ++pcount; + fmt++; + } + keylen = fmt - keystart - 1; + if (fmtcnt < 0 || pcount > 0) { + PyErr_SetString(PyExc_ValueError, + "incomplete format key"); + goto error; + } + key = Text_FromUTF8AndSize(keystart, keylen); + if (key == NULL) + goto error; + if (args_owned) { + Py_DECREF(args); + args_owned = 0; + } + args = PyObject_GetItem(dict, key); + Py_DECREF(key); + if (args == NULL) { + goto error; + } + args_owned = 1; + arglen = -1; + argidx = -2; + } + while (--fmtcnt >= 0) { + c = *fmt++; + break; + } + if (fmtcnt < 0) { + PyErr_SetString(PyExc_ValueError, + "incomplete format"); + goto error; + } + switch (c) { + case '%': + pbuf = "%"; + len = 1; + break; + case 's': + /* only bytes! */ + if (!(v = getnextarg(args, arglen, &argidx))) + goto error; + if (!Bytes_CheckExact(v)) { + PyErr_Format(PyExc_ValueError, + "only bytes values expected, got %s", + Py_TYPE(v)->tp_name); + goto error; + } + temp = v; + Py_INCREF(v); + pbuf = Bytes_AS_STRING(temp); + len = Bytes_GET_SIZE(temp); + break; + default: + PyErr_Format(PyExc_ValueError, + "unsupported format character '%c' (0x%x) " + "at index " FORMAT_CODE_PY_SSIZE_T, + c, c, + (Py_ssize_t)(fmt - 1 - Bytes_AS_STRING(format))); + goto error; + } + if (width < len) + width = len; + if (rescnt < width) { + reslen -= rescnt; + rescnt = width + fmtcnt + 100; + reslen += rescnt; + if (reslen < 0) { + Py_DECREF(result); + Py_XDECREF(temp); + if (args_owned) + Py_DECREF(args); + return PyErr_NoMemory(); + } + if (!(result = resize_bytes(result, reslen))) { + Py_XDECREF(temp); + if (args_owned) + Py_DECREF(args); + return NULL; + } + res = Bytes_AS_STRING(result) + + reslen - rescnt; + } + Py_MEMCPY(res, pbuf, len); + res += len; + rescnt -= len; + while (--width >= len) { + --rescnt; + *res++ = ' '; + } + if (dict && (argidx < arglen) && c != '%') { + PyErr_SetString(PyExc_TypeError, + "not all arguments converted during string formatting"); + Py_XDECREF(temp); + goto error; + } + Py_XDECREF(temp); + } /* '%' */ + } /* until end */ + if (argidx < arglen && !dict) { + PyErr_SetString(PyExc_TypeError, + "not all arguments converted during string formatting"); + goto error; + } + if (args_owned) { + Py_DECREF(args); + } + if (!(result = resize_bytes(result, reslen - rescnt))) { + return NULL; + } + return result; + + error: + Py_DECREF(result); + if (args_owned) { + Py_DECREF(args); + } + return NULL; +} diff --git a/source-code/psycopg2/psycopg/column.h b/source-code/psycopg2/psycopg/column.h new file mode 100644 index 0000000..1173fb5 --- /dev/null +++ b/source-code/psycopg2/psycopg/column.h @@ -0,0 +1,49 @@ +/* column.h - definition for a column in cursor.description type + * + * Copyright (C) 2018-2019 Daniele Varrazzo + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_COLUMN_H +#define PSYCOPG_COLUMN_H 1 + +extern HIDDEN PyTypeObject columnType; + +typedef struct { + PyObject_HEAD + + PyObject *name; + PyObject *type_code; + PyObject *display_size; + PyObject *internal_size; + PyObject *precision; + PyObject *scale; + PyObject *null_ok; + + /* Extensions to the DBAPI */ + PyObject *table_oid; + PyObject *table_column; + +} columnObject; + +#endif /* PSYCOPG_COLUMN_H */ diff --git a/source-code/psycopg2/psycopg/column_type.c b/source-code/psycopg2/psycopg/column_type.c new file mode 100644 index 0000000..2f98950 --- /dev/null +++ b/source-code/psycopg2/psycopg/column_type.c @@ -0,0 +1,420 @@ +/* column_type.c - python interface to cursor.description objects + * + * Copyright (C) 2018-2019 Daniele Varrazzo + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/column.h" + + +static const char column_doc[] = + "Description of a column returned by a query.\n\n" + "The DBAPI demands this object to be a 7-items sequence. This object\n" + "respects this interface, but adds names for the exposed attributes\n" + "and adds attribute not requested by the DBAPI."; + +static const char name_doc[] = + "The name of the column returned."; + +static const char type_code_doc[] = + "The PostgreSQL OID of the column.\n\n" + "You can use the pg_type system table to get more informations about the\n" + "type. This is the value used by Psycopg to decide what Python type use\n" + "to represent the value"; + +static const char display_size_doc[] = + "The actual length of the column in bytes.\n\n" + "Obtaining this value is computationally intensive, so it is always None"; + +static const char internal_size_doc[] = + "The size in bytes of the column associated to this column on the server.\n\n" + "Set to a negative value for variable-size types."; + +static const char precision_doc[] = + "Total number of significant digits in columns of type NUMERIC.\n\n" + "None for other types."; + +static const char scale_doc[] = + "Count of decimal digits in the fractional part in columns of type NUMERIC.\n\n" + "None for other types."; + +static const char null_ok_doc[] = + "Always none."; + +static const char table_oid_doc[] = + "The OID of the table from which the column was fetched.\n\n" + "None if not available"; + +static const char table_column_doc[] = + "The number (within its table) of the column making up the result\n\n" + "None if not available. Note that PostgreSQL column numbers start at 1"; + + +static PyMemberDef column_members[] = { + { "name", T_OBJECT, offsetof(columnObject, name), READONLY, (char *)name_doc }, + { "type_code", T_OBJECT, offsetof(columnObject, type_code), READONLY, (char *)type_code_doc }, + { "display_size", T_OBJECT, offsetof(columnObject, display_size), READONLY, (char *)display_size_doc }, + { "internal_size", T_OBJECT, offsetof(columnObject, internal_size), READONLY, (char *)internal_size_doc }, + { "precision", T_OBJECT, offsetof(columnObject, precision), READONLY, (char *)precision_doc }, + { "scale", T_OBJECT, offsetof(columnObject, scale), READONLY, (char *)scale_doc }, + { "null_ok", T_OBJECT, offsetof(columnObject, null_ok), READONLY, (char *)null_ok_doc }, + { "table_oid", T_OBJECT, offsetof(columnObject, table_oid), READONLY, (char *)table_oid_doc }, + { "table_column", T_OBJECT, offsetof(columnObject, table_column), READONLY, (char *)table_column_doc }, + { NULL } +}; + + +static PyObject * +column_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + return type->tp_alloc(type, 0); +} + + +static int +column_init(columnObject *self, PyObject *args, PyObject *kwargs) +{ + PyObject *name = NULL; + PyObject *type_code = NULL; + PyObject *display_size = NULL; + PyObject *internal_size = NULL; + PyObject *precision = NULL; + PyObject *scale = NULL; + PyObject *null_ok = NULL; + PyObject *table_oid = NULL; + PyObject *table_column = NULL; + + static char *kwlist[] = { + "name", "type_code", "display_size", "internal_size", + "precision", "scale", "null_ok", "table_oid", "table_column", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOOOOOOOO", kwlist, + &name, &type_code, &display_size, &internal_size, &precision, + &scale, &null_ok, &table_oid, &table_column)) { + return -1; + } + + Py_XINCREF(name); self->name = name; + Py_XINCREF(type_code); self->type_code = type_code; + Py_XINCREF(display_size); self->display_size = display_size; + Py_XINCREF(internal_size); self->internal_size = internal_size; + Py_XINCREF(precision); self->precision = precision; + Py_XINCREF(scale); self->scale = scale; + Py_XINCREF(null_ok); self->null_ok = null_ok; + Py_XINCREF(table_oid); self->table_oid = table_oid; + Py_XINCREF(table_column); self->table_column = table_column; + + return 0; +} + + +static void +column_dealloc(columnObject *self) +{ + Py_CLEAR(self->name); + Py_CLEAR(self->type_code); + Py_CLEAR(self->display_size); + Py_CLEAR(self->internal_size); + Py_CLEAR(self->precision); + Py_CLEAR(self->scale); + Py_CLEAR(self->null_ok); + Py_CLEAR(self->table_oid); + Py_CLEAR(self->table_column); + + Py_TYPE(self)->tp_free((PyObject *)self); +} + + +static PyObject* +column_repr(columnObject *self) +{ + PyObject *rv = NULL; + PyObject *format = NULL; + PyObject *args = NULL; + PyObject *tmp; + + if (!(format = Text_FromUTF8("Column(name=%r, type_code=%r)"))) { + goto exit; + } + + if (!(args = PyTuple_New(2))) { goto exit; } + + tmp = self->name ? self->name : Py_None; + Py_INCREF(tmp); + PyTuple_SET_ITEM(args, 0, tmp); + + tmp = self->type_code ? self->type_code : Py_None; + Py_INCREF(tmp); + PyTuple_SET_ITEM(args, 1, tmp); + + rv = Text_Format(format, args); + +exit: + Py_XDECREF(args); + Py_XDECREF(format); + + return rv; +} + + +static PyObject * +column_richcompare(columnObject *self, PyObject *other, int op) +{ + PyObject *rv = NULL; + PyObject *tself = NULL; + + if (!(tself = PyObject_CallFunctionObjArgs( + (PyObject *)&PyTuple_Type, (PyObject *)self, NULL))) { + goto exit; + } + + rv = PyObject_RichCompare(tself, other, op); + +exit: + Py_XDECREF(tself); + return rv; +} + + +/* column description can be accessed as a 7 items tuple for DBAPI compatibility */ + +static Py_ssize_t +column_len(columnObject *self) +{ + return 7; +} + + +static PyObject * +column_getitem(columnObject *self, Py_ssize_t item) +{ + PyObject *rv = NULL; + + if (item < 0) + item += 7; + + switch (item) { + case 0: + rv = self->name; + break; + case 1: + rv = self->type_code; + break; + case 2: + rv = self->display_size; + break; + case 3: + rv = self->internal_size; + break; + case 4: + rv = self->precision; + break; + case 5: + rv = self->scale; + break; + case 6: + rv = self->null_ok; + break; + default: + PyErr_SetString(PyExc_IndexError, "index out of range"); + return NULL; + } + + if (!rv) { + rv = Py_None; + } + + Py_INCREF(rv); + return rv; +} + + +static PyObject* +column_subscript(columnObject* self, PyObject* item) +{ + PyObject *t = NULL; + PyObject *rv = NULL; + + /* t = tuple(self) */ + if (!(t = PyObject_CallFunctionObjArgs( + (PyObject *)&PyTuple_Type, (PyObject *)self, NULL))) { + goto exit; + } + + /* rv = t[item] */ + rv = PyObject_GetItem(t, item); + +exit: + Py_XDECREF(t); + return rv; +} + +static PyMappingMethods column_mapping = { + (lenfunc)column_len, /* mp_length */ + (binaryfunc)column_subscript, /* mp_subscript */ + 0 /* mp_ass_subscript */ +}; + +static PySequenceMethods column_sequence = { + (lenfunc)column_len, /* sq_length */ + 0, /* sq_concat */ + 0, /* sq_repeat */ + (ssizeargfunc)column_getitem, /* sq_item */ + 0, /* sq_slice */ + 0, /* sq_ass_item */ + 0, /* sq_ass_slice */ + 0, /* sq_contains */ + 0, /* sq_inplace_concat */ + 0, /* sq_inplace_repeat */ +}; + + +static PyObject * +column_getstate(columnObject *self, PyObject *dummy) +{ + return PyObject_CallFunctionObjArgs( + (PyObject *)&PyTuple_Type, (PyObject *)self, NULL); +} + + +PyObject * +column_setstate(columnObject *self, PyObject *state) +{ + Py_ssize_t size; + PyObject *rv = NULL; + + if (state == Py_None) { + goto exit; + } + if (!PyTuple_Check(state)) { + PyErr_SetString(PyExc_TypeError, "state is not a tuple"); + goto error; + } + + size = PyTuple_GET_SIZE(state); + + if (size > 0) { + Py_CLEAR(self->name); + self->name = PyTuple_GET_ITEM(state, 0); + Py_INCREF(self->name); + } + if (size > 1) { + Py_CLEAR(self->type_code); + self->type_code = PyTuple_GET_ITEM(state, 1); + Py_INCREF(self->type_code); + } + if (size > 2) { + Py_CLEAR(self->display_size); + self->display_size = PyTuple_GET_ITEM(state, 2); + Py_INCREF(self->display_size); + } + if (size > 3) { + Py_CLEAR(self->internal_size); + self->internal_size = PyTuple_GET_ITEM(state, 3); + Py_INCREF(self->internal_size); + } + if (size > 4) { + Py_CLEAR(self->precision); + self->precision = PyTuple_GET_ITEM(state, 4); + Py_INCREF(self->precision); + } + if (size > 5) { + Py_CLEAR(self->scale); + self->scale = PyTuple_GET_ITEM(state, 5); + Py_INCREF(self->scale); + } + if (size > 6) { + Py_CLEAR(self->null_ok); + self->null_ok = PyTuple_GET_ITEM(state, 6); + Py_INCREF(self->null_ok); + } + if (size > 7) { + Py_CLEAR(self->table_oid); + self->table_oid = PyTuple_GET_ITEM(state, 7); + Py_INCREF(self->table_oid); + } + if (size > 8) { + Py_CLEAR(self->table_column); + self->table_column = PyTuple_GET_ITEM(state, 8); + Py_INCREF(self->table_column); + } + +exit: + rv = Py_None; + Py_INCREF(rv); + +error: + return rv; +} + + +static PyMethodDef column_methods[] = { + /* Make Column picklable. */ + {"__getstate__", (PyCFunction)column_getstate, METH_NOARGS }, + {"__setstate__", (PyCFunction)column_setstate, METH_O }, + {NULL} +}; + + +PyTypeObject columnType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2.extensions.Column", + sizeof(columnObject), 0, + (destructor)column_dealloc, /* tp_dealloc */ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + (reprfunc)column_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + &column_sequence, /*tp_as_sequence*/ + &column_mapping, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + column_doc, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + (richcmpfunc)column_richcompare, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + column_methods, /*tp_methods*/ + column_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + (initproc)column_init, /*tp_init*/ + 0, /*tp_alloc*/ + column_new, /*tp_new*/ +}; diff --git a/source-code/psycopg2/psycopg/config.h b/source-code/psycopg2/psycopg/config.h new file mode 100644 index 0000000..0830f93 --- /dev/null +++ b/source-code/psycopg2/psycopg/config.h @@ -0,0 +1,216 @@ +/* config.h - general config and Dprintf macro + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_CONFIG_H +#define PSYCOPG_CONFIG_H 1 + +/* GCC 4.0 and later have support for specifying symbol visibility */ +#if __GNUC__ >= 4 && !defined(__MINGW32__) +# define HIDDEN __attribute__((visibility("hidden"))) +#else +# define HIDDEN +#endif + +/* support for getpid() */ +#if defined( __GNUC__) +#define CONN_CHECK_PID +#include +#include +#endif +#ifdef _WIN32 +/* Windows doesn't seem affected by bug #829: just make it compile. */ +#define pid_t int +#endif + + +/* debug printf-like function */ +#ifdef PSYCOPG_DEBUG +extern HIDDEN int psycopg_debug_enabled; +#endif + +#if defined( __GNUC__) && !defined(__APPLE__) +#ifdef PSYCOPG_DEBUG +#define Dprintf(fmt, args...) \ + if (!psycopg_debug_enabled) ; else \ + fprintf(stderr, "[%d] " fmt "\n", (int) getpid() , ## args) +#else +#define Dprintf(fmt, args...) +#endif +#else /* !__GNUC__ or __APPLE__ */ +#ifdef PSYCOPG_DEBUG +#include +#ifdef _WIN32 +#include +#define getpid _getpid +#endif +static void Dprintf(const char *fmt, ...) +{ + va_list ap; + + if (!psycopg_debug_enabled) + return; + printf("[%d] ", (int) getpid()); + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + printf("\n"); +} +#else +static void Dprintf(const char *fmt, ...) {} +#endif +#endif + +/* pthreads work-arounds for mutilated operating systems */ +#if defined(_WIN32) || defined(__BEOS__) + +#ifdef _WIN32 + +/* A Python extension should be linked to only one C runtime: the same one as + * the Python interpreter itself. Straightforwardly using the strdup function + * causes MinGW to implicitly link to the msvcrt.dll, which is not appropriate + * for any Python version later than 2.3. + * Microsoft C runtimes for Windows 98 and later make a _strdup function + * available, which replaces the "normal" strdup. If we replace all psycopg + * calls to strdup with calls to _strdup, MinGW no longer implicitly links to + * the obsolete C runtime. */ +#define strdup _strdup + +#include +#define pthread_mutex_t HANDLE +#define pthread_condvar_t HANDLE +#define pthread_mutex_lock(object) WaitForSingleObject(*(object), INFINITE) +#define pthread_mutex_unlock(object) ReleaseMutex(*(object)) +#define pthread_mutex_destroy(ref) (CloseHandle(*(ref))) +/* convert pthread mutex to native mutex */ +static int pthread_mutex_init(pthread_mutex_t *mutex, void* fake) +{ + *mutex = CreateMutex(NULL, FALSE, NULL); + return 0; +} +#endif /* _WIN32 */ + +#ifdef __BEOS__ +#include +#define pthread_mutex_t sem_id +#define pthread_mutex_lock(object) acquire_sem(object) +#define pthread_mutex_unlock(object) release_sem(object) +#define pthread_mutex_destroy(ref) delete_sem(*ref) +static int pthread_mutex_init(pthread_mutex_t *mutex, void* fake) +{ + *mutex = create_sem(1, "psycopg_mutex"); + if (*mutex < B_OK) + return *mutex; + return 0; +} +#endif /* __BEOS__ */ + +#else /* pthread is available */ +#include +#endif + +/* to work around the fact that Windows does not have a gmtime_r function, or + a proper gmtime function */ +#ifdef _WIN32 +#define gmtime_r(t, tm) (gmtime(t)?memcpy((tm), gmtime(t), sizeof(*(tm))):NULL) +#define localtime_r(t, tm) (localtime(t)?memcpy((tm), localtime(t), sizeof(*(tm))):NULL) + +/* remove the inline keyword, since it doesn't work unless C++ file */ +#define inline + +/* Hmmm, MSVC <2015 doesn't have a isnan/isinf function, but has _isnan function */ +#if defined (_MSC_VER) +#if !defined(isnan) +#define isnan(x) (_isnan(x)) +/* The following line was hacked together from simliar code by Bjorn Reese + * in libxml2 code */ +#define isinf(x) ((_fpclass(x) == _FPCLASS_PINF) ? 1 \ + : ((_fpclass(x) == _FPCLASS_NINF) ? -1 : 0)) +#endif +#define strcasecmp(x, y) lstrcmpi(x, y) + +typedef __int8 int8_t; +typedef __int16 int16_t; +typedef __int32 int32_t; +typedef __int64 int64_t; +typedef unsigned __int8 uint8_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; +#endif + +#include "win32_support.h" +#endif + +/* what's this, we have no round function either? */ +#if (defined(_WIN32) && !defined(__GNUC__)) \ + || (defined(sun) || defined(__sun__)) \ + && (defined(__SunOS_5_8) || defined(__SunOS_5_9)) + +/* round has been added in the standard library with MSVC 2015 */ +#if _MSC_VER < 1900 +static double round(double num) +{ + return (num >= 0) ? floor(num + 0.5) : ceil(num - 0.5); +} +#endif +#endif + +/* resolve missing isinf() function for Solaris */ +#if defined (__SVR4) && defined (__sun) +#include +#define isinf(x) (!finite((x)) && (x)==(x)) +#endif + +/* decorators for the gcc cpychecker plugin */ +#if defined(WITH_CPYCHECKER_RETURNS_BORROWED_REF_ATTRIBUTE) +#define BORROWED \ + __attribute__((cpychecker_returns_borrowed_ref)) +#else +#define BORROWED +#endif + +#if defined(WITH_CPYCHECKER_STEALS_REFERENCE_TO_ARG_ATTRIBUTE) +#define STEALS(n) \ + __attribute__((cpychecker_steals_reference_to_arg(n))) +#else +#define STEALS(n) +#endif + +#if defined(WITH_CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION_ATTRIBUTE) +#define RAISES_NEG \ + __attribute__((cpychecker_negative_result_sets_exception)) +#else +#define RAISES_NEG +#endif + +#if defined(WITH_CPYCHECKER_SETS_EXCEPTION_ATTRIBUTE) +#define RAISES \ + __attribute__((cpychecker_sets_exception)) +#else +#define RAISES +#endif + +#endif /* !defined(PSYCOPG_CONFIG_H) */ diff --git a/source-code/psycopg2/psycopg/connection.h b/source-code/psycopg2/psycopg/connection.h new file mode 100644 index 0000000..6d61c2e --- /dev/null +++ b/source-code/psycopg2/psycopg/connection.h @@ -0,0 +1,229 @@ +/* connection.h - definition for the psycopg connection type + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_CONNECTION_H +#define PSYCOPG_CONNECTION_H 1 + +#include "psycopg/xid.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* isolation levels */ +#define ISOLATION_LEVEL_AUTOCOMMIT 0 +#define ISOLATION_LEVEL_READ_UNCOMMITTED 4 +#define ISOLATION_LEVEL_READ_COMMITTED 1 +#define ISOLATION_LEVEL_REPEATABLE_READ 2 +#define ISOLATION_LEVEL_SERIALIZABLE 3 +#define ISOLATION_LEVEL_DEFAULT 5 + +/* 3-state values on/off/default */ +#define STATE_OFF 0 +#define STATE_ON 1 +#define STATE_DEFAULT 2 + +/* connection status */ +#define CONN_STATUS_SETUP 0 +#define CONN_STATUS_READY 1 +#define CONN_STATUS_BEGIN 2 +#define CONN_STATUS_PREPARED 5 +/* async connection building statuses */ +#define CONN_STATUS_CONNECTING 20 +#define CONN_STATUS_DATESTYLE 21 + +/* async query execution status */ +#define ASYNC_DONE 0 +#define ASYNC_READ 1 +#define ASYNC_WRITE 2 + +/* polling result */ +#define PSYCO_POLL_OK 0 +#define PSYCO_POLL_READ 1 +#define PSYCO_POLL_WRITE 2 +#define PSYCO_POLL_ERROR 3 + +/* Hard limit on the notices stored by the Python connection */ +#define CONN_NOTICES_LIMIT 50 + +/* we need the initial date style to be ISO, for typecasters; if the user + later change it, she must know what she's doing... these are the queries we + need to issue */ +#define psyco_datestyle "SET DATESTYLE TO 'ISO'" + +extern HIDDEN PyTypeObject connectionType; + +struct connectionObject_notice { + struct connectionObject_notice *next; + char *message; +}; + +/* the typedef is forward-declared in psycopg.h */ +struct connectionObject { + PyObject_HEAD + + pthread_mutex_t lock; /* the global connection lock */ + + char *dsn; /* data source name */ + char *error; /* temporarily stored error before raising */ + char *encoding; /* current backend encoding */ + + long int closed; /* 1 means connection has been closed; + 2 that something horrible happened */ + long int mark; /* number of commits/rollbacks done so far */ + int status; /* status of the connection */ + xidObject *tpc_xid; /* Transaction ID in two-phase commit */ + + long int async; /* 1 means the connection is async */ + int protocol; /* protocol version */ + int server_version; /* server version */ + + PGconn *pgconn; /* the postgresql connection */ + PGcancel *cancel; /* the cancellation structure */ + + /* Weakref to the object executing an asynchronous query. The object + * is a cursor for async connections, but it may be something else + * for a green connection. If NULL, the connection is idle. */ + PyObject *async_cursor; + int async_status; /* asynchronous execution status */ + PGresult *pgres; /* temporary result across async calls */ + + /* notice processing */ + PyObject *notice_list; + struct connectionObject_notice *notice_pending; + struct connectionObject_notice *last_notice; + + /* notifies */ + PyObject *notifies; + + /* per-connection typecasters */ + PyObject *string_types; /* a set of typecasters for string types */ + PyObject *binary_types; /* a set of typecasters for binary types */ + + int equote; /* use E''-style quotes for escaped strings */ + PyObject *weakreflist; /* list of weak references */ + + int autocommit; + + PyObject *cursor_factory; /* default cursor factory from cursor() */ + + /* Optional pointer to a decoding C function, e.g. PyUnicode_DecodeUTF8 */ + PyObject *(*cdecoder)(const char *, Py_ssize_t, const char *); + + /* Pointers to python encoding/decoding functions, e.g. + * codecs.getdecoder('utf8') */ + PyObject *pyencoder; /* python codec encoding function */ + PyObject *pydecoder; /* python codec decoding function */ + + /* Values for the transactions characteristics */ + int isolevel; + int readonly; + int deferrable; + + /* the pid this connection was created into */ + pid_t procpid; + + /* inside a with block */ + int entered; +}; + +/* map isolation level values into a numeric const */ +typedef struct { + char *name; + int value; +} IsolationLevel; + +/* C-callable functions in connection_int.c and connection_ext.c */ +HIDDEN PyObject *conn_text_from_chars(connectionObject *pgconn, const char *str); +HIDDEN PyObject *conn_encode(connectionObject *self, PyObject *b); +HIDDEN PyObject *conn_decode(connectionObject *self, const char *str, Py_ssize_t len); +HIDDEN int conn_get_standard_conforming_strings(PGconn *pgconn); +HIDDEN PyObject *conn_pgenc_to_pyenc(const char *encoding, char **clean_encoding); +HIDDEN int conn_get_protocol_version(PGconn *pgconn); +HIDDEN int conn_get_server_version(PGconn *pgconn); +HIDDEN void conn_notice_process(connectionObject *self); +HIDDEN void conn_notice_clean(connectionObject *self); +HIDDEN void conn_notifies_process(connectionObject *self); +RAISES_NEG HIDDEN int conn_setup(connectionObject *self); +HIDDEN int conn_connect(connectionObject *self, const char *dsn, long int async); +HIDDEN char *conn_obscure_password(const char *dsn); +HIDDEN void conn_close(connectionObject *self); +HIDDEN void conn_close_locked(connectionObject *self); +RAISES_NEG HIDDEN int conn_commit(connectionObject *self); +RAISES_NEG HIDDEN int conn_rollback(connectionObject *self); +RAISES_NEG HIDDEN int conn_set_session(connectionObject *self, int autocommit, + int isolevel, int readonly, int deferrable); +RAISES_NEG HIDDEN int conn_set_client_encoding(connectionObject *self, const char *enc); +HIDDEN int conn_poll(connectionObject *self); +RAISES_NEG HIDDEN int conn_tpc_begin(connectionObject *self, xidObject *xid); +RAISES_NEG HIDDEN int conn_tpc_command(connectionObject *self, + const char *cmd, xidObject *xid); +HIDDEN PyObject *conn_tpc_recover(connectionObject *self); +HIDDEN void conn_set_result(connectionObject *self, PGresult *pgres); +HIDDEN void conn_set_error(connectionObject *self, const char *msg); + +/* exception-raising macros */ +#define EXC_IF_CONN_CLOSED(self) if ((self)->closed > 0) { \ + PyErr_SetString(InterfaceError, "connection already closed"); \ + return NULL; } + +#define EXC_IF_CONN_ASYNC(self, cmd) if ((self)->async == 1) { \ + PyErr_SetString(ProgrammingError, #cmd " cannot be used " \ + "in asynchronous mode"); \ + return NULL; } + +#define EXC_IF_IN_TRANSACTION(self, cmd) \ + if (self->status != CONN_STATUS_READY) { \ + PyErr_Format(ProgrammingError, \ + "%s cannot be used inside a transaction", #cmd); \ + return NULL; \ + } + +#define EXC_IF_TPC_NOT_SUPPORTED(self) \ + if ((self)->server_version < 80100) { \ + PyErr_Format(NotSupportedError, \ + "server version %d: " \ + "two-phase transactions not supported", \ + (self)->server_version); \ + return NULL; \ + } + +#define EXC_IF_TPC_BEGIN(self, cmd) if ((self)->tpc_xid) { \ + PyErr_Format(ProgrammingError, "%s cannot be used " \ + "during a two-phase transaction", #cmd); \ + return NULL; } + +#define EXC_IF_TPC_PREPARED(self, cmd) \ + if ((self)->status == CONN_STATUS_PREPARED) { \ + PyErr_Format(ProgrammingError, "%s cannot be used " \ + "with a prepared two-phase transaction", #cmd); \ + return NULL; } + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_CONNECTION_H) */ diff --git a/source-code/psycopg2/psycopg/connection_int.c b/source-code/psycopg2/psycopg/connection_int.c new file mode 100644 index 0000000..8365e04 --- /dev/null +++ b/source-code/psycopg2/psycopg/connection_int.c @@ -0,0 +1,1554 @@ +/* connection_int.c - code used by the connection object + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/connection.h" +#include "psycopg/cursor.h" +#include "psycopg/pqpath.h" +#include "psycopg/green.h" +#include "psycopg/notify.h" + +#include +#include + +/* String indexes match the ISOLATION_LEVEL_* consts */ +const char *srv_isolevels[] = { + NULL, /* autocommit */ + "READ COMMITTED", + "REPEATABLE READ", + "SERIALIZABLE", + "READ UNCOMMITTED", + "default" /* only to set GUC, not for BEGIN */ +}; + +/* Read only false, true */ +const char *srv_readonly[] = { + " READ WRITE", + " READ ONLY", + "" /* default */ +}; + +/* Deferrable false, true */ +const char *srv_deferrable[] = { + " NOT DEFERRABLE", + " DEFERRABLE", + "" /* default */ +}; + +/* On/Off/Default GUC states + */ +const char *srv_state_guc[] = { + "off", + "on", + "default" +}; + + +const int SRV_STATE_UNCHANGED = -1; + + +/* Return a new "string" from a char* from the database. + * + * On Py2 just get a string, on Py3 decode it in the connection codec. + * + * Use a fallback if the connection is NULL. + */ +PyObject * +conn_text_from_chars(connectionObject *self, const char *str) +{ + return psyco_text_from_chars_safe(str, -1, self ? self->pydecoder : NULL); +} + + +/* Encode an unicode object into a bytes object in the connection encoding. + * + * If no connection or encoding is available, default to utf8 + */ +PyObject * +conn_encode(connectionObject *self, PyObject *u) +{ + PyObject *t = NULL; + PyObject *rv = NULL; + + if (!(self && self->pyencoder)) { + rv = PyUnicode_AsUTF8String(u); + goto exit; + } + + if (!(t = PyObject_CallFunctionObjArgs(self->pyencoder, u, NULL))) { + goto exit; + } + + if (!(rv = PyTuple_GetItem(t, 0))) { goto exit; } + Py_INCREF(rv); + +exit: + Py_XDECREF(t); + + return rv; +} + + +/* decode a c string into a Python unicode in the connection encoding + * + * len can be < 0: in this case it will be calculated + * + * If no connection or encoding is available, default to utf8 + */ +PyObject * +conn_decode(connectionObject *self, const char *str, Py_ssize_t len) +{ + if (len < 0) { len = strlen(str); } + + if (self) { + if (self->cdecoder) { + return self->cdecoder(str, len, NULL); + } + else if (self->pydecoder) { + PyObject *b = NULL; + PyObject *t = NULL; + PyObject *rv = NULL; + + if (!(b = Bytes_FromStringAndSize(str, len))) { goto error; } + if (!(t = PyObject_CallFunctionObjArgs(self->pydecoder, b, NULL))) { + goto error; + } + if (!(rv = PyTuple_GetItem(t, 0))) { goto error; } + Py_INCREF(rv); /* PyTuple_GetItem gives a borrowes one */ +error: + Py_XDECREF(t); + Py_XDECREF(b); + return rv; + } + else { + return PyUnicode_FromStringAndSize(str, len); + } + } + else { + return PyUnicode_FromStringAndSize(str, len); + } +} + +/* conn_notice_callback - process notices */ + +static void +conn_notice_callback(void *args, const char *message) +{ + struct connectionObject_notice *notice; + connectionObject *self = (connectionObject *)args; + + Dprintf("conn_notice_callback: %s", message); + + /* NOTE: if we get here and the connection is unlocked then there is a + problem but this should happen because the notice callback is only + called from libpq and when we're inside libpq the connection is usually + locked. + */ + notice = (struct connectionObject_notice *) + malloc(sizeof(struct connectionObject_notice)); + if (NULL == notice) { + /* Discard the notice in case of failed allocation. */ + return; + } + notice->next = NULL; + notice->message = strdup(message); + if (NULL == notice->message) { + free(notice); + return; + } + + if (NULL == self->last_notice) { + self->notice_pending = self->last_notice = notice; + } + else { + self->last_notice->next = notice; + self->last_notice = notice; + } +} + +/* Expose the notices received as Python objects. + * + * The function should be called with the connection lock and the GIL. + */ +void +conn_notice_process(connectionObject *self) +{ + struct connectionObject_notice *notice; + PyObject *msg = NULL; + PyObject *tmp = NULL; + static PyObject *append; + + if (NULL == self->notice_pending) { + return; + } + + if (!append) { + if (!(append = Text_FromUTF8("append"))) { + goto error; + } + } + + notice = self->notice_pending; + while (notice != NULL) { + Dprintf("conn_notice_process: %s", notice->message); + + if (!(msg = conn_text_from_chars(self, notice->message))) { goto error; } + + if (!(tmp = PyObject_CallMethodObjArgs( + self->notice_list, append, msg, NULL))) { + + goto error; + } + + Py_DECREF(tmp); tmp = NULL; + Py_DECREF(msg); msg = NULL; + + notice = notice->next; + } + + /* Remove the oldest item if the queue is getting too long. */ + if (PyList_Check(self->notice_list)) { + Py_ssize_t nnotices; + nnotices = PyList_GET_SIZE(self->notice_list); + if (nnotices > CONN_NOTICES_LIMIT) { + if (-1 == PySequence_DelSlice(self->notice_list, + 0, nnotices - CONN_NOTICES_LIMIT)) { + PyErr_Clear(); + } + } + } + + conn_notice_clean(self); + return; + +error: + Py_XDECREF(tmp); + Py_XDECREF(msg); + conn_notice_clean(self); + + /* TODO: the caller doesn't expects errors from us */ + PyErr_Clear(); +} + +void +conn_notice_clean(connectionObject *self) +{ + struct connectionObject_notice *tmp, *notice; + + notice = self->notice_pending; + + while (notice != NULL) { + tmp = notice; + notice = notice->next; + free(tmp->message); + free(tmp); + } + + self->last_notice = self->notice_pending = NULL; +} + + +/* conn_notifies_process - make received notification available + * + * The function should be called with the connection lock and holding the GIL. + */ + +void +conn_notifies_process(connectionObject *self) +{ + PGnotify *pgn = NULL; + PyObject *notify = NULL; + PyObject *pid = NULL, *channel = NULL, *payload = NULL; + PyObject *tmp = NULL; + + static PyObject *append; + + if (!append) { + if (!(append = Text_FromUTF8("append"))) { + goto error; + } + } + + while ((pgn = PQnotifies(self->pgconn)) != NULL) { + + Dprintf("conn_notifies_process: got NOTIFY from pid %d, msg = %s", + (int) pgn->be_pid, pgn->relname); + + if (!(pid = PyInt_FromLong((long)pgn->be_pid))) { goto error; } + if (!(channel = conn_text_from_chars(self, pgn->relname))) { goto error; } + if (!(payload = conn_text_from_chars(self, pgn->extra))) { goto error; } + + if (!(notify = PyObject_CallFunctionObjArgs((PyObject *)¬ifyType, + pid, channel, payload, NULL))) { + goto error; + } + + Py_DECREF(pid); pid = NULL; + Py_DECREF(channel); channel = NULL; + Py_DECREF(payload); payload = NULL; + + if (!(tmp = PyObject_CallMethodObjArgs( + self->notifies, append, notify, NULL))) { + goto error; + } + Py_DECREF(tmp); tmp = NULL; + + Py_DECREF(notify); notify = NULL; + PQfreemem(pgn); pgn = NULL; + } + return; /* no error */ + +error: + if (pgn) { PQfreemem(pgn); } + Py_XDECREF(tmp); + Py_XDECREF(notify); + Py_XDECREF(pid); + Py_XDECREF(channel); + Py_XDECREF(payload); + + /* TODO: callers currently don't expect an error from us */ + PyErr_Clear(); + +} + + +/* + * the conn_get_* family of functions makes it easier to obtain the connection + * parameters from query results or by interrogating the connection itself +*/ + +int +conn_get_standard_conforming_strings(PGconn *pgconn) +{ + int equote; + const char *scs; + /* + * The presence of the 'standard_conforming_strings' parameter + * means that the server _accepts_ the E'' quote. + * + * If the parameter is off, the PQescapeByteaConn returns + * backslash escaped strings (e.g. '\001' -> "\\001"), + * so the E'' quotes are required to avoid warnings + * if 'escape_string_warning' is set. + * + * If the parameter is on, the PQescapeByteaConn returns + * not escaped strings (e.g. '\001' -> "\001"), relying on the + * fact that the '\' will pass untouched the string parser. + * In this case the E'' quotes are NOT to be used. + */ + scs = PQparameterStatus(pgconn, "standard_conforming_strings"); + Dprintf("conn_connect: server standard_conforming_strings parameter: %s", + scs ? scs : "unavailable"); + + equote = (scs && (0 == strcmp("off", scs))); + Dprintf("conn_connect: server requires E'' quotes: %s", + equote ? "YES" : "NO"); + + return equote; +} + + +/* Remove irrelevant chars from encoding name and turn it uppercase. + * + * Return a buffer allocated on Python heap into 'clean' and return 0 on + * success, otherwise return -1 and set an exception. + */ +RAISES_NEG static int +clear_encoding_name(const char *enc, char **clean) +{ + const char *i = enc; + char *j, *buf; + int rv = -1; + + /* convert to upper case and remove '-' and '_' from string */ + if (!(j = buf = PyMem_Malloc(strlen(enc) + 1))) { + PyErr_NoMemory(); + goto exit; + } + + while (*i) { + if (!isalnum(*i)) { + ++i; + } + else { + *j++ = toupper(*i++); + } + } + *j = '\0'; + + Dprintf("clear_encoding_name: %s -> %s", enc, buf); + *clean = buf; + rv = 0; + +exit: + return rv; +} + +/* set fast access functions according to the currently selected encoding + */ +static void +conn_set_fast_codec(connectionObject *self) +{ + Dprintf("conn_set_fast_codec: encoding=%s", self->encoding); + + if (0 == strcmp(self->encoding, "UTF8")) { + Dprintf("conn_set_fast_codec: PyUnicode_DecodeUTF8"); + self->cdecoder = PyUnicode_DecodeUTF8; + return; + } + + if (0 == strcmp(self->encoding, "LATIN1")) { + Dprintf("conn_set_fast_codec: PyUnicode_DecodeLatin1"); + self->cdecoder = PyUnicode_DecodeLatin1; + return; + } + + Dprintf("conn_set_fast_codec: no fast codec"); + self->cdecoder = NULL; +} + + +/* Return the Python encoding from a PostgreSQL encoding. + * + * Optionally return the clean version of the postgres encoding too + */ +PyObject * +conn_pgenc_to_pyenc(const char *encoding, char **clean_encoding) +{ + char *pgenc = NULL; + PyObject *rv = NULL; + + if (0 > clear_encoding_name(encoding, &pgenc)) { goto exit; } + if (!(rv = PyDict_GetItemString(psycoEncodings, pgenc))) { + PyErr_Format(OperationalError, + "no Python encoding for PostgreSQL encoding '%s'", pgenc); + goto exit; + } + Py_INCREF(rv); + + if (clean_encoding) { + *clean_encoding = pgenc; + } + else { + PyMem_Free(pgenc); + } + +exit: + return rv; +} + +/* Convert a Postgres encoding into Python encoding and decoding functions. + * + * Set clean_encoding to a clean version of the Postgres encoding name + * and pyenc and pydec to python codec functions. + * + * Return 0 on success, else -1 and set an exception. + */ +RAISES_NEG static int +conn_get_python_codec(const char *encoding, + char **clean_encoding, PyObject **pyenc, PyObject **pydec) +{ + int rv = -1; + char *pgenc = NULL; + PyObject *encname = NULL; + PyObject *enc_tmp = NULL, *dec_tmp = NULL; + + /* get the Python name of the encoding as a C string */ + if (!(encname = conn_pgenc_to_pyenc(encoding, &pgenc))) { goto exit; } + if (!(encname = psyco_ensure_bytes(encname))) { goto exit; } + + /* Look up the codec functions */ + if (!(enc_tmp = PyCodec_Encoder(Bytes_AS_STRING(encname)))) { goto exit; } + if (!(dec_tmp = PyCodec_Decoder(Bytes_AS_STRING(encname)))) { goto exit; } + + /* success */ + *pyenc = enc_tmp; enc_tmp = NULL; + *pydec = dec_tmp; dec_tmp = NULL; + *clean_encoding = pgenc; pgenc = NULL; + rv = 0; + +exit: + Py_XDECREF(enc_tmp); + Py_XDECREF(dec_tmp); + Py_XDECREF(encname); + PyMem_Free(pgenc); + + return rv; +} + + +/* Store the encoding in the pgconn->encoding field and set the other related + * encoding fields in the connection structure. + * + * Return 0 on success, else -1 and set an exception. + */ +RAISES_NEG static int +conn_store_encoding(connectionObject *self, const char *encoding) +{ + int rv = -1; + char *pgenc = NULL; + PyObject *enc_tmp = NULL, *dec_tmp = NULL; + + if (0 > conn_get_python_codec(encoding, &pgenc, &enc_tmp, &dec_tmp)) { + goto exit; + } + + /* Good, success: store the encoding/codec in the connection. */ + { + char *tmp = self->encoding; + self->encoding = pgenc; + PyMem_Free(tmp); + pgenc = NULL; + } + + Py_CLEAR(self->pyencoder); + self->pyencoder = enc_tmp; + enc_tmp = NULL; + + Py_CLEAR(self->pydecoder); + self->pydecoder = dec_tmp; + dec_tmp = NULL; + + conn_set_fast_codec(self); + + rv = 0; + +exit: + Py_XDECREF(enc_tmp); + Py_XDECREF(dec_tmp); + PyMem_Free(pgenc); + return rv; +} + + +/* Read the client encoding from the backend and store it in the connection. + * + * Return 0 on success, else -1. + */ +RAISES_NEG static int +conn_read_encoding(connectionObject *self, PGconn *pgconn) +{ + const char *encoding; + int rv = -1; + + encoding = PQparameterStatus(pgconn, "client_encoding"); + Dprintf("conn_connect: client encoding: %s", encoding ? encoding : "(none)"); + if (!encoding) { + PyErr_SetString(OperationalError, + "server didn't return client encoding"); + goto exit; + } + + if (0 > conn_store_encoding(self, encoding)) { + goto exit; + } + + rv = 0; + +exit: + return rv; +} + + +int +conn_get_protocol_version(PGconn *pgconn) +{ + int ret; + ret = PQprotocolVersion(pgconn); + Dprintf("conn_connect: using protocol %d", ret); + return ret; +} + +int +conn_get_server_version(PGconn *pgconn) +{ + return (int)PQserverVersion(pgconn); +} + +/* set up the cancel key of the connection. + * On success return 0, else set an exception and return -1 + */ +RAISES_NEG static int +conn_setup_cancel(connectionObject *self, PGconn *pgconn) +{ + if (self->cancel) { + PQfreeCancel(self->cancel); + } + + if (!(self->cancel = PQgetCancel(self->pgconn))) { + PyErr_SetString(OperationalError, "can't get cancellation key"); + return -1; + } + + return 0; +} + +/* Return 1 if the "replication" keyword is set in the DSN, 0 otherwise */ +static int +dsn_has_replication(char *pgdsn) +{ + int ret = 0; + PQconninfoOption *connopts, *ptr; + + connopts = PQconninfoParse(pgdsn, NULL); + + for(ptr = connopts; ptr->keyword != NULL; ptr++) { + if(strcmp(ptr->keyword, "replication") == 0 && ptr->val != NULL) + ret = 1; + } + + PQconninfoFree(connopts); + + return ret; +} + + +/* Return 1 if the server datestyle allows us to work without problems, + 0 if it needs to be set to something better, e.g. ISO. */ +static int +conn_is_datestyle_ok(PGconn *pgconn) +{ + const char *ds; + + ds = PQparameterStatus(pgconn, "DateStyle"); + Dprintf("conn_connect: DateStyle %s", ds); + + /* pgbouncer does not pass on DateStyle */ + if (ds == NULL) + return 0; + + /* Return true if ds starts with "ISO" + * e.g. "ISO, DMY" is fine, "German" not. */ + return (ds[0] == 'I' && ds[1] == 'S' && ds[2] == 'O'); +} + + +/* conn_setup - setup and read basic information about the connection */ + +RAISES_NEG int +conn_setup(connectionObject *self) +{ + int rv = -1; + + self->equote = conn_get_standard_conforming_strings(self->pgconn); + self->server_version = conn_get_server_version(self->pgconn); + self->protocol = conn_get_protocol_version(self->pgconn); + if (3 != self->protocol) { + PyErr_SetString(InterfaceError, "only protocol 3 supported"); + goto exit; + } + + if (0 > conn_read_encoding(self, self->pgconn)) { + goto exit; + } + + if (0 > conn_setup_cancel(self, self->pgconn)) { + goto exit; + } + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&self->lock); + Py_BLOCK_THREADS; + + if (!dsn_has_replication(self->dsn) && !conn_is_datestyle_ok(self->pgconn)) { + int res; + Py_UNBLOCK_THREADS; + res = pq_set_guc_locked(self, "datestyle", "ISO", &_save); + Py_BLOCK_THREADS; + if (res < 0) { + pq_complete_error(self); + goto unlock; + } + } + + /* for reset */ + self->autocommit = 0; + self->isolevel = ISOLATION_LEVEL_DEFAULT; + self->readonly = STATE_DEFAULT; + self->deferrable = STATE_DEFAULT; + + /* success */ + rv = 0; + +unlock: + Py_UNBLOCK_THREADS; + pthread_mutex_unlock(&self->lock); + Py_END_ALLOW_THREADS; + +exit: + return rv; +} + +/* conn_connect - execute a connection to the database */ + +static int +_conn_sync_connect(connectionObject *self, const char *dsn) +{ + int green; + + /* store this value to prevent inconsistencies due to a change + * in the middle of the function. */ + green = psyco_green(); + if (!green) { + Py_BEGIN_ALLOW_THREADS; + self->pgconn = PQconnectdb(dsn); + Py_END_ALLOW_THREADS; + Dprintf("conn_connect: new PG connection at %p", self->pgconn); + } + else { + Py_BEGIN_ALLOW_THREADS; + self->pgconn = PQconnectStart(dsn); + Py_END_ALLOW_THREADS; + Dprintf("conn_connect: new green PG connection at %p", self->pgconn); + } + + if (!self->pgconn) + { + Dprintf("conn_connect: PQconnectdb(%s) FAILED", dsn); + PyErr_SetString(OperationalError, "PQconnectdb() failed"); + return -1; + } + else if (PQstatus(self->pgconn) == CONNECTION_BAD) + { + Dprintf("conn_connect: PQconnectdb(%s) returned BAD", dsn); + PyErr_SetString(OperationalError, PQerrorMessage(self->pgconn)); + return -1; + } + + PQsetNoticeProcessor(self->pgconn, conn_notice_callback, (void*)self); + + /* if the connection is green, wait to finish connection */ + if (green) { + if (0 > pq_set_non_blocking(self, 1)) { + return -1; + } + if (0 != psyco_wait(self)) { + return -1; + } + } + + /* From here the connection is considered ready: with the new status, + * poll() will use PQisBusy instead of PQconnectPoll. + */ + self->status = CONN_STATUS_READY; + + if (conn_setup(self) == -1) { + return -1; + } + + return 0; +} + +static int +_conn_async_connect(connectionObject *self, const char *dsn) +{ + PGconn *pgconn; + + self->pgconn = pgconn = PQconnectStart(dsn); + + Dprintf("conn_connect: new postgresql connection at %p", pgconn); + + if (pgconn == NULL) + { + Dprintf("conn_connect: PQconnectStart(%s) FAILED", dsn); + PyErr_SetString(OperationalError, "PQconnectStart() failed"); + return -1; + } + else if (PQstatus(pgconn) == CONNECTION_BAD) + { + Dprintf("conn_connect: PQconnectdb(%s) returned BAD", dsn); + PyErr_SetString(OperationalError, PQerrorMessage(pgconn)); + return -1; + } + + PQsetNoticeProcessor(pgconn, conn_notice_callback, (void*)self); + + /* Set the connection to nonblocking now. */ + if (pq_set_non_blocking(self, 1) != 0) { + return -1; + } + + /* The connection will be completed banging on poll(): + * First with _conn_poll_connecting() that will finish connection, + * then with _conn_poll_setup_async() that will do the same job + * of setup_async(). */ + + return 0; +} + +int +conn_connect(connectionObject *self, const char *dsn, long int async) +{ + int rv; + + if (async == 1) { + Dprintf("con_connect: connecting in ASYNC mode"); + rv = _conn_async_connect(self, dsn); + } + else { + Dprintf("con_connect: connecting in SYNC mode"); + rv = _conn_sync_connect(self, dsn); + } + + if (rv != 0) { + /* connection failed, so let's close ourselves */ + self->closed = 2; + } + + return rv; +} + + +/* poll during a connection attempt until the connection has established. */ + +static int +_conn_poll_connecting(connectionObject *self) +{ + int res = PSYCO_POLL_ERROR; + const char *msg; + + Dprintf("conn_poll: poll connecting"); + switch (PQconnectPoll(self->pgconn)) { + case PGRES_POLLING_OK: + res = PSYCO_POLL_OK; + break; + case PGRES_POLLING_READING: + res = PSYCO_POLL_READ; + break; + case PGRES_POLLING_WRITING: + res = PSYCO_POLL_WRITE; + break; + case PGRES_POLLING_FAILED: + case PGRES_POLLING_ACTIVE: + msg = PQerrorMessage(self->pgconn); + if (!(msg && *msg)) { + msg = "asynchronous connection failed"; + } + PyErr_SetString(OperationalError, msg); + res = PSYCO_POLL_ERROR; + break; + } + + return res; +} + + +/* Advance to the next state after an attempt of flushing output */ + +static int +_conn_poll_advance_write(connectionObject *self) +{ + int res; + int flush; + + Dprintf("conn_poll: poll writing"); + + flush = PQflush(self->pgconn); + Dprintf("conn_poll: PQflush() = %i", flush); + + switch (flush) { + case 0: /* success */ + /* we've finished pushing the query to the server. Let's start + reading the results. */ + Dprintf("conn_poll: async_status -> ASYNC_READ"); + self->async_status = ASYNC_READ; + res = PSYCO_POLL_READ; + break; + case 1: /* would block */ + res = PSYCO_POLL_WRITE; + break; + case -1: /* error */ + PyErr_SetString(OperationalError, PQerrorMessage(self->pgconn)); + res = PSYCO_POLL_ERROR; + break; + default: + Dprintf("conn_poll: unexpected result from flush: %d", flush); + res = PSYCO_POLL_ERROR; + break; + } + return res; +} + + +/* Advance to the next state after reading results */ + +static int +_conn_poll_advance_read(connectionObject *self) +{ + int res; + int busy; + + Dprintf("conn_poll: poll reading"); + + busy = pq_get_result_async(self); + + switch (busy) { + case 0: /* result is ready */ + Dprintf("conn_poll: async_status -> ASYNC_DONE"); + self->async_status = ASYNC_DONE; + res = PSYCO_POLL_OK; + break; + case 1: /* result not ready: fd would block */ + res = PSYCO_POLL_READ; + break; + case -1: /* ouch, error */ + res = PSYCO_POLL_ERROR; + break; + default: + Dprintf("conn_poll: unexpected result from pq_get_result_async: %d", + busy); + res = PSYCO_POLL_ERROR; + break; + } + return res; +} + + +/* Poll the connection for the send query/retrieve result phase + + Advance the async_status (usually going WRITE -> READ -> DONE) but don't + mess with the connection status. */ + +static int +_conn_poll_query(connectionObject *self) +{ + int res = PSYCO_POLL_ERROR; + + switch (self->async_status) { + case ASYNC_WRITE: + Dprintf("conn_poll: async_status = ASYNC_WRITE"); + res = _conn_poll_advance_write(self); + break; + + case ASYNC_READ: + Dprintf("conn_poll: async_status = ASYNC_READ"); + res = _conn_poll_advance_read(self); + break; + + case ASYNC_DONE: + Dprintf("conn_poll: async_status = ASYNC_DONE"); + /* We haven't asked anything: just check for notifications. */ + res = _conn_poll_advance_read(self); + break; + + default: + Dprintf("conn_poll: in unexpected async status: %d", + self->async_status); + res = PSYCO_POLL_ERROR; + break; + } + + return res; +} + +/* Advance to the next state during an async connection setup + * + * If the connection is green, this is performed by the regular + * sync code so the queries are sent by conn_setup() while in + * CONN_STATUS_READY state. + */ +static int +_conn_poll_setup_async(connectionObject *self) +{ + int res = PSYCO_POLL_ERROR; + + switch (self->status) { + case CONN_STATUS_CONNECTING: + self->equote = conn_get_standard_conforming_strings(self->pgconn); + self->protocol = conn_get_protocol_version(self->pgconn); + self->server_version = conn_get_server_version(self->pgconn); + if (3 != self->protocol) { + PyErr_SetString(InterfaceError, "only protocol 3 supported"); + break; + } + if (0 > conn_read_encoding(self, self->pgconn)) { + break; + } + if (0 > conn_setup_cancel(self, self->pgconn)) { + return -1; + } + + /* asynchronous connections always use isolation level 0, the user is + * expected to manage the transactions himself, by sending + * (asynchronously) BEGIN and COMMIT statements. + */ + self->autocommit = 1; + + /* If the datestyle is ISO or anything else good, + * we can skip the CONN_STATUS_DATESTYLE step. + * Note that we cannot change the datestyle on a replication + * connection. + */ + if (!dsn_has_replication(self->dsn) && !conn_is_datestyle_ok(self->pgconn)) { + Dprintf("conn_poll: status -> CONN_STATUS_DATESTYLE"); + self->status = CONN_STATUS_DATESTYLE; + if (0 == pq_send_query(self, psyco_datestyle)) { + PyErr_SetString(OperationalError, PQerrorMessage(self->pgconn)); + break; + } + Dprintf("conn_poll: async_status -> ASYNC_WRITE"); + self->async_status = ASYNC_WRITE; + res = PSYCO_POLL_WRITE; + } + else { + Dprintf("conn_poll: status -> CONN_STATUS_READY"); + self->status = CONN_STATUS_READY; + res = PSYCO_POLL_OK; + } + break; + + case CONN_STATUS_DATESTYLE: + res = _conn_poll_query(self); + if (res == PSYCO_POLL_OK) { + res = PSYCO_POLL_ERROR; + if (self->pgres == NULL + || PQresultStatus(self->pgres) != PGRES_COMMAND_OK ) { + PyErr_SetString(OperationalError, "can't set datestyle to ISO"); + break; + } + CLEARPGRES(self->pgres); + + Dprintf("conn_poll: status -> CONN_STATUS_READY"); + self->status = CONN_STATUS_READY; + res = PSYCO_POLL_OK; + } + break; + } + return res; +} + + +static cursorObject * +_conn_get_async_cursor(connectionObject *self) { + PyObject *py_curs; + + if (!(py_curs = PyWeakref_GetObject(self->async_cursor))) { + PyErr_SetString(PyExc_SystemError, + "got null dereferencing cursor weakref"); + goto error; + } + if (Py_None == py_curs) { + PyErr_SetString(InterfaceError, + "the asynchronous cursor has disappeared"); + goto error; + } + + Py_INCREF(py_curs); + return (cursorObject *)py_curs; + +error: + pq_clear_async(self); + return NULL; +} + +/* conn_poll - Main polling switch + * + * The function is called in all the states and connection types and invokes + * the right "next step". + */ + +int +conn_poll(connectionObject *self) +{ + int res = PSYCO_POLL_ERROR; + Dprintf("conn_poll: status = %d", self->status); + + switch (self->status) { + case CONN_STATUS_SETUP: + Dprintf("conn_poll: status -> CONN_STATUS_SETUP"); + self->status = CONN_STATUS_CONNECTING; + res = PSYCO_POLL_WRITE; + break; + + case CONN_STATUS_CONNECTING: + Dprintf("conn_poll: status -> CONN_STATUS_CONNECTING"); + res = _conn_poll_connecting(self); + if (res == PSYCO_POLL_OK && self->async) { + res = _conn_poll_setup_async(self); + } + break; + + case CONN_STATUS_DATESTYLE: + Dprintf("conn_poll: status -> CONN_STATUS_DATESTYLE"); + res = _conn_poll_setup_async(self); + break; + + case CONN_STATUS_READY: + case CONN_STATUS_BEGIN: + case CONN_STATUS_PREPARED: + Dprintf("conn_poll: status -> CONN_STATUS_*"); + res = _conn_poll_query(self); + + if (res == PSYCO_POLL_OK && self->async && self->async_cursor) { + cursorObject *curs; + + /* An async query has just finished: parse the tuple in the + * target cursor. */ + if (!(curs = _conn_get_async_cursor(self))) { + res = PSYCO_POLL_ERROR; + break; + } + + curs_set_result(curs, self->pgres); + self->pgres = NULL; + + /* fetch the tuples (if there are any) and build the result. We + * don't care if pq_fetch return 0 or 1, but if there was an error, + * we want to signal it to the caller. */ + if (pq_fetch(curs, 0) == -1) { + res = PSYCO_POLL_ERROR; + } + + /* We have finished with our async_cursor */ + Py_DECREF(curs); + Py_CLEAR(self->async_cursor); + } + break; + + default: + Dprintf("conn_poll: in unexpected state"); + res = PSYCO_POLL_ERROR; + } + + Dprintf("conn_poll: returning %d", res); + return res; +} + +/* conn_close - do anything needed to shut down the connection */ + +void +conn_close(connectionObject *self) +{ + /* a connection with closed == 2 still requires cleanup */ + if (self->closed == 1) { + return; + } + + /* sets this connection as closed even for other threads; */ + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&self->lock); + + conn_close_locked(self); + + pthread_mutex_unlock(&self->lock); + Py_END_ALLOW_THREADS; +} + + +/* Return a copy of the 'dsn' string with the password scrubbed. + * + * The string returned is allocated on the Python heap. + * + * In case of error return NULL and raise an exception. + */ +char * +conn_obscure_password(const char *dsn) +{ + PQconninfoOption *options = NULL; + PyObject *d = NULL, *v = NULL, *pydsn = NULL; + char *rv = NULL; + + if (!dsn) { + PyErr_SetString(InternalError, "unexpected null string"); + goto exit; + } + + if (!(options = PQconninfoParse(dsn, NULL))) { + /* unlikely: the dsn was already tested valid */ + PyErr_SetString(InternalError, "the connection string is not valid"); + goto exit; + } + + if (!(d = psyco_dict_from_conninfo_options( + options, /* include_password = */ 1))) { + goto exit; + } + if (NULL == PyDict_GetItemString(d, "password")) { + /* the dsn doesn't have a password */ + psyco_strdup(&rv, dsn, -1); + goto exit; + } + + /* scrub the password and put back the connection string together */ + if (!(v = Text_FromUTF8("xxx"))) { goto exit; } + if (0 > PyDict_SetItemString(d, "password", v)) { goto exit; } + if (!(pydsn = psyco_make_dsn(Py_None, d))) { goto exit; } + if (!(pydsn = psyco_ensure_bytes(pydsn))) { goto exit; } + + /* Return the connection string with the password replaced */ + psyco_strdup(&rv, Bytes_AS_STRING(pydsn), -1); + +exit: + PQconninfoFree(options); + Py_XDECREF(v); + Py_XDECREF(d); + Py_XDECREF(pydsn); + + return rv; +} + + +/* conn_close_locked - shut down the connection with the lock already taken */ + +void conn_close_locked(connectionObject *self) +{ + if (self->closed == 1) { + return; + } + + /* We used to call pq_abort_locked here, but the idea of issuing a + * rollback on close/GC has been considered inappropriate. + * + * Dropping the connection on the server has the same effect as the + * transaction is automatically rolled back. Some middleware, such as + * PgBouncer, have problem with connections closed in the middle of the + * transaction though: to avoid these problems the transaction should be + * closed only in status CONN_STATUS_READY. + */ + self->closed = 1; + + /* we need to check the value of pgconn, because we get called even when + * the connection fails! */ + if (self->pgconn) { + PQfinish(self->pgconn); + self->pgconn = NULL; + Dprintf("conn_close: PQfinish called"); + } +} + +/* conn_commit - commit on a connection */ + +RAISES_NEG int +conn_commit(connectionObject *self) +{ + int res; + + res = pq_commit(self); + return res; +} + +/* conn_rollback - rollback a connection */ + +RAISES_NEG int +conn_rollback(connectionObject *self) +{ + int res; + + res = pq_abort(self); + return res; +} + + +/* Change the state of the session */ +RAISES_NEG int +conn_set_session(connectionObject *self, int autocommit, + int isolevel, int readonly, int deferrable) +{ + int rv = -1; + int want_autocommit = autocommit == SRV_STATE_UNCHANGED ? + self->autocommit : autocommit; + + if (deferrable != SRV_STATE_UNCHANGED && self->server_version < 90100) { + PyErr_SetString(ProgrammingError, + "the 'deferrable' setting is only available" + " from PostgreSQL 9.1"); + goto exit; + } + + /* Promote an isolation level to one of the levels supported by the server */ + if (self->server_version < 80000) { + if (isolevel == ISOLATION_LEVEL_READ_UNCOMMITTED) { + isolevel = ISOLATION_LEVEL_READ_COMMITTED; + } + else if (isolevel == ISOLATION_LEVEL_REPEATABLE_READ) { + isolevel = ISOLATION_LEVEL_SERIALIZABLE; + } + } + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&self->lock); + + if (want_autocommit) { + /* we are or are going in autocommit state, so no BEGIN will be issued: + * configure the session with the characteristics requested */ + if (isolevel != SRV_STATE_UNCHANGED) { + if (0 > pq_set_guc_locked(self, + "default_transaction_isolation", srv_isolevels[isolevel], + &_save)) { + goto endlock; + } + } + if (readonly != SRV_STATE_UNCHANGED) { + if (0 > pq_set_guc_locked(self, + "default_transaction_read_only", srv_state_guc[readonly], + &_save)) { + goto endlock; + } + } + if (deferrable != SRV_STATE_UNCHANGED) { + if (0 > pq_set_guc_locked(self, + "default_transaction_deferrable", srv_state_guc[deferrable], + &_save)) { + goto endlock; + } + } + } + else if (self->autocommit) { + /* we are moving from autocommit to not autocommit, so revert the + * characteristics to defaults to let BEGIN do its work */ + if (self->isolevel != ISOLATION_LEVEL_DEFAULT) { + if (0 > pq_set_guc_locked(self, + "default_transaction_isolation", "default", + &_save)) { + goto endlock; + } + } + if (self->readonly != STATE_DEFAULT) { + if (0 > pq_set_guc_locked(self, + "default_transaction_read_only", "default", + &_save)) { + goto endlock; + } + } + if (self->server_version >= 90100 && self->deferrable != STATE_DEFAULT) { + if (0 > pq_set_guc_locked(self, + "default_transaction_deferrable", "default", + &_save)) { + goto endlock; + } + } + } + + if (autocommit != SRV_STATE_UNCHANGED) { + self->autocommit = autocommit; + } + if (isolevel != SRV_STATE_UNCHANGED) { + self->isolevel = isolevel; + } + if (readonly != SRV_STATE_UNCHANGED) { + self->readonly = readonly; + } + if (deferrable != SRV_STATE_UNCHANGED) { + self->deferrable = deferrable; + } + rv = 0; + +endlock: + pthread_mutex_unlock(&self->lock); + Py_END_ALLOW_THREADS; + + if (rv < 0) { + pq_complete_error(self); + goto exit; + } + + Dprintf( + "conn_set_session: autocommit %d, isolevel %d, readonly %d, deferrable %d", + autocommit, isolevel, readonly, deferrable); + + +exit: + return rv; +} + + +/* conn_set_client_encoding - switch client encoding on connection */ + +RAISES_NEG int +conn_set_client_encoding(connectionObject *self, const char *pgenc) +{ + int res = -1; + char *clean_enc = NULL; + + /* We must know what python encoding this encoding is. */ + if (0 > clear_encoding_name(pgenc, &clean_enc)) { goto exit; } + + /* If the current encoding is equal to the requested one we don't + issue any query to the backend */ + if (strcmp(self->encoding, clean_enc) == 0) { + res = 0; + goto exit; + } + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&self->lock); + + /* abort the current transaction, to set the encoding ouside of + transactions */ + if ((res = pq_abort_locked(self, &_save))) { + goto endlock; + } + + if ((res = pq_set_guc_locked(self, "client_encoding", clean_enc, &_save))) { + goto endlock; + } + +endlock: + pthread_mutex_unlock(&self->lock); + Py_END_ALLOW_THREADS; + + if (res < 0) { + pq_complete_error(self); + goto exit; + } + + res = conn_store_encoding(self, pgenc); + + Dprintf("conn_set_client_encoding: encoding set to %s", self->encoding); + +exit: + PyMem_Free(clean_enc); + + return res; +} + + +/* conn_tpc_begin -- begin a two-phase commit. + * + * The state of a connection in the middle of a TPC is exactly the same + * of a normal transaction, in CONN_STATUS_BEGIN, but with the tpc_xid + * member set to the xid used. This allows to reuse all the code paths used + * in regular transactions, as PostgreSQL won't even know we are in a TPC + * until PREPARE. */ + +RAISES_NEG int +conn_tpc_begin(connectionObject *self, xidObject *xid) +{ + Dprintf("conn_tpc_begin: starting transaction"); + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&self->lock); + + if (pq_begin_locked(self, &_save) < 0) { + pthread_mutex_unlock(&(self->lock)); + Py_BLOCK_THREADS; + pq_complete_error(self); + return -1; + } + + pthread_mutex_unlock(&self->lock); + Py_END_ALLOW_THREADS; + + /* The transaction started ok, let's store this xid. */ + Py_INCREF(xid); + self->tpc_xid = xid; + + return 0; +} + + +/* conn_tpc_command -- run one of the TPC-related PostgreSQL commands. + * + * The function doesn't change the connection state as it can be used + * for many commands and for recovered transactions. */ + +RAISES_NEG int +conn_tpc_command(connectionObject *self, const char *cmd, xidObject *xid) +{ + PyObject *tid = NULL; + const char *ctid; + int rv = -1; + + Dprintf("conn_tpc_command: %s", cmd); + + /* convert the xid into PostgreSQL transaction id while keeping the GIL */ + if (!(tid = psyco_ensure_bytes(xid_get_tid(xid)))) { goto exit; } + if (!(ctid = Bytes_AsString(tid))) { goto exit; } + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&self->lock); + + if (0 > (rv = pq_tpc_command_locked(self, cmd, ctid, &_save))) { + pthread_mutex_unlock(&self->lock); + Py_BLOCK_THREADS; + pq_complete_error(self); + goto exit; + } + + pthread_mutex_unlock(&self->lock); + Py_END_ALLOW_THREADS; + +exit: + Py_XDECREF(tid); + return rv; +} + +/* conn_tpc_recover -- return a list of pending TPC Xid */ + +PyObject * +conn_tpc_recover(connectionObject *self) +{ + int status; + PyObject *xids = NULL; + PyObject *rv = NULL; + PyObject *tmp; + + /* store the status to restore it. */ + status = self->status; + + if (!(xids = xid_recover((PyObject *)self))) { goto exit; } + + if (status == CONN_STATUS_READY && self->status == CONN_STATUS_BEGIN) { + /* recover began a transaction: let's abort it. */ + if (!(tmp = PyObject_CallMethod((PyObject *)self, "rollback", NULL))) { + goto exit; + } + Py_DECREF(tmp); + } + + /* all fine */ + rv = xids; + xids = NULL; + +exit: + Py_XDECREF(xids); + + return rv; + +} + + +void +conn_set_result(connectionObject *self, PGresult *pgres) +{ + PQclear(self->pgres); + self->pgres = pgres; +} + + +void +conn_set_error(connectionObject *self, const char *msg) +{ + if (self->error) { + free(self->error); + self->error = NULL; + } + if (msg && *msg) { + self->error = strdup(msg); + } +} diff --git a/source-code/psycopg2/psycopg/connection_type.c b/source-code/psycopg2/psycopg/connection_type.c new file mode 100644 index 0000000..339f7f1 --- /dev/null +++ b/source-code/psycopg2/psycopg/connection_type.c @@ -0,0 +1,1518 @@ +/* connection_type.c - python interface to connection objects + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/connection.h" +#include "psycopg/cursor.h" +#include "psycopg/pqpath.h" +#include "psycopg/conninfo.h" +#include "psycopg/lobject.h" +#include "psycopg/green.h" +#include "psycopg/xid.h" + +#include +#include +#include + +extern HIDDEN const char *srv_isolevels[]; +extern HIDDEN const char *srv_readonly[]; +extern HIDDEN const char *srv_deferrable[]; +extern HIDDEN const int SRV_STATE_UNCHANGED; + +/** DBAPI methods **/ + +/* cursor method - allocate a new cursor */ + +#define psyco_conn_cursor_doc \ +"cursor(name=None, cursor_factory=extensions.cursor, withhold=False) -- new cursor\n\n" \ +"Return a new cursor.\n\nThe ``cursor_factory`` argument can be used to\n" \ +"create non-standard cursors by passing a class different from the\n" \ +"default. Note that the new class *should* be a sub-class of\n" \ +"`extensions.cursor`.\n\n" \ +":rtype: `extensions.cursor`" + +static PyObject * +psyco_conn_cursor(connectionObject *self, PyObject *args, PyObject *kwargs) +{ + PyObject *obj = NULL; + PyObject *rv = NULL; + PyObject *name = Py_None; + PyObject *factory = Py_None; + PyObject *withhold = Py_False; + PyObject *scrollable = Py_None; + + static char *kwlist[] = { + "name", "cursor_factory", "withhold", "scrollable", NULL}; + + EXC_IF_CONN_CLOSED(self); + + if (!PyArg_ParseTupleAndKeywords( + args, kwargs, "|OOOO", kwlist, + &name, &factory, &withhold, &scrollable)) { + goto exit; + } + + if (factory == Py_None) { + if (self->cursor_factory && self->cursor_factory != Py_None) { + factory = self->cursor_factory; + } + else { + factory = (PyObject *)&cursorType; + } + } + + if (self->status != CONN_STATUS_READY && + self->status != CONN_STATUS_BEGIN && + self->status != CONN_STATUS_PREPARED) { + PyErr_SetString(OperationalError, + "asynchronous connection attempt underway"); + goto exit; + } + + if (name != Py_None && self->async == 1) { + PyErr_SetString(ProgrammingError, + "asynchronous connections " + "cannot produce named cursors"); + goto exit; + } + + Dprintf("psyco_conn_cursor: new %s cursor for connection at %p", + (name == Py_None ? "unnamed" : "named"), self); + + if (!(obj = PyObject_CallFunctionObjArgs(factory, self, name, NULL))) { + goto exit; + } + + if (PyObject_IsInstance(obj, (PyObject *)&cursorType) == 0) { + PyErr_SetString(PyExc_TypeError, + "cursor factory must be subclass of psycopg2.extensions.cursor"); + goto exit; + } + + if (0 > curs_withhold_set((cursorObject *)obj, withhold)) { + goto exit; + } + if (0 > curs_scrollable_set((cursorObject *)obj, scrollable)) { + goto exit; + } + + Dprintf("psyco_conn_cursor: new cursor at %p: refcnt = " + FORMAT_CODE_PY_SSIZE_T, + obj, Py_REFCNT(obj) + ); + + rv = obj; + obj = NULL; + +exit: + Py_XDECREF(obj); + return rv; +} + + +/* close method - close the connection and all related cursors */ + +#define psyco_conn_close_doc "close() -- Close the connection." + +static PyObject * +psyco_conn_close(connectionObject *self, PyObject *dummy) +{ + Dprintf("psyco_conn_close: closing connection at %p", self); + conn_close(self); + Dprintf("psyco_conn_close: connection at %p closed", self); + + Py_RETURN_NONE; +} + + +/* commit method - commit all changes to the database */ + +#define psyco_conn_commit_doc "commit() -- Commit all changes to database." + +static PyObject * +psyco_conn_commit(connectionObject *self, PyObject *dummy) +{ + EXC_IF_CONN_CLOSED(self); + EXC_IF_CONN_ASYNC(self, commit); + EXC_IF_TPC_BEGIN(self, commit); + + if (conn_commit(self) < 0) + return NULL; + + Py_RETURN_NONE; +} + + +/* rollback method - roll back all changes done to the database */ + +#define psyco_conn_rollback_doc \ +"rollback() -- Roll back all changes done to database." + +static PyObject * +psyco_conn_rollback(connectionObject *self, PyObject *dummy) +{ + EXC_IF_CONN_CLOSED(self); + EXC_IF_CONN_ASYNC(self, rollback); + EXC_IF_TPC_BEGIN(self, rollback); + + if (conn_rollback(self) < 0) + return NULL; + + Py_RETURN_NONE; +} + + +#define psyco_conn_xid_doc \ +"xid(format_id, gtrid, bqual) -- create a transaction identifier." + +static PyObject * +psyco_conn_xid(connectionObject *self, PyObject *args, PyObject *kwargs) +{ + EXC_IF_CONN_CLOSED(self); + EXC_IF_TPC_NOT_SUPPORTED(self); + + return PyObject_Call((PyObject *)&xidType, args, kwargs); +} + + +#define psyco_conn_tpc_begin_doc \ +"tpc_begin(xid) -- begin a TPC transaction with given transaction ID xid." + +static PyObject * +psyco_conn_tpc_begin(connectionObject *self, PyObject *args) +{ + PyObject *rv = NULL; + xidObject *xid = NULL; + PyObject *oxid; + + EXC_IF_CONN_CLOSED(self); + EXC_IF_CONN_ASYNC(self, tpc_begin); + EXC_IF_TPC_NOT_SUPPORTED(self); + EXC_IF_IN_TRANSACTION(self, tpc_begin); + + if (!PyArg_ParseTuple(args, "O", &oxid)) { + goto exit; + } + + if (NULL == (xid = xid_ensure(oxid))) { + goto exit; + } + + /* two phase commit and autocommit make no point */ + if (self->autocommit) { + PyErr_SetString(ProgrammingError, + "tpc_begin can't be called in autocommit mode"); + goto exit; + } + + if (conn_tpc_begin(self, xid) < 0) { + goto exit; + } + + Py_INCREF(Py_None); + rv = Py_None; + +exit: + Py_XDECREF(xid); + return rv; +} + + +#define psyco_conn_tpc_prepare_doc \ +"tpc_prepare() -- perform the first phase of a two-phase transaction." + +static PyObject * +psyco_conn_tpc_prepare(connectionObject *self, PyObject *dummy) +{ + EXC_IF_CONN_CLOSED(self); + EXC_IF_CONN_ASYNC(self, tpc_prepare); + EXC_IF_TPC_PREPARED(self, tpc_prepare); + + if (NULL == self->tpc_xid) { + PyErr_SetString(ProgrammingError, + "prepare must be called inside a two-phase transaction"); + return NULL; + } + + if (0 > conn_tpc_command(self, "PREPARE TRANSACTION", self->tpc_xid)) { + return NULL; + } + + /* transaction prepared: set the state so that no operation + * can be performed until commit. */ + self->status = CONN_STATUS_PREPARED; + + Py_RETURN_NONE; +} + + +/* the type of conn_commit/conn_rollback */ +typedef int (*_finish_f)(connectionObject *self); + +/* Implement tpc_commit/tpc_rollback. + * + * This is a common framework performing the chechs and state manipulation + * common to the two functions. + * + * Parameters are: + * - self, args: passed by Python + * - opc_f: the function to call in case of one-phase commit/rollback + * one of conn_commit/conn_rollback + * - tpc_cmd: the command to execute for a two-phase commit/rollback + * + * The function can be called in three cases: + * - If xid is specified, the status must be "ready"; + * issue the commit/rollback prepared. + * - if xid is not specified and status is "begin" with a xid, + * issue a normal commit/rollback. + * - if xid is not specified and status is "prepared", + * issue the commit/rollback prepared. + */ +static PyObject * +_psyco_conn_tpc_finish(connectionObject *self, PyObject *args, + _finish_f opc_f, char *tpc_cmd) +{ + PyObject *oxid = NULL; + xidObject *xid = NULL; + PyObject *rv = NULL; + + if (!PyArg_ParseTuple(args, "|O", &oxid)) { goto exit; } + + if (oxid) { + if (!(xid = xid_ensure(oxid))) { goto exit; } + } + + if (xid) { + /* committing/aborting a recovered transaction. */ + if (self->status != CONN_STATUS_READY) { + PyErr_SetString(ProgrammingError, + "tpc_commit/tpc_rollback with a xid " + "must be called outside a transaction"); + goto exit; + } + if (0 > conn_tpc_command(self, tpc_cmd, xid)) { + goto exit; + } + } else { + /* committing/aborting our own transaction. */ + if (!self->tpc_xid) { + PyErr_SetString(ProgrammingError, + "tpc_commit/tpc_rollback with no parameter " + "must be called in a two-phase transaction"); + goto exit; + } + + switch (self->status) { + case CONN_STATUS_BEGIN: + if (0 > opc_f(self)) { goto exit; } + break; + + case CONN_STATUS_PREPARED: + if (0 > conn_tpc_command(self, tpc_cmd, self->tpc_xid)) { + goto exit; + } + break; + + default: + PyErr_SetString(InterfaceError, + "unexpected state in tpc_commit/tpc_rollback"); + goto exit; + } + + Py_CLEAR(self->tpc_xid); + + /* connection goes ready */ + self->status = CONN_STATUS_READY; + } + + Py_INCREF(Py_None); + rv = Py_None; + +exit: + Py_XDECREF(xid); + return rv; +} + +#define psyco_conn_tpc_commit_doc \ +"tpc_commit([xid]) -- commit a transaction previously prepared." + +static PyObject * +psyco_conn_tpc_commit(connectionObject *self, PyObject *args) +{ + EXC_IF_CONN_CLOSED(self); + EXC_IF_CONN_ASYNC(self, tpc_commit); + EXC_IF_TPC_NOT_SUPPORTED(self); + + return _psyco_conn_tpc_finish(self, args, + conn_commit, "COMMIT PREPARED"); +} + +#define psyco_conn_tpc_rollback_doc \ +"tpc_rollback([xid]) -- abort a transaction previously prepared." + +static PyObject * +psyco_conn_tpc_rollback(connectionObject *self, PyObject *args) +{ + EXC_IF_CONN_CLOSED(self); + EXC_IF_CONN_ASYNC(self, tpc_rollback); + EXC_IF_TPC_NOT_SUPPORTED(self); + + return _psyco_conn_tpc_finish(self, args, + conn_rollback, "ROLLBACK PREPARED"); +} + +#define psyco_conn_tpc_recover_doc \ +"tpc_recover() -- returns a list of pending transaction IDs." + +static PyObject * +psyco_conn_tpc_recover(connectionObject *self, PyObject *dummy) +{ + EXC_IF_CONN_CLOSED(self); + EXC_IF_CONN_ASYNC(self, tpc_recover); + EXC_IF_TPC_PREPARED(self, tpc_recover); + EXC_IF_TPC_NOT_SUPPORTED(self); + + return conn_tpc_recover(self); +} + + +#define psyco_conn_enter_doc \ +"__enter__ -> self" + +static PyObject * +psyco_conn_enter(connectionObject *self, PyObject *dummy) +{ + PyObject *rv = NULL; + + EXC_IF_CONN_CLOSED(self); + + if (self->entered) { + PyErr_SetString(ProgrammingError, + "the connection cannot be re-entered recursively"); + goto exit; + } + + self->entered = 1; + Py_INCREF(self); + rv = (PyObject *)self; + +exit: + return rv; +} + + +#define psyco_conn_exit_doc \ +"__exit__ -- commit if no exception, else roll back" + +static PyObject * +psyco_conn_exit(connectionObject *self, PyObject *args) +{ + PyObject *type, *name, *tb; + PyObject *tmp = NULL; + PyObject *rv = NULL; + + if (!PyArg_ParseTuple(args, "OOO", &type, &name, &tb)) { + goto exit; + } + + /* even if there will be an error, consider ourselves out */ + self->entered = 0; + + if (type == Py_None) { + if (!(tmp = PyObject_CallMethod((PyObject *)self, "commit", NULL))) { + goto exit; + } + } else { + if (!(tmp = PyObject_CallMethod((PyObject *)self, "rollback", NULL))) { + goto exit; + } + } + + /* success (of the commit or rollback, there may have been an exception in + * the block). Return None to avoid swallowing the exception */ + rv = Py_None; + Py_INCREF(rv); + +exit: + Py_XDECREF(tmp); + return rv; +} + + +/* parse a python object into one of the possible isolation level values */ + +RAISES_NEG static int +_psyco_conn_parse_isolevel(PyObject *pyval) +{ + int rv = -1; + long level; + + Py_INCREF(pyval); /* for ensure_bytes */ + + /* None is default. This is only used when setting the property, because + * set_session() has None used as "don't change" */ + if (pyval == Py_None) { + rv = ISOLATION_LEVEL_DEFAULT; + } + + /* parse from one of the level constants */ + else if (PyInt_Check(pyval)) { + level = PyInt_AsLong(pyval); + if (level == -1 && PyErr_Occurred()) { goto exit; } + if (level < 1 || level > 4) { + PyErr_SetString(PyExc_ValueError, + "isolation_level must be between 1 and 4"); + goto exit; + } + + rv = level; + } + + /* parse from the string -- this includes "default" */ + else { + if (!(pyval = psyco_ensure_bytes(pyval))) { + goto exit; + } + for (level = 1; level <= 4; level++) { + if (0 == strcasecmp(srv_isolevels[level], Bytes_AS_STRING(pyval))) { + rv = level; + break; + } + } + if (rv < 0 && 0 == strcasecmp("default", Bytes_AS_STRING(pyval))) { + rv = ISOLATION_LEVEL_DEFAULT; + } + if (rv < 0) { + PyErr_Format(PyExc_ValueError, + "bad value for isolation_level: '%s'", Bytes_AS_STRING(pyval)); + goto exit; + } + } + +exit: + Py_XDECREF(pyval); + + return rv; +} + +/* convert False/True/"default" -> 0/1/2 */ + +RAISES_NEG static int +_psyco_conn_parse_onoff(PyObject *pyval) +{ + int rv = -1; + + Py_INCREF(pyval); /* for ensure_bytes */ + + if (pyval == Py_None) { + rv = STATE_DEFAULT; + } + else if (PyUnicode_CheckExact(pyval) || Bytes_CheckExact(pyval)) { + if (!(pyval = psyco_ensure_bytes(pyval))) { + goto exit; + } + if (0 == strcasecmp("default", Bytes_AS_STRING(pyval))) { + rv = STATE_DEFAULT; + } + else { + PyErr_Format(PyExc_ValueError, + "the only string accepted is 'default'; got %s", + Bytes_AS_STRING(pyval)); + goto exit; + } + } + else { + int istrue; + if (0 > (istrue = PyObject_IsTrue(pyval))) { goto exit; } + rv = istrue ? STATE_ON : STATE_OFF; + } + +exit: + Py_XDECREF(pyval); + + return rv; +} + +#define _set_session_checks(self,what) \ +do { \ + EXC_IF_CONN_CLOSED(self); \ + EXC_IF_CONN_ASYNC(self, what); \ + EXC_IF_IN_TRANSACTION(self, what); \ + EXC_IF_TPC_PREPARED(self, what); \ +} while(0) + +/* set_session - set default transaction characteristics */ + +#define psyco_conn_set_session_doc \ +"set_session(...) -- Set one or more parameters for the next transactions.\n\n" \ +"Accepted arguments are 'isolation_level', 'readonly', 'deferrable', 'autocommit'." + +static PyObject * +psyco_conn_set_session(connectionObject *self, PyObject *args, PyObject *kwargs) +{ + PyObject *isolevel = Py_None; + PyObject *readonly = Py_None; + PyObject *deferrable = Py_None; + PyObject *autocommit = Py_None; + + int c_isolevel = SRV_STATE_UNCHANGED; + int c_readonly = SRV_STATE_UNCHANGED; + int c_deferrable = SRV_STATE_UNCHANGED; + int c_autocommit = SRV_STATE_UNCHANGED; + + static char *kwlist[] = + {"isolation_level", "readonly", "deferrable", "autocommit", NULL}; + + _set_session_checks(self, set_session); + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOOO", kwlist, + &isolevel, &readonly, &deferrable, &autocommit)) { + return NULL; + } + + if (Py_None != isolevel) { + if (0 > (c_isolevel = _psyco_conn_parse_isolevel(isolevel))) { + return NULL; + } + } + + if (Py_None != readonly) { + if (0 > (c_readonly = _psyco_conn_parse_onoff(readonly))) { + return NULL; + } + } + if (Py_None != deferrable) { + if (0 > (c_deferrable = _psyco_conn_parse_onoff(deferrable))) { + return NULL; + } + } + + if (Py_None != autocommit) { + if (-1 == (c_autocommit = PyObject_IsTrue(autocommit))) { return NULL; } + } + + if (0 > conn_set_session( + self, c_autocommit, c_isolevel, c_readonly, c_deferrable)) { + return NULL; + } + + Py_RETURN_NONE; +} + + +/* autocommit - return or set the current autocommit status */ + +#define psyco_conn_autocommit_doc \ +"Set or return the autocommit status." + +static PyObject * +psyco_conn_autocommit_get(connectionObject *self) +{ + return PyBool_FromLong(self->autocommit); +} + +BORROWED static PyObject * +_psyco_set_session_check_setter_wrapper(connectionObject *self) +{ + /* wrapper to use the EXC_IF macros. + * return NULL in case of error, else whatever */ + _set_session_checks(self, set_session); + return Py_None; /* borrowed */ +} + +static int +psyco_conn_autocommit_set(connectionObject *self, PyObject *pyvalue) +{ + int value; + + if (!_psyco_set_session_check_setter_wrapper(self)) { return -1; } + if (-1 == (value = PyObject_IsTrue(pyvalue))) { return -1; } + if (0 > conn_set_session(self, value, + SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED)) { + return -1; + } + + return 0; +} + + +/* isolation_level - return or set the current isolation level */ + +#define psyco_conn_isolation_level_doc \ +"Set or return the connection transaction isolation level." + +static PyObject * +psyco_conn_isolation_level_get(connectionObject *self) +{ + if (self->isolevel == ISOLATION_LEVEL_DEFAULT) { + Py_RETURN_NONE; + } else { + return PyInt_FromLong((long)self->isolevel); + } +} + + +static int +psyco_conn_isolation_level_set(connectionObject *self, PyObject *pyvalue) +{ + int value; + + if (!_psyco_set_session_check_setter_wrapper(self)) { return -1; } + if (0 > (value = _psyco_conn_parse_isolevel(pyvalue))) { return -1; } + if (0 > conn_set_session(self, SRV_STATE_UNCHANGED, + value, SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED)) { + return -1; + } + + return 0; +} + + +/* set_isolation_level method - switch connection isolation level */ + +#define psyco_conn_set_isolation_level_doc \ +"set_isolation_level(level) -- Switch isolation level to ``level``." + +static PyObject * +psyco_conn_set_isolation_level(connectionObject *self, PyObject *args) +{ + int level = 1; + PyObject *pyval = NULL; + + EXC_IF_CONN_CLOSED(self); + EXC_IF_CONN_ASYNC(self, "isolation_level"); + EXC_IF_TPC_PREPARED(self, "isolation_level"); + + if (!PyArg_ParseTuple(args, "O", &pyval)) return NULL; + + if (pyval == Py_None) { + level = ISOLATION_LEVEL_DEFAULT; + } + + /* parse from one of the level constants */ + else if (PyInt_Check(pyval)) { + level = PyInt_AsLong(pyval); + + if (level < 0 || level > 4) { + PyErr_SetString(PyExc_ValueError, + "isolation level must be between 0 and 4"); + return NULL; + } + } + + if (0 > conn_rollback(self)) { + return NULL; + } + + if (level == 0) { + if (0 > conn_set_session(self, 1, + SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED)) { + return NULL; + } + } + else { + if (0 > conn_set_session(self, 0, + level, SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED)) { + return NULL; + } + } + + Py_RETURN_NONE; +} + + +/* readonly - return or set the current read-only status */ + +#define psyco_conn_readonly_doc \ +"Set or return the connection read-only status." + +static PyObject * +psyco_conn_readonly_get(connectionObject *self) +{ + PyObject *rv = NULL; + + switch (self->readonly) { + case STATE_OFF: + rv = Py_False; + break; + case STATE_ON: + rv = Py_True; + break; + case STATE_DEFAULT: + rv = Py_None; + break; + default: + PyErr_Format(InternalError, + "bad internal value for readonly: %d", self->readonly); + break; + } + + Py_XINCREF(rv); + return rv; +} + + +static int +psyco_conn_readonly_set(connectionObject *self, PyObject *pyvalue) +{ + int value; + + if (!_psyco_set_session_check_setter_wrapper(self)) { return -1; } + if (0 > (value = _psyco_conn_parse_onoff(pyvalue))) { return -1; } + if (0 > conn_set_session(self, SRV_STATE_UNCHANGED, + SRV_STATE_UNCHANGED, value, SRV_STATE_UNCHANGED)) { + return -1; + } + + return 0; +} + + +/* deferrable - return or set the current deferrable status */ + +#define psyco_conn_deferrable_doc \ +"Set or return the connection deferrable status." + +static PyObject * +psyco_conn_deferrable_get(connectionObject *self) +{ + PyObject *rv = NULL; + + switch (self->deferrable) { + case STATE_OFF: + rv = Py_False; + break; + case STATE_ON: + rv = Py_True; + break; + case STATE_DEFAULT: + rv = Py_None; + break; + default: + PyErr_Format(InternalError, + "bad internal value for deferrable: %d", self->deferrable); + break; + } + + Py_XINCREF(rv); + return rv; +} + + +static int +psyco_conn_deferrable_set(connectionObject *self, PyObject *pyvalue) +{ + int value; + + if (!_psyco_set_session_check_setter_wrapper(self)) { return -1; } + if (0 > (value = _psyco_conn_parse_onoff(pyvalue))) { return -1; } + if (0 > conn_set_session(self, SRV_STATE_UNCHANGED, + SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED, value)) { + return -1; + } + + return 0; +} + +/* psyco_get_native_connection - expose PGconn* as a Python capsule */ + +#define psyco_get_native_connection_doc \ +"get_native_connection() -- Return the internal PGconn* as a Python Capsule." + +static PyObject * +psyco_get_native_connection(connectionObject *self, PyObject *dummy) +{ + EXC_IF_CONN_CLOSED(self); + + return PyCapsule_New(self->pgconn, "psycopg2.connection.native_connection", NULL); +} + + +/* set_client_encoding method - set client encoding */ + +#define psyco_conn_set_client_encoding_doc \ +"set_client_encoding(encoding) -- Set client encoding to ``encoding``." + +static PyObject * +psyco_conn_set_client_encoding(connectionObject *self, PyObject *args) +{ + const char *enc; + PyObject *rv = NULL; + + EXC_IF_CONN_CLOSED(self); + EXC_IF_CONN_ASYNC(self, set_client_encoding); + EXC_IF_TPC_PREPARED(self, set_client_encoding); + + if (!PyArg_ParseTuple(args, "s", &enc)) return NULL; + + if (conn_set_client_encoding(self, enc) >= 0) { + Py_INCREF(Py_None); + rv = Py_None; + } + return rv; +} + +/* get_transaction_status method - Get backend transaction status */ + +#define psyco_conn_get_transaction_status_doc \ +"get_transaction_status() -- Get backend transaction status." + +static PyObject * +psyco_conn_get_transaction_status(connectionObject *self, PyObject *dummy) +{ + return PyInt_FromLong((long)PQtransactionStatus(self->pgconn)); +} + +/* get_parameter_status method - Get server parameter status */ + +#define psyco_conn_get_parameter_status_doc \ +"get_parameter_status(parameter) -- Get backend parameter status.\n\n" \ +"Potential values for ``parameter``:\n" \ +" server_version, server_encoding, client_encoding, is_superuser,\n" \ +" session_authorization, DateStyle, TimeZone, integer_datetimes,\n" \ +" and standard_conforming_strings\n" \ +"If server did not report requested parameter, None is returned.\n\n" \ +"See libpq docs for PQparameterStatus() for further details." + +static PyObject * +psyco_conn_get_parameter_status(connectionObject *self, PyObject *args) +{ + const char *param = NULL; + const char *val = NULL; + + EXC_IF_CONN_CLOSED(self); + + if (!PyArg_ParseTuple(args, "s", ¶m)) return NULL; + + val = PQparameterStatus(self->pgconn, param); + if (!val) { + Py_RETURN_NONE; + } + return conn_text_from_chars(self, val); +} + +/* get_dsn_parameters method - Get connection parameters */ + +#define psyco_conn_get_dsn_parameters_doc \ +"get_dsn_parameters() -- Get effective connection parameters.\n\n" + +static PyObject * +psyco_conn_get_dsn_parameters(connectionObject *self, PyObject *dummy) +{ +#if PG_VERSION_NUM >= 90300 + PyObject *res = NULL; + PQconninfoOption *options = NULL; + + EXC_IF_CONN_CLOSED(self); + + if (!(options = PQconninfo(self->pgconn))) { + PyErr_NoMemory(); + goto exit; + } + + res = psyco_dict_from_conninfo_options(options, /* include_password = */ 0); + +exit: + PQconninfoFree(options); + + return res; +#else + PyErr_SetString(NotSupportedError, "PQconninfo not available in libpq < 9.3"); + return NULL; +#endif +} + + +/* lobject method - allocate a new lobject */ + +#define psyco_conn_lobject_doc \ +"lobject(oid=0, mode=0, new_oid=0, new_file=None,\n" \ +" lobject_factory=extensions.lobject) -- new lobject\n\n" \ +"Return a new lobject.\n\nThe ``lobject_factory`` argument can be used\n" \ +"to create non-standard lobjects by passing a class different from the\n" \ +"default. Note that the new class *should* be a sub-class of\n" \ +"`extensions.lobject`.\n\n" \ +":rtype: `extensions.lobject`" + +static PyObject * +psyco_conn_lobject(connectionObject *self, PyObject *args, PyObject *keywds) +{ + Oid oid = InvalidOid, new_oid = InvalidOid; + const char *new_file = NULL; + const char *smode = ""; + PyObject *factory = (PyObject *)&lobjectType; + PyObject *obj; + + static char *kwlist[] = {"oid", "mode", "new_oid", "new_file", + "lobject_factory", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, keywds, "|IzIzO", kwlist, + &oid, &smode, &new_oid, &new_file, + &factory)) { + return NULL; + } + + EXC_IF_CONN_CLOSED(self); + EXC_IF_CONN_ASYNC(self, lobject); + EXC_IF_GREEN(lobject); + EXC_IF_TPC_PREPARED(self, lobject); + + Dprintf("psyco_conn_lobject: new lobject for connection at %p", self); + Dprintf("psyco_conn_lobject: parameters: oid = %u, mode = %s", + oid, smode); + Dprintf("psyco_conn_lobject: parameters: new_oid = %u, new_file = %s", + new_oid, new_file); + + if (new_file) + obj = PyObject_CallFunction(factory, "OIsIs", + self, oid, smode, new_oid, new_file); + else + obj = PyObject_CallFunction(factory, "OIsI", + self, oid, smode, new_oid); + + if (obj == NULL) return NULL; + if (PyObject_IsInstance(obj, (PyObject *)&lobjectType) == 0) { + PyErr_SetString(PyExc_TypeError, + "lobject factory must be subclass of psycopg2.extensions.lobject"); + Py_DECREF(obj); + return NULL; + } + + Dprintf("psyco_conn_lobject: new lobject at %p: refcnt = " + FORMAT_CODE_PY_SSIZE_T, + obj, Py_REFCNT(obj)); + return obj; +} + +/* get the current backend pid */ + +#define psyco_conn_get_backend_pid_doc \ +"get_backend_pid() -- Get backend process id." + +static PyObject * +psyco_conn_get_backend_pid(connectionObject *self, PyObject *dummy) +{ + EXC_IF_CONN_CLOSED(self); + + return PyInt_FromLong((long)PQbackendPID(self->pgconn)); +} + + +/* get info about the connection */ + +#define psyco_conn_info_doc \ +"info -- Get connection info." + +static PyObject * +psyco_conn_info_get(connectionObject *self) +{ + return PyObject_CallFunctionObjArgs( + (PyObject *)&connInfoType, (PyObject *)self, NULL); +} + + +/* return the pointer to the PGconn structure */ + +#define psyco_conn_pgconn_ptr_doc \ +"pgconn_ptr -- Get the PGconn structure pointer." + +static PyObject * +psyco_conn_pgconn_ptr_get(connectionObject *self) +{ + if (self->pgconn) { + return PyLong_FromVoidPtr((void *)self->pgconn); + } + else { + Py_RETURN_NONE; + } +} + + +/* reset the currect connection */ + +#define psyco_conn_reset_doc \ +"reset() -- Reset current connection to defaults." + +static PyObject * +psyco_conn_reset(connectionObject *self, PyObject *dummy) +{ + int res; + + EXC_IF_CONN_CLOSED(self); + EXC_IF_CONN_ASYNC(self, reset); + + if (pq_reset(self) < 0) + return NULL; + + res = conn_setup(self); + if (res < 0) + return NULL; + + Py_RETURN_NONE; +} + +static PyObject * +psyco_conn_get_exception(PyObject *self, void *closure) +{ + PyObject *exception = *(PyObject **)closure; + + Py_INCREF(exception); + return exception; +} + + +#define psyco_conn_poll_doc \ +"poll() -> int -- Advance the connection or query process without blocking." + +static PyObject * +psyco_conn_poll(connectionObject *self, PyObject *dummy) +{ + int res; + + EXC_IF_CONN_CLOSED(self); + + res = conn_poll(self); + if (res != PSYCO_POLL_ERROR || !PyErr_Occurred()) { + return PyInt_FromLong(res); + } else { + /* There is an error and an exception is already in place */ + return NULL; + } +} + + +#define psyco_conn_fileno_doc \ +"fileno() -> int -- Return file descriptor associated to database connection." + +static PyObject * +psyco_conn_fileno(connectionObject *self, PyObject *dummy) +{ + long int socket; + + EXC_IF_CONN_CLOSED(self); + + socket = (long int)PQsocket(self->pgconn); + + return PyInt_FromLong(socket); +} + + +#define psyco_conn_isexecuting_doc \ +"isexecuting() -> bool -- Return True if the connection is " \ + "executing an asynchronous operation." + +static PyObject * +psyco_conn_isexecuting(connectionObject *self, PyObject *dummy) +{ + /* synchronous connections will always return False */ + if (self->async == 0) { + Py_RETURN_FALSE; + } + + /* check if the connection is still being built */ + if (self->status != CONN_STATUS_READY) { + Py_RETURN_TRUE; + } + + /* check if there is a query being executed */ + if (self->async_cursor != NULL) { + Py_RETURN_TRUE; + } + + /* otherwise it's not executing */ + Py_RETURN_FALSE; +} + + +#define psyco_conn_cancel_doc \ +"cancel() -- cancel the current operation" + +static PyObject * +psyco_conn_cancel(connectionObject *self, PyObject *dummy) +{ + char errbuf[256]; + + EXC_IF_CONN_CLOSED(self); + EXC_IF_TPC_PREPARED(self, cancel); + + /* do not allow cancellation while the connection is being built */ + Dprintf("psyco_conn_cancel: cancelling with key %p", self->cancel); + if (self->status != CONN_STATUS_READY && + self->status != CONN_STATUS_BEGIN) { + PyErr_SetString(OperationalError, + "asynchronous connection attempt underway"); + return NULL; + } + + if (PQcancel(self->cancel, errbuf, sizeof(errbuf)) == 0) { + Dprintf("psyco_conn_cancel: cancelling failed: %s", errbuf); + PyErr_SetString(OperationalError, errbuf); + return NULL; + } + Py_RETURN_NONE; +} + + +/** the connection object **/ + + +/* object method list */ + +static struct PyMethodDef connectionObject_methods[] = { + {"cursor", (PyCFunction)psyco_conn_cursor, + METH_VARARGS|METH_KEYWORDS, psyco_conn_cursor_doc}, + {"close", (PyCFunction)psyco_conn_close, + METH_NOARGS, psyco_conn_close_doc}, + {"commit", (PyCFunction)psyco_conn_commit, + METH_NOARGS, psyco_conn_commit_doc}, + {"rollback", (PyCFunction)psyco_conn_rollback, + METH_NOARGS, psyco_conn_rollback_doc}, + {"xid", (PyCFunction)psyco_conn_xid, + METH_VARARGS|METH_KEYWORDS, psyco_conn_xid_doc}, + {"tpc_begin", (PyCFunction)psyco_conn_tpc_begin, + METH_VARARGS, psyco_conn_tpc_begin_doc}, + {"tpc_prepare", (PyCFunction)psyco_conn_tpc_prepare, + METH_NOARGS, psyco_conn_tpc_prepare_doc}, + {"tpc_commit", (PyCFunction)psyco_conn_tpc_commit, + METH_VARARGS, psyco_conn_tpc_commit_doc}, + {"tpc_rollback", (PyCFunction)psyco_conn_tpc_rollback, + METH_VARARGS, psyco_conn_tpc_rollback_doc}, + {"tpc_recover", (PyCFunction)psyco_conn_tpc_recover, + METH_NOARGS, psyco_conn_tpc_recover_doc}, + {"__enter__", (PyCFunction)psyco_conn_enter, + METH_NOARGS, psyco_conn_enter_doc}, + {"__exit__", (PyCFunction)psyco_conn_exit, + METH_VARARGS, psyco_conn_exit_doc}, + {"set_session", (PyCFunction)psyco_conn_set_session, + METH_VARARGS|METH_KEYWORDS, psyco_conn_set_session_doc}, + {"set_isolation_level", (PyCFunction)psyco_conn_set_isolation_level, + METH_VARARGS, psyco_conn_set_isolation_level_doc}, + {"set_client_encoding", (PyCFunction)psyco_conn_set_client_encoding, + METH_VARARGS, psyco_conn_set_client_encoding_doc}, + {"get_transaction_status", (PyCFunction)psyco_conn_get_transaction_status, + METH_NOARGS, psyco_conn_get_transaction_status_doc}, + {"get_parameter_status", (PyCFunction)psyco_conn_get_parameter_status, + METH_VARARGS, psyco_conn_get_parameter_status_doc}, + {"get_dsn_parameters", (PyCFunction)psyco_conn_get_dsn_parameters, + METH_NOARGS, psyco_conn_get_dsn_parameters_doc}, + {"get_backend_pid", (PyCFunction)psyco_conn_get_backend_pid, + METH_NOARGS, psyco_conn_get_backend_pid_doc}, + {"lobject", (PyCFunction)psyco_conn_lobject, + METH_VARARGS|METH_KEYWORDS, psyco_conn_lobject_doc}, + {"reset", (PyCFunction)psyco_conn_reset, + METH_NOARGS, psyco_conn_reset_doc}, + {"poll", (PyCFunction)psyco_conn_poll, + METH_NOARGS, psyco_conn_poll_doc}, + {"fileno", (PyCFunction)psyco_conn_fileno, + METH_NOARGS, psyco_conn_fileno_doc}, + {"isexecuting", (PyCFunction)psyco_conn_isexecuting, + METH_NOARGS, psyco_conn_isexecuting_doc}, + {"cancel", (PyCFunction)psyco_conn_cancel, + METH_NOARGS, psyco_conn_cancel_doc}, + {"get_native_connection", (PyCFunction)psyco_get_native_connection, + METH_NOARGS, psyco_get_native_connection_doc}, + {NULL} +}; + +/* object member list */ + +static struct PyMemberDef connectionObject_members[] = { + {"closed", T_LONG, offsetof(connectionObject, closed), READONLY, + "True if the connection is closed."}, + {"encoding", T_STRING, offsetof(connectionObject, encoding), READONLY, + "The current client encoding."}, + {"notices", T_OBJECT, offsetof(connectionObject, notice_list), 0}, + {"notifies", T_OBJECT, offsetof(connectionObject, notifies), 0}, + {"dsn", T_STRING, offsetof(connectionObject, dsn), READONLY, + "The current connection string."}, + {"async", T_LONG, offsetof(connectionObject, async), READONLY, + "True if the connection is asynchronous."}, + {"async_", T_LONG, offsetof(connectionObject, async), READONLY, + "True if the connection is asynchronous."}, + {"status", T_INT, + offsetof(connectionObject, status), READONLY, + "The current transaction status."}, + {"cursor_factory", T_OBJECT, offsetof(connectionObject, cursor_factory), 0, + "Default cursor_factory for cursor()."}, + {"string_types", T_OBJECT, offsetof(connectionObject, string_types), READONLY, + "A set of typecasters to convert textual values."}, + {"binary_types", T_OBJECT, offsetof(connectionObject, binary_types), READONLY, + "A set of typecasters to convert binary values."}, + {"protocol_version", T_INT, + offsetof(connectionObject, protocol), READONLY, + "Protocol version used for this connection. Currently always 3."}, + {"server_version", T_INT, + offsetof(connectionObject, server_version), READONLY, + "Server version."}, + {NULL} +}; + +#define EXCEPTION_GETTER(exc) \ + { #exc, psyco_conn_get_exception, NULL, exc ## _doc, &exc } + +static struct PyGetSetDef connectionObject_getsets[] = { + EXCEPTION_GETTER(Error), + EXCEPTION_GETTER(Warning), + EXCEPTION_GETTER(InterfaceError), + EXCEPTION_GETTER(DatabaseError), + EXCEPTION_GETTER(InternalError), + EXCEPTION_GETTER(OperationalError), + EXCEPTION_GETTER(ProgrammingError), + EXCEPTION_GETTER(IntegrityError), + EXCEPTION_GETTER(DataError), + EXCEPTION_GETTER(NotSupportedError), + { "autocommit", + (getter)psyco_conn_autocommit_get, + (setter)psyco_conn_autocommit_set, + psyco_conn_autocommit_doc }, + { "isolation_level", + (getter)psyco_conn_isolation_level_get, + (setter)psyco_conn_isolation_level_set, + psyco_conn_isolation_level_doc }, + { "readonly", + (getter)psyco_conn_readonly_get, + (setter)psyco_conn_readonly_set, + psyco_conn_readonly_doc }, + { "deferrable", + (getter)psyco_conn_deferrable_get, + (setter)psyco_conn_deferrable_set, + psyco_conn_deferrable_doc }, + { "info", + (getter)psyco_conn_info_get, NULL, + psyco_conn_info_doc }, + { "pgconn_ptr", + (getter)psyco_conn_pgconn_ptr_get, NULL, + psyco_conn_pgconn_ptr_doc }, + {NULL} +}; +#undef EXCEPTION_GETTER + +/* initialization and finalization methods */ + +static int +connection_setup(connectionObject *self, const char *dsn, long int async) +{ + int rv = -1; + + Dprintf("connection_setup: init connection object at %p, " + "async %ld, refcnt = " FORMAT_CODE_PY_SSIZE_T, + self, async, Py_REFCNT(self) + ); + + if (!(self->dsn = conn_obscure_password(dsn))) { goto exit; } + if (!(self->notice_list = PyList_New(0))) { goto exit; } + if (!(self->notifies = PyList_New(0))) { goto exit; } + self->async = async; + self->status = CONN_STATUS_SETUP; + self->async_status = ASYNC_DONE; + if (!(self->string_types = PyDict_New())) { goto exit; } + if (!(self->binary_types = PyDict_New())) { goto exit; } + self->isolevel = ISOLATION_LEVEL_DEFAULT; + self->readonly = STATE_DEFAULT; + self->deferrable = STATE_DEFAULT; +#ifdef CONN_CHECK_PID + self->procpid = getpid(); +#endif + + /* other fields have been zeroed by tp_alloc */ + + if (0 != pthread_mutex_init(&(self->lock), NULL)) { + PyErr_SetString(InternalError, "lock initialization failed"); + goto exit; + } + + if (conn_connect(self, dsn, async) != 0) { + Dprintf("connection_init: FAILED"); + goto exit; + } + + rv = 0; + + Dprintf("connection_setup: good connection object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + self, Py_REFCNT(self)); + +exit: + return rv; +} + + +static int +connection_clear(connectionObject *self) +{ + Py_CLEAR(self->tpc_xid); + Py_CLEAR(self->async_cursor); + Py_CLEAR(self->notice_list); + Py_CLEAR(self->notifies); + Py_CLEAR(self->string_types); + Py_CLEAR(self->binary_types); + Py_CLEAR(self->cursor_factory); + Py_CLEAR(self->pyencoder); + Py_CLEAR(self->pydecoder); + return 0; +} + +static void +connection_dealloc(PyObject* obj) +{ + connectionObject *self = (connectionObject *)obj; + + /* Make sure to untrack the connection before calling conn_close, which may + * allow a different thread to try and dealloc the connection again, + * resulting in a double-free segfault (ticket #166). */ + PyObject_GC_UnTrack(self); + + /* close the connection only if this is the same process it was created + * into, otherwise using multiprocessing we may close the connection + * belonging to another process. */ +#ifdef CONN_CHECK_PID + if (self->procpid == getpid()) +#endif + { + conn_close(self); + } + + if (self->weakreflist) { + PyObject_ClearWeakRefs(obj); + } + + conn_notice_clean(self); + + PyMem_Free(self->dsn); + PyMem_Free(self->encoding); + if (self->error) free(self->error); + if (self->cancel) PQfreeCancel(self->cancel); + PQclear(self->pgres); + + connection_clear(self); + + pthread_mutex_destroy(&(self->lock)); + + Dprintf("connection_dealloc: deleted connection object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + obj, Py_REFCNT(obj) + ); + + Py_TYPE(obj)->tp_free(obj); +} + +static int +connection_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + const char *dsn; + long int async = 0, async_ = 0; + static char *kwlist[] = {"dsn", "async", "async_", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|ll", kwlist, + &dsn, &async, &async_)) + return -1; + + if (async_) { async = async_; } + return connection_setup((connectionObject *)obj, dsn, async); +} + +static PyObject * +connection_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + +static PyObject * +connection_repr(connectionObject *self) +{ + return PyString_FromFormat( + "", + self, (self->dsn ? self->dsn : ""), self->closed); +} + +static int +connection_traverse(connectionObject *self, visitproc visit, void *arg) +{ + Py_VISIT((PyObject *)(self->tpc_xid)); + Py_VISIT(self->async_cursor); + Py_VISIT(self->notice_list); + Py_VISIT(self->notifies); + Py_VISIT(self->string_types); + Py_VISIT(self->binary_types); + Py_VISIT(self->cursor_factory); + Py_VISIT(self->pyencoder); + Py_VISIT(self->pydecoder); + return 0; +} + + +/* object type */ + +#define connectionType_doc \ +"connection(dsn, ...) -> new connection object\n\n" \ +":Groups:\n" \ +" * `DBAPI-2.0 errors`: Error, Warning, InterfaceError,\n" \ +" DatabaseError, InternalError, OperationalError,\n" \ +" ProgrammingError, IntegrityError, DataError, NotSupportedError" + +PyTypeObject connectionType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2.extensions.connection", + sizeof(connectionObject), 0, + connection_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + (reprfunc)connection_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + (reprfunc)connection_repr, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC | + Py_TPFLAGS_HAVE_WEAKREFS, + /*tp_flags*/ + connectionType_doc, /*tp_doc*/ + (traverseproc)connection_traverse, /*tp_traverse*/ + (inquiry)connection_clear, /*tp_clear*/ + 0, /*tp_richcompare*/ + offsetof(connectionObject, weakreflist), /* tp_weaklistoffset */ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + connectionObject_methods, /*tp_methods*/ + connectionObject_members, /*tp_members*/ + connectionObject_getsets, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + connection_init, /*tp_init*/ + 0, /*tp_alloc*/ + connection_new, /*tp_new*/ +}; diff --git a/source-code/psycopg2/psycopg/conninfo.h b/source-code/psycopg2/psycopg/conninfo.h new file mode 100644 index 0000000..6887d4b --- /dev/null +++ b/source-code/psycopg2/psycopg/conninfo.h @@ -0,0 +1,41 @@ +/* connection.h - definition for the psycopg ConnectionInfo type + * + * Copyright (C) 2018-2019 Daniele Varrazzo + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_CONNINFO_H +#define PSYCOPG_CONNINFO_H 1 + +#include "psycopg/connection.h" + +extern HIDDEN PyTypeObject connInfoType; + +typedef struct { + PyObject_HEAD + + connectionObject *conn; + +} connInfoObject; + +#endif /* PSYCOPG_CONNINFO_H */ diff --git a/source-code/psycopg2/psycopg/conninfo_type.c b/source-code/psycopg2/psycopg/conninfo_type.c new file mode 100644 index 0000000..9a10c94 --- /dev/null +++ b/source-code/psycopg2/psycopg/conninfo_type.c @@ -0,0 +1,648 @@ +/* conninfo_type.c - present information about the libpq connection + * + * Copyright (C) 2018-2019 Daniele Varrazzo + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/conninfo.h" + + +static const char connInfoType_doc[] = +"Details about the native PostgreSQL database connection.\n" +"\n" +"This class exposes several `informative functions`__ about the status\n" +"of the libpq connection.\n" +"\n" +"Objects of this class are exposed as the `connection.info` attribute.\n" +"\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"; + + +static const char dbname_doc[] = +"The database name of the connection.\n" +"\n" +".. seealso:: libpq docs for `PQdb()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQDB"; + +static PyObject * +dbname_get(connInfoObject *self) +{ + const char *val; + + val = PQdb(self->conn->pgconn); + if (!val) { + Py_RETURN_NONE; + } + return conn_text_from_chars(self->conn, val); +} + + +static const char user_doc[] = +"The user name of the connection.\n" +"\n" +".. seealso:: libpq docs for `PQuser()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQUSER"; + +static PyObject * +user_get(connInfoObject *self) +{ + const char *val; + + val = PQuser(self->conn->pgconn); + if (!val) { + Py_RETURN_NONE; + } + return conn_text_from_chars(self->conn, val); +} + + +static const char password_doc[] = +"The password of the connection.\n" +"\n" +".. seealso:: libpq docs for `PQpass()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQPASS"; + +static PyObject * +password_get(connInfoObject *self) +{ + const char *val; + + val = PQpass(self->conn->pgconn); + if (!val) { + Py_RETURN_NONE; + } + return conn_text_from_chars(self->conn, val); +} + + +static const char host_doc[] = +"The server host name of the connection.\n" +"\n" +"This can be a host name, an IP address, or a directory path if the\n" +"connection is via Unix socket. (The path case can be distinguished\n" +"because it will always be an absolute path, beginning with ``/``.)\n" +"\n" +".. seealso:: libpq docs for `PQhost()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQHOST"; + +static PyObject * +host_get(connInfoObject *self) +{ + const char *val; + + val = PQhost(self->conn->pgconn); + if (!val) { + Py_RETURN_NONE; + } + return conn_text_from_chars(self->conn, val); +} + + +static const char port_doc[] = +"The port of the connection.\n" +"\n" +":type: `!int`\n" +"\n" +".. seealso:: libpq docs for `PQport()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQPORT"; + +static PyObject * +port_get(connInfoObject *self) +{ + const char *val; + + val = PQport(self->conn->pgconn); + if (!val || !val[0]) { + Py_RETURN_NONE; + } + return PyInt_FromString((char *)val, NULL, 10); +} + + +static const char options_doc[] = +"The command-line options passed in the connection request.\n" +"\n" +".. seealso:: libpq docs for `PQoptions()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQOPTIONS"; + +static PyObject * +options_get(connInfoObject *self) +{ + const char *val; + + val = PQoptions(self->conn->pgconn); + if (!val) { + Py_RETURN_NONE; + } + return conn_text_from_chars(self->conn, val); +} + + +static const char dsn_parameters_doc[] = +"The effective connection parameters.\n" +"\n" +":type: `!dict`\n" +"\n" +"The results include values which weren't explicitly set by the connection\n" +"string, such as defaults, environment variables, etc.\n" +"The *password* parameter is removed from the results.\n" +"\n" +".. seealso:: libpq docs for `PQconninfo()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/libpq-connect.html" + "#LIBPQ-PQCONNINFO"; + +static PyObject * +dsn_parameters_get(connInfoObject *self) +{ +#if PG_VERSION_NUM >= 90300 + PyObject *res = NULL; + PQconninfoOption *options = NULL; + + EXC_IF_CONN_CLOSED(self->conn); + + if (!(options = PQconninfo(self->conn->pgconn))) { + PyErr_NoMemory(); + goto exit; + } + + res = psyco_dict_from_conninfo_options(options, /* include_password = */ 0); + +exit: + PQconninfoFree(options); + + return res; +#else + PyErr_SetString(NotSupportedError, "PQconninfo not available in libpq < 9.3"); + return NULL; +#endif +} + + +static const char status_doc[] = +"The status of the connection.\n" +"\n" +":type: `!int`\n" +"\n" +".. seealso:: libpq docs for `PQstatus()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQSTATUS"; + +static PyObject * +status_get(connInfoObject *self) +{ + ConnStatusType val; + + val = PQstatus(self->conn->pgconn); + return PyInt_FromLong((long)val); +} + + +static const char transaction_status_doc[] = +"The current in-transaction status of the connection.\n" +"\n" +"Symbolic constants for the values are defined in the module\n" +"`psycopg2.extensions`: see :ref:`transaction-status-constants` for the\n" +"available values.\n" +"\n" +":type: `!int`\n" +"\n" +".. seealso:: libpq docs for `PQtransactionStatus()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQTRANSACTIONSTATUS"; + +static PyObject * +transaction_status_get(connInfoObject *self) +{ + PGTransactionStatusType val; + + val = PQtransactionStatus(self->conn->pgconn); + return PyInt_FromLong((long)val); +} + + +static const char parameter_status_doc[] = +"Looks up a current parameter setting of the server.\n" +"\n" +":param name: The name of the parameter to return.\n" +":type name: `!str`\n" +":return: The parameter value, `!None` if the parameter is unknown.\n" +":rtype: `!str`\n" +"\n" +".. seealso:: libpq docs for `PQparameterStatus()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQPARAMETERSTATUS"; + +static PyObject * +parameter_status(connInfoObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"name", NULL}; + const char *name; + const char *val; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", kwlist, &name)) { + return NULL; + } + + val = PQparameterStatus(self->conn->pgconn, name); + + if (!val) { + Py_RETURN_NONE; + } + else { + return conn_text_from_chars(self->conn, val); + } +} + + +static const char protocol_version_doc[] = +"The frontend/backend protocol being used.\n" +"\n" +":type: `!int`\n" +"\n" +".. seealso:: libpq docs for `PQprotocolVersion()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQPROTOCOLVERSION"; + +static PyObject * +protocol_version_get(connInfoObject *self) +{ + int val; + + val = PQprotocolVersion(self->conn->pgconn); + return PyInt_FromLong((long)val); +} + + +static const char server_version_doc[] = +"Returns an integer representing the server version.\n" +"\n" +":type: `!int`\n" +"\n" +".. seealso:: libpq docs for `PQserverVersion()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQSERVERVERSION"; + +static PyObject * +server_version_get(connInfoObject *self) +{ + int val; + + val = PQserverVersion(self->conn->pgconn); + return PyInt_FromLong((long)val); +} + + +static const char error_message_doc[] = +"The error message most recently generated by an operation on the connection.\n" +"\n" +"`!None` if there is no current message.\n" +"\n" +".. seealso:: libpq docs for `PQerrorMessage()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQERRORMESSAGE"; + +static PyObject * +error_message_get(connInfoObject *self) +{ + const char *val; + + val = PQerrorMessage(self->conn->pgconn); + if (!val || !val[0]) { + Py_RETURN_NONE; + } + return conn_text_from_chars(self->conn, val); +} + + +static const char socket_doc[] = +"The file descriptor number of the connection socket to the server.\n" +"\n" +":type: `!int`\n" +"\n" +".. seealso:: libpq docs for `PQsocket()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQSOCKET"; + +static PyObject * +socket_get(connInfoObject *self) +{ + int val; + + val = PQsocket(self->conn->pgconn); + return PyInt_FromLong((long)val); +} + + +static const char backend_pid_doc[] = +"The process ID (PID) of the backend process you connected to.\n" +"\n" +":type: `!int`\n" +"\n" +".. seealso:: libpq docs for `PQbackendPID()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQBACKENDPID"; + +static PyObject * +backend_pid_get(connInfoObject *self) +{ + int val; + + val = PQbackendPID(self->conn->pgconn); + return PyInt_FromLong((long)val); +} + + +static const char needs_password_doc[] = +"The connection authentication method required a password, but none was available.\n" +"\n" +":type: `!bool`\n" +"\n" +".. seealso:: libpq docs for `PQconnectionNeedsPassword()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQCONNECTIONNEEDSPASSWORD"; + +static PyObject * +needs_password_get(connInfoObject *self) +{ + return PyBool_FromLong(PQconnectionNeedsPassword(self->conn->pgconn)); +} + + +static const char used_password_doc[] = +"The connection authentication method used a password.\n" +"\n" +":type: `!bool`\n" +"\n" +".. seealso:: libpq docs for `PQconnectionUsedPassword()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQCONNECTIONUSEDPASSWORD"; + +static PyObject * +used_password_get(connInfoObject *self) +{ + return PyBool_FromLong(PQconnectionUsedPassword(self->conn->pgconn)); +} + + +static const char ssl_in_use_doc[] = +"`!True` if the connection uses SSL, `!False` if not.\n" +"\n" +"Only available if psycopg was built with libpq >= 9.5; raise\n" +"`~psycopg2.NotSupportedError` otherwise.\n" +"\n" +":type: `!bool`\n" +"\n" +".. seealso:: libpq docs for `PQsslInUse()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQSSLINUSE"; + +static PyObject * +ssl_in_use_get(connInfoObject *self) +{ + PyObject *rv = NULL; + +#if PG_VERSION_NUM >= 90500 + rv = PyBool_FromLong(PQsslInUse(self->conn->pgconn)); +#else + PyErr_SetString(NotSupportedError, + "'ssl_in_use' not available in libpq < 9.5"); +#endif + return rv; +} + + +static const char ssl_attribute_doc[] = +"Returns SSL-related information about the connection.\n" +"\n" +":param name: The name of the attribute to return.\n" +":type name: `!str`\n" +":return: The attribute value, `!None` if unknown.\n" +":rtype: `!str`\n" +"\n" +"Only available if psycopg was built with libpq >= 9.5; raise\n" +"`~psycopg2.NotSupportedError` otherwise.\n" +"\n" +"Valid names are available in `ssl_attribute_names`.\n" +"\n" +".. seealso:: libpq docs for `PQsslAttribute()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQSSLATTRIBUTE"; + +static PyObject * +ssl_attribute(connInfoObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"name", NULL}; + const char *name; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", kwlist, &name)) { + return NULL; + } + +#if PG_VERSION_NUM >= 90500 + { + const char *val; + + val = PQsslAttribute(self->conn->pgconn, name); + + if (!val) { + Py_RETURN_NONE; + } + else { + return conn_text_from_chars(self->conn, val); + } + } +#else + PyErr_SetString(NotSupportedError, + "'ssl_attribute()' not available in libpq < 9.5"); + return NULL; +#endif +} + +static const char ssl_attribute_names_doc[] = +"The list of the SSL attribute names available.\n" +"\n" +":type: `!list` of `!str`\n" +"\n" +"Only available if psycopg was built with libpq >= 9.5; raise\n" +"`~psycopg2.NotSupportedError` otherwise.\n" +"\n" +".. seealso:: libpq docs for `PQsslAttributeNames()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQSSLATTRIBUTENAMES"; + +static PyObject * +ssl_attribute_names_get(connInfoObject *self) +{ +#if PG_VERSION_NUM >= 90500 + const char* const* names; + int i; + PyObject *l = NULL, *s = NULL, *rv = NULL; + + names = PQsslAttributeNames(self->conn->pgconn); + if (!(l = PyList_New(0))) { goto exit; } + + for (i = 0; names[i]; i++) { + if (!(s = conn_text_from_chars(self->conn, names[i]))) { goto exit; } + if (0 != PyList_Append(l, s)) { goto exit; } + Py_CLEAR(s); + } + + rv = l; + l = NULL; + +exit: + Py_XDECREF(l); + Py_XDECREF(s); + return rv; +#else + PyErr_SetString(NotSupportedError, + "'ssl_attribute_names not available in libpq < 9.5"); + return NULL; +#endif +} + + +static struct PyGetSetDef connInfoObject_getsets[] = { + { "dbname", (getter)dbname_get, NULL, (char *)dbname_doc }, + { "user", (getter)user_get, NULL, (char *)user_doc }, + { "password", (getter)password_get, NULL, (char *)password_doc }, + { "host", (getter)host_get, NULL, (char *)host_doc }, + { "port", (getter)port_get, NULL, (char *)port_doc }, + { "options", (getter)options_get, NULL, (char *)options_doc }, + { "dsn_parameters", (getter)dsn_parameters_get, NULL, + (char *)dsn_parameters_doc }, + { "status", (getter)status_get, NULL, (char *)status_doc }, + { "transaction_status", (getter)transaction_status_get, NULL, + (char *)transaction_status_doc }, + { "protocol_version", (getter)protocol_version_get, NULL, + (char *)protocol_version_doc }, + { "server_version", (getter)server_version_get, NULL, + (char *)server_version_doc }, + { "error_message", (getter)error_message_get, NULL, + (char *)error_message_doc }, + { "socket", (getter)socket_get, NULL, (char *)socket_doc }, + { "backend_pid", (getter)backend_pid_get, NULL, (char *)backend_pid_doc }, + { "used_password", (getter)used_password_get, NULL, + (char *)used_password_doc }, + { "needs_password", (getter)needs_password_get, NULL, + (char *)needs_password_doc }, + { "ssl_in_use", (getter)ssl_in_use_get, NULL, + (char *)ssl_in_use_doc }, + { "ssl_attribute_names", (getter)ssl_attribute_names_get, NULL, + (char *)ssl_attribute_names_doc }, + {NULL} +}; + +static struct PyMethodDef connInfoObject_methods[] = { + {"ssl_attribute", (PyCFunction)ssl_attribute, + METH_VARARGS|METH_KEYWORDS, ssl_attribute_doc}, + {"parameter_status", (PyCFunction)parameter_status, + METH_VARARGS|METH_KEYWORDS, parameter_status_doc}, + {NULL} +}; + +/* initialization and finalization methods */ + +static PyObject * +conninfo_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + +static int +conninfo_init(connInfoObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *conn = NULL; + + if (!PyArg_ParseTuple(args, "O", &conn)) + return -1; + + if (!PyObject_TypeCheck(conn, &connectionType)) { + PyErr_SetString(PyExc_TypeError, + "The argument must be a psycopg2 connection"); + return -1; + } + + Py_INCREF(conn); + self->conn = (connectionObject *)conn; + return 0; +} + +static void +conninfo_dealloc(connInfoObject* self) +{ + Py_CLEAR(self->conn); + Py_TYPE(self)->tp_free((PyObject *)self); +} + + +/* object type */ + +PyTypeObject connInfoType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2.extensions.ConnectionInfo", + sizeof(connInfoObject), 0, + (destructor)conninfo_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + connInfoType_doc, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + connInfoObject_methods, /*tp_methods*/ + 0, /*tp_members*/ + connInfoObject_getsets, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + (initproc)conninfo_init, /*tp_init*/ + 0, /*tp_alloc*/ + conninfo_new, /*tp_new*/ +}; diff --git a/source-code/psycopg2/psycopg/cursor.h b/source-code/psycopg2/psycopg/cursor.h new file mode 100644 index 0000000..b50894c --- /dev/null +++ b/source-code/psycopg2/psycopg/cursor.h @@ -0,0 +1,147 @@ +/* cursor.h - definition for the psycopg cursor type + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_CURSOR_H +#define PSYCOPG_CURSOR_H 1 + +#include "psycopg/connection.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern HIDDEN PyTypeObject cursorType; + +/* the typedef is forward-declared in psycopg.h */ +struct cursorObject { + PyObject_HEAD + + connectionObject *conn; /* connection owning the cursor */ + + int closed:1; /* 1 if the cursor is closed */ + int notuples:1; /* 1 if the command was not a SELECT query */ + int withhold:1; /* 1 if the cursor is named and uses WITH HOLD */ + + int scrollable; /* 1 if the cursor is named and SCROLLABLE, + 0 if not scrollable + -1 if undefined (PG may decide scrollable or not) + */ + + long int rowcount; /* number of rows affected by last execute */ + long int columns; /* number of columns fetched from the db */ + long int arraysize; /* how many rows should fetchmany() return */ + long int itersize; /* how many rows should iter(cur) fetch in named cursors */ + long int row; /* the row counter for fetch*() operations */ + long int mark; /* transaction marker, copied from conn */ + + PyObject *description; /* read-only attribute: sequence of 7-item + sequences.*/ + + /* postgres connection stuff */ + PGresult *pgres; /* result of last query */ + PyObject *pgstatus; /* last message from the server after an execute */ + Oid lastoid; /* last oid from an insert or InvalidOid */ + + PyObject *casts; /* an array (tuple) of typecast functions */ + PyObject *caster; /* the current typecaster object */ + + PyObject *copyfile; /* file-like used during COPY TO/FROM ops */ + Py_ssize_t copysize; /* size of the copy buffer during COPY TO/FROM ops */ +#define DEFAULT_COPYSIZE 16384 +#define DEFAULT_COPYBUFF 8192 + + PyObject *tuple_factory; /* factory for result tuples */ + PyObject *tzinfo_factory; /* factory for tzinfo objects */ + + PyObject *query; /* last query executed */ + + char *qattr; /* quoting attr, used when quoting strings */ + char *notice; /* a notice from the backend */ + char *name; /* this cursor name */ + char *qname; /* this cursor name, quoted */ + + PyObject *string_types; /* a set of typecasters for string types */ + PyObject *binary_types; /* a set of typecasters for binary types */ + + PyObject *weakreflist; /* list of weak references */ + +}; + + +/* C-callable functions in cursor_int.c and cursor_type.c */ +BORROWED HIDDEN PyObject *curs_get_cast(cursorObject *self, PyObject *oid); +HIDDEN void curs_reset(cursorObject *self); +RAISES_NEG HIDDEN int curs_withhold_set(cursorObject *self, PyObject *pyvalue); +RAISES_NEG HIDDEN int curs_scrollable_set(cursorObject *self, PyObject *pyvalue); +HIDDEN PyObject *curs_validate_sql_basic(cursorObject *self, PyObject *sql); +HIDDEN void curs_set_result(cursorObject *self, PGresult *pgres); + +/* exception-raising macros */ +#define EXC_IF_CURS_CLOSED(self) \ +do { \ + if (!(self)->conn) { \ + PyErr_SetString(InterfaceError, "the cursor has no connection"); \ + return NULL; } \ + if ((self)->closed || (self)->conn->closed) { \ + PyErr_SetString(InterfaceError, "cursor already closed"); \ + return NULL; } \ +} while (0) + +#define EXC_IF_NO_TUPLES(self) \ +do \ + if ((self)->notuples && (self)->name == NULL) { \ + PyErr_SetString(ProgrammingError, "no results to fetch"); \ + return NULL; } \ +while (0) + +#define EXC_IF_NO_MARK(self) \ +do \ + if ((self)->mark != (self)->conn->mark && (self)->withhold == 0) { \ + PyErr_SetString(ProgrammingError, "named cursor isn't valid anymore"); \ + return NULL; } \ +while (0) + +#define EXC_IF_CURS_ASYNC(self, cmd) \ +do \ + if ((self)->conn->async == 1) { \ + PyErr_SetString(ProgrammingError, \ + #cmd " cannot be used in asynchronous mode"); \ + return NULL; } \ +while (0) + +#define EXC_IF_ASYNC_IN_PROGRESS(self, cmd) \ +do \ + if ((self)->conn->async_cursor != NULL) { \ + PyErr_SetString(ProgrammingError, \ + #cmd " cannot be used while an asynchronous query is underway"); \ + return NULL; } \ +while (0) + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_CURSOR_H) */ diff --git a/source-code/psycopg2/psycopg/cursor_int.c b/source-code/psycopg2/psycopg/cursor_int.c new file mode 100644 index 0000000..7009ee8 --- /dev/null +++ b/source-code/psycopg2/psycopg/cursor_int.c @@ -0,0 +1,171 @@ +/* cursor_int.c - code used by the cursor object + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/cursor.h" +#include "psycopg/pqpath.h" +#include "psycopg/typecast.h" + +/* curs_get_cast - return the type caster for an oid. + * + * Return the most specific type caster, from cursor to connection to global. + * If no type caster is found, return the default one. + * + * Return a borrowed reference. + */ + +BORROWED PyObject * +curs_get_cast(cursorObject *self, PyObject *oid) +{ + PyObject *cast; + + /* cursor lookup */ + if (self->string_types != NULL && self->string_types != Py_None) { + cast = PyDict_GetItem(self->string_types, oid); + Dprintf("curs_get_cast: per-cursor dict: %p", cast); + if (cast) { return cast; } + } + + /* connection lookup */ + cast = PyDict_GetItem(self->conn->string_types, oid); + Dprintf("curs_get_cast: per-connection dict: %p", cast); + if (cast) { return cast; } + + /* global lookup */ + cast = PyDict_GetItem(psyco_types, oid); + Dprintf("curs_get_cast: global dict: %p", cast); + if (cast) { return cast; } + + /* fallback */ + return psyco_default_cast; +} + +#include + + +/* curs_reset - reset the cursor to a clean state */ + +void +curs_reset(cursorObject *self) +{ + /* initialize some variables to default values */ + self->notuples = 1; + self->rowcount = -1; + self->row = 0; + + Py_CLEAR(self->description); + Py_CLEAR(self->casts); +} + + +/* Return 1 if `obj` is a `psycopg2.sql.Composable` instance, else 0 + * Set an exception and return -1 in case of error. + */ +RAISES_NEG static int +_curs_is_composible(PyObject *obj) +{ + int rv = -1; + PyObject *m = NULL; + PyObject *comp = NULL; + + if (!(m = PyImport_ImportModule("psycopg2.sql"))) { goto exit; } + if (!(comp = PyObject_GetAttrString(m, "Composable"))) { goto exit; } + rv = PyObject_IsInstance(obj, comp); + +exit: + Py_XDECREF(comp); + Py_XDECREF(m); + return rv; + +} + +/* Performs very basic validation on an incoming SQL string. + * Returns a new reference to a str instance on success; NULL on failure, + * after having set an exception. + */ +PyObject * +curs_validate_sql_basic(cursorObject *self, PyObject *sql) +{ + PyObject *rv = NULL; + PyObject *comp = NULL; + int iscomp; + + if (!sql || !PyObject_IsTrue(sql)) { + psyco_set_error(ProgrammingError, self, + "can't execute an empty query"); + goto exit; + } + + if (Bytes_Check(sql)) { + /* Necessary for ref-count symmetry with the unicode case: */ + Py_INCREF(sql); + rv = sql; + } + else if (PyUnicode_Check(sql)) { + if (!(rv = conn_encode(self->conn, sql))) { goto exit; } + } + else if (0 != (iscomp = _curs_is_composible(sql))) { + if (iscomp < 0) { goto exit; } + if (!(comp = PyObject_CallMethod(sql, "as_string", "O", self->conn))) { + goto exit; + } + + if (Bytes_Check(comp)) { + rv = comp; + comp = NULL; + } + else if (PyUnicode_Check(comp)) { + if (!(rv = conn_encode(self->conn, comp))) { goto exit; } + } + else { + PyErr_Format(PyExc_TypeError, + "as_string() should return a string: got %s instead", + Py_TYPE(comp)->tp_name); + goto exit; + } + } + else { + /* the is not unicode or string, raise an error */ + PyErr_Format(PyExc_TypeError, + "argument 1 must be a string or unicode object: got %s instead", + Py_TYPE(sql)->tp_name); + goto exit; + } + +exit: + Py_XDECREF(comp); + return rv; +} + + +void +curs_set_result(cursorObject *self, PGresult *pgres) +{ + PQclear(self->pgres); + self->pgres = pgres; +} diff --git a/source-code/psycopg2/psycopg/cursor_type.c b/source-code/psycopg2/psycopg/cursor_type.c new file mode 100644 index 0000000..efdeefc --- /dev/null +++ b/source-code/psycopg2/psycopg/cursor_type.c @@ -0,0 +1,2126 @@ +/* cursor_type.c - python interface to cursor objects + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/cursor.h" +#include "psycopg/connection.h" +#include "psycopg/green.h" +#include "psycopg/pqpath.h" +#include "psycopg/typecast.h" +#include "psycopg/microprotocols.h" +#include "psycopg/microprotocols_proto.h" + +#include + +#include + + +/** DBAPI methods **/ + +/* close method - close the cursor */ + +#define curs_close_doc \ +"close() -- Close the cursor." + +static PyObject * +curs_close(cursorObject *self, PyObject *dummy) +{ + PyObject *rv = NULL; + char *lname = NULL; + + if (self->closed) { + rv = Py_None; + Py_INCREF(rv); + goto exit; + } + + if (self->qname != NULL) { + char buffer[256]; + PGTransactionStatusType status; + + EXC_IF_ASYNC_IN_PROGRESS(self, close_named); + + if (self->conn) { + status = PQtransactionStatus(self->conn->pgconn); + } + else { + status = PQTRANS_UNKNOWN; + } + + if (status == PQTRANS_UNKNOWN || status == PQTRANS_INERROR) { + Dprintf("skipping named curs close because tx status %d", + (int)status); + goto close; + } + + /* We should close a server-side cursor only if exists, or we get an + * error (#716). If we execute()d the cursor should exist alright, but + * if we didn't there is still the expectation that the cursor is + * closed (#746). + * + * So if we didn't execute() check for the cursor existence before + * closing it (the view exists since PG 8.2 according to docs). + */ + if (!self->query && self->conn->server_version >= 80200) { + if (!(lname = psyco_escape_string( + self->conn, self->name, -1, NULL, NULL))) { + goto exit; + } + PyOS_snprintf(buffer, sizeof(buffer), + "SELECT 1 FROM pg_catalog.pg_cursors where name = %s", + lname); + if (pq_execute(self, buffer, 0, 0, 1) == -1) { goto exit; } + + if (self->rowcount == 0) { + Dprintf("skipping named cursor close because not existing"); + goto close; + } + } + + EXC_IF_NO_MARK(self); + PyOS_snprintf(buffer, sizeof(buffer), "CLOSE %s", self->qname); + if (pq_execute(self, buffer, 0, 0, 1) == -1) { goto exit; } + } + +close: + CLEARPGRES(self->pgres); + + self->closed = 1; + Dprintf("curs_close: cursor at %p closed", self); + + rv = Py_None; + Py_INCREF(rv); + +exit: + PyMem_Free(lname); + return rv; +} + + +/* execute method - executes a query */ + +/* mogrify a query string and build argument array or dict */ + +RAISES_NEG static int +_mogrify(PyObject *var, PyObject *fmt, cursorObject *curs, PyObject **new) +{ + PyObject *key, *value, *n; + const char *d, *c; + Py_ssize_t index = 0; + int force = 0, kind = 0; + + /* from now on we'll use n and replace its value in *new only at the end, + just before returning. we also init *new to NULL to exit with an error + if we can't complete the mogrification */ + n = *new = NULL; + c = Bytes_AsString(fmt); + + while(*c) { + if (*c++ != '%') { + /* a regular character */ + continue; + } + + switch (*c) { + + /* handle plain percent symbol in format string */ + case '%': + ++c; + force = 1; + break; + + /* if we find '%(' then this is a dictionary, we: + 1/ find the matching ')' and extract the key name + 2/ locate the value in the dictionary (or return an error) + 3/ mogrify the value into something useful (quoting)... + 4/ ...and add it to the new dictionary to be used as argument + */ + case '(': + /* check if some crazy guy mixed formats */ + if (kind == 2) { + Py_XDECREF(n); + psyco_set_error(ProgrammingError, curs, + "argument formats can't be mixed"); + return -1; + } + kind = 1; + + /* let's have d point the end of the argument */ + for (d = c + 1; *d && *d != ')' && *d != '%'; d++); + + if (*d == ')') { + if (!(key = Text_FromUTF8AndSize(c+1, (Py_ssize_t)(d-c-1)))) { + Py_XDECREF(n); + return -1; + } + + /* if value is NULL we did not find the key (or this is not a + dictionary): let python raise a KeyError */ + if (!(value = PyObject_GetItem(var, key))) { + Py_DECREF(key); /* destroy key */ + Py_XDECREF(n); /* destroy n */ + return -1; + } + /* key has refcnt 1, value the original value + 1 */ + + Dprintf("_mogrify: value refcnt: " + FORMAT_CODE_PY_SSIZE_T " (+1)", Py_REFCNT(value)); + + if (n == NULL) { + if (!(n = PyDict_New())) { + Py_DECREF(key); + Py_DECREF(value); + return -1; + } + } + + if (0 == PyDict_Contains(n, key)) { + PyObject *t = NULL; + + /* None is always converted to NULL; this is an + optimization over the adapting code and can go away in + the future if somebody finds a None adapter useful. */ + if (value == Py_None) { + Py_INCREF(psyco_null); + t = psyco_null; + PyDict_SetItem(n, key, t); + /* t is a new object, refcnt = 1, key is at 2 */ + } + else { + t = microprotocol_getquoted(value, curs->conn); + if (t != NULL) { + PyDict_SetItem(n, key, t); + /* both key and t refcnt +1, key is at 2 now */ + } + else { + /* no adapter found, raise a BIG exception */ + Py_DECREF(key); + Py_DECREF(value); + Py_DECREF(n); + return -1; + } + } + + Py_XDECREF(t); /* t dies here */ + } + Py_DECREF(value); + Py_DECREF(key); /* key has the original refcnt now */ + Dprintf("_mogrify: after value refcnt: " + FORMAT_CODE_PY_SSIZE_T, Py_REFCNT(value)); + } + else { + /* we found %( but not a ) */ + Py_XDECREF(n); + psyco_set_error(ProgrammingError, curs, + "incomplete placeholder: '%(' without ')'"); + return -1; + } + c = d + 1; /* after the ) */ + break; + + default: + /* this is a format that expects a tuple; it is much easier, + because we don't need to check the old/new dictionary for + keys */ + + /* check if some crazy guy mixed formats */ + if (kind == 1) { + Py_XDECREF(n); + psyco_set_error(ProgrammingError, curs, + "argument formats can't be mixed"); + return -1; + } + kind = 2; + + value = PySequence_GetItem(var, index); + /* value has refcnt inc'ed by 1 here */ + + /* if value is NULL this is not a sequence or the index is wrong; + anyway we let python set its own exception */ + if (value == NULL) { + Py_XDECREF(n); + return -1; + } + + if (n == NULL) { + if (!(n = PyTuple_New(PyObject_Length(var)))) { + Py_DECREF(value); + return -1; + } + } + + /* let's have d point just after the '%' */ + if (value == Py_None) { + Py_INCREF(psyco_null); + PyTuple_SET_ITEM(n, index, psyco_null); + Py_DECREF(value); + } + else { + PyObject *t = microprotocol_getquoted(value, curs->conn); + + if (t != NULL) { + PyTuple_SET_ITEM(n, index, t); + Py_DECREF(value); + } + else { + Py_DECREF(n); + Py_DECREF(value); + return -1; + } + } + index += 1; + } + } + + if (force && n == NULL) + n = PyTuple_New(0); + *new = n; + + return 0; +} + + +/* Merge together a query string and its arguments. + * + * The arguments have been already adapted to SQL. + * + * Return a new reference to a string with the merged query, + * NULL and set an exception if any happened. + */ +static PyObject * +_psyco_curs_merge_query_args(cursorObject *self, + PyObject *query, PyObject *args) +{ + PyObject *fquery; + + /* if PyString_Format() return NULL an error occurred: if the error is + a TypeError we need to check the exception.args[0] string for the + values: + + "not enough arguments for format string" + "not all arguments converted" + + and return the appropriate ProgrammingError. we do that by grabbing + the current exception (we will later restore it if the type or the + strings do not match.) */ + + if (!(fquery = Bytes_Format(query, args))) { + PyObject *err, *arg, *trace; + int pe = 0; + + PyErr_Fetch(&err, &arg, &trace); + + if (err && PyErr_GivenExceptionMatches(err, PyExc_TypeError)) { + Dprintf("curs_execute: TypeError exception caught"); + PyErr_NormalizeException(&err, &arg, &trace); + + if (PyObject_HasAttrString(arg, "args")) { + PyObject *args = PyObject_GetAttrString(arg, "args"); + PyObject *str = PySequence_GetItem(args, 0); + const char *s = Bytes_AS_STRING(str); + + Dprintf("curs_execute: -> %s", s); + + if (!strcmp(s, "not enough arguments for format string") + || !strcmp(s, "not all arguments converted")) { + Dprintf("curs_execute: -> got a match"); + psyco_set_error(ProgrammingError, self, s); + pe = 1; + } + + Py_DECREF(args); + Py_DECREF(str); + } + } + + /* if we did not manage our own exception, restore old one */ + if (pe == 1) { + Py_XDECREF(err); Py_XDECREF(arg); Py_XDECREF(trace); + } + else { + PyErr_Restore(err, arg, trace); + } + } + + return fquery; +} + +#define curs_execute_doc \ +"execute(query, vars=None) -- Execute query with bound vars." + +RAISES_NEG static int +_psyco_curs_execute(cursorObject *self, + PyObject *query, PyObject *vars, + long int async, int no_result) +{ + int res = -1; + int tmp; + PyObject *fquery = NULL, *cvt = NULL; + + /* query becomes NULL or refcount +1, so good to XDECREF at the end */ + if (!(query = curs_validate_sql_basic(self, query))) { + goto exit; + } + + CLEARPGRES(self->pgres); + Py_CLEAR(self->query); + Dprintf("curs_execute: starting execution of new query"); + + /* here we are, and we have a sequence or a dictionary filled with + objects to be substituted (bound variables). we try to be smart and do + the right thing (i.e., what the user expects) */ + if (vars && vars != Py_None) + { + if (0 > _mogrify(vars, query, self, &cvt)) { goto exit; } + } + + /* Merge the query to the arguments if needed */ + if (cvt) { + if (!(fquery = _psyco_curs_merge_query_args(self, query, cvt))) { + goto exit; + } + } + else { + Py_INCREF(query); + fquery = query; + } + + if (self->qname != NULL) { + const char *scroll; + switch (self->scrollable) { + case -1: + scroll = ""; + break; + case 0: + scroll = "NO SCROLL "; + break; + case 1: + scroll = "SCROLL "; + break; + default: + PyErr_SetString(InternalError, "unexpected scrollable value"); + goto exit; + } + + if (!(self->query = Bytes_FromFormat( + "DECLARE %s %sCURSOR %s HOLD FOR %s", + self->qname, + scroll, + self->withhold ? "WITH" : "WITHOUT", + Bytes_AS_STRING(fquery)))) { + goto exit; + } + if (!self->query) { goto exit; } + } + else { + /* Transfer ownership */ + Py_INCREF(fquery); + self->query = fquery; + } + + /* At this point, the SQL statement must be str, not unicode */ + tmp = pq_execute(self, Bytes_AS_STRING(self->query), async, no_result, 0); + Dprintf("curs_execute: res = %d, pgres = %p", tmp, self->pgres); + if (tmp < 0) { goto exit; } + + res = 0; /* Success */ + +exit: + Py_XDECREF(query); + Py_XDECREF(fquery); + Py_XDECREF(cvt); + + return res; +} + +static PyObject * +curs_execute(cursorObject *self, PyObject *args, PyObject *kwargs) +{ + PyObject *vars = NULL, *operation = NULL; + + static char *kwlist[] = {"query", "vars", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O", kwlist, + &operation, &vars)) { + return NULL; + } + + if (self->name != NULL) { + if (self->query) { + psyco_set_error(ProgrammingError, self, + "can't call .execute() on named cursors more than once"); + return NULL; + } + if (self->conn->autocommit && !self->withhold) { + psyco_set_error(ProgrammingError, self, + "can't use a named cursor outside of transactions"); + return NULL; + } + EXC_IF_NO_MARK(self); + } + + EXC_IF_CURS_CLOSED(self); + EXC_IF_ASYNC_IN_PROGRESS(self, execute); + EXC_IF_TPC_PREPARED(self->conn, execute); + + if (0 > _psyco_curs_execute(self, operation, vars, self->conn->async, 0)) { + return NULL; + } + + /* success */ + Py_RETURN_NONE; +} + +#define curs_executemany_doc \ +"executemany(query, vars_list) -- Execute many queries with bound vars." + +static PyObject * +curs_executemany(cursorObject *self, PyObject *args, PyObject *kwargs) +{ + PyObject *operation = NULL, *vars = NULL; + PyObject *v, *iter = NULL; + long rowcount = 0; + + static char *kwlist[] = {"query", "vars_list", NULL}; + + /* reset rowcount to -1 to avoid setting it when an exception is raised */ + self->rowcount = -1; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO", kwlist, + &operation, &vars)) { + return NULL; + } + + EXC_IF_CURS_CLOSED(self); + EXC_IF_CURS_ASYNC(self, executemany); + EXC_IF_TPC_PREPARED(self->conn, executemany); + + if (self->name != NULL) { + psyco_set_error(ProgrammingError, self, + "can't call .executemany() on named cursors"); + return NULL; + } + + if (!PyIter_Check(vars)) { + vars = iter = PyObject_GetIter(vars); + if (iter == NULL) return NULL; + } + + while ((v = PyIter_Next(vars)) != NULL) { + if (0 > _psyco_curs_execute(self, operation, v, 0, 1)) { + Py_DECREF(v); + Py_XDECREF(iter); + return NULL; + } + else { + if (self->rowcount == -1) + rowcount = -1; + else if (rowcount >= 0) + rowcount += self->rowcount; + Py_DECREF(v); + } + } + Py_XDECREF(iter); + self->rowcount = rowcount; + + if (!PyErr_Occurred()) { + Py_RETURN_NONE; + } + else { + return NULL; + } +} + + +#define curs_mogrify_doc \ +"mogrify(query, vars=None) -> str -- Return query after vars binding." + +static PyObject * +_psyco_curs_mogrify(cursorObject *self, + PyObject *operation, PyObject *vars) +{ + PyObject *fquery = NULL, *cvt = NULL; + + operation = curs_validate_sql_basic(self, operation); + if (operation == NULL) { goto cleanup; } + + Dprintf("curs_mogrify: starting mogrify"); + + /* here we are, and we have a sequence or a dictionary filled with + objects to be substituted (bound variables). we try to be smart and do + the right thing (i.e., what the user expects) */ + + if (vars && vars != Py_None) + { + if (0 > _mogrify(vars, operation, self, &cvt)) { + goto cleanup; + } + } + + if (vars && cvt) { + if (!(fquery = _psyco_curs_merge_query_args(self, operation, cvt))) { + goto cleanup; + } + + Dprintf("curs_mogrify: cvt->refcnt = " FORMAT_CODE_PY_SSIZE_T + ", fquery->refcnt = " FORMAT_CODE_PY_SSIZE_T, + Py_REFCNT(cvt), Py_REFCNT(fquery)); + } + else { + fquery = operation; + Py_INCREF(fquery); + } + +cleanup: + Py_XDECREF(operation); + Py_XDECREF(cvt); + + return fquery; +} + +static PyObject * +curs_mogrify(cursorObject *self, PyObject *args, PyObject *kwargs) +{ + PyObject *vars = NULL, *operation = NULL; + + static char *kwlist[] = {"query", "vars", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O", kwlist, + &operation, &vars)) { + return NULL; + } + + return _psyco_curs_mogrify(self, operation, vars); +} + + +/* cast method - convert an oid/string into a Python object */ +#define curs_cast_doc \ +"cast(oid, s) -> value\n\n" \ +"Convert the string s to a Python object according to its oid.\n\n" \ +"Look for a typecaster first in the cursor, then in its connection," \ +"then in the global register. If no suitable typecaster is found," \ +"leave the value as a string." + +static PyObject * +curs_cast(cursorObject *self, PyObject *args) +{ + PyObject *oid; + PyObject *s; + PyObject *cast; + + if (!PyArg_ParseTuple(args, "OO", &oid, &s)) + return NULL; + + cast = curs_get_cast(self, oid); + return PyObject_CallFunctionObjArgs(cast, s, (PyObject *)self, NULL); +} + + +/* fetchone method - fetch one row of results */ + +#define curs_fetchone_doc \ +"fetchone() -> tuple or None\n\n" \ +"Return the next row of a query result set in the form of a tuple (by\n" \ +"default) or using the sequence factory previously set in the\n" \ +"`row_factory` attribute. Return `!None` when no more data is available.\n" + +RAISES_NEG static int +_psyco_curs_prefetch(cursorObject *self) +{ + int i = 0; + + if (self->pgres == NULL) { + Dprintf("_psyco_curs_prefetch: trying to fetch data"); + do { + i = pq_fetch(self, 0); + Dprintf("_psycopg_curs_prefetch: result = %d", i); + } while(i == 1); + } + + Dprintf("_psyco_curs_prefetch: result = %d", i); + return i; +} + +RAISES_NEG static int +_psyco_curs_buildrow_fill(cursorObject *self, PyObject *res, + int row, int n, int istuple) +{ + int i, len, err; + const char *str; + PyObject *val; + int rv = -1; + + for (i=0; i < n; i++) { + if (PQgetisnull(self->pgres, row, i)) { + str = NULL; + len = 0; + } + else { + str = PQgetvalue(self->pgres, row, i); + len = PQgetlength(self->pgres, row, i); + } + + Dprintf("_psyco_curs_buildrow: row %ld, element %d, len %d", + self->row, i, len); + + if (!(val = typecast_cast(PyTuple_GET_ITEM(self->casts, i), str, len, + (PyObject*)self))) { + goto exit; + } + + Dprintf("_psyco_curs_buildrow: val->refcnt = " + FORMAT_CODE_PY_SSIZE_T, + Py_REFCNT(val) + ); + if (istuple) { + PyTuple_SET_ITEM(res, i, val); + } + else { + err = PySequence_SetItem(res, i, val); + Py_DECREF(val); + if (err == -1) { goto exit; } + } + } + + rv = 0; + +exit: + return rv; +} + +static PyObject * +_psyco_curs_buildrow(cursorObject *self, int row) +{ + int n; + int istuple; + PyObject *t = NULL; + PyObject *rv = NULL; + + n = PQnfields(self->pgres); + istuple = (self->tuple_factory == Py_None); + + if (istuple) { + t = PyTuple_New(n); + } + else { + t = PyObject_CallFunctionObjArgs(self->tuple_factory, self, NULL); + } + if (!t) { goto exit; } + + if (0 <= _psyco_curs_buildrow_fill(self, t, row, n, istuple)) { + rv = t; + t = NULL; + } + +exit: + Py_XDECREF(t); + return rv; + +} + +static PyObject * +curs_fetchone(cursorObject *self, PyObject *dummy) +{ + PyObject *res; + + EXC_IF_CURS_CLOSED(self); + if (_psyco_curs_prefetch(self) < 0) return NULL; + EXC_IF_NO_TUPLES(self); + + if (self->qname != NULL) { + char buffer[128]; + + EXC_IF_NO_MARK(self); + EXC_IF_ASYNC_IN_PROGRESS(self, fetchone); + EXC_IF_TPC_PREPARED(self->conn, fetchone); + PyOS_snprintf(buffer, sizeof(buffer), "FETCH FORWARD 1 FROM %s", self->qname); + if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) return NULL; + if (_psyco_curs_prefetch(self) < 0) return NULL; + } + + Dprintf("curs_fetchone: fetching row %ld", self->row); + Dprintf("curs_fetchone: rowcount = %ld", self->rowcount); + + if (self->row >= self->rowcount) { + /* we exhausted available data: return None */ + Py_RETURN_NONE; + } + + res = _psyco_curs_buildrow(self, self->row); + self->row++; /* move the counter to next line */ + + /* if the query was async aggresively free pgres, to allow + successive requests to reallocate it */ + if (self->row >= self->rowcount + && self->conn->async_cursor + && PyWeakref_GetObject(self->conn->async_cursor) == (PyObject*)self) + CLEARPGRES(self->pgres); + + return res; +} + +/* Efficient cursor.next() implementation for named cursors. + * + * Fetch several records at time. Return NULL when the cursor is exhausted. + */ +static PyObject * +curs_next_named(cursorObject *self) +{ + PyObject *res; + + Dprintf("curs_next_named"); + EXC_IF_CURS_CLOSED(self); + EXC_IF_ASYNC_IN_PROGRESS(self, next); + if (_psyco_curs_prefetch(self) < 0) return NULL; + EXC_IF_NO_TUPLES(self); + + EXC_IF_NO_MARK(self); + EXC_IF_TPC_PREPARED(self->conn, next); + + Dprintf("curs_next_named: row %ld", self->row); + Dprintf("curs_next_named: rowcount = %ld", self->rowcount); + if (self->row >= self->rowcount) { + char buffer[128]; + + PyOS_snprintf(buffer, sizeof(buffer), "FETCH FORWARD %ld FROM %s", + self->itersize, self->qname); + if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) return NULL; + if (_psyco_curs_prefetch(self) < 0) return NULL; + } + + /* We exhausted the data: return NULL to stop iteration. */ + if (self->row >= self->rowcount) { + return NULL; + } + + res = _psyco_curs_buildrow(self, self->row); + self->row++; /* move the counter to next line */ + + /* if the query was async aggresively free pgres, to allow + successive requests to reallocate it */ + if (self->row >= self->rowcount + && self->conn->async_cursor + && PyWeakref_GetObject(self->conn->async_cursor) == (PyObject*)self) + CLEARPGRES(self->pgres); + + return res; +} + + +/* fetch many - fetch some results */ + +#define curs_fetchmany_doc \ +"fetchmany(size=self.arraysize) -> list of tuple\n\n" \ +"Return the next `size` rows of a query result set in the form of a list\n" \ +"of tuples (by default) or using the sequence factory previously set in\n" \ +"the `row_factory` attribute.\n\n" \ +"Return an empty list when no more data is available.\n" + +static PyObject * +curs_fetchmany(cursorObject *self, PyObject *args, PyObject *kwords) +{ + int i; + PyObject *list = NULL; + PyObject *row = NULL; + PyObject *rv = NULL; + + PyObject *pysize = NULL; + long int size = self->arraysize; + static char *kwlist[] = {"size", NULL}; + + /* allow passing None instead of omitting the *size* argument, + * or using the method from subclasses would be a problem */ + if (!PyArg_ParseTupleAndKeywords(args, kwords, "|O", kwlist, &pysize)) { + return NULL; + } + + if (pysize && pysize != Py_None) { + size = PyInt_AsLong(pysize); + if (size == -1 && PyErr_Occurred()) { + return NULL; + } + } + + EXC_IF_CURS_CLOSED(self); + if (_psyco_curs_prefetch(self) < 0) return NULL; + EXC_IF_NO_TUPLES(self); + + if (self->qname != NULL) { + char buffer[128]; + + EXC_IF_NO_MARK(self); + EXC_IF_ASYNC_IN_PROGRESS(self, fetchmany); + EXC_IF_TPC_PREPARED(self->conn, fetchone); + PyOS_snprintf(buffer, sizeof(buffer), "FETCH FORWARD %d FROM %s", + (int)size, self->qname); + if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) { goto exit; } + if (_psyco_curs_prefetch(self) < 0) { goto exit; } + } + + /* make sure size is not > than the available number of rows */ + if (size > self->rowcount - self->row || size < 0) { + size = self->rowcount - self->row; + } + + Dprintf("curs_fetchmany: size = %ld", size); + + if (size <= 0) { + rv = PyList_New(0); + goto exit; + } + + if (!(list = PyList_New(size))) { goto exit; } + + for (i = 0; i < size; i++) { + row = _psyco_curs_buildrow(self, self->row); + self->row++; + + if (row == NULL) { goto exit; } + + PyList_SET_ITEM(list, i, row); + } + row = NULL; + + /* if the query was async aggresively free pgres, to allow + successive requests to reallocate it */ + if (self->row >= self->rowcount + && self->conn->async_cursor + && PyWeakref_GetObject(self->conn->async_cursor) == (PyObject*)self) + CLEARPGRES(self->pgres); + + /* success */ + rv = list; + list = NULL; + +exit: + Py_XDECREF(list); + Py_XDECREF(row); + + return rv; +} + + +/* fetch all - fetch all results */ + +#define curs_fetchall_doc \ +"fetchall() -> list of tuple\n\n" \ +"Return all the remaining rows of a query result set.\n\n" \ +"Rows are returned in the form of a list of tuples (by default) or using\n" \ +"the sequence factory previously set in the `row_factory` attribute.\n" \ +"Return `!None` when no more data is available.\n" + +static PyObject * +curs_fetchall(cursorObject *self, PyObject *dummy) +{ + int i, size; + PyObject *list = NULL; + PyObject *row = NULL; + PyObject *rv = NULL; + + EXC_IF_CURS_CLOSED(self); + if (_psyco_curs_prefetch(self) < 0) return NULL; + EXC_IF_NO_TUPLES(self); + + if (self->qname != NULL) { + char buffer[128]; + + EXC_IF_NO_MARK(self); + EXC_IF_ASYNC_IN_PROGRESS(self, fetchall); + EXC_IF_TPC_PREPARED(self->conn, fetchall); + PyOS_snprintf(buffer, sizeof(buffer), "FETCH FORWARD ALL FROM %s", self->qname); + if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) { goto exit; } + if (_psyco_curs_prefetch(self) < 0) { goto exit; } + } + + size = self->rowcount - self->row; + + if (size <= 0) { + rv = PyList_New(0); + goto exit; + } + + if (!(list = PyList_New(size))) { goto exit; } + + for (i = 0; i < size; i++) { + row = _psyco_curs_buildrow(self, self->row); + self->row++; + if (row == NULL) { goto exit; } + + PyList_SET_ITEM(list, i, row); + } + row = NULL; + + /* if the query was async aggresively free pgres, to allow + successive requests to reallocate it */ + if (self->row >= self->rowcount + && self->conn->async_cursor + && PyWeakref_GetObject(self->conn->async_cursor) == (PyObject*)self) + CLEARPGRES(self->pgres); + + /* success */ + rv = list; + list = NULL; + +exit: + Py_XDECREF(list); + Py_XDECREF(row); + + return rv; +} + + +/* callproc method - execute a stored procedure */ + +#define curs_callproc_doc \ +"callproc(procname, parameters=None) -- Execute stored procedure." + +static PyObject * +curs_callproc(cursorObject *self, PyObject *args) +{ + const char *procname = NULL; + char *sql = NULL; + Py_ssize_t procname_len, i, nparameters = 0, sl = 0; + PyObject *parameters = Py_None; + PyObject *operation = NULL; + PyObject *res = NULL; + + int using_dict; + PyObject *pname = NULL; + PyObject *pnames = NULL; + PyObject *pvals = NULL; + char *cpname = NULL; + char **scpnames = NULL; + + if (!PyArg_ParseTuple(args, "s#|O", &procname, &procname_len, + ¶meters)) { + goto exit; + } + + EXC_IF_CURS_CLOSED(self); + EXC_IF_ASYNC_IN_PROGRESS(self, callproc); + EXC_IF_TPC_PREPARED(self->conn, callproc); + + if (self->name != NULL) { + psyco_set_error(ProgrammingError, self, + "can't call .callproc() on named cursors"); + goto exit; + } + + if (parameters != Py_None) { + if (-1 == (nparameters = PyObject_Length(parameters))) { goto exit; } + } + + using_dict = nparameters > 0 && PyDict_Check(parameters); + + /* a Dict is complicated; the parameter names go into the query */ + if (using_dict) { + if (!(pnames = PyDict_Keys(parameters))) { goto exit; } + if (!(pvals = PyDict_Values(parameters))) { goto exit; } + + sl = procname_len + 17 + nparameters * 5 - (nparameters ? 1 : 0); + + if (!(scpnames = PyMem_New(char *, nparameters))) { + PyErr_NoMemory(); + goto exit; + } + + memset(scpnames, 0, sizeof(char *) * nparameters); + + /* each parameter has to be processed; it's a few steps. */ + for (i = 0; i < nparameters; i++) { + /* all errors are RuntimeErrors as they should never occur */ + + if (!(pname = PyList_GetItem(pnames, i))) { goto exit; } + Py_INCREF(pname); /* was borrowed */ + + /* this also makes a check for keys being strings */ + if (!(pname = psyco_ensure_bytes(pname))) { goto exit; } + if (!(cpname = Bytes_AsString(pname))) { goto exit; } + + if (!(scpnames[i] = psyco_escape_identifier( + self->conn, cpname, -1))) { + Py_CLEAR(pname); + goto exit; + } + + Py_CLEAR(pname); + + sl += strlen(scpnames[i]); + } + + if (!(sql = (char*)PyMem_Malloc(sl))) { + PyErr_NoMemory(); + goto exit; + } + + sprintf(sql, "SELECT * FROM %s(", procname); + for (i = 0; i < nparameters; i++) { + strcat(sql, scpnames[i]); + strcat(sql, ":=%s,"); + } + sql[sl-2] = ')'; + sql[sl-1] = '\0'; + } + + /* a list (or None, or empty data structure) is a little bit simpler */ + else { + Py_INCREF(parameters); + pvals = parameters; + + sl = procname_len + 17 + nparameters * 3 - (nparameters ? 1 : 0); + + sql = (char*)PyMem_Malloc(sl); + if (sql == NULL) { + PyErr_NoMemory(); + goto exit; + } + + sprintf(sql, "SELECT * FROM %s(", procname); + for (i = 0; i < nparameters; i++) { + strcat(sql, "%s,"); + } + sql[sl-2] = ')'; + sql[sl-1] = '\0'; + } + + if (!(operation = Bytes_FromString(sql))) { + goto exit; + } + + if (0 <= _psyco_curs_execute( + self, operation, pvals, self->conn->async, 0)) { + /* The dict case is outside DBAPI scope anyway, so simply return None */ + if (using_dict) { + res = Py_None; + } + else { + res = pvals; + } + Py_INCREF(res); + } + +exit: + if (scpnames != NULL) { + for (i = 0; i < nparameters; i++) { + if (scpnames[i] != NULL) { + PQfreemem(scpnames[i]); + } + } + } + PyMem_Free(scpnames); + Py_XDECREF(pname); + Py_XDECREF(pnames); + Py_XDECREF(operation); + Py_XDECREF(pvals); + PyMem_Free((void*)sql); + return res; +} + + +/* nextset method - return the next set of data (not supported) */ + +#define curs_nextset_doc \ +"nextset() -- Skip to next set of data.\n\n" \ +"This method is not supported (PostgreSQL does not have multiple data \n" \ +"sets) and will raise a NotSupportedError exception." + +static PyObject * +curs_nextset(cursorObject *self, PyObject *dummy) +{ + EXC_IF_CURS_CLOSED(self); + + PyErr_SetString(NotSupportedError, "not supported by PostgreSQL"); + return NULL; +} + + +/* setinputsizes - predefine memory areas for execute (does nothing) */ + +#define curs_setinputsizes_doc \ +"setinputsizes(sizes) -- Set memory areas before execute.\n\n" \ +"This method currently does nothing but it is safe to call it." + +static PyObject * +curs_setinputsizes(cursorObject *self, PyObject *args) +{ + PyObject *sizes; + + if (!PyArg_ParseTuple(args, "O", &sizes)) + return NULL; + + EXC_IF_CURS_CLOSED(self); + + Py_RETURN_NONE; +} + + +/* setoutputsize - predefine memory areas for execute (does nothing) */ + +#define curs_setoutputsize_doc \ +"setoutputsize(size, column=None) -- Set column buffer size.\n\n" \ +"This method currently does nothing but it is safe to call it." + +static PyObject * +curs_setoutputsize(cursorObject *self, PyObject *args) +{ + long int size, column; + + if (!PyArg_ParseTuple(args, "l|l", &size, &column)) + return NULL; + + EXC_IF_CURS_CLOSED(self); + + Py_RETURN_NONE; +} + + +/* scroll - scroll position in result list */ + +#define curs_scroll_doc \ +"scroll(value, mode='relative') -- Scroll to new position according to mode." + +static PyObject * +curs_scroll(cursorObject *self, PyObject *args, PyObject *kwargs) +{ + int value, newpos; + const char *mode = "relative"; + + static char *kwlist[] = {"value", "mode", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|s", + kwlist, &value, &mode)) + return NULL; + + EXC_IF_CURS_CLOSED(self); + + /* if the cursor is not named we have the full result set and we can do + our own calculations to scroll; else we just delegate the scrolling + to the MOVE SQL statement */ + if (self->qname == NULL) { + if (strcmp(mode, "relative") == 0) { + newpos = self->row + value; + } else if (strcmp( mode, "absolute") == 0) { + newpos = value; + } else { + psyco_set_error(ProgrammingError, self, + "scroll mode must be 'relative' or 'absolute'"); + return NULL; + } + + if (newpos < 0 || newpos >= self->rowcount ) { + psyco_set_error(ProgrammingError, self, + "scroll destination out of bounds"); + return NULL; + } + + self->row = newpos; + } + + else { + char buffer[128]; + + EXC_IF_NO_MARK(self); + EXC_IF_ASYNC_IN_PROGRESS(self, scroll); + EXC_IF_TPC_PREPARED(self->conn, scroll); + + if (strcmp(mode, "absolute") == 0) { + PyOS_snprintf(buffer, sizeof(buffer), "MOVE ABSOLUTE %d FROM %s", + value, self->qname); + } + else { + PyOS_snprintf(buffer, sizeof(buffer), "MOVE %d FROM %s", value, self->qname); + } + if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) return NULL; + if (_psyco_curs_prefetch(self) < 0) return NULL; + } + + Py_RETURN_NONE; +} + + +#define curs_enter_doc \ +"__enter__ -> self" + +static PyObject * +curs_enter(cursorObject *self, PyObject *dummy) +{ + Py_INCREF(self); + return (PyObject *)self; +} + +#define curs_exit_doc \ +"__exit__ -- close the cursor" + +static PyObject * +curs_exit(cursorObject *self, PyObject *args) +{ + PyObject *tmp = NULL; + PyObject *rv = NULL; + + /* don't care about the arguments here: don't need to parse them */ + + if (!(tmp = PyObject_CallMethod((PyObject *)self, "close", ""))) { + goto exit; + } + + /* success (of curs.close()). + * Return None to avoid swallowing the exception */ + rv = Py_None; + Py_INCREF(rv); + +exit: + Py_XDECREF(tmp); + return rv; +} + + +/* Return a newly allocated buffer containing the list of columns to be + * copied. On error return NULL and set an exception. + */ +static char *_psyco_curs_copy_columns(cursorObject *self, PyObject *columns) +{ + PyObject *col, *coliter; + char *columnlist = NULL; + Py_ssize_t bufsize = 512; + Py_ssize_t offset = 1; + + if (columns == NULL || columns == Py_None) { + if (NULL == (columnlist = PyMem_Malloc(2))) { + PyErr_NoMemory(); + goto error; + } + columnlist[0] = '\0'; + goto exit; + } + + if (NULL == (coliter = PyObject_GetIter(columns))) { + goto error; + } + + if (NULL == (columnlist = PyMem_Malloc(bufsize))) { + Py_DECREF(coliter); + PyErr_NoMemory(); + goto error; + } + columnlist[0] = '('; + + while ((col = PyIter_Next(coliter)) != NULL) { + Py_ssize_t collen; + char *colname; + char *quoted_colname; + + if (!(col = psyco_ensure_bytes(col))) { + Py_DECREF(coliter); + goto error; + } + Bytes_AsStringAndSize(col, &colname, &collen); + if (!(quoted_colname = psyco_escape_identifier( + self->conn, colname, collen))) { + Py_DECREF(col); + Py_DECREF(coliter); + goto error; + } + collen = strlen(quoted_colname); + + while (offset + collen > bufsize - 2) { + char *tmp; + bufsize *= 2; + if (NULL == (tmp = PyMem_Realloc(columnlist, bufsize))) { + PQfreemem(quoted_colname); + Py_DECREF(col); + Py_DECREF(coliter); + PyErr_NoMemory(); + goto error; + } + columnlist = tmp; + } + strncpy(&columnlist[offset], quoted_colname, collen); + offset += collen; + columnlist[offset++] = ','; + Py_DECREF(col); + PQfreemem(quoted_colname); + } + Py_DECREF(coliter); + + /* Error raised by the coliter generator */ + if (PyErr_Occurred()) { + goto error; + } + + if (offset == 2) { + goto exit; + } + else { + columnlist[offset - 1] = ')'; + columnlist[offset] = '\0'; + goto exit; + } + +error: + PyMem_Free(columnlist); + columnlist = NULL; + +exit: + return columnlist; +} + +/* extension: copy_from - implements COPY FROM */ + +#define curs_copy_from_doc \ +"copy_from(file, table, sep='\\t', null='\\\\N', size=8192, columns=None) -- Copy table from file." + +static PyObject * +curs_copy_from(cursorObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = { + "file", "table", "sep", "null", "size", "columns", NULL}; + + const char *sep = "\t"; + const char *null = "\\N"; + const char *command = + "COPY %s%s FROM stdin WITH DELIMITER AS %s NULL AS %s"; + + Py_ssize_t query_size; + char *query = NULL; + char *columnlist = NULL; + char *quoted_delimiter = NULL; + char *quoted_null = NULL; + char *quoted_table_name = NULL; + const char *table_name; + + Py_ssize_t bufsize = DEFAULT_COPYBUFF; + PyObject *file, *columns = NULL, *res = NULL; + + if (!PyArg_ParseTupleAndKeywords( + args, kwargs, "Os|ssnO", kwlist, + &file, &table_name, &sep, &null, &bufsize, &columns)) { + return NULL; + } + + if (!PyObject_HasAttrString(file, "read")) { + PyErr_SetString(PyExc_TypeError, + "argument 1 must have a .read() method"); + return NULL; + } + + EXC_IF_CURS_CLOSED(self); + EXC_IF_CURS_ASYNC(self, copy_from); + EXC_IF_GREEN(copy_from); + EXC_IF_TPC_PREPARED(self->conn, copy_from); + + if (!(columnlist = _psyco_curs_copy_columns(self, columns))) { + goto exit; + } + + if (!(quoted_delimiter = psyco_escape_string( + self->conn, sep, -1, NULL, NULL))) { + goto exit; + } + + if (!(quoted_null = psyco_escape_string( + self->conn, null, -1, NULL, NULL))) { + goto exit; + } + + if (!(quoted_table_name = psyco_escape_identifier( + self->conn, table_name, -1))) { + goto exit; + } + + query_size = strlen(command) + strlen(quoted_table_name) + strlen(columnlist) + + strlen(quoted_delimiter) + strlen(quoted_null) + 1; + if (!(query = PyMem_New(char, query_size))) { + PyErr_NoMemory(); + goto exit; + } + + PyOS_snprintf(query, query_size, command, + quoted_table_name, columnlist, quoted_delimiter, quoted_null); + + Dprintf("curs_copy_from: query = %s", query); + + Py_CLEAR(self->query); + if (!(self->query = Bytes_FromString(query))) { + goto exit; + } + + /* This routine stores a borrowed reference. Although it is only held + * for the duration of curs_copy_from, nested invocations of + * Py_BEGIN_ALLOW_THREADS could surrender control to another thread, + * which could invoke the garbage collector. We thus need an + * INCREF/DECREF pair if we store this pointer in a GC object, such as + * a cursorObject */ + self->copysize = bufsize; + Py_INCREF(file); + self->copyfile = file; + + if (pq_execute(self, query, 0, 0, 0) >= 0) { + res = Py_None; + Py_INCREF(Py_None); + } + + Py_CLEAR(self->copyfile); + +exit: + if (quoted_table_name) { + PQfreemem(quoted_table_name); + } + PyMem_Free(columnlist); + PyMem_Free(quoted_delimiter); + PyMem_Free(quoted_null); + PyMem_Free(query); + + return res; +} + +/* extension: copy_to - implements COPY TO */ + +#define curs_copy_to_doc \ +"copy_to(file, table, sep='\\t', null='\\\\N', columns=None) -- Copy table to file." + +static PyObject * +curs_copy_to(cursorObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"file", "table", "sep", "null", "columns", NULL}; + + const char *sep = "\t"; + const char *null = "\\N"; + const char *command = + "COPY %s%s TO stdout WITH DELIMITER AS %s NULL AS %s"; + + Py_ssize_t query_size; + char *query = NULL; + char *columnlist = NULL; + char *quoted_delimiter = NULL; + char *quoted_null = NULL; + + const char *table_name; + char *quoted_table_name = NULL; + PyObject *file = NULL, *columns = NULL, *res = NULL; + + if (!PyArg_ParseTupleAndKeywords( + args, kwargs, "Os|ssO", kwlist, + &file, &table_name, &sep, &null, &columns)) { + return NULL; + } + + if (!PyObject_HasAttrString(file, "write")) { + PyErr_SetString(PyExc_TypeError, + "argument 1 must have a .write() method"); + return NULL; + } + + EXC_IF_CURS_CLOSED(self); + EXC_IF_CURS_ASYNC(self, copy_to); + EXC_IF_GREEN(copy_to); + EXC_IF_TPC_PREPARED(self->conn, copy_to); + + if (!(quoted_table_name = psyco_escape_identifier( + self->conn, table_name, -1))) { + goto exit; + } + + if (!(columnlist = _psyco_curs_copy_columns(self, columns))) { + goto exit; + } + + if (!(quoted_delimiter = psyco_escape_string( + self->conn, sep, -1, NULL, NULL))) { + goto exit; + } + + if (!(quoted_null = psyco_escape_string( + self->conn, null, -1, NULL, NULL))) { + goto exit; + } + + query_size = strlen(command) + strlen(quoted_table_name) + strlen(columnlist) + + strlen(quoted_delimiter) + strlen(quoted_null) + 1; + if (!(query = PyMem_New(char, query_size))) { + PyErr_NoMemory(); + goto exit; + } + + PyOS_snprintf(query, query_size, command, + quoted_table_name, columnlist, quoted_delimiter, quoted_null); + + Dprintf("curs_copy_to: query = %s", query); + + Py_CLEAR(self->query); + if (!(self->query = Bytes_FromString(query))) { + goto exit; + } + + self->copysize = 0; + Py_INCREF(file); + self->copyfile = file; + + if (pq_execute(self, query, 0, 0, 0) >= 0) { + res = Py_None; + Py_INCREF(Py_None); + } + + Py_CLEAR(self->copyfile); + +exit: + if (quoted_table_name) { + PQfreemem(quoted_table_name); + } + PyMem_Free(columnlist); + PyMem_Free(quoted_delimiter); + PyMem_Free(quoted_null); + PyMem_Free(query); + + return res; +} + +/* extension: copy_expert - implements extended COPY FROM/TO + + This method supports both COPY FROM and COPY TO with user-specifiable + SQL statement, rather than composing the statement from parameters. +*/ + +#define curs_copy_expert_doc \ +"copy_expert(sql, file, size=8192) -- Submit a user-composed COPY statement.\n" \ +"`file` must be an open, readable file for COPY FROM or an open, writable\n" \ +"file for COPY TO. The optional `size` argument, when specified for a COPY\n" \ +"FROM statement, will be passed to file's read method to control the read\n" \ +"buffer size." + +static PyObject * +curs_copy_expert(cursorObject *self, PyObject *args, PyObject *kwargs) +{ + Py_ssize_t bufsize = DEFAULT_COPYBUFF; + PyObject *sql, *file, *res = NULL; + + static char *kwlist[] = {"sql", "file", "size", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, + "OO|n", kwlist, &sql, &file, &bufsize)) + { return NULL; } + + EXC_IF_CURS_CLOSED(self); + EXC_IF_CURS_ASYNC(self, copy_expert); + EXC_IF_GREEN(copy_expert); + EXC_IF_TPC_PREPARED(self->conn, copy_expert); + + sql = curs_validate_sql_basic(self, sql); + + /* Any failure from here forward should 'goto exit' rather than + 'return NULL' directly. */ + + if (sql == NULL) { goto exit; } + + /* This validation of file is rather weak, in that it doesn't enforce the + association between "COPY FROM" -> "read" and "COPY TO" -> "write". + However, the error handling in _pq_copy_[in|out] must be able to handle + the case where the attempt to call file.read|write fails, so no harm + done. */ + + if ( !PyObject_HasAttrString(file, "read") + && !PyObject_HasAttrString(file, "write") + ) + { + PyErr_SetString(PyExc_TypeError, "file must be a readable file-like" + " object for COPY FROM; a writable file-like object for COPY TO." + ); + goto exit; + } + + self->copysize = bufsize; + Py_INCREF(file); + self->copyfile = file; + + Py_CLEAR(self->query); + Py_INCREF(sql); + self->query = sql; + + /* At this point, the SQL statement must be str, not unicode */ + if (pq_execute(self, Bytes_AS_STRING(sql), 0, 0, 0) >= 0) { + res = Py_None; + Py_INCREF(res); + } + + Py_CLEAR(self->copyfile); + +exit: + Py_XDECREF(sql); + + return res; +} + +/* extension: closed - return true if cursor is closed */ + +#define curs_closed_doc \ +"True if cursor is closed, False if cursor is open" + +static PyObject * +curs_closed_get(cursorObject *self, void *closure) +{ + return PyBool_FromLong(self->closed || (self->conn && self->conn->closed)); +} + +/* extension: withhold - get or set "WITH HOLD" for named cursors */ + +#define curs_withhold_doc \ +"Set or return cursor use of WITH HOLD" + +static PyObject * +curs_withhold_get(cursorObject *self) +{ + return PyBool_FromLong(self->withhold); +} + +RAISES_NEG int +curs_withhold_set(cursorObject *self, PyObject *pyvalue) +{ + int value; + + if (pyvalue != Py_False && self->name == NULL) { + PyErr_SetString(ProgrammingError, + "trying to set .withhold on unnamed cursor"); + return -1; + } + + if ((value = PyObject_IsTrue(pyvalue)) == -1) + return -1; + + self->withhold = value; + + return 0; +} + +#define curs_scrollable_doc \ +"Set or return cursor use of SCROLL" + +static PyObject * +curs_scrollable_get(cursorObject *self) +{ + PyObject *ret = NULL; + + switch (self->scrollable) { + case -1: + ret = Py_None; + break; + case 0: + ret = Py_False; + break; + case 1: + ret = Py_True; + break; + default: + PyErr_SetString(InternalError, "unexpected scrollable value"); + } + + Py_XINCREF(ret); + return ret; +} + +RAISES_NEG int +curs_scrollable_set(cursorObject *self, PyObject *pyvalue) +{ + int value; + + if (pyvalue != Py_None && self->name == NULL) { + PyErr_SetString(ProgrammingError, + "trying to set .scrollable on unnamed cursor"); + return -1; + } + + if (pyvalue == Py_None) { + value = -1; + } else if ((value = PyObject_IsTrue(pyvalue)) == -1) { + return -1; + } + + self->scrollable = value; + + return 0; +} + + +#define curs_pgresult_ptr_doc \ +"pgresult_ptr -- Get the PGresult structure pointer." + +static PyObject * +curs_pgresult_ptr_get(cursorObject *self) +{ + if (self->pgres) { + return PyLong_FromVoidPtr((void *)self->pgres); + } + else { + Py_RETURN_NONE; + } +} + + +/** the cursor object **/ + +/* iterator protocol */ + +static PyObject * +cursor_iter(PyObject *self) +{ + EXC_IF_CURS_CLOSED((cursorObject*)self); + Py_INCREF(self); + return self; +} + +static PyObject * +cursor_next(PyObject *self) +{ + PyObject *res; + + if (NULL == ((cursorObject*)self)->name) { + /* we don't parse arguments: curs_fetchone will do that for us */ + res = curs_fetchone((cursorObject*)self, NULL); + + /* convert a None to NULL to signal the end of iteration */ + if (res && res == Py_None) { + Py_DECREF(res); + res = NULL; + } + } + else { + res = curs_next_named((cursorObject*)self); + } + + return res; +} + +/* object method list */ + +static struct PyMethodDef cursorObject_methods[] = { + /* DBAPI-2.0 core */ + {"close", (PyCFunction)curs_close, + METH_NOARGS, curs_close_doc}, + {"execute", (PyCFunction)curs_execute, + METH_VARARGS|METH_KEYWORDS, curs_execute_doc}, + {"executemany", (PyCFunction)curs_executemany, + METH_VARARGS|METH_KEYWORDS, curs_executemany_doc}, + {"fetchone", (PyCFunction)curs_fetchone, + METH_NOARGS, curs_fetchone_doc}, + {"fetchmany", (PyCFunction)curs_fetchmany, + METH_VARARGS|METH_KEYWORDS, curs_fetchmany_doc}, + {"fetchall", (PyCFunction)curs_fetchall, + METH_NOARGS, curs_fetchall_doc}, + {"callproc", (PyCFunction)curs_callproc, + METH_VARARGS, curs_callproc_doc}, + {"nextset", (PyCFunction)curs_nextset, + METH_NOARGS, curs_nextset_doc}, + {"setinputsizes", (PyCFunction)curs_setinputsizes, + METH_VARARGS, curs_setinputsizes_doc}, + {"setoutputsize", (PyCFunction)curs_setoutputsize, + METH_VARARGS, curs_setoutputsize_doc}, + /* DBAPI-2.0 extensions */ + {"scroll", (PyCFunction)curs_scroll, + METH_VARARGS|METH_KEYWORDS, curs_scroll_doc}, + {"__enter__", (PyCFunction)curs_enter, + METH_NOARGS, curs_enter_doc}, + {"__exit__", (PyCFunction)curs_exit, + METH_VARARGS, curs_exit_doc}, + /* psycopg extensions */ + {"cast", (PyCFunction)curs_cast, + METH_VARARGS, curs_cast_doc}, + {"mogrify", (PyCFunction)curs_mogrify, + METH_VARARGS|METH_KEYWORDS, curs_mogrify_doc}, + {"copy_from", (PyCFunction)curs_copy_from, + METH_VARARGS|METH_KEYWORDS, curs_copy_from_doc}, + {"copy_to", (PyCFunction)curs_copy_to, + METH_VARARGS|METH_KEYWORDS, curs_copy_to_doc}, + {"copy_expert", (PyCFunction)curs_copy_expert, + METH_VARARGS|METH_KEYWORDS, curs_copy_expert_doc}, + {NULL} +}; + +/* object member list */ + +#define OFFSETOF(x) offsetof(cursorObject, x) + +static struct PyMemberDef cursorObject_members[] = { + /* DBAPI-2.0 basics */ + {"rowcount", T_LONG, OFFSETOF(rowcount), READONLY, + "Number of rows read from the backend in the last command."}, + {"arraysize", T_LONG, OFFSETOF(arraysize), 0, + "Number of records `fetchmany()` must fetch if not explicitly " \ + "specified."}, + {"itersize", T_LONG, OFFSETOF(itersize), 0, + "Number of records ``iter(cur)`` must fetch per network roundtrip."}, + {"description", T_OBJECT, OFFSETOF(description), READONLY, + "Cursor description as defined in DBAPI-2.0."}, + {"lastrowid", T_OID, OFFSETOF(lastoid), READONLY, + "The ``oid`` of the last row inserted by the cursor."}, + /* DBAPI-2.0 extensions */ + {"rownumber", T_LONG, OFFSETOF(row), READONLY, + "The current row position."}, + {"connection", T_OBJECT, OFFSETOF(conn), READONLY, + "The connection where the cursor comes from."}, + {"name", T_STRING, OFFSETOF(name), READONLY}, + {"statusmessage", T_OBJECT, OFFSETOF(pgstatus), READONLY, + "The return message of the last command."}, + {"query", T_OBJECT, OFFSETOF(query), READONLY, + "The last query text sent to the backend."}, + {"row_factory", T_OBJECT, OFFSETOF(tuple_factory), 0}, + {"tzinfo_factory", T_OBJECT, OFFSETOF(tzinfo_factory), 0}, + {"typecaster", T_OBJECT, OFFSETOF(caster), READONLY}, + {"string_types", T_OBJECT, OFFSETOF(string_types), 0}, + {"binary_types", T_OBJECT, OFFSETOF(binary_types), 0}, + {NULL} +}; + +/* object calculated member list */ +static struct PyGetSetDef cursorObject_getsets[] = { + { "closed", (getter)curs_closed_get, NULL, + curs_closed_doc, NULL }, + { "withhold", + (getter)curs_withhold_get, + (setter)curs_withhold_set, + curs_withhold_doc, NULL }, + { "scrollable", + (getter)curs_scrollable_get, + (setter)curs_scrollable_set, + curs_scrollable_doc, NULL }, + { "pgresult_ptr", + (getter)curs_pgresult_ptr_get, NULL, + curs_pgresult_ptr_doc, NULL }, + {NULL} +}; + +/* initialization and finalization methods */ + +static int +cursor_setup(cursorObject *self, connectionObject *conn, const char *name) +{ + Dprintf("cursor_setup: init cursor object at %p", self); + Dprintf("cursor_setup: parameters: name = %s, conn = %p", name, conn); + + if (name) { + if (0 > psyco_strdup(&self->name, name, -1)) { + return -1; + } + if (!(self->qname = psyco_escape_identifier(conn, name, -1))) { + return -1; + } + } + + /* FIXME: why does this raise an exception on the _next_ line of code? + if (PyObject_IsInstance((PyObject*)conn, + (PyObject *)&connectionType) == 0) { + PyErr_SetString(PyExc_TypeError, + "argument 1 must be subclass of psycopg2.extensions.connection"); + return -1; + } */ + Py_INCREF(conn); + self->conn = conn; + + self->mark = conn->mark; + self->notuples = 1; + self->arraysize = 1; + self->itersize = 2000; + self->rowcount = -1; + self->lastoid = InvalidOid; + + Py_INCREF(Py_None); + self->tuple_factory = Py_None; + + /* default tzinfo factory */ + { + /* The datetime api doesn't seem to have a constructor to make a + * datetime.timezone, so use the Python interface. */ + PyObject *m = NULL; + if ((m = PyImport_ImportModule("datetime"))) { + self->tzinfo_factory = PyObject_GetAttrString(m, "timezone"); + Py_DECREF(m); + } + if (!self->tzinfo_factory) { + return -1; + } + } + + Dprintf("cursor_setup: good cursor object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + self, Py_REFCNT(self) + ); + return 0; +} + +static int +cursor_clear(cursorObject *self) +{ + Py_CLEAR(self->conn); + Py_CLEAR(self->description); + Py_CLEAR(self->pgstatus); + Py_CLEAR(self->casts); + Py_CLEAR(self->caster); + Py_CLEAR(self->copyfile); + Py_CLEAR(self->tuple_factory); + Py_CLEAR(self->tzinfo_factory); + Py_CLEAR(self->query); + Py_CLEAR(self->string_types); + Py_CLEAR(self->binary_types); + return 0; +} + +static void +cursor_dealloc(PyObject* obj) +{ + cursorObject *self = (cursorObject *)obj; + + PyObject_GC_UnTrack(self); + + if (self->weakreflist) { + PyObject_ClearWeakRefs(obj); + } + + cursor_clear(self); + + PyMem_Free(self->name); + PQfreemem(self->qname); + + CLEARPGRES(self->pgres); + + Dprintf("cursor_dealloc: deleted cursor object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + obj, Py_REFCNT(obj)); + + Py_TYPE(obj)->tp_free(obj); +} + +static int +cursor_init(PyObject *obj, PyObject *args, PyObject *kwargs) +{ + PyObject *conn; + PyObject *name = Py_None; + PyObject *bname = NULL; + const char *cname = NULL; + int rv = -1; + + static char *kwlist[] = {"conn", "name", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!|O", kwlist, + &connectionType, &conn, &name)) { + goto exit; + } + + if (name != Py_None) { + Py_INCREF(name); /* for ensure_bytes */ + if (!(bname = psyco_ensure_bytes(name))) { + /* name has had a ref stolen */ + goto exit; + } + + if (!(cname = Bytes_AsString(bname))) { + goto exit; + } + } + + rv = cursor_setup((cursorObject *)obj, (connectionObject *)conn, cname); + +exit: + Py_XDECREF(bname); + return rv; +} + +static PyObject * +cursor_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + +static PyObject * +cursor_repr(cursorObject *self) +{ + return PyString_FromFormat( + "", self, self->closed); +} + +static int +cursor_traverse(cursorObject *self, visitproc visit, void *arg) +{ + Py_VISIT((PyObject *)self->conn); + Py_VISIT(self->description); + Py_VISIT(self->pgstatus); + Py_VISIT(self->casts); + Py_VISIT(self->caster); + Py_VISIT(self->copyfile); + Py_VISIT(self->tuple_factory); + Py_VISIT(self->tzinfo_factory); + Py_VISIT(self->query); + Py_VISIT(self->string_types); + Py_VISIT(self->binary_types); + return 0; +} + + +/* object type */ + +#define cursorType_doc \ +"A database cursor." + +PyTypeObject cursorType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2.extensions.cursor", + sizeof(cursorObject), 0, + cursor_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + (reprfunc)cursor_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + (reprfunc)cursor_repr, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_ITER | + Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_WEAKREFS , + /*tp_flags*/ + cursorType_doc, /*tp_doc*/ + (traverseproc)cursor_traverse, /*tp_traverse*/ + (inquiry)cursor_clear, /*tp_clear*/ + 0, /*tp_richcompare*/ + offsetof(cursorObject, weakreflist), /*tp_weaklistoffset*/ + cursor_iter, /*tp_iter*/ + cursor_next, /*tp_iternext*/ + cursorObject_methods, /*tp_methods*/ + cursorObject_members, /*tp_members*/ + cursorObject_getsets, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + cursor_init, /*tp_init*/ + 0, /*tp_alloc*/ + cursor_new, /*tp_new*/ +}; diff --git a/source-code/psycopg2/psycopg/diagnostics.h b/source-code/psycopg2/psycopg/diagnostics.h new file mode 100644 index 0000000..2e2858d --- /dev/null +++ b/source-code/psycopg2/psycopg/diagnostics.h @@ -0,0 +1,41 @@ +/* diagnostics.c - definition for the psycopg Diagnostics type + * + * Copyright (C) 2013-2019 Matthew Woodcraft + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_DIAGNOSTICS_H +#define PSYCOPG_DIAGNOSTICS_H 1 + +#include "psycopg/error.h" + +extern HIDDEN PyTypeObject diagnosticsType; + +typedef struct { + PyObject_HEAD + + errorObject *err; /* exception to retrieve the diagnostics from */ + +} diagnosticsObject; + +#endif /* PSYCOPG_DIAGNOSTICS_H */ diff --git a/source-code/psycopg2/psycopg/diagnostics_type.c b/source-code/psycopg2/psycopg/diagnostics_type.c new file mode 100644 index 0000000..a46e7d8 --- /dev/null +++ b/source-code/psycopg2/psycopg/diagnostics_type.c @@ -0,0 +1,208 @@ +/* diagnostics.c - present information from libpq error responses + * + * Copyright (C) 2013-2019 Matthew Woodcraft + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/diagnostics.h" +#include "psycopg/error.h" + + +/* These constants are defined in src/include/postgres_ext.h but some may not + * be available with the libpq we currently support at compile time. */ + +/* Available from PG 9.3 */ +#ifndef PG_DIAG_SCHEMA_NAME +#define PG_DIAG_SCHEMA_NAME 's' +#endif +#ifndef PG_DIAG_TABLE_NAME +#define PG_DIAG_TABLE_NAME 't' +#endif +#ifndef PG_DIAG_COLUMN_NAME +#define PG_DIAG_COLUMN_NAME 'c' +#endif +#ifndef PG_DIAG_DATATYPE_NAME +#define PG_DIAG_DATATYPE_NAME 'd' +#endif +#ifndef PG_DIAG_CONSTRAINT_NAME +#define PG_DIAG_CONSTRAINT_NAME 'n' +#endif + +/* Available from PG 9.6 */ +#ifndef PG_DIAG_SEVERITY_NONLOCALIZED +#define PG_DIAG_SEVERITY_NONLOCALIZED 'V' +#endif + + +/* Retrieve an error string from the exception's cursor. + * + * If the cursor or its result isn't available, return None. + */ +static PyObject * +diagnostics_get_field(diagnosticsObject *self, void *closure) +{ + const char *errortext; + + if (!self->err->pgres) { + Py_RETURN_NONE; + } + + errortext = PQresultErrorField(self->err->pgres, (int)(Py_intptr_t)closure); + return error_text_from_chars(self->err, errortext); +} + + +/* object calculated member list */ +static struct PyGetSetDef diagnosticsObject_getsets[] = { + { "severity", (getter)diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_SEVERITY }, + { "severity_nonlocalized", (getter)diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_SEVERITY_NONLOCALIZED }, + { "sqlstate", (getter)diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_SQLSTATE }, + { "message_primary", (getter)diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_MESSAGE_PRIMARY }, + { "message_detail", (getter)diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_MESSAGE_DETAIL }, + { "message_hint", (getter)diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_MESSAGE_HINT }, + { "statement_position", (getter)diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_STATEMENT_POSITION }, + { "internal_position", (getter)diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_INTERNAL_POSITION }, + { "internal_query", (getter)diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_INTERNAL_QUERY }, + { "context", (getter)diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_CONTEXT }, + { "schema_name", (getter)diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_SCHEMA_NAME }, + { "table_name", (getter)diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_TABLE_NAME }, + { "column_name", (getter)diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_COLUMN_NAME }, + { "datatype_name", (getter)diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_DATATYPE_NAME }, + { "constraint_name", (getter)diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_CONSTRAINT_NAME }, + { "source_file", (getter)diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_SOURCE_FILE }, + { "source_line", (getter)diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_SOURCE_LINE }, + { "source_function", (getter)diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_SOURCE_FUNCTION }, + {NULL} +}; + +/* initialization and finalization methods */ + +static PyObject * +diagnostics_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + +static int +diagnostics_init(diagnosticsObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *err = NULL; + + if (!PyArg_ParseTuple(args, "O", &err)) + return -1; + + if (!PyObject_TypeCheck(err, &errorType)) { + PyErr_SetString(PyExc_TypeError, + "The argument must be a psycopg2.Error"); + return -1; + } + + Py_INCREF(err); + self->err = (errorObject *)err; + return 0; +} + +static void +diagnostics_dealloc(diagnosticsObject* self) +{ + Py_CLEAR(self->err); + Py_TYPE(self)->tp_free((PyObject *)self); +} + + +/* object type */ + +static const char diagnosticsType_doc[] = + "Details from a database error report.\n\n" + "The object is returned by the `~psycopg2.Error.diag` attribute of the\n" + "`!Error` object.\n" + "All the information available from the |PQresultErrorField|_ function\n" + "are exposed as attributes by the object, e.g. the `!severity` attribute\n" + "returns the `!PG_DIAG_SEVERITY` code. " + "Please refer to the `PostgreSQL documentation`__ for the meaning of all" + " the attributes.\n\n" + ".. |PQresultErrorField| replace:: `!PQresultErrorField()`\n" + ".. _PQresultErrorField: https://www.postgresql.org/docs/current/static/" + "libpq-exec.html#LIBPQ-PQRESULTERRORFIELD\n" + ".. __: PQresultErrorField_\n"; + +PyTypeObject diagnosticsType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2.extensions.Diagnostics", + sizeof(diagnosticsObject), 0, + (destructor)diagnostics_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + diagnosticsType_doc, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ + 0, /*tp_members*/ + diagnosticsObject_getsets, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + (initproc)diagnostics_init, /*tp_init*/ + 0, /*tp_alloc*/ + diagnostics_new, /*tp_new*/ +}; diff --git a/source-code/psycopg2/psycopg/error.h b/source-code/psycopg2/psycopg/error.h new file mode 100644 index 0000000..3312899 --- /dev/null +++ b/source-code/psycopg2/psycopg/error.h @@ -0,0 +1,46 @@ +/* error.h - definition for the psycopg base Error type + * + * Copyright (C) 2013-2019 Daniele Varrazzo + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_ERROR_H +#define PSYCOPG_ERROR_H 1 + +extern HIDDEN PyTypeObject errorType; + +typedef struct { + PyBaseExceptionObject exc; + + PyObject *pgerror; + PyObject *pgcode; + cursorObject *cursor; + PyObject *pydecoder; + PGresult *pgres; +} errorObject; + +HIDDEN PyObject *error_text_from_chars(errorObject *self, const char *str); +HIDDEN BORROWED PyObject *exception_from_sqlstate(const char *sqlstate); +HIDDEN BORROWED PyObject *base_exception_from_sqlstate(const char *sqlstate); + +#endif /* PSYCOPG_ERROR_H */ diff --git a/source-code/psycopg2/psycopg/error_type.c b/source-code/psycopg2/psycopg/error_type.c new file mode 100644 index 0000000..5fd96e2 --- /dev/null +++ b/source-code/psycopg2/psycopg/error_type.c @@ -0,0 +1,376 @@ +/* error_type.c - python interface to the Error objects + * + * Copyright (C) 2013-2019 Daniele Varrazzo + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/error.h" +#include "psycopg/diagnostics.h" +#include "psycopg/pqpath.h" + + +PyObject * +error_text_from_chars(errorObject *self, const char *str) +{ + return psyco_text_from_chars_safe(str, -1, self->pydecoder); +} + + +/* Return the Python exception corresponding to an SQLSTATE error + * code. A list of error codes can be found at: + * https://www.postgresql.org/docs/current/static/errcodes-appendix.html + */ +BORROWED PyObject * +exception_from_sqlstate(const char *sqlstate) +{ + PyObject *exc; + + /* First look up an exception of the proper class */ + exc = PyDict_GetItemString(sqlstate_errors, sqlstate); + if (exc) { + return exc; + } + else { + PyErr_Clear(); + return base_exception_from_sqlstate(sqlstate); + } +} + +BORROWED PyObject * +base_exception_from_sqlstate(const char *sqlstate) +{ + switch (sqlstate[0]) { + case '0': + switch (sqlstate[1]) { + case '8': /* Class 08 - Connection Exception */ + return OperationalError; + case 'A': /* Class 0A - Feature Not Supported */ + return NotSupportedError; + } + break; + case '2': + switch (sqlstate[1]) { + case '0': /* Class 20 - Case Not Found */ + case '1': /* Class 21 - Cardinality Violation */ + return ProgrammingError; + case '2': /* Class 22 - Data Exception */ + return DataError; + case '3': /* Class 23 - Integrity Constraint Violation */ + return IntegrityError; + case '4': /* Class 24 - Invalid Cursor State */ + case '5': /* Class 25 - Invalid Transaction State */ + return InternalError; + case '6': /* Class 26 - Invalid SQL Statement Name */ + case '7': /* Class 27 - Triggered Data Change Violation */ + case '8': /* Class 28 - Invalid Authorization Specification */ + return OperationalError; + case 'B': /* Class 2B - Dependent Privilege Descriptors Still Exist */ + case 'D': /* Class 2D - Invalid Transaction Termination */ + case 'F': /* Class 2F - SQL Routine Exception */ + return InternalError; + } + break; + case '3': + switch (sqlstate[1]) { + case '4': /* Class 34 - Invalid Cursor Name */ + return OperationalError; + case '8': /* Class 38 - External Routine Exception */ + case '9': /* Class 39 - External Routine Invocation Exception */ + case 'B': /* Class 3B - Savepoint Exception */ + return InternalError; + case 'D': /* Class 3D - Invalid Catalog Name */ + case 'F': /* Class 3F - Invalid Schema Name */ + return ProgrammingError; + } + break; + case '4': + switch (sqlstate[1]) { + case '0': /* Class 40 - Transaction Rollback */ + return TransactionRollbackError; + case '2': /* Class 42 - Syntax Error or Access Rule Violation */ + case '4': /* Class 44 - WITH CHECK OPTION Violation */ + return ProgrammingError; + } + break; + case '5': + /* Class 53 - Insufficient Resources + Class 54 - Program Limit Exceeded + Class 55 - Object Not In Prerequisite State + Class 57 - Operator Intervention + Class 58 - System Error (errors external to PostgreSQL itself) */ + if (!strcmp(sqlstate, "57014")) + return QueryCanceledError; + else + return OperationalError; + case 'F': /* Class F0 - Configuration File Error */ + return InternalError; + case 'H': /* Class HV - Foreign Data Wrapper Error (SQL/MED) */ + return OperationalError; + case 'P': /* Class P0 - PL/pgSQL Error */ + return InternalError; + case 'X': /* Class XX - Internal Error */ + return InternalError; + } + /* return DatabaseError as a fallback */ + return DatabaseError; +} + + +static const char pgerror_doc[] = + "The error message returned by the backend, if available, else None"; + +static const char pgcode_doc[] = + "The error code returned by the backend, if available, else None"; + +static const char cursor_doc[] = + "The cursor that raised the exception, if available, else None"; + +static const char diag_doc[] = + "A Diagnostics object to get further information about the error"; + +static PyMemberDef error_members[] = { + { "pgerror", T_OBJECT, offsetof(errorObject, pgerror), + READONLY, (char *)pgerror_doc }, + { "pgcode", T_OBJECT, offsetof(errorObject, pgcode), + READONLY, (char *)pgcode_doc }, + { "cursor", T_OBJECT, offsetof(errorObject, cursor), + READONLY, (char *)cursor_doc }, + { NULL } +}; + +static PyObject * +error_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + return ((PyTypeObject *)PyExc_StandardError)->tp_new( + type, args, kwargs); +} + +static int +error_init(errorObject *self, PyObject *args, PyObject *kwargs) +{ + if (((PyTypeObject *)PyExc_StandardError)->tp_init( + (PyObject *)self, args, kwargs) < 0) { + return -1; + } + return 0; +} + +static int +error_traverse(errorObject *self, visitproc visit, void *arg) +{ + Py_VISIT(self->pgerror); + Py_VISIT(self->pgcode); + Py_VISIT(self->cursor); + Py_VISIT(self->pydecoder); + + return ((PyTypeObject *)PyExc_StandardError)->tp_traverse( + (PyObject *)self, visit, arg); +} + +static int +error_clear(errorObject *self) +{ + Py_CLEAR(self->pgerror); + Py_CLEAR(self->pgcode); + Py_CLEAR(self->cursor); + Py_CLEAR(self->pydecoder); + + return ((PyTypeObject *)PyExc_StandardError)->tp_clear((PyObject *)self); +} + +static void +error_dealloc(errorObject *self) +{ + PyObject_GC_UnTrack((PyObject *)self); + error_clear(self); + CLEARPGRES(self->pgres); + + Py_TYPE(self)->tp_free((PyObject *)self); +} + + +static PyObject * +error_get_diag(errorObject *self, void *closure) +{ + return PyObject_CallFunctionObjArgs( + (PyObject *)&diagnosticsType, (PyObject *)self, NULL); +} + +static struct PyGetSetDef error_getsets[] = { + { "diag", (getter)error_get_diag, NULL, (char *)diag_doc }, + { NULL } +}; + + +/* Error.__reduce__ + * + * The method is required to make exceptions picklable: set the cursor + * attribute to None. Only working from Py 2.5: previous versions + * would require implementing __getstate__, and as of 2012 it's a little + * bit too late to care. */ +static PyObject * +error_reduce(errorObject *self, PyObject *dummy) +{ + PyObject *meth = NULL; + PyObject *tuple = NULL; + PyObject *dict = NULL; + PyObject *rv = NULL; + + if (!(meth = PyObject_GetAttrString(PyExc_StandardError, "__reduce__"))) { + goto error; + } + if (!(tuple = PyObject_CallFunctionObjArgs(meth, self, NULL))) { + goto error; + } + + /* tuple is (type, args): convert it to (type, args, dict) + * with our extra items in the dict. + * + * If these checks fail, we can still return a valid object. Pickle + * will likely fail downstream, but there's nothing else we can do here */ + if (!PyTuple_Check(tuple)) { goto exit; } + if (2 != PyTuple_GET_SIZE(tuple)) { goto exit; } + + if (!(dict = PyDict_New())) { goto error; } + if (self->pgerror) { + if (0 != PyDict_SetItemString(dict, "pgerror", self->pgerror)) { + goto error; + } + } + if (self->pgcode) { + if (0 != PyDict_SetItemString(dict, "pgcode", self->pgcode)) { + goto error; + } + } + + { + PyObject *newtuple; + if (!(newtuple = PyTuple_Pack(3, + PyTuple_GET_ITEM(tuple, 0), + PyTuple_GET_ITEM(tuple, 1), + dict))) { + goto error; + } + Py_DECREF(tuple); + tuple = newtuple; + } + +exit: + rv = tuple; + tuple = NULL; + +error: + Py_XDECREF(dict); + Py_XDECREF(tuple); + Py_XDECREF(meth); + + return rv; +} + +PyObject * +error_setstate(errorObject *self, PyObject *state) +{ + PyObject *rv = NULL; + + /* we don't call the StandartError's setstate as it would try to load the + * dict content as attributes */ + + if (state == Py_None) { + goto exit; + } + if (!PyDict_Check(state)) { + PyErr_SetString(PyExc_TypeError, "state is not a dictionary"); + goto error; + } + + /* load the dict content in the structure */ + Py_CLEAR(self->pgerror); + self->pgerror = PyDict_GetItemString(state, "pgerror"); + Py_XINCREF(self->pgerror); + + Py_CLEAR(self->pgcode); + self->pgcode = PyDict_GetItemString(state, "pgcode"); + Py_XINCREF(self->pgcode); + + Py_CLEAR(self->cursor); + /* We never expect a cursor in the state as it's not picklable. + * at most there could be a None here, coming from Psycopg < 2.5 */ + +exit: + rv = Py_None; + Py_INCREF(rv); + +error: + return rv; +} + +static PyMethodDef error_methods[] = { + /* Make Error and all its subclasses picklable. */ + {"__reduce__", (PyCFunction)error_reduce, METH_NOARGS }, + {"__setstate__", (PyCFunction)error_setstate, METH_O }, + {NULL} +}; + + +PyTypeObject errorType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2.Error", + sizeof(errorObject), 0, + (destructor)error_dealloc, /* tp_dealloc */ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/ + Error_doc, /*tp_doc*/ + (traverseproc)error_traverse, /*tp_traverse*/ + (inquiry)error_clear, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + error_methods, /*tp_methods*/ + error_members, /*tp_members*/ + error_getsets, /*tp_getset*/ + 0, /*tp_base Will be set to StandardError in module init */ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + (initproc)error_init, /*tp_init*/ + 0, /*tp_alloc*/ + error_new, /*tp_new*/ +}; diff --git a/source-code/psycopg2/psycopg/green.c b/source-code/psycopg2/psycopg/green.c new file mode 100644 index 0000000..9de05e7 --- /dev/null +++ b/source-code/psycopg2/psycopg/green.c @@ -0,0 +1,210 @@ +/* green.c - cooperation with coroutine libraries. + * + * Copyright (C) 2010-2019 Daniele Varrazzo + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/green.h" +#include "psycopg/connection.h" +#include "psycopg/pqpath.h" + + +HIDDEN PyObject *wait_callback = NULL; + +static PyObject *have_wait_callback(void); +static void green_panic(connectionObject *conn); + +/* Register a callback function to block waiting for data. + * + * The function is exported by the _psycopg module. + */ +PyObject * +psyco_set_wait_callback(PyObject *self, PyObject *obj) +{ + Py_XDECREF(wait_callback); + + if (obj != Py_None) { + wait_callback = obj; + Py_INCREF(obj); + } + else { + wait_callback = NULL; + } + + Py_RETURN_NONE; +} + + +/* Return the currently registered wait callback function. + * + * The function is exported by the _psycopg module. + */ +PyObject * +psyco_get_wait_callback(PyObject *self, PyObject *obj) +{ + PyObject *ret; + + ret = wait_callback; + if (!ret) { + ret = Py_None; + } + + Py_INCREF(ret); + return ret; +} + + +/* Return nonzero if a wait callback should be called. */ +int +psyco_green() +{ + return (NULL != wait_callback); +} + +/* Return the wait callback if available. + * + * If not available, set a Python exception and return. + * + * The function returns a new reference: decref after use. + */ +static PyObject * +have_wait_callback() +{ + PyObject *cb; + + cb = wait_callback; + if (!cb) { + PyErr_SetString(OperationalError, "wait callback not available"); + return NULL; + } + Py_INCREF(cb); + return cb; +} + +/* Block waiting for data available in an async connection. + * + * This function assumes `wait_callback` to be available: + * raise `InterfaceError` if it is not. Use `psyco_green()` to check if + * the function is to be called. + * + * Return 0 on success, else nonzero and set a Python exception. + */ +int +psyco_wait(connectionObject *conn) +{ + PyObject *rv; + PyObject *cb; + + Dprintf("psyco_wait"); + if (!(cb = have_wait_callback())) { + return -1; + } + + rv = PyObject_CallFunctionObjArgs(cb, conn, NULL); + Py_DECREF(cb); + + if (NULL != rv) { + Py_DECREF(rv); + return 0; + } else { + Dprintf("psyco_wait: error in wait callback"); + return -1; + } +} + +/* Replacement for PQexec using the user-provided wait function. + * + * The function should be called helding the connection lock, and + * the GIL because some Python code is expected to be called. + * + * If PGresult is NULL, there may have been either a libpq error + * or an exception raised by Python code: before raising an exception + * check if there is already one using `PyErr_Occurred()` */ +PGresult * +psyco_exec_green(connectionObject *conn, const char *command) +{ + PGresult *result = NULL; + + /* Check that there is a single concurrently executing query */ + if (conn->async_cursor) { + PyErr_SetString(ProgrammingError, + "a single async query can be executed on the same connection"); + goto end; + } + /* we don't care about which cursor is executing the query, and + * it may also be that no cursor is involved at all and this is + * an internal query. So just store anything in the async_cursor, + * respecting the code expecting it to be a weakref */ + if (!(conn->async_cursor = PyWeakref_NewRef((PyObject*)conn, NULL))) { + goto end; + } + + /* Send the query asynchronously */ + if (0 == pq_send_query(conn, command)) { + goto end; + } + + /* Enter the poll loop with a write. When writing is finished the poll + implementation will set the status to ASYNC_READ without exiting the + loop. If read is finished the status is finally set to ASYNC_DONE. + */ + conn->async_status = ASYNC_WRITE; + + if (0 != psyco_wait(conn)) { + green_panic(conn); + goto end; + } + + /* the result is now in the connection: take its ownership */ + result = conn->pgres; + conn->pgres = NULL; + +end: + CLEARPGRES(conn->pgres); + conn->async_status = ASYNC_DONE; + Py_CLEAR(conn->async_cursor); + return result; +} + + +/* There has been a communication error during query execution. It may have + * happened e.g. for a network error or an error in the callback, and we + * cannot tell the two apart. + * Trying to PQcancel or PQgetResult to put the connection back into a working + * state doesn't work nice (issue #113): the program blocks and the + * interpreter won't even respond to SIGINT. PQreset could work async, but the + * python program would have then a connection made but not configured where + * it is probably not designed to handled. So for the moment we do the kindest + * thing we can: we close the connection. A long-running program should + * already have a way to discard broken connections; a short-lived one would + * benefit of working ctrl-c. + */ +static void +green_panic(connectionObject *conn) +{ + Dprintf("green_panic: closing the connection"); + conn_close_locked(conn); +} diff --git a/source-code/psycopg2/psycopg/green.h b/source-code/psycopg2/psycopg/green.h new file mode 100644 index 0000000..f4a675c --- /dev/null +++ b/source-code/psycopg2/psycopg/green.h @@ -0,0 +1,76 @@ +/* green.c - cooperation with coroutine libraries. + * + * Copyright (C) 2010-2019 Daniele Varrazzo + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_GREEN_H +#define PSYCOPG_GREEN_H 1 + +#include +#include "psycopg/connection.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define psyco_set_wait_callback_doc \ +"Register a callback function to block waiting for data.\n" \ +"\n" \ +"The callback should have signature :samp:`fun({conn})` and\n" \ +"is called to wait for data available whenever a blocking function from the\n" \ +"libpq is called. Use `!set_wait_callback(None)` to revert to the\n" \ +"original behaviour (i.e. using blocking libpq functions).\n" \ +"\n" \ +"The function is an hook to allow coroutine-based libraries (such as\n" \ +"Eventlet_ or gevent_) to switch when Psycopg is blocked, allowing\n" \ +"other coroutines to run concurrently.\n" \ +"\n" \ +"See `~psycopg2.extras.wait_select()` for an example of a wait callback\n" \ +"implementation.\n" \ +"\n" \ +".. _Eventlet: https://eventlet.net/\n" \ +".. _gevent: http://www.gevent.org/\n" +HIDDEN PyObject *psyco_set_wait_callback(PyObject *self, PyObject *obj); + +#define psyco_get_wait_callback_doc \ +"Return the currently registered wait callback.\n" \ +"\n" \ +"Return `!None` if no callback is currently registered.\n" +HIDDEN PyObject *psyco_get_wait_callback(PyObject *self, PyObject *obj); + +HIDDEN int psyco_green(void); +HIDDEN int psyco_wait(connectionObject *conn); +HIDDEN PGresult *psyco_exec_green(connectionObject *conn, const char *command); + +#define EXC_IF_GREEN(cmd) \ +if (psyco_green()) { \ + PyErr_SetString(ProgrammingError, #cmd " cannot be used " \ + "with an asynchronous callback."); \ + return NULL; } + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_GREEN_H) */ diff --git a/source-code/psycopg2/psycopg/libpq_support.c b/source-code/psycopg2/psycopg/libpq_support.c new file mode 100644 index 0000000..712102e --- /dev/null +++ b/source-code/psycopg2/psycopg/libpq_support.c @@ -0,0 +1,106 @@ +/* libpq_support.c - functions not provided by libpq, but which are + * required for advanced communication with the server, such as + * streaming replication + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/libpq_support.h" + +/* htonl(), ntohl() */ +#ifdef _WIN32 +#include +/* gettimeofday() */ +#include "psycopg/win32_support.h" +#else +#include +#include +#endif + +/* support routines taken from pg_basebackup/streamutil.c */ + +/* + * Frontend version of GetCurrentTimestamp(), since we are not linked with + * backend code. The protocol always uses integer timestamps, regardless of + * server setting. + */ +int64_t +feGetCurrentTimestamp(void) +{ + int64_t result; + struct timeval tp; + + gettimeofday(&tp, NULL); + + result = (int64_t) tp.tv_sec - + ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY); + + result = (result * USECS_PER_SEC) + tp.tv_usec; + + return result; +} + +/* + * Converts an int64 to network byte order. + */ +void +fe_sendint64(int64_t i, char *buf) +{ + uint32_t n32; + + /* High order half first, since we're doing MSB-first */ + n32 = (uint32_t) (i >> 32); + n32 = htonl(n32); + memcpy(&buf[0], &n32, 4); + + /* Now the low order half */ + n32 = (uint32_t) i; + n32 = htonl(n32); + memcpy(&buf[4], &n32, 4); +} + +/* + * Converts an int64 from network byte order to native format. + */ +int64_t +fe_recvint64(char *buf) +{ + int64_t result; + uint32_t h32; + uint32_t l32; + + memcpy(&h32, buf, 4); + memcpy(&l32, buf + 4, 4); + h32 = ntohl(h32); + l32 = ntohl(l32); + + result = h32; + result <<= 32; + result |= l32; + + return result; +} diff --git a/source-code/psycopg2/psycopg/libpq_support.h b/source-code/psycopg2/psycopg/libpq_support.h new file mode 100644 index 0000000..0b304d6 --- /dev/null +++ b/source-code/psycopg2/psycopg/libpq_support.h @@ -0,0 +1,49 @@ +/* libpq_support.h - definitions for libpq_support.c + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ +#ifndef PSYCOPG_LIBPQ_SUPPORT_H +#define PSYCOPG_LIBPQ_SUPPORT_H 1 + +#include "psycopg/config.h" + +/* type and constant definitions from internal postgres include */ +typedef uint64_t XLogRecPtr; + +/* have to use lowercase %x, as PyString_FromFormat can't do %X */ +#define XLOGFMTSTR "%x/%x" +#define XLOGFMTARGS(x) ((uint32_t)((x) >> 32)), ((uint32_t)((x) & 0xFFFFFFFF)) + +/* Julian-date equivalents of Day 0 in Unix and Postgres reckoning */ +#define UNIX_EPOCH_JDATE 2440588 /* == date2j(1970, 1, 1) */ +#define POSTGRES_EPOCH_JDATE 2451545 /* == date2j(2000, 1, 1) */ + +#define SECS_PER_DAY 86400 +#define USECS_PER_SEC 1000000LL + +HIDDEN int64_t feGetCurrentTimestamp(void); +HIDDEN void fe_sendint64(int64_t i, char *buf); +HIDDEN int64_t fe_recvint64(char *buf); + +#endif /* !defined(PSYCOPG_LIBPQ_SUPPORT_H) */ diff --git a/source-code/psycopg2/psycopg/lobject.h b/source-code/psycopg2/psycopg/lobject.h new file mode 100644 index 0000000..37e6b13 --- /dev/null +++ b/source-code/psycopg2/psycopg/lobject.h @@ -0,0 +1,102 @@ +/* lobject.h - definition for the psycopg lobject type + * + * Copyright (C) 2006-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_LOBJECT_H +#define PSYCOPG_LOBJECT_H 1 + +#include + +#include "psycopg/connection.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern HIDDEN PyTypeObject lobjectType; + +typedef struct { + PyObject HEAD; + + connectionObject *conn; /* connection owning the lobject */ + long int mark; /* copied from conn->mark */ + + char *smode; /* string mode if lobject was opened */ + int mode; /* numeric version of smode */ + + int fd; /* the file descriptor for file-like ops */ + Oid oid; /* the oid for this lobject */ +} lobjectObject; + +/* functions exported from lobject_int.c */ + +RAISES_NEG HIDDEN int lobject_open(lobjectObject *self, connectionObject *conn, + Oid oid, const char *smode, Oid new_oid, + const char *new_file); +RAISES_NEG HIDDEN int lobject_unlink(lobjectObject *self); +RAISES_NEG HIDDEN int lobject_export(lobjectObject *self, const char *filename); + +RAISES_NEG HIDDEN Py_ssize_t lobject_read(lobjectObject *self, char *buf, size_t len); +RAISES_NEG HIDDEN Py_ssize_t lobject_write(lobjectObject *self, const char *buf, + size_t len); +RAISES_NEG HIDDEN Py_ssize_t lobject_seek(lobjectObject *self, Py_ssize_t pos, int whence); +RAISES_NEG HIDDEN Py_ssize_t lobject_tell(lobjectObject *self); +RAISES_NEG HIDDEN int lobject_truncate(lobjectObject *self, size_t len); +RAISES_NEG HIDDEN int lobject_close(lobjectObject *self); + +#define lobject_is_closed(self) \ + ((self)->fd < 0 || !(self)->conn || (self)->conn->closed) + +/* exception-raising macros */ + +#define EXC_IF_LOBJ_CLOSED(self) \ + if (lobject_is_closed(self)) { \ + PyErr_SetString(InterfaceError, "lobject already closed"); \ + return NULL; } + +#define EXC_IF_LOBJ_LEVEL0(self) \ +if (self->conn->autocommit) { \ + psyco_set_error(ProgrammingError, NULL, \ + "can't use a lobject outside of transactions"); \ + return NULL; \ +} +#define EXC_IF_LOBJ_UNMARKED(self) \ +if (self->conn->mark != self->mark) { \ + psyco_set_error(ProgrammingError, NULL, \ + "lobject isn't valid anymore"); \ + return NULL; \ +} + +/* Values for the lobject mode */ +#define LOBJECT_READ 1 +#define LOBJECT_WRITE 2 +#define LOBJECT_BINARY 4 +#define LOBJECT_TEXT 8 + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_LOBJECT_H) */ diff --git a/source-code/psycopg2/psycopg/lobject_int.c b/source-code/psycopg2/psycopg/lobject_int.c new file mode 100644 index 0000000..f0c72c1 --- /dev/null +++ b/source-code/psycopg2/psycopg/lobject_int.c @@ -0,0 +1,486 @@ +/* lobject_int.c - code used by the lobject object + * + * Copyright (C) 2006-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/lobject.h" +#include "psycopg/connection.h" +#include "psycopg/pqpath.h" + +#include + +static void +collect_error(connectionObject *conn) +{ + conn_set_error(conn, PQerrorMessage(conn->pgconn)); +} + + +/* Check if the mode passed to the large object is valid. + * In case of success return a value >= 0 + * On error return a value < 0 and set an exception. + * + * Valid mode are [r|w|rw|n][t|b] + */ +RAISES_NEG static int +_lobject_parse_mode(const char *mode) +{ + int rv = 0; + size_t pos = 0; + + if (0 == strncmp("rw", mode, 2)) { + rv |= LOBJECT_READ | LOBJECT_WRITE; + pos += 2; + } + else { + switch (mode[0]) { + case 'r': + rv |= LOBJECT_READ; + pos += 1; + break; + case 'w': + rv |= LOBJECT_WRITE; + pos += 1; + break; + case 'n': + pos += 1; + break; + default: + rv |= LOBJECT_READ; + break; + } + } + + switch (mode[pos]) { + case 't': + rv |= LOBJECT_TEXT; + pos += 1; + break; + case 'b': + rv |= LOBJECT_BINARY; + pos += 1; + break; + default: + rv |= LOBJECT_TEXT; + break; + } + + if (pos != strlen(mode)) { + PyErr_Format(PyExc_ValueError, + "bad mode for lobject: '%s'", mode); + rv = -1; + } + + return rv; +} + + +/* Return a string representing the lobject mode. + * + * The return value is a new string allocated on the Python heap. + * + * The function must be called holding the GIL. + */ +static char * +_lobject_unparse_mode(int mode) +{ + char *buf; + char *c; + + /* the longest is 'rwt' */ + if (!(c = buf = PyMem_Malloc(4))) { + PyErr_NoMemory(); + return NULL; + } + + if (mode & LOBJECT_READ) { *c++ = 'r'; } + if (mode & LOBJECT_WRITE) { *c++ = 'w'; } + + if (buf == c) { + /* neither read nor write */ + *c++ = 'n'; + } + else { + if (mode & LOBJECT_TEXT) { + *c++ = 't'; + } + else { + *c++ = 'b'; + } + } + *c = '\0'; + + return buf; +} + +/* lobject_open - create a new/open an existing lo */ + +RAISES_NEG int +lobject_open(lobjectObject *self, connectionObject *conn, + Oid oid, const char *smode, Oid new_oid, const char *new_file) +{ + int retvalue = -1; + int pgmode = 0; + int mode; + + if (0 > (mode = _lobject_parse_mode(smode))) { + return -1; + } + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&(self->conn->lock)); + + retvalue = pq_begin_locked(self->conn, &_save); + if (retvalue < 0) + goto end; + + /* if the oid is InvalidOid we create a new lob before opening it + or we import a file from the FS, depending on the value of + new_file */ + if (oid == InvalidOid) { + if (new_file) + self->oid = lo_import(self->conn->pgconn, new_file); + else { + /* Use lo_creat when possible to be more middleware-friendly. + See ticket #88. */ + if (new_oid != InvalidOid) + self->oid = lo_create(self->conn->pgconn, new_oid); + else + self->oid = lo_creat(self->conn->pgconn, INV_READ | INV_WRITE); + } + + Dprintf("lobject_open: large object created with oid = %u", + self->oid); + + if (self->oid == InvalidOid) { + collect_error(self->conn); + retvalue = -1; + goto end; + } + + mode = (mode & ~LOBJECT_READ) | LOBJECT_WRITE; + } + else { + self->oid = oid; + } + + /* if the oid is a real one we try to open with the given mode */ + if (mode & LOBJECT_READ) { pgmode |= INV_READ; } + if (mode & LOBJECT_WRITE) { pgmode |= INV_WRITE; } + if (pgmode) { + self->fd = lo_open(self->conn->pgconn, self->oid, pgmode); + Dprintf("lobject_open: large object opened with mode = %i fd = %d", + pgmode, self->fd); + + if (self->fd == -1) { + collect_error(self->conn); + retvalue = -1; + goto end; + } + } + + /* set the mode for future reference */ + self->mode = mode; + Py_BLOCK_THREADS; + self->smode = _lobject_unparse_mode(mode); + Py_UNBLOCK_THREADS; + if (NULL == self->smode) { + retvalue = 1; /* exception already set */ + goto end; + } + + retvalue = 0; + + end: + pthread_mutex_unlock(&(self->conn->lock)); + Py_END_ALLOW_THREADS; + + if (retvalue < 0) + pq_complete_error(self->conn); + /* if retvalue > 0, an exception is already set */ + + return retvalue; +} + +/* lobject_close - close an existing lo */ + +RAISES_NEG static int +lobject_close_locked(lobjectObject *self) +{ + int retvalue; + + Dprintf("lobject_close_locked: conn->closed %ld", self->conn->closed); + switch (self->conn->closed) { + case 0: + /* Connection is open, go ahead */ + break; + case 1: + /* Connection is closed, return a success */ + return 0; + break; + default: + conn_set_error(self->conn, "the connection is broken"); + return -1; + break; + } + + if (self->conn->autocommit || + self->conn->mark != self->mark || + self->fd == -1) + return 0; + + retvalue = lo_close(self->conn->pgconn, self->fd); + self->fd = -1; + if (retvalue < 0) + collect_error(self->conn); + + return retvalue; +} + +RAISES_NEG int +lobject_close(lobjectObject *self) +{ + int retvalue; + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&(self->conn->lock)); + + retvalue = lobject_close_locked(self); + + pthread_mutex_unlock(&(self->conn->lock)); + Py_END_ALLOW_THREADS; + + if (retvalue < 0) + pq_complete_error(self->conn); + return retvalue; +} + +/* lobject_unlink - remove an lo from database */ + +RAISES_NEG int +lobject_unlink(lobjectObject *self) +{ + int retvalue = -1; + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&(self->conn->lock)); + + retvalue = pq_begin_locked(self->conn, &_save); + if (retvalue < 0) + goto end; + + /* first we make sure the lobject is closed and then we unlink */ + retvalue = lobject_close_locked(self); + if (retvalue < 0) + goto end; + + retvalue = lo_unlink(self->conn->pgconn, self->oid); + if (retvalue < 0) + collect_error(self->conn); + + end: + pthread_mutex_unlock(&(self->conn->lock)); + Py_END_ALLOW_THREADS; + + if (retvalue < 0) + pq_complete_error(self->conn); + return retvalue; +} + +/* lobject_write - write bytes to a lo */ + +RAISES_NEG Py_ssize_t +lobject_write(lobjectObject *self, const char *buf, size_t len) +{ + Py_ssize_t written; + + Dprintf("lobject_writing: fd = %d, len = " FORMAT_CODE_SIZE_T, + self->fd, len); + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&(self->conn->lock)); + + written = lo_write(self->conn->pgconn, self->fd, buf, len); + if (written < 0) + collect_error(self->conn); + + pthread_mutex_unlock(&(self->conn->lock)); + Py_END_ALLOW_THREADS; + + if (written < 0) + pq_complete_error(self->conn); + return written; +} + +/* lobject_read - read bytes from a lo */ + +RAISES_NEG Py_ssize_t +lobject_read(lobjectObject *self, char *buf, size_t len) +{ + Py_ssize_t n_read; + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&(self->conn->lock)); + + n_read = lo_read(self->conn->pgconn, self->fd, buf, len); + if (n_read < 0) + collect_error(self->conn); + + pthread_mutex_unlock(&(self->conn->lock)); + Py_END_ALLOW_THREADS; + + if (n_read < 0) + pq_complete_error(self->conn); + return n_read; +} + +/* lobject_seek - move the current position in the lo */ + +RAISES_NEG Py_ssize_t +lobject_seek(lobjectObject *self, Py_ssize_t pos, int whence) +{ + Py_ssize_t where; + + Dprintf("lobject_seek: fd = %d, pos = " FORMAT_CODE_PY_SSIZE_T ", whence = %d", + self->fd, pos, whence); + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&(self->conn->lock)); + +#ifdef HAVE_LO64 + if (self->conn->server_version < 90300) { + where = (Py_ssize_t)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence); + } else { + where = (Py_ssize_t)lo_lseek64(self->conn->pgconn, self->fd, pos, whence); + } +#else + where = (Py_ssize_t)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence); +#endif + Dprintf("lobject_seek: where = " FORMAT_CODE_PY_SSIZE_T, where); + if (where < 0) + collect_error(self->conn); + + pthread_mutex_unlock(&(self->conn->lock)); + Py_END_ALLOW_THREADS; + + if (where < 0) + pq_complete_error(self->conn); + return where; +} + +/* lobject_tell - tell the current position in the lo */ + +RAISES_NEG Py_ssize_t +lobject_tell(lobjectObject *self) +{ + Py_ssize_t where; + + Dprintf("lobject_tell: fd = %d", self->fd); + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&(self->conn->lock)); + +#ifdef HAVE_LO64 + if (self->conn->server_version < 90300) { + where = (Py_ssize_t)lo_tell(self->conn->pgconn, self->fd); + } else { + where = (Py_ssize_t)lo_tell64(self->conn->pgconn, self->fd); + } +#else + where = (Py_ssize_t)lo_tell(self->conn->pgconn, self->fd); +#endif + Dprintf("lobject_tell: where = " FORMAT_CODE_PY_SSIZE_T, where); + if (where < 0) + collect_error(self->conn); + + pthread_mutex_unlock(&(self->conn->lock)); + Py_END_ALLOW_THREADS; + + if (where < 0) + pq_complete_error(self->conn); + return where; +} + +/* lobject_export - export to a local file */ + +RAISES_NEG int +lobject_export(lobjectObject *self, const char *filename) +{ + int retvalue; + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&(self->conn->lock)); + + retvalue = pq_begin_locked(self->conn, &_save); + if (retvalue < 0) + goto end; + + retvalue = lo_export(self->conn->pgconn, self->oid, filename); + if (retvalue < 0) + collect_error(self->conn); + + end: + pthread_mutex_unlock(&(self->conn->lock)); + Py_END_ALLOW_THREADS; + + if (retvalue < 0) + pq_complete_error(self->conn); + return retvalue; +} + +RAISES_NEG int +lobject_truncate(lobjectObject *self, size_t len) +{ + int retvalue; + + Dprintf("lobject_truncate: fd = %d, len = " FORMAT_CODE_SIZE_T, + self->fd, len); + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&(self->conn->lock)); + +#ifdef HAVE_LO64 + if (self->conn->server_version < 90300) { + retvalue = lo_truncate(self->conn->pgconn, self->fd, len); + } else { + retvalue = lo_truncate64(self->conn->pgconn, self->fd, len); + } +#else + retvalue = lo_truncate(self->conn->pgconn, self->fd, len); +#endif + Dprintf("lobject_truncate: result = %d", retvalue); + if (retvalue < 0) + collect_error(self->conn); + + pthread_mutex_unlock(&(self->conn->lock)); + Py_END_ALLOW_THREADS; + + if (retvalue < 0) + pq_complete_error(self->conn); + return retvalue; + +} diff --git a/source-code/psycopg2/psycopg/lobject_type.c b/source-code/psycopg2/psycopg/lobject_type.c new file mode 100644 index 0000000..8376b3a --- /dev/null +++ b/source-code/psycopg2/psycopg/lobject_type.c @@ -0,0 +1,471 @@ +/* lobject_type.c - python interface to lobject objects + * + * Copyright (C) 2006-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/lobject.h" +#include "psycopg/connection.h" +#include "psycopg/microprotocols.h" +#include "psycopg/microprotocols_proto.h" +#include "psycopg/pqpath.h" + +#include + + +/** public methods **/ + +/* close method - close the lobject */ + +#define psyco_lobj_close_doc \ +"close() -- Close the lobject." + +static PyObject * +psyco_lobj_close(lobjectObject *self, PyObject *args) +{ + /* file-like objects can be closed multiple times and remember that + closing the current transaction is equivalent to close all the + opened large objects */ + if (!lobject_is_closed(self) + && !self->conn->autocommit + && self->conn->mark == self->mark) + { + Dprintf("psyco_lobj_close: closing lobject at %p", self); + if (lobject_close(self) < 0) + return NULL; + } + + Py_RETURN_NONE; +} + +/* write method - write data to the lobject */ + +#define psyco_lobj_write_doc \ +"write(str) -- Write a string to the large object." + +static PyObject * +psyco_lobj_write(lobjectObject *self, PyObject *args) +{ + char *buffer; + Py_ssize_t len; + Py_ssize_t res; + PyObject *obj; + PyObject *data = NULL; + PyObject *rv = NULL; + + if (!PyArg_ParseTuple(args, "O", &obj)) return NULL; + + EXC_IF_LOBJ_CLOSED(self); + EXC_IF_LOBJ_LEVEL0(self); + EXC_IF_LOBJ_UNMARKED(self); + + if (Bytes_Check(obj)) { + Py_INCREF(obj); + data = obj; + } + else if (PyUnicode_Check(obj)) { + if (!(data = conn_encode(self->conn, obj))) { goto exit; } + } + else { + PyErr_Format(PyExc_TypeError, + "lobject.write requires a string; got %s instead", + Py_TYPE(obj)->tp_name); + goto exit; + } + + if (-1 == Bytes_AsStringAndSize(data, &buffer, &len)) { + goto exit; + } + + if (0 > (res = lobject_write(self, buffer, (size_t)len))) { + goto exit; + } + + rv = PyInt_FromSsize_t((Py_ssize_t)res); + +exit: + Py_XDECREF(data); + return rv; +} + +/* read method - read data from the lobject */ + +#define psyco_lobj_read_doc \ +"read(size=-1) -- Read at most size bytes or to the end of the large object." + +static PyObject * +psyco_lobj_read(lobjectObject *self, PyObject *args) +{ + PyObject *res; + Py_ssize_t where, end; + Py_ssize_t size = -1; + char *buffer; + + if (!PyArg_ParseTuple(args, "|n", &size)) return NULL; + + EXC_IF_LOBJ_CLOSED(self); + EXC_IF_LOBJ_LEVEL0(self); + EXC_IF_LOBJ_UNMARKED(self); + + if (size < 0) { + if ((where = lobject_tell(self)) < 0) return NULL; + if ((end = lobject_seek(self, 0, SEEK_END)) < 0) return NULL; + if (lobject_seek(self, where, SEEK_SET) < 0) return NULL; + size = end - where; + } + + if ((buffer = PyMem_Malloc(size)) == NULL) { + PyErr_NoMemory(); + return NULL; + } + if ((size = lobject_read(self, buffer, size)) < 0) { + PyMem_Free(buffer); + return NULL; + } + + if (self->mode & LOBJECT_BINARY) { + res = Bytes_FromStringAndSize(buffer, size); + } else { + res = conn_decode(self->conn, buffer, size); + } + PyMem_Free(buffer); + + return res; +} + +/* seek method - seek in the lobject */ + +#define psyco_lobj_seek_doc \ +"seek(offset, whence=0) -- Set the lobject's current position." + +static PyObject * +psyco_lobj_seek(lobjectObject *self, PyObject *args) +{ + Py_ssize_t offset, pos=0; + int whence=0; + + if (!PyArg_ParseTuple(args, "n|i", &offset, &whence)) + return NULL; + + EXC_IF_LOBJ_CLOSED(self); + EXC_IF_LOBJ_LEVEL0(self); + EXC_IF_LOBJ_UNMARKED(self); + +#ifdef HAVE_LO64 + if ((offset < INT_MIN || offset > INT_MAX) + && self->conn->server_version < 90300) { + PyErr_Format(NotSupportedError, + "offset out of range (%ld): server version %d " + "does not support the lobject 64 API", + offset, self->conn->server_version); + return NULL; + } +#else + if (offset < INT_MIN || offset > INT_MAX) { + PyErr_Format(InterfaceError, + "offset out of range (" FORMAT_CODE_PY_SSIZE_T "): " + "this psycopg version was not built with lobject 64 API support", + offset); + return NULL; + } +#endif + + if ((pos = lobject_seek(self, offset, whence)) < 0) + return NULL; + + return PyInt_FromSsize_t(pos); +} + +/* tell method - tell current position in the lobject */ + +#define psyco_lobj_tell_doc \ +"tell() -- Return the lobject's current position." + +static PyObject * +psyco_lobj_tell(lobjectObject *self, PyObject *args) +{ + Py_ssize_t pos; + + EXC_IF_LOBJ_CLOSED(self); + EXC_IF_LOBJ_LEVEL0(self); + EXC_IF_LOBJ_UNMARKED(self); + + if ((pos = lobject_tell(self)) < 0) + return NULL; + + return PyInt_FromSsize_t(pos); +} + +/* unlink method - unlink (destroy) the lobject */ + +#define psyco_lobj_unlink_doc \ +"unlink() -- Close and then remove the lobject." + +static PyObject * +psyco_lobj_unlink(lobjectObject *self, PyObject *args) +{ + if (lobject_unlink(self) < 0) + return NULL; + + Py_RETURN_NONE; +} + +/* export method - export lobject's content to given file */ + +#define psyco_lobj_export_doc \ +"export(filename) -- Export large object to given file." + +static PyObject * +psyco_lobj_export(lobjectObject *self, PyObject *args) +{ + const char *filename; + + if (!PyArg_ParseTuple(args, "s", &filename)) + return NULL; + + EXC_IF_LOBJ_LEVEL0(self); + + if (lobject_export(self, filename) < 0) + return NULL; + + Py_RETURN_NONE; +} + + +static PyObject * +psyco_lobj_get_closed(lobjectObject *self, void *closure) +{ + return PyBool_FromLong(lobject_is_closed(self)); +} + +#define psyco_lobj_truncate_doc \ +"truncate(len=0) -- Truncate large object to given size." + +static PyObject * +psyco_lobj_truncate(lobjectObject *self, PyObject *args) +{ + Py_ssize_t len = 0; + + if (!PyArg_ParseTuple(args, "|n", &len)) + return NULL; + + EXC_IF_LOBJ_CLOSED(self); + EXC_IF_LOBJ_LEVEL0(self); + EXC_IF_LOBJ_UNMARKED(self); + +#ifdef HAVE_LO64 + if (len > INT_MAX && self->conn->server_version < 90300) { + PyErr_Format(NotSupportedError, + "len out of range (" FORMAT_CODE_PY_SSIZE_T "): " + "server version %d does not support the lobject 64 API", + len, self->conn->server_version); + return NULL; + } +#else + if (len > INT_MAX) { + PyErr_Format(InterfaceError, + "len out of range (" FORMAT_CODE_PY_SSIZE_T "): " + "this psycopg version was not built with lobject 64 API support", + len); + return NULL; + } +#endif + + if (lobject_truncate(self, len) < 0) + return NULL; + + Py_RETURN_NONE; +} + + +/** the lobject object **/ + +/* object method list */ + +static struct PyMethodDef lobjectObject_methods[] = { + {"read", (PyCFunction)psyco_lobj_read, + METH_VARARGS, psyco_lobj_read_doc}, + {"write", (PyCFunction)psyco_lobj_write, + METH_VARARGS, psyco_lobj_write_doc}, + {"seek", (PyCFunction)psyco_lobj_seek, + METH_VARARGS, psyco_lobj_seek_doc}, + {"tell", (PyCFunction)psyco_lobj_tell, + METH_NOARGS, psyco_lobj_tell_doc}, + {"close", (PyCFunction)psyco_lobj_close, + METH_NOARGS, psyco_lobj_close_doc}, + {"unlink",(PyCFunction)psyco_lobj_unlink, + METH_NOARGS, psyco_lobj_unlink_doc}, + {"export",(PyCFunction)psyco_lobj_export, + METH_VARARGS, psyco_lobj_export_doc}, + {"truncate",(PyCFunction)psyco_lobj_truncate, + METH_VARARGS, psyco_lobj_truncate_doc}, + {NULL} +}; + +/* object member list */ + +static struct PyMemberDef lobjectObject_members[] = { + {"oid", T_OID, offsetof(lobjectObject, oid), READONLY, + "The backend OID associated to this lobject."}, + {"mode", T_STRING, offsetof(lobjectObject, smode), READONLY, + "Open mode."}, + {NULL} +}; + +/* object getset list */ + +static struct PyGetSetDef lobjectObject_getsets[] = { + {"closed", (getter)psyco_lobj_get_closed, NULL, + "The if the large object is closed (no file-like methods)."}, + {NULL} +}; + +/* initialization and finalization methods */ + +static int +lobject_setup(lobjectObject *self, connectionObject *conn, + Oid oid, const char *smode, Oid new_oid, const char *new_file) +{ + Dprintf("lobject_setup: init lobject object at %p", self); + + if (conn->autocommit) { + psyco_set_error(ProgrammingError, NULL, + "can't use a lobject outside of transactions"); + return -1; + } + + Py_INCREF((PyObject*)conn); + self->conn = conn; + self->mark = conn->mark; + + self->fd = -1; + self->oid = InvalidOid; + + if (0 != lobject_open(self, conn, oid, smode, new_oid, new_file)) + return -1; + + Dprintf("lobject_setup: good lobject object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, self, Py_REFCNT(self)); + Dprintf("lobject_setup: oid = %u, fd = %d", self->oid, self->fd); + return 0; +} + +static void +lobject_dealloc(PyObject* obj) +{ + lobjectObject *self = (lobjectObject *)obj; + + if (self->conn && self->fd != -1) { + if (lobject_close(self) < 0) + PyErr_Print(); + } + Py_CLEAR(self->conn); + PyMem_Free(self->smode); + + Dprintf("lobject_dealloc: deleted lobject object at %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, obj, Py_REFCNT(obj)); + + Py_TYPE(obj)->tp_free(obj); +} + +static int +lobject_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + Oid oid = InvalidOid, new_oid = InvalidOid; + const char *smode = NULL; + const char *new_file = NULL; + PyObject *conn = NULL; + + if (!PyArg_ParseTuple(args, "O!|IzIz", + &connectionType, &conn, + &oid, &smode, &new_oid, &new_file)) + return -1; + + if (!smode) + smode = ""; + + return lobject_setup((lobjectObject *)obj, + (connectionObject *)conn, oid, smode, new_oid, new_file); +} + +static PyObject * +lobject_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + +static PyObject * +lobject_repr(lobjectObject *self) +{ + return PyString_FromFormat( + "", self, lobject_is_closed(self)); +} + + +/* object type */ + +#define lobjectType_doc \ +"A database large object." + +PyTypeObject lobjectType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2.extensions.lobject", + sizeof(lobjectObject), 0, + lobject_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + (reprfunc)lobject_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + (reprfunc)lobject_repr, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_ITER, /*tp_flags*/ + lobjectType_doc, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + lobjectObject_methods, /*tp_methods*/ + lobjectObject_members, /*tp_members*/ + lobjectObject_getsets, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + lobject_init, /*tp_init*/ + 0, /*tp_alloc*/ + lobject_new, /*tp_new*/ +}; diff --git a/source-code/psycopg2/psycopg/microprotocols.c b/source-code/psycopg2/psycopg/microprotocols.c new file mode 100644 index 0000000..cbd22da --- /dev/null +++ b/source-code/psycopg2/psycopg/microprotocols.c @@ -0,0 +1,277 @@ +/* microprotocols.c - minimalist and non-validating protocols implementation + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/microprotocols.h" +#include "psycopg/microprotocols_proto.h" +#include "psycopg/cursor.h" +#include "psycopg/connection.h" + + +/** the adapters registry **/ + +PyObject *psyco_adapters; + +/* microprotocols_init - initialize the adapters dictionary */ + +RAISES_NEG int +microprotocols_init(PyObject *module) +{ + /* create adapters dictionary and put it in module namespace */ + if (!(psyco_adapters = PyDict_New())) { + return -1; + } + + Py_INCREF(psyco_adapters); + if (0 > PyModule_AddObject(module, "adapters", psyco_adapters)) { + Py_DECREF(psyco_adapters); + return -1; + } + + return 0; +} + + +/* microprotocols_add - add a reverse type-caster to the dictionary + * + * Return 0 on success, else -1 and set an exception. + */ +RAISES_NEG int +microprotocols_add(PyTypeObject *type, PyObject *proto, PyObject *cast) +{ + PyObject *key = NULL; + int rv = -1; + + if (proto == NULL) proto = (PyObject*)&isqlquoteType; + + if (!(key = PyTuple_Pack(2, (PyObject*)type, proto))) { goto exit; } + if (0 != PyDict_SetItem(psyco_adapters, key, cast)) { goto exit; } + + rv = 0; + +exit: + Py_XDECREF(key); + return rv; +} + +/* Check if one of `obj` superclasses has an adapter for `proto`. + * + * If it does, return a *borrowed reference* to the adapter, else to None. + */ +BORROWED static PyObject * +_get_superclass_adapter(PyObject *obj, PyObject *proto) +{ + PyTypeObject *type; + PyObject *mro, *st; + PyObject *key, *adapter; + Py_ssize_t i, ii; + + type = Py_TYPE(obj); + if (!(type->tp_mro)) { + /* has no mro */ + return Py_None; + } + + /* Walk the mro from the most specific subclass. */ + mro = type->tp_mro; + for (i = 1, ii = PyTuple_GET_SIZE(mro); i < ii; ++i) { + st = PyTuple_GET_ITEM(mro, i); + if (!(key = PyTuple_Pack(2, st, proto))) { return NULL; } + adapter = PyDict_GetItem(psyco_adapters, key); + Py_DECREF(key); + + if (adapter) { + Dprintf( + "microprotocols_adapt: using '%s' adapter to adapt '%s'", + ((PyTypeObject *)st)->tp_name, type->tp_name); + + /* register this adapter as good for the subclass too, + * so that the next time it will be found in the fast path */ + + /* Well, no, maybe this is not a good idea. + * It would become a leak in case of dynamic + * classes generated in a loop (think namedtuples). */ + + /* key = PyTuple_Pack(2, (PyObject*)type, proto); + * PyDict_SetItem(psyco_adapters, key, adapter); + * Py_DECREF(key); + */ + return adapter; + } + } + return Py_None; +} + + +/* microprotocols_adapt - adapt an object to the built-in protocol */ + +PyObject * +microprotocols_adapt(PyObject *obj, PyObject *proto, PyObject *alt) +{ + PyObject *adapter, *adapted, *key, *meth; + char buffer[256]; + + /* we don't check for exact type conformance as specified in PEP 246 + because the ISQLQuote type is abstract and there is no way to get a + quotable object to be its instance */ + + Dprintf("microprotocols_adapt: trying to adapt %s", + Py_TYPE(obj)->tp_name); + + /* look for an adapter in the registry */ + if (!(key = PyTuple_Pack(2, Py_TYPE(obj), proto))) { return NULL; } + adapter = PyDict_GetItem(psyco_adapters, key); + Py_DECREF(key); + if (adapter) { + adapted = PyObject_CallFunctionObjArgs(adapter, obj, NULL); + return adapted; + } + + /* try to have the protocol adapt this object*/ + if ((meth = PyObject_GetAttrString(proto, "__adapt__"))) { + adapted = PyObject_CallFunctionObjArgs(meth, obj, NULL); + Py_DECREF(meth); + if (adapted && adapted != Py_None) return adapted; + Py_XDECREF(adapted); + if (PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_TypeError)) { + PyErr_Clear(); + } else { + return NULL; + } + } + } + else { + /* proto.__adapt__ not found. */ + PyErr_Clear(); + } + + /* then try to have the object adapt itself */ + if ((meth = PyObject_GetAttrString(obj, "__conform__"))) { + adapted = PyObject_CallFunctionObjArgs(meth, proto, NULL); + Py_DECREF(meth); + if (adapted && adapted != Py_None) return adapted; + Py_XDECREF(adapted); + if (PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_TypeError)) { + PyErr_Clear(); + } else { + return NULL; + } + } + } + else { + /* obj.__conform__ not found. */ + PyErr_Clear(); + } + + /* Finally check if a superclass can be adapted and use the same adapter. */ + if (!(adapter = _get_superclass_adapter(obj, proto))) { + return NULL; + } + if (Py_None != adapter) { + adapted = PyObject_CallFunctionObjArgs(adapter, obj, NULL); + return adapted; + } + + /* else set the right exception and return NULL */ + PyOS_snprintf(buffer, 255, "can't adapt type '%s'", + Py_TYPE(obj)->tp_name); + psyco_set_error(ProgrammingError, NULL, buffer); + return NULL; +} + +/* microprotocol_getquoted - utility function that adapt and call getquoted. + * + * Return a bytes string, NULL on error. + */ + +PyObject * +microprotocol_getquoted(PyObject *obj, connectionObject *conn) +{ + PyObject *res = NULL; + PyObject *prepare = NULL; + PyObject *adapted; + + if (!(adapted = microprotocols_adapt(obj, (PyObject*)&isqlquoteType, NULL))) { + goto exit; + } + + Dprintf("microprotocol_getquoted: adapted to %s", + Py_TYPE(adapted)->tp_name); + + /* if requested prepare the object passing it the connection */ + if (conn) { + if ((prepare = PyObject_GetAttrString(adapted, "prepare"))) { + res = PyObject_CallFunctionObjArgs( + prepare, (PyObject *)conn, NULL); + if (res) { + Py_DECREF(res); + res = NULL; + } else { + goto exit; + } + } + else { + /* adapted.prepare not found */ + PyErr_Clear(); + } + } + + /* call the getquoted method on adapted (that should exist because we + adapted to the right protocol) */ + res = PyObject_CallMethod(adapted, "getquoted", NULL); + + /* Convert to bytes. */ + if (res && PyUnicode_CheckExact(res)) { + PyObject *b; + b = conn_encode(conn, res); + Py_DECREF(res); + res = b; + } + +exit: + Py_XDECREF(adapted); + Py_XDECREF(prepare); + + /* we return res with one extra reference, the caller shall free it */ + return res; +} + + +/** module-level functions **/ + +PyObject * +psyco_microprotocols_adapt(cursorObject *self, PyObject *args) +{ + PyObject *obj, *alt = NULL; + PyObject *proto = (PyObject*)&isqlquoteType; + + if (!PyArg_ParseTuple(args, "O|OO", &obj, &proto, &alt)) return NULL; + return microprotocols_adapt(obj, proto, alt); +} diff --git a/source-code/psycopg2/psycopg/microprotocols.h b/source-code/psycopg2/psycopg/microprotocols.h new file mode 100644 index 0000000..434e9e9 --- /dev/null +++ b/source-code/psycopg2/psycopg/microprotocols.h @@ -0,0 +1,64 @@ +/* microprotocols.c - definitions for minimalist and non-validating protocols + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_MICROPROTOCOLS_H +#define PSYCOPG_MICROPROTOCOLS_H 1 + +#include "psycopg/connection.h" +#include "psycopg/cursor.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** adapters registry **/ + +extern HIDDEN PyObject *psyco_adapters; + +/** the names of the three mandatory methods **/ + +#define MICROPROTOCOLS_GETQUOTED_NAME "getquoted" +#define MICROPROTOCOLS_GETSTRING_NAME "getstring" +#define MICROPROTOCOLS_GETBINARY_NAME "getbinary" + +/** exported functions **/ + +/* used by module.c to init the microprotocols system */ +HIDDEN RAISES_NEG int microprotocols_init(PyObject *dict); +HIDDEN RAISES_NEG int microprotocols_add( + PyTypeObject *type, PyObject *proto, PyObject *cast); + +HIDDEN PyObject *microprotocols_adapt( + PyObject *obj, PyObject *proto, PyObject *alt); +HIDDEN PyObject *microprotocol_getquoted( + PyObject *obj, connectionObject *conn); + +HIDDEN PyObject * + psyco_microprotocols_adapt(cursorObject *self, PyObject *args); +#define psyco_microprotocols_adapt_doc \ + "adapt(obj, protocol, alternate) -> object -- adapt obj to given protocol" + +#endif /* !defined(PSYCOPG_MICROPROTOCOLS_H) */ diff --git a/source-code/psycopg2/psycopg/microprotocols_proto.c b/source-code/psycopg2/psycopg/microprotocols_proto.c new file mode 100644 index 0000000..d32250e --- /dev/null +++ b/source-code/psycopg2/psycopg/microprotocols_proto.c @@ -0,0 +1,180 @@ +/* microprotocol_proto.c - psycopg protocols + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/microprotocols_proto.h" + +#include + + +/** void protocol implementation **/ + + +/* getquoted - return quoted representation for object */ + +#define isqlquote_getquoted_doc \ +"getquoted() -- return SQL-quoted representation of this object" + +static PyObject * +isqlquote_getquoted(isqlquoteObject *self, PyObject *args) +{ + Py_RETURN_NONE; +} + +/* getbinary - return quoted representation for object */ + +#define isqlquote_getbinary_doc \ +"getbinary() -- return SQL-quoted binary representation of this object" + +static PyObject * +isqlquote_getbinary(isqlquoteObject *self, PyObject *args) +{ + Py_RETURN_NONE; +} + +/* getbuffer - return quoted representation for object */ + +#define isqlquote_getbuffer_doc \ +"getbuffer() -- return this object" + +static PyObject * +isqlquote_getbuffer(isqlquoteObject *self, PyObject *args) +{ + Py_RETURN_NONE; +} + + + +/** the ISQLQuote object **/ + + +/* object method list */ + +static struct PyMethodDef isqlquoteObject_methods[] = { + {"getquoted", (PyCFunction)isqlquote_getquoted, + METH_NOARGS, isqlquote_getquoted_doc}, + {"getbinary", (PyCFunction)isqlquote_getbinary, + METH_NOARGS, isqlquote_getbinary_doc}, + {"getbuffer", (PyCFunction)isqlquote_getbuffer, + METH_NOARGS, isqlquote_getbuffer_doc}, + {NULL} +}; + +/* object member list */ + +static struct PyMemberDef isqlquoteObject_members[] = { + /* DBAPI-2.0 extensions (exception objects) */ + {"_wrapped", T_OBJECT, offsetof(isqlquoteObject, wrapped), READONLY}, + {NULL} +}; + +/* initialization and finalization methods */ + +static int +isqlquote_setup(isqlquoteObject *self, PyObject *wrapped) +{ + self->wrapped = wrapped; + Py_INCREF(wrapped); + + return 0; +} + +static void +isqlquote_dealloc(PyObject* obj) +{ + isqlquoteObject *self = (isqlquoteObject *)obj; + + Py_XDECREF(self->wrapped); + + Py_TYPE(obj)->tp_free(obj); +} + +static int +isqlquote_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + PyObject *wrapped = NULL; + + if (!PyArg_ParseTuple(args, "O", &wrapped)) + return -1; + + return isqlquote_setup((isqlquoteObject *)obj, wrapped); +} + +static PyObject * +isqlquote_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + + +/* object type */ + +#define isqlquoteType_doc \ +"Abstract ISQLQuote protocol\n\n" \ +"An object conform to this protocol should expose a ``getquoted()`` method\n" \ +"returning the SQL representation of the object.\n\n" + +PyTypeObject isqlquoteType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2.extensions.ISQLQuote", + sizeof(isqlquoteObject), 0, + isqlquote_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + isqlquoteType_doc, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + isqlquoteObject_methods, /*tp_methods*/ + isqlquoteObject_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + isqlquote_init, /*tp_init*/ + 0, /*tp_alloc*/ + isqlquote_new, /*tp_new*/ +}; diff --git a/source-code/psycopg2/psycopg/microprotocols_proto.h b/source-code/psycopg2/psycopg/microprotocols_proto.h new file mode 100644 index 0000000..8d47d12 --- /dev/null +++ b/source-code/psycopg2/psycopg/microprotocols_proto.h @@ -0,0 +1,47 @@ +/* microporotocols_proto.h - definition for psycopg's protocols + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_ISQLQUOTE_H +#define PSYCOPG_ISQLQUOTE_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +extern HIDDEN PyTypeObject isqlquoteType; + +typedef struct { + PyObject_HEAD + + PyObject *wrapped; + +} isqlquoteObject; + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_ISQLQUOTE_H) */ diff --git a/source-code/psycopg2/psycopg/notify.h b/source-code/psycopg2/psycopg/notify.h new file mode 100644 index 0000000..2641db8 --- /dev/null +++ b/source-code/psycopg2/psycopg/notify.h @@ -0,0 +1,41 @@ +/* notify.h - definition for the psycopg Notify type + * + * Copyright (C) 2010-2019 Daniele Varrazzo + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_NOTIFY_H +#define PSYCOPG_NOTIFY_H 1 + +extern HIDDEN PyTypeObject notifyType; + +typedef struct { + PyObject_HEAD + + PyObject *pid; + PyObject *channel; + PyObject *payload; + +} notifyObject; + +#endif /* PSYCOPG_NOTIFY_H */ diff --git a/source-code/psycopg2/psycopg/notify_type.c b/source-code/psycopg2/psycopg/notify_type.c new file mode 100644 index 0000000..44b66b5 --- /dev/null +++ b/source-code/psycopg2/psycopg/notify_type.c @@ -0,0 +1,298 @@ +/* notify_type.c - python interface to Notify objects + * + * Copyright (C) 2010-2019 Daniele Varrazzo + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/notify.h" + + +static const char notify_doc[] = + "A notification received from the backend.\n\n" + "`!Notify` instances are made available upon reception on the\n" + "`~connection.notifies` member of the listening connection. The object\n" + "can be also accessed as a 2 items tuple returning the members\n" + ":samp:`({pid},{channel})` for backward compatibility.\n\n" + "See :ref:`async-notify` for details."; + +static const char pid_doc[] = + "The ID of the backend process that sent the notification.\n\n" + "Note: if the sending session was handled by Psycopg, you can use\n" + "`~connection.info.backend_pid` to know its PID."; + +static const char channel_doc[] = + "The name of the channel to which the notification was sent."; + +static const char payload_doc[] = + "The payload message of the notification.\n\n" + "Attaching a payload to a notification is only available since\n" + "PostgreSQL 9.0: for notifications received from previous versions\n" + "of the server this member is always the empty string."; + +static PyMemberDef notify_members[] = { + { "pid", T_OBJECT, offsetof(notifyObject, pid), READONLY, (char *)pid_doc }, + { "channel", T_OBJECT, offsetof(notifyObject, channel), READONLY, (char *)channel_doc }, + { "payload", T_OBJECT, offsetof(notifyObject, payload), READONLY, (char *)payload_doc }, + { NULL } +}; + +static PyObject * +notify_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + return type->tp_alloc(type, 0); +} + +static int +notify_init(notifyObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"pid", "channel", "payload", NULL}; + PyObject *pid = NULL, *channel = NULL, *payload = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|O", kwlist, + &pid, &channel, &payload)) { + return -1; + } + + if (!payload) { + payload = Text_FromUTF8(""); + } + + Py_INCREF(pid); + self->pid = pid; + + Py_INCREF(channel); + self->channel = channel; + + Py_INCREF(payload); + self->payload = payload; + + return 0; +} + +static void +notify_dealloc(notifyObject *self) +{ + Py_CLEAR(self->pid); + Py_CLEAR(self->channel); + Py_CLEAR(self->payload); + + Py_TYPE(self)->tp_free((PyObject *)self); +} + + +/* Convert a notify into a 2 or 3 items tuple. */ +static PyObject * +notify_astuple(notifyObject *self, int with_payload) +{ + PyObject *tself; + if (!(tself = PyTuple_New(with_payload ? 3 : 2))) { return NULL; } + + Py_INCREF(self->pid); + PyTuple_SET_ITEM(tself, 0, self->pid); + + Py_INCREF(self->channel); + PyTuple_SET_ITEM(tself, 1, self->channel); + + if (with_payload) { + Py_INCREF(self->payload); + PyTuple_SET_ITEM(tself, 2, self->payload); + } + + return tself; +} + +/* note on Notify-tuple comparison. + * + * Such a comparison is required otherwise a check n == (pid, channel) + * would fail. We also want to compare two notifies, and the obvious meaning is + * "check that all the attributes are equal". Unfortunately this leads to an + * inconsistent situation: + * Notify(pid, channel, payload1) + * == (pid, channel) + * == Notify(pid, channel, payload2) + * even when payload1 != payload2. We can probably live with that, but hashing + * makes things worse: hashability is a desirable property for a Notify, and + * to maintain compatibility we should put a notify object in the same bucket + * of a 2-item tuples... but we can't put all the payloads with the same + * (pid, channel) in the same bucket: it would be an extremely poor hash. + * So we maintain compatibility in the sense that notify without payload + * behave as 2-item tuples in term of hashability, but if a payload is present + * the (pid, channel) pair is no more equivalent as dict key to the Notify. + */ +static PyObject * +notify_richcompare(notifyObject *self, PyObject *other, int op) +{ + PyObject *rv = NULL; + PyObject *tself = NULL; + PyObject *tother = NULL; + + if (Py_TYPE(other) == ¬ifyType) { + if (!(tself = notify_astuple(self, 1))) { goto exit; } + if (!(tother = notify_astuple((notifyObject *)other, 1))) { goto exit; } + rv = PyObject_RichCompare(tself, tother, op); + } + else if (PyTuple_Check(other)) { + if (!(tself = notify_astuple(self, 0))) { goto exit; } + rv = PyObject_RichCompare(tself, other, op); + } + else { + Py_INCREF(Py_False); + rv = Py_False; + } + +exit: + Py_XDECREF(tself); + Py_XDECREF(tother); + return rv; +} + + +static Py_hash_t +notify_hash(notifyObject *self) +{ + Py_hash_t rv = -1L; + PyObject *tself = NULL; + + /* if self == a tuple, then their hashes are the same. */ + int has_payload = PyObject_IsTrue(self->payload); + if (!(tself = notify_astuple(self, has_payload))) { goto exit; } + rv = PyObject_Hash(tself); + +exit: + Py_XDECREF(tself); + return rv; +} + + +static PyObject* +notify_repr(notifyObject *self) +{ + PyObject *rv = NULL; + PyObject *format = NULL; + PyObject *args = NULL; + + if (!(format = Text_FromUTF8("Notify(%r, %r, %r)"))) { + goto exit; + } + + if (!(args = PyTuple_New(3))) { goto exit; } + Py_INCREF(self->pid); + PyTuple_SET_ITEM(args, 0, self->pid); + Py_INCREF(self->channel); + PyTuple_SET_ITEM(args, 1, self->channel); + Py_INCREF(self->payload); + PyTuple_SET_ITEM(args, 2, self->payload); + + rv = Text_Format(format, args); + +exit: + Py_XDECREF(args); + Py_XDECREF(format); + + return rv; +} + +/* Notify can be accessed as a 2 items tuple for backward compatibility */ + +static Py_ssize_t +notify_len(notifyObject *self) +{ + return 2; +} + +static PyObject * +notify_getitem(notifyObject *self, Py_ssize_t item) +{ + if (item < 0) + item += 2; + + switch (item) { + case 0: + Py_INCREF(self->pid); + return self->pid; + case 1: + Py_INCREF(self->channel); + return self->channel; + default: + PyErr_SetString(PyExc_IndexError, "index out of range"); + return NULL; + } +} + +static PySequenceMethods notify_sequence = { + (lenfunc)notify_len, /* sq_length */ + 0, /* sq_concat */ + 0, /* sq_repeat */ + (ssizeargfunc)notify_getitem, /* sq_item */ + 0, /* sq_slice */ + 0, /* sq_ass_item */ + 0, /* sq_ass_slice */ + 0, /* sq_contains */ + 0, /* sq_inplace_concat */ + 0, /* sq_inplace_repeat */ +}; + + +PyTypeObject notifyType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2.extensions.Notify", + sizeof(notifyObject), 0, + (destructor)notify_dealloc, /* tp_dealloc */ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + (reprfunc)notify_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + ¬ify_sequence, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + (hashfunc)notify_hash, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + /* Notify is not GC as it only has string attributes */ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + notify_doc, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + (richcmpfunc)notify_richcompare, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ + notify_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + (initproc)notify_init, /*tp_init*/ + 0, /*tp_alloc*/ + notify_new, /*tp_new*/ +}; diff --git a/source-code/psycopg2/psycopg/pgtypes.h b/source-code/psycopg2/psycopg/pgtypes.h new file mode 100644 index 0000000..1fdbda9 --- /dev/null +++ b/source-code/psycopg2/psycopg/pgtypes.h @@ -0,0 +1,65 @@ +#define BOOLOID 16 +#define BYTEAOID 17 +#define CHAROID 18 +#define NAMEOID 19 +#define INT8OID 20 +#define INT2OID 21 +#define INT2VECTOROID 22 +#define INT4OID 23 +#define REGPROCOID 24 +#define TEXTOID 25 +#define OIDOID 26 +#define TIDOID 27 +#define XIDOID 28 +#define CIDOID 29 +#define OIDVECTOROID 30 +#define PG_TYPE_RELTYPE_OID 71 +#define PG_ATTRIBUTE_RELTYPE_OID 75 +#define PG_PROC_RELTYPE_OID 81 +#define PG_CLASS_RELTYPE_OID 83 +#define POINTOID 600 +#define LSEGOID 601 +#define PATHOID 602 +#define BOXOID 603 +#define POLYGONOID 604 +#define LINEOID 628 +#define FLOAT4OID 700 +#define FLOAT8OID 701 +#define ABSTIMEOID 702 +#define RELTIMEOID 703 +#define TINTERVALOID 704 +#define UNKNOWNOID 705 +#define CIRCLEOID 718 +#define CASHOID 790 +#define MACADDROID 829 +#define INETOID 869 +#define CIDROID 650 +#define INT4ARRAYOID 1007 +#define ACLITEMOID 1033 +#define BPCHAROID 1042 +#define VARCHAROID 1043 +#define DATEOID 1082 +#define TIMEOID 1083 +#define TIMESTAMPOID 1114 +#define TIMESTAMPTZOID 1184 +#define INTERVALOID 1186 +#define TIMETZOID 1266 +#define BITOID 1560 +#define VARBITOID 1562 +#define NUMERICOID 1700 +#define REFCURSOROID 1790 +#define REGPROCEDUREOID 2202 +#define REGOPEROID 2203 +#define REGOPERATOROID 2204 +#define REGCLASSOID 2205 +#define REGTYPEOID 2206 +#define RECORDOID 2249 +#define CSTRINGOID 2275 +#define ANYOID 2276 +#define ANYARRAYOID 2277 +#define VOIDOID 2278 +#define TRIGGEROID 2279 +#define LANGUAGE_HANDLEROID 2280 +#define INTERNALOID 2281 +#define OPAQUEOID 2282 +#define ANYELEMENTOID 2283 diff --git a/source-code/psycopg2/psycopg/pqpath.c b/source-code/psycopg2/psycopg/pqpath.c new file mode 100644 index 0000000..83ab91f --- /dev/null +++ b/source-code/psycopg2/psycopg/pqpath.c @@ -0,0 +1,1835 @@ +/* pqpath.c - single path into libpq + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +/* IMPORTANT NOTE: no function in this file do its own connection locking + except for pg_execute and pq_fetch (that are somehow high-level). This means + that all the other functions should be called while holding a lock to the + connection. +*/ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/pqpath.h" +#include "psycopg/connection.h" +#include "psycopg/cursor.h" +#include "psycopg/replication_cursor.h" +#include "psycopg/replication_message.h" +#include "psycopg/green.h" +#include "psycopg/typecast.h" +#include "psycopg/pgtypes.h" +#include "psycopg/error.h" +#include "psycopg/column.h" + +#include "psycopg/libpq_support.h" +#include "libpq-fe.h" + +#include +#ifdef _WIN32 +/* select() */ +#include +/* gettimeofday() */ +#include "win32_support.h" +#elif defined(__sun) && defined(__SVR4) +#include "solaris_support.h" +#elif defined(_AIX) +#include "aix_support.h" +#else +#include +#endif + +extern HIDDEN PyObject *psyco_DescriptionType; +extern HIDDEN const char *srv_isolevels[]; +extern HIDDEN const char *srv_readonly[]; +extern HIDDEN const char *srv_deferrable[]; + +/* Strip off the severity from a Postgres error message. */ +static const char * +strip_severity(const char *msg) +{ + if (!msg) + return NULL; + + if (strlen(msg) > 8 && (!strncmp(msg, "ERROR: ", 8) || + !strncmp(msg, "FATAL: ", 8) || + !strncmp(msg, "PANIC: ", 8))) + return &msg[8]; + else + return msg; +} + + +/* pq_raise - raise a python exception of the right kind + + This function should be called while holding the GIL. + + The function passes the ownership of the pgres to the returned exception, + where the pgres was the explicit argument or taken from the cursor. + So, after calling it curs->pgres will be set to null */ + +RAISES static void +pq_raise(connectionObject *conn, cursorObject *curs, PGresult **pgres) +{ + PyObject *exc = NULL; + const char *err = NULL; + const char *err2 = NULL; + const char *code = NULL; + PyObject *pyerr = NULL; + PyObject *pgerror = NULL, *pgcode = NULL; + + if (conn == NULL) { + PyErr_SetString(DatabaseError, + "psycopg went psychotic and raised a null error"); + return; + } + + /* if the connection has somehow been broken, we mark the connection + object as closed but requiring cleanup */ + if (conn->pgconn != NULL && PQstatus(conn->pgconn) == CONNECTION_BAD) { + conn->closed = 2; + exc = OperationalError; + } + + if (pgres == NULL && curs != NULL) + pgres = &curs->pgres; + + if (pgres && *pgres) { + err = PQresultErrorMessage(*pgres); + if (err != NULL) { + Dprintf("pq_raise: PQresultErrorMessage: err=%s", err); + code = PQresultErrorField(*pgres, PG_DIAG_SQLSTATE); + } + } + if (err == NULL) { + err = PQerrorMessage(conn->pgconn); + Dprintf("pq_raise: PQerrorMessage: err=%s", err); + } + + /* if the is no error message we probably called pq_raise without reason: + we need to set an exception anyway because the caller will probably + raise and a meaningful message is better than an empty one. + Note: it can happen without it being our error: see ticket #82 */ + if (err == NULL || err[0] == '\0') { + PyErr_Format(DatabaseError, + "error with status %s and no message from the libpq", + PQresStatus(pgres == NULL ? + PQstatus(conn->pgconn) : PQresultStatus(*pgres))); + return; + } + + /* Analyze the message and try to deduce the right exception kind + (only if we got the SQLSTATE from the pgres, obviously) */ + if (code != NULL) { + exc = exception_from_sqlstate(code); + } + else if (exc == NULL) { + /* Fallback if there is no exception code (unless we already + determined that the connection was closed). */ + exc = DatabaseError; + } + + /* try to remove the initial "ERROR: " part from the postgresql error */ + err2 = strip_severity(err); + Dprintf("pq_raise: err2=%s", err2); + + /* decode now the details of the error, because after psyco_set_error + * decoding will fail. + */ + if (!(pgerror = conn_text_from_chars(conn, err))) { + /* we can't really handle an exception while handling this error + * so just print it. */ + PyErr_Print(); + PyErr_Clear(); + } + + if (!(pgcode = conn_text_from_chars(conn, code))) { + PyErr_Print(); + PyErr_Clear(); + } + + pyerr = psyco_set_error(exc, curs, err2); + + if (pyerr && PyObject_TypeCheck(pyerr, &errorType)) { + errorObject *perr = (errorObject *)pyerr; + + Py_CLEAR(perr->pydecoder); + Py_XINCREF(conn->pydecoder); + perr->pydecoder = conn->pydecoder; + + Py_CLEAR(perr->pgerror); + perr->pgerror = pgerror; + pgerror = NULL; + + Py_CLEAR(perr->pgcode); + perr->pgcode = pgcode; + pgcode = NULL; + + CLEARPGRES(perr->pgres); + if (pgres && *pgres) { + perr->pgres = *pgres; + *pgres = NULL; + } + } + + Py_XDECREF(pgerror); + Py_XDECREF(pgcode); +} + +/* pq_clear_async - clear the effects of a previous async query + + note that this function does block because it needs to wait for the full + result sets of the previous query to clear them. + + this function does not call any Py_*_ALLOW_THREADS macros */ + +void +pq_clear_async(connectionObject *conn) +{ + PGresult *pgres; + + /* this will get all pending results (if the submitted query consisted of + many parts, i.e. "select 1; select 2", there will be many) and also + finalize asynchronous processing so the connection will be ready to + accept another query */ + + while ((pgres = PQgetResult(conn->pgconn))) { + Dprintf("pq_clear_async: clearing PGresult at %p", pgres); + PQclear(pgres); + } + Py_CLEAR(conn->async_cursor); +} + + +/* pq_set_non_blocking - set the nonblocking status on a connection. + + Accepted arg values are 1 (nonblocking) and 0 (blocking). + + Return 0 if everything ok, else < 0 and set an exception. + */ +RAISES_NEG int +pq_set_non_blocking(connectionObject *conn, int arg) +{ + int ret = PQsetnonblocking(conn->pgconn, arg); + if (0 != ret) { + Dprintf("PQsetnonblocking(%d) FAILED", arg); + PyErr_SetString(OperationalError, "PQsetnonblocking() failed"); + ret = -1; + } + return ret; +} + + +/* pg_execute_command_locked - execute a no-result query on a locked connection. + + This function should only be called on a locked connection without + holding the global interpreter lock. + + On error, -1 is returned, and the conn->pgres will hold the + relevant result structure. + + The tstate parameter should be the pointer of the _save variable created by + Py_BEGIN_ALLOW_THREADS: this enables the function to acquire and release + again the GIL if needed, i.e. if a Python wait callback must be invoked. + */ +int +pq_execute_command_locked( + connectionObject *conn, const char *query, PyThreadState **tstate) +{ + int pgstatus, retvalue = -1; + Dprintf("pq_execute_command_locked: pgconn = %p, query = %s", + conn->pgconn, query); + + if (!psyco_green()) { + conn_set_result(conn, PQexec(conn->pgconn, query)); + } else { + PyEval_RestoreThread(*tstate); + conn_set_result(conn, psyco_exec_green(conn, query)); + *tstate = PyEval_SaveThread(); + } + if (conn->pgres == NULL) { + Dprintf("pq_execute_command_locked: PQexec returned NULL"); + PyEval_RestoreThread(*tstate); + if (!PyErr_Occurred()) { + conn_set_error(conn, PQerrorMessage(conn->pgconn)); + } + *tstate = PyEval_SaveThread(); + goto cleanup; + } + + pgstatus = PQresultStatus(conn->pgres); + if (pgstatus != PGRES_COMMAND_OK ) { + Dprintf("pq_execute_command_locked: result was not COMMAND_OK (%d)", + pgstatus); + goto cleanup; + } + + retvalue = 0; + CLEARPGRES(conn->pgres); + +cleanup: + return retvalue; +} + +/* pq_complete_error: handle an error from pq_execute_command_locked() + + If pq_execute_command_locked() returns -1, this function should be + called to convert the result to a Python exception. + + This function should be called while holding the global interpreter + lock. + */ +RAISES void +pq_complete_error(connectionObject *conn) +{ + Dprintf("pq_complete_error: pgconn = %p, error = %s", + conn->pgconn, conn->error); + if (conn->pgres) { + pq_raise(conn, NULL, &conn->pgres); + /* now conn->pgres is null */ + } + else { + if (conn->error) { + PyErr_SetString(OperationalError, conn->error); + } else if (PyErr_Occurred()) { + /* There was a Python error (e.g. in the callback). Don't clobber + * it with an unknown exception. (see #410) */ + Dprintf("pq_complete_error: forwarding Python exception"); + } else { + PyErr_SetString(OperationalError, "unknown error"); + } + /* Trivia: with a broken socket connection PQexec returns NULL, so we + * end up here. With a TCP connection we get a pgres with an error + * instead, and the connection gets closed in the pq_raise call above + * (see ticket #196) + */ + if (CONNECTION_BAD == PQstatus(conn->pgconn)) { + conn->closed = 2; + } + } + conn_set_error(conn, NULL); +} + + +/* pq_begin_locked - begin a transaction, if necessary + + This function should only be called on a locked connection without + holding the global interpreter lock. + + On error, -1 is returned, and the conn->pgres argument will hold the + relevant result structure. + */ +int +pq_begin_locked(connectionObject *conn, PyThreadState **tstate) +{ + const size_t bufsize = 256; + char buf[256]; /* buf size must be same as bufsize */ + int result; + + Dprintf("pq_begin_locked: pgconn = %p, %d, status = %d", + conn->pgconn, conn->autocommit, conn->status); + + if (conn->status != CONN_STATUS_READY) { + Dprintf("pq_begin_locked: transaction in progress"); + return 0; + } + + if (conn->autocommit && !conn->entered) { + Dprintf("pq_begin_locked: autocommit and no with block"); + return 0; + } + + if (conn->isolevel == ISOLATION_LEVEL_DEFAULT + && conn->readonly == STATE_DEFAULT + && conn->deferrable == STATE_DEFAULT) { + strcpy(buf, "BEGIN"); + } + else { + snprintf(buf, bufsize, + conn->server_version >= 80000 ? + "BEGIN%s%s%s%s" : "BEGIN;SET TRANSACTION%s%s%s%s", + (conn->isolevel >= 1 && conn->isolevel <= 4) + ? " ISOLATION LEVEL " : "", + (conn->isolevel >= 1 && conn->isolevel <= 4) + ? srv_isolevels[conn->isolevel] : "", + srv_readonly[conn->readonly], + srv_deferrable[conn->deferrable]); + } + + result = pq_execute_command_locked(conn, buf, tstate); + if (result == 0) + conn->status = CONN_STATUS_BEGIN; + + return result; +} + +/* pq_commit - send an END, if necessary + + This function should be called while holding the global interpreter + lock. +*/ + +int +pq_commit(connectionObject *conn) +{ + int retvalue = -1; + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&conn->lock); + + Dprintf("pq_commit: pgconn = %p, status = %d", + conn->pgconn, conn->status); + + if (conn->status != CONN_STATUS_BEGIN) { + Dprintf("pq_commit: no transaction to commit"); + retvalue = 0; + } + else { + conn->mark += 1; + retvalue = pq_execute_command_locked(conn, "COMMIT", &_save); + } + + Py_BLOCK_THREADS; + conn_notice_process(conn); + Py_UNBLOCK_THREADS; + + /* Even if an error occurred, the connection will be rolled back, + so we unconditionally set the connection status here. */ + conn->status = CONN_STATUS_READY; + + pthread_mutex_unlock(&conn->lock); + Py_END_ALLOW_THREADS; + + if (retvalue < 0) + pq_complete_error(conn); + + return retvalue; +} + +RAISES_NEG int +pq_abort_locked(connectionObject *conn, PyThreadState **tstate) +{ + int retvalue = -1; + + Dprintf("pq_abort_locked: pgconn = %p, status = %d", + conn->pgconn, conn->status); + + if (conn->status != CONN_STATUS_BEGIN) { + Dprintf("pq_abort_locked: no transaction to abort"); + return 0; + } + + conn->mark += 1; + retvalue = pq_execute_command_locked(conn, "ROLLBACK", tstate); + if (retvalue == 0) + conn->status = CONN_STATUS_READY; + + return retvalue; +} + +/* pq_abort - send an ABORT, if necessary + + This function should be called while holding the global interpreter + lock. */ + +RAISES_NEG int +pq_abort(connectionObject *conn) +{ + int retvalue = -1; + + Dprintf("pq_abort: pgconn = %p, autocommit = %d, status = %d", + conn->pgconn, conn->autocommit, conn->status); + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&conn->lock); + + retvalue = pq_abort_locked(conn, &_save); + + Py_BLOCK_THREADS; + conn_notice_process(conn); + Py_UNBLOCK_THREADS; + + pthread_mutex_unlock(&conn->lock); + Py_END_ALLOW_THREADS; + + if (retvalue < 0) + pq_complete_error(conn); + + return retvalue; +} + +/* pq_reset - reset the connection + + This function should be called while holding the global interpreter + lock. + + The _locked version of this function should be called on a locked + connection without holding the global interpreter lock. +*/ + +RAISES_NEG int +pq_reset_locked(connectionObject *conn, PyThreadState **tstate) +{ + int retvalue = -1; + + Dprintf("pq_reset_locked: pgconn = %p, status = %d", + conn->pgconn, conn->status); + + conn->mark += 1; + + if (conn->status == CONN_STATUS_BEGIN) { + retvalue = pq_execute_command_locked(conn, "ABORT", tstate); + if (retvalue != 0) return retvalue; + } + + if (conn->server_version >= 80300) { + retvalue = pq_execute_command_locked(conn, "DISCARD ALL", tstate); + if (retvalue != 0) return retvalue; + } + else { + retvalue = pq_execute_command_locked(conn, "RESET ALL", tstate); + if (retvalue != 0) return retvalue; + + retvalue = pq_execute_command_locked(conn, + "SET SESSION AUTHORIZATION DEFAULT", tstate); + if (retvalue != 0) return retvalue; + } + + /* should set the tpc xid to null: postponed until we get the GIL again */ + conn->status = CONN_STATUS_READY; + + return retvalue; +} + +int +pq_reset(connectionObject *conn) +{ + int retvalue = -1; + + Dprintf("pq_reset: pgconn = %p, autocommit = %d, status = %d", + conn->pgconn, conn->autocommit, conn->status); + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&conn->lock); + + retvalue = pq_reset_locked(conn, &_save); + + Py_BLOCK_THREADS; + conn_notice_process(conn); + Py_UNBLOCK_THREADS; + + pthread_mutex_unlock(&conn->lock); + Py_END_ALLOW_THREADS; + + if (retvalue < 0) { + pq_complete_error(conn); + } + else { + Py_CLEAR(conn->tpc_xid); + } + return retvalue; +} + + +/* Get a session parameter. + * + * The function should be called on a locked connection without + * holding the GIL. + * + * The result is a new string allocated with malloc. + */ + +char * +pq_get_guc_locked(connectionObject *conn, const char *param, PyThreadState **tstate) +{ + char query[256]; + int size; + char *rv = NULL; + + Dprintf("pq_get_guc_locked: reading %s", param); + + size = PyOS_snprintf(query, sizeof(query), "SHOW %s", param); + if (size < 0 || (size_t)size >= sizeof(query)) { + conn_set_error(conn, "SHOW: query too large"); + goto cleanup; + } + + Dprintf("pq_get_guc_locked: pgconn = %p, query = %s", conn->pgconn, query); + + if (!psyco_green()) { + conn_set_result(conn, PQexec(conn->pgconn, query)); + } else { + PyEval_RestoreThread(*tstate); + conn_set_result(conn, psyco_exec_green(conn, query)); + *tstate = PyEval_SaveThread(); + } + + if (!conn->pgres) { + Dprintf("pq_get_guc_locked: PQexec returned NULL"); + PyEval_RestoreThread(*tstate); + if (!PyErr_Occurred()) { + conn_set_error(conn, PQerrorMessage(conn->pgconn)); + } + *tstate = PyEval_SaveThread(); + goto cleanup; + } + if (PQresultStatus(conn->pgres) != PGRES_TUPLES_OK) { + Dprintf("pq_get_guc_locked: result was not TUPLES_OK (%s)", + PQresStatus(PQresultStatus(conn->pgres))); + goto cleanup; + } + + rv = strdup(PQgetvalue(conn->pgres, 0, 0)); + CLEARPGRES(conn->pgres); + +cleanup: + return rv; +} + +/* Set a session parameter. + * + * The function should be called on a locked connection without + * holding the GIL + */ + +int +pq_set_guc_locked( + connectionObject *conn, const char *param, const char *value, + PyThreadState **tstate) +{ + char query[256]; + int size; + int rv = -1; + + Dprintf("pq_set_guc_locked: setting %s to %s", param, value); + + if (0 == strcmp(value, "default")) { + size = PyOS_snprintf(query, sizeof(query), + "SET %s TO DEFAULT", param); + } + else { + size = PyOS_snprintf(query, sizeof(query), + "SET %s TO '%s'", param, value); + } + if (size < 0 || (size_t)size >= sizeof(query)) { + conn_set_error(conn, "SET: query too large"); + goto exit; + } + + rv = pq_execute_command_locked(conn, query, tstate); + +exit: + return rv; +} + +/* Call one of the PostgreSQL tpc-related commands. + * + * This function should only be called on a locked connection without + * holding the global interpreter lock. */ + +int +pq_tpc_command_locked( + connectionObject *conn, const char *cmd, const char *tid, + PyThreadState **tstate) +{ + int rv = -1; + char *etid = NULL, *buf = NULL; + Py_ssize_t buflen; + + Dprintf("_pq_tpc_command: pgconn = %p, command = %s", + conn->pgconn, cmd); + + conn->mark += 1; + + PyEval_RestoreThread(*tstate); + + /* convert the xid into the postgres transaction_id and quote it. */ + if (!(etid = psyco_escape_string(conn, tid, -1, NULL, NULL))) + { goto exit; } + + /* prepare the command to the server */ + buflen = 2 + strlen(cmd) + strlen(etid); /* add space, zero */ + if (!(buf = PyMem_Malloc(buflen))) { + PyErr_NoMemory(); + goto exit; + } + if (0 > PyOS_snprintf(buf, buflen, "%s %s", cmd, etid)) { goto exit; } + + /* run the command and let it handle the error cases */ + *tstate = PyEval_SaveThread(); + rv = pq_execute_command_locked(conn, buf, tstate); + PyEval_RestoreThread(*tstate); + +exit: + PyMem_Free(buf); + PyMem_Free(etid); + + *tstate = PyEval_SaveThread(); + return rv; +} + + +/* pq_get_result_async - read an available result without blocking. + * + * Return 0 if the result is ready, 1 if it will block, -1 on error. + * The last result will be returned in conn->pgres. + * + * The function should be called with the lock and holding the GIL. + */ + +RAISES_NEG int +pq_get_result_async(connectionObject *conn) +{ + int rv = -1; + + Dprintf("pq_get_result_async: calling PQconsumeInput()"); + if (PQconsumeInput(conn->pgconn) == 0) { + Dprintf("pq_get_result_async: PQconsumeInput() failed"); + + /* if the libpq says pgconn is lost, close the py conn */ + if (CONNECTION_BAD == PQstatus(conn->pgconn)) { + conn->closed = 2; + } + + PyErr_SetString(OperationalError, PQerrorMessage(conn->pgconn)); + goto exit; + } + + conn_notifies_process(conn); + conn_notice_process(conn); + + for (;;) { + int busy; + PGresult *res; + ExecStatusType status; + + Dprintf("pq_get_result_async: calling PQisBusy()"); + busy = PQisBusy(conn->pgconn); + + if (busy) { + /* try later */ + Dprintf("pq_get_result_async: PQisBusy() = 1"); + rv = 1; + goto exit; + } + + if (!(res = PQgetResult(conn->pgconn))) { + Dprintf("pq_get_result_async: got no result"); + /* the result is ready: it was the previously read one */ + rv = 0; + goto exit; + } + + status = PQresultStatus(res); + Dprintf("pq_get_result_async: got result %s", PQresStatus(status)); + + /* Store the result outside because we want to return the last non-null + * one and we may have to do it across poll calls. However if there is + * an error in the stream of results we want to handle the *first* + * error. So don't clobber it with the following ones. */ + if (conn->pgres && PQresultStatus(conn->pgres) == PGRES_FATAL_ERROR) { + Dprintf("previous pgres is error: discarding"); + PQclear(res); + } + else { + conn_set_result(conn, res); + } + + switch (status) { + case PGRES_COPY_OUT: + case PGRES_COPY_IN: + case PGRES_COPY_BOTH: + /* After entering copy mode, libpq will make a phony + * PGresult for us every time we query for it, so we need to + * break out of this endless loop. */ + rv = 0; + goto exit; + + default: + /* keep on reading to check if there are other results or + * we have finished. */ + continue; + } + } + +exit: + return rv; +} + +/* pq_flush - flush output and return connection status + + a status of 1 means that a some data is still pending to be flushed, while a + status of 0 means that there is no data waiting to be sent. -1 means an + error and an exception will be set accordingly. + + this function locks the connection object + this function call Py_*_ALLOW_THREADS macros */ + +int +pq_flush(connectionObject *conn) +{ + int res; + + Dprintf("pq_flush: flushing output"); + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&(conn->lock)); + res = PQflush(conn->pgconn); + pthread_mutex_unlock(&(conn->lock)); + Py_END_ALLOW_THREADS; + + return res; +} + +/* pq_execute - execute a query, possibly asynchronously + * + * With no_result an eventual query result is discarded. + * Currently only used to implement cursor.executemany(). + * + * This function locks the connection object + * This function call Py_*_ALLOW_THREADS macros +*/ + +RAISES_NEG int +_pq_execute_sync(cursorObject *curs, const char *query, int no_result, int no_begin) +{ + connectionObject *conn = curs->conn; + + CLEARPGRES(curs->pgres); + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&(conn->lock)); + + if (!no_begin && pq_begin_locked(conn, &_save) < 0) { + pthread_mutex_unlock(&(conn->lock)); + Py_BLOCK_THREADS; + pq_complete_error(conn); + return -1; + } + + Dprintf("pq_execute: executing SYNC query: pgconn = %p", conn->pgconn); + Dprintf(" %-.200s", query); + if (!psyco_green()) { + conn_set_result(conn, PQexec(conn->pgconn, query)); + } + else { + Py_BLOCK_THREADS; + conn_set_result(conn, psyco_exec_green(conn, query)); + Py_UNBLOCK_THREADS; + } + + /* don't let pgres = NULL go to pq_fetch() */ + if (!conn->pgres) { + if (CONNECTION_BAD == PQstatus(conn->pgconn)) { + conn->closed = 2; + } + pthread_mutex_unlock(&(conn->lock)); + Py_BLOCK_THREADS; + if (!PyErr_Occurred()) { + PyErr_SetString(OperationalError, + PQerrorMessage(conn->pgconn)); + } + return -1; + } + + Py_BLOCK_THREADS; + + /* assign the result back to the cursor now that we have the GIL */ + curs_set_result(curs, conn->pgres); + conn->pgres = NULL; + + /* Process notifies here instead of when fetching the tuple as we are + * into the same critical section that received the data. Without this + * care, reading notifies may disrupt other thread communications. + * (as in ticket #55). */ + conn_notifies_process(conn); + conn_notice_process(conn); + Py_UNBLOCK_THREADS; + + pthread_mutex_unlock(&(conn->lock)); + Py_END_ALLOW_THREADS; + + /* if the execute was sync, we call pq_fetch() immediately, + to respect the old DBAPI-2.0 compatible behaviour */ + Dprintf("pq_execute: entering synchronous DBAPI compatibility mode"); + if (pq_fetch(curs, no_result) < 0) return -1; + + return 1; +} + +RAISES_NEG int +_pq_execute_async(cursorObject *curs, const char *query, int no_result) +{ + int async_status = ASYNC_WRITE; + connectionObject *conn = curs->conn; + int ret; + + CLEARPGRES(curs->pgres); + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&(conn->lock)); + + Dprintf("pq_execute: executing ASYNC query: pgconn = %p", conn->pgconn); + Dprintf(" %-.200s", query); + + if (PQsendQuery(conn->pgconn, query) == 0) { + if (CONNECTION_BAD == PQstatus(conn->pgconn)) { + conn->closed = 2; + } + pthread_mutex_unlock(&(conn->lock)); + Py_BLOCK_THREADS; + PyErr_SetString(OperationalError, + PQerrorMessage(conn->pgconn)); + return -1; + } + Dprintf("pq_execute: async query sent to backend"); + + ret = PQflush(conn->pgconn); + if (ret == 0) { + /* the query got fully sent to the server */ + Dprintf("pq_execute: query got flushed immediately"); + /* the async status will be ASYNC_READ */ + async_status = ASYNC_READ; + } + else if (ret == 1) { + /* not all of the query got sent to the server */ + async_status = ASYNC_WRITE; + } + else { + /* there was an error */ + pthread_mutex_unlock(&(conn->lock)); + Py_BLOCK_THREADS; + PyErr_SetString(OperationalError, + PQerrorMessage(conn->pgconn)); + return -1; + } + + pthread_mutex_unlock(&(conn->lock)); + Py_END_ALLOW_THREADS; + + conn->async_status = async_status; + if (!(conn->async_cursor + = PyWeakref_NewRef((PyObject *)curs, NULL))) { + return -1; + } + + return 0; +} + +RAISES_NEG int +pq_execute(cursorObject *curs, const char *query, int async, int no_result, int no_begin) +{ + /* check status of connection, raise error if not OK */ + if (PQstatus(curs->conn->pgconn) != CONNECTION_OK) { + Dprintf("pq_execute: connection NOT OK"); + PyErr_SetString(OperationalError, PQerrorMessage(curs->conn->pgconn)); + return -1; + } + Dprintf("pq_execute: pg connection at %p OK", curs->conn->pgconn); + + if (!async) { + return _pq_execute_sync(curs, query, no_result, no_begin); + } else { + return _pq_execute_async(curs, query, no_result); + } +} + + +/* send an async query to the backend. + * + * Return 1 if command succeeded, else 0. + * + * The function should be called helding the connection lock and the GIL. + */ +int +pq_send_query(connectionObject *conn, const char *query) +{ + int rv; + + Dprintf("pq_send_query: sending ASYNC query:"); + Dprintf(" %-.200s", query); + + CLEARPGRES(conn->pgres); + if (0 == (rv = PQsendQuery(conn->pgconn, query))) { + Dprintf("pq_send_query: error: %s", PQerrorMessage(conn->pgconn)); + } + + return rv; +} + + +/* pq_fetch - fetch data after a query + + this function locks the connection object + this function call Py_*_ALLOW_THREADS macros + + return value: + -1 - some error occurred while calling libpq + 0 - no result from the backend but no libpq errors + 1 - result from backend (possibly data is ready) +*/ + +static PyObject * +_get_cast(cursorObject *curs, PGresult *pgres, int i) +{ + /* fill the right cast function by accessing three different dictionaries: + - the per-cursor dictionary, if available (can be NULL or None) + - the per-connection dictionary (always exists but can be null) + - the global dictionary (at module level) + if we get no defined cast use the default one */ + PyObject *type = NULL; + PyObject *cast = NULL; + PyObject *rv = NULL; + + Oid ftype = PQftype(pgres, i); + if (!(type = PyLong_FromOid(ftype))) { goto exit; } + + Dprintf("_pq_fetch_tuples: looking for cast %u:", ftype); + if (!(cast = curs_get_cast(curs, type))) { goto exit; } + + /* else if we got binary tuples and if we got a field that + is binary use the default cast + FIXME: what the hell am I trying to do here? This just can't work.. + */ + if (cast == psyco_default_binary_cast && PQbinaryTuples(pgres)) { + Dprintf("_pq_fetch_tuples: Binary cursor and " + "binary field: %u using default cast", ftype); + cast = psyco_default_cast; + } + + Dprintf("_pq_fetch_tuples: using cast at %p for type %u", cast, ftype); + + /* success */ + Py_INCREF(cast); + rv = cast; + +exit: + Py_XDECREF(type); + return rv; +} + +static PyObject * +_make_column(connectionObject *conn, PGresult *pgres, int i) +{ + Oid ftype = PQftype(pgres, i); + int fsize = PQfsize(pgres, i); + int fmod = PQfmod(pgres, i); + Oid ftable = PQftable(pgres, i); + int ftablecol = PQftablecol(pgres, i); + + columnObject *column = NULL; + PyObject *rv = NULL; + + if (!(column = (columnObject *)PyObject_CallObject( + (PyObject *)&columnType, NULL))) { + goto exit; + } + + /* fill the type and name fields */ + { + PyObject *tmp; + if (!(tmp = PyLong_FromOid(ftype))) { + goto exit; + } + column->type_code = tmp; + } + + { + PyObject *tmp; + if (!(tmp = conn_text_from_chars(conn, PQfname(pgres, i)))) { + goto exit; + } + column->name = tmp; + } + + /* display size is the maximum size of this field result tuples. */ + Py_INCREF(Py_None); + column->display_size = Py_None; + + /* size on the backend */ + if (fmod > 0) { + fmod = fmod - sizeof(int); + } + if (fsize == -1) { + if (ftype == NUMERICOID) { + PyObject *tmp; + if (!(tmp = PyInt_FromLong((fmod >> 16)))) { goto exit; } + column->internal_size = tmp; + } + else { /* If variable length record, return maximum size */ + PyObject *tmp; + if (!(tmp = PyInt_FromLong(fmod))) { goto exit; } + column->internal_size = tmp; + } + } + else { + PyObject *tmp; + if (!(tmp = PyInt_FromLong(fsize))) { goto exit; } + column->internal_size = tmp; + } + + /* scale and precision */ + if (ftype == NUMERICOID) { + PyObject *tmp; + + if (!(tmp = PyInt_FromLong((fmod >> 16) & 0xFFFF))) { + goto exit; + } + column->precision = tmp; + + if (!(tmp = PyInt_FromLong(fmod & 0xFFFF))) { + goto exit; + } + column->scale = tmp; + } + + /* table_oid, table_column */ + if (ftable != InvalidOid) { + PyObject *tmp; + if (!(tmp = PyLong_FromOid(ftable))) { goto exit; } + column->table_oid = tmp; + } + + if (ftablecol > 0) { + PyObject *tmp; + if (!(tmp = PyInt_FromLong((long)ftablecol))) { goto exit; } + column->table_column = tmp; + } + + /* success */ + rv = (PyObject *)column; + column = NULL; + +exit: + Py_XDECREF(column); + return rv; +} + +RAISES_NEG static int +_pq_fetch_tuples(cursorObject *curs) +{ + int i; + int pgnfields; + int rv = -1; + PyObject *description = NULL; + PyObject *casts = NULL; + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_lock(&(curs->conn->lock)); + Py_END_ALLOW_THREADS; + + pgnfields = PQnfields(curs->pgres); + + curs->notuples = 0; + + /* create the tuple for description and typecasting */ + Py_CLEAR(curs->description); + Py_CLEAR(curs->casts); + if (!(description = PyTuple_New(pgnfields))) { goto exit; } + if (!(casts = PyTuple_New(pgnfields))) { goto exit; } + curs->columns = pgnfields; + + /* calculate each field's parameters and typecasters */ + for (i = 0; i < pgnfields; i++) { + PyObject *column = NULL; + PyObject *cast = NULL; + + if (!(column = _make_column(curs->conn, curs->pgres, i))) { + goto exit; + } + PyTuple_SET_ITEM(description, i, (PyObject *)column); + + if (!(cast = _get_cast(curs, curs->pgres, i))) { + goto exit; + } + PyTuple_SET_ITEM(casts, i, cast); + } + + curs->description = description; + description = NULL; + + curs->casts = casts; + casts = NULL; + + rv = 0; + +exit: + Py_XDECREF(description); + Py_XDECREF(casts); + + Py_BEGIN_ALLOW_THREADS; + pthread_mutex_unlock(&(curs->conn->lock)); + Py_END_ALLOW_THREADS; + + return rv; +} + + +void +_read_rowcount(cursorObject *curs) +{ + const char *rowcount; + + rowcount = PQcmdTuples(curs->pgres); + Dprintf("_read_rowcount: PQcmdTuples = %s", rowcount); + if (!rowcount || !rowcount[0]) { + curs->rowcount = -1; + } else { + curs->rowcount = atol(rowcount); + } +} + +static int +_pq_copy_in_v3(cursorObject *curs) +{ + /* COPY FROM implementation when protocol 3 is available: this function + uses the new PQputCopyData() and can detect errors and set the correct + exception */ + PyObject *o, *func = NULL, *size = NULL; + Py_ssize_t length = 0; + int res, error = 0; + + if (!curs->copyfile) { + PyErr_SetString(ProgrammingError, + "can't execute COPY FROM: use the copy_from() method instead"); + error = 1; + goto exit; + } + + if (!(func = PyObject_GetAttrString(curs->copyfile, "read"))) { + Dprintf("_pq_copy_in_v3: can't get o.read"); + error = 1; + goto exit; + } + if (!(size = PyInt_FromSsize_t(curs->copysize))) { + Dprintf("_pq_copy_in_v3: can't get int from copysize"); + error = 1; + goto exit; + } + + while (1) { + if (!(o = PyObject_CallFunctionObjArgs(func, size, NULL))) { + Dprintf("_pq_copy_in_v3: read() failed"); + error = 1; + break; + } + + /* a file may return unicode if implements io.TextIOBase */ + if (PyUnicode_Check(o)) { + PyObject *tmp; + if (!(tmp = conn_encode(curs->conn, o))) { + Dprintf("_pq_copy_in_v3: encoding() failed"); + error = 1; + break; + } + Py_DECREF(o); + o = tmp; + } + + if (!Bytes_Check(o)) { + Dprintf("_pq_copy_in_v3: got %s instead of bytes", + Py_TYPE(o)->tp_name); + error = 1; + break; + } + + if (0 == (length = Bytes_GET_SIZE(o))) { + break; + } + if (length > INT_MAX) { + Dprintf("_pq_copy_in_v3: bad length: " FORMAT_CODE_PY_SSIZE_T, + length); + error = 1; + break; + } + + Py_BEGIN_ALLOW_THREADS; + res = PQputCopyData(curs->conn->pgconn, Bytes_AS_STRING(o), + /* Py_ssize_t->int cast was validated above */ + (int) length); + Dprintf("_pq_copy_in_v3: sent " FORMAT_CODE_PY_SSIZE_T " bytes of data; res = %d", + length, res); + + if (res == 0) { + /* FIXME: in theory this should not happen but adding a check + here would be a nice idea */ + } + else if (res == -1) { + Dprintf("_pq_copy_in_v3: PQerrorMessage = %s", + PQerrorMessage(curs->conn->pgconn)); + error = 2; + } + Py_END_ALLOW_THREADS; + + if (error == 2) break; + + Py_DECREF(o); + } + + Py_XDECREF(o); + + Dprintf("_pq_copy_in_v3: error = %d", error); + + /* 0 means that the copy went well, 2 that there was an error on the + backend: in both cases we'll get the error message from the PQresult */ + if (error == 0) + res = PQputCopyEnd(curs->conn->pgconn, NULL); + else if (error == 2) + res = PQputCopyEnd(curs->conn->pgconn, "error in PQputCopyData() call"); + else { + char buf[1024]; + strcpy(buf, "error in .read() call"); + if (PyErr_Occurred()) { + PyObject *t, *ex, *tb; + PyErr_Fetch(&t, &ex, &tb); + if (ex) { + PyObject *str; + str = PyObject_Str(ex); + str = psyco_ensure_bytes(str); + if (str) { + PyOS_snprintf(buf, sizeof(buf), + "error in .read() call: %s %s", + ((PyTypeObject *)t)->tp_name, Bytes_AsString(str)); + Py_DECREF(str); + } + } + /* Clear the Py exception: it will be re-raised from the libpq */ + Py_XDECREF(t); + Py_XDECREF(ex); + Py_XDECREF(tb); + PyErr_Clear(); + } + res = PQputCopyEnd(curs->conn->pgconn, buf); + } + + CLEARPGRES(curs->pgres); + + Dprintf("_pq_copy_in_v3: copy ended; res = %d", res); + + /* if the result is -1 we should not even try to get a result from the + because that will lock the current thread forever */ + if (res == -1) { + pq_raise(curs->conn, curs, NULL); + /* FIXME: pq_raise check the connection but for some reason even + if the error message says "server closed the connection unexpectedly" + the status returned by PQstatus is CONNECTION_OK! */ + curs->conn->closed = 2; + } + else { + /* and finally we grab the operation result from the backend */ + for (;;) { + Py_BEGIN_ALLOW_THREADS; + curs_set_result(curs, PQgetResult(curs->conn->pgconn)); + Py_END_ALLOW_THREADS; + + if (NULL == curs->pgres) + break; + _read_rowcount(curs); + if (PQresultStatus(curs->pgres) == PGRES_FATAL_ERROR) + pq_raise(curs->conn, curs, NULL); + CLEARPGRES(curs->pgres); + } + } + +exit: + Py_XDECREF(func); + Py_XDECREF(size); + return (error == 0 ? 1 : -1); +} + +static int +_pq_copy_out_v3(cursorObject *curs) +{ + PyObject *tmp = NULL; + PyObject *func = NULL; + PyObject *obj = NULL; + int ret = -1; + int is_text; + + char *buffer; + Py_ssize_t len; + + if (!curs->copyfile) { + PyErr_SetString(ProgrammingError, + "can't execute COPY TO: use the copy_to() method instead"); + goto exit; + } + + if (!(func = PyObject_GetAttrString(curs->copyfile, "write"))) { + Dprintf("_pq_copy_out_v3: can't get o.write"); + goto exit; + } + + /* if the file is text we must pass it unicode. */ + if (-1 == (is_text = psyco_is_text_file(curs->copyfile))) { + goto exit; + } + + while (1) { + Py_BEGIN_ALLOW_THREADS; + len = PQgetCopyData(curs->conn->pgconn, &buffer, 0); + Py_END_ALLOW_THREADS; + + if (len > 0 && buffer) { + if (is_text) { + obj = conn_decode(curs->conn, buffer, len); + } else { + obj = Bytes_FromStringAndSize(buffer, len); + } + + PQfreemem(buffer); + if (!obj) { goto exit; } + tmp = PyObject_CallFunctionObjArgs(func, obj, NULL); + Py_DECREF(obj); + + if (tmp == NULL) { + goto exit; + } else { + Py_DECREF(tmp); + } + } + /* we break on len == 0 but note that that should *not* happen, + because we are not doing an async call (if it happens blame + postgresql authors :/) */ + else if (len <= 0) break; + } + + if (len == -2) { + pq_raise(curs->conn, curs, NULL); + goto exit; + } + + /* and finally we grab the operation result from the backend */ + for (;;) { + Py_BEGIN_ALLOW_THREADS; + curs_set_result(curs, PQgetResult(curs->conn->pgconn)); + Py_END_ALLOW_THREADS; + + if (NULL == curs->pgres) + break; + _read_rowcount(curs); + if (PQresultStatus(curs->pgres) == PGRES_FATAL_ERROR) + pq_raise(curs->conn, curs, NULL); + CLEARPGRES(curs->pgres); + } + ret = 1; + +exit: + Py_XDECREF(func); + return ret; +} + +/* Tries to read the next message from the replication stream, without + blocking, in both sync and async connection modes. If no message + is ready in the CopyData buffer, tries to read from the server, + again without blocking. If that doesn't help, returns Py_None. + The caller is then supposed to block on the socket(s) and call this + function again. + + Any keepalive messages from the server are silently consumed and + are never returned to the caller. + */ +int +pq_read_replication_message(replicationCursorObject *repl, replicationMessageObject **msg) +{ + cursorObject *curs = &repl->cur; + connectionObject *conn = curs->conn; + PGconn *pgconn = conn->pgconn; + char *buffer = NULL; + int len, data_size, consumed, hdr, reply; + XLogRecPtr data_start, wal_end; + int64_t send_time; + PyObject *str = NULL, *result = NULL; + int ret = -1; + struct timeval curr_time, feedback_time; + + Dprintf("pq_read_replication_message"); + + *msg = NULL; + consumed = 0; + + /* Is it a time to send the next feedback message? */ + gettimeofday(&curr_time, NULL); + timeradd(&repl->last_feedback, &repl->status_interval, &feedback_time); + if (timercmp(&curr_time, &feedback_time, >=) && pq_send_replication_feedback(repl, 0) < 0) { + goto exit; + } + +retry: + len = PQgetCopyData(pgconn, &buffer, 1 /* async */); + + if (len == 0) { + /* If we've tried reading some data, but there was none, bail out. */ + if (consumed) { + ret = 0; + goto exit; + } + /* We should only try reading more data when there is nothing + available at the moment. Otherwise, with a really highly loaded + server we might be reading a number of messages for every single + one we process, thus overgrowing the internal buffer until the + client system runs out of memory. */ + if (!PQconsumeInput(pgconn)) { + pq_raise(conn, curs, NULL); + goto exit; + } + /* But PQconsumeInput() doesn't tell us if it has actually read + anything into the internal buffer and there is no (supported) way + to ask libpq about this directly. The way we check is setting the + flag and re-trying PQgetCopyData(): if that returns 0 again, + there's no more data available in the buffer, so we return None. */ + consumed = 1; + goto retry; + } + + if (len == -2) { + /* serious error */ + pq_raise(conn, curs, NULL); + goto exit; + } + if (len == -1) { + /* EOF */ + curs_set_result(curs, PQgetResult(pgconn)); + + if (curs->pgres && PQresultStatus(curs->pgres) == PGRES_FATAL_ERROR) { + pq_raise(conn, curs, NULL); + goto exit; + } + + CLEARPGRES(curs->pgres); + ret = 0; + goto exit; + } + + /* It also makes sense to set this flag here to make us return early in + case of retry due to keepalive message. Any pending data on the socket + will trigger read condition in select() in the calling code anyway. */ + consumed = 1; + + /* ok, we did really read something: update the io timestamp */ + gettimeofday(&repl->last_io, NULL); + + Dprintf("pq_read_replication_message: msg=%c, len=%d", buffer[0], len); + if (buffer[0] == 'w') { + /* XLogData: msgtype(1), dataStart(8), walEnd(8), sendTime(8) */ + hdr = 1 + 8 + 8 + 8; + if (len < hdr + 1) { + psyco_set_error(OperationalError, curs, "data message header too small"); + goto exit; + } + + data_size = len - hdr; + data_start = fe_recvint64(buffer + 1); + wal_end = fe_recvint64(buffer + 1 + 8); + send_time = fe_recvint64(buffer + 1 + 8 + 8); + + Dprintf("pq_read_replication_message: data_start="XLOGFMTSTR", wal_end="XLOGFMTSTR, + XLOGFMTARGS(data_start), XLOGFMTARGS(wal_end)); + + Dprintf("pq_read_replication_message: >>%.*s<<", data_size, buffer + hdr); + + if (repl->decode) { + str = conn_decode(conn, buffer + hdr, data_size); + } else { + str = Bytes_FromStringAndSize(buffer + hdr, data_size); + } + if (!str) { goto exit; } + + result = PyObject_CallFunctionObjArgs((PyObject *)&replicationMessageType, + curs, str, NULL); + Py_DECREF(str); + if (!result) { goto exit; } + + *msg = (replicationMessageObject *)result; + (*msg)->data_size = data_size; + (*msg)->data_start = data_start; + (*msg)->wal_end = wal_end; + (*msg)->send_time = send_time; + + repl->wal_end = wal_end; + repl->last_msg_data_start = data_start; + } + else if (buffer[0] == 'k') { + /* Primary keepalive message: msgtype(1), walEnd(8), sendTime(8), reply(1) */ + hdr = 1 + 8 + 8; + if (len < hdr + 1) { + psyco_set_error(OperationalError, curs, "keepalive message header too small"); + goto exit; + } + + wal_end = fe_recvint64(buffer + 1); + Dprintf("pq_read_replication_message: wal_end="XLOGFMTSTR, XLOGFMTARGS(wal_end)); + repl->wal_end = wal_end; + + /* We can safely forward flush_lsn to the wal_end from the server keepalive message + * if we know that the client already processed (confirmed) the last XLogData message */ + if (repl->explicitly_flushed_lsn >= repl->last_msg_data_start + && wal_end > repl->explicitly_flushed_lsn + && wal_end > repl->flush_lsn) { + repl->flush_lsn = wal_end; + } + + reply = buffer[hdr]; + if (reply && pq_send_replication_feedback(repl, 0) < 0) { + goto exit; + } + + PQfreemem(buffer); + buffer = NULL; + goto retry; + } + else { + psyco_set_error(OperationalError, curs, "unrecognized replication message type"); + goto exit; + } + + ret = 0; + +exit: + if (buffer) { + PQfreemem(buffer); + } + + return ret; +} + +int +pq_send_replication_feedback(replicationCursorObject *repl, int reply_requested) +{ + cursorObject *curs = &repl->cur; + connectionObject *conn = curs->conn; + PGconn *pgconn = conn->pgconn; + char replybuf[1 + 8 + 8 + 8 + 8 + 1]; + int len = 0; + + Dprintf("pq_send_replication_feedback: write="XLOGFMTSTR", flush="XLOGFMTSTR", apply="XLOGFMTSTR, + XLOGFMTARGS(repl->write_lsn), + XLOGFMTARGS(repl->flush_lsn), + XLOGFMTARGS(repl->apply_lsn)); + + replybuf[len] = 'r'; len += 1; + fe_sendint64(repl->write_lsn, &replybuf[len]); len += 8; + fe_sendint64(repl->flush_lsn, &replybuf[len]); len += 8; + fe_sendint64(repl->apply_lsn, &replybuf[len]); len += 8; + fe_sendint64(feGetCurrentTimestamp(), &replybuf[len]); len += 8; + replybuf[len] = reply_requested ? 1 : 0; len += 1; + + if (PQputCopyData(pgconn, replybuf, len) <= 0 || PQflush(pgconn) != 0) { + pq_raise(conn, curs, NULL); + return -1; + } + gettimeofday(&repl->last_feedback, NULL); + repl->last_io = repl->last_feedback; + + return 0; +} + +/* Calls pq_read_replication_message in an endless loop, until + stop_replication is called or a fatal error occurs. The messages + are passed to the consumer object. + + When no message is available, blocks on the connection socket, but + manages to send keepalive messages to the server as needed. +*/ +int +pq_copy_both(replicationCursorObject *repl, PyObject *consume) +{ + cursorObject *curs = &repl->cur; + connectionObject *conn = curs->conn; + PGconn *pgconn = conn->pgconn; + replicationMessageObject *msg = NULL; + PyObject *tmp = NULL; + int fd, sel, ret = -1; + fd_set fds; + struct timeval curr_time, feedback_time, timeout; + + if (!PyCallable_Check(consume)) { + Dprintf("pq_copy_both: expected callable consume object"); + goto exit; + } + + CLEARPGRES(curs->pgres); + + while (1) { + if (pq_read_replication_message(repl, &msg) < 0) { + goto exit; + } + else if (msg == NULL) { + fd = PQsocket(pgconn); + if (fd < 0) { + pq_raise(conn, curs, NULL); + goto exit; + } + + FD_ZERO(&fds); + FD_SET(fd, &fds); + + /* how long can we wait before we need to send a feedback? */ + gettimeofday(&curr_time, NULL); + + timeradd(&repl->last_feedback, &repl->status_interval, &feedback_time); + timersub(&feedback_time, &curr_time, &timeout); + + if (timeout.tv_sec >= 0) { + Py_BEGIN_ALLOW_THREADS; + sel = select(fd + 1, &fds, NULL, NULL, &timeout); + Py_END_ALLOW_THREADS; + + if (sel < 0) { + if (errno != EINTR) { + PyErr_SetFromErrno(PyExc_OSError); + goto exit; + } + if (PyErr_CheckSignals()) { + goto exit; + } + } + } + } + else { + tmp = PyObject_CallFunctionObjArgs(consume, msg, NULL); + Py_DECREF(msg); + + if (tmp == NULL) { + Dprintf("pq_copy_both: consume returned NULL"); + goto exit; + } + Py_DECREF(tmp); + } + } + + ret = 1; + +exit: + return ret; +} + +int +pq_fetch(cursorObject *curs, int no_result) +{ + int pgstatus, ex = -1; + + /* even if we fail, we remove any information about the previous query */ + curs_reset(curs); + + if (curs->pgres == NULL) return 0; + + pgstatus = PQresultStatus(curs->pgres); + Dprintf("pq_fetch: pgstatus = %s", PQresStatus(pgstatus)); + + /* backend status message */ + Py_CLEAR(curs->pgstatus); + if (!(curs->pgstatus = conn_text_from_chars( + curs->conn, PQcmdStatus(curs->pgres)))) { + ex = -1; + return ex; + } + + switch(pgstatus) { + + case PGRES_COMMAND_OK: + Dprintf("pq_fetch: command returned OK (no tuples)"); + _read_rowcount(curs); + curs->lastoid = PQoidValue(curs->pgres); + CLEARPGRES(curs->pgres); + ex = 1; + break; + + case PGRES_COPY_OUT: + Dprintf("pq_fetch: data from a COPY TO (no tuples)"); + curs->rowcount = -1; + ex = _pq_copy_out_v3(curs); + /* error caught by out glorious notice handler */ + if (PyErr_Occurred()) ex = -1; + CLEARPGRES(curs->pgres); + break; + + case PGRES_COPY_IN: + Dprintf("pq_fetch: data from a COPY FROM (no tuples)"); + curs->rowcount = -1; + ex = _pq_copy_in_v3(curs); + /* error caught by out glorious notice handler */ + if (PyErr_Occurred()) ex = -1; + CLEARPGRES(curs->pgres); + break; + + case PGRES_COPY_BOTH: + Dprintf("pq_fetch: data from a streaming replication slot (no tuples)"); + curs->rowcount = -1; + ex = 0; + /* Nothing to do here: pq_copy_both will be called separately. + + Also don't clear the result status: it's checked in + consume_stream. */ + /*CLEARPGRES(curs->pgres);*/ + break; + + case PGRES_TUPLES_OK: + if (!no_result) { + Dprintf("pq_fetch: got tuples"); + curs->rowcount = PQntuples(curs->pgres); + if (0 == _pq_fetch_tuples(curs)) { ex = 0; } + /* don't clear curs->pgres, because it contains the results! */ + } + else { + Dprintf("pq_fetch: got tuples, discarding them"); + /* TODO: is there any case in which PQntuples == PQcmdTuples? */ + _read_rowcount(curs); + CLEARPGRES(curs->pgres); + ex = 0; + } + break; + + case PGRES_EMPTY_QUERY: + PyErr_SetString(ProgrammingError, + "can't execute an empty query"); + CLEARPGRES(curs->pgres); + ex = -1; + break; + + case PGRES_BAD_RESPONSE: + case PGRES_NONFATAL_ERROR: + case PGRES_FATAL_ERROR: + Dprintf("pq_fetch: uh-oh, something FAILED: status = %d pgconn = %p", + pgstatus, curs->conn); + pq_raise(curs->conn, curs, NULL); + ex = -1; + break; + + default: + /* PGRES_SINGLE_TUPLE, future statuses */ + Dprintf("pq_fetch: got unsupported result: status = %d pgconn = %p", + pgstatus, curs->conn); + PyErr_Format(NotSupportedError, + "got server response with unsupported status %s", + PQresStatus(curs->pgres == NULL ? + PQstatus(curs->conn->pgconn) : PQresultStatus(curs->pgres))); + CLEARPGRES(curs->pgres); + ex = -1; + break; + } + + return ex; +} diff --git a/source-code/psycopg2/psycopg/pqpath.h b/source-code/psycopg2/psycopg/pqpath.h new file mode 100644 index 0000000..d5ba4d1 --- /dev/null +++ b/source-code/psycopg2/psycopg/pqpath.h @@ -0,0 +1,74 @@ +/* pqpath.h - definitions for pqpath.c + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_PQPATH_H +#define PSYCOPG_PQPATH_H 1 + +#include "psycopg/cursor.h" +#include "psycopg/connection.h" +#include "psycopg/replication_cursor.h" +#include "psycopg/replication_message.h" + +/* macro to clean the pg result */ +#define CLEARPGRES(pgres) do { PQclear(pgres); pgres = NULL; } while (0) + +/* exported functions */ +RAISES_NEG HIDDEN int pq_fetch(cursorObject *curs, int no_result); +RAISES_NEG HIDDEN int pq_execute(cursorObject *curs, const char *query, + int async, int no_result, int no_begin); +HIDDEN int pq_send_query(connectionObject *conn, const char *query); +HIDDEN int pq_begin_locked(connectionObject *conn, PyThreadState **tstate); +HIDDEN int pq_commit(connectionObject *conn); +RAISES_NEG HIDDEN int pq_abort_locked(connectionObject *conn, + PyThreadState **tstate); +RAISES_NEG HIDDEN int pq_abort(connectionObject *conn); +HIDDEN int pq_reset_locked(connectionObject *conn, PyThreadState **tstate); +RAISES_NEG HIDDEN int pq_reset(connectionObject *conn); +HIDDEN char *pq_get_guc_locked(connectionObject *conn, const char *param, + PyThreadState **tstate); +HIDDEN int pq_set_guc_locked(connectionObject *conn, const char *param, + const char *value, PyThreadState **tstate); +HIDDEN int pq_tpc_command_locked(connectionObject *conn, + const char *cmd, const char *tid, + PyThreadState **tstate); +RAISES_NEG HIDDEN int pq_get_result_async(connectionObject *conn); +HIDDEN int pq_flush(connectionObject *conn); +HIDDEN void pq_clear_async(connectionObject *conn); +RAISES_NEG HIDDEN int pq_set_non_blocking(connectionObject *conn, int arg); + +HIDDEN void pq_set_critical(connectionObject *conn, const char *msg); + +HIDDEN int pq_execute_command_locked(connectionObject *conn, const char *query, + PyThreadState **tstate); +RAISES HIDDEN void pq_complete_error(connectionObject *conn); + +/* replication protocol support */ +HIDDEN int pq_copy_both(replicationCursorObject *repl, PyObject *consumer); +HIDDEN int pq_read_replication_message(replicationCursorObject *repl, + replicationMessageObject **msg); +HIDDEN int pq_send_replication_feedback(replicationCursorObject *repl, int reply_requested); + +#endif /* !defined(PSYCOPG_PQPATH_H) */ diff --git a/source-code/psycopg2/psycopg/psycopg.h b/source-code/psycopg2/psycopg/psycopg.h new file mode 100644 index 0000000..afda00f --- /dev/null +++ b/source-code/psycopg2/psycopg/psycopg.h @@ -0,0 +1,107 @@ +/* psycopg.h - definitions for the psycopg python module + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_H +#define PSYCOPG_H 1 + +#if PG_VERSION_NUM < 90100 +#error "Psycopg requires PostgreSQL client library (libpq) >= 9.1" +#endif + +#define PY_SSIZE_T_CLEAN +#include +#include + +#include "psycopg/config.h" +#include "psycopg/python.h" +#include "psycopg/utils.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* DBAPI compliance parameters */ +#define APILEVEL "2.0" +#define THREADSAFETY 2 +#define PARAMSTYLE "pyformat" + +/* global exceptions */ +extern HIDDEN PyObject *Error, *Warning, *InterfaceError, *DatabaseError, + *InternalError, *OperationalError, *ProgrammingError, + *IntegrityError, *DataError, *NotSupportedError; +extern HIDDEN PyObject *QueryCanceledError, *TransactionRollbackError; + +/* sqlstate -> exception map */ +extern HIDDEN PyObject *sqlstate_errors; + +/* postgresql<->python encoding map */ +extern HIDDEN PyObject *psycoEncodings; + +/* SQL NULL */ +extern HIDDEN PyObject *psyco_null; + +/* Exceptions docstrings */ +#define Error_doc \ +"Base class for error exceptions." + +#define Warning_doc \ +"A database warning." + +#define InterfaceError_doc \ +"Error related to the database interface." + +#define DatabaseError_doc \ +"Error related to the database engine." + +#define InternalError_doc \ +"The database encountered an internal error." + +#define OperationalError_doc \ +"Error related to database operation (disconnect, memory allocation etc)." + +#define ProgrammingError_doc \ +"Error related to database programming (SQL error, table not found etc)." + +#define IntegrityError_doc \ +"Error related to database integrity." + +#define DataError_doc \ +"Error related to problems with the processed data." + +#define NotSupportedError_doc \ +"A method or database API was used which is not supported by the database." + +#define QueryCanceledError_doc \ +"Error related to SQL query cancellation." + +#define TransactionRollbackError_doc \ +"Error causing transaction rollback (deadlocks, serialization failures, etc)." + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_H) */ diff --git a/source-code/psycopg2/psycopg/psycopgmodule.c b/source-code/psycopg2/psycopg/psycopgmodule.c new file mode 100644 index 0000000..1490a92 --- /dev/null +++ b/source-code/psycopg2/psycopg/psycopgmodule.c @@ -0,0 +1,1035 @@ +/* psycopgmodule.c - psycopg module (will import other C classes) + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/connection.h" +#include "psycopg/cursor.h" +#include "psycopg/replication_connection.h" +#include "psycopg/replication_cursor.h" +#include "psycopg/replication_message.h" +#include "psycopg/green.h" +#include "psycopg/column.h" +#include "psycopg/lobject.h" +#include "psycopg/notify.h" +#include "psycopg/xid.h" +#include "psycopg/typecast.h" +#include "psycopg/microprotocols.h" +#include "psycopg/microprotocols_proto.h" +#include "psycopg/conninfo.h" +#include "psycopg/diagnostics.h" + +#include "psycopg/adapter_qstring.h" +#include "psycopg/adapter_binary.h" +#include "psycopg/adapter_pboolean.h" +#include "psycopg/adapter_pint.h" +#include "psycopg/adapter_pfloat.h" +#include "psycopg/adapter_pdecimal.h" +#include "psycopg/adapter_asis.h" +#include "psycopg/adapter_list.h" +#include "psycopg/typecast_binary.h" + +/* some module-level variables, like the datetime module */ +#include +#include "psycopg/adapter_datetime.h" + +#include + +HIDDEN PyObject *psycoEncodings = NULL; +HIDDEN PyObject *sqlstate_errors = NULL; + +#ifdef PSYCOPG_DEBUG +HIDDEN int psycopg_debug_enabled = 0; +#endif + +/* Python representation of SQL NULL */ +HIDDEN PyObject *psyco_null = NULL; + +/* macro trick to stringify a macro expansion */ +#define xstr(s) str(s) +#define str(s) #s + +/** connect module-level function **/ +#define psyco_connect_doc \ +"_connect(dsn, [connection_factory], [async]) -- New database connection.\n\n" + +static PyObject * +psyco_connect(PyObject *self, PyObject *args, PyObject *keywds) +{ + PyObject *conn = NULL; + PyObject *factory = NULL; + const char *dsn = NULL; + int async = 0, async_ = 0; + + static char *kwlist[] = {"dsn", "connection_factory", "async", "async_", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, keywds, "s|Oii", kwlist, + &dsn, &factory, &async, &async_)) { + return NULL; + } + + if (async_) { async = async_; } + + Dprintf("psyco_connect: dsn = '%s', async = %d", dsn, async); + + /* allocate connection, fill with errors and return it */ + if (factory == NULL || factory == Py_None) { + factory = (PyObject *)&connectionType; + } + + /* Here we are breaking the connection.__init__ interface defined + * by psycopg2. So, if not requiring an async conn, avoid passing + * the async parameter. */ + /* TODO: would it be possible to avoid an additional parameter + * to the conn constructor? A subclass? (but it would require mixins + * to further subclass) Another dsn parameter (but is not really + * a connection parameter that can be configured) */ + if (!async) { + conn = PyObject_CallFunction(factory, "s", dsn); + } else { + conn = PyObject_CallFunction(factory, "si", dsn, async); + } + + return conn; +} + + +#define parse_dsn_doc \ +"parse_dsn(dsn) -> dict -- parse a connection string into parameters" + +static PyObject * +parse_dsn(PyObject *self, PyObject *args, PyObject *kwargs) +{ + char *err = NULL; + PQconninfoOption *options = NULL; + PyObject *res = NULL, *dsn; + + static char *kwlist[] = {"dsn", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &dsn)) { + return NULL; + } + + Py_INCREF(dsn); /* for ensure_bytes */ + if (!(dsn = psyco_ensure_bytes(dsn))) { goto exit; } + + options = PQconninfoParse(Bytes_AS_STRING(dsn), &err); + if (options == NULL) { + if (err != NULL) { + PyErr_Format(ProgrammingError, "invalid dsn: %s", err); + PQfreemem(err); + } else { + PyErr_SetString(OperationalError, "PQconninfoParse() failed"); + } + goto exit; + } + + res = psyco_dict_from_conninfo_options(options, /* include_password = */ 1); + +exit: + PQconninfoFree(options); /* safe on null */ + Py_XDECREF(dsn); + + return res; +} + + +#define quote_ident_doc \ +"quote_ident(str, conn_or_curs) -> str -- wrapper around PQescapeIdentifier\n\n" \ +":Parameters:\n" \ +" * `str`: A bytes or unicode object\n" \ +" * `conn_or_curs`: A connection or cursor, required" + +static PyObject * +quote_ident(PyObject *self, PyObject *args, PyObject *kwargs) +{ + PyObject *ident = NULL, *obj = NULL, *result = NULL; + connectionObject *conn; + char *quoted = NULL; + + static char *kwlist[] = {"ident", "scope", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO", kwlist, &ident, &obj)) { + return NULL; + } + + if (PyObject_TypeCheck(obj, &cursorType)) { + conn = ((cursorObject*)obj)->conn; + } + else if (PyObject_TypeCheck(obj, &connectionType)) { + conn = (connectionObject*)obj; + } + else { + PyErr_SetString(PyExc_TypeError, + "argument 2 must be a connection or a cursor"); + return NULL; + } + + Py_INCREF(ident); /* for ensure_bytes */ + if (!(ident = psyco_ensure_bytes(ident))) { goto exit; } + + if (!(quoted = psyco_escape_identifier(conn, + Bytes_AS_STRING(ident), Bytes_GET_SIZE(ident)))) { goto exit; } + + result = conn_text_from_chars(conn, quoted); + +exit: + PQfreemem(quoted); + Py_XDECREF(ident); + + return result; +} + +/** type registration **/ +#define register_type_doc \ +"register_type(obj, conn_or_curs) -> None -- register obj with psycopg type system\n\n" \ +":Parameters:\n" \ +" * `obj`: A type adapter created by `new_type()`\n" \ +" * `conn_or_curs`: A connection, cursor or None" + +#define typecast_from_python_doc \ +"new_type(oids, name, castobj) -> new type object\n\n" \ +"Create a new binding object. The object can be used with the\n" \ +"`register_type()` function to bind PostgreSQL objects to python objects.\n\n" \ +":Parameters:\n" \ +" * `oids`: Tuple of ``oid`` of the PostgreSQL types to convert.\n" \ +" * `name`: Name for the new type\n" \ +" * `adapter`: Callable to perform type conversion.\n" \ +" It must have the signature ``fun(value, cur)`` where ``value`` is\n" \ +" the string representation returned by PostgreSQL (`!None` if ``NULL``)\n" \ +" and ``cur`` is the cursor from which data are read." + +#define typecast_array_from_python_doc \ +"new_array_type(oids, name, baseobj) -> new type object\n\n" \ +"Create a new binding object to parse an array.\n\n" \ +"The object can be used with `register_type()`.\n\n" \ +":Parameters:\n" \ +" * `oids`: Tuple of ``oid`` of the PostgreSQL types to convert.\n" \ +" * `name`: Name for the new type\n" \ +" * `baseobj`: Adapter to perform type conversion of a single array item." + +static PyObject * +register_type(PyObject *self, PyObject *args) +{ + PyObject *type, *obj = NULL; + + if (!PyArg_ParseTuple(args, "O!|O", &typecastType, &type, &obj)) { + return NULL; + } + + if (obj != NULL && obj != Py_None) { + if (PyObject_TypeCheck(obj, &cursorType)) { + PyObject **dict = &(((cursorObject*)obj)->string_types); + if (*dict == NULL) { + if (!(*dict = PyDict_New())) { return NULL; } + } + if (0 > typecast_add(type, *dict, 0)) { return NULL; } + } + else if (PyObject_TypeCheck(obj, &connectionType)) { + if (0 > typecast_add(type, ((connectionObject*)obj)->string_types, 0)) { + return NULL; + } + } + else { + PyErr_SetString(PyExc_TypeError, + "argument 2 must be a connection, cursor or None"); + return NULL; + } + } + else { + if (0 > typecast_add(type, NULL, 0)) { return NULL; } + } + + Py_RETURN_NONE; +} + + + +/* Make sure libcrypto thread callbacks are set up. */ +static void +libcrypto_threads_init(void) +{ + PyObject *m; + + Dprintf("psycopgmodule: configuring libpq libcrypto callbacks "); + + /* importing the ssl module sets up Python's libcrypto callbacks */ + if ((m = PyImport_ImportModule("ssl"))) { + /* disable libcrypto setup in libpq, so it won't stomp on the callbacks + that have already been set up */ + PQinitOpenSSL(1, 0); + Py_DECREF(m); + } + else { + /* might mean that Python has been compiled without OpenSSL support, + fall back to relying on libpq's libcrypto locking */ + PyErr_Clear(); + } +} + +/* Initialize the default adapters map + * + * Return 0 on success, else -1 and set an exception. + */ +RAISES_NEG static int +adapters_init(PyObject *module) +{ + PyObject *dict = NULL, *obj = NULL; + int rv = -1; + + if (0 > microprotocols_init(module)) { goto exit; } + + Dprintf("psycopgmodule: initializing adapters"); + + if (0 > microprotocols_add(&PyFloat_Type, NULL, (PyObject*)&pfloatType)) { + goto exit; + } + if (0 > microprotocols_add(&PyLong_Type, NULL, (PyObject*)&pintType)) { + goto exit; + } + if (0 > microprotocols_add(&PyBool_Type, NULL, (PyObject*)&pbooleanType)) { + goto exit; + } + + /* strings */ + if (0 > microprotocols_add(&PyUnicode_Type, NULL, (PyObject*)&qstringType)) { + goto exit; + } + + /* binary */ + if (0 > microprotocols_add(&PyBytes_Type, NULL, (PyObject*)&binaryType)) { + goto exit; + } + + if (0 > microprotocols_add(&PyByteArray_Type, NULL, (PyObject*)&binaryType)) { + goto exit; + } + + if (0 > microprotocols_add(&PyMemoryView_Type, NULL, (PyObject*)&binaryType)) { + goto exit; + } + + if (0 > microprotocols_add(&PyList_Type, NULL, (PyObject*)&listType)) { + goto exit; + } + + /* the module has already been initialized, so we can obtain the callable + objects directly from its dictionary :) */ + if (!(dict = PyModule_GetDict(module))) { goto exit; } + + if (!(obj = PyMapping_GetItemString(dict, "DateFromPy"))) { goto exit; } + if (0 > microprotocols_add(PyDateTimeAPI->DateType, NULL, obj)) { goto exit; } + Py_CLEAR(obj); + + if (!(obj = PyMapping_GetItemString(dict, "TimeFromPy"))) { goto exit; } + if (0 > microprotocols_add(PyDateTimeAPI->TimeType, NULL, obj)) { goto exit; } + Py_CLEAR(obj); + + if (!(obj = PyMapping_GetItemString(dict, "TimestampFromPy"))) { goto exit; } + if (0 > microprotocols_add(PyDateTimeAPI->DateTimeType, NULL, obj)) { goto exit; } + Py_CLEAR(obj); + + if (!(obj = PyMapping_GetItemString(dict, "IntervalFromPy"))) { goto exit; } + if (0 > microprotocols_add(PyDateTimeAPI->DeltaType, NULL, obj)) { goto exit; } + Py_CLEAR(obj); + + /* Success! */ + rv = 0; + +exit: + Py_XDECREF(obj); + + return rv; +} + +#define libpq_version_doc "Query actual libpq version loaded." + +static PyObject* +libpq_version(PyObject *self, PyObject *dummy) +{ + return PyInt_FromLong(PQlibVersion()); +} + +/* encrypt_password - Prepare the encrypted password form */ +#define encrypt_password_doc \ +"encrypt_password(password, user, [scope], [algorithm]) -- Prepares the encrypted form of a PostgreSQL password.\n\n" + +static PyObject * +encrypt_password(PyObject *self, PyObject *args, PyObject *kwargs) +{ + char *encrypted = NULL; + PyObject *password = NULL, *user = NULL; + PyObject *scope = Py_None, *algorithm = Py_None; + PyObject *res = NULL; + connectionObject *conn = NULL; + + static char *kwlist[] = {"password", "user", "scope", "algorithm", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|OO", kwlist, + &password, &user, &scope, &algorithm)) { + return NULL; + } + + /* for ensure_bytes */ + Py_INCREF(user); + Py_INCREF(password); + Py_INCREF(algorithm); + + if (scope != Py_None) { + if (PyObject_TypeCheck(scope, &cursorType)) { + conn = ((cursorObject*)scope)->conn; + } + else if (PyObject_TypeCheck(scope, &connectionType)) { + conn = (connectionObject*)scope; + } + else { + PyErr_SetString(PyExc_TypeError, + "the scope must be a connection or a cursor"); + goto exit; + } + } + + if (!(user = psyco_ensure_bytes(user))) { goto exit; } + if (!(password = psyco_ensure_bytes(password))) { goto exit; } + if (algorithm != Py_None) { + if (!(algorithm = psyco_ensure_bytes(algorithm))) { + goto exit; + } + } + + /* If we have to encrypt md5 we can use the libpq < 10 API */ + if (algorithm != Py_None && + strcmp(Bytes_AS_STRING(algorithm), "md5") == 0) { + encrypted = PQencryptPassword( + Bytes_AS_STRING(password), Bytes_AS_STRING(user)); + } + + /* If the algorithm is not md5 we have to use the API available from + * libpq 10. */ + else { +#if PG_VERSION_NUM >= 100000 + if (!conn) { + PyErr_SetString(ProgrammingError, + "password encryption (other than 'md5' algorithm)" + " requires a connection or cursor"); + goto exit; + } + + /* TODO: algo = None will block: forbid on async/green conn? */ + encrypted = PQencryptPasswordConn(conn->pgconn, + Bytes_AS_STRING(password), Bytes_AS_STRING(user), + algorithm != Py_None ? Bytes_AS_STRING(algorithm) : NULL); +#else + PyErr_SetString(NotSupportedError, + "password encryption (other than 'md5' algorithm)" + " requires libpq 10"); + goto exit; +#endif + } + + if (encrypted) { + res = Text_FromUTF8(encrypted); + } + else { + const char *msg = PQerrorMessage(conn->pgconn); + PyErr_Format(ProgrammingError, + "password encryption failed: %s", msg ? msg : "no reason given"); + goto exit; + } + +exit: + if (encrypted) { + PQfreemem(encrypted); + } + Py_XDECREF(user); + Py_XDECREF(password); + Py_XDECREF(algorithm); + + return res; +} + + +/* Fill the module's postgresql<->python encoding table */ +static struct { + char *pgenc; + char *pyenc; +} enctable[] = { + {"ABC", "cp1258"}, + {"ALT", "cp866"}, + {"BIG5", "big5"}, + {"EUC_CN", "euccn"}, + {"EUC_JIS_2004", "euc_jis_2004"}, + {"EUC_JP", "euc_jp"}, + {"EUC_KR", "euc_kr"}, + {"GB18030", "gb18030"}, + {"GBK", "gbk"}, + {"ISO_8859_1", "iso8859_1"}, + {"ISO_8859_2", "iso8859_2"}, + {"ISO_8859_3", "iso8859_3"}, + {"ISO_8859_5", "iso8859_5"}, + {"ISO_8859_6", "iso8859_6"}, + {"ISO_8859_7", "iso8859_7"}, + {"ISO_8859_8", "iso8859_8"}, + {"ISO_8859_9", "iso8859_9"}, + {"ISO_8859_10", "iso8859_10"}, + {"ISO_8859_13", "iso8859_13"}, + {"ISO_8859_14", "iso8859_14"}, + {"ISO_8859_15", "iso8859_15"}, + {"ISO_8859_16", "iso8859_16"}, + {"JOHAB", "johab"}, + {"KOI8", "koi8_r"}, + {"KOI8R", "koi8_r"}, + {"KOI8U", "koi8_u"}, + {"LATIN1", "iso8859_1"}, + {"LATIN2", "iso8859_2"}, + {"LATIN3", "iso8859_3"}, + {"LATIN4", "iso8859_4"}, + {"LATIN5", "iso8859_9"}, + {"LATIN6", "iso8859_10"}, + {"LATIN7", "iso8859_13"}, + {"LATIN8", "iso8859_14"}, + {"LATIN9", "iso8859_15"}, + {"LATIN10", "iso8859_16"}, + {"Mskanji", "cp932"}, + {"ShiftJIS", "cp932"}, + {"SHIFT_JIS_2004", "shift_jis_2004"}, + {"SJIS", "cp932"}, + {"SQL_ASCII", "ascii"}, /* XXX this is wrong: SQL_ASCII means "no + * encoding" we should fix the unicode + * typecaster to return a str or bytes in Py3 + */ + {"TCVN", "cp1258"}, + {"TCVN5712", "cp1258"}, + {"UHC", "cp949"}, + {"UNICODE", "utf_8"}, /* Not valid in 8.2, backward compatibility */ + {"UTF8", "utf_8"}, + {"VSCII", "cp1258"}, + {"WIN", "cp1251"}, + {"WIN866", "cp866"}, + {"WIN874", "cp874"}, + {"WIN932", "cp932"}, + {"WIN936", "gbk"}, + {"WIN949", "cp949"}, + {"WIN950", "cp950"}, + {"WIN1250", "cp1250"}, + {"WIN1251", "cp1251"}, + {"WIN1252", "cp1252"}, + {"WIN1253", "cp1253"}, + {"WIN1254", "cp1254"}, + {"WIN1255", "cp1255"}, + {"WIN1256", "cp1256"}, + {"WIN1257", "cp1257"}, + {"WIN1258", "cp1258"}, + {"Windows932", "cp932"}, + {"Windows936", "gbk"}, + {"Windows949", "cp949"}, + {"Windows950", "cp950"}, + +/* those are missing from Python: */ +/* {"EUC_TW", "?"}, */ +/* {"MULE_INTERNAL", "?"}, */ + {NULL, NULL} +}; + +/* Initialize the encodings table. + * + * Return 0 on success, else -1 and set an exception. + */ +RAISES_NEG static int +encodings_init(PyObject *module) +{ + PyObject *value = NULL; + int i; + int rv = -1; + + Dprintf("psycopgmodule: initializing encodings table"); + if (psycoEncodings) { + Dprintf("encodings_init(): already called"); + return 0; + } + + if (!(psycoEncodings = PyDict_New())) { goto exit; } + Py_INCREF(psycoEncodings); + if (0 > PyModule_AddObject(module, "encodings", psycoEncodings)) { + Py_DECREF(psycoEncodings); + goto exit; + } + + for (i = 0; enctable[i].pgenc != NULL; i++) { + if (!(value = Text_FromUTF8(enctable[i].pyenc))) { goto exit; } + if (0 > PyDict_SetItemString( + psycoEncodings, enctable[i].pgenc, value)) { + goto exit; + } + Py_CLEAR(value); + } + rv = 0; + +exit: + Py_XDECREF(value); + + return rv; +} + +/* Initialize the module's exceptions and after that a dictionary with a full + set of exceptions. */ + +PyObject *Error, *Warning, *InterfaceError, *DatabaseError, + *InternalError, *OperationalError, *ProgrammingError, + *IntegrityError, *DataError, *NotSupportedError; +PyObject *QueryCanceledError, *TransactionRollbackError; + +/* mapping between exception names and their PyObject */ +static struct { + char *name; + PyObject **exc; + PyObject **base; + const char *docstr; +} exctable[] = { + { "psycopg2.Error", &Error, NULL, Error_doc }, + { "psycopg2.Warning", &Warning, NULL, Warning_doc }, + { "psycopg2.InterfaceError", &InterfaceError, &Error, InterfaceError_doc }, + { "psycopg2.DatabaseError", &DatabaseError, &Error, DatabaseError_doc }, + { "psycopg2.InternalError", &InternalError, &DatabaseError, InternalError_doc }, + { "psycopg2.OperationalError", &OperationalError, &DatabaseError, + OperationalError_doc }, + { "psycopg2.ProgrammingError", &ProgrammingError, &DatabaseError, + ProgrammingError_doc }, + { "psycopg2.IntegrityError", &IntegrityError, &DatabaseError, + IntegrityError_doc }, + { "psycopg2.DataError", &DataError, &DatabaseError, DataError_doc }, + { "psycopg2.NotSupportedError", &NotSupportedError, &DatabaseError, + NotSupportedError_doc }, + { "psycopg2.extensions.QueryCanceledError", &QueryCanceledError, + &OperationalError, QueryCanceledError_doc }, + { "psycopg2.extensions.TransactionRollbackError", + &TransactionRollbackError, &OperationalError, + TransactionRollbackError_doc }, + {NULL} /* Sentinel */ +}; + + +RAISES_NEG static int +basic_errors_init(PyObject *module) +{ + /* the names of the exceptions here reflect the organization of the + psycopg2 module and not the fact the original error objects live in + _psycopg */ + + int i; + PyObject *dict = NULL; + PyObject *str = NULL; + PyObject *errmodule = NULL; + int rv = -1; + + Dprintf("psycopgmodule: initializing basic exceptions"); + + /* 'Error' has been defined elsewhere: only init the other classes */ + Error = (PyObject *)&errorType; + + for (i = 1; exctable[i].name; i++) { + if (!(dict = PyDict_New())) { goto exit; } + + if (exctable[i].docstr) { + if (!(str = Text_FromUTF8(exctable[i].docstr))) { goto exit; } + if (0 > PyDict_SetItemString(dict, "__doc__", str)) { goto exit; } + Py_CLEAR(str); + } + + /* can't put PyExc_StandardError in the static exctable: + * windows build will fail */ + if (!(*exctable[i].exc = PyErr_NewException( + exctable[i].name, + exctable[i].base ? *exctable[i].base : PyExc_StandardError, + dict))) { + goto exit; + } + Py_CLEAR(dict); + } + + if (!(errmodule = PyImport_ImportModule("psycopg2.errors"))) { + /* don't inject the exceptions into the errors module */ + PyErr_Clear(); + } + + for (i = 0; exctable[i].name; i++) { + char *name; + if (NULL == exctable[i].exc) { continue; } + + /* the name is the part after the last dot */ + name = strrchr(exctable[i].name, '.'); + name = name ? name + 1 : exctable[i].name; + + Py_INCREF(*exctable[i].exc); + if (0 > PyModule_AddObject(module, name, *exctable[i].exc)) { + Py_DECREF(*exctable[i].exc); + goto exit; + } + if (errmodule) { + Py_INCREF(*exctable[i].exc); + if (0 > PyModule_AddObject(errmodule, name, *exctable[i].exc)) { + Py_DECREF(*exctable[i].exc); + goto exit; + } + } + } + + rv = 0; + +exit: + Py_XDECREF(errmodule); + Py_XDECREF(str); + Py_XDECREF(dict); + return rv; +} + + +/* mapping between sqlstate and exception name */ +static struct { + char *sqlstate; + char *name; +} sqlstate_table[] = { +#include "sqlstate_errors.h" + {NULL} /* Sentinel */ +}; + + +RAISES_NEG static int +sqlstate_errors_init(PyObject *module) +{ + int i; + char namebuf[120]; + char prefix[] = "psycopg2.errors."; + char *suffix; + size_t bufsize; + PyObject *exc = NULL; + PyObject *errmodule = NULL; + int rv = -1; + + Dprintf("psycopgmodule: initializing sqlstate exceptions"); + + if (sqlstate_errors) { + Dprintf("sqlstate_errors_init(): already called"); + return 0; + } + if (!(errmodule = PyImport_ImportModule("psycopg2.errors"))) { + /* don't inject the exceptions into the errors module */ + PyErr_Clear(); + } + if (!(sqlstate_errors = PyDict_New())) { + goto exit; + } + Py_INCREF(sqlstate_errors); + if (0 > PyModule_AddObject(module, "sqlstate_errors", sqlstate_errors)) { + Py_DECREF(sqlstate_errors); + return -1; + } + + strcpy(namebuf, prefix); + suffix = namebuf + sizeof(prefix) - 1; + bufsize = sizeof(namebuf) - sizeof(prefix) - 1; + /* If this 0 gets deleted the buffer was too small. */ + namebuf[sizeof(namebuf) - 1] = '\0'; + + for (i = 0; sqlstate_table[i].sqlstate; i++) { + PyObject *base; + + base = base_exception_from_sqlstate(sqlstate_table[i].sqlstate); + strncpy(suffix, sqlstate_table[i].name, bufsize); + if (namebuf[sizeof(namebuf) - 1] != '\0') { + PyErr_SetString( + PyExc_SystemError, "sqlstate_errors_init(): buffer too small"); + goto exit; + } + if (!(exc = PyErr_NewException(namebuf, base, NULL))) { + goto exit; + } + if (0 > PyDict_SetItemString( + sqlstate_errors, sqlstate_table[i].sqlstate, exc)) { + goto exit; + } + + /* Expose the exceptions to psycopg2.errors */ + if (errmodule) { + if (0 > PyModule_AddObject( + errmodule, sqlstate_table[i].name, exc)) { + goto exit; + } + else { + exc = NULL; /* ref stolen by the module */ + } + } + else { + Py_CLEAR(exc); + } + } + + rv = 0; + +exit: + Py_XDECREF(errmodule); + Py_XDECREF(exc); + return rv; +} + + +RAISES_NEG static int +add_module_constants(PyObject *module) +{ + PyObject *tmp; + Dprintf("psycopgmodule: initializing module constants"); + + if (0 > PyModule_AddStringConstant(module, + "__version__", xstr(PSYCOPG_VERSION))) + { return -1; } + + if (0 > PyModule_AddStringConstant(module, + "__doc__", "psycopg2 PostgreSQL driver")) + { return -1; } + + if (0 > PyModule_AddIntConstant(module, + "__libpq_version__", PG_VERSION_NUM)) + { return -1; } + + if (0 > PyModule_AddObject(module, + "apilevel", tmp = Text_FromUTF8(APILEVEL))) + { + Py_XDECREF(tmp); + return -1; + } + + if (0 > PyModule_AddObject(module, + "threadsafety", tmp = PyInt_FromLong(THREADSAFETY))) + { + Py_XDECREF(tmp); + return -1; + } + + if (0 > PyModule_AddObject(module, + "paramstyle", tmp = Text_FromUTF8(PARAMSTYLE))) + { + Py_XDECREF(tmp); + return -1; + } + + if (0 > PyModule_AddIntMacro(module, REPLICATION_PHYSICAL)) { return -1; } + if (0 > PyModule_AddIntMacro(module, REPLICATION_LOGICAL)) { return -1; } + + return 0; +} + + +static struct { + char *name; + PyTypeObject *type; +} typetable[] = { + { "connection", &connectionType }, + { "cursor", &cursorType }, + { "ReplicationConnection", &replicationConnectionType }, + { "ReplicationCursor", &replicationCursorType }, + { "ReplicationMessage", &replicationMessageType }, + { "ISQLQuote", &isqlquoteType }, + { "Column", &columnType }, + { "Notify", ¬ifyType }, + { "Xid", &xidType }, + { "ConnectionInfo", &connInfoType }, + { "Diagnostics", &diagnosticsType }, + { "AsIs", &asisType }, + { "Binary", &binaryType }, + { "Boolean", &pbooleanType }, + { "Decimal", &pdecimalType }, + { "Int", &pintType }, + { "Float", &pfloatType }, + { "List", &listType }, + { "QuotedString", &qstringType }, + { "lobject", &lobjectType }, + {NULL} /* Sentinel */ +}; + +RAISES_NEG static int +add_module_types(PyObject *module) +{ + int i; + + Dprintf("psycopgmodule: initializing module types"); + + for (i = 0; typetable[i].name; i++) { + PyObject *type = (PyObject *)typetable[i].type; + + Py_SET_TYPE(typetable[i].type, &PyType_Type); + if (0 > PyType_Ready(typetable[i].type)) { return -1; } + + Py_INCREF(type); + if (0 > PyModule_AddObject(module, typetable[i].name, type)) { + Py_DECREF(type); + return -1; + } + } + return 0; +} + + +RAISES_NEG static int +datetime_init(void) +{ + PyObject *dt = NULL; + + Dprintf("psycopgmodule: initializing datetime module"); + + /* import python builtin datetime module, if available */ + if (!(dt = PyImport_ImportModule("datetime"))) { + return -1; + } + Py_DECREF(dt); + + /* Initialize the PyDateTimeAPI everywhere is used */ + PyDateTime_IMPORT; + if (0 > adapter_datetime_init()) { return -1; } + if (0 > repl_curs_datetime_init()) { return -1; } + if (0 > replmsg_datetime_init()) { return -1; } + + Py_SET_TYPE(&pydatetimeType, &PyType_Type); + if (0 > PyType_Ready(&pydatetimeType)) { return -1; } + + return 0; +} + +/** method table and module initialization **/ + +static PyMethodDef psycopgMethods[] = { + {"_connect", (PyCFunction)psyco_connect, + METH_VARARGS|METH_KEYWORDS, psyco_connect_doc}, + {"parse_dsn", (PyCFunction)parse_dsn, + METH_VARARGS|METH_KEYWORDS, parse_dsn_doc}, + {"quote_ident", (PyCFunction)quote_ident, + METH_VARARGS|METH_KEYWORDS, quote_ident_doc}, + {"adapt", (PyCFunction)psyco_microprotocols_adapt, + METH_VARARGS, psyco_microprotocols_adapt_doc}, + + {"register_type", (PyCFunction)register_type, + METH_VARARGS, register_type_doc}, + {"new_type", (PyCFunction)typecast_from_python, + METH_VARARGS|METH_KEYWORDS, typecast_from_python_doc}, + {"new_array_type", (PyCFunction)typecast_array_from_python, + METH_VARARGS|METH_KEYWORDS, typecast_array_from_python_doc}, + {"libpq_version", (PyCFunction)libpq_version, + METH_NOARGS, libpq_version_doc}, + + {"Date", (PyCFunction)psyco_Date, + METH_VARARGS, psyco_Date_doc}, + {"Time", (PyCFunction)psyco_Time, + METH_VARARGS, psyco_Time_doc}, + {"Timestamp", (PyCFunction)psyco_Timestamp, + METH_VARARGS, psyco_Timestamp_doc}, + {"DateFromTicks", (PyCFunction)psyco_DateFromTicks, + METH_VARARGS, psyco_DateFromTicks_doc}, + {"TimeFromTicks", (PyCFunction)psyco_TimeFromTicks, + METH_VARARGS, psyco_TimeFromTicks_doc}, + {"TimestampFromTicks", (PyCFunction)psyco_TimestampFromTicks, + METH_VARARGS, psyco_TimestampFromTicks_doc}, + + {"DateFromPy", (PyCFunction)psyco_DateFromPy, + METH_VARARGS, psyco_DateFromPy_doc}, + {"TimeFromPy", (PyCFunction)psyco_TimeFromPy, + METH_VARARGS, psyco_TimeFromPy_doc}, + {"TimestampFromPy", (PyCFunction)psyco_TimestampFromPy, + METH_VARARGS, psyco_TimestampFromPy_doc}, + {"IntervalFromPy", (PyCFunction)psyco_IntervalFromPy, + METH_VARARGS, psyco_IntervalFromPy_doc}, + + {"set_wait_callback", (PyCFunction)psyco_set_wait_callback, + METH_O, psyco_set_wait_callback_doc}, + {"get_wait_callback", (PyCFunction)psyco_get_wait_callback, + METH_NOARGS, psyco_get_wait_callback_doc}, + {"encrypt_password", (PyCFunction)encrypt_password, + METH_VARARGS|METH_KEYWORDS, encrypt_password_doc}, + + {NULL, NULL, 0, NULL} /* Sentinel */ +}; + +static struct PyModuleDef psycopgmodule = { + PyModuleDef_HEAD_INIT, + "_psycopg", + NULL, + -1, + psycopgMethods, + NULL, + NULL, + NULL, + NULL +}; + +#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ +#define PyMODINIT_FUNC void +#endif +PyMODINIT_FUNC +INIT_MODULE(_psycopg)(void) +{ + PyObject *module = NULL; + +#ifdef PSYCOPG_DEBUG + if (getenv("PSYCOPG_DEBUG")) + psycopg_debug_enabled = 1; +#endif + + Dprintf("psycopgmodule: initializing psycopg %s", xstr(PSYCOPG_VERSION)); + + /* initialize libcrypto threading callbacks */ + libcrypto_threads_init(); + + /* initialize types and objects not exposed to the module */ + Py_SET_TYPE(&typecastType, &PyType_Type); + if (0 > PyType_Ready(&typecastType)) { goto error; } + + Py_SET_TYPE(&chunkType, &PyType_Type); + if (0 > PyType_Ready(&chunkType)) { goto error; } + + Py_SET_TYPE(&errorType, &PyType_Type); + errorType.tp_base = (PyTypeObject *)PyExc_StandardError; + if (0 > PyType_Ready(&errorType)) { goto error; } + + if (!(psyco_null = Bytes_FromString("NULL"))) { goto error; } + + /* initialize the module */ + module = PyModule_Create(&psycopgmodule); + if (!module) { goto error; } + + if (0 > add_module_constants(module)) { goto error; } + if (0 > add_module_types(module)) { goto error; } + if (0 > datetime_init()) { goto error; } + if (0 > encodings_init(module)) { goto error; } + if (0 > typecast_init(module)) { goto error; } + if (0 > adapters_init(module)) { goto error; } + if (0 > basic_errors_init(module)) { goto error; } + if (0 > sqlstate_errors_init(module)) { goto error; } + + Dprintf("psycopgmodule: module initialization complete"); + return module; + +error: + if (module) + Py_DECREF(module); + return NULL; +} diff --git a/source-code/psycopg2/psycopg/python.h b/source-code/psycopg2/psycopg/python.h new file mode 100644 index 0000000..37de231 --- /dev/null +++ b/source-code/psycopg2/psycopg/python.h @@ -0,0 +1,99 @@ +/* python.h - python version compatibility stuff + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_PYTHON_H +#define PSYCOPG_PYTHON_H 1 + +#if PY_VERSION_HEX < 0x03070000 +#error "psycopg requires Python 3.7" +#endif + +#include + +/* Since Py_TYPE() is changed to the inline static function, + * Py_TYPE(obj) = new_type must be replaced with Py_SET_TYPE(obj, new_type) + * https://docs.python.org/3.10/whatsnew/3.10.html#id2 + */ +#if PY_VERSION_HEX < 0x030900A4 + #define Py_SET_TYPE(obj, type) ((Py_TYPE(obj) = (type)), (void)0) +#endif + +/* FORMAT_CODE_PY_SSIZE_T is for Py_ssize_t: */ +#define FORMAT_CODE_PY_SSIZE_T "%" PY_FORMAT_SIZE_T "d" + +/* FORMAT_CODE_SIZE_T is for plain size_t, not for Py_ssize_t: */ +#ifdef _MSC_VER + /* For MSVC: */ + #define FORMAT_CODE_SIZE_T "%Iu" +#else + /* C99 standard format code: */ + #define FORMAT_CODE_SIZE_T "%zu" +#endif + +#define Text_Type PyUnicode_Type +#define Text_Check(s) PyUnicode_Check(s) +#define Text_Format(f,a) PyUnicode_Format(f,a) +#define Text_FromUTF8(s) PyUnicode_FromString(s) +#define Text_FromUTF8AndSize(s,n) PyUnicode_FromStringAndSize(s,n) + +#define PyInt_Type PyLong_Type +#define PyInt_Check PyLong_Check +#define PyInt_AsLong PyLong_AsLong +#define PyInt_FromLong PyLong_FromLong +#define PyInt_FromString PyLong_FromString +#define PyInt_FromSsize_t PyLong_FromSsize_t +#define PyExc_StandardError PyExc_Exception +#define PyString_FromFormat PyUnicode_FromFormat +#define Py_TPFLAGS_HAVE_ITER 0L +#define Py_TPFLAGS_HAVE_RICHCOMPARE 0L +#define Py_TPFLAGS_HAVE_WEAKREFS 0L + +#ifndef PyNumber_Int +#define PyNumber_Int PyNumber_Long +#endif + +#define Bytes_Type PyBytes_Type +#define Bytes_Check PyBytes_Check +#define Bytes_CheckExact PyBytes_CheckExact +#define Bytes_AS_STRING PyBytes_AS_STRING +#define Bytes_GET_SIZE PyBytes_GET_SIZE +#define Bytes_Size PyBytes_Size +#define Bytes_AsString PyBytes_AsString +#define Bytes_AsStringAndSize PyBytes_AsStringAndSize +#define Bytes_FromString PyBytes_FromString +#define Bytes_FromStringAndSize PyBytes_FromStringAndSize +#define Bytes_FromFormat PyBytes_FromFormat +#define Bytes_ConcatAndDel PyBytes_ConcatAndDel +#define _Bytes_Resize _PyBytes_Resize + +#define INIT_MODULE(m) PyInit_ ## m + +#define PyLong_FromOid(x) (PyLong_FromUnsignedLong((unsigned long)(x))) + +/* expose Oid attributes in Python C objects */ +#define T_OID T_UINT + +#endif /* !defined(PSYCOPG_PYTHON_H) */ diff --git a/source-code/psycopg2/psycopg/replication_connection.h b/source-code/psycopg2/psycopg/replication_connection.h new file mode 100644 index 0000000..bf3c91c --- /dev/null +++ b/source-code/psycopg2/psycopg/replication_connection.h @@ -0,0 +1,53 @@ +/* replication_connection.h - definition for the psycopg replication connection type + * + * Copyright (C) 2015-2019 Daniele Varrazzo + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_REPLICATION_CONNECTION_H +#define PSYCOPG_REPLICATION_CONNECTION_H 1 + +#include "psycopg/connection.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern HIDDEN PyTypeObject replicationConnectionType; + +typedef struct replicationConnectionObject { + connectionObject conn; + + long int type; +} replicationConnectionObject; + +/* The funny constant values should help to avoid mixups with some + commonly used numbers like 1 and 2. */ +#define REPLICATION_PHYSICAL 12345678 +#define REPLICATION_LOGICAL 87654321 + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_REPLICATION_CONNECTION_H) */ diff --git a/source-code/psycopg2/psycopg/replication_connection_type.c b/source-code/psycopg2/psycopg/replication_connection_type.c new file mode 100644 index 0000000..7e51904 --- /dev/null +++ b/source-code/psycopg2/psycopg/replication_connection_type.c @@ -0,0 +1,198 @@ +/* replication_connection_type.c - python interface to replication connection objects + * + * Copyright (C) 2015-2019 Daniele Varrazzo + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/replication_connection.h" +#include "psycopg/replication_message.h" +#include "psycopg/green.h" +#include "psycopg/pqpath.h" + +#include +#include + + +#define psyco_repl_conn_type_doc \ +"replication_type -- the replication connection type" + +static PyObject * +psyco_repl_conn_get_type(replicationConnectionObject *self) +{ + return PyInt_FromLong(self->type); +} + + +static int +replicationConnection_init(replicationConnectionObject *self, + PyObject *args, PyObject *kwargs) +{ + PyObject *dsn = NULL, *async = Py_False, + *item = NULL, *extras = NULL, *cursor = NULL, + *newdsn = NULL, *newargs = NULL, *dsnopts = NULL; + int ret = -1; + long int replication_type; + + /* 'replication_type' is not actually optional, but there's no + good way to put it before 'async' in the list */ + static char *kwlist[] = {"dsn", "async", "replication_type", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|Ol", kwlist, + &dsn, &async, &replication_type)) { + return ret; + } + + /* + We have to call make_dsn() to add replication-specific + connection parameters, because the DSN might be an URI (if there + were no keyword arguments to connect() it is passed unchanged). + */ + if (!(dsnopts = PyDict_New())) { return ret; } + + /* all the nice stuff is located in python-level ReplicationCursor class */ + if (!(extras = PyImport_ImportModule("psycopg2.extras"))) { goto exit; } + if (!(cursor = PyObject_GetAttrString(extras, "ReplicationCursor"))) { goto exit; } + + if (replication_type == REPLICATION_PHYSICAL) { + self->type = REPLICATION_PHYSICAL; + +#define SET_ITEM(k, v) \ + if (!(item = Text_FromUTF8(#v))) { goto exit; } \ + if (PyDict_SetItemString(dsnopts, #k, item) != 0) { goto exit; } \ + Py_DECREF(item); \ + item = NULL; + + SET_ITEM(replication, true); + SET_ITEM(dbname, replication); /* required for .pgpass lookup */ + } else if (replication_type == REPLICATION_LOGICAL) { + self->type = REPLICATION_LOGICAL; + + SET_ITEM(replication, database); +#undef SET_ITEM + } else { + PyErr_SetString(PyExc_TypeError, + "replication_type must be either " + "REPLICATION_PHYSICAL or REPLICATION_LOGICAL"); + goto exit; + } + + if (!(newdsn = psyco_make_dsn(dsn, dsnopts))) { goto exit; } + if (!(newargs = PyTuple_Pack(2, newdsn, async))) { goto exit; } + + /* only attempt the connection once we've handled all possible errors */ + if ((ret = connectionType.tp_init((PyObject *)self, newargs, NULL)) < 0) { + goto exit; + } + + self->conn.autocommit = 1; + Py_INCREF(cursor); + self->conn.cursor_factory = cursor; + +exit: + Py_XDECREF(item); + Py_XDECREF(extras); + Py_XDECREF(cursor); + Py_XDECREF(newdsn); + Py_XDECREF(newargs); + Py_XDECREF(dsnopts); + + return ret; +} + +static PyObject * +replicationConnection_repr(replicationConnectionObject *self) +{ + return PyString_FromFormat( + "", + self, self->conn.dsn, self->conn.closed); +} + +static int +replicationConnectionType_traverse(PyObject *self, visitproc visit, void *arg) +{ + return connectionType.tp_traverse(self, visit, arg); +} + +/* object calculated member list */ + +static struct PyGetSetDef replicationConnectionObject_getsets[] = { + /* override to prevent user tweaking these: */ + { "autocommit", NULL, NULL, NULL }, + { "isolation_level", NULL, NULL, NULL }, + { "set_session", NULL, NULL, NULL }, + { "set_isolation_level", NULL, NULL, NULL }, + { "reset", NULL, NULL, NULL }, + /* an actual getter */ + { "replication_type", + (getter)psyco_repl_conn_get_type, NULL, + psyco_repl_conn_type_doc, NULL }, + {NULL} +}; + +/* object type */ + +#define replicationConnectionType_doc \ +"A replication connection." + +PyTypeObject replicationConnectionType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2.extensions.ReplicationConnection", + sizeof(replicationConnectionObject), 0, + 0, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + (reprfunc)replicationConnection_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ + (reprfunc)replicationConnection_repr, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_ITER | + Py_TPFLAGS_HAVE_GC, /*tp_flags*/ + replicationConnectionType_doc, /*tp_doc*/ + replicationConnectionType_traverse, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ + 0, /*tp_members*/ + replicationConnectionObject_getsets, /*tp_getset*/ + &connectionType, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + (initproc)replicationConnection_init, /*tp_init*/ + 0, /*tp_alloc*/ + 0, /*tp_new*/ +}; diff --git a/source-code/psycopg2/psycopg/replication_cursor.h b/source-code/psycopg2/psycopg/replication_cursor.h new file mode 100644 index 0000000..d102d73 --- /dev/null +++ b/source-code/psycopg2/psycopg/replication_cursor.h @@ -0,0 +1,66 @@ +/* replication_cursor.h - definition for the psycopg replication cursor type + * + * Copyright (C) 2015-2019 Daniele Varrazzo + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_REPLICATION_CURSOR_H +#define PSYCOPG_REPLICATION_CURSOR_H 1 + +#include "psycopg/cursor.h" +#include "libpq_support.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern HIDDEN PyTypeObject replicationCursorType; + +typedef struct replicationCursorObject { + cursorObject cur; + + int consuming:1; /* if running the consume loop */ + int decode:1; /* if we should use character decoding on the messages */ + + struct timeval last_io; /* timestamp of the last exchange with the server */ + struct timeval status_interval; /* time between status packets sent to the server */ + + XLogRecPtr write_lsn; /* LSNs for replication feedback messages */ + XLogRecPtr flush_lsn; + XLogRecPtr apply_lsn; + + XLogRecPtr wal_end; /* WAL end pointer from the last exchange with the server */ + + XLogRecPtr last_msg_data_start; /* WAL pointer to the last non-keepalive message from the server */ + struct timeval last_feedback; /* timestamp of the last feedback message to the server */ + XLogRecPtr explicitly_flushed_lsn; /* the flush LSN explicitly set by the send_feedback call */ +} replicationCursorObject; + + +RAISES_NEG HIDDEN int repl_curs_datetime_init(void); + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_REPLICATION_CURSOR_H) */ diff --git a/source-code/psycopg2/psycopg/replication_cursor_type.c b/source-code/psycopg2/psycopg/replication_cursor_type.c new file mode 100644 index 0000000..76f7563 --- /dev/null +++ b/source-code/psycopg2/psycopg/replication_cursor_type.c @@ -0,0 +1,402 @@ +/* replication_cursor_type.c - python interface to replication cursor objects + * + * Copyright (C) 2015-2019 Daniele Varrazzo + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/replication_cursor.h" +#include "psycopg/replication_message.h" +#include "psycopg/green.h" +#include "psycopg/pqpath.h" + +#include +#include +#ifndef _WIN32 +#include +#endif + +/* python */ +#include "datetime.h" + + +static void set_status_interval(replicationCursorObject *self, double status_interval) +{ + self->status_interval.tv_sec = (int)status_interval; + self->status_interval.tv_usec = (long)((status_interval - self->status_interval.tv_sec)*1.0e6); +} + +#define start_replication_expert_doc \ +"start_replication_expert(command, decode=False, status_interval=10) -- Start replication with a given command." + +static PyObject * +start_replication_expert(replicationCursorObject *self, + PyObject *args, PyObject *kwargs) +{ + cursorObject *curs = &self->cur; + connectionObject *conn = self->cur.conn; + PyObject *res = NULL; + PyObject *command = NULL; + double status_interval = 10; + long int decode = 0; + static char *kwlist[] = {"command", "decode", "status_interval", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|ld", kwlist, + &command, &decode, &status_interval)) { + return NULL; + } + + EXC_IF_CURS_CLOSED(curs); + EXC_IF_GREEN(start_replication_expert); + EXC_IF_TPC_PREPARED(conn, start_replication_expert); + + if (!(command = curs_validate_sql_basic((cursorObject *)self, command))) { + goto exit; + } + + if (status_interval < 1.0) { + psyco_set_error(ProgrammingError, curs, "status_interval must be >= 1 (sec)"); + return NULL; + } + + Dprintf("start_replication_expert: '%s'; decode: %ld", + Bytes_AS_STRING(command), decode); + + if (pq_execute(curs, Bytes_AS_STRING(command), conn->async, + 1 /* no_result */, 1 /* no_begin */) >= 0) { + res = Py_None; + Py_INCREF(res); + + set_status_interval(self, status_interval); + self->decode = decode; + gettimeofday(&self->last_io, NULL); + } + +exit: + Py_XDECREF(command); + return res; +} + +#define consume_stream_doc \ +"consume_stream(consumer, keepalive_interval=None) -- Consume replication stream." + +static PyObject * +consume_stream(replicationCursorObject *self, + PyObject *args, PyObject *kwargs) +{ + cursorObject *curs = &self->cur; + PyObject *consume = NULL, *interval = NULL, *res = NULL; + double keepalive_interval = 0; + static char *kwlist[] = {"consume", "keepalive_interval", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O", kwlist, + &consume, &interval)) { + return NULL; + } + + EXC_IF_CURS_CLOSED(curs); + EXC_IF_CURS_ASYNC(curs, consume_stream); + EXC_IF_GREEN(consume_stream); + EXC_IF_TPC_PREPARED(self->cur.conn, consume_stream); + + Dprintf("consume_stream"); + + if (interval && interval != Py_None) { + + if (PyFloat_Check(interval)) { + keepalive_interval = PyFloat_AsDouble(interval); + } else if (PyLong_Check(interval)) { + keepalive_interval = PyLong_AsDouble(interval); + } else if (PyInt_Check(interval)) { + keepalive_interval = PyInt_AsLong(interval); + } else { + psyco_set_error(ProgrammingError, curs, "keepalive_interval must be int or float"); + return NULL; + } + + if (keepalive_interval < 1.0) { + psyco_set_error(ProgrammingError, curs, "keepalive_interval must be >= 1 (sec)"); + return NULL; + } + } + + if (self->consuming) { + PyErr_SetString(ProgrammingError, + "consume_stream cannot be used when already in the consume loop"); + return NULL; + } + + if (curs->pgres == NULL || PQresultStatus(curs->pgres) != PGRES_COPY_BOTH) { + PyErr_SetString(ProgrammingError, + "consume_stream: not replicating, call start_replication first"); + return NULL; + } + CLEARPGRES(curs->pgres); + + self->consuming = 1; + if (keepalive_interval > 0) { + set_status_interval(self, keepalive_interval); + } + + if (pq_copy_both(self, consume) >= 0) { + res = Py_None; + Py_INCREF(res); + } + + self->consuming = 0; + + return res; +} + +#define read_message_doc \ +"read_message() -- Try reading a replication message from the server (non-blocking)." + +static PyObject * +read_message(replicationCursorObject *self, PyObject *dummy) +{ + cursorObject *curs = &self->cur; + replicationMessageObject *msg = NULL; + + EXC_IF_CURS_CLOSED(curs); + EXC_IF_GREEN(read_message); + EXC_IF_TPC_PREPARED(self->cur.conn, read_message); + + if (pq_read_replication_message(self, &msg) < 0) { + return NULL; + } + if (msg) { + return (PyObject *)msg; + } + + Py_RETURN_NONE; +} + +#define send_feedback_doc \ +"send_feedback(write_lsn=0, flush_lsn=0, apply_lsn=0, reply=False, force=False) -- Update a replication feedback, optionally request a reply or force sending a feedback message regardless of the timeout." + +static PyObject * +send_feedback(replicationCursorObject *self, + PyObject *args, PyObject *kwargs) +{ + cursorObject *curs = &self->cur; + XLogRecPtr write_lsn = 0, flush_lsn = 0, apply_lsn = 0; + int reply = 0, force = 0; + static char* kwlist[] = {"write_lsn", "flush_lsn", "apply_lsn", "reply", "force", NULL}; + + EXC_IF_CURS_CLOSED(curs); + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|KKKii", kwlist, + &write_lsn, &flush_lsn, &apply_lsn, &reply, &force)) { + return NULL; + } + + if (write_lsn > self->write_lsn) + self->write_lsn = write_lsn; + + if (flush_lsn > self->explicitly_flushed_lsn) + self->explicitly_flushed_lsn = flush_lsn; + + if (flush_lsn > self->flush_lsn) + self->flush_lsn = flush_lsn; + + if (apply_lsn > self->apply_lsn) + self->apply_lsn = apply_lsn; + + if ((force || reply) && pq_send_replication_feedback(self, reply) < 0) { + return NULL; + } + + Py_RETURN_NONE; +} + + +RAISES_NEG int +repl_curs_datetime_init(void) +{ + PyDateTime_IMPORT; + + if (!PyDateTimeAPI) { + PyErr_SetString(PyExc_ImportError, "datetime initialization failed"); + return -1; + } + return 0; +} + +#define repl_curs_io_timestamp_doc \ +"io_timestamp -- the timestamp of latest IO with the server" + +static PyObject * +repl_curs_get_io_timestamp(replicationCursorObject *self) +{ + cursorObject *curs = &self->cur; + PyObject *tval, *res = NULL; + double seconds; + + EXC_IF_CURS_CLOSED(curs); + + seconds = self->last_io.tv_sec + self->last_io.tv_usec / 1.0e6; + + tval = Py_BuildValue("(d)", seconds); + if (tval) { + res = PyDateTime_FromTimestamp(tval); + Py_DECREF(tval); + } + return res; +} + +#define repl_curs_feedback_timestamp_doc \ +"feedback_timestamp -- the timestamp of the latest feedback message sent to the server" + +static PyObject * +repl_curs_get_feedback_timestamp(replicationCursorObject *self) +{ + cursorObject *curs = &self->cur; + PyObject *tval, *res = NULL; + double seconds; + + EXC_IF_CURS_CLOSED(curs); + + seconds = self->last_feedback.tv_sec + self->last_feedback.tv_usec / 1.0e6; + + tval = Py_BuildValue("(d)", seconds); + if (tval) { + res = PyDateTime_FromTimestamp(tval); + Py_DECREF(tval); + } + return res; +} + +/* object member list */ + +#define OFFSETOF(x) offsetof(replicationCursorObject, x) + +static struct PyMemberDef replicationCursorObject_members[] = { + {"wal_end", T_ULONGLONG, OFFSETOF(wal_end), READONLY, + "LSN position of the current end of WAL on the server."}, + {NULL} +}; + + +/* object method list */ + +static struct PyMethodDef replicationCursorObject_methods[] = { + {"start_replication_expert", (PyCFunction)start_replication_expert, + METH_VARARGS|METH_KEYWORDS, start_replication_expert_doc}, + {"consume_stream", (PyCFunction)consume_stream, + METH_VARARGS|METH_KEYWORDS, consume_stream_doc}, + {"read_message", (PyCFunction)read_message, + METH_NOARGS, read_message_doc}, + {"send_feedback", (PyCFunction)send_feedback, + METH_VARARGS|METH_KEYWORDS, send_feedback_doc}, + {NULL} +}; + +/* object calculated member list */ + +static struct PyGetSetDef replicationCursorObject_getsets[] = { + { "io_timestamp", + (getter)repl_curs_get_io_timestamp, NULL, + repl_curs_io_timestamp_doc, NULL }, + { "feedback_timestamp", + (getter)repl_curs_get_feedback_timestamp, NULL, + repl_curs_feedback_timestamp_doc, NULL }, + {NULL} +}; + +static int +replicationCursor_init(PyObject *obj, PyObject *args, PyObject *kwargs) +{ + replicationCursorObject *self = (replicationCursorObject *)obj; + + self->consuming = 0; + self->decode = 0; + + self->wal_end = 0; + + self->write_lsn = 0; + self->flush_lsn = 0; + self->apply_lsn = 0; + + return cursorType.tp_init(obj, args, kwargs); +} + +static PyObject * +replicationCursor_repr(replicationCursorObject *self) +{ + return PyString_FromFormat( + "", self, self->cur.closed); +} + +static int +replicationCursorType_traverse(PyObject *self, visitproc visit, void *arg) +{ + return cursorType.tp_traverse(self, visit, arg); +} + +/* object type */ + +#define replicationCursorType_doc \ +"A database replication cursor." + +PyTypeObject replicationCursorType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2.extensions.ReplicationCursor", + sizeof(replicationCursorObject), 0, + 0, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + (reprfunc)replicationCursor_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ + (reprfunc)replicationCursor_repr, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_ITER | + Py_TPFLAGS_HAVE_GC, /*tp_flags*/ + replicationCursorType_doc, /*tp_doc*/ + replicationCursorType_traverse, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + replicationCursorObject_methods, /*tp_methods*/ + replicationCursorObject_members, /*tp_members*/ + replicationCursorObject_getsets, /*tp_getset*/ + &cursorType, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + replicationCursor_init, /*tp_init*/ + 0, /*tp_alloc*/ + 0, /*tp_new*/ +}; diff --git a/source-code/psycopg2/psycopg/replication_message.h b/source-code/psycopg2/psycopg/replication_message.h new file mode 100644 index 0000000..c03e606 --- /dev/null +++ b/source-code/psycopg2/psycopg/replication_message.h @@ -0,0 +1,58 @@ +/* replication_message.h - definition for the psycopg ReplicationMessage type + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_REPLICATION_MESSAGE_H +#define PSYCOPG_REPLICATION_MESSAGE_H 1 + +#include "cursor.h" +#include "libpq_support.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern HIDDEN PyTypeObject replicationMessageType; + +/* the typedef is forward-declared in psycopg.h */ +struct replicationMessageObject { + PyObject_HEAD + + cursorObject *cursor; + PyObject *payload; + + int data_size; + XLogRecPtr data_start; + XLogRecPtr wal_end; + int64_t send_time; +}; + +RAISES_NEG HIDDEN int replmsg_datetime_init(void); + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_REPLICATION_MESSAGE_H) */ diff --git a/source-code/psycopg2/psycopg/replication_message_type.c b/source-code/psycopg2/psycopg/replication_message_type.c new file mode 100644 index 0000000..a137f84 --- /dev/null +++ b/source-code/psycopg2/psycopg/replication_message_type.c @@ -0,0 +1,195 @@ +/* replication_message_type.c - python interface to ReplcationMessage objects + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/replication_message.h" + +#include "datetime.h" + +RAISES_NEG int +replmsg_datetime_init(void) +{ + PyDateTime_IMPORT; + + if (!PyDateTimeAPI) { + PyErr_SetString(PyExc_ImportError, "datetime initialization failed"); + return -1; + } + return 0; +} + + +static PyObject * +replmsg_repr(replicationMessageObject *self) +{ + return PyString_FromFormat( + "", + self, self->data_size, XLOGFMTARGS(self->data_start), XLOGFMTARGS(self->wal_end), + (long int)self->send_time); +} + +static int +replmsg_init(PyObject *obj, PyObject *args, PyObject *kwargs) +{ + PyObject *cur = NULL; + replicationMessageObject *self = (replicationMessageObject *)obj; + + if (!PyArg_ParseTuple( + args, "O!O", &cursorType, &cur, &self->payload)) { + return -1; + } + + Py_INCREF(cur); + self->cursor = (cursorObject *)cur; + Py_INCREF(self->payload); + + self->data_size = 0; + self->data_start = 0; + self->wal_end = 0; + self->send_time = 0; + + return 0; +} + +static int +replmsg_traverse(replicationMessageObject *self, visitproc visit, void *arg) +{ + Py_VISIT((PyObject *)self->cursor); + Py_VISIT(self->payload); + return 0; +} + +static int +replmsg_clear(replicationMessageObject *self) +{ + Py_CLEAR(self->cursor); + Py_CLEAR(self->payload); + return 0; +} + +static void +replmsg_dealloc(PyObject* obj) +{ + PyObject_GC_UnTrack(obj); + + replmsg_clear((replicationMessageObject*) obj); + + Py_TYPE(obj)->tp_free(obj); +} + +#define replmsg_send_time_doc \ +"send_time - Timestamp of the replication message departure from the server." + +static PyObject * +replmsg_get_send_time(replicationMessageObject *self) +{ + PyObject *tval, *res = NULL; + double t; + + t = (double)self->send_time / USECS_PER_SEC + + ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY); + + tval = Py_BuildValue("(d)", t); + if (tval) { + res = PyDateTime_FromTimestamp(tval); + Py_DECREF(tval); + } + + return res; +} + +#define OFFSETOF(x) offsetof(replicationMessageObject, x) + +/* object member list */ + +static struct PyMemberDef replicationMessageObject_members[] = { + {"cursor", T_OBJECT, OFFSETOF(cursor), READONLY, + "Related ReplcationCursor object."}, + {"payload", T_OBJECT, OFFSETOF(payload), READONLY, + "The actual message data."}, + {"data_size", T_INT, OFFSETOF(data_size), READONLY, + "Raw size of the message data in bytes."}, + {"data_start", T_ULONGLONG, OFFSETOF(data_start), READONLY, + "LSN position of the start of this message."}, + {"wal_end", T_ULONGLONG, OFFSETOF(wal_end), READONLY, + "LSN position of the current end of WAL on the server."}, + {NULL} +}; + +static struct PyGetSetDef replicationMessageObject_getsets[] = { + { "send_time", (getter)replmsg_get_send_time, NULL, + replmsg_send_time_doc, NULL }, + {NULL} +}; + +/* object type */ + +#define replicationMessageType_doc \ +"A replication protocol message." + +PyTypeObject replicationMessageType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2.extensions.ReplicationMessage", + sizeof(replicationMessageObject), 0, + replmsg_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + (reprfunc)replmsg_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_HAVE_GC, /*tp_flags*/ + replicationMessageType_doc, /*tp_doc*/ + (traverseproc)replmsg_traverse, /*tp_traverse*/ + (inquiry)replmsg_clear, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ + replicationMessageObject_members, /*tp_members*/ + replicationMessageObject_getsets, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + replmsg_init, /*tp_init*/ + 0, /*tp_alloc*/ + PyType_GenericNew, /*tp_new*/ +}; diff --git a/source-code/psycopg2/psycopg/solaris_support.c b/source-code/psycopg2/psycopg/solaris_support.c new file mode 100644 index 0000000..da95b38 --- /dev/null +++ b/source-code/psycopg2/psycopg/solaris_support.c @@ -0,0 +1,58 @@ +/* solaris_support.c - emulate functions missing on Solaris + * + * Copyright (C) 2017 My Karlsson + * Copyright (c) 2018, Joyent, Inc. + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" +#include "psycopg/solaris_support.h" + +#if defined(__sun) && defined(__SVR4) +/* timeradd is missing on Solaris 10 */ +#ifndef timeradd +void +timeradd(struct timeval *a, struct timeval *b, struct timeval *c) +{ + c->tv_sec = a->tv_sec + b->tv_sec; + c->tv_usec = a->tv_usec + b->tv_usec; + if (c->tv_usec >= 1000000) { + c->tv_usec -= 1000000; + c->tv_sec += 1; + } +} + +/* timersub is missing on Solaris */ +void +timersub(struct timeval *a, struct timeval *b, struct timeval *c) +{ + c->tv_sec = a->tv_sec - b->tv_sec; + c->tv_usec = a->tv_usec - b->tv_usec; + if (c->tv_usec < 0) { + c->tv_usec += 1000000; + c->tv_sec -= 1; + } +} +#endif /* timeradd */ +#endif /* defined(__sun) && defined(__SVR4) */ diff --git a/source-code/psycopg2/psycopg/solaris_support.h b/source-code/psycopg2/psycopg/solaris_support.h new file mode 100644 index 0000000..ba9a565 --- /dev/null +++ b/source-code/psycopg2/psycopg/solaris_support.h @@ -0,0 +1,48 @@ +/* solaris_support.h - definitions for solaris_support.c + * + * Copyright (C) 2017 My Karlsson + * Copyright (c) 2018-2019, Joyent, Inc. + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ +#ifndef PSYCOPG_SOLARIS_SUPPORT_H +#define PSYCOPG_SOLARIS_SUPPORT_H + +#include "psycopg/config.h" + +#if defined(__sun) && defined(__SVR4) +#include + +#ifndef timeradd +extern HIDDEN void timeradd(struct timeval *a, struct timeval *b, struct timeval *c); +extern HIDDEN void timersub(struct timeval *a, struct timeval *b, struct timeval *c); +#endif + +#ifndef timercmp +#define timercmp(a, b, cmp) \ + (((a)->tv_sec == (b)->tv_sec) ? \ + ((a)->tv_usec cmp (b)->tv_usec) : \ + ((a)->tv_sec cmp (b)->tv_sec)) +#endif +#endif + +#endif /* !defined(PSYCOPG_SOLARIS_SUPPORT_H) */ diff --git a/source-code/psycopg2/psycopg/sqlstate_errors.h b/source-code/psycopg2/psycopg/sqlstate_errors.h new file mode 100644 index 0000000..38ad78d --- /dev/null +++ b/source-code/psycopg2/psycopg/sqlstate_errors.h @@ -0,0 +1,337 @@ +/* + * Autogenerated by 'scripts/make_errors.py'. + */ + + +/* Class 02 - No Data (this is also a warning class per the SQL standard) */ +{"02000", "NoData"}, +{"02001", "NoAdditionalDynamicResultSetsReturned"}, + +/* Class 03 - SQL Statement Not Yet Complete */ +{"03000", "SqlStatementNotYetComplete"}, + +/* Class 08 - Connection Exception */ +{"08000", "ConnectionException"}, +{"08001", "SqlclientUnableToEstablishSqlconnection"}, +{"08003", "ConnectionDoesNotExist"}, +{"08004", "SqlserverRejectedEstablishmentOfSqlconnection"}, +{"08006", "ConnectionFailure"}, +{"08007", "TransactionResolutionUnknown"}, +{"08P01", "ProtocolViolation"}, + +/* Class 09 - Triggered Action Exception */ +{"09000", "TriggeredActionException"}, + +/* Class 0A - Feature Not Supported */ +{"0A000", "FeatureNotSupported"}, + +/* Class 0B - Invalid Transaction Initiation */ +{"0B000", "InvalidTransactionInitiation"}, + +/* Class 0F - Locator Exception */ +{"0F000", "LocatorException"}, +{"0F001", "InvalidLocatorSpecification"}, + +/* Class 0L - Invalid Grantor */ +{"0L000", "InvalidGrantor"}, +{"0LP01", "InvalidGrantOperation"}, + +/* Class 0P - Invalid Role Specification */ +{"0P000", "InvalidRoleSpecification"}, + +/* Class 0Z - Diagnostics Exception */ +{"0Z000", "DiagnosticsException"}, +{"0Z002", "StackedDiagnosticsAccessedWithoutActiveHandler"}, + +/* Class 20 - Case Not Found */ +{"20000", "CaseNotFound"}, + +/* Class 21 - Cardinality Violation */ +{"21000", "CardinalityViolation"}, + +/* Class 22 - Data Exception */ +{"22000", "DataException"}, +{"22001", "StringDataRightTruncation"}, +{"22002", "NullValueNoIndicatorParameter"}, +{"22003", "NumericValueOutOfRange"}, +{"22004", "NullValueNotAllowed"}, +{"22005", "ErrorInAssignment"}, +{"22007", "InvalidDatetimeFormat"}, +{"22008", "DatetimeFieldOverflow"}, +{"22009", "InvalidTimeZoneDisplacementValue"}, +{"2200B", "EscapeCharacterConflict"}, +{"2200C", "InvalidUseOfEscapeCharacter"}, +{"2200D", "InvalidEscapeOctet"}, +{"2200F", "ZeroLengthCharacterString"}, +{"2200G", "MostSpecificTypeMismatch"}, +{"2200H", "SequenceGeneratorLimitExceeded"}, +{"2200L", "NotAnXmlDocument"}, +{"2200M", "InvalidXmlDocument"}, +{"2200N", "InvalidXmlContent"}, +{"2200S", "InvalidXmlComment"}, +{"2200T", "InvalidXmlProcessingInstruction"}, +{"22010", "InvalidIndicatorParameterValue"}, +{"22011", "SubstringError"}, +{"22012", "DivisionByZero"}, +{"22013", "InvalidPrecedingOrFollowingSize"}, +{"22014", "InvalidArgumentForNtileFunction"}, +{"22015", "IntervalFieldOverflow"}, +{"22016", "InvalidArgumentForNthValueFunction"}, +{"22018", "InvalidCharacterValueForCast"}, +{"22019", "InvalidEscapeCharacter"}, +{"2201B", "InvalidRegularExpression"}, +{"2201E", "InvalidArgumentForLogarithm"}, +{"2201F", "InvalidArgumentForPowerFunction"}, +{"2201G", "InvalidArgumentForWidthBucketFunction"}, +{"2201W", "InvalidRowCountInLimitClause"}, +{"2201X", "InvalidRowCountInResultOffsetClause"}, +{"22021", "CharacterNotInRepertoire"}, +{"22022", "IndicatorOverflow"}, +{"22023", "InvalidParameterValue"}, +{"22024", "UnterminatedCString"}, +{"22025", "InvalidEscapeSequence"}, +{"22026", "StringDataLengthMismatch"}, +{"22027", "TrimError"}, +{"2202E", "ArraySubscriptError"}, +{"2202G", "InvalidTablesampleRepeat"}, +{"2202H", "InvalidTablesampleArgument"}, +{"22030", "DuplicateJsonObjectKeyValue"}, +{"22031", "InvalidArgumentForSqlJsonDatetimeFunction"}, +{"22032", "InvalidJsonText"}, +{"22033", "InvalidSqlJsonSubscript"}, +{"22034", "MoreThanOneSqlJsonItem"}, +{"22035", "NoSqlJsonItem"}, +{"22036", "NonNumericSqlJsonItem"}, +{"22037", "NonUniqueKeysInAJsonObject"}, +{"22038", "SingletonSqlJsonItemRequired"}, +{"22039", "SqlJsonArrayNotFound"}, +{"2203A", "SqlJsonMemberNotFound"}, +{"2203B", "SqlJsonNumberNotFound"}, +{"2203C", "SqlJsonObjectNotFound"}, +{"2203D", "TooManyJsonArrayElements"}, +{"2203E", "TooManyJsonObjectMembers"}, +{"2203F", "SqlJsonScalarRequired"}, +{"2203G", "SqlJsonItemCannotBeCastToTargetType"}, +{"22P01", "FloatingPointException"}, +{"22P02", "InvalidTextRepresentation"}, +{"22P03", "InvalidBinaryRepresentation"}, +{"22P04", "BadCopyFileFormat"}, +{"22P05", "UntranslatableCharacter"}, +{"22P06", "NonstandardUseOfEscapeCharacter"}, + +/* Class 23 - Integrity Constraint Violation */ +{"23000", "IntegrityConstraintViolation"}, +{"23001", "RestrictViolation"}, +{"23502", "NotNullViolation"}, +{"23503", "ForeignKeyViolation"}, +{"23505", "UniqueViolation"}, +{"23514", "CheckViolation"}, +{"23P01", "ExclusionViolation"}, + +/* Class 24 - Invalid Cursor State */ +{"24000", "InvalidCursorState"}, + +/* Class 25 - Invalid Transaction State */ +{"25000", "InvalidTransactionState"}, +{"25001", "ActiveSqlTransaction"}, +{"25002", "BranchTransactionAlreadyActive"}, +{"25003", "InappropriateAccessModeForBranchTransaction"}, +{"25004", "InappropriateIsolationLevelForBranchTransaction"}, +{"25005", "NoActiveSqlTransactionForBranchTransaction"}, +{"25006", "ReadOnlySqlTransaction"}, +{"25007", "SchemaAndDataStatementMixingNotSupported"}, +{"25008", "HeldCursorRequiresSameIsolationLevel"}, +{"25P01", "NoActiveSqlTransaction"}, +{"25P02", "InFailedSqlTransaction"}, +{"25P03", "IdleInTransactionSessionTimeout"}, + +/* Class 26 - Invalid SQL Statement Name */ +{"26000", "InvalidSqlStatementName"}, + +/* Class 27 - Triggered Data Change Violation */ +{"27000", "TriggeredDataChangeViolation"}, + +/* Class 28 - Invalid Authorization Specification */ +{"28000", "InvalidAuthorizationSpecification"}, +{"28P01", "InvalidPassword"}, + +/* Class 2B - Dependent Privilege Descriptors Still Exist */ +{"2B000", "DependentPrivilegeDescriptorsStillExist"}, +{"2BP01", "DependentObjectsStillExist"}, + +/* Class 2D - Invalid Transaction Termination */ +{"2D000", "InvalidTransactionTermination"}, + +/* Class 2F - SQL Routine Exception */ +{"2F000", "SqlRoutineException"}, +{"2F002", "ModifyingSqlDataNotPermitted"}, +{"2F003", "ProhibitedSqlStatementAttempted"}, +{"2F004", "ReadingSqlDataNotPermitted"}, +{"2F005", "FunctionExecutedNoReturnStatement"}, + +/* Class 34 - Invalid Cursor Name */ +{"34000", "InvalidCursorName"}, + +/* Class 38 - External Routine Exception */ +{"38000", "ExternalRoutineException"}, +{"38001", "ContainingSqlNotPermitted"}, +{"38002", "ModifyingSqlDataNotPermittedExt"}, +{"38003", "ProhibitedSqlStatementAttemptedExt"}, +{"38004", "ReadingSqlDataNotPermittedExt"}, + +/* Class 39 - External Routine Invocation Exception */ +{"39000", "ExternalRoutineInvocationException"}, +{"39001", "InvalidSqlstateReturned"}, +{"39004", "NullValueNotAllowedExt"}, +{"39P01", "TriggerProtocolViolated"}, +{"39P02", "SrfProtocolViolated"}, +{"39P03", "EventTriggerProtocolViolated"}, + +/* Class 3B - Savepoint Exception */ +{"3B000", "SavepointException"}, +{"3B001", "InvalidSavepointSpecification"}, + +/* Class 3D - Invalid Catalog Name */ +{"3D000", "InvalidCatalogName"}, + +/* Class 3F - Invalid Schema Name */ +{"3F000", "InvalidSchemaName"}, + +/* Class 40 - Transaction Rollback */ +{"40000", "TransactionRollback"}, +{"40001", "SerializationFailure"}, +{"40002", "TransactionIntegrityConstraintViolation"}, +{"40003", "StatementCompletionUnknown"}, +{"40P01", "DeadlockDetected"}, + +/* Class 42 - Syntax Error or Access Rule Violation */ +{"42000", "SyntaxErrorOrAccessRuleViolation"}, +{"42501", "InsufficientPrivilege"}, +{"42601", "SyntaxError"}, +{"42602", "InvalidName"}, +{"42611", "InvalidColumnDefinition"}, +{"42622", "NameTooLong"}, +{"42701", "DuplicateColumn"}, +{"42702", "AmbiguousColumn"}, +{"42703", "UndefinedColumn"}, +{"42704", "UndefinedObject"}, +{"42710", "DuplicateObject"}, +{"42712", "DuplicateAlias"}, +{"42723", "DuplicateFunction"}, +{"42725", "AmbiguousFunction"}, +{"42803", "GroupingError"}, +{"42804", "DatatypeMismatch"}, +{"42809", "WrongObjectType"}, +{"42830", "InvalidForeignKey"}, +{"42846", "CannotCoerce"}, +{"42883", "UndefinedFunction"}, +{"428C9", "GeneratedAlways"}, +{"42939", "ReservedName"}, +{"42P01", "UndefinedTable"}, +{"42P02", "UndefinedParameter"}, +{"42P03", "DuplicateCursor"}, +{"42P04", "DuplicateDatabase"}, +{"42P05", "DuplicatePreparedStatement"}, +{"42P06", "DuplicateSchema"}, +{"42P07", "DuplicateTable"}, +{"42P08", "AmbiguousParameter"}, +{"42P09", "AmbiguousAlias"}, +{"42P10", "InvalidColumnReference"}, +{"42P11", "InvalidCursorDefinition"}, +{"42P12", "InvalidDatabaseDefinition"}, +{"42P13", "InvalidFunctionDefinition"}, +{"42P14", "InvalidPreparedStatementDefinition"}, +{"42P15", "InvalidSchemaDefinition"}, +{"42P16", "InvalidTableDefinition"}, +{"42P17", "InvalidObjectDefinition"}, +{"42P18", "IndeterminateDatatype"}, +{"42P19", "InvalidRecursion"}, +{"42P20", "WindowingError"}, +{"42P21", "CollationMismatch"}, +{"42P22", "IndeterminateCollation"}, + +/* Class 44 - WITH CHECK OPTION Violation */ +{"44000", "WithCheckOptionViolation"}, + +/* Class 53 - Insufficient Resources */ +{"53000", "InsufficientResources"}, +{"53100", "DiskFull"}, +{"53200", "OutOfMemory"}, +{"53300", "TooManyConnections"}, +{"53400", "ConfigurationLimitExceeded"}, + +/* Class 54 - Program Limit Exceeded */ +{"54000", "ProgramLimitExceeded"}, +{"54001", "StatementTooComplex"}, +{"54011", "TooManyColumns"}, +{"54023", "TooManyArguments"}, + +/* Class 55 - Object Not In Prerequisite State */ +{"55000", "ObjectNotInPrerequisiteState"}, +{"55006", "ObjectInUse"}, +{"55P02", "CantChangeRuntimeParam"}, +{"55P03", "LockNotAvailable"}, +{"55P04", "UnsafeNewEnumValueUsage"}, + +/* Class 57 - Operator Intervention */ +{"57000", "OperatorIntervention"}, +{"57014", "QueryCanceled"}, +{"57P01", "AdminShutdown"}, +{"57P02", "CrashShutdown"}, +{"57P03", "CannotConnectNow"}, +{"57P04", "DatabaseDropped"}, +{"57P05", "IdleSessionTimeout"}, + +/* Class 58 - System Error (errors external to PostgreSQL itself) */ +{"58000", "SystemError"}, +{"58030", "IoError"}, +{"58P01", "UndefinedFile"}, +{"58P02", "DuplicateFile"}, + +/* Class 72 - Snapshot Failure */ +{"72000", "SnapshotTooOld"}, + +/* Class F0 - Configuration File Error */ +{"F0000", "ConfigFileError"}, +{"F0001", "LockFileExists"}, + +/* Class HV - Foreign Data Wrapper Error (SQL/MED) */ +{"HV000", "FdwError"}, +{"HV001", "FdwOutOfMemory"}, +{"HV002", "FdwDynamicParameterValueNeeded"}, +{"HV004", "FdwInvalidDataType"}, +{"HV005", "FdwColumnNameNotFound"}, +{"HV006", "FdwInvalidDataTypeDescriptors"}, +{"HV007", "FdwInvalidColumnName"}, +{"HV008", "FdwInvalidColumnNumber"}, +{"HV009", "FdwInvalidUseOfNullPointer"}, +{"HV00A", "FdwInvalidStringFormat"}, +{"HV00B", "FdwInvalidHandle"}, +{"HV00C", "FdwInvalidOptionIndex"}, +{"HV00D", "FdwInvalidOptionName"}, +{"HV00J", "FdwOptionNameNotFound"}, +{"HV00K", "FdwReplyHandle"}, +{"HV00L", "FdwUnableToCreateExecution"}, +{"HV00M", "FdwUnableToCreateReply"}, +{"HV00N", "FdwUnableToEstablishConnection"}, +{"HV00P", "FdwNoSchemas"}, +{"HV00Q", "FdwSchemaNotFound"}, +{"HV00R", "FdwTableNotFound"}, +{"HV010", "FdwFunctionSequenceError"}, +{"HV014", "FdwTooManyHandles"}, +{"HV021", "FdwInconsistentDescriptorInformation"}, +{"HV024", "FdwInvalidAttributeValue"}, +{"HV090", "FdwInvalidStringLengthOrBufferLength"}, +{"HV091", "FdwInvalidDescriptorFieldIdentifier"}, + +/* Class P0 - PL/pgSQL Error */ +{"P0000", "PlpgsqlError"}, +{"P0001", "RaiseException"}, +{"P0002", "NoDataFound"}, +{"P0003", "TooManyRows"}, +{"P0004", "AssertFailure"}, + +/* Class XX - Internal Error */ +{"XX000", "InternalError_"}, +{"XX001", "DataCorrupted"}, +{"XX002", "IndexCorrupted"}, diff --git a/source-code/psycopg2/psycopg/typecast.c b/source-code/psycopg2/psycopg/typecast.c new file mode 100644 index 0000000..c1facc4 --- /dev/null +++ b/source-code/psycopg2/psycopg/typecast.c @@ -0,0 +1,620 @@ +/* typecast.c - basic utility functions related to typecasting + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/typecast.h" +#include "psycopg/cursor.h" + +/* useful function used by some typecasters */ + +static const char * +skip_until_space2(const char *s, Py_ssize_t *len) +{ + while (*len > 0 && *s && *s != ' ') { + s++; (*len)--; + } + return s; +} + +static int +typecast_parse_date(const char* s, const char** t, Py_ssize_t* len, + int* year, int* month, int* day) +{ + int acc = -1, cz = 0; + + Dprintf("typecast_parse_date: len = " FORMAT_CODE_PY_SSIZE_T ", s = %s", + *len, s); + + while (cz < 3 && *len > 0 && *s) { + switch (*s) { + case '-': + case ' ': + case 'T': + if (cz == 0) *year = acc; + else if (cz == 1) *month = acc; + else if (cz == 2) *day = acc; + acc = -1; cz++; + break; + default: + acc = (acc == -1 ? 0 : acc*10) + ((int)*s - (int)'0'); + break; + } + + s++; (*len)--; + } + + if (acc != -1) { + *day = acc; + cz += 1; + } + + /* Is this a BC date? If so, adjust the year value. However + * Python datetime module does not support BC dates, so this will raise + * an exception downstream. */ + if (*len >= 2 && s[*len-2] == 'B' && s[*len-1] == 'C') + *year = -(*year); + + if (t != NULL) *t = s; + + return cz; +} + +static int +typecast_parse_time(const char* s, const char** t, Py_ssize_t* len, + int* hh, int* mm, int* ss, int* us, int* tz) +{ + int acc = -1, cz = 0; + int tzsign = 1, tzhh = 0, tzmm = 0, tzss = 0; + int usd = 0; + + /* sets microseconds and timezone to 0 because they may be missing */ + *us = *tz = 0; + + Dprintf("typecast_parse_time: len = " FORMAT_CODE_PY_SSIZE_T ", s = %s", + *len, s); + + while (cz < 7 && *len > 0 && *s) { + switch (*s) { + case ':': + if (cz == 0) *hh = acc; + else if (cz == 1) *mm = acc; + else if (cz == 2) *ss = acc; + else if (cz == 3) *us = acc; + else if (cz == 4) tzhh = acc; + else if (cz == 5) tzmm = acc; + acc = -1; cz++; + break; + case '.': + /* we expect seconds and if we don't get them we return an error */ + if (cz != 2) return -1; + *ss = acc; + acc = -1; cz++; + break; + case '+': + case '-': + /* seconds or microseconds here, anything else is an error */ + if (cz < 2 || cz > 3) return -1; + if (*s == '-') tzsign = -1; + if (cz == 2) *ss = acc; + else if (cz == 3) *us = acc; + acc = -1; cz = 4; + break; + case ' ': + case 'B': + case 'C': + /* Ignore the " BC" suffix, if passed -- it is handled + * when parsing the date portion. */ + break; + default: + acc = (acc == -1 ? 0 : acc*10) + ((int)*s - (int)'0'); + if (cz == 3) usd += 1; + break; + } + + s++; (*len)--; + } + + if (acc != -1) { + if (cz == 0) { *hh = acc; cz += 1; } + else if (cz == 1) { *mm = acc; cz += 1; } + else if (cz == 2) { *ss = acc; cz += 1; } + else if (cz == 3) { *us = acc; cz += 1; } + else if (cz == 4) { tzhh = acc; cz += 1; } + else if (cz == 5) { tzmm = acc; cz += 1; } + else if (cz == 6) tzss = acc; + } + if (t != NULL) *t = s; + + *tz = tzsign * (3600 * tzhh + 60 * tzmm + tzss); + + if (*us != 0) { + while (usd++ < 6) *us *= 10; + } + + /* 24:00:00 -> 00:00:00 (ticket #278) */ + if (*hh == 24) { *hh = 0; } + + return cz; +} + +/** include casting objects **/ +#include "psycopg/typecast_basic.c" +#include "psycopg/typecast_binary.c" +#include "psycopg/typecast_datetime.c" +#include "psycopg/typecast_array.c" + +static long int typecast_default_DEFAULT[] = {0}; +static typecastObject_initlist typecast_default = { + "DEFAULT", typecast_default_DEFAULT, typecast_STRING_cast}; + +static PyObject * +typecast_UNKNOWN_cast(const char *str, Py_ssize_t len, PyObject *curs) +{ + Dprintf("typecast_UNKNOWN_cast: str = '%s'," + " len = " FORMAT_CODE_PY_SSIZE_T, str, len); + + return typecast_default.cast(str, len, curs); +} + +#include "psycopg/typecast_builtins.c" + +#define typecast_PYDATETIMEARRAY_cast typecast_GENERIC_ARRAY_cast +#define typecast_PYDATETIMETZARRAY_cast typecast_GENERIC_ARRAY_cast +#define typecast_PYDATEARRAY_cast typecast_GENERIC_ARRAY_cast +#define typecast_PYTIMEARRAY_cast typecast_GENERIC_ARRAY_cast +#define typecast_PYINTERVALARRAY_cast typecast_GENERIC_ARRAY_cast + +/* a list of initializers, used to make the typecasters accessible anyway */ +static typecastObject_initlist typecast_pydatetime[] = { + {"PYDATETIME", typecast_DATETIME_types, typecast_PYDATETIME_cast}, + {"PYDATETIMETZ", typecast_DATETIMETZ_types, typecast_PYDATETIMETZ_cast}, + {"PYTIME", typecast_TIME_types, typecast_PYTIME_cast}, + {"PYDATE", typecast_DATE_types, typecast_PYDATE_cast}, + {"PYINTERVAL", typecast_INTERVAL_types, typecast_PYINTERVAL_cast}, + {"PYDATETIMEARRAY", typecast_DATETIMEARRAY_types, typecast_PYDATETIMEARRAY_cast, "PYDATETIME"}, + {"PYDATETIMETZARRAY", typecast_DATETIMETZARRAY_types, typecast_PYDATETIMETZARRAY_cast, "PYDATETIMETZ"}, + {"PYTIMEARRAY", typecast_TIMEARRAY_types, typecast_PYTIMEARRAY_cast, "PYTIME"}, + {"PYDATEARRAY", typecast_DATEARRAY_types, typecast_PYDATEARRAY_cast, "PYDATE"}, + {"PYINTERVALARRAY", typecast_INTERVALARRAY_types, typecast_PYINTERVALARRAY_cast, "PYINTERVAL"}, + {NULL, NULL, NULL} +}; + + +/** the type dictionary and associated functions **/ + +PyObject *psyco_types; +PyObject *psyco_default_cast; +PyObject *psyco_binary_types; +PyObject *psyco_default_binary_cast; + + +/* typecast_init - initialize the dictionary and create default types */ + +RAISES_NEG int +typecast_init(PyObject *module) +{ + int i; + int rv = -1; + typecastObject *t = NULL; + PyObject *dict = NULL; + + if (!(dict = PyModule_GetDict(module))) { goto exit; } + + /* create type dictionary and put it in module namespace */ + if (!(psyco_types = PyDict_New())) { goto exit; } + PyDict_SetItemString(dict, "string_types", psyco_types); + + if (!(psyco_binary_types = PyDict_New())) { goto exit; } + PyDict_SetItemString(dict, "binary_types", psyco_binary_types); + + /* insert the cast types into the 'types' dictionary and register them in + the module dictionary */ + for (i = 0; typecast_builtins[i].name != NULL; i++) { + t = (typecastObject *)typecast_from_c(&(typecast_builtins[i]), dict); + if (t == NULL) { goto exit; } + if (typecast_add((PyObject *)t, NULL, 0) < 0) { goto exit; } + + PyDict_SetItem(dict, t->name, (PyObject *)t); + + /* export binary object */ + if (typecast_builtins[i].values == typecast_BINARY_types) { + Py_INCREF((PyObject *)t); + psyco_default_binary_cast = (PyObject *)t; + } + Py_DECREF((PyObject *)t); + t = NULL; + } + + /* create and save a default cast object (but do not register it) */ + psyco_default_cast = typecast_from_c(&typecast_default, dict); + + /* register the date/time typecasters with their original names */ + if (0 > typecast_datetime_init()) { goto exit; } + for (i = 0; typecast_pydatetime[i].name != NULL; i++) { + t = (typecastObject *)typecast_from_c(&(typecast_pydatetime[i]), dict); + if (t == NULL) { goto exit; } + PyDict_SetItem(dict, t->name, (PyObject *)t); + Py_DECREF((PyObject *)t); + t = NULL; + } + + rv = 0; + +exit: + Py_XDECREF((PyObject *)t); + return rv; +} + +/* typecast_add - add a type object to the dictionary */ +RAISES_NEG int +typecast_add(PyObject *obj, PyObject *dict, int binary) +{ + PyObject *val; + Py_ssize_t len, i; + + typecastObject *type = (typecastObject *)obj; + + if (dict == NULL) + dict = (binary ? psyco_binary_types : psyco_types); + + len = PyTuple_Size(type->values); + for (i = 0; i < len; i++) { + val = PyTuple_GetItem(type->values, i); + PyDict_SetItem(dict, val, obj); + } + + return 0; +} + + +/** typecast type **/ + +#define OFFSETOF(x) offsetof(typecastObject, x) + +static int +typecast_cmp(PyObject *obj1, PyObject* obj2) +{ + typecastObject *self = (typecastObject*)obj1; + typecastObject *other = NULL; + PyObject *number = NULL; + Py_ssize_t i, j; + int res = -1; + + if (PyObject_TypeCheck(obj2, &typecastType)) { + other = (typecastObject*)obj2; + } + else { + number = PyNumber_Int(obj2); + } + + Dprintf("typecast_cmp: other = %p, number = %p", other, number); + + for (i=0; i < PyObject_Length(self->values) && res == -1; i++) { + long int val = PyInt_AsLong(PyTuple_GET_ITEM(self->values, i)); + + if (other != NULL) { + for (j=0; j < PyObject_Length(other->values); j++) { + if (PyInt_AsLong(PyTuple_GET_ITEM(other->values, j)) == val) { + res = 0; break; + } + } + } + + else if (number != NULL) { + if (PyInt_AsLong(number) == val) { + res = 0; break; + } + } + } + + Py_XDECREF(number); + return res; +} + +static PyObject* +typecast_richcompare(PyObject *obj1, PyObject* obj2, int opid) +{ + int res = typecast_cmp(obj1, obj2); + + if (PyErr_Occurred()) return NULL; + + return PyBool_FromLong((opid == Py_EQ && res == 0) || (opid != Py_EQ && res != 0)); +} + +static struct PyMemberDef typecastObject_members[] = { + {"name", T_OBJECT, OFFSETOF(name), READONLY}, + {"values", T_OBJECT, OFFSETOF(values), READONLY}, + {NULL} +}; + +static int +typecast_clear(typecastObject *self) +{ + Py_CLEAR(self->values); + Py_CLEAR(self->name); + Py_CLEAR(self->pcast); + Py_CLEAR(self->bcast); + return 0; +} + +static void +typecast_dealloc(typecastObject *self) +{ + PyObject_GC_UnTrack(self); + typecast_clear(self); + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static int +typecast_traverse(typecastObject *self, visitproc visit, void *arg) +{ + Py_VISIT(self->values); + Py_VISIT(self->name); + Py_VISIT(self->pcast); + Py_VISIT(self->bcast); + return 0; +} + +static PyObject * +typecast_repr(PyObject *self) +{ + PyObject *name = ((typecastObject *)self)->name; + PyObject *rv; + + Py_INCREF(name); + if (!(name = psyco_ensure_bytes(name))) { + return NULL; + } + + rv = PyString_FromFormat("<%s '%s' at %p>", + Py_TYPE(self)->tp_name, Bytes_AS_STRING(name), self); + + Py_DECREF(name); + return rv; +} + +static PyObject * +typecast_call(PyObject *obj, PyObject *args, PyObject *kwargs) +{ + const char *string; + Py_ssize_t length; + PyObject *cursor; + + if (!PyArg_ParseTuple(args, "z#O", &string, &length, &cursor)) { + return NULL; + } + + // If the string is not a string but a None value we're being called + // from a Python-defined caster. + if (!string) { + Py_RETURN_NONE; + } + + return typecast_cast(obj, string, length, cursor); +} + +PyTypeObject typecastType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2._psycopg.type", + sizeof(typecastObject), 0, + (destructor)typecast_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_reserved*/ + typecast_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + typecast_call, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_RICHCOMPARE | + Py_TPFLAGS_HAVE_GC, /*tp_flags*/ + "psycopg type-casting object", /*tp_doc*/ + (traverseproc)typecast_traverse, /*tp_traverse*/ + (inquiry)typecast_clear, /*tp_clear*/ + typecast_richcompare, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ + typecastObject_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + 0, /*tp_init*/ + 0, /*tp_alloc*/ + 0, /*tp_new*/ +}; + +static PyObject * +typecast_new(PyObject *name, PyObject *values, PyObject *cast, PyObject *base) +{ + typecastObject *obj; + + obj = PyObject_GC_New(typecastObject, &typecastType); + if (obj == NULL) return NULL; + + Py_INCREF(values); + obj->values = values; + + if (name) { + Py_INCREF(name); + obj->name = name; + } + else { + Py_INCREF(Py_None); + obj->name = Py_None; + } + + obj->pcast = NULL; + obj->ccast = NULL; + obj->bcast = base; + + if (obj->bcast) Py_INCREF(obj->bcast); + + /* FIXME: raise an exception when None is passed as Python caster */ + if (cast && cast != Py_None) { + Py_INCREF(cast); + obj->pcast = cast; + } + + PyObject_GC_Track(obj); + + return (PyObject *)obj; +} + +PyObject * +typecast_from_python(PyObject *self, PyObject *args, PyObject *keywds) +{ + PyObject *v, *name = NULL, *cast = NULL, *base = NULL; + + static char *kwlist[] = {"values", "name", "castobj", "baseobj", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, keywds, "O!|O!OO", kwlist, + &PyTuple_Type, &v, + &Text_Type, &name, + &cast, &base)) { + return NULL; + } + + return typecast_new(name, v, cast, base); +} + +PyObject * +typecast_array_from_python(PyObject *self, PyObject *args, PyObject *keywds) +{ + PyObject *values, *name = NULL, *base = NULL; + typecastObject *obj = NULL; + + static char *kwlist[] = {"values", "name", "baseobj", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, keywds, "O!O!O!", kwlist, + &PyTuple_Type, &values, + &Text_Type, &name, + &typecastType, &base)) { + return NULL; + } + + if ((obj = (typecastObject *)typecast_new(name, values, NULL, base))) { + obj->ccast = typecast_GENERIC_ARRAY_cast; + obj->pcast = NULL; + } + + return (PyObject *)obj; +} + +PyObject * +typecast_from_c(typecastObject_initlist *type, PyObject *dict) +{ + PyObject *name = NULL, *values = NULL, *base = NULL; + typecastObject *obj = NULL; + Py_ssize_t i, len = 0; + + /* before doing anything else we look for the base */ + if (type->base) { + /* NOTE: base is a borrowed reference! */ + base = PyDict_GetItemString(dict, type->base); + if (!base) { + PyErr_Format(Error, "typecast base not found: %s", type->base); + goto end; + } + } + + name = Text_FromUTF8(type->name); + if (!name) goto end; + + while (type->values[len] != 0) len++; + + values = PyTuple_New(len); + if (!values) goto end; + + for (i = 0; i < len ; i++) { + PyTuple_SET_ITEM(values, i, PyInt_FromLong(type->values[i])); + } + + obj = (typecastObject *)typecast_new(name, values, NULL, base); + + if (obj) { + obj->ccast = type->cast; + obj->pcast = NULL; + } + + end: + Py_XDECREF(values); + Py_XDECREF(name); + return (PyObject *)obj; +} + +PyObject * +typecast_cast(PyObject *obj, const char *str, Py_ssize_t len, PyObject *curs) +{ + PyObject *old, *res = NULL; + typecastObject *self = (typecastObject *)obj; + + Py_INCREF(obj); + old = ((cursorObject*)curs)->caster; + ((cursorObject*)curs)->caster = obj; + + if (self->ccast) { + res = self->ccast(str, len, curs); + } + else if (self->pcast) { + PyObject *s; + /* XXX we have bytes in the adapters and strings in the typecasters. + * are you sure this is ok? + * Notice that this way it is about impossible to create a python + * typecaster on a binary type. */ + if (str) { + s = conn_decode(((cursorObject *)curs)->conn, str, len); + } + else { + Py_INCREF(Py_None); + s = Py_None; + } + if (s) { + res = PyObject_CallFunctionObjArgs(self->pcast, s, curs, NULL); + Py_DECREF(s); + } + } + else { + PyErr_SetString(Error, "internal error: no casting function found"); + } + + ((cursorObject*)curs)->caster = old; + Py_DECREF(obj); + + return res; +} diff --git a/source-code/psycopg2/psycopg/typecast.h b/source-code/psycopg2/psycopg/typecast.h new file mode 100644 index 0000000..050345f --- /dev/null +++ b/source-code/psycopg2/psycopg/typecast.h @@ -0,0 +1,91 @@ +/* typecast.h - definitions for typecasters + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_TYPECAST_H +#define PSYCOPG_TYPECAST_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* type of type-casting functions (both C and Python) */ +typedef PyObject *(*typecast_function)(const char *str, Py_ssize_t len, + PyObject *cursor); + +/** typecast type **/ + +extern HIDDEN PyTypeObject typecastType; + +typedef struct { + PyObject_HEAD + + PyObject *name; /* the name of this type */ + PyObject *values; /* the different types this instance can match */ + + typecast_function ccast; /* the C casting function */ + PyObject *pcast; /* the python casting function */ + PyObject *bcast; /* base cast, used by array typecasters */ +} typecastObject; + +/* the initialization values are stored here */ + +typedef struct { + char *name; + long int *values; + typecast_function cast; + + /* base is the base typecaster for arrays */ + char *base; +} typecastObject_initlist; + +/* the type dictionary, much faster to access it globally */ +extern HIDDEN PyObject *psyco_types; +extern HIDDEN PyObject *psyco_binary_types; + +/* the default casting objects, used when no other objects are available */ +extern HIDDEN PyObject *psyco_default_cast; +extern HIDDEN PyObject *psyco_default_binary_cast; + +/** exported functions **/ + +/* used by module.c to init the type system and register types */ +RAISES_NEG HIDDEN int typecast_init(PyObject *dict); +RAISES_NEG HIDDEN int typecast_add(PyObject *obj, PyObject *dict, int binary); + +/* the C callable typecastObject creator function */ +HIDDEN PyObject *typecast_from_c(typecastObject_initlist *type, PyObject *d); + +/* the python callable typecast creator functions */ +HIDDEN PyObject *typecast_from_python( + PyObject *self, PyObject *args, PyObject *keywds); +HIDDEN PyObject *typecast_array_from_python( + PyObject *self, PyObject *args, PyObject *keywds); + +/* the function used to dispatch typecasting calls */ +HIDDEN PyObject *typecast_cast( + PyObject *self, const char *str, Py_ssize_t len, PyObject *curs); + +#endif /* !defined(PSYCOPG_TYPECAST_H) */ diff --git a/source-code/psycopg2/psycopg/typecast_array.c b/source-code/psycopg2/psycopg/typecast_array.c new file mode 100644 index 0000000..7eac99d --- /dev/null +++ b/source-code/psycopg2/psycopg/typecast_array.c @@ -0,0 +1,298 @@ +/* typecast_array.c - array typecasters + * + * Copyright (C) 2005-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define MAX_DIMENSIONS 16 + +/** typecast_array_cleanup - remove the horrible [...]= stuff **/ + +static int +typecast_array_cleanup(const char **str, Py_ssize_t *len) +{ + Py_ssize_t i, depth = 1; + + if ((*str)[0] != '[') return -1; + + for (i=1 ; depth > 0 && i < *len ; i++) { + if ((*str)[i] == '[') + depth += 1; + else if ((*str)[i] == ']') + depth -= 1; + } + if ((*str)[i] != '=') return -1; + + *str = &((*str)[i+1]); + *len = *len - i - 1; + return 0; +} + +/** typecast_array_scan - scan a string looking for array items **/ + +#define ASCAN_ERROR -1 +#define ASCAN_EOF 0 +#define ASCAN_BEGIN 1 +#define ASCAN_END 2 +#define ASCAN_TOKEN 3 +#define ASCAN_QUOTED 4 + +static int +typecast_array_tokenize(const char *str, Py_ssize_t strlength, + Py_ssize_t *pos, char** token, + Py_ssize_t *length, int *quotes) +{ + /* FORTRAN glory */ + Py_ssize_t i, l; + int q, b, res; + + Dprintf("typecast_array_tokenize: '%s', " + FORMAT_CODE_PY_SSIZE_T "/" FORMAT_CODE_PY_SSIZE_T, + &str[*pos], *pos, strlength); + + /* we always get called with pos pointing at the start of a token, so a + fast check is enough for ASCAN_EOF, ASCAN_BEGIN and ASCAN_END */ + if (*pos == strlength) { + return ASCAN_EOF; + } + else if (str[*pos] == '{') { + *pos += 1; + return ASCAN_BEGIN; + } + else if (str[*pos] == '}') { + *pos += 1; + if (str[*pos] == ',') + *pos += 1; + return ASCAN_END; + } + + /* now we start looking for the first unquoted ',' or '}', the only two + tokens that can limit an array element */ + q = 0; /* if q is odd we're inside quotes */ + b = 0; /* if b is 1 we just encountered a backslash */ + res = ASCAN_TOKEN; + + for (i = *pos ; i < strlength ; i++) { + switch (str[i]) { + case '"': + if (b == 0) + q += 1; + else + b = 0; + break; + + case '\\': + res = ASCAN_QUOTED; + if (b == 0) + b = 1; + else + /* we're backslashing a backslash */ + b = 0; + break; + + case '}': + case ',': + if (b == 0 && ((q&1) == 0)) + goto tokenize; + break; + + default: + /* reset the backslash counter */ + b = 0; + break; + } + } + + tokenize: + /* remove initial quoting character and calculate raw length */ + *quotes = 0; + l = i - *pos; + if (str[*pos] == '"') { + *pos += 1; + l -= 2; + *quotes = 1; + } + + if (res == ASCAN_QUOTED) { + const char *j, *jj; + char *buffer = PyMem_Malloc(l+1); + if (buffer == NULL) { + PyErr_NoMemory(); + return ASCAN_ERROR; + } + + *token = buffer; + + for (j = str + *pos, jj = j + l; j < jj; ++j) { + if (*j == '\\') { ++j; } + *(buffer++) = *j; + } + + *buffer = '\0'; + /* The variable that was used to indicate the size of buffer is of type + * Py_ssize_t, so a subsegment of buffer couldn't possibly exceed + * PY_SSIZE_T_MAX: */ + *length = (Py_ssize_t) (buffer - *token); + } + else { + *token = (char *)&str[*pos]; + *length = l; + } + + *pos = i; + + /* skip the comma and set position to the start of next token */ + if (str[i] == ',') *pos += 1; + + return res; +} + +RAISES_NEG static int +typecast_array_scan(const char *str, Py_ssize_t strlength, + PyObject *curs, PyObject *base, PyObject *array) +{ + int state, quotes = 0; + Py_ssize_t length = 0, pos = 0; + char *token; + + PyObject *stack[MAX_DIMENSIONS]; + size_t stack_index = 0; + + while (1) { + token = NULL; + state = typecast_array_tokenize(str, strlength, + &pos, &token, &length, "es); + Dprintf("typecast_array_scan: state = %d," + " length = " FORMAT_CODE_PY_SSIZE_T ", token = '%s'", + state, length, token); + if (state == ASCAN_TOKEN || state == ASCAN_QUOTED) { + PyObject *obj; + if (!quotes && length == 4 + && (token[0] == 'n' || token[0] == 'N') + && (token[1] == 'u' || token[1] == 'U') + && (token[2] == 'l' || token[2] == 'L') + && (token[3] == 'l' || token[3] == 'L')) + { + obj = typecast_cast(base, NULL, 0, curs); + } else { + obj = typecast_cast(base, token, length, curs); + } + + /* before anything else we free the memory */ + if (state == ASCAN_QUOTED) PyMem_Free(token); + if (obj == NULL) return -1; + + PyList_Append(array, obj); + Py_DECREF(obj); + } + + else if (state == ASCAN_BEGIN) { + PyObject *sub = PyList_New(0); + if (sub == NULL) return -1; + + PyList_Append(array, sub); + Py_DECREF(sub); + + if (stack_index == MAX_DIMENSIONS) { + PyErr_SetString(DataError, "excessive array dimensions"); + return -1; + } + + stack[stack_index++] = array; + array = sub; + } + + else if (state == ASCAN_ERROR) { + return -1; + } + + else if (state == ASCAN_END) { + if (stack_index == 0) { + PyErr_SetString(DataError, "unbalanced braces in array"); + return -1; + } + array = stack[--stack_index]; + } + + else if (state == ASCAN_EOF) + break; + } + + return 0; +} + + +/** GENERIC - a generic typecaster that can be used when no special actions + have to be taken on the single items **/ + +static PyObject * +typecast_GENERIC_ARRAY_cast(const char *str, Py_ssize_t len, PyObject *curs) +{ + PyObject *obj = NULL; + PyObject *base = ((typecastObject*)((cursorObject*)curs)->caster)->bcast; + + Dprintf("typecast_GENERIC_ARRAY_cast: str = '%s'," + " len = " FORMAT_CODE_PY_SSIZE_T, str, len); + + if (str == NULL) { Py_RETURN_NONE; } + if (str[0] == '[') + typecast_array_cleanup(&str, &len); + if (str[0] != '{') { + PyErr_SetString(DataError, "array does not start with '{'"); + return NULL; + } + if (str[1] == '\0') { + PyErr_SetString(DataError, "malformed array: '{'"); + return NULL; + } + + Dprintf("typecast_GENERIC_ARRAY_cast: str = '%s'," + " len = " FORMAT_CODE_PY_SSIZE_T, str, len); + + if (!(obj = PyList_New(0))) { return NULL; } + + /* scan the array skipping the first level of {} */ + if (typecast_array_scan(&str[1], len-2, curs, base, obj) < 0) { + Py_CLEAR(obj); + } + + return obj; +} + +/** almost all the basic array typecasters are derived from GENERIC **/ + +#define typecast_LONGINTEGERARRAY_cast typecast_GENERIC_ARRAY_cast +#define typecast_INTEGERARRAY_cast typecast_GENERIC_ARRAY_cast +#define typecast_FLOATARRAY_cast typecast_GENERIC_ARRAY_cast +#define typecast_DECIMALARRAY_cast typecast_GENERIC_ARRAY_cast +#define typecast_STRINGARRAY_cast typecast_GENERIC_ARRAY_cast +#define typecast_UNICODEARRAY_cast typecast_GENERIC_ARRAY_cast +#define typecast_BYTESARRAY_cast typecast_GENERIC_ARRAY_cast +#define typecast_BOOLEANARRAY_cast typecast_GENERIC_ARRAY_cast +#define typecast_DATETIMEARRAY_cast typecast_GENERIC_ARRAY_cast +#define typecast_DATETIMETZARRAY_cast typecast_GENERIC_ARRAY_cast +#define typecast_DATEARRAY_cast typecast_GENERIC_ARRAY_cast +#define typecast_TIMEARRAY_cast typecast_GENERIC_ARRAY_cast +#define typecast_INTERVALARRAY_cast typecast_GENERIC_ARRAY_cast +#define typecast_BINARYARRAY_cast typecast_GENERIC_ARRAY_cast +#define typecast_ROWIDARRAY_cast typecast_GENERIC_ARRAY_cast diff --git a/source-code/psycopg2/psycopg/typecast_basic.c b/source-code/psycopg2/psycopg/typecast_basic.c new file mode 100644 index 0000000..f73f60b --- /dev/null +++ b/source-code/psycopg2/psycopg/typecast_basic.c @@ -0,0 +1,150 @@ +/* pgcasts_basic.c - basic typecasting functions to python types + * + * Copyright (C) 2001-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +/** INTEGER - cast normal integers (4 bytes) to python int **/ + +#define typecast_INTEGER_cast typecast_LONGINTEGER_cast + +/** LONGINTEGER - cast long integers (8 bytes) to python long **/ + +static PyObject * +typecast_LONGINTEGER_cast(const char *s, Py_ssize_t len, PyObject *curs) +{ + char buffer[24]; + + if (s == NULL) { Py_RETURN_NONE; } + if (s[len] != '\0') { + strncpy(buffer, s, (size_t) len); buffer[len] = '\0'; + s = buffer; + } + return PyLong_FromString((char *)s, NULL, 0); +} + +/** FLOAT - cast floating point numbers to python float **/ + +static PyObject * +typecast_FLOAT_cast(const char *s, Py_ssize_t len, PyObject *curs) +{ + PyObject *str = NULL, *flo = NULL; + + if (s == NULL) { Py_RETURN_NONE; } + if (!(str = Text_FromUTF8AndSize(s, len))) { return NULL; } + flo = PyFloat_FromString(str); + Py_DECREF(str); + return flo; +} + + +/** BYTES - cast strings of any type to python bytes **/ + +static PyObject * +typecast_BYTES_cast(const char *s, Py_ssize_t len, PyObject *curs) +{ + if (s == NULL) { Py_RETURN_NONE; } + return Bytes_FromStringAndSize(s, len); +} + + +/** UNICODE - cast strings of any type to a python unicode object **/ + +static PyObject * +typecast_UNICODE_cast(const char *s, Py_ssize_t len, PyObject *curs) +{ + connectionObject *conn; + + if (s == NULL) { Py_RETURN_NONE; } + + conn = ((cursorObject*)curs)->conn; + return conn_decode(conn, s, len); +} + + +/** STRING - cast strings of any type to python string **/ + +#define typecast_STRING_cast typecast_UNICODE_cast + + +/** BOOLEAN - cast boolean value into right python object **/ + +static PyObject * +typecast_BOOLEAN_cast(const char *s, Py_ssize_t len, PyObject *curs) +{ + PyObject *res = NULL; + + if (s == NULL) { Py_RETURN_NONE; } + + switch (s[0]) { + case 't': + case 'T': + res = Py_True; + break; + + case 'f': + case 'F': + res = Py_False; + break; + + default: + PyErr_Format(InterfaceError, "can't parse boolean: '%s'", s); + break; + } + + Py_XINCREF(res); + return res; +} + +/** DECIMAL - cast any kind of number into a Python Decimal object **/ + +static PyObject * +typecast_DECIMAL_cast(const char *s, Py_ssize_t len, PyObject *curs) +{ + PyObject *res = NULL; + PyObject *decimalType; + char *buffer; + + if (s == NULL) { Py_RETURN_NONE; } + + if ((buffer = PyMem_Malloc(len+1)) == NULL) + return PyErr_NoMemory(); + strncpy(buffer, s, (size_t) len); buffer[len] = '\0'; + decimalType = psyco_get_decimal_type(); + /* Fall back on float if decimal is not available */ + if (decimalType != NULL) { + res = PyObject_CallFunction(decimalType, "s", buffer); + Py_DECREF(decimalType); + } + else { + PyErr_Clear(); + res = PyObject_CallFunction((PyObject*)&PyFloat_Type, "s", buffer); + } + PyMem_Free(buffer); + + return res; +} + +/* some needed aliases */ +#define typecast_NUMBER_cast typecast_FLOAT_cast +#define typecast_ROWID_cast typecast_INTEGER_cast diff --git a/source-code/psycopg2/psycopg/typecast_binary.c b/source-code/psycopg2/psycopg/typecast_binary.c new file mode 100644 index 0000000..e255581 --- /dev/null +++ b/source-code/psycopg2/psycopg/typecast_binary.c @@ -0,0 +1,275 @@ +/* typecast_binary.c - binary typecasting functions to python types + * + * Copyright (C) 2001-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "typecast_binary.h" + +#include + + +/* Python object holding a memory chunk. The memory is deallocated when + the object is destroyed. This type is used to let users directly access + memory chunks holding unescaped binary data through the buffer interface. + */ + +static void +chunk_dealloc(chunkObject *self) +{ + Dprintf("chunk_dealloc: deallocating memory at %p, size " + FORMAT_CODE_PY_SSIZE_T, + self->base, self->len + ); + PyMem_Free(self->base); + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static PyObject * +chunk_repr(chunkObject *self) +{ + return PyString_FromFormat( + "", + self->base, self->len + ); +} + +/* 3.0 buffer interface */ +int chunk_getbuffer(PyObject *_self, Py_buffer *view, int flags) +{ + int rv; + chunkObject *self = (chunkObject*)_self; + rv = PyBuffer_FillInfo(view, _self, self->base, self->len, 1, flags); + if (rv == 0) { + view->format = "c"; + } + return rv; +} + +static PyBufferProcs chunk_as_buffer = +{ + chunk_getbuffer, + NULL, +}; + +#define chunk_doc "memory chunk" + +PyTypeObject chunkType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2._psycopg.chunk", + sizeof(chunkObject), 0, + (destructor) chunk_dealloc, /* tp_dealloc*/ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc) chunk_repr, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + &chunk_as_buffer, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /* tp_flags */ + chunk_doc /* tp_doc */ +}; + + +static char *parse_hex( + const char *bufin, Py_ssize_t sizein, Py_ssize_t *sizeout); +static char *parse_escape( + const char *bufin, Py_ssize_t sizein, Py_ssize_t *sizeout); + +/* The function is not static and not hidden as we use ctypes to test it. */ +PyObject * +typecast_BINARY_cast(const char *s, Py_ssize_t l, PyObject *curs) +{ + chunkObject *chunk = NULL; + PyObject *res = NULL; + char *buffer = NULL; + Py_ssize_t len; + + if (s == NULL) { Py_RETURN_NONE; } + + if (s[0] == '\\' && s[1] == 'x') { + /* This is a buffer escaped in hex format: libpq before 9.0 can't + * parse it and we can't detect reliably the libpq version at runtime. + * So the only robust option is to parse it ourselves - luckily it's + * an easy format. + */ + if (NULL == (buffer = parse_hex(s, l, &len))) { + goto exit; + } + } + else { + /* This is a buffer in the classic bytea format. So we can handle it + * to the PQunescapeBytea to have it parsed, right? ...Wrong. We + * could, but then we'd have to record whether buffer was allocated by + * Python or by the libpq to dispose it properly. Furthermore the + * PQunescapeBytea interface is not the most brilliant as it wants a + * null-terminated string even if we have known its length thus + * requiring a useless memcpy and strlen. + * So we'll just have our better integrated parser, let's finish this + * story. + */ + if (NULL == (buffer = parse_escape(s, l, &len))) { + goto exit; + } + } + + chunk = (chunkObject *) PyObject_New(chunkObject, &chunkType); + if (chunk == NULL) goto exit; + + /* **Transfer** ownership of buffer's memory to the chunkObject: */ + chunk->base = buffer; + buffer = NULL; + chunk->len = (Py_ssize_t)len; + + if ((res = PyMemoryView_FromObject((PyObject*)chunk)) == NULL) + goto exit; + +exit: + Py_XDECREF((PyObject *)chunk); + PyMem_Free(buffer); + + return res; +} + + +static const char hex_lut[128] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +}; + +/* Parse a bytea output buffer encoded in 'hex' format. + * + * the format is described in + * https://www.postgresql.org/docs/current/static/datatype-binary.html + * + * Parse the buffer in 'bufin', whose length is 'sizein'. + * Return a new buffer allocated by PyMem_Malloc and set 'sizeout' to its size. + * In case of error set an exception and return NULL. + */ +static char * +parse_hex(const char *bufin, Py_ssize_t sizein, Py_ssize_t *sizeout) +{ + char *ret = NULL; + const char *bufend = bufin + sizein; + const char *pi = bufin + 2; /* past the \x */ + char *bufout; + char *po; + + po = bufout = PyMem_Malloc((sizein - 2) >> 1); /* output size upper bound */ + if (NULL == bufout) { + PyErr_NoMemory(); + goto exit; + } + + /* Implementation note: we call this function upon database response, not + * user input (because we are parsing the output format of a buffer) so we + * don't expect errors. On bad input we reserve the right to return a bad + * output, not an error. + */ + while (pi < bufend) { + char c; + while (-1 == (c = hex_lut[*pi++ & '\x7f'])) { + if (pi >= bufend) { goto endloop; } + } + *po = c << 4; + + while (-1 == (c = hex_lut[*pi++ & '\x7f'])) { + if (pi >= bufend) { goto endloop; } + } + *po++ |= c; + } +endloop: + + ret = bufout; + *sizeout = po - bufout; + +exit: + return ret; +} + +/* Parse a bytea output buffer encoded in 'escape' format. + * + * the format is described in + * https://www.postgresql.org/docs/current/static/datatype-binary.html + * + * Parse the buffer in 'bufin', whose length is 'sizein'. + * Return a new buffer allocated by PyMem_Malloc and set 'sizeout' to its size. + * In case of error set an exception and return NULL. + */ +static char * +parse_escape(const char *bufin, Py_ssize_t sizein, Py_ssize_t *sizeout) +{ + char *ret = NULL; + const char *bufend = bufin + sizein; + const char *pi = bufin; + char *bufout; + char *po; + + po = bufout = PyMem_Malloc(sizein); /* output size upper bound */ + if (NULL == bufout) { + PyErr_NoMemory(); + goto exit; + } + + while (pi < bufend) { + if (*pi != '\\') { + /* Unescaped char */ + *po++ = *pi++; + continue; + } + if ((pi[1] >= '0' && pi[1] <= '3') && + (pi[2] >= '0' && pi[2] <= '7') && + (pi[3] >= '0' && pi[3] <= '7')) + { + /* Escaped octal value */ + *po++ = ((pi[1] - '0') << 6) | + ((pi[2] - '0') << 3) | + ((pi[3] - '0')); + pi += 4; + } + else { + /* Escaped char */ + *po++ = pi[1]; + pi += 2; + } + } + + ret = bufout; + *sizeout = po - bufout; + +exit: + return ret; +} diff --git a/source-code/psycopg2/psycopg/typecast_binary.h b/source-code/psycopg2/psycopg/typecast_binary.h new file mode 100644 index 0000000..e6773ed --- /dev/null +++ b/source-code/psycopg2/psycopg/typecast_binary.h @@ -0,0 +1,50 @@ +/* typecast_binary.h - definitions for binary typecaster + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_TYPECAST_BINARY_H +#define PSYCOPG_TYPECAST_BINARY_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/** chunk type **/ + +extern HIDDEN PyTypeObject chunkType; + +typedef struct { + PyObject_HEAD + + void *base; /* Pointer to the memory chunk. */ + Py_ssize_t len; /* Size in bytes of the memory chunk. */ + +} chunkObject; + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_TYPECAST_BINARY_H) */ diff --git a/source-code/psycopg2/psycopg/typecast_builtins.c b/source-code/psycopg2/psycopg/typecast_builtins.c new file mode 100644 index 0000000..0e4901d --- /dev/null +++ b/source-code/psycopg2/psycopg/typecast_builtins.c @@ -0,0 +1,71 @@ +static long int typecast_NUMBER_types[] = {20, 23, 21, 701, 700, 1700, 0}; +static long int typecast_LONGINTEGER_types[] = {20, 0}; +static long int typecast_INTEGER_types[] = {23, 21, 0}; +static long int typecast_FLOAT_types[] = {701, 700, 0}; +static long int typecast_DECIMAL_types[] = {1700, 0}; +static long int typecast_STRING_types[] = {19, 18, 25, 1042, 1043, 0}; +static long int typecast_BOOLEAN_types[] = {16, 0}; +static long int typecast_DATETIME_types[] = {1114, 0}; +static long int typecast_DATETIMETZ_types[] = {1184, 0}; +static long int typecast_TIME_types[] = {1083, 1266, 0}; +static long int typecast_DATE_types[] = {1082, 0}; +static long int typecast_INTERVAL_types[] = {704, 1186, 0}; +static long int typecast_BINARY_types[] = {17, 0}; +static long int typecast_ROWID_types[] = {26, 0}; +static long int typecast_LONGINTEGERARRAY_types[] = {1016, 0}; +static long int typecast_INTEGERARRAY_types[] = {1005, 1006, 1007, 0}; +static long int typecast_FLOATARRAY_types[] = {1021, 1022, 0}; +static long int typecast_DECIMALARRAY_types[] = {1231, 0}; +static long int typecast_STRINGARRAY_types[] = {1002, 1003, 1009, 1014, 1015, 0}; +static long int typecast_BOOLEANARRAY_types[] = {1000, 0}; +static long int typecast_DATETIMEARRAY_types[] = {1115, 0}; +static long int typecast_DATETIMETZARRAY_types[] = {1185, 0}; +static long int typecast_TIMEARRAY_types[] = {1183, 1270, 0}; +static long int typecast_DATEARRAY_types[] = {1182, 0}; +static long int typecast_INTERVALARRAY_types[] = {1187, 0}; +static long int typecast_BINARYARRAY_types[] = {1001, 0}; +static long int typecast_ROWIDARRAY_types[] = {1028, 1013, 0}; +static long int typecast_INETARRAY_types[] = {1041, 0}; +static long int typecast_CIDRARRAY_types[] = {651, 0}; +static long int typecast_MACADDRARRAY_types[] = {1040, 0}; +static long int typecast_UNKNOWN_types[] = {705, 0}; + + +static typecastObject_initlist typecast_builtins[] = { + {"NUMBER", typecast_NUMBER_types, typecast_NUMBER_cast, NULL}, + {"LONGINTEGER", typecast_LONGINTEGER_types, typecast_LONGINTEGER_cast, NULL}, + {"INTEGER", typecast_INTEGER_types, typecast_INTEGER_cast, NULL}, + {"FLOAT", typecast_FLOAT_types, typecast_FLOAT_cast, NULL}, + {"DECIMAL", typecast_DECIMAL_types, typecast_DECIMAL_cast, NULL}, + {"UNICODE", typecast_STRING_types, typecast_UNICODE_cast, NULL}, + {"BYTES", typecast_STRING_types, typecast_BYTES_cast, NULL}, + {"STRING", typecast_STRING_types, typecast_STRING_cast, NULL}, + {"BOOLEAN", typecast_BOOLEAN_types, typecast_BOOLEAN_cast, NULL}, + {"DATETIME", typecast_DATETIME_types, typecast_DATETIME_cast, NULL}, + {"DATETIMETZ", typecast_DATETIMETZ_types, typecast_DATETIMETZ_cast, NULL}, + {"TIME", typecast_TIME_types, typecast_TIME_cast, NULL}, + {"DATE", typecast_DATE_types, typecast_DATE_cast, NULL}, + {"INTERVAL", typecast_INTERVAL_types, typecast_INTERVAL_cast, NULL}, + {"BINARY", typecast_BINARY_types, typecast_BINARY_cast, NULL}, + {"ROWID", typecast_ROWID_types, typecast_ROWID_cast, NULL}, + {"LONGINTEGERARRAY", typecast_LONGINTEGERARRAY_types, typecast_LONGINTEGERARRAY_cast, "LONGINTEGER"}, + {"INTEGERARRAY", typecast_INTEGERARRAY_types, typecast_INTEGERARRAY_cast, "INTEGER"}, + {"FLOATARRAY", typecast_FLOATARRAY_types, typecast_FLOATARRAY_cast, "FLOAT"}, + {"DECIMALARRAY", typecast_DECIMALARRAY_types, typecast_DECIMALARRAY_cast, "DECIMAL"}, + {"UNICODEARRAY", typecast_STRINGARRAY_types, typecast_UNICODEARRAY_cast, "UNICODE"}, + {"BYTESARRAY", typecast_STRINGARRAY_types, typecast_BYTESARRAY_cast, "BYTES"}, + {"STRINGARRAY", typecast_STRINGARRAY_types, typecast_STRINGARRAY_cast, "STRING"}, + {"BOOLEANARRAY", typecast_BOOLEANARRAY_types, typecast_BOOLEANARRAY_cast, "BOOLEAN"}, + {"DATETIMEARRAY", typecast_DATETIMEARRAY_types, typecast_DATETIMEARRAY_cast, "DATETIME"}, + {"DATETIMETZARRAY", typecast_DATETIMETZARRAY_types, typecast_DATETIMETZARRAY_cast, "DATETIMETZ"}, + {"TIMEARRAY", typecast_TIMEARRAY_types, typecast_TIMEARRAY_cast, "TIME"}, + {"DATEARRAY", typecast_DATEARRAY_types, typecast_DATEARRAY_cast, "DATE"}, + {"INTERVALARRAY", typecast_INTERVALARRAY_types, typecast_INTERVALARRAY_cast, "INTERVAL"}, + {"BINARYARRAY", typecast_BINARYARRAY_types, typecast_BINARYARRAY_cast, "BINARY"}, + {"ROWIDARRAY", typecast_ROWIDARRAY_types, typecast_ROWIDARRAY_cast, "ROWID"}, + {"UNKNOWN", typecast_UNKNOWN_types, typecast_UNKNOWN_cast, NULL}, + {"INETARRAY", typecast_INETARRAY_types, typecast_STRINGARRAY_cast, "STRING"}, + {"CIDRARRAY", typecast_CIDRARRAY_types, typecast_STRINGARRAY_cast, "STRING"}, + {"MACADDRARRAY", typecast_MACADDRARRAY_types, typecast_STRINGARRAY_cast, "STRING"}, + {NULL, NULL, NULL, NULL} +}; diff --git a/source-code/psycopg2/psycopg/typecast_datetime.c b/source-code/psycopg2/psycopg/typecast_datetime.c new file mode 100644 index 0000000..f601a54 --- /dev/null +++ b/source-code/psycopg2/psycopg/typecast_datetime.c @@ -0,0 +1,463 @@ +/* typecast_datetime.c - date and time typecasting functions to python types + * + * Copyright (C) 2001-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include +#include "datetime.h" + +RAISES_NEG static int +typecast_datetime_init(void) +{ + PyDateTime_IMPORT; + + if (!PyDateTimeAPI) { + PyErr_SetString(PyExc_ImportError, "datetime initialization failed"); + return -1; + } + return 0; +} + +/** DATE - cast a date into a date python object **/ + +static PyObject * +typecast_PYDATE_cast(const char *str, Py_ssize_t len, PyObject *curs) +{ + PyObject* obj = NULL; + int n, y=0, m=0, d=0; + + if (str == NULL) { Py_RETURN_NONE; } + + if (!strcmp(str, "infinity") || !strcmp(str, "-infinity")) { + if (str[0] == '-') { + obj = PyObject_GetAttrString( + (PyObject*)PyDateTimeAPI->DateType, "min"); + } + else { + obj = PyObject_GetAttrString( + (PyObject*)PyDateTimeAPI->DateType, "max"); + } + } + + else { + n = typecast_parse_date(str, NULL, &len, &y, &m, &d); + Dprintf("typecast_PYDATE_cast: " + "n = %d, len = " FORMAT_CODE_PY_SSIZE_T ", " + "y = %d, m = %d, d = %d", + n, len, y, m, d); + if (n != 3) { + PyErr_SetString(DataError, "unable to parse date"); + return NULL; + } + else { + obj = PyObject_CallFunction( + (PyObject*)PyDateTimeAPI->DateType, "iii", y, m, d); + } + } + return obj; +} + +/* convert the strings -infinity and infinity into a datetime with timezone */ +static PyObject * +_parse_inftz(const char *str, PyObject *curs) +{ + PyObject *rv = NULL; + PyObject *m = NULL; + PyObject *tzinfo_factory = NULL; + PyObject *tzinfo = NULL; + PyObject *args = NULL; + PyObject *kwargs = NULL; + PyObject *replace = NULL; + + if (!(m = PyObject_GetAttrString( + (PyObject*)PyDateTimeAPI->DateTimeType, + (str[0] == '-' ? "min" : "max")))) { + goto exit; + } + + tzinfo_factory = ((cursorObject *)curs)->tzinfo_factory; + if (tzinfo_factory == Py_None) { + rv = m; + m = NULL; + goto exit; + } + + tzinfo = PyDateTime_TimeZone_UTC; + Py_INCREF(tzinfo); + + /* m.replace(tzinfo=tzinfo) */ + if (!(args = PyTuple_New(0))) { goto exit; } + if (!(kwargs = PyDict_New())) { goto exit; } + if (0 != PyDict_SetItemString(kwargs, "tzinfo", tzinfo)) { goto exit; } + if (!(replace = PyObject_GetAttrString(m, "replace"))) { goto exit; } + rv = PyObject_Call(replace, args, kwargs); + +exit: + Py_XDECREF(replace); + Py_XDECREF(args); + Py_XDECREF(kwargs); + Py_XDECREF(tzinfo); + Py_XDECREF(m); + + return rv; +} + +static PyObject * +_parse_noninftz(const char *str, Py_ssize_t len, PyObject *curs) +{ + PyObject* rv = NULL; + PyObject *tzoff = NULL; + PyObject *tzinfo = NULL; + PyObject *tzinfo_factory; + int n, y=0, m=0, d=0; + int hh=0, mm=0, ss=0, us=0, tzsec=0; + const char *tp = NULL; + + Dprintf("typecast_PYDATETIMETZ_cast: s = %s", str); + n = typecast_parse_date(str, &tp, &len, &y, &m, &d); + Dprintf("typecast_PYDATE_cast: tp = %p " + "n = %d, len = " FORMAT_CODE_PY_SSIZE_T "," + " y = %d, m = %d, d = %d", + tp, n, len, y, m, d); + if (n != 3) { + PyErr_SetString(DataError, "unable to parse date"); + goto exit; + } + + if (len > 0) { + n = typecast_parse_time(tp, NULL, &len, &hh, &mm, &ss, &us, &tzsec); + Dprintf("typecast_PYDATETIMETZ_cast: n = %d," + " len = " FORMAT_CODE_PY_SSIZE_T "," + " hh = %d, mm = %d, ss = %d, us = %d, tzsec = %d", + n, len, hh, mm, ss, us, tzsec); + if (n < 3 || n > 6) { + PyErr_SetString(DataError, "unable to parse time"); + goto exit; + } + } + + if (ss > 59) { + mm += 1; + ss -= 60; + } + + tzinfo_factory = ((cursorObject *)curs)->tzinfo_factory; + if (n >= 5 && tzinfo_factory != Py_None) { + /* we have a time zone, calculate minutes and create + appropriate tzinfo object calling the factory */ + Dprintf("typecast_PYDATETIMETZ_cast: UTC offset = %ds", tzsec); + + if (!(tzoff = PyDelta_FromDSU(0, tzsec, 0))) { goto exit; } + if (!(tzinfo = PyObject_CallFunctionObjArgs( + tzinfo_factory, tzoff, NULL))) { + goto exit; + } + } + else { + Py_INCREF(Py_None); + tzinfo = Py_None; + } + + Dprintf("typecast_PYDATETIMETZ_cast: tzinfo: %p, refcnt = " + FORMAT_CODE_PY_SSIZE_T, + tzinfo, Py_REFCNT(tzinfo)); + rv = PyObject_CallFunction( + (PyObject*)PyDateTimeAPI->DateTimeType, "iiiiiiiO", + y, m, d, hh, mm, ss, us, tzinfo); + +exit: + Py_XDECREF(tzoff); + Py_XDECREF(tzinfo); + return rv; +} + +/** DATETIME - cast a timestamp into a datetime python object **/ + +static PyObject * +typecast_PYDATETIME_cast(const char *str, Py_ssize_t len, PyObject *curs) +{ + if (str == NULL) { Py_RETURN_NONE; } + + /* check for infinity */ + if (!strcmp(str, "infinity") || !strcmp(str, "-infinity")) { + return PyObject_GetAttrString( + (PyObject*)PyDateTimeAPI->DateTimeType, + (str[0] == '-' ? "min" : "max")); + } + + return _parse_noninftz(str, len, curs); +} + +/** DATETIMETZ - cast a timestamptz into a datetime python object **/ + +static PyObject * +typecast_PYDATETIMETZ_cast(const char *str, Py_ssize_t len, PyObject *curs) +{ + if (str == NULL) { Py_RETURN_NONE; } + + if (!strcmp(str, "infinity") || !strcmp(str, "-infinity")) { + return _parse_inftz(str, curs); + } + + return _parse_noninftz(str, len, curs); +} + +/** TIME - parse time into a time object **/ + +static PyObject * +typecast_PYTIME_cast(const char *str, Py_ssize_t len, PyObject *curs) +{ + PyObject* rv = NULL; + PyObject *tzoff = NULL; + PyObject *tzinfo = NULL; + PyObject *tzinfo_factory; + int n, hh=0, mm=0, ss=0, us=0, tzsec=0; + + if (str == NULL) { Py_RETURN_NONE; } + + n = typecast_parse_time(str, NULL, &len, &hh, &mm, &ss, &us, &tzsec); + Dprintf("typecast_PYTIME_cast: n = %d, len = " FORMAT_CODE_PY_SSIZE_T ", " + "hh = %d, mm = %d, ss = %d, us = %d, tzsec = %d", + n, len, hh, mm, ss, us, tzsec); + + if (n < 3 || n > 6) { + PyErr_SetString(DataError, "unable to parse time"); + return NULL; + } + if (ss > 59) { + mm += 1; + ss -= 60; + } + tzinfo_factory = ((cursorObject *)curs)->tzinfo_factory; + if (n >= 5 && tzinfo_factory != Py_None) { + /* we have a time zone, calculate seconds and create + appropriate tzinfo object calling the factory */ + Dprintf("typecast_PYTIME_cast: UTC offset = %ds", tzsec); + + if (!(tzoff = PyDelta_FromDSU(0, tzsec, 0))) { goto exit; } + if (!(tzinfo = PyObject_CallFunctionObjArgs(tzinfo_factory, tzoff, NULL))) { + goto exit; + } + } + else { + Py_INCREF(Py_None); + tzinfo = Py_None; + } + + rv = PyObject_CallFunction((PyObject*)PyDateTimeAPI->TimeType, "iiiiO", + hh, mm, ss, us, tzinfo); + +exit: + Py_XDECREF(tzoff); + Py_XDECREF(tzinfo); + return rv; +} + + +/* Attempt parsing a number as microseconds + * Redshift is reported returning this stuff, see #558 + * + * Return a new `timedelta()` object in case of success or NULL and set an error + */ +static PyObject * +interval_from_usecs(const char *str) +{ + PyObject *us = NULL; + char *pend; + PyObject *rv = NULL; + + Dprintf("interval_from_usecs: %s", str); + + if (!(us = PyLong_FromString((char *)str, &pend, 0))) { + Dprintf("interval_from_usecs: parsing long failed"); + goto exit; + } + + if (*pend != '\0') { + /* there are trailing chars, it's not just micros. Barf. */ + Dprintf("interval_from_usecs: spurious chars %s", pend); + PyErr_Format(PyExc_ValueError, + "expected number of microseconds, got %s", str); + goto exit; + } + + rv = PyObject_CallFunction( + (PyObject*)PyDateTimeAPI->DeltaType, "iiO", 0, 0, us); + +exit: + Py_XDECREF(us); + return rv; +} + + +/** INTERVAL - parse an interval into a timedelta object **/ + +static PyObject * +typecast_PYINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs) +{ + long v = 0, years = 0, months = 0, hours = 0, minutes = 0, micros = 0; + PY_LONG_LONG days = 0, seconds = 0; + int sign = 1, denom = 1, part = 0; + const char *orig = str; + + if (str == NULL) { Py_RETURN_NONE; } + + Dprintf("typecast_PYINTERVAL_cast: s = %s", str); + + while (len-- > 0 && *str) { + switch (*str) { + + case '-': + sign = -1; + break; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + { + long v1; + v1 = v * 10 + (*str - '0'); + /* detect either a rollover, happening if v is really too short, + * or too big value. On Win where long == int the 2nd check + * is useless. */ + if (v1 < v || v1 > (long)INT_MAX) { + /* uhm, oops... but before giving up, maybe it's redshift + * returning microseconds? See #558 */ + PyObject *rv; + if ((rv = interval_from_usecs(orig))) { + return rv; + } + else { + PyErr_Clear(); + } + + PyErr_SetString( + PyExc_OverflowError, "interval component too big"); + return NULL; + } + v = v1; + } + if (part == 6) { + denom *= 10; + } + break; + + case 'y': + if (part == 0) { + years = v * sign; + v = 0; sign = 1; part = 1; + str = skip_until_space2(str, &len); + } + break; + + case 'm': + if (part <= 1) { + months = v * sign; + v = 0; sign = 1; part = 2; + str = skip_until_space2(str, &len); + } + break; + + case 'd': + if (part <= 2) { + days = v * sign; + v = 0; sign = 1; part = 3; + str = skip_until_space2(str, &len); + } + break; + + case ':': + if (part <= 3) { + hours = v; + v = 0; part = 4; + } + else if (part == 4) { + minutes = v; + v = 0; part = 5; + } + break; + + case '.': + if (part == 5) { + seconds = v; + v = 0; part = 6; + } + break; + + case 'P': + PyErr_SetString(NotSupportedError, + "iso_8601 intervalstyle currently not supported"); + return NULL; + + default: + break; + } + + str++; + } + + /* manage last value, be it minutes or seconds or microseconds */ + if (part == 4) { + minutes = v; + } + else if (part == 5) { + seconds = v; + } + else if (part == 6) { + micros = v; + if (denom < 1000000L) { + do { + micros *= 10; + denom *= 10; + } while (denom < 1000000L); + } + else if (denom > 1000000L) { + micros = (long)round((double)micros / denom * 1000000.0); + } + } + else if (part == 0) { + /* Parsing failed, maybe it's just an integer? Assume usecs */ + return interval_from_usecs(orig); + } + + /* add hour, minutes, seconds together and include the sign */ + seconds += 60 * (PY_LONG_LONG)minutes + 3600 * (PY_LONG_LONG)hours; + if (sign < 0) { + seconds = -seconds; + micros = -micros; + } + + /* add the days, months years together - they already include a sign */ + days += 30 * (PY_LONG_LONG)months + 365 * (PY_LONG_LONG)years; + + return PyObject_CallFunction((PyObject*)PyDateTimeAPI->DeltaType, "LLl", + days, seconds, micros); +} + +/* psycopg defaults to using python datetime types */ + +#define typecast_DATE_cast typecast_PYDATE_cast +#define typecast_TIME_cast typecast_PYTIME_cast +#define typecast_INTERVAL_cast typecast_PYINTERVAL_cast +#define typecast_DATETIME_cast typecast_PYDATETIME_cast +#define typecast_DATETIMETZ_cast typecast_PYDATETIMETZ_cast diff --git a/source-code/psycopg2/psycopg/utils.c b/source-code/psycopg2/psycopg/utils.c new file mode 100644 index 0000000..16be906 --- /dev/null +++ b/source-code/psycopg2/psycopg/utils.c @@ -0,0 +1,456 @@ +/* utils.c - miscellaneous utility functions + * + * Copyright (C) 2008-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/connection.h" +#include "psycopg/cursor.h" +#include "psycopg/pgtypes.h" +#include "psycopg/error.h" + +#include +#include + +/* Escape a string for sql inclusion. + * + * The function must be called holding the GIL. + * + * Return a pointer to a new string on the Python heap on success, else NULL + * and set an exception. The returned string includes quotes and leading E if + * needed. + * + * `len` is optional: if < 0 it will be calculated. + * + * If tolen is set, it will contain the length of the escaped string, + * including quotes. + */ +char * +psyco_escape_string(connectionObject *conn, const char *from, Py_ssize_t len, + char *to, Py_ssize_t *tolen) +{ + Py_ssize_t ql; + int eq = (conn && (conn->equote)) ? 1 : 0; + + if (len < 0) { + len = strlen(from); + } else if (strchr(from, '\0') != from + len) { + PyErr_Format(PyExc_ValueError, + "A string literal cannot contain NUL (0x00) characters."); + return NULL; + } + + if (to == NULL) { + to = (char *)PyMem_Malloc((len * 2 + 4) * sizeof(char)); + if (to == NULL) { + PyErr_NoMemory(); + return NULL; + } + } + + { + int err; + if (conn && conn->pgconn) + ql = PQescapeStringConn(conn->pgconn, to+eq+1, from, len, &err); + else + ql = PQescapeString(to+eq+1, from, len); + } + + if (eq) { + to[0] = 'E'; + to[1] = to[ql+2] = '\''; + to[ql+3] = '\0'; + } + else { + to[0] = to[ql+1] = '\''; + to[ql+2] = '\0'; + } + + if (tolen) + *tolen = ql+eq+2; + + return to; +} + +/* Escape a string for inclusion in a query as identifier. + * + * 'len' is optional: if < 0 it will be calculated. + * + * Return a string allocated by Postgres: free it using PQfreemem + * In case of error set a Python exception. + */ +char * +psyco_escape_identifier(connectionObject *conn, const char *str, Py_ssize_t len) +{ + char *rv = NULL; + + if (!conn || !conn->pgconn) { + PyErr_SetString(InterfaceError, "connection not valid"); + goto exit; + } + + if (len < 0) { len = strlen(str); } + + rv = PQescapeIdentifier(conn->pgconn, str, len); + if (!rv) { + char *msg; + msg = PQerrorMessage(conn->pgconn); + if (!msg || !msg[0]) { + msg = "no message provided"; + } + PyErr_Format(InterfaceError, "failed to escape identifier: %s", msg); + } + +exit: + return rv; +} + + +/* Duplicate a string. + * + * Allocate a new buffer on the Python heap containing the new string. + * 'len' is optional: if < 0 the length is calculated. + * + * Store the return in 'to' and return 0 in case of success, else return -1 + * and raise an exception. + * + * If from is null, store null into to. + */ +RAISES_NEG int +psyco_strdup(char **to, const char *from, Py_ssize_t len) +{ + if (!from) { + *to = NULL; + return 0; + } + if (len < 0) { len = strlen(from); } + if (!(*to = PyMem_Malloc(len + 1))) { + PyErr_NoMemory(); + return -1; + } + strcpy(*to, from); + return 0; +} + +/* Ensure a Python object is a bytes string. + * + * Useful when a char * is required out of it. + * + * The function is ref neutral: steals a ref from obj and adds one to the + * return value. This also means that you shouldn't call the function on a + * borrowed ref, if having the object unallocated is not what you want. + * + * It is safe to call the function on NULL. + */ +STEALS(1) PyObject * +psyco_ensure_bytes(PyObject *obj) +{ + PyObject *rv = NULL; + if (!obj) { return NULL; } + + if (PyUnicode_Check(obj)) { + rv = PyUnicode_AsUTF8String(obj); + Py_DECREF(obj); + } + else if (Bytes_Check(obj)) { + rv = obj; + } + else { + PyErr_Format(PyExc_TypeError, + "Expected bytes or unicode string, got %s instead", + Py_TYPE(obj)->tp_name); + Py_DECREF(obj); /* steal the ref anyway */ + } + + return rv; +} + +/* Take a Python object and return text from it. + * + * This means converting bytes to unicode. + * + * The function is ref neutral: steals a ref from obj and adds one to the + * return value. It is safe to call it on NULL. + */ +STEALS(1) PyObject * +psyco_ensure_text(PyObject *obj) +{ + if (obj) { + /* bytes to unicode in Py3 */ + PyObject *rv = PyUnicode_FromEncodedObject(obj, "utf8", "replace"); + Py_DECREF(obj); + return rv; + } + else { + return NULL; + } +} + +/* Check if a file derives from TextIOBase. + * + * Return 1 if it does, else 0, -1 on errors. + */ +int +psyco_is_text_file(PyObject *f) +{ + /* NULL before any call. + * then io.TextIOBase if exists, else None. */ + static PyObject *base; + + /* Try to import os.TextIOBase */ + if (NULL == base) { + PyObject *m; + Dprintf("psyco_is_text_file: importing io.TextIOBase"); + if (!(m = PyImport_ImportModule("io"))) { + Dprintf("psyco_is_text_file: io module not found"); + PyErr_Clear(); + Py_INCREF(Py_None); + base = Py_None; + } + else { + if (!(base = PyObject_GetAttrString(m, "TextIOBase"))) { + Dprintf("psyco_is_text_file: io.TextIOBase not found"); + PyErr_Clear(); + Py_INCREF(Py_None); + base = Py_None; + } + } + Py_XDECREF(m); + } + + if (base != Py_None) { + return PyObject_IsInstance(f, base); + } else { + return 0; + } +} + +/* Make a dict out of PQconninfoOption array */ +PyObject * +psyco_dict_from_conninfo_options(PQconninfoOption *options, int include_password) +{ + PyObject *dict, *res = NULL; + PQconninfoOption *o; + + if (!(dict = PyDict_New())) { goto exit; } + for (o = options; o->keyword != NULL; o++) { + if (o->val != NULL && + (include_password || strcmp(o->keyword, "password") != 0)) { + PyObject *value; + if (!(value = Text_FromUTF8(o->val))) { goto exit; } + if (PyDict_SetItemString(dict, o->keyword, value) != 0) { + Py_DECREF(value); + goto exit; + } + Py_DECREF(value); + } + } + + res = dict; + dict = NULL; + +exit: + Py_XDECREF(dict); + + return res; +} + + +/* Make a connection string out of a string and a dictionary of arguments. + * + * Helper to call psycopg2.extensions.make_dsn() + */ +PyObject * +psyco_make_dsn(PyObject *dsn, PyObject *kwargs) +{ + PyObject *ext = NULL, *make_dsn = NULL; + PyObject *args = NULL, *rv = NULL; + + if (!(ext = PyImport_ImportModule("psycopg2.extensions"))) { goto exit; } + if (!(make_dsn = PyObject_GetAttrString(ext, "make_dsn"))) { goto exit; } + + if (!(args = PyTuple_Pack(1, dsn))) { goto exit; } + rv = PyObject_Call(make_dsn, args, kwargs); + +exit: + Py_XDECREF(args); + Py_XDECREF(make_dsn); + Py_XDECREF(ext); + + return rv; +} + +/* Convert a C string into Python Text using a specified codec. + * + * The codec is the python function codec.getdecoder(enc). + * + * len is optional: use -1 to have it calculated by the function. + */ +PyObject * +psyco_text_from_chars_safe(const char *str, Py_ssize_t len, PyObject *decoder) +{ + static PyObject *replace = NULL; + PyObject *rv = NULL; + PyObject *b = NULL; + PyObject *t = NULL; + + if (!str) { Py_RETURN_NONE; } + + if (len < 0) { len = strlen(str); } + + if (decoder) { + if (!replace) { + if (!(replace = PyUnicode_FromString("replace"))) { goto exit; } + } + if (!(b = PyBytes_FromStringAndSize(str, len))) { goto exit; } + if (!(t = PyObject_CallFunctionObjArgs(decoder, b, replace, NULL))) { + goto exit; + } + + if (!(rv = PyTuple_GetItem(t, 0))) { goto exit; } + Py_INCREF(rv); + } + else { + rv = PyUnicode_DecodeASCII(str, len, "replace"); + } + +exit: + Py_XDECREF(t); + Py_XDECREF(b); + return rv; +} + + +/* psyco_set_error + * + * Create a new error of the given type with extra attributes. + */ + +RAISES BORROWED PyObject * +psyco_set_error(PyObject *exc, cursorObject *curs, const char *msg) +{ + PyObject *pymsg; + PyObject *err = NULL; + connectionObject *conn = NULL; + + if (curs) { + conn = ((cursorObject *)curs)->conn; + } + + if ((pymsg = conn_text_from_chars(conn, msg))) { + err = PyObject_CallFunctionObjArgs(exc, pymsg, NULL); + Py_DECREF(pymsg); + } + else { + /* what's better than an error in an error handler in the morning? + * Anyway, some error was set, refcount is ok... get outta here. */ + return NULL; + } + + if (err && PyObject_TypeCheck(err, &errorType)) { + errorObject *perr = (errorObject *)err; + if (curs) { + Py_CLEAR(perr->cursor); + Py_INCREF(curs); + perr->cursor = curs; + } + } + + if (err) { + PyErr_SetObject(exc, err); + Py_DECREF(err); + } + + return err; +} + + +/* Return nonzero if the current one is the main interpreter */ +static int +psyco_is_main_interp(void) +{ +#if PY_VERSION_HEX >= 0x03080000 + /* tested with Python 3.8.0a2 */ + return _PyInterpreterState_Get() == PyInterpreterState_Main(); +#else + static PyInterpreterState *main_interp = NULL; /* Cached reference */ + PyInterpreterState *interp; + + if (main_interp) { + return (main_interp == PyThreadState_Get()->interp); + } + + /* No cached value: cache the proper value and try again. */ + interp = PyInterpreterState_Head(); + while (interp->next) + interp = interp->next; + + main_interp = interp; + assert (main_interp); + return psyco_is_main_interp(); +#endif +} + +/* psyco_get_decimal_type + + Return a new reference to the decimal type. + + The function returns a cached version of the object, but only in the main + interpreter because subinterpreters are confusing. +*/ + +PyObject * +psyco_get_decimal_type(void) +{ + static PyObject *cachedType = NULL; + PyObject *decimalType = NULL; + PyObject *decimal; + + /* Use the cached object if running from the main interpreter. */ + int can_cache = psyco_is_main_interp(); + if (can_cache && cachedType) { + Py_INCREF(cachedType); + return cachedType; + } + + /* Get a new reference to the Decimal type. */ + decimal = PyImport_ImportModule("decimal"); + if (decimal) { + decimalType = PyObject_GetAttrString(decimal, "Decimal"); + Py_DECREF(decimal); + } + else { + decimalType = NULL; + } + + /* Store the object from future uses. */ + if (can_cache && !cachedType && decimalType) { + Py_INCREF(decimalType); + cachedType = decimalType; + } + + return decimalType; +} diff --git a/source-code/psycopg2/psycopg/utils.h b/source-code/psycopg2/psycopg/utils.h new file mode 100644 index 0000000..5223d3a --- /dev/null +++ b/source-code/psycopg2/psycopg/utils.h @@ -0,0 +1,65 @@ +/* utils.h - function definitions for utility file + * + * Copyright (C) 2018-2019 Daniele Varrazzo + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef UTILS_H +#define UTILS_H 1 + +/* forward declarations */ +typedef struct cursorObject cursorObject; +typedef struct connectionObject connectionObject; +typedef struct replicationMessageObject replicationMessageObject; + +HIDDEN char *psyco_escape_string( + connectionObject *conn, + const char *from, Py_ssize_t len, char *to, Py_ssize_t *tolen); + +HIDDEN char *psyco_escape_identifier( + connectionObject *conn, const char *str, Py_ssize_t len); + +HIDDEN int psyco_strdup(char **to, const char *from, Py_ssize_t len); + +STEALS(1) HIDDEN PyObject * psyco_ensure_bytes(PyObject *obj); +STEALS(1) HIDDEN PyObject * psyco_ensure_text(PyObject *obj); + +HIDDEN int psyco_is_text_file(PyObject *f); + +HIDDEN PyObject *psyco_dict_from_conninfo_options( + PQconninfoOption *options, int include_password); + +HIDDEN PyObject *psyco_make_dsn(PyObject *dsn, PyObject *kwargs); + +HIDDEN PyObject *psyco_text_from_chars_safe( + const char *str, Py_ssize_t len, PyObject *decoder); + +HIDDEN RAISES BORROWED PyObject *psyco_set_error( + PyObject *exc, cursorObject *curs, const char *msg); + +HIDDEN PyObject *psyco_get_decimal_type(void); + +HIDDEN PyObject *Bytes_Format(PyObject *format, PyObject *args); + + +#endif /* !defined(UTILS_H) */ diff --git a/source-code/psycopg2/psycopg/win32_support.c b/source-code/psycopg2/psycopg/win32_support.c new file mode 100644 index 0000000..e82575a --- /dev/null +++ b/source-code/psycopg2/psycopg/win32_support.c @@ -0,0 +1,90 @@ +/* win32_support.c - emulate some functions missing on Win32 + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/win32_support.h" + +#ifdef _WIN32 + +#ifndef __MINGW32__ +/* millisecond-precision port of gettimeofday for Win32, taken from + src/port/gettimeofday.c in PostgreSQL core */ + +/* FILETIME of Jan 1 1970 00:00:00. */ +static const unsigned __int64 epoch = ((unsigned __int64) 116444736000000000ULL); + +/* + * timezone information is stored outside the kernel so tzp isn't used anymore. + * + * Note: this function is not for Win32 high precision timing purpose. See + * elapsed_time(). + */ +int +gettimeofday(struct timeval * tp, void * tzp) +{ + FILETIME file_time; + SYSTEMTIME system_time; + ULARGE_INTEGER ularge; + + GetSystemTime(&system_time); + SystemTimeToFileTime(&system_time, &file_time); + ularge.LowPart = file_time.dwLowDateTime; + ularge.HighPart = file_time.dwHighDateTime; + + tp->tv_sec = (long) ((ularge.QuadPart - epoch) / 10000000L); + tp->tv_usec = (long) (system_time.wMilliseconds * 1000); + + return 0; +} + +/* timeradd missing on MS VC */ +void +timeradd(struct timeval *a, struct timeval *b, struct timeval *c) +{ + c->tv_sec = a->tv_sec + b->tv_sec; + c->tv_usec = a->tv_usec + b->tv_usec; + if(c->tv_usec >= 1000000L) { + c->tv_usec -= 1000000L; + c->tv_sec += 1; + } +} +#endif /* !defined(__MINGW32__) */ + +/* timersub is missing on mingw & MS VC */ +void +timersub(struct timeval *a, struct timeval *b, struct timeval *c) +{ + c->tv_sec = a->tv_sec - b->tv_sec; + c->tv_usec = a->tv_usec - b->tv_usec; + if (c->tv_usec < 0) { + c->tv_usec += 1000000; + c->tv_sec -= 1; + } +} + +#endif /* defined(_WIN32) */ diff --git a/source-code/psycopg2/psycopg/win32_support.h b/source-code/psycopg2/psycopg/win32_support.h new file mode 100644 index 0000000..9fca0d6 --- /dev/null +++ b/source-code/psycopg2/psycopg/win32_support.h @@ -0,0 +1,56 @@ +/* win32_support.h - definitions for win32_support.c + * + * Copyright (C) 2003-2019 Federico Di Gregorio + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ +#ifndef PSYCOPG_WIN32_SUPPORT_H +#define PSYCOPG_WIN32_SUPPORT_H + +#include "psycopg/config.h" + +#ifdef _WIN32 +#include +#endif +#ifdef __MINGW32__ +#include +#endif + + +#ifdef _WIN32 +#ifndef __MINGW32__ +extern HIDDEN int gettimeofday(struct timeval * tp, void * tzp); +extern HIDDEN void timeradd(struct timeval *a, struct timeval *b, struct timeval *c); +#elif +#endif + +extern HIDDEN void timersub(struct timeval *a, struct timeval *b, struct timeval *c); + +#ifndef timercmp +#define timercmp(a, b, cmp) \ + (((a)->tv_sec == (b)->tv_sec) ? \ + ((a)->tv_usec cmp (b)->tv_usec) : \ + ((a)->tv_sec cmp (b)->tv_sec)) +#endif +#endif + +#endif /* !defined(PSYCOPG_WIN32_SUPPORT_H) */ diff --git a/source-code/psycopg2/psycopg/xid.h b/source-code/psycopg2/psycopg/xid.h new file mode 100644 index 0000000..d8d90bd --- /dev/null +++ b/source-code/psycopg2/psycopg/xid.h @@ -0,0 +1,52 @@ +/* xid.h - definition for the psycopg Xid type + * + * Copyright (C) 2008-2019 James Henstridge + * Copyright (C) 2010-2019 Daniele Varrazzo + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_XID_H +#define PSYCOPG_XID_H 1 + +extern HIDDEN PyTypeObject xidType; + +typedef struct { + PyObject_HEAD + + /* the Python-style three-part transaction ID */ + PyObject *format_id; + PyObject *gtrid; + PyObject *bqual; + + /* Additional information PostgreSQL exposes about prepared transactions */ + PyObject *prepared; + PyObject *owner; + PyObject *database; +} xidObject; + +HIDDEN xidObject *xid_ensure(PyObject *oxid); +HIDDEN xidObject *xid_from_string(PyObject *s); +HIDDEN PyObject *xid_get_tid(xidObject *self); +HIDDEN PyObject *xid_recover(PyObject *conn); + +#endif /* PSYCOPG_XID_H */ diff --git a/source-code/psycopg2/psycopg/xid_type.c b/source-code/psycopg2/psycopg/xid_type.c new file mode 100644 index 0000000..094c58c --- /dev/null +++ b/source-code/psycopg2/psycopg/xid_type.c @@ -0,0 +1,665 @@ +/* xid_type.c - python interface to Xid objects + * + * Copyright (C) 2008 Canonical Ltd. + * Copyright (C) 2010-2019 Daniele Varrazzo + * Copyright (C) 2020-2021 The Psycopg Team + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/xid.h" +#include "psycopg/cursor.h" + + +static const char xid_doc[] = + "A transaction identifier used for two-phase commit.\n\n" + "Usually returned by the connection methods `~connection.xid()` and\n" + "`~connection.tpc_recover()`.\n" + "`!Xid` instances can be unpacked as a 3-item tuples containing the items\n" + ":samp:`({format_id},{gtrid},{bqual})`.\n" + "The `!str()` of the object returns the *transaction ID* used\n" + "in the commands sent to the server.\n\n" + "See :ref:`tpc` for an introduction."; + +static const char format_id_doc[] = + "Format ID in a XA transaction.\n\n" + "A non-negative 32 bit integer.\n" + "`!None` if the transaction doesn't follow the XA standard."; + +static const char gtrid_doc[] = + "Global transaction ID in a XA transaction.\n\n" + "If the transaction doesn't follow the XA standard, it is the plain\n" + "*transaction ID* used in the server commands."; + +static const char bqual_doc[] = + "Branch qualifier of the transaction.\n\n" + "In a XA transaction every resource participating to a transaction\n" + "receives a distinct branch qualifier.\n" + "`!None` if the transaction doesn't follow the XA standard."; + +static const char prepared_doc[] = + "Timestamp (with timezone) in which a recovered transaction was prepared."; + +static const char owner_doc[] = + "Name of the user who prepared a recovered transaction."; + +static const char database_doc[] = + "Database the recovered transaction belongs to."; + +static PyMemberDef xid_members[] = { + { "format_id", T_OBJECT, offsetof(xidObject, format_id), READONLY, (char *)format_id_doc }, + { "gtrid", T_OBJECT, offsetof(xidObject, gtrid), READONLY, (char *)gtrid_doc }, + { "bqual", T_OBJECT, offsetof(xidObject, bqual), READONLY, (char *)bqual_doc }, + { "prepared", T_OBJECT, offsetof(xidObject, prepared), READONLY, (char *)prepared_doc }, + { "owner", T_OBJECT, offsetof(xidObject, owner), READONLY, (char *)owner_doc }, + { "database", T_OBJECT, offsetof(xidObject, database), READONLY, (char *)database_doc }, + { NULL } +}; + +static PyObject * +xid_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + return type->tp_alloc(type, 0); +} + +static int +xid_init(xidObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"format_id", "gtrid", "bqual", NULL}; + int format_id; + size_t i, gtrid_len, bqual_len; + const char *gtrid, *bqual; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iss", kwlist, + &format_id, >rid, &bqual)) + return -1; + + if (format_id < 0 || format_id > 0x7fffffff) { + PyErr_SetString(PyExc_ValueError, + "format_id must be a non-negative 32-bit integer"); + return -1; + } + + /* make sure that gtrid is no more than 64 characters long and + made of printable characters (which we're defining as those + between 0x20 and 0x7f). */ + gtrid_len = strlen(gtrid); + if (gtrid_len > 64) { + PyErr_SetString(PyExc_ValueError, + "gtrid must be a string no longer than 64 characters"); + return -1; + } + for (i = 0; i < gtrid_len; i++) { + if (gtrid[i] < 0x20 || gtrid[i] >= 0x7f) { + PyErr_SetString(PyExc_ValueError, + "gtrid must contain only printable characters."); + return -1; + } + } + /* Same for bqual */ + bqual_len = strlen(bqual); + if (bqual_len > 64) { + PyErr_SetString(PyExc_ValueError, + "bqual must be a string no longer than 64 characters"); + return -1; + } + for (i = 0; i < bqual_len; i++) { + if (bqual[i] < 0x20 || bqual[i] >= 0x7f) { + PyErr_SetString(PyExc_ValueError, + "bqual must contain only printable characters."); + return -1; + } + } + + if (!(self->format_id = PyInt_FromLong(format_id))) { return -1; } + if (!(self->gtrid = Text_FromUTF8(gtrid))) { return -1; } + if (!(self->bqual = Text_FromUTF8(bqual))) { return -1; } + Py_INCREF(Py_None); self->prepared = Py_None; + Py_INCREF(Py_None); self->owner = Py_None; + Py_INCREF(Py_None); self->database = Py_None; + + return 0; +} + +static void +xid_dealloc(xidObject *self) +{ + Py_CLEAR(self->format_id); + Py_CLEAR(self->gtrid); + Py_CLEAR(self->bqual); + Py_CLEAR(self->prepared); + Py_CLEAR(self->owner); + Py_CLEAR(self->database); + + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static Py_ssize_t +xid_len(xidObject *self) +{ + return 3; +} + +static PyObject * +xid_getitem(xidObject *self, Py_ssize_t item) +{ + if (item < 0) + item += 3; + + switch (item) { + case 0: + Py_INCREF(self->format_id); + return self->format_id; + case 1: + Py_INCREF(self->gtrid); + return self->gtrid; + case 2: + Py_INCREF(self->bqual); + return self->bqual; + default: + PyErr_SetString(PyExc_IndexError, "index out of range"); + return NULL; + } +} + +static PyObject * +xid_str(xidObject *self) +{ + return xid_get_tid(self); +} + +static PyObject * +xid_repr(xidObject *self) +{ + PyObject *rv = NULL; + PyObject *format = NULL; + PyObject *args = NULL; + + if (Py_None == self->format_id) { + if (!(format = Text_FromUTF8(""))) { + goto exit; + } + if (!(args = PyTuple_New(1))) { goto exit; } + Py_INCREF(self->gtrid); + PyTuple_SET_ITEM(args, 0, self->gtrid); + } + else { + if (!(format = Text_FromUTF8(""))) { + goto exit; + } + if (!(args = PyTuple_New(3))) { goto exit; } + Py_INCREF(self->format_id); + PyTuple_SET_ITEM(args, 0, self->format_id); + Py_INCREF(self->gtrid); + PyTuple_SET_ITEM(args, 1, self->gtrid); + Py_INCREF(self->bqual); + PyTuple_SET_ITEM(args, 2, self->bqual); + } + + rv = Text_Format(format, args); + +exit: + Py_XDECREF(args); + Py_XDECREF(format); + + return rv; +} + + +static const char xid_from_string_doc[] = + "Create a `!Xid` object from a string representation. Static method.\n\n" + "If *s* is a PostgreSQL transaction ID produced by a XA transaction,\n" + "the returned object will have `format_id`, `gtrid`, `bqual` set to\n" + "the values of the preparing XA id.\n" + "Otherwise only the `!gtrid` is populated with the unparsed string.\n" + "The operation is the inverse of the one performed by `!str(xid)`."; + +static PyObject * +xid_from_string_method(PyObject *cls, PyObject *args) +{ + PyObject *s = NULL; + + if (!PyArg_ParseTuple(args, "O", &s)) { return NULL; } + + return (PyObject *)xid_from_string(s); +} + + +static PySequenceMethods xid_sequence = { + (lenfunc)xid_len, /* sq_length */ + 0, /* sq_concat */ + 0, /* sq_repeat */ + (ssizeargfunc)xid_getitem, /* sq_item */ + 0, /* sq_slice */ + 0, /* sq_ass_item */ + 0, /* sq_ass_slice */ + 0, /* sq_contains */ + 0, /* sq_inplace_concat */ + 0, /* sq_inplace_repeat */ +}; + +static struct PyMethodDef xid_methods[] = { + {"from_string", (PyCFunction)xid_from_string_method, + METH_VARARGS|METH_STATIC, xid_from_string_doc}, + {NULL} +}; + +PyTypeObject xidType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2.extensions.Xid", + sizeof(xidObject), 0, + (destructor)xid_dealloc, /* tp_dealloc */ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + (reprfunc)xid_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + &xid_sequence, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + (reprfunc)xid_str, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + /* Notify is not GC as it only has string attributes */ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + xid_doc, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + xid_methods, /*tp_methods*/ + xid_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + (initproc)xid_init, /*tp_init*/ + 0, /*tp_alloc*/ + xid_new, /*tp_new*/ +}; + + +/* Convert a Python object into a proper xid. + * + * Return a new reference to the object or set an exception. + * + * The idea is that people can either create a xid from connection.xid + * or use a regular string they have found in PostgreSQL's pg_prepared_xacts + * in order to recover a transaction not generated by psycopg. + */ +xidObject *xid_ensure(PyObject *oxid) +{ + xidObject *rv = NULL; + + if (PyObject_TypeCheck(oxid, &xidType)) { + Py_INCREF(oxid); + rv = (xidObject *)oxid; + } + else { + rv = xid_from_string(oxid); + } + + return rv; +} + + +/* Encode or decode a string in base64. */ + +static PyObject * +_xid_base64_enc_dec(const char *funcname, PyObject *s) +{ + PyObject *base64 = NULL; + PyObject *func = NULL; + PyObject *rv = NULL; + + if (!(base64 = PyImport_ImportModule("base64"))) { goto exit; } + if (!(func = PyObject_GetAttrString(base64, funcname))) { goto exit; } + + Py_INCREF(s); + if (!(s = psyco_ensure_bytes(s))) { goto exit; } + rv = psyco_ensure_text(PyObject_CallFunctionObjArgs(func, s, NULL)); + Py_DECREF(s); + +exit: + Py_XDECREF(func); + Py_XDECREF(base64); + + return rv; +} + +/* Return a base64-encoded string. */ + +static PyObject * +_xid_encode64(PyObject *s) +{ + return _xid_base64_enc_dec("b64encode", s); +} + +/* Decode a base64-encoded string */ + +static PyObject * +_xid_decode64(PyObject *s) +{ + return _xid_base64_enc_dec("b64decode", s); +} + + +/* Return the PostgreSQL transaction_id for this XA xid. + * + * PostgreSQL wants just a string, while the DBAPI supports the XA standard + * and thus a triple. We use the same conversion algorithm implemented by JDBC + * in order to allow some form of interoperation. + * + * The function must be called while holding the GIL. + * + * see also: the pgjdbc implementation + * http://cvs.pgfoundry.org/cgi-bin/cvsweb.cgi/jdbc/pgjdbc/org/postgresql/xa/RecoveredXid.java?rev=1.2 + */ +PyObject * +xid_get_tid(xidObject *self) +{ + PyObject *rv = NULL; + PyObject *egtrid = NULL; + PyObject *ebqual = NULL; + PyObject *format = NULL; + PyObject *args = NULL; + + if (Py_None == self->format_id) { + /* Unparsed xid: return the gtrid. */ + Py_INCREF(self->gtrid); + rv = self->gtrid; + } + else { + /* XA xid: mash together the components. */ + if (!(egtrid = _xid_encode64(self->gtrid))) { goto exit; } + if (!(ebqual = _xid_encode64(self->bqual))) { goto exit; } + + /* rv = "%d_%s_%s" % (format_id, egtrid, ebqual) */ + if (!(format = Text_FromUTF8("%d_%s_%s"))) { goto exit; } + + if (!(args = PyTuple_New(3))) { goto exit; } + Py_INCREF(self->format_id); + PyTuple_SET_ITEM(args, 0, self->format_id); + PyTuple_SET_ITEM(args, 1, egtrid); egtrid = NULL; + PyTuple_SET_ITEM(args, 2, ebqual); ebqual = NULL; + + if (!(rv = Text_Format(format, args))) { goto exit; } + } + +exit: + Py_XDECREF(args); + Py_XDECREF(format); + Py_XDECREF(egtrid); + Py_XDECREF(ebqual); + + return rv; +} + + +/* Return the regex object to parse a Xid string. + * + * Return a borrowed reference. */ + +BORROWED static PyObject * +_xid_get_parse_regex(void) { + static PyObject *rv; + + if (!rv) { + PyObject *re_mod = NULL; + PyObject *comp = NULL; + PyObject *regex = NULL; + + Dprintf("compiling regexp to parse transaction id"); + + if (!(re_mod = PyImport_ImportModule("re"))) { goto exit; } + if (!(comp = PyObject_GetAttrString(re_mod, "compile"))) { goto exit; } + if (!(regex = PyObject_CallFunction(comp, "s", + "^(\\d+)_([^_]*)_([^_]*)$"))) { + goto exit; + } + + /* Good, compiled. */ + rv = regex; + regex = NULL; + +exit: + Py_XDECREF(regex); + Py_XDECREF(comp); + Py_XDECREF(re_mod); + } + + return rv; +} + +/* Try to parse a Xid string representation in a Xid object. + * + * + * Return NULL + exception if parsing failed. Else a new Xid object. */ + +static xidObject * +_xid_parse_string(PyObject *str) { + PyObject *regex; + PyObject *m = NULL; + PyObject *group = NULL; + PyObject *item = NULL; + PyObject *format_id = NULL; + PyObject *egtrid = NULL; + PyObject *ebqual = NULL; + PyObject *gtrid = NULL; + PyObject *bqual = NULL; + xidObject *rv = NULL; + + /* check if the string is a possible XA triple with a regexp */ + if (!(regex = _xid_get_parse_regex())) { goto exit; } + if (!(m = PyObject_CallMethod(regex, "match", "O", str))) { goto exit; } + if (m == Py_None) { + PyErr_SetString(PyExc_ValueError, "bad xid format"); + goto exit; + } + + /* Extract the components from the regexp */ + if (!(group = PyObject_GetAttrString(m, "group"))) { goto exit; } + if (!(item = PyObject_CallFunction(group, "i", 1))) { goto exit; } + if (!(format_id = PyObject_CallFunctionObjArgs( + (PyObject *)&PyInt_Type, item, NULL))) { + goto exit; + } + if (!(egtrid = PyObject_CallFunction(group, "i", 2))) { goto exit; } + if (!(gtrid = _xid_decode64(egtrid))) { goto exit; } + + if (!(ebqual = PyObject_CallFunction(group, "i", 3))) { goto exit; } + if (!(bqual = _xid_decode64(ebqual))) { goto exit; } + + /* Try to build the xid with the parsed material */ + rv = (xidObject *)PyObject_CallFunctionObjArgs((PyObject *)&xidType, + format_id, gtrid, bqual, NULL); + +exit: + Py_XDECREF(bqual); + Py_XDECREF(ebqual); + Py_XDECREF(gtrid); + Py_XDECREF(egtrid); + Py_XDECREF(format_id); + Py_XDECREF(item); + Py_XDECREF(group); + Py_XDECREF(m); + + return rv; +} + +/* Return a new Xid object representing a transaction ID not conform to + * the XA specifications. */ + +static xidObject * +_xid_unparsed_from_string(PyObject *str) { + xidObject *xid = NULL; + xidObject *rv = NULL; + + /* fake args to work around the checks performed by the xid init */ + if (!(xid = (xidObject *)PyObject_CallFunction((PyObject *)&xidType, + "iss", 0, "", ""))) { + goto exit; + } + + /* set xid.gtrid = str */ + Py_CLEAR(xid->gtrid); + Py_INCREF(str); + xid->gtrid = str; + + /* set xid.format_id = None */ + Py_CLEAR(xid->format_id); + Py_INCREF(Py_None); + xid->format_id = Py_None; + + /* set xid.bqual = None */ + Py_CLEAR(xid->bqual); + Py_INCREF(Py_None); + xid->bqual = Py_None; + + /* return the finished object */ + rv = xid; + xid = NULL; + +exit: + Py_XDECREF(xid); + + return rv; +} + +/* Build a Xid from a string representation. + * + * If the xid is in the format generated by Psycopg, unpack the tuple into + * the struct members. Otherwise generate an "unparsed" xid. + */ +xidObject * +xid_from_string(PyObject *str) { + xidObject *rv; + + if (!(Bytes_Check(str) || PyUnicode_Check(str))) { + PyErr_SetString(PyExc_TypeError, "not a valid transaction id"); + return NULL; + } + + /* Try to parse an XA triple from the string. This may fail for several + * reasons, such as the rules stated in Xid.__init__. */ + rv = _xid_parse_string(str); + if (!rv) { + /* If parsing failed, treat the string as an unparsed id */ + PyErr_Clear(); + rv = _xid_unparsed_from_string(str); + } + + return rv; +} + + +/* conn_tpc_recover -- return a list of pending TPC Xid */ + +PyObject * +xid_recover(PyObject *conn) +{ + PyObject *rv = NULL; + PyObject *curs = NULL; + PyObject *xids = NULL; + xidObject *xid = NULL; + PyObject *recs = NULL; + PyObject *rec = NULL; + PyObject *item = NULL; + PyObject *tmp; + Py_ssize_t len, i; + + /* curs = conn.cursor() + * (sort of. Use the real cursor in case the connection returns + * something non-dbapi -- see ticket #114) */ + if (!(curs = PyObject_CallFunctionObjArgs( + (PyObject *)&cursorType, conn, NULL))) { goto exit; } + + /* curs.execute(...) */ + if (!(tmp = PyObject_CallMethod(curs, "execute", "s", + "SELECT gid, prepared, owner, database FROM pg_prepared_xacts"))) + { + goto exit; + } + Py_DECREF(tmp); + + /* recs = curs.fetchall() */ + if (!(recs = PyObject_CallMethod(curs, "fetchall", NULL))) { goto exit; } + + /* curs.close() */ + if (!(tmp = PyObject_CallMethod(curs, "close", NULL))) { goto exit; } + Py_DECREF(tmp); + + /* Build the list with return values. */ + if (0 > (len = PySequence_Size(recs))) { goto exit; } + if (!(xids = PyList_New(len))) { goto exit; } + + /* populate the xids list */ + for (i = 0; i < len; ++i) { + if (!(rec = PySequence_GetItem(recs, i))) { goto exit; } + + /* Get the xid with the XA triple set */ + if (!(item = PySequence_GetItem(rec, 0))) { goto exit; } + if (!(xid = xid_from_string(item))) { goto exit; } + Py_CLEAR(item); + + /* set xid.prepared */ + Py_CLEAR(xid->prepared); + if (!(xid->prepared = PySequence_GetItem(rec, 1))) { goto exit; } + + /* set xid.owner */ + Py_CLEAR(xid->owner); + if (!(xid->owner = PySequence_GetItem(rec, 2))) { goto exit; } + + /* set xid.database */ + Py_CLEAR(xid->database); + if (!(xid->database = PySequence_GetItem(rec, 3))) { goto exit; } + + /* xid finished: add it to the returned list */ + PyList_SET_ITEM(xids, i, (PyObject *)xid); + xid = NULL; /* ref stolen */ + + Py_CLEAR(rec); + } + + /* set the return value. */ + rv = xids; + xids = NULL; + +exit: + Py_XDECREF(xids); + Py_XDECREF(xid); + Py_XDECREF(curs); + Py_XDECREF(recs); + Py_XDECREF(rec); + Py_XDECREF(item); + + return rv; +} diff --git a/source-code/psycopg2/psycopg1.py b/source-code/psycopg2/psycopg1.py deleted file mode 100755 index 3808aaa..0000000 --- a/source-code/psycopg2/psycopg1.py +++ /dev/null @@ -1,96 +0,0 @@ -"""psycopg 1.1.x compatibility module - -This module uses the new style connection and cursor types to build a psycopg -1.1.1.x compatibility layer. It should be considered a temporary hack to run -old code while porting to psycopg 2. Import it as follows:: - - from psycopg2 import psycopg1 as psycopg -""" -# psycopg/psycopg1.py - psycopg 1.1.x compatibility module -# -# Copyright (C) 2003-2010 Federico Di Gregorio -# -# psycopg2 is free software: you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# In addition, as a special exception, the copyright holders give -# permission to link this program with the OpenSSL library (or with -# modified versions of OpenSSL that use the same license as OpenSSL), -# and distribute linked combinations including the two. -# -# You must obey the GNU Lesser General Public License in all respects for -# all of the code used other than OpenSSL. -# -# psycopg2 is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -# License for more details. - -import psycopg2._psycopg as _2psycopg # noqa -from psycopg2.extensions import cursor as _2cursor -from psycopg2.extensions import connection as _2connection - -from psycopg2 import * # noqa -import psycopg2.extensions as _ext -_2connect = connect - - -def connect(*args, **kwargs): - """connect(dsn, ...) -> new psycopg 1.1.x compatible connection object""" - kwargs['connection_factory'] = connection - conn = _2connect(*args, **kwargs) - conn.set_isolation_level(_ext.ISOLATION_LEVEL_READ_COMMITTED) - return conn - - -class connection(_2connection): - """psycopg 1.1.x connection.""" - - def cursor(self): - """cursor() -> new psycopg 1.1.x compatible cursor object""" - return _2connection.cursor(self, cursor_factory=cursor) - - def autocommit(self, on_off=1): - """autocommit(on_off=1) -> switch autocommit on (1) or off (0)""" - if on_off > 0: - self.set_isolation_level(_ext.ISOLATION_LEVEL_AUTOCOMMIT) - else: - self.set_isolation_level(_ext.ISOLATION_LEVEL_READ_COMMITTED) - - -class cursor(_2cursor): - """psycopg 1.1.x cursor. - - Note that this cursor implements the exact procedure used by psycopg 1 to - build dictionaries out of result rows. The DictCursor in the - psycopg.extras modules implements a much better and faster algorithm. - """ - - def __build_dict(self, row): - res = {} - for i in range(len(self.description)): - res[self.description[i][0]] = row[i] - return res - - def dictfetchone(self): - row = _2cursor.fetchone(self) - if row: - return self.__build_dict(row) - else: - return row - - def dictfetchmany(self, size): - res = [] - rows = _2cursor.fetchmany(self, size) - for row in rows: - res.append(self.__build_dict(row)) - return res - - def dictfetchall(self): - res = [] - rows = _2cursor.fetchall(self) - for row in rows: - res.append(self.__build_dict(row)) - return res diff --git a/source-code/psycopg2/psycopg2_binary.egg-info/PKG-INFO b/source-code/psycopg2/psycopg2_binary.egg-info/PKG-INFO new file mode 100644 index 0000000..3887a5c --- /dev/null +++ b/source-code/psycopg2/psycopg2_binary.egg-info/PKG-INFO @@ -0,0 +1,112 @@ +Metadata-Version: 2.1 +Name: psycopg2-binary +Version: 2.9.9 +Summary: psycopg2 - Python-PostgreSQL Database Adapter +Home-page: https://psycopg.org/ +Author: Federico Di Gregorio +Author-email: fog@initd.org +Maintainer: Daniele Varrazzo +Maintainer-email: daniele.varrazzo@gmail.com +License: LGPL with exceptions +Project-URL: Homepage, https://psycopg.org/ +Project-URL: Documentation, https://www.psycopg.org/docs/ +Project-URL: Code, https://github.com/psycopg/psycopg2 +Project-URL: Issue Tracker, https://github.com/psycopg/psycopg2/issues +Project-URL: Download, https://pypi.org/project/psycopg2/ +Platform: any +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: C +Classifier: Programming Language :: SQL +Classifier: Topic :: Database +Classifier: Topic :: Database :: Front-Ends +Classifier: Topic :: Software Development +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: Unix +Requires-Python: >=3.7 +License-File: LICENSE + +Psycopg is the most popular PostgreSQL database adapter for the Python +programming language. Its main features are the complete implementation of +the Python DB API 2.0 specification and the thread safety (several threads can +share the same connection). It was designed for heavily multi-threaded +applications that create and destroy lots of cursors and make a large number +of concurrent "INSERT"s or "UPDATE"s. + +Psycopg 2 is mostly implemented in C as a libpq wrapper, resulting in being +both efficient and secure. It features client-side and server-side cursors, +asynchronous communication and notifications, "COPY TO/COPY FROM" support. +Many Python types are supported out-of-the-box and adapted to matching +PostgreSQL data types; adaptation can be extended and customized thanks to a +flexible objects adaptation system. + +Psycopg 2 is both Unicode and Python 3 friendly. + + +Documentation +------------- + +Documentation is included in the ``doc`` directory and is `available online`__. + +.. __: https://www.psycopg.org/docs/ + +For any other resource (source code repository, bug tracker, mailing list) +please check the `project homepage`__. + +.. __: https://psycopg.org/ + + +Installation +------------ + +Building Psycopg requires a few prerequisites (a C compiler, some development +packages): please check the install_ and the faq_ documents in the ``doc`` dir +or online for the details. + +If prerequisites are met, you can install psycopg like any other Python +package, using ``pip`` to download it from PyPI_:: + + $ pip install psycopg2 + +or using ``setup.py`` if you have downloaded the source package locally:: + + $ python setup.py build + $ sudo python setup.py install + +You can also obtain a stand-alone package, not requiring a compiler or +external libraries, by installing the `psycopg2-binary`_ package from PyPI:: + + $ pip install psycopg2-binary + +The binary package is a practical choice for development and testing but in +production it is advised to use the package built from sources. + +.. _PyPI: https://pypi.org/project/psycopg2/ +.. _psycopg2-binary: https://pypi.org/project/psycopg2-binary/ +.. _install: https://www.psycopg.org/docs/install.html#install-from-source +.. _faq: https://www.psycopg.org/docs/faq.html#faq-compile + +:Linux/OSX: |gh-actions| +:Windows: |appveyor| + +.. |gh-actions| image:: https://github.com/psycopg/psycopg2/actions/workflows/tests.yml/badge.svg + :target: https://github.com/psycopg/psycopg2/actions/workflows/tests.yml + :alt: Linux and OSX build status + +.. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/psycopg/psycopg2?branch=master&svg=true + :target: https://ci.appveyor.com/project/psycopg/psycopg2/branch/master + :alt: Windows build status + + diff --git a/source-code/psycopg2/psycopg2_binary.egg-info/SOURCES.txt b/source-code/psycopg2/psycopg2_binary.egg-info/SOURCES.txt new file mode 100644 index 0000000..8445d50 --- /dev/null +++ b/source-code/psycopg2/psycopg2_binary.egg-info/SOURCES.txt @@ -0,0 +1,178 @@ +AUTHORS +INSTALL +LICENSE +MANIFEST.in +Makefile +NEWS +README.rst +setup.cfg +setup.py +doc/COPYING.LESSER +doc/Makefile +doc/README.rst +doc/SUCCESS +doc/pep-0249.txt +doc/requirements.txt +doc/src/Makefile +doc/src/advanced.rst +doc/src/conf.py +doc/src/connection.rst +doc/src/cursor.rst +doc/src/errorcodes.rst +doc/src/errors.rst +doc/src/extensions.rst +doc/src/extras.rst +doc/src/faq.rst +doc/src/index.rst +doc/src/install.rst +doc/src/license.rst +doc/src/module.rst +doc/src/news.rst +doc/src/pool.rst +doc/src/sql.rst +doc/src/tz.rst +doc/src/usage.rst +doc/src/_static/psycopg.css +doc/src/tools/make_sqlstate_docs.py +doc/src/tools/lib/dbapi_extension.py +doc/src/tools/lib/sql_role.py +doc/src/tools/lib/ticket_role.py +lib/__init__.py +lib/_ipaddress.py +lib/_json.py +lib/_range.py +lib/errorcodes.py +lib/errors.py +lib/extensions.py +lib/extras.py +lib/pool.py +lib/sql.py +lib/tz.py +psycopg/_psycopg.vc9.amd64.manifest +psycopg/_psycopg.vc9.x86.manifest +psycopg/adapter_asis.c +psycopg/adapter_asis.h +psycopg/adapter_binary.c +psycopg/adapter_binary.h +psycopg/adapter_datetime.c +psycopg/adapter_datetime.h +psycopg/adapter_list.c +psycopg/adapter_list.h +psycopg/adapter_pboolean.c +psycopg/adapter_pboolean.h +psycopg/adapter_pdecimal.c +psycopg/adapter_pdecimal.h +psycopg/adapter_pfloat.c +psycopg/adapter_pfloat.h +psycopg/adapter_pint.c +psycopg/adapter_pint.h +psycopg/adapter_qstring.c +psycopg/adapter_qstring.h +psycopg/aix_support.c +psycopg/aix_support.h +psycopg/bytes_format.c +psycopg/column.h +psycopg/column_type.c +psycopg/config.h +psycopg/connection.h +psycopg/connection_int.c +psycopg/connection_type.c +psycopg/conninfo.h +psycopg/conninfo_type.c +psycopg/cursor.h +psycopg/cursor_int.c +psycopg/cursor_type.c +psycopg/diagnostics.h +psycopg/diagnostics_type.c +psycopg/error.h +psycopg/error_type.c +psycopg/green.c +psycopg/green.h +psycopg/libpq_support.c +psycopg/libpq_support.h +psycopg/lobject.h +psycopg/lobject_int.c +psycopg/lobject_type.c +psycopg/microprotocols.c +psycopg/microprotocols.h +psycopg/microprotocols_proto.c +psycopg/microprotocols_proto.h +psycopg/notify.h +psycopg/notify_type.c +psycopg/pgtypes.h +psycopg/pqpath.c +psycopg/pqpath.h +psycopg/psycopg.h +psycopg/psycopgmodule.c +psycopg/python.h +psycopg/replication_connection.h +psycopg/replication_connection_type.c +psycopg/replication_cursor.h +psycopg/replication_cursor_type.c +psycopg/replication_message.h +psycopg/replication_message_type.c +psycopg/solaris_support.c +psycopg/solaris_support.h +psycopg/sqlstate_errors.h +psycopg/typecast.c +psycopg/typecast.h +psycopg/typecast_array.c +psycopg/typecast_basic.c +psycopg/typecast_binary.c +psycopg/typecast_binary.h +psycopg/typecast_builtins.c +psycopg/typecast_datetime.c +psycopg/utils.c +psycopg/utils.h +psycopg/win32_support.c +psycopg/win32_support.h +psycopg/xid.h +psycopg/xid_type.c +psycopg2_binary.egg-info/PKG-INFO +psycopg2_binary.egg-info/SOURCES.txt +psycopg2_binary.egg-info/dependency_links.txt +psycopg2_binary.egg-info/top_level.txt +scripts/make_errorcodes.py +scripts/make_errors.py +scripts/refcounter.py +scripts/build/appveyor.py +scripts/build/build_libpq.sh +scripts/build/build_macos_arm64.sh +scripts/build/build_sdist.sh +scripts/build/download_packages_appveyor.py +scripts/build/print_so_versions.sh +scripts/build/run_build_macos_arm64.sh +scripts/build/scaleway_m1.sh +scripts/build/strip_wheel.sh +scripts/build/wheel_linux_before_all.sh +scripts/build/wheel_macos_before_all.sh +tests/__init__.py +tests/dbapi20.py +tests/dbapi20_tpc.py +tests/test_async.py +tests/test_bugX000.py +tests/test_bug_gc.py +tests/test_cancel.py +tests/test_connection.py +tests/test_copy.py +tests/test_cursor.py +tests/test_dates.py +tests/test_errcodes.py +tests/test_errors.py +tests/test_extras_dictcursor.py +tests/test_fast_executemany.py +tests/test_green.py +tests/test_ipaddress.py +tests/test_lobject.py +tests/test_module.py +tests/test_notify.py +tests/test_psycopg2_dbapi20.py +tests/test_quote.py +tests/test_replication.py +tests/test_sql.py +tests/test_transaction.py +tests/test_types_basic.py +tests/test_types_extras.py +tests/test_with.py +tests/testconfig.py +tests/testutils.py \ No newline at end of file diff --git a/source-code/psycopg2/psycopg2_binary.egg-info/dependency_links.txt b/source-code/psycopg2/psycopg2_binary.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/source-code/psycopg2/psycopg2_binary.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/source-code/psycopg2/psycopg2_binary.egg-info/top_level.txt b/source-code/psycopg2/psycopg2_binary.egg-info/top_level.txt new file mode 100644 index 0000000..658130b --- /dev/null +++ b/source-code/psycopg2/psycopg2_binary.egg-info/top_level.txt @@ -0,0 +1 @@ +psycopg2 From 3e135b2eda90c346793836773dd9b736589fe157 Mon Sep 17 00:00:00 2001 From: SKIMRAN0509 Date: Fri, 8 Mar 2024 12:32:19 +0530 Subject: [PATCH 8/8] updaed python verison change to 3.12 --- main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.tf b/main.tf index 1a2c5e6..e7d561c 100644 --- a/main.tf +++ b/main.tf @@ -109,7 +109,7 @@ resource "aws_lambda_function" "default" { role = join("", aws_iam_role.lambda.*.arn) handler = "main.lambda_handler" - runtime = "python3.7" + runtime = "python3.12" timeout = var.timeout memory_size = var.memory kms_key_arn = var.kms_key