diff --git a/main.tf b/main.tf index d01f561..e7d561c 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 @@ -78,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.16.0" + source = "cloudposse/label/null" + version = "0.25.0" attributes = compact(concat(var.attributes, ["db", "provisioner"])) delimiter = var.delimiter name = var.name @@ -117,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 @@ -136,6 +128,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 } } @@ -267,7 +260,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 +296,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}"] } } @@ -331,21 +324,23 @@ 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" +######################################################## +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( + 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"] : [], + ) } +data "aws_iam_policy_document" "empty" {} + resource "aws_iam_role" "lambda" { count = var.enabled ? 1 : 0 @@ -362,7 +357,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/source-code/main.py b/source-code/main.py index 2365a67..2ccbfa6 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): @@ -208,8 +209,10 @@ 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.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, @@ -232,8 +235,30 @@ def provision_mysql_db(self, info: DBInfo): 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() @@ -257,7 +282,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/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 613c419..0000000 Binary files a/source-code/psycopg2/_psycopg.cpython-37m-x86_64-linux-gnu.so and /dev/null differ 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 diff --git a/variables.tf b/variables.tf index 1d5cb88..b64df12 100644 --- a/variables.tf +++ b/variables.tf @@ -149,3 +149,15 @@ 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" +} + +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 cf4d633..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" + aws = "~> 5.26.0" archive = "~> 1.3" local = "~> 1.2" } -} +} \ No newline at end of file