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