summaryrefslogtreecommitdiffstats
path: root/contrib/python/pg8000
diff options
context:
space:
mode:
authorvitalyisaev <[email protected]>2023-11-14 09:58:56 +0300
committervitalyisaev <[email protected]>2023-11-14 10:20:20 +0300
commitc2b2dfd9827a400a8495e172a56343462e3ceb82 (patch)
treecd4e4f597d01bede4c82dffeb2d780d0a9046bd0 /contrib/python/pg8000
parentd4ae8f119e67808cb0cf776ba6e0cf95296f2df7 (diff)
YQ Connector: move tests from yql to ydb (OSS)
Перенос папки с тестами на Коннектор из папки yql в папку ydb (синхронизируется с github).
Diffstat (limited to 'contrib/python/pg8000')
-rw-r--r--contrib/python/pg8000/.dist-info/METADATA2459
-rw-r--r--contrib/python/pg8000/.dist-info/top_level.txt1
-rw-r--r--contrib/python/pg8000/LICENSE29
-rw-r--r--contrib/python/pg8000/README.rst2431
-rw-r--r--contrib/python/pg8000/pg8000/__init__.py213
-rw-r--r--contrib/python/pg8000/pg8000/converters.py771
-rw-r--r--contrib/python/pg8000/pg8000/core.py870
-rw-r--r--contrib/python/pg8000/pg8000/dbapi.py976
-rw-r--r--contrib/python/pg8000/pg8000/exceptions.py32
-rw-r--r--contrib/python/pg8000/pg8000/legacy.py822
-rw-r--r--contrib/python/pg8000/pg8000/native.py293
-rw-r--r--contrib/python/pg8000/pg8000/types.py305
-rw-r--r--contrib/python/pg8000/ya.make34
13 files changed, 9236 insertions, 0 deletions
diff --git a/contrib/python/pg8000/.dist-info/METADATA b/contrib/python/pg8000/.dist-info/METADATA
new file mode 100644
index 00000000000..6980a1f026a
--- /dev/null
+++ b/contrib/python/pg8000/.dist-info/METADATA
@@ -0,0 +1,2459 @@
+Metadata-Version: 2.1
+Name: pg8000
+Version: 1.30.2
+Summary: PostgreSQL interface library
+License: BSD 3-Clause License
+Project-URL: Homepage, https://github.com/tlocke/pg8000
+Keywords: postgresql,dbapi
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+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 :: Implementation
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Operating System :: OS Independent
+Classifier: Topic :: Database :: Front-Ends
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Requires-Python: >=3.8
+Description-Content-Type: text/x-rst
+License-File: LICENSE
+Requires-Dist: scramp >=1.4.4
+Requires-Dist: python-dateutil >=2.8.2
+
+======
+pg8000
+======
+
+.. |ssl.SSLContext| replace:: ``ssl.SSLContext``
+.. _ssl.SSLContext: https://docs.python.org/3/library/ssl.html#ssl.SSLContext
+
+.. |ssl.create_default_context()| replace:: ``ssl.create_default_context()``
+.. _ssl.create_default_context(): https://docs.python.org/3/library/ssl.html#ssl.create_default_context
+
+pg8000 is a pure-`Python <https://www.python.org/>`_
+`PostgreSQL <http://www.postgresql.org/>`_ driver that complies with
+`DB-API 2.0 <http://www.python.org/dev/peps/pep-0249/>`_. It is tested on Python
+versions 3.8+, on CPython and PyPy, and PostgreSQL versions 11+. pg8000's name comes
+from the belief that it is probably about the 8000th PostgreSQL interface for Python.
+pg8000 is distributed under the BSD 3-clause license.
+
+All bug reports, feature requests and contributions are welcome at
+`http://github.com/tlocke/pg8000/ <http://github.com/tlocke/pg8000/>`_.
+
+.. image:: https://github.com/tlocke/pg8000/workflows/pg8000/badge.svg
+ :alt: Build Status
+
+.. contents:: Table of Contents
+ :depth: 2
+ :local:
+
+Installation
+------------
+
+To install pg8000 using `pip` type:
+
+`pip install pg8000`
+
+
+Native API Interactive Examples
+-------------------------------
+
+pg8000 comes with two APIs, the native pg8000 API and the DB-API 2.0 standard
+API. These are the examples for the native API, and the DB-API 2.0 examples
+follow in the next section.
+
+
+Basic Example
+`````````````
+
+Import pg8000, connect to the database, create a table, add some rows and then
+query the table:
+
+>>> import pg8000.native
+>>>
+>>> # Connect to the database with user name postgres
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> # Create a temporary table
+>>>
+>>> con.run("CREATE TEMPORARY TABLE book (id SERIAL, title TEXT)")
+>>>
+>>> # Populate the table
+>>>
+>>> for title in ("Ender's Game", "The Magus"):
+... con.run("INSERT INTO book (title) VALUES (:title)", title=title)
+>>>
+>>> # Print all the rows in the table
+>>>
+>>> for row in con.run("SELECT * FROM book"):
+... print(row)
+[1, "Ender's Game"]
+[2, 'The Magus']
+>>>
+>>> con.close()
+
+
+Transactions
+````````````
+
+Here's how to run groups of SQL statements in a
+`transaction <https://www.postgresql.org/docs/current/tutorial-transactions.html>`_:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("START TRANSACTION")
+>>>
+>>> # Create a temporary table
+>>> con.run("CREATE TEMPORARY TABLE book (id SERIAL, title TEXT)")
+>>>
+>>> for title in ("Ender's Game", "The Magus", "Phineas Finn"):
+... con.run("INSERT INTO book (title) VALUES (:title)", title=title)
+>>> con.run("COMMIT")
+>>> for row in con.run("SELECT * FROM book"):
+... print(row)
+[1, "Ender's Game"]
+[2, 'The Magus']
+[3, 'Phineas Finn']
+>>>
+>>> con.close()
+
+rolling back a transaction:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> # Create a temporary table
+>>> con.run("CREATE TEMPORARY TABLE book (id SERIAL, title TEXT)")
+>>>
+>>> for title in ("Ender's Game", "The Magus", "Phineas Finn"):
+... con.run("INSERT INTO book (title) VALUES (:title)", title=title)
+>>>
+>>> con.run("START TRANSACTION")
+>>> con.run("DELETE FROM book WHERE title = :title", title="Phineas Finn")
+>>> con.run("ROLLBACK")
+>>> for row in con.run("SELECT * FROM book"):
+... print(row)
+[1, "Ender's Game"]
+[2, 'The Magus']
+[3, 'Phineas Finn']
+>>>
+>>> con.close()
+
+NB. There is `a longstanding bug <https://github.com/tlocke/pg8000/issues/36>`_
+in the PostgreSQL server whereby if a `COMMIT` is issued against a failed
+transaction, the transaction is silently rolled back, rather than an error being
+returned. pg8000 attempts to detect when this has happened and raise an
+`InterfaceError`.
+
+
+Query Using Functions
+`````````````````````
+
+Another query, using some PostgreSQL functions:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("SELECT TO_CHAR(TIMESTAMP '2021-10-10', 'YYYY BC')")
+[['2021 AD']]
+>>>
+>>> con.close()
+
+
+Interval Type
+`````````````
+
+A query that returns the PostgreSQL interval type:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> import datetime
+>>>
+>>> ts = datetime.date(1980, 4, 27)
+>>> con.run("SELECT timestamp '2013-12-01 16:06' - :ts", ts=ts)
+[[datetime.timedelta(days=12271, seconds=57960)]]
+>>>
+>>> con.close()
+
+
+Point Type
+``````````
+
+A round-trip with a
+`PostgreSQL point <https://www.postgresql.org/docs/current/datatype-geometric.html>`_
+type:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("SELECT CAST(:pt as point)", pt=(2.3,1))
+[[(2.3, 1.0)]]
+>>>
+>>> con.close()
+
+
+Client Encoding
+```````````````
+
+When communicating with the server, pg8000 uses the character set that the server asks
+it to use (the client encoding). By default the client encoding is the database's
+character set (chosen when the database is created), but the client encoding can be
+changed in a number of ways (eg. setting ``CLIENT_ENCODING`` in ``postgresql.conf``).
+Another way of changing the client encoding is by using an SQL command. For example:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("SET CLIENT_ENCODING TO 'UTF8'")
+>>> con.run("SHOW CLIENT_ENCODING")
+[['UTF8']]
+>>>
+>>> con.close()
+
+
+JSON
+````
+
+`JSON <https://www.postgresql.org/docs/current/datatype-json.html>`_ always comes back
+from the server de-serialized. If the JSON you want to send is a ``dict`` then you can
+just do:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> val = {'name': 'Apollo 11 Cave', 'zebra': True, 'age': 26.003}
+>>> con.run("SELECT CAST(:apollo as jsonb)", apollo=val)
+[[{'age': 26.003, 'name': 'Apollo 11 Cave', 'zebra': True}]]
+>>>
+>>> con.close()
+
+JSON can always be sent in serialized form to the server:
+
+>>> import json
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>>
+>>> val = ['Apollo 11 Cave', True, 26.003]
+>>> con.run("SELECT CAST(:apollo as jsonb)", apollo=json.dumps(val))
+[[['Apollo 11 Cave', True, 26.003]]]
+>>>
+>>> con.close()
+
+JSON queries can be have parameters:
+
+>>> import pg8000.native
+>>>
+>>> with pg8000.native.Connection("postgres", password="cpsnow") as con:
+... con.run(""" SELECT CAST('{"a":1, "b":2}' AS jsonb) @> :v """, v={"b": 2})
+[[True]]
+
+
+Retrieve Column Metadata From Results
+`````````````````````````````````````
+
+Find the column metadata returned from a query:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("create temporary table quark (id serial, name text)")
+>>> for name in ('Up', 'Down'):
+... con.run("INSERT INTO quark (name) VALUES (:name)", name=name)
+>>> # Now execute the query
+>>>
+>>> con.run("SELECT * FROM quark")
+[[1, 'Up'], [2, 'Down']]
+>>>
+>>> # and retrieve the metadata
+>>>
+>>> con.columns
+[{'table_oid': ..., 'column_attrnum': 1, 'type_oid': 23, 'type_size': 4, 'type_modifier': -1, 'format': 0, 'name': 'id'}, {'table_oid': ..., 'column_attrnum': 2, 'type_oid': 25, 'type_size': -1, 'type_modifier': -1, 'format': 0, 'name': 'name'}]
+>>>
+>>> # Show just the column names
+>>>
+>>> [c['name'] for c in con.columns]
+['id', 'name']
+>>>
+>>> con.close()
+
+
+Notices And Notifications
+`````````````````````````
+
+PostgreSQL `notices
+<https://www.postgresql.org/docs/current/static/plpgsql-errors-and-messages.html>`_ are
+stored in a deque called ``Connection.notices`` and added using the ``append()``
+method. Similarly there are ``Connection.notifications`` for `notifications
+<https://www.postgresql.org/docs/current/static/sql-notify.html>`_ and
+``Connection.parameter_statuses`` for changes to the server configuration. Here's an
+example:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("LISTEN aliens_landed")
+>>> con.run("NOTIFY aliens_landed")
+>>> # A notification is a tuple containing (backend_pid, channel, payload)
+>>>
+>>> con.notifications[0]
+(..., 'aliens_landed', '')
+>>>
+>>> con.close()
+
+
+LIMIT ALL
+`````````
+
+You might think that the following would work, but in fact it fails:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("SELECT 'silo 1' LIMIT :lim", lim='ALL')
+Traceback (most recent call last):
+pg8000.exceptions.DatabaseError: ...
+>>>
+>>> con.close()
+
+Instead the `docs say <https://www.postgresql.org/docs/current/sql-select.html>`_ that
+you can send ``null`` as an alternative to ``ALL``, which does work:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("SELECT 'silo 1' LIMIT :lim", lim=None)
+[['silo 1']]
+>>>
+>>> con.close()
+
+
+IN and NOT IN
+`````````````
+
+You might think that the following would work, but in fact the server doesn't like it:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("SELECT 'silo 1' WHERE 'a' IN :v", v=['a', 'b'])
+Traceback (most recent call last):
+pg8000.exceptions.DatabaseError: ...
+>>>
+>>> con.close()
+
+instead you can write it using the `unnest
+<https://www.postgresql.org/docs/current/functions-array.html>`_ function:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run(
+... "SELECT 'silo 1' WHERE 'a' IN (SELECT unnest(CAST(:v as varchar[])))",
+... v=['a', 'b'])
+[['silo 1']]
+>>> con.close()
+
+and you can do the same for ``NOT IN``.
+
+
+Many SQL Statements Can't Be Parameterized
+``````````````````````````````````````````
+
+In PostgreSQL parameters can only be used for `data values, not identifiers
+<https://www.postgresql.org/docs/current/xfunc-sql.html#XFUNC-SQL-FUNCTION-ARGUMENTS>`_.
+Sometimes this might not work as expected, for example the following fails:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> channel = 'top_secret'
+>>>
+>>> con.run("LISTEN :channel", channel=channel)
+Traceback (most recent call last):
+pg8000.exceptions.DatabaseError: ...
+>>>
+>>> con.close()
+
+It fails because the PostgreSQL server doesn't allow this statement to have any
+parameters. There are many SQL statements that one might think would have parameters,
+but don't. For these cases the SQL has to be created manually, being careful to use the
+``identifier()`` and ``literal()`` functions to escape the values to avoid `SQL
+injection attacks <https://en.wikipedia.org/wiki/SQL_injection>`_:
+
+>>> from pg8000.native import Connection, identifier, literal
+>>>
+>>> con = Connection("postgres", password="cpsnow")
+>>>
+>>> channel = 'top_secret'
+>>> payload = 'Aliens Landed!'
+>>> con.run(f"LISTEN {identifier(channel)}")
+>>> con.run(f"NOTIFY {identifier(channel)}, {literal(payload)}")
+>>>
+>>> con.notifications[0]
+(..., 'top_secret', 'Aliens Landed!')
+>>>
+>>> con.close()
+
+
+COPY FROM And TO A Stream
+`````````````````````````
+
+The SQL `COPY <https://www.postgresql.org/docs/current/sql-copy.html>`_ statement can be
+used to copy from and to a file or file-like object. Here' an example using the CSV
+format:
+
+>>> import pg8000.native
+>>> from io import StringIO
+>>> import csv
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> # Create a CSV file in memory
+>>>
+>>> stream_in = StringIO()
+>>> csv_writer = csv.writer(stream_in)
+>>> csv_writer.writerow([1, "electron"])
+12
+>>> csv_writer.writerow([2, "muon"])
+8
+>>> csv_writer.writerow([3, "tau"])
+7
+>>> stream_in.seek(0)
+0
+>>>
+>>> # Create a table and then copy the CSV into it
+>>>
+>>> con.run("CREATE TEMPORARY TABLE lepton (id SERIAL, name TEXT)")
+>>> con.run("COPY lepton FROM STDIN WITH (FORMAT CSV)", stream=stream_in)
+>>>
+>>> # COPY from a table to a stream
+>>>
+>>> stream_out = StringIO()
+>>> con.run("COPY lepton TO STDOUT WITH (FORMAT CSV)", stream=stream_out)
+>>> stream_out.seek(0)
+0
+>>> for row in csv.reader(stream_out):
+... print(row)
+['1', 'electron']
+['2', 'muon']
+['3', 'tau']
+>>>
+>>> con.close()
+
+It's also possible to COPY FROM an iterable, which is useful if you're creating rows
+programmatically:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> # Generator function for creating rows
+>>> def row_gen():
+... for i, name in ((1, "electron"), (2, "muon"), (3, "tau")):
+... yield f"{i},{name}\n"
+>>>
+>>> # Create a table and then copy the CSV into it
+>>>
+>>> con.run("CREATE TEMPORARY TABLE lepton (id SERIAL, name TEXT)")
+>>> con.run("COPY lepton FROM STDIN WITH (FORMAT CSV)", stream=row_gen())
+>>>
+>>> # COPY from a table to a stream
+>>>
+>>> stream_out = StringIO()
+>>> con.run("COPY lepton TO STDOUT WITH (FORMAT CSV)", stream=stream_out)
+>>> stream_out.seek(0)
+0
+>>> for row in csv.reader(stream_out):
+... print(row)
+['1', 'electron']
+['2', 'muon']
+['3', 'tau']
+>>>
+>>> con.close()
+
+
+Execute Multiple SQL Statements
+```````````````````````````````
+
+If you want to execute a series of SQL statements (eg. an ``.sql`` file), you can run
+them as expected:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> statements = "SELECT 5; SELECT 'Erich Fromm';"
+>>>
+>>> con.run(statements)
+[[5], ['Erich Fromm']]
+>>>
+>>> con.close()
+
+The only caveat is that when executing multiple statements you can't have any
+parameters.
+
+
+Quoted Identifiers in SQL
+`````````````````````````
+
+Say you had a column called ``My Column``. Since it's case sensitive and contains a
+space, you'd have to `surround it by double quotes
+<https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIER>`_.
+But you can't do:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("select 'hello' as "My Column"")
+Traceback (most recent call last):
+SyntaxError: invalid syntax...
+>>>
+>>> con.close()
+
+since Python uses double quotes to delimit string literals, so one solution is
+to use Python's `triple quotes
+<https://docs.python.org/3/tutorial/introduction.html#strings>`_ to delimit the string
+instead:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run('''SELECT 'hello' AS "My Column"''')
+[['hello']]
+>>>
+>>> con.close()
+
+another solution, that's especially useful if the identifier comes from an untrusted
+source, is to use the ``identifier()`` function, which correctly quotes and escapes the
+identifier as needed:
+
+>>> from pg8000.native import Connection, identifier
+>>>
+>>> con = Connection("postgres", password="cpsnow")
+>>>
+>>> sql = f"SELECT 'hello' as {identifier('My Column')}"
+>>> print(sql)
+SELECT 'hello' as "My Column"
+>>>
+>>> con.run(sql)
+[['hello']]
+>>>
+>>> con.close()
+
+this approach guards against `SQL injection attacks
+<https://en.wikipedia.org/wiki/SQL_injection>`_. One thing to note if you're using
+explicit schemas (eg. ``pg_catalog.pg_language``) is that the schema name and table name
+are both separate identifiers. So to escape them you'd do:
+
+>>> from pg8000.native import Connection, identifier
+>>>
+>>> con = Connection("postgres", password="cpsnow")
+>>>
+>>> query = (
+... f"SELECT lanname FROM {identifier('pg_catalog')}.{identifier('pg_language')} "
+... f"WHERE lanname = 'sql'"
+... )
+>>> print(query)
+SELECT lanname FROM pg_catalog.pg_language WHERE lanname = 'sql'
+>>>
+>>> con.run(query)
+[['sql']]
+>>>
+>>> con.close()
+
+
+Custom adapter from a Python type to a PostgreSQL type
+``````````````````````````````````````````````````````
+
+pg8000 has a mapping from Python types to PostgreSQL types for when it needs to send
+SQL parameters to the server. The default mapping that comes with pg8000 is designed to
+work well in most cases, but you might want to add or replace the default mapping.
+
+A Python ``datetime.timedelta`` object is sent to the server as a PostgreSQL
+``interval`` type, which has the ``oid`` 1186. But let's say we wanted to create our
+own Python class to be sent as an ``interval`` type. Then we'd have to register an
+adapter:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> class MyInterval(str):
+... pass
+>>>
+>>> def my_interval_out(my_interval):
+... return my_interval # Must return a str
+>>>
+>>> con.register_out_adapter(MyInterval, my_interval_out)
+>>> con.run("SELECT CAST(:interval as interval)", interval=MyInterval("2 hours"))
+[[datetime.timedelta(seconds=7200)]]
+>>>
+>>> con.close()
+
+Note that it still came back as a ``datetime.timedelta`` object because we only changed
+the mapping from Python to PostgreSQL. See below for an example of how to change the
+mapping from PostgreSQL to Python.
+
+
+Custom adapter from a PostgreSQL type to a Python type
+``````````````````````````````````````````````````````
+
+pg8000 has a mapping from PostgreSQL types to Python types for when it receives SQL
+results from the server. The default mapping that comes with pg8000 is designed to work
+well in most cases, but you might want to add or replace the default mapping.
+
+If pg8000 receives PostgreSQL ``interval`` type, which has the ``oid`` 1186, it converts
+it into a Python ``datetime.timedelta`` object. But let's say we wanted to create our
+own Python class to be used instead of ``datetime.timedelta``. Then we'd have to
+register an adapter:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> class MyInterval(str):
+... pass
+>>>
+>>> def my_interval_in(my_interval_str): # The parameter is of type str
+... return MyInterval(my_interval)
+>>>
+>>> con.register_in_adapter(1186, my_interval_in)
+>>> con.run("SELECT \'2 years'")
+[['2 years']]
+>>>
+>>> con.close()
+
+Note that registering the 'in' adapter only afects the mapping from the PostgreSQL type
+to the Python type. See above for an example of how to change the mapping from
+PostgreSQL to Python.
+
+
+Could Not Determine Data Type Of Parameter
+``````````````````````````````````````````
+
+Sometimes you'll get the 'could not determine data type of parameter' error message from
+the server:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("SELECT :v IS NULL", v=None)
+Traceback (most recent call last):
+pg8000.exceptions.DatabaseError: {'S': 'ERROR', 'V': 'ERROR', 'C': '42P18', 'M': 'could not determine data type of parameter $1', 'F': 'postgres.c', 'L': '...', 'R': 'exec_parse_message'}
+>>>
+>>> con.close()
+
+One way of solving it is to put a ``CAST`` in the SQL:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("SELECT cast(:v as TIMESTAMP) IS NULL", v=None)
+[[True]]
+>>>
+>>> con.close()
+
+Another way is to override the type that pg8000 sends along with each parameter:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("SELECT :v IS NULL", v=None, types={'v': pg8000.native.TIMESTAMP})
+[[True]]
+>>>
+>>> con.close()
+
+
+Prepared Statements
+```````````````````
+
+`Prepared statements <https://www.postgresql.org/docs/current/sql-prepare.html>`_
+can be useful in improving performance when you have a statement that's executed
+repeatedly. Here's an example:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> # Create the prepared statement
+>>> ps = con.prepare("SELECT cast(:v as varchar)")
+>>>
+>>> # Execute the statement repeatedly
+>>> ps.run(v="speedy")
+[['speedy']]
+>>> ps.run(v="rapid")
+[['rapid']]
+>>> ps.run(v="swift")
+[['swift']]
+>>>
+>>> # Close the prepared statement, releasing resources on the server
+>>> ps.close()
+>>>
+>>> con.close()
+
+
+Use Environment Variables As Connection Defaults
+````````````````````````````````````````````````
+
+You might want to use the current user as the database username for example:
+
+>>> import pg8000.native
+>>> import getpass
+>>>
+>>> # Connect to the database with current user name
+>>> username = getpass.getuser()
+>>> connection = pg8000.native.Connection(username, password="cpsnow")
+>>>
+>>> connection.run("SELECT 'pilau'")
+[['pilau']]
+>>>
+>>> connection.close()
+
+or perhaps you may want to use some of the same `environment variables that libpg uses
+<https://www.postgresql.org/docs/current/libpq-envars.html>`_:
+
+>>> import pg8000.native
+>>> from os import environ
+>>>
+>>> username = environ.get('PGUSER', 'postgres')
+>>> password = environ.get('PGPASSWORD', 'cpsnow')
+>>> host = environ.get('PGHOST', 'localhost')
+>>> port = environ.get('PGPORT', '5432')
+>>> database = environ.get('PGDATABASE')
+>>>
+>>> connection = pg8000.native.Connection(
+... username, password=password, host=host, port=port, database=database)
+>>>
+>>> connection.run("SELECT 'Mr Cairo'")
+[['Mr Cairo']]
+>>>
+>>> connection.close()
+
+It might be asked, why doesn't pg8000 have this behaviour built in? The thinking
+follows the second aphorism of `The Zen of Python
+<https://www.python.org/dev/peps/pep-0020/>`_:
+
+ Explicit is better than implicit.
+
+So we've taken the approach of only being able to set connection parameters using the
+``pg8000.native.Connection()`` constructor.
+
+
+Connect To PostgreSQL Over SSL
+``````````````````````````````
+
+To connect to the server using SSL defaults do::
+
+ import pg8000.native
+ connection = pg8000.native.Connection('postgres', password="cpsnow", ssl_context=True)
+ connection.run("SELECT 'The game is afoot!'")
+
+To connect over SSL with custom settings, set the ``ssl_context`` parameter to an
+|ssl.SSLContext|_ object:
+
+::
+
+ import pg8000.native
+ import ssl
+
+
+ ssl_context = ssl.create_default_context()
+ ssl_context.verify_mode = ssl.CERT_REQUIRED
+ ssl_context.load_verify_locations('root.pem')
+ connection = pg8000.native.Connection(
+ 'postgres', password="cpsnow", ssl_context=ssl_context)
+
+It may be that your PostgreSQL server is behind an SSL proxy server in which case you
+can set a pg8000-specific attribute ``ssl.SSLContext.request_ssl = False`` which tells
+pg8000 to connect using an SSL socket, but not to request SSL from the PostgreSQL
+server:
+
+::
+
+ import pg8000.native
+ import ssl
+
+ ssl_context = ssl.create_default_context()
+ ssl_context.request_ssl = False
+ connection = pg8000.native.Connection(
+ 'postgres', password="cpsnow", ssl_context=ssl_context)
+
+
+Server-Side Cursors
+```````````````````
+
+You can use the SQL commands `DECLARE
+<https://www.postgresql.org/docs/current/sql-declare.html>`_,
+`FETCH <https://www.postgresql.org/docs/current/sql-fetch.html>`_,
+`MOVE <https://www.postgresql.org/docs/current/sql-move.html>`_ and
+`CLOSE <https://www.postgresql.org/docs/current/sql-close.html>`_ to manipulate
+server-side cursors. For example:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection('postgres', password="cpsnow")
+>>> con.run("START TRANSACTION")
+>>> con.run("DECLARE c SCROLL CURSOR FOR SELECT * FROM generate_series(1, 100)")
+>>> con.run("FETCH FORWARD 5 FROM c")
+[[1], [2], [3], [4], [5]]
+>>> con.run("MOVE FORWARD 50 FROM c")
+>>> con.run("FETCH BACKWARD 10 FROM c")
+[[54], [53], [52], [51], [50], [49], [48], [47], [46], [45]]
+>>> con.run("CLOSE c")
+>>> con.run("ROLLBACK")
+>>>
+>>> con.close()
+
+
+BLOBs (Binary Large Objects)
+````````````````````````````
+
+There's a set of `SQL functions
+<https://www.postgresql.org/docs/current/lo-funcs.html>`_ for manipulating BLOBs.
+Here's an example:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection('postgres', password="cpsnow")
+>>>
+>>> # Create a BLOB and get its oid
+>>> data = b'hello'
+>>> res = con.run("SELECT lo_from_bytea(0, :data)", data=data)
+>>> oid = res[0][0]
+>>>
+>>> # Create a table and store the oid of the BLOB
+>>> con.run("CREATE TEMPORARY TABLE image (raster oid)")
+>>>
+>>> con.run("INSERT INTO image (raster) VALUES (:oid)", oid=oid)
+>>> # Retrieve the data using the oid
+>>> con.run("SELECT lo_get(:oid)", oid=oid)
+[[b'hello']]
+>>>
+>>> # Add some data to the end of the BLOB
+>>> more_data = b' all'
+>>> offset = len(data)
+>>> con.run(
+... "SELECT lo_put(:oid, :offset, :data)",
+... oid=oid, offset=offset, data=more_data)
+[['']]
+>>> con.run("SELECT lo_get(:oid)", oid=oid)
+[[b'hello all']]
+>>>
+>>> # Download a part of the data
+>>> con.run("SELECT lo_get(:oid, 6, 3)", oid=oid)
+[[b'all']]
+>>>
+>>> con.close()
+
+
+Replication Protocol
+````````````````````
+
+The PostgreSQL `Replication Protocol
+<https://www.postgresql.org/docs/current/protocol-replication.html>`_ is supported using
+the ``replication`` keyword when creating a connection:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection(
+... 'postgres', password="cpsnow", replication="database")
+>>>
+>>> con.run("IDENTIFY_SYSTEM")
+[['...', 1, '0/...', 'postgres']]
+>>>
+>>> con.close()
+
+
+DB-API 2 Interactive Examples
+-----------------------------
+
+These examples stick to the DB-API 2.0 standard.
+
+
+Basic Example
+`````````````
+
+Import pg8000, connect to the database, create a table, add some rows and then query the
+table:
+
+>>> import pg8000.dbapi
+>>>
+>>> conn = pg8000.dbapi.connect(user="postgres", password="cpsnow")
+>>> cursor = conn.cursor()
+>>> cursor.execute("CREATE TEMPORARY TABLE book (id SERIAL, title TEXT)")
+>>> cursor.execute(
+... "INSERT INTO book (title) VALUES (%s), (%s) RETURNING id, title",
+... ("Ender's Game", "Speaker for the Dead"))
+>>> results = cursor.fetchall()
+>>> for row in results:
+... id, title = row
+... print("id = %s, title = %s" % (id, title))
+id = 1, title = Ender's Game
+id = 2, title = Speaker for the Dead
+>>> conn.commit()
+>>>
+>>> conn.close()
+
+
+Query Using Functions
+`````````````````````
+
+Another query, using some PostgreSQL functions:
+
+>>> import pg8000.dbapi
+>>>
+>>> con = pg8000.dbapi.connect(user="postgres", password="cpsnow")
+>>> cursor = con.cursor()
+>>>
+>>> cursor.execute("SELECT TO_CHAR(TIMESTAMP '2021-10-10', 'YYYY BC')")
+>>> cursor.fetchone()
+['2021 AD']
+>>>
+>>> con.close()
+
+
+Interval Type
+`````````````
+
+A query that returns the PostgreSQL interval type:
+
+>>> import datetime
+>>> import pg8000.dbapi
+>>>
+>>> con = pg8000.dbapi.connect(user="postgres", password="cpsnow")
+>>> cursor = con.cursor()
+>>>
+>>> cursor.execute("SELECT timestamp '2013-12-01 16:06' - %s",
+... (datetime.date(1980, 4, 27),))
+>>> cursor.fetchone()
+[datetime.timedelta(days=12271, seconds=57960)]
+>>>
+>>> con.close()
+
+
+Point Type
+``````````
+
+A round-trip with a `PostgreSQL point
+<https://www.postgresql.org/docs/current/datatype-geometric.html>`_ type:
+
+>>> import pg8000.dbapi
+>>>
+>>> con = pg8000.dbapi.connect(user="postgres", password="cpsnow")
+>>> cursor = con.cursor()
+>>>
+>>> cursor.execute("SELECT cast(%s as point)", ((2.3,1),))
+>>> cursor.fetchone()
+[(2.3, 1.0)]
+>>>
+>>> con.close()
+
+
+Numeric Parameter Style
+```````````````````````
+
+pg8000 supports all the DB-API parameter styles. Here's an example of using the
+'numeric' parameter style:
+
+>>> import pg8000.dbapi
+>>>
+>>> pg8000.dbapi.paramstyle = "numeric"
+>>> con = pg8000.dbapi.connect(user="postgres", password="cpsnow")
+>>> cursor = con.cursor()
+>>>
+>>> cursor.execute("SELECT array_prepend(:1, CAST(:2 AS int[]))", (500, [1, 2, 3, 4],))
+>>> cursor.fetchone()
+[[500, 1, 2, 3, 4]]
+>>> pg8000.dbapi.paramstyle = "format"
+>>>
+>>> con.close()
+
+
+Autocommit
+``````````
+
+Following the DB-API specification, autocommit is off by default. It can be turned on by
+using the autocommit property of the connection:
+
+>>> import pg8000.dbapi
+>>>
+>>> con = pg8000.dbapi.connect(user="postgres", password="cpsnow")
+>>> con.autocommit = True
+>>>
+>>> cur = con.cursor()
+>>> cur.execute("vacuum")
+>>> conn.autocommit = False
+>>> cur.close()
+>>>
+>>> con.close()
+
+
+Client Encoding
+```````````````
+
+When communicating with the server, pg8000 uses the character set that the server asks
+it to use (the client encoding). By default the client encoding is the database's
+character set (chosen when the database is created), but the client encoding can be
+changed in a number of ways (eg. setting ``CLIENT_ENCODING`` in ``postgresql.conf``).
+Another way of changing the client encoding is by using an SQL command. For example:
+
+>>> import pg8000.dbapi
+>>>
+>>> con = pg8000.dbapi.connect(user="postgres", password="cpsnow")
+>>> cur = con.cursor()
+>>> cur.execute("SET CLIENT_ENCODING TO 'UTF8'")
+>>> cur.execute("SHOW CLIENT_ENCODING")
+>>> cur.fetchone()
+['UTF8']
+>>> cur.close()
+>>>
+>>> con.close()
+
+
+JSON
+````
+
+JSON is sent to the server serialized, and returned de-serialized. Here's an example:
+
+>>> import json
+>>> import pg8000.dbapi
+>>>
+>>> con = pg8000.dbapi.connect(user="postgres", password="cpsnow")
+>>> cur = con.cursor()
+>>> val = ['Apollo 11 Cave', True, 26.003]
+>>> cur.execute("SELECT cast(%s as json)", (json.dumps(val),))
+>>> cur.fetchone()
+[['Apollo 11 Cave', True, 26.003]]
+>>> cur.close()
+>>>
+>>> con.close()
+
+JSON queries can be have parameters:
+
+>>> import pg8000.dbapi
+>>>
+>>> with pg8000.dbapi.connect("postgres", password="cpsnow") as con:
+... cur = con.cursor()
+... cur.execute(""" SELECT CAST('{"a":1, "b":2}' AS jsonb) @> %s """, ({"b": 2},))
+... for row in cur.fetchall():
+... print(row)
+[True]
+
+
+Retrieve Column Names From Results
+``````````````````````````````````
+
+Use the columns names retrieved from a query:
+
+>>> import pg8000
+>>> conn = pg8000.dbapi.connect(user="postgres", password="cpsnow")
+>>> c = conn.cursor()
+>>> c.execute("create temporary table quark (id serial, name text)")
+>>> c.executemany("INSERT INTO quark (name) VALUES (%s)", (("Up",), ("Down",)))
+>>> #
+>>> # Now retrieve the results
+>>> #
+>>> c.execute("select * from quark")
+>>> rows = c.fetchall()
+>>> keys = [k[0] for k in c.description]
+>>> results = [dict(zip(keys, row)) for row in rows]
+>>> assert results == [{'id': 1, 'name': 'Up'}, {'id': 2, 'name': 'Down'}]
+>>>
+>>> conn.close()
+
+
+COPY from and to a file
+```````````````````````
+
+The SQL `COPY <https://www.postgresql.org/docs/current/sql-copy.html>`__ statement can
+be used to copy from and to a file or file-like object:
+
+>>> from io import StringIO
+>>> import pg8000.dbapi
+>>>
+>>> con = pg8000.dbapi.connect(user="postgres", password="cpsnow")
+>>> cur = con.cursor()
+>>> #
+>>> # COPY from a stream to a table
+>>> #
+>>> stream_in = StringIO('1\telectron\n2\tmuon\n3\ttau\n')
+>>> cur = con.cursor()
+>>> cur.execute("create temporary table lepton (id serial, name text)")
+>>> cur.execute("COPY lepton FROM stdin", stream=stream_in)
+>>> #
+>>> # Now COPY from a table to a stream
+>>> #
+>>> stream_out = StringIO()
+>>> cur.execute("copy lepton to stdout", stream=stream_out)
+>>> stream_out.getvalue()
+'1\telectron\n2\tmuon\n3\ttau\n'
+>>>
+>>> con.close()
+
+
+Server-Side Cursors
+```````````````````
+
+You can use the SQL commands `DECLARE
+<https://www.postgresql.org/docs/current/sql-declare.html>`_,
+`FETCH <https://www.postgresql.org/docs/current/sql-fetch.html>`_,
+`MOVE <https://www.postgresql.org/docs/current/sql-move.html>`_ and
+`CLOSE <https://www.postgresql.org/docs/current/sql-close.html>`_ to manipulate
+server-side cursors. For example:
+
+>>> import pg8000.dbapi
+>>>
+>>> con = pg8000.dbapi.connect(user="postgres", password="cpsnow")
+>>> cur = con.cursor()
+>>> cur.execute("START TRANSACTION")
+>>> cur.execute(
+... "DECLARE c SCROLL CURSOR FOR SELECT * FROM generate_series(1, 100)")
+>>> cur.execute("FETCH FORWARD 5 FROM c")
+>>> cur.fetchall()
+([1], [2], [3], [4], [5])
+>>> cur.execute("MOVE FORWARD 50 FROM c")
+>>> cur.execute("FETCH BACKWARD 10 FROM c")
+>>> cur.fetchall()
+([54], [53], [52], [51], [50], [49], [48], [47], [46], [45])
+>>> cur.execute("CLOSE c")
+>>> cur.execute("ROLLBACK")
+>>>
+>>> con.close()
+
+
+BLOBs (Binary Large Objects)
+````````````````````````````
+
+There's a set of `SQL functions
+<https://www.postgresql.org/docs/current/lo-funcs.html>`_ for manipulating BLOBs.
+Here's an example:
+
+>>> import pg8000.dbapi
+>>>
+>>> con = pg8000.dbapi.connect(user="postgres", password="cpsnow")
+>>> cur = con.cursor()
+>>>
+>>> # Create a BLOB and get its oid
+>>> data = b'hello'
+>>> cur = con.cursor()
+>>> cur.execute("SELECT lo_from_bytea(0, %s)", [data])
+>>> oid = cur.fetchone()[0]
+>>>
+>>> # Create a table and store the oid of the BLOB
+>>> cur.execute("CREATE TEMPORARY TABLE image (raster oid)")
+>>> cur.execute("INSERT INTO image (raster) VALUES (%s)", [oid])
+>>>
+>>> # Retrieve the data using the oid
+>>> cur.execute("SELECT lo_get(%s)", [oid])
+>>> cur.fetchall()
+([b'hello'],)
+>>>
+>>> # Add some data to the end of the BLOB
+>>> more_data = b' all'
+>>> offset = len(data)
+>>> cur.execute("SELECT lo_put(%s, %s, %s)", [oid, offset, more_data])
+>>> cur.execute("SELECT lo_get(%s)", [oid])
+>>> cur.fetchall()
+([b'hello all'],)
+>>>
+>>> # Download a part of the data
+>>> cur.execute("SELECT lo_get(%s, 6, 3)", [oid])
+>>> cur.fetchall()
+([b'all'],)
+>>>
+>>> con.close()
+
+
+Type Mapping
+------------
+
+The following table shows the default mapping between Python types and PostgreSQL types,
+and vice versa.
+
+If pg8000 doesn't recognize a type that it receives from PostgreSQL, it will return it
+as a ``str`` type. This is how pg8000 handles PostgreSQL ``enum`` and XML types. It's
+possible to change the default mapping using adapters (see the examples).
+
+.. table:: Python to PostgreSQL Type Mapping
+
+ +-----------------------+-----------------+-----------------------------------------+
+ | Python Type | PostgreSQL Type | Notes |
+ +=======================+=================+=========================================+
+ | bool | bool | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | int | int4 | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | str | text | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | float | float8 | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | decimal.Decimal | numeric | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | bytes | bytea | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | datetime.datetime | timestamp | +/-infinity PostgreSQL values are |
+ | (without tzinfo) | without | represented as Python ``str`` values. |
+ | | timezone | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | datetime.datetime | timestamp with | +/-infinity PostgreSQL values are |
+ | (with tzinfo) | timezone | represented as Python ``str`` values. |
+ +-----------------------+-----------------+-----------------------------------------+
+ | datetime.date | date | +/-infinity PostgreSQL values are |
+ | | | represented as Python ``str`` values. |
+ +-----------------------+-----------------+-----------------------------------------+
+ | datetime.time | time without | |
+ | | time zone | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | datetime.timedelta | interval | If an ``interval`` is too big for |
+ | | | ``datetime.timedelta`` then a |
+ | | | ``PGInterval`` is used. |
+ +-----------------------+-----------------+-----------------------------------------+
+ | None | NULL | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | uuid.UUID | uuid | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | ipaddress.IPv4Address | inet | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | ipaddress.IPv6Address | inet | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | ipaddress.IPv4Network | inet | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | ipaddress.IPv6Network | inet | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | int | xid | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | list of int | INT4[] | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | list of float | FLOAT8[] | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | list of bool | BOOL[] | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | list of str | TEXT[] | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | int | int2vector | Only from PostgreSQL to Python |
+ +-----------------------+-----------------+-----------------------------------------+
+ | JSON | json, jsonb | The Python JSON is provided as a Python |
+ | | | serialized string. Results returned as |
+ | | | de-serialized JSON. |
+ +-----------------------+-----------------+-----------------------------------------+
+ | pg8000.Range | \*range | PostgreSQL multirange types are |
+ | | | represented in Python as a list of |
+ | | | range types. |
+ +-----------------------+-----------------+-----------------------------------------+
+ | tuple | composite type | Only from Python to PostgreSQL |
+ +-----------------------+-----------------+-----------------------------------------+
+
+
+
+Theory Of Operation
+-------------------
+
+ A concept is tolerated inside the microkernel only if moving it outside the kernel,
+ i.e., permitting competing implementations, would prevent the implementation of the
+ system's required functionality.
+
+ -- Jochen Liedtke, Liedtke's minimality principle
+
+pg8000 is designed to be used with one thread per connection.
+
+Pg8000 communicates with the database using the `PostgreSQL Frontend/Backend Protocol
+<https://www.postgresql.org/docs/current/protocol.html>`_ (FEBE). If a query has no
+parameters, pg8000 uses the 'simple query protocol'. If a query does have parameters,
+pg8000 uses the 'extended query protocol' with unnamed prepared statements. The steps
+for a query with parameters are:
+
+1. Query comes in.
+
+#. Send a PARSE message to the server to create an unnamed prepared statement.
+
+#. Send a BIND message to run against the unnamed prepared statement, resulting in an
+ unnamed portal on the server.
+
+#. Send an EXECUTE message to read all the results from the portal.
+
+It's also possible to use named prepared statements. In which case the prepared
+statement persists on the server, and represented in pg8000 using a
+``PreparedStatement`` object. This means that the PARSE step gets executed once up
+front, and then only the BIND and EXECUTE steps are repeated subsequently.
+
+There are a lot of PostgreSQL data types, but few primitive data types in Python. By
+default, pg8000 doesn't send PostgreSQL data type information in the PARSE step, in
+which case PostgreSQL assumes the types implied by the SQL statement. In some cases
+PostgreSQL can't work out a parameter type and so an `explicit cast
+<https://www.postgresql.org/docs/current/static/sql-expressions.html#SQL-SYNTAX-TYPE-CASTS>`_
+can be used in the SQL.
+
+In the FEBE protocol, each query parameter can be sent to the server either as binary
+or text according to the format code. In pg8000 the parameters are always sent as text.
+
+Occasionally, the network connection between pg8000 and the server may go down. If
+pg8000 encounters a network problem it'll raise an ``InterfaceError`` with the message
+``network error`` and with the original exception set as the `cause
+<https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement>`_.
+
+
+Native API Docs
+---------------
+
+pg8000.native.Error
+```````````````````
+
+Generic exception that is the base exception of the other error exceptions.
+
+
+pg8000.native.InterfaceError
+````````````````````````````
+
+For errors that originate within pg8000.
+
+
+pg8000.native.DatabaseError
+```````````````````````````
+
+For errors that originate from the server.
+
+pg8000.native.Connection(user, host='localhost', database=None, port=5432, password=None, source_address=None, unix_sock=None, ssl_context=None, timeout=None, tcp_keepalive=True, application_name=None, replication=None, sock=None)
+``````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````
+
+Creates a connection to a PostgreSQL database.
+
+user
+ The username to connect to the PostgreSQL server with. If your server character
+ encoding is not ``ascii`` or ``utf8``, then you need to provide ``user`` as bytes,
+ eg. ``'my_name'.encode('EUC-JP')``.
+
+host
+ The hostname of the PostgreSQL server to connect with. Providing this parameter is
+ necessary for TCP/IP connections. One of either ``host`` or ``unix_sock`` must be
+ provided. The default is ``localhost``.
+
+database
+ The name of the database instance to connect with. If ``None`` then the PostgreSQL
+ server will assume the database name is the same as the username. If your server
+ character encoding is not ``ascii`` or ``utf8``, then you need to provide ``database``
+ as bytes, eg. ``'my_db'.encode('EUC-JP')``.
+
+port
+ The TCP/IP port of the PostgreSQL server instance. This parameter defaults to
+ ``5432``, the registered common port of PostgreSQL TCP/IP servers.
+
+password
+ The user password to connect to the server with. This parameter is optional; if
+ omitted and the database server requests password-based authentication, the connection
+ will fail to open. If this parameter is provided but not
+ requested by the server, no error will occur.
+
+ If your server character encoding is not ``ascii`` or ``utf8``, then you need to
+ provide ``password`` as bytes, eg. ``'my_password'.encode('EUC-JP')``.
+
+source_address
+ The source IP address which initiates the connection to the PostgreSQL server. The
+ default is ``None`` which means that the operating system will choose the source
+ address.
+
+unix_sock
+ The path to the UNIX socket to access the database through, for example,
+ ``'/tmp/.s.PGSQL.5432'``. One of either ``host`` or ``unix_sock`` must be provided.
+
+ssl_context
+ This governs SSL encryption for TCP/IP sockets. It can have three values:
+
+ - ``None``, meaning no SSL (the default)
+
+ - ``True``, means use SSL with an |ssl.SSLContext|_ created using
+ |ssl.create_default_context()|_
+
+ - An instance of |ssl.SSLContext|_ which will be used to create the SSL connection.
+
+ If your PostgreSQL server is behind an SSL proxy, you can set the pg8000-specific
+ attribute ``ssl.SSLContext.request_ssl = False``, which tells pg8000 to use an SSL
+ socket, but not to request SSL from the PostgreSQL server. Note that this means you
+ can't use SCRAM authentication with channel binding.
+
+timeout
+ This is the time in seconds before the connection to the server will time out. The
+ default is ``None`` which means no timeout.
+
+tcp_keepalive
+ If ``True`` then use `TCP keepalive
+ <https://en.wikipedia.org/wiki/Keepalive#TCP_keepalive>`_. The default is ``True``.
+
+application_name
+ Sets the `application_name
+ <https://www.postgresql.org/docs/current/runtime-config-logging.html#GUC-APPLICATION-NAME>`_.
+ If your server character encoding is not ``ascii`` or ``utf8``, then you need to
+ provide values as bytes, eg. ``'my_application_name'.encode('EUC-JP')``. The default
+ is ``None`` which means that the server will set the application name.
+
+replication
+ Used to run in `streaming replication mode
+ <https://www.postgresql.org/docs/current/protocol-replication.html>`_. If your server
+ character encoding is not ``ascii`` or ``utf8``, then you need to provide values as
+ bytes, eg. ``'database'.encode('EUC-JP')``.
+
+sock
+ A socket-like object to use for the connection. For example, ``sock`` could be a plain
+ ``socket.socket``, or it could represent an SSH tunnel or perhaps an
+ ``ssl.SSLSocket`` to an SSL proxy. If an |ssl.SSLContext| is provided, then it will be
+ used to attempt to create an SSL socket from the provided socket.
+
+pg8000.native.Connection.notifications
+``````````````````````````````````````
+
+A deque of server-side `notifications
+<https://www.postgresql.org/docs/current/sql-notify.html>`__ received by this database
+connection (via the ``LISTEN`` / ``NOTIFY`` PostgreSQL commands). Each list item is a
+three-element tuple containing the PostgreSQL backend PID that issued the notify, the
+channel and the payload.
+
+
+pg8000.native.Connection.notices
+````````````````````````````````
+
+A deque of server-side notices received by this database connection.
+
+
+pg8000.native.Connection.parameter_statuses
+```````````````````````````````````````````
+
+A deque of server-side parameter statuses received by this database connection.
+
+
+pg8000.native.Connection.run(sql, stream=None, types=None, \*\*kwargs)
+``````````````````````````````````````````````````````````````````````
+
+Executes an sql statement, and returns the results as a ``list``. For example::
+
+ con.run("SELECT * FROM cities where population > :pop", pop=10000)
+
+sql
+ The SQL statement to execute. Parameter placeholders appear as a ``:`` followed by the
+ parameter name.
+
+stream
+ For use with the PostgreSQL `COPY
+ <http://www.postgresql.org/docs/current/static/sql-copy.html>`__ command. The nature
+ of the parameter depends on whether the SQL command is ``COPY FROM`` or ``COPY TO``.
+
+ ``COPY FROM``
+ The stream parameter must be a readable file-like object or an iterable. If it's an
+ iterable then the items can be ``str`` or binary.
+ ``COPY TO``
+ The stream parameter must be a writable file-like object.
+
+types
+ A dictionary of oids. A key corresponds to a parameter.
+
+kwargs
+ The parameters of the SQL statement.
+
+
+pg8000.native.Connection.row_count
+``````````````````````````````````
+
+This read-only attribute contains the number of rows that the last ``run()`` method
+produced (for query statements like ``SELECT``) or affected (for modification statements
+like ``UPDATE``.
+
+The value is -1 if:
+
+- No ``run()`` method has been performed yet.
+- There was no rowcount associated with the last ``run()``.
+
+
+pg8000.native.Connection.columns
+````````````````````````````````
+
+A list of column metadata. Each item in the list is a dictionary with the following
+keys:
+
+- name
+- table_oid
+- column_attrnum
+- type_oid
+- type_size
+- type_modifier
+- format
+
+
+pg8000.native.Connection.close()
+````````````````````````````````
+
+Closes the database connection.
+
+
+pg8000.native.Connection.register_out_adapter(typ, out_func)
+````````````````````````````````````````````````````````````
+
+Register a type adapter for types going out from pg8000 to the server.
+
+typ
+ The Python class that the adapter is for.
+
+out_func
+ A function that takes the Python object and returns its string representation
+ in the format that the server requires.
+
+
+pg8000.native.Connection.register_in_adapter(oid, in_func)
+``````````````````````````````````````````````````````````
+
+Register a type adapter for types coming in from the server to pg8000.
+
+oid
+ The PostgreSQL type identifier found in the `pg_type system catalog
+ <https://www.postgresql.org/docs/current/catalog-pg-type.html>`_.
+
+in_func
+ A function that takes the PostgreSQL string representation and returns a corresponding
+ Python object.
+
+
+pg8000.native.Connection.prepare(sql)
+`````````````````````````````````````
+
+Returns a ``PreparedStatement`` object which represents a `prepared statement
+<https://www.postgresql.org/docs/current/sql-prepare.html>`_ on the server. It can
+subsequently be repeatedly executed.
+
+sql
+ The SQL statement to prepare. Parameter placeholders appear as a ``:`` followed by the
+ parameter name.
+
+
+pg8000.native.PreparedStatement
+```````````````````````````````
+
+A prepared statement object is returned by the ``pg8000.native.Connection.prepare()``
+method of a connection. It has the following methods:
+
+
+pg8000.native.PreparedStatement.run(\*\*kwargs)
+```````````````````````````````````````````````
+
+Executes the prepared statement, and returns the results as a ``tuple``.
+
+kwargs
+ The parameters of the prepared statement.
+
+
+pg8000.native.PreparedStatement.close()
+```````````````````````````````````````
+
+Closes the prepared statement, releasing the prepared statement held on the server.
+
+
+pg8000.native.identifier(ident)
+```````````````````````````````
+
+Correctly quotes and escapes a string to be used as an `SQL identifier
+<https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS>`_.
+
+ident
+ The ``str`` to be used as an SQL identifier.
+
+
+pg8000.native.literal(value)
+````````````````````````````
+
+Correctly quotes and escapes a value to be used as an `SQL literal
+<https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-CONSTANTS>`_.
+
+value
+ The value to be used as an SQL literal.
+
+
+DB-API 2 Docs
+-------------
+
+
+Properties
+``````````
+
+
+pg8000.dbapi.apilevel
+:::::::::::::::::::::
+
+The DBAPI level supported, currently "2.0".
+
+
+pg8000.dbapi.threadsafety
+:::::::::::::::::::::::::
+
+Integer constant stating the level of thread safety the DBAPI interface supports. For
+pg8000, the threadsafety value is 1, meaning that threads may share the module but not
+connections.
+
+
+pg8000.dbapi.paramstyle
+:::::::::::::::::::::::
+
+String property stating the type of parameter marker formatting expected by
+the interface. This value defaults to "format", in which parameters are
+marked in this format: "WHERE name=%s".
+
+As an extension to the DBAPI specification, this value is not constant; it can be
+changed to any of the following values:
+
+qmark
+ Question mark style, eg. ``WHERE name=?``
+
+numeric
+ Numeric positional style, eg. ``WHERE name=:1``
+
+named
+ Named style, eg. ``WHERE name=:paramname``
+
+format
+ printf format codes, eg. ``WHERE name=%s``
+
+pyformat
+ Python format codes, eg. ``WHERE name=%(paramname)s``
+
+
+pg8000.dbapi.STRING
+:::::::::::::::::::
+
+String type oid.
+
+pg8000.dbapi.BINARY
+:::::::::::::::::::
+
+
+pg8000.dbapi.NUMBER
+:::::::::::::::::::
+
+Numeric type oid.
+
+
+pg8000.dbapi.DATETIME
+:::::::::::::::::::::
+
+Timestamp type oid
+
+
+pg8000.dbapi.ROWID
+::::::::::::::::::
+
+ROWID type oid
+
+
+Functions
+`````````
+
+pg8000.dbapi.connect(user, host='localhost', database=None, port=5432, password=None, source_address=None, unix_sock=None, ssl_context=None, timeout=None, tcp_keepalive=True, application_name=None, replication=None, sock=None)
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+
+Creates a connection to a PostgreSQL database.
+
+user
+ The username to connect to the PostgreSQL server with. If your server character
+ encoding is not ``ascii`` or ``utf8``, then you need to provide ``user`` as bytes,
+ eg. ``'my_name'.encode('EUC-JP')``.
+
+host
+ The hostname of the PostgreSQL server to connect with. Providing this parameter is
+ necessary for TCP/IP connections. One of either ``host`` or ``unix_sock`` must be
+ provided. The default is ``localhost``.
+
+database
+ The name of the database instance to connect with. If ``None`` then the PostgreSQL
+ server will assume the database name is the same as the username. If your server
+ character encoding is not ``ascii`` or ``utf8``, then you need to provide ``database``
+ as bytes, eg. ``'my_db'.encode('EUC-JP')``.
+
+port
+ The TCP/IP port of the PostgreSQL server instance. This parameter defaults to
+ ``5432``, the registered common port of PostgreSQL TCP/IP servers.
+
+password
+ The user password to connect to the server with. This parameter is optional; if
+ omitted and the database server requests password-based authentication, the
+ connection will fail to open. If this parameter is provided but not requested by the
+ server, no error will occur.
+
+ If your server character encoding is not ``ascii`` or ``utf8``, then you need to
+ provide ``password`` as bytes, eg. ``'my_password'.encode('EUC-JP')``.
+
+source_address
+ The source IP address which initiates the connection to the PostgreSQL server. The
+ default is ``None`` which means that the operating system will choose the source
+ address.
+
+unix_sock
+ The path to the UNIX socket to access the database through, for example,
+ ``'/tmp/.s.PGSQL.5432'``. One of either ``host`` or ``unix_sock`` must be provided.
+
+ssl_context
+ This governs SSL encryption for TCP/IP sockets. It can have three values:
+
+ - ``None``, meaning no SSL (the default)
+ - ``True``, means use SSL with an |ssl.SSLContext|_ created using
+ |ssl.create_default_context()|_.
+
+ - An instance of |ssl.SSLContext|_ which will be used to create the SSL connection.
+
+ If your PostgreSQL server is behind an SSL proxy, you can set the pg8000-specific
+ attribute ``ssl.SSLContext.request_ssl = False``, which tells pg8000 to use an SSL
+ socket, but not to request SSL from the PostgreSQL server. Note that this means you
+ can't use SCRAM authentication with channel binding.
+
+timeout
+ This is the time in seconds before the connection to the server will time out. The
+ default is ``None`` which means no timeout.
+
+tcp_keepalive
+ If ``True`` then use `TCP keepalive
+ <https://en.wikipedia.org/wiki/Keepalive#TCP_keepalive>`_. The default is ``True``.
+
+application_name
+ Sets the `application_name
+ <https://www.postgresql.org/docs/current/runtime-config-logging.html#GUC-APPLICATION-NAME>`_. If your server character encoding is not ``ascii`` or ``utf8``, then you need to
+ provide values as bytes, eg. ``'my_application_name'.encode('EUC-JP')``. The default
+ is ``None`` which means that the server will set the application name.
+
+replication
+ Used to run in `streaming replication mode
+ <https://www.postgresql.org/docs/current/protocol-replication.html>`_. If your server
+ character encoding is not ``ascii`` or ``utf8``, then you need to provide values as
+ bytes, eg. ``'database'.encode('EUC-JP')``.
+
+sock
+ A socket-like object to use for the connection. For example, ``sock`` could be a plain
+ ``socket.socket``, or it could represent an SSH tunnel or perhaps an
+ ``ssl.SSLSocket`` to an SSL proxy. If an |ssl.SSLContext| is provided, then it will be
+ used to attempt to create an SSL socket from the provided socket.
+
+
+pg8000.dbapi.Date(year, month, day)
+
+Construct an object holding a date value.
+
+This property is part of the `DBAPI 2.0 specification
+<http://www.python.org/dev/peps/pep-0249/>`_.
+
+Returns: `datetime.date`
+
+
+pg8000.dbapi.Time(hour, minute, second)
+:::::::::::::::::::::::::::::::::::::::
+
+Construct an object holding a time value.
+
+Returns: ``datetime.time``
+
+
+pg8000.dbapi.Timestamp(year, month, day, hour, minute, second)
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+
+Construct an object holding a timestamp value.
+
+Returns: ``datetime.datetime``
+
+
+pg8000.dbapi.DateFromTicks(ticks)
+:::::::::::::::::::::::::::::::::
+
+Construct an object holding a date value from the given ticks value (number of seconds
+since the epoch).
+
+Returns: ``datetime.datetime``
+
+
+pg8000.dbapi.TimeFromTicks(ticks)
+:::::::::::::::::::::::::::::::::
+
+Construct an object holding a time value from the given ticks value (number of seconds
+since the epoch).
+
+Returns: ``datetime.time``
+
+
+pg8000.dbapi.TimestampFromTicks(ticks)
+::::::::::::::::::::::::::::::::::::::
+
+Construct an object holding a timestamp value from the given ticks value (number of
+seconds since the epoch).
+
+Returns: ``datetime.datetime``
+
+
+pg8000.dbapi.Binary(value)
+::::::::::::::::::::::::::
+
+Construct an object holding binary data.
+
+Returns: ``bytes``.
+
+
+Generic Exceptions
+``````````````````
+
+Pg8000 uses the standard DBAPI 2.0 exception tree as "generic" exceptions. Generally,
+more specific exception types are raised; these specific exception types are derived
+from the generic exceptions.
+
+pg8000.dbapi.Warning
+::::::::::::::::::::
+
+Generic exception raised for important database warnings like data truncations. This
+exception is not currently used by pg8000.
+
+
+pg8000.dbapi.Error
+::::::::::::::::::
+
+Generic exception that is the base exception of all other error exceptions.
+
+
+pg8000.dbapi.InterfaceError
+:::::::::::::::::::::::::::
+
+Generic exception raised for errors that are related to the database interface rather
+than the database itself. For example, if the interface attempts to use an SSL
+connection but the server refuses, an InterfaceError will be raised.
+
+
+pg8000.dbapi.DatabaseError
+::::::::::::::::::::::::::
+
+Generic exception raised for errors that are related to the database. This exception is
+currently never raised by pg8000.
+
+
+pg8000.dbapi.DataError
+::::::::::::::::::::::
+
+Generic exception raised for errors that are due to problems with the processed data.
+This exception is not currently raised by pg8000.
+
+
+pg8000.dbapi.OperationalError
+:::::::::::::::::::::::::::::
+
+Generic exception raised for errors that are related to the database's operation and not
+necessarily under the control of the programmer. This exception is currently never
+raised by pg8000.
+
+
+pg8000.dbapi.IntegrityError
+:::::::::::::::::::::::::::
+
+Generic exception raised when the relational integrity of the database is affected. This
+exception is not currently raised by pg8000.
+
+
+pg8000.dbapi.InternalError
+::::::::::::::::::::::::::
+
+Generic exception raised when the database encounters an internal error. This is
+currently only raised when unexpected state occurs in the pg8000 interface itself, and
+is typically the result of a interface bug.
+
+
+pg8000.dbapi.ProgrammingError
+:::::::::::::::::::::::::::::
+
+Generic exception raised for programming errors. For example, this exception is raised
+if more parameter fields are in a query string than there are available parameters.
+
+
+pg8000.dbapi.NotSupportedError
+::::::::::::::::::::::::::::::
+
+Generic exception raised in case a method or database API was used which is not
+supported by the database.
+
+
+Classes
+```````
+
+
+pg8000.dbapi.Connection
+:::::::::::::::::::::::
+
+A connection object is returned by the ``pg8000.connect()`` function. It represents a
+single physical connection to a PostgreSQL database.
+
+
+pg8000.dbapi.Connection.autocommit
+::::::::::::::::::::::::::::::::::
+
+Following the DB-API specification, autocommit is off by default. It can be turned on by
+setting this boolean pg8000-specific autocommit property to ``True``.
+
+
+pg8000.dbapi.Connection.close()
+:::::::::::::::::::::::::::::::
+
+Closes the database connection.
+
+
+pg8000.dbapi.Connection.cursor()
+::::::::::::::::::::::::::::::::
+
+Creates a ``pg8000.dbapi.Cursor`` object bound to this connection.
+
+
+pg8000.dbapi.Connection.rollback()
+::::::::::::::::::::::::::::::::::
+
+Rolls back the current database transaction.
+
+
+pg8000.dbapi.Connection.tpc_begin(xid)
+::::::::::::::::::::::::::::::::::::::
+
+Begins a TPC transaction with the given transaction ID xid. This method should be
+called outside of a transaction (i.e. nothing may have executed since the last
+``commit()`` or ``rollback()``. Furthermore, it is an error to call ``commit()`` or
+``rollback()`` within the TPC transaction. A ``ProgrammingError`` is raised, if the
+application calls ``commit()`` or ``rollback()`` during an active TPC transaction.
+
+
+pg8000.dbapi.Connection.tpc_commit(xid=None)
+::::::::::::::::::::::::::::::::::::::::::::
+
+When called with no arguments, ``tpc_commit()`` commits a TPC transaction previously
+prepared with ``tpc_prepare()``. If ``tpc_commit()`` is called prior to
+``tpc_prepare()``, a single phase commit is performed. A transaction manager may choose
+to do this if only a single resource is participating in the global transaction.
+
+When called with a transaction ID ``xid``, the database commits the given transaction.
+If an invalid transaction ID is provided, a ``ProgrammingError`` will be raised. This
+form should be called outside of a transaction, and is intended for use in recovery.
+
+On return, the TPC transaction is ended.
+
+
+pg8000.dbapi.Connection.tpc_prepare()
+:::::::::::::::::::::::::::::::::::::
+
+Performs the first phase of a transaction started with ``.tpc_begin()``. A
+``ProgrammingError`` is be raised if this method is called outside of a TPC transaction.
+
+After calling ``tpc_prepare()``, no statements can be executed until ``tpc_commit()`` or
+``tpc_rollback()`` have been called.
+
+
+pg8000.dbapi.Connection.tpc_recover()
+:::::::::::::::::::::::::::::::::::::
+
+Returns a list of pending transaction IDs suitable for use with ``tpc_commit(xid)`` or
+``tpc_rollback(xid)``.
+
+
+pg8000.dbapi.Connection.tpc_rollback(xid=None)
+::::::::::::::::::::::::::::::::::::::::::::::
+
+When called with no arguments, ``tpc_rollback()`` rolls back a TPC transaction. It may
+be called before or after ``tpc_prepare()``.
+
+When called with a transaction ID xid, it rolls back the given transaction. If an
+invalid transaction ID is provided, a ``ProgrammingError`` is raised. This form should
+be called outside of a transaction, and is intended for use in recovery.
+
+On return, the TPC transaction is ended.
+
+
+pg8000.dbapi.Connection.xid(format_id, global_transaction_id, branch_qualifier)
+:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+
+Create a Transaction IDs (only global_transaction_id is used in pg) format_id and
+branch_qualifier are not used in postgres global_transaction_id may be any string
+identifier supported by postgres returns a tuple (format_id, global_transaction_id,
+branch_qualifier)
+
+
+pg8000.dbapi.Cursor
+:::::::::::::::::::
+
+A cursor object is returned by the ``pg8000.dbapi.Connection.cursor()`` method of a
+connection. It has the following attributes and methods:
+
+pg8000.dbapi.Cursor.arraysize
+'''''''''''''''''''''''''''''
+
+This read/write attribute specifies the number of rows to fetch at a time with
+``pg8000.dbapi.Cursor.fetchmany()``. It defaults to 1.
+
+
+pg8000.dbapi.Cursor.connection
+''''''''''''''''''''''''''''''
+
+This read-only attribute contains a reference to the connection object (an instance of
+``pg8000.dbapi.Connection``) on which the cursor was created.
+
+
+pg8000.dbapi.Cursor.rowcount
+''''''''''''''''''''''''''''
+
+This read-only attribute contains the number of rows that the last ``execute()`` or
+``executemany()`` method produced (for query statements like ``SELECT``) or affected
+(for modification statements like ``UPDATE``.
+
+The value is -1 if:
+
+- No ``execute()`` or ``executemany()`` method has been performed yet on the cursor.
+
+- There was no rowcount associated with the last ``execute()``.
+
+- At least one of the statements executed as part of an ``executemany()`` had no row
+ count associated with it.
+
+
+pg8000.dbapi.Cursor.description
+'''''''''''''''''''''''''''''''
+
+This read-only attribute is a sequence of 7-item sequences. Each value contains
+information describing one result column. The 7 items returned for each column are
+(name, type_code, display_size, internal_size, precision, scale, null_ok). Only the
+first two values are provided by the current implementation.
+
+
+pg8000.dbapi.Cursor.close()
+'''''''''''''''''''''''''''
+
+Closes the cursor.
+
+
+pg8000.dbapi.Cursor.execute(operation, args=None, stream=None)
+''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+Executes a database operation. Parameters may be provided as a sequence, or as a
+mapping, depending upon the value of ``pg8000.dbapi.paramstyle``. Returns the cursor,
+which may be iterated over.
+
+operation
+ The SQL statement to execute.
+
+args
+ If ``pg8000.dbapi.paramstyle`` is ``qmark``, ``numeric``, or ``format``, this
+ argument should be an array of parameters to bind into the statement. If
+ ``pg8000.dbapi.paramstyle`` is ``named``, the argument should be a ``dict`` mapping of
+ parameters. If ``pg8000.dbapi.paramstyle`` is ``pyformat``, the argument value may be
+ either an array or a mapping.
+
+stream
+ This is a pg8000 extension for use with the PostgreSQL `COPY
+ <http://www.postgresql.org/docs/current/static/sql-copy.html>`__ command. For a
+ ``COPY FROM`` the parameter must be a readable file-like object, and for ``COPY TO``
+ it must be writable.
+
+
+pg8000.dbapi.Cursor.executemany(operation, param_sets)
+''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+Prepare a database operation, and then execute it against all parameter sequences or
+mappings provided.
+
+operation
+ The SQL statement to execute.
+
+parameter_sets
+ A sequence of parameters to execute the statement with. The values in the sequence
+ should be sequences or mappings of parameters, the same as the args argument of the
+ ``pg8000.dbapi.Cursor.execute()`` method.
+
+
+pg8000.dbapi.Cursor.callproc(procname, parameters=None)
+'''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+Call a stored database procedure with the given name and optional parameters.
+
+
+procname
+ The name of the procedure to call.
+
+parameters
+ A list of parameters.
+
+
+pg8000.dbapi.Cursor.fetchall()
+''''''''''''''''''''''''''''''
+
+Fetches all remaining rows of a query result.
+
+Returns: A sequence, each entry of which is a sequence of field values making up a row.
+
+
+pg8000.dbapi.Cursor.fetchmany(size=None)
+''''''''''''''''''''''''''''''''''''''''
+
+Fetches the next set of rows of a query result.
+
+size
+ The number of rows to fetch when called. If not provided, the
+ ``pg8000.dbapi.Cursor.arraysize`` attribute value is used instead.
+
+Returns: A sequence, each entry of which is a sequence of field values making up a row.
+If no more rows are available, an empty sequence will be returned.
+
+
+pg8000.dbapi.Cursor.fetchone()
+''''''''''''''''''''''''''''''
+
+Fetch the next row of a query result set.
+
+Returns: A row as a sequence of field values, or ``None`` if no more rows are available.
+
+
+pg8000.dbapi.Cursor.setinputsizes(\*sizes)
+''''''''''''''''''''''''''''''''''''''''''
+
+Used to set the parameter types of the next query. This is useful if it's difficult for
+pg8000 to work out the types from the parameters themselves (eg. for parameters of type
+None).
+
+sizes
+ Positional parameters that are either the Python type of the parameter to be sent, or
+ the PostgreSQL oid. Common oids are available as constants such as ``pg8000.STRING``,
+ ``pg8000.INTEGER``, ``pg8000.TIME`` etc.
+
+
+pg8000.dbapi.Cursor.setoutputsize(size, column=None)
+''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+Not implemented by pg8000.
+
+
+pg8000.dbapi.Interval
+'''''''''''''''''''''
+
+An Interval represents a measurement of time. In PostgreSQL, an interval is defined in
+the measure of months, days, and microseconds; as such, the pg8000 interval type
+represents the same information.
+
+Note that values of the ``pg8000.dbapi.Interval.microseconds``,
+``pg8000.dbapi.Interval.days``, and ``pg8000.dbapi.Interval.months`` properties are
+independently measured and cannot be converted to each other. A month may be 28, 29, 30,
+or 31 days, and a day may occasionally be lengthened slightly by a leap second.
+
+
+Design Decisions
+----------------
+
+For the ``Range`` type, the constructor follows the `PostgreSQL range constructor functions <https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-CONSTRUCT>`_
+which makes `[closed, open) <https://fhur.me/posts/always-use-closed-open-intervals>`_
+the easiest to express:
+
+>>> from pg8000.types import Range
+>>>
+>>> pg_range = Range(2, 6)
+
+
+Tests
+-----
+
+- Install `tox <http://testrun.org/tox/latest/>`_: ``pip install tox``
+
+- Enable the PostgreSQL hstore extension by running the SQL command:
+ ``create extension hstore;``
+
+- Add a line to ``pg_hba.conf`` for the various authentication options:
+
+::
+
+ host pg8000_md5 all 127.0.0.1/32 md5
+ host pg8000_gss all 127.0.0.1/32 gss
+ host pg8000_password all 127.0.0.1/32 password
+ host pg8000_scram_sha_256 all 127.0.0.1/32 scram-sha-256
+ host all all 127.0.0.1/32 trust
+
+- Set password encryption to ``scram-sha-256`` in ``postgresql.conf``:
+ ``password_encryption = 'scram-sha-256'``
+
+- Set the password for the postgres user: ``ALTER USER postgresql WITH PASSWORD 'pw';``
+
+- Run ``tox`` from the ``pg8000`` directory: ``tox``
+
+This will run the tests against the Python version of the virtual environment, on the
+machine, and the installed PostgreSQL version listening on port 5432, or the ``PGPORT``
+environment variable if set.
+
+Benchmarks are run as part of the test suite at ``tests/test_benchmarks.py``.
+
+
+README.rst
+----------
+
+This file is written in the `reStructuredText
+<https://docutils.sourceforge.io/docs/user/rst/quickref.html>`_ format. To generate an
+HTML page from it, do:
+
+- Activate the virtual environment: ``source venv/bin/activate``
+- Install ``Sphinx``: ``pip install Sphinx``
+- Run ``rst2html.py``: ``rst2html.py README.rst README.html``
+
+
+Doing A Release Of pg8000
+-------------------------
+
+Run ``tox`` to make sure all tests pass, then update the release notes, then do:
+
+::
+
+ git tag -a x.y.z -m "version x.y.z"
+ rm -r dist
+ python -m build
+ twine upload dist/*
+
+
+Release Notes
+-------------
+
+Version 1.30.2, 2023-09-17
+``````````````````````````
+
+- Bug fix where dollar-quoted string constants weren't supported.
+
+
+Version 1.30.1, 2023-07-29
+``````````````````````````
+
+- There was a problem uploading the previous version (1.30.0) to PyPI because the
+ markup of the README.rst was invalid. There's now a step in the automated tests to
+ check for this.
+
+
+Version 1.30.0, 2023-07-27
+``````````````````````````
+
+- Remove support for Python 3.7
+
+- Add a ``sock`` keyword parameter for creating a connection from a pre-configured
+ socket.
+
+
+Version 1.29.8, 2023-06-16
+``````````````````````````
+
+- Ranges don't work with legacy API.
+
+
+Version 1.29.7, 2023-06-16
+``````````````````````````
+
+- Add support for PostgreSQL ``range`` and ``multirange`` types. Previously pg8000
+ would just return them as strings, but now they're returned as ``Range`` and lists of
+ ``Range``.
+
+- The PostgreSQL ``record`` type is now returned as a ``tuple`` of strings, whereas
+ before it was returned as one string.
+
+
+Version 1.29.6, 2023-05-29
+``````````````````````````
+
+- Fixed two bugs with composite types. Nulls should be represented by an empty string,
+ and in an array of composite types, the elements should be surrounded by double
+ quotes.
+
+
+Version 1.29.5, 2023-05-09
+``````````````````````````
+
+- Fixed bug where pg8000 didn't handle the case when the number of bytes received from
+ a socket was fewer than requested. This was being interpreted as a network error, but
+ in fact we just needed to wait until more bytes were available.
+
+- When using the ``PGInterval`` type, if a response from the server contained the period
+ ``millennium``, it wasn't recognised. This was caused by a spelling mistake where we
+ had ``millenium`` rather than ``millennium``.
+
+- Added support for sending PostgreSQL composite types. If a value is sent as a
+ ``tuple``, pg8000 will send it to the server as a ``(`` delimited composite string.
+
+
+Version 1.29.4, 2022-12-14
+``````````````````````````
+
+- Fixed bug in ``pg8000.dbapi`` in the ``setinputsizes()`` method where if a ``size``
+ was a recognized Python type, the method failed.
+
+
+Version 1.29.3, 2022-10-26
+``````````````````````````
+
+- Upgrade the SCRAM library to version 1.4.3. This adds support for the case where the
+ client supports channel binding but the server doesn't.
+
+
+Version 1.29.2, 2022-10-09
+``````````````````````````
+
+- Fixed a bug where in a literal array, items such as ``\n`` and ``\r`` weren't
+ escaped properly before being sent to the server.
+
+- Fixed a bug where if the PostgreSQL server has a half-hour time zone set, values of
+ type ``timestamp with time zone`` failed. This has been fixed by using the ``parse``
+ function of the ``dateutil`` package if the ``datetime`` parser fails.
+
+
+Version 1.29.1, 2022-05-23
+``````````````````````````
+
+- In trying to determine if there's been a failed commit, check for ``ROLLBACK TO
+ SAVEPOINT``.
+
+
+Version 1.29.0, 2022-05-21
+``````````````````````````
+
+- Implement a workaround for the `silent failed commit
+ <https://github.com/tlocke/pg8000/issues/36>`_ bug.
+
+- Previously if an empty string was sent as the query an exception would be raised, but
+ that isn't done now.
+
+
+Version 1.28.3, 2022-05-18
+``````````````````````````
+
+- Put back ``__version__`` attributes that were inadvertently removed.
+
+
+Version 1.28.2, 2022-05-17
+``````````````````````````
+
+- Use a build system that's compliant with PEP517.
+
+
+Version 1.28.1, 2022-05-17
+``````````````````````````
+
+- If when doing a ``COPY FROM`` the ``stream`` parameter is an iterator of ``str``,
+ pg8000 used to silently append a newline to the end. That no longer happens.
+
+
+Version 1.28.0, 2022-05-17
+``````````````````````````
+
+- When using the ``COPY FROM`` SQL statement, allow the ``stream`` parameter to be an
+ iterable.
+
+
+Version 1.27.1, 2022-05-16
+``````````````````````````
+
+- The ``seconds`` attribute of ``PGInterval`` is now always a ``float``, to cope with
+ fractional seconds.
+
+- Updated the ``interval`` parsers for ``iso_8601`` and ``sql_standard`` to take
+ account of fractional seconds.
+
+
+Version 1.27.0, 2022-05-16
+``````````````````````````
+
+- It used to be that by default, if pg8000 received an ``interval`` type from the server
+ and it was too big to fit into a ``datetime.timedelta`` then an exception would be
+ raised. Now if an interval is too big for ``datetime.timedelta`` a ``PGInterval`` is
+ returned.
+
+* pg8000 now supports all the output formats for an ``interval`` (``postgres``,
+ ``postgres_verbose``, ``iso_8601`` and ``sql_standard``).
+
+
+Version 1.26.1, 2022-04-23
+``````````````````````````
+
+- Make sure all tests are run by the GitHub Actions tests on commit.
+- Remove support for Python 3.6
+- Remove support for PostgreSQL 9.6
+
+
+Version 1.26.0, 2022-04-18
+``````````````````````````
+
+- When connecting, raise an ``InterfaceError('network error')`` rather than let the
+ underlying ``struct.error`` float up.
+
+- Make licence text the same as that used by the OSI. Previously the licence wording
+ differed slightly from the BSD 3 Clause licence at
+ https://opensource.org/licenses/BSD-3-Clause. This meant that automated tools didn't
+ pick it up as being Open Source. The changes are believed to not alter the meaning of the license at all.
+
+
+Version 1.25.0, 2022-04-17
+``````````````````````````
+
+- Fix more cases where a ``ResourceWarning`` would be raise because of a socket that had
+ been left open.
+
+- We now have a single ``InterfaceError`` with the message 'network error' for all
+ network errors, with the underlying exception held in the ``cause`` of the exception.
+
+
+Version 1.24.2, 2022-04-15
+``````````````````````````
+
+- To prevent a ``ResourceWarning`` close socket if a connection can't be created.
+
+
+Version 1.24.1, 2022-03-02
+``````````````````````````
+
+- Return pg +/-infinity dates as ``str``. Previously +/-infinity pg values would cause
+ an error when returned, but now we return +/-infinity as strings.
+
+
+Version 1.24.0, 2022-02-06
+``````````````````````````
+
+- Add SQL escape functions identifier() and literal() to the native API. For use when a
+ query can't be parameterised and the SQL string has to be created using untrusted
+ values.
+
+
+Version 1.23.0, 2021-11-13
+``````````````````````````
+
+- If a query has no parameters, then the query will no longer be parsed. Although there
+ are performance benefits for doing this, the main reason is to avoid query rewriting,
+ which can introduce errors.
+
+
+Version 1.22.1, 2021-11-10
+``````````````````````````
+
+- Fix bug in PGInterval type where ``str()`` failed for a millennia value.
+
+
+Version 1.22.0, 2021-10-13
+``````````````````````````
+
+- Rather than specifying the oids in the ``Parse`` step of the Postgres protocol, pg8000
+ now omits them, and so Postgres will use the oids it determines from the query. This
+ makes the pg8000 code simpler and also it should also make the nuances of type
+ matching more straightforward.
diff --git a/contrib/python/pg8000/.dist-info/top_level.txt b/contrib/python/pg8000/.dist-info/top_level.txt
new file mode 100644
index 00000000000..94345bbec5b
--- /dev/null
+++ b/contrib/python/pg8000/.dist-info/top_level.txt
@@ -0,0 +1 @@
+pg8000
diff --git a/contrib/python/pg8000/LICENSE b/contrib/python/pg8000/LICENSE
new file mode 100644
index 00000000000..1c9c560e304
--- /dev/null
+++ b/contrib/python/pg8000/LICENSE
@@ -0,0 +1,29 @@
+BSD 3-Clause License
+
+Copyright Mathieu Fenniak and Contributors to pg8000.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/contrib/python/pg8000/README.rst b/contrib/python/pg8000/README.rst
new file mode 100644
index 00000000000..2d0973d2685
--- /dev/null
+++ b/contrib/python/pg8000/README.rst
@@ -0,0 +1,2431 @@
+======
+pg8000
+======
+
+.. |ssl.SSLContext| replace:: ``ssl.SSLContext``
+.. _ssl.SSLContext: https://docs.python.org/3/library/ssl.html#ssl.SSLContext
+
+.. |ssl.create_default_context()| replace:: ``ssl.create_default_context()``
+.. _ssl.create_default_context(): https://docs.python.org/3/library/ssl.html#ssl.create_default_context
+
+pg8000 is a pure-`Python <https://www.python.org/>`_
+`PostgreSQL <http://www.postgresql.org/>`_ driver that complies with
+`DB-API 2.0 <http://www.python.org/dev/peps/pep-0249/>`_. It is tested on Python
+versions 3.8+, on CPython and PyPy, and PostgreSQL versions 11+. pg8000's name comes
+from the belief that it is probably about the 8000th PostgreSQL interface for Python.
+pg8000 is distributed under the BSD 3-clause license.
+
+All bug reports, feature requests and contributions are welcome at
+`http://github.com/tlocke/pg8000/ <http://github.com/tlocke/pg8000/>`_.
+
+.. image:: https://github.com/tlocke/pg8000/workflows/pg8000/badge.svg
+ :alt: Build Status
+
+.. contents:: Table of Contents
+ :depth: 2
+ :local:
+
+Installation
+------------
+
+To install pg8000 using `pip` type:
+
+`pip install pg8000`
+
+
+Native API Interactive Examples
+-------------------------------
+
+pg8000 comes with two APIs, the native pg8000 API and the DB-API 2.0 standard
+API. These are the examples for the native API, and the DB-API 2.0 examples
+follow in the next section.
+
+
+Basic Example
+`````````````
+
+Import pg8000, connect to the database, create a table, add some rows and then
+query the table:
+
+>>> import pg8000.native
+>>>
+>>> # Connect to the database with user name postgres
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> # Create a temporary table
+>>>
+>>> con.run("CREATE TEMPORARY TABLE book (id SERIAL, title TEXT)")
+>>>
+>>> # Populate the table
+>>>
+>>> for title in ("Ender's Game", "The Magus"):
+... con.run("INSERT INTO book (title) VALUES (:title)", title=title)
+>>>
+>>> # Print all the rows in the table
+>>>
+>>> for row in con.run("SELECT * FROM book"):
+... print(row)
+[1, "Ender's Game"]
+[2, 'The Magus']
+>>>
+>>> con.close()
+
+
+Transactions
+````````````
+
+Here's how to run groups of SQL statements in a
+`transaction <https://www.postgresql.org/docs/current/tutorial-transactions.html>`_:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("START TRANSACTION")
+>>>
+>>> # Create a temporary table
+>>> con.run("CREATE TEMPORARY TABLE book (id SERIAL, title TEXT)")
+>>>
+>>> for title in ("Ender's Game", "The Magus", "Phineas Finn"):
+... con.run("INSERT INTO book (title) VALUES (:title)", title=title)
+>>> con.run("COMMIT")
+>>> for row in con.run("SELECT * FROM book"):
+... print(row)
+[1, "Ender's Game"]
+[2, 'The Magus']
+[3, 'Phineas Finn']
+>>>
+>>> con.close()
+
+rolling back a transaction:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> # Create a temporary table
+>>> con.run("CREATE TEMPORARY TABLE book (id SERIAL, title TEXT)")
+>>>
+>>> for title in ("Ender's Game", "The Magus", "Phineas Finn"):
+... con.run("INSERT INTO book (title) VALUES (:title)", title=title)
+>>>
+>>> con.run("START TRANSACTION")
+>>> con.run("DELETE FROM book WHERE title = :title", title="Phineas Finn")
+>>> con.run("ROLLBACK")
+>>> for row in con.run("SELECT * FROM book"):
+... print(row)
+[1, "Ender's Game"]
+[2, 'The Magus']
+[3, 'Phineas Finn']
+>>>
+>>> con.close()
+
+NB. There is `a longstanding bug <https://github.com/tlocke/pg8000/issues/36>`_
+in the PostgreSQL server whereby if a `COMMIT` is issued against a failed
+transaction, the transaction is silently rolled back, rather than an error being
+returned. pg8000 attempts to detect when this has happened and raise an
+`InterfaceError`.
+
+
+Query Using Functions
+`````````````````````
+
+Another query, using some PostgreSQL functions:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("SELECT TO_CHAR(TIMESTAMP '2021-10-10', 'YYYY BC')")
+[['2021 AD']]
+>>>
+>>> con.close()
+
+
+Interval Type
+`````````````
+
+A query that returns the PostgreSQL interval type:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> import datetime
+>>>
+>>> ts = datetime.date(1980, 4, 27)
+>>> con.run("SELECT timestamp '2013-12-01 16:06' - :ts", ts=ts)
+[[datetime.timedelta(days=12271, seconds=57960)]]
+>>>
+>>> con.close()
+
+
+Point Type
+``````````
+
+A round-trip with a
+`PostgreSQL point <https://www.postgresql.org/docs/current/datatype-geometric.html>`_
+type:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("SELECT CAST(:pt as point)", pt=(2.3,1))
+[[(2.3, 1.0)]]
+>>>
+>>> con.close()
+
+
+Client Encoding
+```````````````
+
+When communicating with the server, pg8000 uses the character set that the server asks
+it to use (the client encoding). By default the client encoding is the database's
+character set (chosen when the database is created), but the client encoding can be
+changed in a number of ways (eg. setting ``CLIENT_ENCODING`` in ``postgresql.conf``).
+Another way of changing the client encoding is by using an SQL command. For example:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("SET CLIENT_ENCODING TO 'UTF8'")
+>>> con.run("SHOW CLIENT_ENCODING")
+[['UTF8']]
+>>>
+>>> con.close()
+
+
+JSON
+````
+
+`JSON <https://www.postgresql.org/docs/current/datatype-json.html>`_ always comes back
+from the server de-serialized. If the JSON you want to send is a ``dict`` then you can
+just do:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> val = {'name': 'Apollo 11 Cave', 'zebra': True, 'age': 26.003}
+>>> con.run("SELECT CAST(:apollo as jsonb)", apollo=val)
+[[{'age': 26.003, 'name': 'Apollo 11 Cave', 'zebra': True}]]
+>>>
+>>> con.close()
+
+JSON can always be sent in serialized form to the server:
+
+>>> import json
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>>
+>>> val = ['Apollo 11 Cave', True, 26.003]
+>>> con.run("SELECT CAST(:apollo as jsonb)", apollo=json.dumps(val))
+[[['Apollo 11 Cave', True, 26.003]]]
+>>>
+>>> con.close()
+
+JSON queries can be have parameters:
+
+>>> import pg8000.native
+>>>
+>>> with pg8000.native.Connection("postgres", password="cpsnow") as con:
+... con.run(""" SELECT CAST('{"a":1, "b":2}' AS jsonb) @> :v """, v={"b": 2})
+[[True]]
+
+
+Retrieve Column Metadata From Results
+`````````````````````````````````````
+
+Find the column metadata returned from a query:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("create temporary table quark (id serial, name text)")
+>>> for name in ('Up', 'Down'):
+... con.run("INSERT INTO quark (name) VALUES (:name)", name=name)
+>>> # Now execute the query
+>>>
+>>> con.run("SELECT * FROM quark")
+[[1, 'Up'], [2, 'Down']]
+>>>
+>>> # and retrieve the metadata
+>>>
+>>> con.columns
+[{'table_oid': ..., 'column_attrnum': 1, 'type_oid': 23, 'type_size': 4, 'type_modifier': -1, 'format': 0, 'name': 'id'}, {'table_oid': ..., 'column_attrnum': 2, 'type_oid': 25, 'type_size': -1, 'type_modifier': -1, 'format': 0, 'name': 'name'}]
+>>>
+>>> # Show just the column names
+>>>
+>>> [c['name'] for c in con.columns]
+['id', 'name']
+>>>
+>>> con.close()
+
+
+Notices And Notifications
+`````````````````````````
+
+PostgreSQL `notices
+<https://www.postgresql.org/docs/current/static/plpgsql-errors-and-messages.html>`_ are
+stored in a deque called ``Connection.notices`` and added using the ``append()``
+method. Similarly there are ``Connection.notifications`` for `notifications
+<https://www.postgresql.org/docs/current/static/sql-notify.html>`_ and
+``Connection.parameter_statuses`` for changes to the server configuration. Here's an
+example:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("LISTEN aliens_landed")
+>>> con.run("NOTIFY aliens_landed")
+>>> # A notification is a tuple containing (backend_pid, channel, payload)
+>>>
+>>> con.notifications[0]
+(..., 'aliens_landed', '')
+>>>
+>>> con.close()
+
+
+LIMIT ALL
+`````````
+
+You might think that the following would work, but in fact it fails:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("SELECT 'silo 1' LIMIT :lim", lim='ALL')
+Traceback (most recent call last):
+pg8000.exceptions.DatabaseError: ...
+>>>
+>>> con.close()
+
+Instead the `docs say <https://www.postgresql.org/docs/current/sql-select.html>`_ that
+you can send ``null`` as an alternative to ``ALL``, which does work:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("SELECT 'silo 1' LIMIT :lim", lim=None)
+[['silo 1']]
+>>>
+>>> con.close()
+
+
+IN and NOT IN
+`````````````
+
+You might think that the following would work, but in fact the server doesn't like it:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("SELECT 'silo 1' WHERE 'a' IN :v", v=['a', 'b'])
+Traceback (most recent call last):
+pg8000.exceptions.DatabaseError: ...
+>>>
+>>> con.close()
+
+instead you can write it using the `unnest
+<https://www.postgresql.org/docs/current/functions-array.html>`_ function:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run(
+... "SELECT 'silo 1' WHERE 'a' IN (SELECT unnest(CAST(:v as varchar[])))",
+... v=['a', 'b'])
+[['silo 1']]
+>>> con.close()
+
+and you can do the same for ``NOT IN``.
+
+
+Many SQL Statements Can't Be Parameterized
+``````````````````````````````````````````
+
+In PostgreSQL parameters can only be used for `data values, not identifiers
+<https://www.postgresql.org/docs/current/xfunc-sql.html#XFUNC-SQL-FUNCTION-ARGUMENTS>`_.
+Sometimes this might not work as expected, for example the following fails:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> channel = 'top_secret'
+>>>
+>>> con.run("LISTEN :channel", channel=channel)
+Traceback (most recent call last):
+pg8000.exceptions.DatabaseError: ...
+>>>
+>>> con.close()
+
+It fails because the PostgreSQL server doesn't allow this statement to have any
+parameters. There are many SQL statements that one might think would have parameters,
+but don't. For these cases the SQL has to be created manually, being careful to use the
+``identifier()`` and ``literal()`` functions to escape the values to avoid `SQL
+injection attacks <https://en.wikipedia.org/wiki/SQL_injection>`_:
+
+>>> from pg8000.native import Connection, identifier, literal
+>>>
+>>> con = Connection("postgres", password="cpsnow")
+>>>
+>>> channel = 'top_secret'
+>>> payload = 'Aliens Landed!'
+>>> con.run(f"LISTEN {identifier(channel)}")
+>>> con.run(f"NOTIFY {identifier(channel)}, {literal(payload)}")
+>>>
+>>> con.notifications[0]
+(..., 'top_secret', 'Aliens Landed!')
+>>>
+>>> con.close()
+
+
+COPY FROM And TO A Stream
+`````````````````````````
+
+The SQL `COPY <https://www.postgresql.org/docs/current/sql-copy.html>`_ statement can be
+used to copy from and to a file or file-like object. Here' an example using the CSV
+format:
+
+>>> import pg8000.native
+>>> from io import StringIO
+>>> import csv
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> # Create a CSV file in memory
+>>>
+>>> stream_in = StringIO()
+>>> csv_writer = csv.writer(stream_in)
+>>> csv_writer.writerow([1, "electron"])
+12
+>>> csv_writer.writerow([2, "muon"])
+8
+>>> csv_writer.writerow([3, "tau"])
+7
+>>> stream_in.seek(0)
+0
+>>>
+>>> # Create a table and then copy the CSV into it
+>>>
+>>> con.run("CREATE TEMPORARY TABLE lepton (id SERIAL, name TEXT)")
+>>> con.run("COPY lepton FROM STDIN WITH (FORMAT CSV)", stream=stream_in)
+>>>
+>>> # COPY from a table to a stream
+>>>
+>>> stream_out = StringIO()
+>>> con.run("COPY lepton TO STDOUT WITH (FORMAT CSV)", stream=stream_out)
+>>> stream_out.seek(0)
+0
+>>> for row in csv.reader(stream_out):
+... print(row)
+['1', 'electron']
+['2', 'muon']
+['3', 'tau']
+>>>
+>>> con.close()
+
+It's also possible to COPY FROM an iterable, which is useful if you're creating rows
+programmatically:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> # Generator function for creating rows
+>>> def row_gen():
+... for i, name in ((1, "electron"), (2, "muon"), (3, "tau")):
+... yield f"{i},{name}\n"
+>>>
+>>> # Create a table and then copy the CSV into it
+>>>
+>>> con.run("CREATE TEMPORARY TABLE lepton (id SERIAL, name TEXT)")
+>>> con.run("COPY lepton FROM STDIN WITH (FORMAT CSV)", stream=row_gen())
+>>>
+>>> # COPY from a table to a stream
+>>>
+>>> stream_out = StringIO()
+>>> con.run("COPY lepton TO STDOUT WITH (FORMAT CSV)", stream=stream_out)
+>>> stream_out.seek(0)
+0
+>>> for row in csv.reader(stream_out):
+... print(row)
+['1', 'electron']
+['2', 'muon']
+['3', 'tau']
+>>>
+>>> con.close()
+
+
+Execute Multiple SQL Statements
+```````````````````````````````
+
+If you want to execute a series of SQL statements (eg. an ``.sql`` file), you can run
+them as expected:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> statements = "SELECT 5; SELECT 'Erich Fromm';"
+>>>
+>>> con.run(statements)
+[[5], ['Erich Fromm']]
+>>>
+>>> con.close()
+
+The only caveat is that when executing multiple statements you can't have any
+parameters.
+
+
+Quoted Identifiers in SQL
+`````````````````````````
+
+Say you had a column called ``My Column``. Since it's case sensitive and contains a
+space, you'd have to `surround it by double quotes
+<https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIER>`_.
+But you can't do:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("select 'hello' as "My Column"")
+Traceback (most recent call last):
+SyntaxError: invalid syntax...
+>>>
+>>> con.close()
+
+since Python uses double quotes to delimit string literals, so one solution is
+to use Python's `triple quotes
+<https://docs.python.org/3/tutorial/introduction.html#strings>`_ to delimit the string
+instead:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run('''SELECT 'hello' AS "My Column"''')
+[['hello']]
+>>>
+>>> con.close()
+
+another solution, that's especially useful if the identifier comes from an untrusted
+source, is to use the ``identifier()`` function, which correctly quotes and escapes the
+identifier as needed:
+
+>>> from pg8000.native import Connection, identifier
+>>>
+>>> con = Connection("postgres", password="cpsnow")
+>>>
+>>> sql = f"SELECT 'hello' as {identifier('My Column')}"
+>>> print(sql)
+SELECT 'hello' as "My Column"
+>>>
+>>> con.run(sql)
+[['hello']]
+>>>
+>>> con.close()
+
+this approach guards against `SQL injection attacks
+<https://en.wikipedia.org/wiki/SQL_injection>`_. One thing to note if you're using
+explicit schemas (eg. ``pg_catalog.pg_language``) is that the schema name and table name
+are both separate identifiers. So to escape them you'd do:
+
+>>> from pg8000.native import Connection, identifier
+>>>
+>>> con = Connection("postgres", password="cpsnow")
+>>>
+>>> query = (
+... f"SELECT lanname FROM {identifier('pg_catalog')}.{identifier('pg_language')} "
+... f"WHERE lanname = 'sql'"
+... )
+>>> print(query)
+SELECT lanname FROM pg_catalog.pg_language WHERE lanname = 'sql'
+>>>
+>>> con.run(query)
+[['sql']]
+>>>
+>>> con.close()
+
+
+Custom adapter from a Python type to a PostgreSQL type
+``````````````````````````````````````````````````````
+
+pg8000 has a mapping from Python types to PostgreSQL types for when it needs to send
+SQL parameters to the server. The default mapping that comes with pg8000 is designed to
+work well in most cases, but you might want to add or replace the default mapping.
+
+A Python ``datetime.timedelta`` object is sent to the server as a PostgreSQL
+``interval`` type, which has the ``oid`` 1186. But let's say we wanted to create our
+own Python class to be sent as an ``interval`` type. Then we'd have to register an
+adapter:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> class MyInterval(str):
+... pass
+>>>
+>>> def my_interval_out(my_interval):
+... return my_interval # Must return a str
+>>>
+>>> con.register_out_adapter(MyInterval, my_interval_out)
+>>> con.run("SELECT CAST(:interval as interval)", interval=MyInterval("2 hours"))
+[[datetime.timedelta(seconds=7200)]]
+>>>
+>>> con.close()
+
+Note that it still came back as a ``datetime.timedelta`` object because we only changed
+the mapping from Python to PostgreSQL. See below for an example of how to change the
+mapping from PostgreSQL to Python.
+
+
+Custom adapter from a PostgreSQL type to a Python type
+``````````````````````````````````````````````````````
+
+pg8000 has a mapping from PostgreSQL types to Python types for when it receives SQL
+results from the server. The default mapping that comes with pg8000 is designed to work
+well in most cases, but you might want to add or replace the default mapping.
+
+If pg8000 receives PostgreSQL ``interval`` type, which has the ``oid`` 1186, it converts
+it into a Python ``datetime.timedelta`` object. But let's say we wanted to create our
+own Python class to be used instead of ``datetime.timedelta``. Then we'd have to
+register an adapter:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> class MyInterval(str):
+... pass
+>>>
+>>> def my_interval_in(my_interval_str): # The parameter is of type str
+... return MyInterval(my_interval)
+>>>
+>>> con.register_in_adapter(1186, my_interval_in)
+>>> con.run("SELECT \'2 years'")
+[['2 years']]
+>>>
+>>> con.close()
+
+Note that registering the 'in' adapter only afects the mapping from the PostgreSQL type
+to the Python type. See above for an example of how to change the mapping from
+PostgreSQL to Python.
+
+
+Could Not Determine Data Type Of Parameter
+``````````````````````````````````````````
+
+Sometimes you'll get the 'could not determine data type of parameter' error message from
+the server:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("SELECT :v IS NULL", v=None)
+Traceback (most recent call last):
+pg8000.exceptions.DatabaseError: {'S': 'ERROR', 'V': 'ERROR', 'C': '42P18', 'M': 'could not determine data type of parameter $1', 'F': 'postgres.c', 'L': '...', 'R': 'exec_parse_message'}
+>>>
+>>> con.close()
+
+One way of solving it is to put a ``CAST`` in the SQL:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("SELECT cast(:v as TIMESTAMP) IS NULL", v=None)
+[[True]]
+>>>
+>>> con.close()
+
+Another way is to override the type that pg8000 sends along with each parameter:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> con.run("SELECT :v IS NULL", v=None, types={'v': pg8000.native.TIMESTAMP})
+[[True]]
+>>>
+>>> con.close()
+
+
+Prepared Statements
+```````````````````
+
+`Prepared statements <https://www.postgresql.org/docs/current/sql-prepare.html>`_
+can be useful in improving performance when you have a statement that's executed
+repeatedly. Here's an example:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection("postgres", password="cpsnow")
+>>>
+>>> # Create the prepared statement
+>>> ps = con.prepare("SELECT cast(:v as varchar)")
+>>>
+>>> # Execute the statement repeatedly
+>>> ps.run(v="speedy")
+[['speedy']]
+>>> ps.run(v="rapid")
+[['rapid']]
+>>> ps.run(v="swift")
+[['swift']]
+>>>
+>>> # Close the prepared statement, releasing resources on the server
+>>> ps.close()
+>>>
+>>> con.close()
+
+
+Use Environment Variables As Connection Defaults
+````````````````````````````````````````````````
+
+You might want to use the current user as the database username for example:
+
+>>> import pg8000.native
+>>> import getpass
+>>>
+>>> # Connect to the database with current user name
+>>> username = getpass.getuser()
+>>> connection = pg8000.native.Connection(username, password="cpsnow")
+>>>
+>>> connection.run("SELECT 'pilau'")
+[['pilau']]
+>>>
+>>> connection.close()
+
+or perhaps you may want to use some of the same `environment variables that libpg uses
+<https://www.postgresql.org/docs/current/libpq-envars.html>`_:
+
+>>> import pg8000.native
+>>> from os import environ
+>>>
+>>> username = environ.get('PGUSER', 'postgres')
+>>> password = environ.get('PGPASSWORD', 'cpsnow')
+>>> host = environ.get('PGHOST', 'localhost')
+>>> port = environ.get('PGPORT', '5432')
+>>> database = environ.get('PGDATABASE')
+>>>
+>>> connection = pg8000.native.Connection(
+... username, password=password, host=host, port=port, database=database)
+>>>
+>>> connection.run("SELECT 'Mr Cairo'")
+[['Mr Cairo']]
+>>>
+>>> connection.close()
+
+It might be asked, why doesn't pg8000 have this behaviour built in? The thinking
+follows the second aphorism of `The Zen of Python
+<https://www.python.org/dev/peps/pep-0020/>`_:
+
+ Explicit is better than implicit.
+
+So we've taken the approach of only being able to set connection parameters using the
+``pg8000.native.Connection()`` constructor.
+
+
+Connect To PostgreSQL Over SSL
+``````````````````````````````
+
+To connect to the server using SSL defaults do::
+
+ import pg8000.native
+ connection = pg8000.native.Connection('postgres', password="cpsnow", ssl_context=True)
+ connection.run("SELECT 'The game is afoot!'")
+
+To connect over SSL with custom settings, set the ``ssl_context`` parameter to an
+|ssl.SSLContext|_ object:
+
+::
+
+ import pg8000.native
+ import ssl
+
+
+ ssl_context = ssl.create_default_context()
+ ssl_context.verify_mode = ssl.CERT_REQUIRED
+ ssl_context.load_verify_locations('root.pem')
+ connection = pg8000.native.Connection(
+ 'postgres', password="cpsnow", ssl_context=ssl_context)
+
+It may be that your PostgreSQL server is behind an SSL proxy server in which case you
+can set a pg8000-specific attribute ``ssl.SSLContext.request_ssl = False`` which tells
+pg8000 to connect using an SSL socket, but not to request SSL from the PostgreSQL
+server:
+
+::
+
+ import pg8000.native
+ import ssl
+
+ ssl_context = ssl.create_default_context()
+ ssl_context.request_ssl = False
+ connection = pg8000.native.Connection(
+ 'postgres', password="cpsnow", ssl_context=ssl_context)
+
+
+Server-Side Cursors
+```````````````````
+
+You can use the SQL commands `DECLARE
+<https://www.postgresql.org/docs/current/sql-declare.html>`_,
+`FETCH <https://www.postgresql.org/docs/current/sql-fetch.html>`_,
+`MOVE <https://www.postgresql.org/docs/current/sql-move.html>`_ and
+`CLOSE <https://www.postgresql.org/docs/current/sql-close.html>`_ to manipulate
+server-side cursors. For example:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection('postgres', password="cpsnow")
+>>> con.run("START TRANSACTION")
+>>> con.run("DECLARE c SCROLL CURSOR FOR SELECT * FROM generate_series(1, 100)")
+>>> con.run("FETCH FORWARD 5 FROM c")
+[[1], [2], [3], [4], [5]]
+>>> con.run("MOVE FORWARD 50 FROM c")
+>>> con.run("FETCH BACKWARD 10 FROM c")
+[[54], [53], [52], [51], [50], [49], [48], [47], [46], [45]]
+>>> con.run("CLOSE c")
+>>> con.run("ROLLBACK")
+>>>
+>>> con.close()
+
+
+BLOBs (Binary Large Objects)
+````````````````````````````
+
+There's a set of `SQL functions
+<https://www.postgresql.org/docs/current/lo-funcs.html>`_ for manipulating BLOBs.
+Here's an example:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection('postgres', password="cpsnow")
+>>>
+>>> # Create a BLOB and get its oid
+>>> data = b'hello'
+>>> res = con.run("SELECT lo_from_bytea(0, :data)", data=data)
+>>> oid = res[0][0]
+>>>
+>>> # Create a table and store the oid of the BLOB
+>>> con.run("CREATE TEMPORARY TABLE image (raster oid)")
+>>>
+>>> con.run("INSERT INTO image (raster) VALUES (:oid)", oid=oid)
+>>> # Retrieve the data using the oid
+>>> con.run("SELECT lo_get(:oid)", oid=oid)
+[[b'hello']]
+>>>
+>>> # Add some data to the end of the BLOB
+>>> more_data = b' all'
+>>> offset = len(data)
+>>> con.run(
+... "SELECT lo_put(:oid, :offset, :data)",
+... oid=oid, offset=offset, data=more_data)
+[['']]
+>>> con.run("SELECT lo_get(:oid)", oid=oid)
+[[b'hello all']]
+>>>
+>>> # Download a part of the data
+>>> con.run("SELECT lo_get(:oid, 6, 3)", oid=oid)
+[[b'all']]
+>>>
+>>> con.close()
+
+
+Replication Protocol
+````````````````````
+
+The PostgreSQL `Replication Protocol
+<https://www.postgresql.org/docs/current/protocol-replication.html>`_ is supported using
+the ``replication`` keyword when creating a connection:
+
+>>> import pg8000.native
+>>>
+>>> con = pg8000.native.Connection(
+... 'postgres', password="cpsnow", replication="database")
+>>>
+>>> con.run("IDENTIFY_SYSTEM")
+[['...', 1, '0/...', 'postgres']]
+>>>
+>>> con.close()
+
+
+DB-API 2 Interactive Examples
+-----------------------------
+
+These examples stick to the DB-API 2.0 standard.
+
+
+Basic Example
+`````````````
+
+Import pg8000, connect to the database, create a table, add some rows and then query the
+table:
+
+>>> import pg8000.dbapi
+>>>
+>>> conn = pg8000.dbapi.connect(user="postgres", password="cpsnow")
+>>> cursor = conn.cursor()
+>>> cursor.execute("CREATE TEMPORARY TABLE book (id SERIAL, title TEXT)")
+>>> cursor.execute(
+... "INSERT INTO book (title) VALUES (%s), (%s) RETURNING id, title",
+... ("Ender's Game", "Speaker for the Dead"))
+>>> results = cursor.fetchall()
+>>> for row in results:
+... id, title = row
+... print("id = %s, title = %s" % (id, title))
+id = 1, title = Ender's Game
+id = 2, title = Speaker for the Dead
+>>> conn.commit()
+>>>
+>>> conn.close()
+
+
+Query Using Functions
+`````````````````````
+
+Another query, using some PostgreSQL functions:
+
+>>> import pg8000.dbapi
+>>>
+>>> con = pg8000.dbapi.connect(user="postgres", password="cpsnow")
+>>> cursor = con.cursor()
+>>>
+>>> cursor.execute("SELECT TO_CHAR(TIMESTAMP '2021-10-10', 'YYYY BC')")
+>>> cursor.fetchone()
+['2021 AD']
+>>>
+>>> con.close()
+
+
+Interval Type
+`````````````
+
+A query that returns the PostgreSQL interval type:
+
+>>> import datetime
+>>> import pg8000.dbapi
+>>>
+>>> con = pg8000.dbapi.connect(user="postgres", password="cpsnow")
+>>> cursor = con.cursor()
+>>>
+>>> cursor.execute("SELECT timestamp '2013-12-01 16:06' - %s",
+... (datetime.date(1980, 4, 27),))
+>>> cursor.fetchone()
+[datetime.timedelta(days=12271, seconds=57960)]
+>>>
+>>> con.close()
+
+
+Point Type
+``````````
+
+A round-trip with a `PostgreSQL point
+<https://www.postgresql.org/docs/current/datatype-geometric.html>`_ type:
+
+>>> import pg8000.dbapi
+>>>
+>>> con = pg8000.dbapi.connect(user="postgres", password="cpsnow")
+>>> cursor = con.cursor()
+>>>
+>>> cursor.execute("SELECT cast(%s as point)", ((2.3,1),))
+>>> cursor.fetchone()
+[(2.3, 1.0)]
+>>>
+>>> con.close()
+
+
+Numeric Parameter Style
+```````````````````````
+
+pg8000 supports all the DB-API parameter styles. Here's an example of using the
+'numeric' parameter style:
+
+>>> import pg8000.dbapi
+>>>
+>>> pg8000.dbapi.paramstyle = "numeric"
+>>> con = pg8000.dbapi.connect(user="postgres", password="cpsnow")
+>>> cursor = con.cursor()
+>>>
+>>> cursor.execute("SELECT array_prepend(:1, CAST(:2 AS int[]))", (500, [1, 2, 3, 4],))
+>>> cursor.fetchone()
+[[500, 1, 2, 3, 4]]
+>>> pg8000.dbapi.paramstyle = "format"
+>>>
+>>> con.close()
+
+
+Autocommit
+``````````
+
+Following the DB-API specification, autocommit is off by default. It can be turned on by
+using the autocommit property of the connection:
+
+>>> import pg8000.dbapi
+>>>
+>>> con = pg8000.dbapi.connect(user="postgres", password="cpsnow")
+>>> con.autocommit = True
+>>>
+>>> cur = con.cursor()
+>>> cur.execute("vacuum")
+>>> conn.autocommit = False
+>>> cur.close()
+>>>
+>>> con.close()
+
+
+Client Encoding
+```````````````
+
+When communicating with the server, pg8000 uses the character set that the server asks
+it to use (the client encoding). By default the client encoding is the database's
+character set (chosen when the database is created), but the client encoding can be
+changed in a number of ways (eg. setting ``CLIENT_ENCODING`` in ``postgresql.conf``).
+Another way of changing the client encoding is by using an SQL command. For example:
+
+>>> import pg8000.dbapi
+>>>
+>>> con = pg8000.dbapi.connect(user="postgres", password="cpsnow")
+>>> cur = con.cursor()
+>>> cur.execute("SET CLIENT_ENCODING TO 'UTF8'")
+>>> cur.execute("SHOW CLIENT_ENCODING")
+>>> cur.fetchone()
+['UTF8']
+>>> cur.close()
+>>>
+>>> con.close()
+
+
+JSON
+````
+
+JSON is sent to the server serialized, and returned de-serialized. Here's an example:
+
+>>> import json
+>>> import pg8000.dbapi
+>>>
+>>> con = pg8000.dbapi.connect(user="postgres", password="cpsnow")
+>>> cur = con.cursor()
+>>> val = ['Apollo 11 Cave', True, 26.003]
+>>> cur.execute("SELECT cast(%s as json)", (json.dumps(val),))
+>>> cur.fetchone()
+[['Apollo 11 Cave', True, 26.003]]
+>>> cur.close()
+>>>
+>>> con.close()
+
+JSON queries can be have parameters:
+
+>>> import pg8000.dbapi
+>>>
+>>> with pg8000.dbapi.connect("postgres", password="cpsnow") as con:
+... cur = con.cursor()
+... cur.execute(""" SELECT CAST('{"a":1, "b":2}' AS jsonb) @> %s """, ({"b": 2},))
+... for row in cur.fetchall():
+... print(row)
+[True]
+
+
+Retrieve Column Names From Results
+``````````````````````````````````
+
+Use the columns names retrieved from a query:
+
+>>> import pg8000
+>>> conn = pg8000.dbapi.connect(user="postgres", password="cpsnow")
+>>> c = conn.cursor()
+>>> c.execute("create temporary table quark (id serial, name text)")
+>>> c.executemany("INSERT INTO quark (name) VALUES (%s)", (("Up",), ("Down",)))
+>>> #
+>>> # Now retrieve the results
+>>> #
+>>> c.execute("select * from quark")
+>>> rows = c.fetchall()
+>>> keys = [k[0] for k in c.description]
+>>> results = [dict(zip(keys, row)) for row in rows]
+>>> assert results == [{'id': 1, 'name': 'Up'}, {'id': 2, 'name': 'Down'}]
+>>>
+>>> conn.close()
+
+
+COPY from and to a file
+```````````````````````
+
+The SQL `COPY <https://www.postgresql.org/docs/current/sql-copy.html>`__ statement can
+be used to copy from and to a file or file-like object:
+
+>>> from io import StringIO
+>>> import pg8000.dbapi
+>>>
+>>> con = pg8000.dbapi.connect(user="postgres", password="cpsnow")
+>>> cur = con.cursor()
+>>> #
+>>> # COPY from a stream to a table
+>>> #
+>>> stream_in = StringIO('1\telectron\n2\tmuon\n3\ttau\n')
+>>> cur = con.cursor()
+>>> cur.execute("create temporary table lepton (id serial, name text)")
+>>> cur.execute("COPY lepton FROM stdin", stream=stream_in)
+>>> #
+>>> # Now COPY from a table to a stream
+>>> #
+>>> stream_out = StringIO()
+>>> cur.execute("copy lepton to stdout", stream=stream_out)
+>>> stream_out.getvalue()
+'1\telectron\n2\tmuon\n3\ttau\n'
+>>>
+>>> con.close()
+
+
+Server-Side Cursors
+```````````````````
+
+You can use the SQL commands `DECLARE
+<https://www.postgresql.org/docs/current/sql-declare.html>`_,
+`FETCH <https://www.postgresql.org/docs/current/sql-fetch.html>`_,
+`MOVE <https://www.postgresql.org/docs/current/sql-move.html>`_ and
+`CLOSE <https://www.postgresql.org/docs/current/sql-close.html>`_ to manipulate
+server-side cursors. For example:
+
+>>> import pg8000.dbapi
+>>>
+>>> con = pg8000.dbapi.connect(user="postgres", password="cpsnow")
+>>> cur = con.cursor()
+>>> cur.execute("START TRANSACTION")
+>>> cur.execute(
+... "DECLARE c SCROLL CURSOR FOR SELECT * FROM generate_series(1, 100)")
+>>> cur.execute("FETCH FORWARD 5 FROM c")
+>>> cur.fetchall()
+([1], [2], [3], [4], [5])
+>>> cur.execute("MOVE FORWARD 50 FROM c")
+>>> cur.execute("FETCH BACKWARD 10 FROM c")
+>>> cur.fetchall()
+([54], [53], [52], [51], [50], [49], [48], [47], [46], [45])
+>>> cur.execute("CLOSE c")
+>>> cur.execute("ROLLBACK")
+>>>
+>>> con.close()
+
+
+BLOBs (Binary Large Objects)
+````````````````````````````
+
+There's a set of `SQL functions
+<https://www.postgresql.org/docs/current/lo-funcs.html>`_ for manipulating BLOBs.
+Here's an example:
+
+>>> import pg8000.dbapi
+>>>
+>>> con = pg8000.dbapi.connect(user="postgres", password="cpsnow")
+>>> cur = con.cursor()
+>>>
+>>> # Create a BLOB and get its oid
+>>> data = b'hello'
+>>> cur = con.cursor()
+>>> cur.execute("SELECT lo_from_bytea(0, %s)", [data])
+>>> oid = cur.fetchone()[0]
+>>>
+>>> # Create a table and store the oid of the BLOB
+>>> cur.execute("CREATE TEMPORARY TABLE image (raster oid)")
+>>> cur.execute("INSERT INTO image (raster) VALUES (%s)", [oid])
+>>>
+>>> # Retrieve the data using the oid
+>>> cur.execute("SELECT lo_get(%s)", [oid])
+>>> cur.fetchall()
+([b'hello'],)
+>>>
+>>> # Add some data to the end of the BLOB
+>>> more_data = b' all'
+>>> offset = len(data)
+>>> cur.execute("SELECT lo_put(%s, %s, %s)", [oid, offset, more_data])
+>>> cur.execute("SELECT lo_get(%s)", [oid])
+>>> cur.fetchall()
+([b'hello all'],)
+>>>
+>>> # Download a part of the data
+>>> cur.execute("SELECT lo_get(%s, 6, 3)", [oid])
+>>> cur.fetchall()
+([b'all'],)
+>>>
+>>> con.close()
+
+
+Type Mapping
+------------
+
+The following table shows the default mapping between Python types and PostgreSQL types,
+and vice versa.
+
+If pg8000 doesn't recognize a type that it receives from PostgreSQL, it will return it
+as a ``str`` type. This is how pg8000 handles PostgreSQL ``enum`` and XML types. It's
+possible to change the default mapping using adapters (see the examples).
+
+.. table:: Python to PostgreSQL Type Mapping
+
+ +-----------------------+-----------------+-----------------------------------------+
+ | Python Type | PostgreSQL Type | Notes |
+ +=======================+=================+=========================================+
+ | bool | bool | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | int | int4 | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | str | text | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | float | float8 | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | decimal.Decimal | numeric | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | bytes | bytea | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | datetime.datetime | timestamp | +/-infinity PostgreSQL values are |
+ | (without tzinfo) | without | represented as Python ``str`` values. |
+ | | timezone | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | datetime.datetime | timestamp with | +/-infinity PostgreSQL values are |
+ | (with tzinfo) | timezone | represented as Python ``str`` values. |
+ +-----------------------+-----------------+-----------------------------------------+
+ | datetime.date | date | +/-infinity PostgreSQL values are |
+ | | | represented as Python ``str`` values. |
+ +-----------------------+-----------------+-----------------------------------------+
+ | datetime.time | time without | |
+ | | time zone | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | datetime.timedelta | interval | If an ``interval`` is too big for |
+ | | | ``datetime.timedelta`` then a |
+ | | | ``PGInterval`` is used. |
+ +-----------------------+-----------------+-----------------------------------------+
+ | None | NULL | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | uuid.UUID | uuid | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | ipaddress.IPv4Address | inet | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | ipaddress.IPv6Address | inet | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | ipaddress.IPv4Network | inet | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | ipaddress.IPv6Network | inet | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | int | xid | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | list of int | INT4[] | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | list of float | FLOAT8[] | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | list of bool | BOOL[] | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | list of str | TEXT[] | |
+ +-----------------------+-----------------+-----------------------------------------+
+ | int | int2vector | Only from PostgreSQL to Python |
+ +-----------------------+-----------------+-----------------------------------------+
+ | JSON | json, jsonb | The Python JSON is provided as a Python |
+ | | | serialized string. Results returned as |
+ | | | de-serialized JSON. |
+ +-----------------------+-----------------+-----------------------------------------+
+ | pg8000.Range | \*range | PostgreSQL multirange types are |
+ | | | represented in Python as a list of |
+ | | | range types. |
+ +-----------------------+-----------------+-----------------------------------------+
+ | tuple | composite type | Only from Python to PostgreSQL |
+ +-----------------------+-----------------+-----------------------------------------+
+
+
+
+Theory Of Operation
+-------------------
+
+ A concept is tolerated inside the microkernel only if moving it outside the kernel,
+ i.e., permitting competing implementations, would prevent the implementation of the
+ system's required functionality.
+
+ -- Jochen Liedtke, Liedtke's minimality principle
+
+pg8000 is designed to be used with one thread per connection.
+
+Pg8000 communicates with the database using the `PostgreSQL Frontend/Backend Protocol
+<https://www.postgresql.org/docs/current/protocol.html>`_ (FEBE). If a query has no
+parameters, pg8000 uses the 'simple query protocol'. If a query does have parameters,
+pg8000 uses the 'extended query protocol' with unnamed prepared statements. The steps
+for a query with parameters are:
+
+1. Query comes in.
+
+#. Send a PARSE message to the server to create an unnamed prepared statement.
+
+#. Send a BIND message to run against the unnamed prepared statement, resulting in an
+ unnamed portal on the server.
+
+#. Send an EXECUTE message to read all the results from the portal.
+
+It's also possible to use named prepared statements. In which case the prepared
+statement persists on the server, and represented in pg8000 using a
+``PreparedStatement`` object. This means that the PARSE step gets executed once up
+front, and then only the BIND and EXECUTE steps are repeated subsequently.
+
+There are a lot of PostgreSQL data types, but few primitive data types in Python. By
+default, pg8000 doesn't send PostgreSQL data type information in the PARSE step, in
+which case PostgreSQL assumes the types implied by the SQL statement. In some cases
+PostgreSQL can't work out a parameter type and so an `explicit cast
+<https://www.postgresql.org/docs/current/static/sql-expressions.html#SQL-SYNTAX-TYPE-CASTS>`_
+can be used in the SQL.
+
+In the FEBE protocol, each query parameter can be sent to the server either as binary
+or text according to the format code. In pg8000 the parameters are always sent as text.
+
+Occasionally, the network connection between pg8000 and the server may go down. If
+pg8000 encounters a network problem it'll raise an ``InterfaceError`` with the message
+``network error`` and with the original exception set as the `cause
+<https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement>`_.
+
+
+Native API Docs
+---------------
+
+pg8000.native.Error
+```````````````````
+
+Generic exception that is the base exception of the other error exceptions.
+
+
+pg8000.native.InterfaceError
+````````````````````````````
+
+For errors that originate within pg8000.
+
+
+pg8000.native.DatabaseError
+```````````````````````````
+
+For errors that originate from the server.
+
+pg8000.native.Connection(user, host='localhost', database=None, port=5432, password=None, source_address=None, unix_sock=None, ssl_context=None, timeout=None, tcp_keepalive=True, application_name=None, replication=None, sock=None)
+``````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````
+
+Creates a connection to a PostgreSQL database.
+
+user
+ The username to connect to the PostgreSQL server with. If your server character
+ encoding is not ``ascii`` or ``utf8``, then you need to provide ``user`` as bytes,
+ eg. ``'my_name'.encode('EUC-JP')``.
+
+host
+ The hostname of the PostgreSQL server to connect with. Providing this parameter is
+ necessary for TCP/IP connections. One of either ``host`` or ``unix_sock`` must be
+ provided. The default is ``localhost``.
+
+database
+ The name of the database instance to connect with. If ``None`` then the PostgreSQL
+ server will assume the database name is the same as the username. If your server
+ character encoding is not ``ascii`` or ``utf8``, then you need to provide ``database``
+ as bytes, eg. ``'my_db'.encode('EUC-JP')``.
+
+port
+ The TCP/IP port of the PostgreSQL server instance. This parameter defaults to
+ ``5432``, the registered common port of PostgreSQL TCP/IP servers.
+
+password
+ The user password to connect to the server with. This parameter is optional; if
+ omitted and the database server requests password-based authentication, the connection
+ will fail to open. If this parameter is provided but not
+ requested by the server, no error will occur.
+
+ If your server character encoding is not ``ascii`` or ``utf8``, then you need to
+ provide ``password`` as bytes, eg. ``'my_password'.encode('EUC-JP')``.
+
+source_address
+ The source IP address which initiates the connection to the PostgreSQL server. The
+ default is ``None`` which means that the operating system will choose the source
+ address.
+
+unix_sock
+ The path to the UNIX socket to access the database through, for example,
+ ``'/tmp/.s.PGSQL.5432'``. One of either ``host`` or ``unix_sock`` must be provided.
+
+ssl_context
+ This governs SSL encryption for TCP/IP sockets. It can have three values:
+
+ - ``None``, meaning no SSL (the default)
+
+ - ``True``, means use SSL with an |ssl.SSLContext|_ created using
+ |ssl.create_default_context()|_
+
+ - An instance of |ssl.SSLContext|_ which will be used to create the SSL connection.
+
+ If your PostgreSQL server is behind an SSL proxy, you can set the pg8000-specific
+ attribute ``ssl.SSLContext.request_ssl = False``, which tells pg8000 to use an SSL
+ socket, but not to request SSL from the PostgreSQL server. Note that this means you
+ can't use SCRAM authentication with channel binding.
+
+timeout
+ This is the time in seconds before the connection to the server will time out. The
+ default is ``None`` which means no timeout.
+
+tcp_keepalive
+ If ``True`` then use `TCP keepalive
+ <https://en.wikipedia.org/wiki/Keepalive#TCP_keepalive>`_. The default is ``True``.
+
+application_name
+ Sets the `application_name
+ <https://www.postgresql.org/docs/current/runtime-config-logging.html#GUC-APPLICATION-NAME>`_.
+ If your server character encoding is not ``ascii`` or ``utf8``, then you need to
+ provide values as bytes, eg. ``'my_application_name'.encode('EUC-JP')``. The default
+ is ``None`` which means that the server will set the application name.
+
+replication
+ Used to run in `streaming replication mode
+ <https://www.postgresql.org/docs/current/protocol-replication.html>`_. If your server
+ character encoding is not ``ascii`` or ``utf8``, then you need to provide values as
+ bytes, eg. ``'database'.encode('EUC-JP')``.
+
+sock
+ A socket-like object to use for the connection. For example, ``sock`` could be a plain
+ ``socket.socket``, or it could represent an SSH tunnel or perhaps an
+ ``ssl.SSLSocket`` to an SSL proxy. If an |ssl.SSLContext| is provided, then it will be
+ used to attempt to create an SSL socket from the provided socket.
+
+pg8000.native.Connection.notifications
+``````````````````````````````````````
+
+A deque of server-side `notifications
+<https://www.postgresql.org/docs/current/sql-notify.html>`__ received by this database
+connection (via the ``LISTEN`` / ``NOTIFY`` PostgreSQL commands). Each list item is a
+three-element tuple containing the PostgreSQL backend PID that issued the notify, the
+channel and the payload.
+
+
+pg8000.native.Connection.notices
+````````````````````````````````
+
+A deque of server-side notices received by this database connection.
+
+
+pg8000.native.Connection.parameter_statuses
+```````````````````````````````````````````
+
+A deque of server-side parameter statuses received by this database connection.
+
+
+pg8000.native.Connection.run(sql, stream=None, types=None, \*\*kwargs)
+``````````````````````````````````````````````````````````````````````
+
+Executes an sql statement, and returns the results as a ``list``. For example::
+
+ con.run("SELECT * FROM cities where population > :pop", pop=10000)
+
+sql
+ The SQL statement to execute. Parameter placeholders appear as a ``:`` followed by the
+ parameter name.
+
+stream
+ For use with the PostgreSQL `COPY
+ <http://www.postgresql.org/docs/current/static/sql-copy.html>`__ command. The nature
+ of the parameter depends on whether the SQL command is ``COPY FROM`` or ``COPY TO``.
+
+ ``COPY FROM``
+ The stream parameter must be a readable file-like object or an iterable. If it's an
+ iterable then the items can be ``str`` or binary.
+ ``COPY TO``
+ The stream parameter must be a writable file-like object.
+
+types
+ A dictionary of oids. A key corresponds to a parameter.
+
+kwargs
+ The parameters of the SQL statement.
+
+
+pg8000.native.Connection.row_count
+``````````````````````````````````
+
+This read-only attribute contains the number of rows that the last ``run()`` method
+produced (for query statements like ``SELECT``) or affected (for modification statements
+like ``UPDATE``.
+
+The value is -1 if:
+
+- No ``run()`` method has been performed yet.
+- There was no rowcount associated with the last ``run()``.
+
+
+pg8000.native.Connection.columns
+````````````````````````````````
+
+A list of column metadata. Each item in the list is a dictionary with the following
+keys:
+
+- name
+- table_oid
+- column_attrnum
+- type_oid
+- type_size
+- type_modifier
+- format
+
+
+pg8000.native.Connection.close()
+````````````````````````````````
+
+Closes the database connection.
+
+
+pg8000.native.Connection.register_out_adapter(typ, out_func)
+````````````````````````````````````````````````````````````
+
+Register a type adapter for types going out from pg8000 to the server.
+
+typ
+ The Python class that the adapter is for.
+
+out_func
+ A function that takes the Python object and returns its string representation
+ in the format that the server requires.
+
+
+pg8000.native.Connection.register_in_adapter(oid, in_func)
+``````````````````````````````````````````````````````````
+
+Register a type adapter for types coming in from the server to pg8000.
+
+oid
+ The PostgreSQL type identifier found in the `pg_type system catalog
+ <https://www.postgresql.org/docs/current/catalog-pg-type.html>`_.
+
+in_func
+ A function that takes the PostgreSQL string representation and returns a corresponding
+ Python object.
+
+
+pg8000.native.Connection.prepare(sql)
+`````````````````````````````````````
+
+Returns a ``PreparedStatement`` object which represents a `prepared statement
+<https://www.postgresql.org/docs/current/sql-prepare.html>`_ on the server. It can
+subsequently be repeatedly executed.
+
+sql
+ The SQL statement to prepare. Parameter placeholders appear as a ``:`` followed by the
+ parameter name.
+
+
+pg8000.native.PreparedStatement
+```````````````````````````````
+
+A prepared statement object is returned by the ``pg8000.native.Connection.prepare()``
+method of a connection. It has the following methods:
+
+
+pg8000.native.PreparedStatement.run(\*\*kwargs)
+```````````````````````````````````````````````
+
+Executes the prepared statement, and returns the results as a ``tuple``.
+
+kwargs
+ The parameters of the prepared statement.
+
+
+pg8000.native.PreparedStatement.close()
+```````````````````````````````````````
+
+Closes the prepared statement, releasing the prepared statement held on the server.
+
+
+pg8000.native.identifier(ident)
+```````````````````````````````
+
+Correctly quotes and escapes a string to be used as an `SQL identifier
+<https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS>`_.
+
+ident
+ The ``str`` to be used as an SQL identifier.
+
+
+pg8000.native.literal(value)
+````````````````````````````
+
+Correctly quotes and escapes a value to be used as an `SQL literal
+<https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-CONSTANTS>`_.
+
+value
+ The value to be used as an SQL literal.
+
+
+DB-API 2 Docs
+-------------
+
+
+Properties
+``````````
+
+
+pg8000.dbapi.apilevel
+:::::::::::::::::::::
+
+The DBAPI level supported, currently "2.0".
+
+
+pg8000.dbapi.threadsafety
+:::::::::::::::::::::::::
+
+Integer constant stating the level of thread safety the DBAPI interface supports. For
+pg8000, the threadsafety value is 1, meaning that threads may share the module but not
+connections.
+
+
+pg8000.dbapi.paramstyle
+:::::::::::::::::::::::
+
+String property stating the type of parameter marker formatting expected by
+the interface. This value defaults to "format", in which parameters are
+marked in this format: "WHERE name=%s".
+
+As an extension to the DBAPI specification, this value is not constant; it can be
+changed to any of the following values:
+
+qmark
+ Question mark style, eg. ``WHERE name=?``
+
+numeric
+ Numeric positional style, eg. ``WHERE name=:1``
+
+named
+ Named style, eg. ``WHERE name=:paramname``
+
+format
+ printf format codes, eg. ``WHERE name=%s``
+
+pyformat
+ Python format codes, eg. ``WHERE name=%(paramname)s``
+
+
+pg8000.dbapi.STRING
+:::::::::::::::::::
+
+String type oid.
+
+pg8000.dbapi.BINARY
+:::::::::::::::::::
+
+
+pg8000.dbapi.NUMBER
+:::::::::::::::::::
+
+Numeric type oid.
+
+
+pg8000.dbapi.DATETIME
+:::::::::::::::::::::
+
+Timestamp type oid
+
+
+pg8000.dbapi.ROWID
+::::::::::::::::::
+
+ROWID type oid
+
+
+Functions
+`````````
+
+pg8000.dbapi.connect(user, host='localhost', database=None, port=5432, password=None, source_address=None, unix_sock=None, ssl_context=None, timeout=None, tcp_keepalive=True, application_name=None, replication=None, sock=None)
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+
+Creates a connection to a PostgreSQL database.
+
+user
+ The username to connect to the PostgreSQL server with. If your server character
+ encoding is not ``ascii`` or ``utf8``, then you need to provide ``user`` as bytes,
+ eg. ``'my_name'.encode('EUC-JP')``.
+
+host
+ The hostname of the PostgreSQL server to connect with. Providing this parameter is
+ necessary for TCP/IP connections. One of either ``host`` or ``unix_sock`` must be
+ provided. The default is ``localhost``.
+
+database
+ The name of the database instance to connect with. If ``None`` then the PostgreSQL
+ server will assume the database name is the same as the username. If your server
+ character encoding is not ``ascii`` or ``utf8``, then you need to provide ``database``
+ as bytes, eg. ``'my_db'.encode('EUC-JP')``.
+
+port
+ The TCP/IP port of the PostgreSQL server instance. This parameter defaults to
+ ``5432``, the registered common port of PostgreSQL TCP/IP servers.
+
+password
+ The user password to connect to the server with. This parameter is optional; if
+ omitted and the database server requests password-based authentication, the
+ connection will fail to open. If this parameter is provided but not requested by the
+ server, no error will occur.
+
+ If your server character encoding is not ``ascii`` or ``utf8``, then you need to
+ provide ``password`` as bytes, eg. ``'my_password'.encode('EUC-JP')``.
+
+source_address
+ The source IP address which initiates the connection to the PostgreSQL server. The
+ default is ``None`` which means that the operating system will choose the source
+ address.
+
+unix_sock
+ The path to the UNIX socket to access the database through, for example,
+ ``'/tmp/.s.PGSQL.5432'``. One of either ``host`` or ``unix_sock`` must be provided.
+
+ssl_context
+ This governs SSL encryption for TCP/IP sockets. It can have three values:
+
+ - ``None``, meaning no SSL (the default)
+ - ``True``, means use SSL with an |ssl.SSLContext|_ created using
+ |ssl.create_default_context()|_.
+
+ - An instance of |ssl.SSLContext|_ which will be used to create the SSL connection.
+
+ If your PostgreSQL server is behind an SSL proxy, you can set the pg8000-specific
+ attribute ``ssl.SSLContext.request_ssl = False``, which tells pg8000 to use an SSL
+ socket, but not to request SSL from the PostgreSQL server. Note that this means you
+ can't use SCRAM authentication with channel binding.
+
+timeout
+ This is the time in seconds before the connection to the server will time out. The
+ default is ``None`` which means no timeout.
+
+tcp_keepalive
+ If ``True`` then use `TCP keepalive
+ <https://en.wikipedia.org/wiki/Keepalive#TCP_keepalive>`_. The default is ``True``.
+
+application_name
+ Sets the `application_name
+ <https://www.postgresql.org/docs/current/runtime-config-logging.html#GUC-APPLICATION-NAME>`_. If your server character encoding is not ``ascii`` or ``utf8``, then you need to
+ provide values as bytes, eg. ``'my_application_name'.encode('EUC-JP')``. The default
+ is ``None`` which means that the server will set the application name.
+
+replication
+ Used to run in `streaming replication mode
+ <https://www.postgresql.org/docs/current/protocol-replication.html>`_. If your server
+ character encoding is not ``ascii`` or ``utf8``, then you need to provide values as
+ bytes, eg. ``'database'.encode('EUC-JP')``.
+
+sock
+ A socket-like object to use for the connection. For example, ``sock`` could be a plain
+ ``socket.socket``, or it could represent an SSH tunnel or perhaps an
+ ``ssl.SSLSocket`` to an SSL proxy. If an |ssl.SSLContext| is provided, then it will be
+ used to attempt to create an SSL socket from the provided socket.
+
+
+pg8000.dbapi.Date(year, month, day)
+
+Construct an object holding a date value.
+
+This property is part of the `DBAPI 2.0 specification
+<http://www.python.org/dev/peps/pep-0249/>`_.
+
+Returns: `datetime.date`
+
+
+pg8000.dbapi.Time(hour, minute, second)
+:::::::::::::::::::::::::::::::::::::::
+
+Construct an object holding a time value.
+
+Returns: ``datetime.time``
+
+
+pg8000.dbapi.Timestamp(year, month, day, hour, minute, second)
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+
+Construct an object holding a timestamp value.
+
+Returns: ``datetime.datetime``
+
+
+pg8000.dbapi.DateFromTicks(ticks)
+:::::::::::::::::::::::::::::::::
+
+Construct an object holding a date value from the given ticks value (number of seconds
+since the epoch).
+
+Returns: ``datetime.datetime``
+
+
+pg8000.dbapi.TimeFromTicks(ticks)
+:::::::::::::::::::::::::::::::::
+
+Construct an object holding a time value from the given ticks value (number of seconds
+since the epoch).
+
+Returns: ``datetime.time``
+
+
+pg8000.dbapi.TimestampFromTicks(ticks)
+::::::::::::::::::::::::::::::::::::::
+
+Construct an object holding a timestamp value from the given ticks value (number of
+seconds since the epoch).
+
+Returns: ``datetime.datetime``
+
+
+pg8000.dbapi.Binary(value)
+::::::::::::::::::::::::::
+
+Construct an object holding binary data.
+
+Returns: ``bytes``.
+
+
+Generic Exceptions
+``````````````````
+
+Pg8000 uses the standard DBAPI 2.0 exception tree as "generic" exceptions. Generally,
+more specific exception types are raised; these specific exception types are derived
+from the generic exceptions.
+
+pg8000.dbapi.Warning
+::::::::::::::::::::
+
+Generic exception raised for important database warnings like data truncations. This
+exception is not currently used by pg8000.
+
+
+pg8000.dbapi.Error
+::::::::::::::::::
+
+Generic exception that is the base exception of all other error exceptions.
+
+
+pg8000.dbapi.InterfaceError
+:::::::::::::::::::::::::::
+
+Generic exception raised for errors that are related to the database interface rather
+than the database itself. For example, if the interface attempts to use an SSL
+connection but the server refuses, an InterfaceError will be raised.
+
+
+pg8000.dbapi.DatabaseError
+::::::::::::::::::::::::::
+
+Generic exception raised for errors that are related to the database. This exception is
+currently never raised by pg8000.
+
+
+pg8000.dbapi.DataError
+::::::::::::::::::::::
+
+Generic exception raised for errors that are due to problems with the processed data.
+This exception is not currently raised by pg8000.
+
+
+pg8000.dbapi.OperationalError
+:::::::::::::::::::::::::::::
+
+Generic exception raised for errors that are related to the database's operation and not
+necessarily under the control of the programmer. This exception is currently never
+raised by pg8000.
+
+
+pg8000.dbapi.IntegrityError
+:::::::::::::::::::::::::::
+
+Generic exception raised when the relational integrity of the database is affected. This
+exception is not currently raised by pg8000.
+
+
+pg8000.dbapi.InternalError
+::::::::::::::::::::::::::
+
+Generic exception raised when the database encounters an internal error. This is
+currently only raised when unexpected state occurs in the pg8000 interface itself, and
+is typically the result of a interface bug.
+
+
+pg8000.dbapi.ProgrammingError
+:::::::::::::::::::::::::::::
+
+Generic exception raised for programming errors. For example, this exception is raised
+if more parameter fields are in a query string than there are available parameters.
+
+
+pg8000.dbapi.NotSupportedError
+::::::::::::::::::::::::::::::
+
+Generic exception raised in case a method or database API was used which is not
+supported by the database.
+
+
+Classes
+```````
+
+
+pg8000.dbapi.Connection
+:::::::::::::::::::::::
+
+A connection object is returned by the ``pg8000.connect()`` function. It represents a
+single physical connection to a PostgreSQL database.
+
+
+pg8000.dbapi.Connection.autocommit
+::::::::::::::::::::::::::::::::::
+
+Following the DB-API specification, autocommit is off by default. It can be turned on by
+setting this boolean pg8000-specific autocommit property to ``True``.
+
+
+pg8000.dbapi.Connection.close()
+:::::::::::::::::::::::::::::::
+
+Closes the database connection.
+
+
+pg8000.dbapi.Connection.cursor()
+::::::::::::::::::::::::::::::::
+
+Creates a ``pg8000.dbapi.Cursor`` object bound to this connection.
+
+
+pg8000.dbapi.Connection.rollback()
+::::::::::::::::::::::::::::::::::
+
+Rolls back the current database transaction.
+
+
+pg8000.dbapi.Connection.tpc_begin(xid)
+::::::::::::::::::::::::::::::::::::::
+
+Begins a TPC transaction with the given transaction ID xid. This method should be
+called outside of a transaction (i.e. nothing may have executed since the last
+``commit()`` or ``rollback()``. Furthermore, it is an error to call ``commit()`` or
+``rollback()`` within the TPC transaction. A ``ProgrammingError`` is raised, if the
+application calls ``commit()`` or ``rollback()`` during an active TPC transaction.
+
+
+pg8000.dbapi.Connection.tpc_commit(xid=None)
+::::::::::::::::::::::::::::::::::::::::::::
+
+When called with no arguments, ``tpc_commit()`` commits a TPC transaction previously
+prepared with ``tpc_prepare()``. If ``tpc_commit()`` is called prior to
+``tpc_prepare()``, a single phase commit is performed. A transaction manager may choose
+to do this if only a single resource is participating in the global transaction.
+
+When called with a transaction ID ``xid``, the database commits the given transaction.
+If an invalid transaction ID is provided, a ``ProgrammingError`` will be raised. This
+form should be called outside of a transaction, and is intended for use in recovery.
+
+On return, the TPC transaction is ended.
+
+
+pg8000.dbapi.Connection.tpc_prepare()
+:::::::::::::::::::::::::::::::::::::
+
+Performs the first phase of a transaction started with ``.tpc_begin()``. A
+``ProgrammingError`` is be raised if this method is called outside of a TPC transaction.
+
+After calling ``tpc_prepare()``, no statements can be executed until ``tpc_commit()`` or
+``tpc_rollback()`` have been called.
+
+
+pg8000.dbapi.Connection.tpc_recover()
+:::::::::::::::::::::::::::::::::::::
+
+Returns a list of pending transaction IDs suitable for use with ``tpc_commit(xid)`` or
+``tpc_rollback(xid)``.
+
+
+pg8000.dbapi.Connection.tpc_rollback(xid=None)
+::::::::::::::::::::::::::::::::::::::::::::::
+
+When called with no arguments, ``tpc_rollback()`` rolls back a TPC transaction. It may
+be called before or after ``tpc_prepare()``.
+
+When called with a transaction ID xid, it rolls back the given transaction. If an
+invalid transaction ID is provided, a ``ProgrammingError`` is raised. This form should
+be called outside of a transaction, and is intended for use in recovery.
+
+On return, the TPC transaction is ended.
+
+
+pg8000.dbapi.Connection.xid(format_id, global_transaction_id, branch_qualifier)
+:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+
+Create a Transaction IDs (only global_transaction_id is used in pg) format_id and
+branch_qualifier are not used in postgres global_transaction_id may be any string
+identifier supported by postgres returns a tuple (format_id, global_transaction_id,
+branch_qualifier)
+
+
+pg8000.dbapi.Cursor
+:::::::::::::::::::
+
+A cursor object is returned by the ``pg8000.dbapi.Connection.cursor()`` method of a
+connection. It has the following attributes and methods:
+
+pg8000.dbapi.Cursor.arraysize
+'''''''''''''''''''''''''''''
+
+This read/write attribute specifies the number of rows to fetch at a time with
+``pg8000.dbapi.Cursor.fetchmany()``. It defaults to 1.
+
+
+pg8000.dbapi.Cursor.connection
+''''''''''''''''''''''''''''''
+
+This read-only attribute contains a reference to the connection object (an instance of
+``pg8000.dbapi.Connection``) on which the cursor was created.
+
+
+pg8000.dbapi.Cursor.rowcount
+''''''''''''''''''''''''''''
+
+This read-only attribute contains the number of rows that the last ``execute()`` or
+``executemany()`` method produced (for query statements like ``SELECT``) or affected
+(for modification statements like ``UPDATE``.
+
+The value is -1 if:
+
+- No ``execute()`` or ``executemany()`` method has been performed yet on the cursor.
+
+- There was no rowcount associated with the last ``execute()``.
+
+- At least one of the statements executed as part of an ``executemany()`` had no row
+ count associated with it.
+
+
+pg8000.dbapi.Cursor.description
+'''''''''''''''''''''''''''''''
+
+This read-only attribute is a sequence of 7-item sequences. Each value contains
+information describing one result column. The 7 items returned for each column are
+(name, type_code, display_size, internal_size, precision, scale, null_ok). Only the
+first two values are provided by the current implementation.
+
+
+pg8000.dbapi.Cursor.close()
+'''''''''''''''''''''''''''
+
+Closes the cursor.
+
+
+pg8000.dbapi.Cursor.execute(operation, args=None, stream=None)
+''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+Executes a database operation. Parameters may be provided as a sequence, or as a
+mapping, depending upon the value of ``pg8000.dbapi.paramstyle``. Returns the cursor,
+which may be iterated over.
+
+operation
+ The SQL statement to execute.
+
+args
+ If ``pg8000.dbapi.paramstyle`` is ``qmark``, ``numeric``, or ``format``, this
+ argument should be an array of parameters to bind into the statement. If
+ ``pg8000.dbapi.paramstyle`` is ``named``, the argument should be a ``dict`` mapping of
+ parameters. If ``pg8000.dbapi.paramstyle`` is ``pyformat``, the argument value may be
+ either an array or a mapping.
+
+stream
+ This is a pg8000 extension for use with the PostgreSQL `COPY
+ <http://www.postgresql.org/docs/current/static/sql-copy.html>`__ command. For a
+ ``COPY FROM`` the parameter must be a readable file-like object, and for ``COPY TO``
+ it must be writable.
+
+
+pg8000.dbapi.Cursor.executemany(operation, param_sets)
+''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+Prepare a database operation, and then execute it against all parameter sequences or
+mappings provided.
+
+operation
+ The SQL statement to execute.
+
+parameter_sets
+ A sequence of parameters to execute the statement with. The values in the sequence
+ should be sequences or mappings of parameters, the same as the args argument of the
+ ``pg8000.dbapi.Cursor.execute()`` method.
+
+
+pg8000.dbapi.Cursor.callproc(procname, parameters=None)
+'''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+Call a stored database procedure with the given name and optional parameters.
+
+
+procname
+ The name of the procedure to call.
+
+parameters
+ A list of parameters.
+
+
+pg8000.dbapi.Cursor.fetchall()
+''''''''''''''''''''''''''''''
+
+Fetches all remaining rows of a query result.
+
+Returns: A sequence, each entry of which is a sequence of field values making up a row.
+
+
+pg8000.dbapi.Cursor.fetchmany(size=None)
+''''''''''''''''''''''''''''''''''''''''
+
+Fetches the next set of rows of a query result.
+
+size
+ The number of rows to fetch when called. If not provided, the
+ ``pg8000.dbapi.Cursor.arraysize`` attribute value is used instead.
+
+Returns: A sequence, each entry of which is a sequence of field values making up a row.
+If no more rows are available, an empty sequence will be returned.
+
+
+pg8000.dbapi.Cursor.fetchone()
+''''''''''''''''''''''''''''''
+
+Fetch the next row of a query result set.
+
+Returns: A row as a sequence of field values, or ``None`` if no more rows are available.
+
+
+pg8000.dbapi.Cursor.setinputsizes(\*sizes)
+''''''''''''''''''''''''''''''''''''''''''
+
+Used to set the parameter types of the next query. This is useful if it's difficult for
+pg8000 to work out the types from the parameters themselves (eg. for parameters of type
+None).
+
+sizes
+ Positional parameters that are either the Python type of the parameter to be sent, or
+ the PostgreSQL oid. Common oids are available as constants such as ``pg8000.STRING``,
+ ``pg8000.INTEGER``, ``pg8000.TIME`` etc.
+
+
+pg8000.dbapi.Cursor.setoutputsize(size, column=None)
+''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+Not implemented by pg8000.
+
+
+pg8000.dbapi.Interval
+'''''''''''''''''''''
+
+An Interval represents a measurement of time. In PostgreSQL, an interval is defined in
+the measure of months, days, and microseconds; as such, the pg8000 interval type
+represents the same information.
+
+Note that values of the ``pg8000.dbapi.Interval.microseconds``,
+``pg8000.dbapi.Interval.days``, and ``pg8000.dbapi.Interval.months`` properties are
+independently measured and cannot be converted to each other. A month may be 28, 29, 30,
+or 31 days, and a day may occasionally be lengthened slightly by a leap second.
+
+
+Design Decisions
+----------------
+
+For the ``Range`` type, the constructor follows the `PostgreSQL range constructor functions <https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-CONSTRUCT>`_
+which makes `[closed, open) <https://fhur.me/posts/always-use-closed-open-intervals>`_
+the easiest to express:
+
+>>> from pg8000.types import Range
+>>>
+>>> pg_range = Range(2, 6)
+
+
+Tests
+-----
+
+- Install `tox <http://testrun.org/tox/latest/>`_: ``pip install tox``
+
+- Enable the PostgreSQL hstore extension by running the SQL command:
+ ``create extension hstore;``
+
+- Add a line to ``pg_hba.conf`` for the various authentication options:
+
+::
+
+ host pg8000_md5 all 127.0.0.1/32 md5
+ host pg8000_gss all 127.0.0.1/32 gss
+ host pg8000_password all 127.0.0.1/32 password
+ host pg8000_scram_sha_256 all 127.0.0.1/32 scram-sha-256
+ host all all 127.0.0.1/32 trust
+
+- Set password encryption to ``scram-sha-256`` in ``postgresql.conf``:
+ ``password_encryption = 'scram-sha-256'``
+
+- Set the password for the postgres user: ``ALTER USER postgresql WITH PASSWORD 'pw';``
+
+- Run ``tox`` from the ``pg8000`` directory: ``tox``
+
+This will run the tests against the Python version of the virtual environment, on the
+machine, and the installed PostgreSQL version listening on port 5432, or the ``PGPORT``
+environment variable if set.
+
+Benchmarks are run as part of the test suite at ``tests/test_benchmarks.py``.
+
+
+README.rst
+----------
+
+This file is written in the `reStructuredText
+<https://docutils.sourceforge.io/docs/user/rst/quickref.html>`_ format. To generate an
+HTML page from it, do:
+
+- Activate the virtual environment: ``source venv/bin/activate``
+- Install ``Sphinx``: ``pip install Sphinx``
+- Run ``rst2html.py``: ``rst2html.py README.rst README.html``
+
+
+Doing A Release Of pg8000
+-------------------------
+
+Run ``tox`` to make sure all tests pass, then update the release notes, then do:
+
+::
+
+ git tag -a x.y.z -m "version x.y.z"
+ rm -r dist
+ python -m build
+ twine upload dist/*
+
+
+Release Notes
+-------------
+
+Version 1.30.2, 2023-09-17
+``````````````````````````
+
+- Bug fix where dollar-quoted string constants weren't supported.
+
+
+Version 1.30.1, 2023-07-29
+``````````````````````````
+
+- There was a problem uploading the previous version (1.30.0) to PyPI because the
+ markup of the README.rst was invalid. There's now a step in the automated tests to
+ check for this.
+
+
+Version 1.30.0, 2023-07-27
+``````````````````````````
+
+- Remove support for Python 3.7
+
+- Add a ``sock`` keyword parameter for creating a connection from a pre-configured
+ socket.
+
+
+Version 1.29.8, 2023-06-16
+``````````````````````````
+
+- Ranges don't work with legacy API.
+
+
+Version 1.29.7, 2023-06-16
+``````````````````````````
+
+- Add support for PostgreSQL ``range`` and ``multirange`` types. Previously pg8000
+ would just return them as strings, but now they're returned as ``Range`` and lists of
+ ``Range``.
+
+- The PostgreSQL ``record`` type is now returned as a ``tuple`` of strings, whereas
+ before it was returned as one string.
+
+
+Version 1.29.6, 2023-05-29
+``````````````````````````
+
+- Fixed two bugs with composite types. Nulls should be represented by an empty string,
+ and in an array of composite types, the elements should be surrounded by double
+ quotes.
+
+
+Version 1.29.5, 2023-05-09
+``````````````````````````
+
+- Fixed bug where pg8000 didn't handle the case when the number of bytes received from
+ a socket was fewer than requested. This was being interpreted as a network error, but
+ in fact we just needed to wait until more bytes were available.
+
+- When using the ``PGInterval`` type, if a response from the server contained the period
+ ``millennium``, it wasn't recognised. This was caused by a spelling mistake where we
+ had ``millenium`` rather than ``millennium``.
+
+- Added support for sending PostgreSQL composite types. If a value is sent as a
+ ``tuple``, pg8000 will send it to the server as a ``(`` delimited composite string.
+
+
+Version 1.29.4, 2022-12-14
+``````````````````````````
+
+- Fixed bug in ``pg8000.dbapi`` in the ``setinputsizes()`` method where if a ``size``
+ was a recognized Python type, the method failed.
+
+
+Version 1.29.3, 2022-10-26
+``````````````````````````
+
+- Upgrade the SCRAM library to version 1.4.3. This adds support for the case where the
+ client supports channel binding but the server doesn't.
+
+
+Version 1.29.2, 2022-10-09
+``````````````````````````
+
+- Fixed a bug where in a literal array, items such as ``\n`` and ``\r`` weren't
+ escaped properly before being sent to the server.
+
+- Fixed a bug where if the PostgreSQL server has a half-hour time zone set, values of
+ type ``timestamp with time zone`` failed. This has been fixed by using the ``parse``
+ function of the ``dateutil`` package if the ``datetime`` parser fails.
+
+
+Version 1.29.1, 2022-05-23
+``````````````````````````
+
+- In trying to determine if there's been a failed commit, check for ``ROLLBACK TO
+ SAVEPOINT``.
+
+
+Version 1.29.0, 2022-05-21
+``````````````````````````
+
+- Implement a workaround for the `silent failed commit
+ <https://github.com/tlocke/pg8000/issues/36>`_ bug.
+
+- Previously if an empty string was sent as the query an exception would be raised, but
+ that isn't done now.
+
+
+Version 1.28.3, 2022-05-18
+``````````````````````````
+
+- Put back ``__version__`` attributes that were inadvertently removed.
+
+
+Version 1.28.2, 2022-05-17
+``````````````````````````
+
+- Use a build system that's compliant with PEP517.
+
+
+Version 1.28.1, 2022-05-17
+``````````````````````````
+
+- If when doing a ``COPY FROM`` the ``stream`` parameter is an iterator of ``str``,
+ pg8000 used to silently append a newline to the end. That no longer happens.
+
+
+Version 1.28.0, 2022-05-17
+``````````````````````````
+
+- When using the ``COPY FROM`` SQL statement, allow the ``stream`` parameter to be an
+ iterable.
+
+
+Version 1.27.1, 2022-05-16
+``````````````````````````
+
+- The ``seconds`` attribute of ``PGInterval`` is now always a ``float``, to cope with
+ fractional seconds.
+
+- Updated the ``interval`` parsers for ``iso_8601`` and ``sql_standard`` to take
+ account of fractional seconds.
+
+
+Version 1.27.0, 2022-05-16
+``````````````````````````
+
+- It used to be that by default, if pg8000 received an ``interval`` type from the server
+ and it was too big to fit into a ``datetime.timedelta`` then an exception would be
+ raised. Now if an interval is too big for ``datetime.timedelta`` a ``PGInterval`` is
+ returned.
+
+* pg8000 now supports all the output formats for an ``interval`` (``postgres``,
+ ``postgres_verbose``, ``iso_8601`` and ``sql_standard``).
+
+
+Version 1.26.1, 2022-04-23
+``````````````````````````
+
+- Make sure all tests are run by the GitHub Actions tests on commit.
+- Remove support for Python 3.6
+- Remove support for PostgreSQL 9.6
+
+
+Version 1.26.0, 2022-04-18
+``````````````````````````
+
+- When connecting, raise an ``InterfaceError('network error')`` rather than let the
+ underlying ``struct.error`` float up.
+
+- Make licence text the same as that used by the OSI. Previously the licence wording
+ differed slightly from the BSD 3 Clause licence at
+ https://opensource.org/licenses/BSD-3-Clause. This meant that automated tools didn't
+ pick it up as being Open Source. The changes are believed to not alter the meaning of the license at all.
+
+
+Version 1.25.0, 2022-04-17
+``````````````````````````
+
+- Fix more cases where a ``ResourceWarning`` would be raise because of a socket that had
+ been left open.
+
+- We now have a single ``InterfaceError`` with the message 'network error' for all
+ network errors, with the underlying exception held in the ``cause`` of the exception.
+
+
+Version 1.24.2, 2022-04-15
+``````````````````````````
+
+- To prevent a ``ResourceWarning`` close socket if a connection can't be created.
+
+
+Version 1.24.1, 2022-03-02
+``````````````````````````
+
+- Return pg +/-infinity dates as ``str``. Previously +/-infinity pg values would cause
+ an error when returned, but now we return +/-infinity as strings.
+
+
+Version 1.24.0, 2022-02-06
+``````````````````````````
+
+- Add SQL escape functions identifier() and literal() to the native API. For use when a
+ query can't be parameterised and the SQL string has to be created using untrusted
+ values.
+
+
+Version 1.23.0, 2021-11-13
+``````````````````````````
+
+- If a query has no parameters, then the query will no longer be parsed. Although there
+ are performance benefits for doing this, the main reason is to avoid query rewriting,
+ which can introduce errors.
+
+
+Version 1.22.1, 2021-11-10
+``````````````````````````
+
+- Fix bug in PGInterval type where ``str()`` failed for a millennia value.
+
+
+Version 1.22.0, 2021-10-13
+``````````````````````````
+
+- Rather than specifying the oids in the ``Parse`` step of the Postgres protocol, pg8000
+ now omits them, and so Postgres will use the oids it determines from the query. This
+ makes the pg8000 code simpler and also it should also make the nuances of type
+ matching more straightforward.
diff --git a/contrib/python/pg8000/pg8000/__init__.py b/contrib/python/pg8000/pg8000/__init__.py
new file mode 100644
index 00000000000..42f13b850c5
--- /dev/null
+++ b/contrib/python/pg8000/pg8000/__init__.py
@@ -0,0 +1,213 @@
+from pg8000.legacy import (
+ BIGINTEGER,
+ BINARY,
+ BOOLEAN,
+ BOOLEAN_ARRAY,
+ BYTES,
+ Binary,
+ CHAR,
+ CHAR_ARRAY,
+ Connection,
+ Cursor,
+ DATE,
+ DATETIME,
+ DECIMAL,
+ DECIMAL_ARRAY,
+ DataError,
+ DatabaseError,
+ Date,
+ DateFromTicks,
+ Error,
+ FLOAT,
+ FLOAT_ARRAY,
+ INET,
+ INT2VECTOR,
+ INTEGER,
+ INTEGER_ARRAY,
+ INTERVAL,
+ IntegrityError,
+ InterfaceError,
+ InternalError,
+ JSON,
+ JSONB,
+ MACADDR,
+ NAME,
+ NAME_ARRAY,
+ NULLTYPE,
+ NUMBER,
+ NotSupportedError,
+ OID,
+ OperationalError,
+ PGInterval,
+ ProgrammingError,
+ ROWID,
+ Range,
+ STRING,
+ TEXT,
+ TEXT_ARRAY,
+ TIME,
+ TIMEDELTA,
+ TIMESTAMP,
+ TIMESTAMPTZ,
+ Time,
+ TimeFromTicks,
+ Timestamp,
+ TimestampFromTicks,
+ UNKNOWN,
+ UUID_TYPE,
+ VARCHAR,
+ VARCHAR_ARRAY,
+ Warning,
+ XID,
+ __version__,
+ pginterval_in,
+ pginterval_out,
+ timedelta_in,
+)
+
+# Copyright (c) 2007-2009, Mathieu Fenniak
+# Copyright (c) The Contributors
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+
+def connect(
+ user,
+ host="localhost",
+ database=None,
+ port=5432,
+ password=None,
+ source_address=None,
+ unix_sock=None,
+ ssl_context=None,
+ timeout=None,
+ tcp_keepalive=True,
+ application_name=None,
+ replication=None,
+):
+ return Connection(
+ user,
+ host=host,
+ database=database,
+ port=port,
+ password=password,
+ source_address=source_address,
+ unix_sock=unix_sock,
+ ssl_context=ssl_context,
+ timeout=timeout,
+ tcp_keepalive=tcp_keepalive,
+ application_name=application_name,
+ replication=replication,
+ )
+
+
+apilevel = "2.0"
+"""The DBAPI level supported, currently "2.0".
+
+This property is part of the `DBAPI 2.0 specification
+<http://www.python.org/dev/peps/pep-0249/>`_.
+"""
+
+threadsafety = 1
+"""Integer constant stating the level of thread safety the DBAPI interface
+supports. This DBAPI module supports sharing of the module only. Connections
+and cursors my not be shared between threads. This gives pg8000 a threadsafety
+value of 1.
+
+This property is part of the `DBAPI 2.0 specification
+<http://www.python.org/dev/peps/pep-0249/>`_.
+"""
+
+paramstyle = "format"
+
+
+__all__ = [
+ "BIGINTEGER",
+ "BINARY",
+ "BOOLEAN",
+ "BOOLEAN_ARRAY",
+ "BYTES",
+ "Binary",
+ "CHAR",
+ "CHAR_ARRAY",
+ "Connection",
+ "Cursor",
+ "DATE",
+ "DATETIME",
+ "DECIMAL",
+ "DECIMAL_ARRAY",
+ "DataError",
+ "DatabaseError",
+ "Date",
+ "DateFromTicks",
+ "Error",
+ "FLOAT",
+ "FLOAT_ARRAY",
+ "INET",
+ "INT2VECTOR",
+ "INTEGER",
+ "INTEGER_ARRAY",
+ "INTERVAL",
+ "IntegrityError",
+ "InterfaceError",
+ "InternalError",
+ "JSON",
+ "JSONB",
+ "MACADDR",
+ "NAME",
+ "NAME_ARRAY",
+ "NULLTYPE",
+ "NUMBER",
+ "NotSupportedError",
+ "OID",
+ "OperationalError",
+ "PGInterval",
+ "ProgrammingError",
+ "ROWID",
+ "Range",
+ "STRING",
+ "TEXT",
+ "TEXT_ARRAY",
+ "TIME",
+ "TIMEDELTA",
+ "TIMESTAMP",
+ "TIMESTAMPTZ",
+ "Time",
+ "TimeFromTicks",
+ "Timestamp",
+ "TimestampFromTicks",
+ "UNKNOWN",
+ "UUID_TYPE",
+ "VARCHAR",
+ "VARCHAR_ARRAY",
+ "Warning",
+ "XID",
+ "__version__",
+ "connect",
+ "pginterval_in",
+ "pginterval_out",
+ "timedelta_in",
+]
diff --git a/contrib/python/pg8000/pg8000/converters.py b/contrib/python/pg8000/pg8000/converters.py
new file mode 100644
index 00000000000..b9d17df5ec2
--- /dev/null
+++ b/contrib/python/pg8000/pg8000/converters.py
@@ -0,0 +1,771 @@
+from datetime import (
+ date as Date,
+ datetime as Datetime,
+ time as Time,
+ timedelta as Timedelta,
+ timezone as Timezone,
+)
+from decimal import Decimal
+from enum import Enum
+from ipaddress import (
+ IPv4Address,
+ IPv4Network,
+ IPv6Address,
+ IPv6Network,
+ ip_address,
+ ip_network,
+)
+from json import dumps, loads
+from uuid import UUID
+
+from dateutil.parser import parse
+
+from pg8000.exceptions import InterfaceError
+from pg8000.types import PGInterval, Range
+
+
+ANY_ARRAY = 2277
+BIGINT = 20
+BIGINT_ARRAY = 1016
+BOOLEAN = 16
+BOOLEAN_ARRAY = 1000
+BYTES = 17
+BYTES_ARRAY = 1001
+CHAR = 1042
+CHAR_ARRAY = 1014
+CIDR = 650
+CIDR_ARRAY = 651
+CSTRING = 2275
+CSTRING_ARRAY = 1263
+DATE = 1082
+DATE_ARRAY = 1182
+DATEMULTIRANGE = 4535
+DATERANGE = 3912
+FLOAT = 701
+FLOAT_ARRAY = 1022
+INET = 869
+INET_ARRAY = 1041
+INT2VECTOR = 22
+INT4MULTIRANGE = 4451
+INT4RANGE = 3904
+INT8MULTIRANGE = 4536
+INT8RANGE = 3926
+INTEGER = 23
+INTEGER_ARRAY = 1007
+INTERVAL = 1186
+INTERVAL_ARRAY = 1187
+OID = 26
+JSON = 114
+JSON_ARRAY = 199
+JSONB = 3802
+JSONB_ARRAY = 3807
+MACADDR = 829
+MONEY = 790
+MONEY_ARRAY = 791
+NAME = 19
+NAME_ARRAY = 1003
+NUMERIC = 1700
+NUMERIC_ARRAY = 1231
+NUMRANGE = 3906
+NUMMULTIRANGE = 4532
+NULLTYPE = -1
+OID = 26
+POINT = 600
+REAL = 700
+REAL_ARRAY = 1021
+RECORD = 2249
+SMALLINT = 21
+SMALLINT_ARRAY = 1005
+SMALLINT_VECTOR = 22
+STRING = 1043
+TEXT = 25
+TEXT_ARRAY = 1009
+TIME = 1083
+TIME_ARRAY = 1183
+TIMESTAMP = 1114
+TIMESTAMP_ARRAY = 1115
+TIMESTAMPTZ = 1184
+TIMESTAMPTZ_ARRAY = 1185
+TSMULTIRANGE = 4533
+TSRANGE = 3908
+TSTZMULTIRANGE = 4534
+TSTZRANGE = 3910
+UNKNOWN = 705
+UUID_TYPE = 2950
+UUID_ARRAY = 2951
+VARCHAR = 1043
+VARCHAR_ARRAY = 1015
+XID = 28
+
+
+MIN_INT2, MAX_INT2 = -(2**15), 2**15
+MIN_INT4, MAX_INT4 = -(2**31), 2**31
+MIN_INT8, MAX_INT8 = -(2**63), 2**63
+
+
+def bool_in(data):
+ return data == "t"
+
+
+def bool_out(v):
+ return "true" if v else "false"
+
+
+def bytes_in(data):
+ return bytes.fromhex(data[2:])
+
+
+def bytes_out(v):
+ return "\\x" + v.hex()
+
+
+def cidr_out(v):
+ return str(v)
+
+
+def cidr_in(data):
+ return ip_network(data, False) if "/" in data else ip_address(data)
+
+
+def date_in(data):
+ if data in ("infinity", "-infinity"):
+ return data
+ else:
+ return Datetime.strptime(data, "%Y-%m-%d").date()
+
+
+def date_out(v):
+ return v.isoformat()
+
+
+def datetime_out(v):
+ if v.tzinfo is None:
+ return v.isoformat()
+ else:
+ return v.astimezone(Timezone.utc).isoformat()
+
+
+def enum_out(v):
+ return str(v.value)
+
+
+def float_out(v):
+ return str(v)
+
+
+def inet_in(data):
+ return ip_network(data, False) if "/" in data else ip_address(data)
+
+
+def inet_out(v):
+ return str(v)
+
+
+def int_in(data):
+ return int(data)
+
+
+def int_out(v):
+ return str(v)
+
+
+def interval_in(data):
+ pg_interval = PGInterval.from_str(data)
+ try:
+ return pg_interval.to_timedelta()
+ except ValueError:
+ return pg_interval
+
+
+def interval_out(v):
+ return f"{v.days} days {v.seconds} seconds {v.microseconds} microseconds"
+
+
+def json_in(data):
+ return loads(data)
+
+
+def json_out(v):
+ return dumps(v)
+
+
+def null_out(v):
+ return None
+
+
+def numeric_in(data):
+ return Decimal(data)
+
+
+def numeric_out(d):
+ return str(d)
+
+
+def point_in(data):
+ return tuple(map(float, data[1:-1].split(",")))
+
+
+def pg_interval_in(data):
+ return PGInterval.from_str(data)
+
+
+def pg_interval_out(v):
+ return str(v)
+
+
+def range_out(v):
+ if v.is_empty:
+ return "empty"
+ else:
+ le = v.lower
+ val_lower = "" if le is None else make_param(PY_TYPES, le)
+ ue = v.upper
+ val_upper = "" if ue is None else make_param(PY_TYPES, ue)
+ return f"{v.bounds[0]}{val_lower},{val_upper}{v.bounds[1]}"
+
+
+def string_in(data):
+ return data
+
+
+def string_out(v):
+ return v
+
+
+def time_in(data):
+ pattern = "%H:%M:%S.%f" if "." in data else "%H:%M:%S"
+ return Datetime.strptime(data, pattern).time()
+
+
+def time_out(v):
+ return v.isoformat()
+
+
+def timestamp_in(data):
+ if data in ("infinity", "-infinity"):
+ return data
+
+ try:
+ pattern = "%Y-%m-%d %H:%M:%S.%f" if "." in data else "%Y-%m-%d %H:%M:%S"
+ return Datetime.strptime(data, pattern)
+ except ValueError:
+ return parse(data)
+
+
+def timestamptz_in(data):
+ if data in ("infinity", "-infinity"):
+ return data
+
+ try:
+ patt = "%Y-%m-%d %H:%M:%S.%f%z" if "." in data else "%Y-%m-%d %H:%M:%S%z"
+ return Datetime.strptime(f"{data}00", patt)
+ except ValueError:
+ return parse(data)
+
+
+def unknown_out(v):
+ return str(v)
+
+
+def vector_in(data):
+ return [int(v) for v in data.split()]
+
+
+def uuid_out(v):
+ return str(v)
+
+
+def uuid_in(data):
+ return UUID(data)
+
+
+class ParserState(Enum):
+ InString = 1
+ InEscape = 2
+ InValue = 3
+ Out = 4
+
+
+def _parse_array(data, adapter):
+ state = ParserState.Out
+ stack = [[]]
+ val = []
+ for c in data:
+ if state == ParserState.InValue:
+ if c in ("}", ","):
+ value = "".join(val)
+ stack[-1].append(None if value == "NULL" else adapter(value))
+ state = ParserState.Out
+ else:
+ val.append(c)
+
+ if state == ParserState.Out:
+ if c == "{":
+ a = []
+ stack[-1].append(a)
+ stack.append(a)
+ elif c == "}":
+ stack.pop()
+ elif c == ",":
+ pass
+ elif c == '"':
+ val = []
+ state = ParserState.InString
+ else:
+ val = [c]
+ state = ParserState.InValue
+
+ elif state == ParserState.InString:
+ if c == '"':
+ stack[-1].append(adapter("".join(val)))
+ state = ParserState.Out
+ elif c == "\\":
+ state = ParserState.InEscape
+ else:
+ val.append(c)
+ elif state == ParserState.InEscape:
+ val.append(c)
+ state = ParserState.InString
+
+ return stack[0][0]
+
+
+def _array_in(adapter):
+ def f(data):
+ return _parse_array(data, adapter)
+
+ return f
+
+
+bool_array_in = _array_in(bool_in)
+bytes_array_in = _array_in(bytes_in)
+cidr_array_in = _array_in(cidr_in)
+date_array_in = _array_in(date_in)
+inet_array_in = _array_in(inet_in)
+int_array_in = _array_in(int)
+interval_array_in = _array_in(interval_in)
+json_array_in = _array_in(json_in)
+float_array_in = _array_in(float)
+numeric_array_in = _array_in(numeric_in)
+string_array_in = _array_in(string_in)
+time_array_in = _array_in(time_in)
+timestamp_array_in = _array_in(timestamp_in)
+timestamptz_array_in = _array_in(timestamptz_in)
+uuid_array_in = _array_in(uuid_in)
+
+
+def array_string_escape(v):
+ cs = []
+ for c in v:
+ if c == "\\":
+ cs.append("\\")
+ elif c == '"':
+ cs.append("\\")
+ cs.append(c)
+ val = "".join(cs)
+ if (
+ len(val) == 0
+ or val == "NULL"
+ or any(c.isspace() for c in val)
+ or any(c in val for c in ("{", "}", ",", "\\"))
+ ):
+ val = f'"{val}"'
+ return val
+
+
+def array_out(ar):
+ result = []
+ for v in ar:
+ if isinstance(v, list):
+ val = array_out(v)
+
+ elif isinstance(v, tuple):
+ val = f'"{composite_out(v)}"'
+
+ elif v is None:
+ val = "NULL"
+
+ elif isinstance(v, dict):
+ val = array_string_escape(json_out(v))
+
+ elif isinstance(v, (bytes, bytearray)):
+ val = f'"\\{bytes_out(v)}"'
+
+ elif isinstance(v, str):
+ val = array_string_escape(v)
+
+ else:
+ val = make_param(PY_TYPES, v)
+
+ result.append(val)
+
+ return f'{{{",".join(result)}}}'
+
+
+def composite_out(ar):
+ result = []
+ for v in ar:
+ if isinstance(v, list):
+ val = array_out(v)
+
+ elif isinstance(v, tuple):
+ val = composite_out(v)
+
+ elif v is None:
+ val = ""
+
+ elif isinstance(v, dict):
+ val = array_string_escape(json_out(v))
+
+ elif isinstance(v, (bytes, bytearray)):
+ val = f'"\\{bytes_out(v)}"'
+
+ elif isinstance(v, str):
+ val = array_string_escape(v)
+
+ else:
+ val = make_param(PY_TYPES, v)
+
+ result.append(val)
+
+ return f'({",".join(result)})'
+
+
+def _range_in(elem_func):
+ def range_in(data):
+ if data == "empty":
+ return Range(is_empty=True)
+ else:
+ le, ue = [None if v == "" else elem_func(v) for v in data[1:-1].split(",")]
+ return Range(le, ue, bounds=f"{data[0]}{data[-1]}")
+
+ return range_in
+
+
+daterange_in = _range_in(date_in)
+int4range_in = _range_in(int)
+int8range_in = _range_in(int)
+numrange_in = _range_in(Decimal)
+
+
+def ts_in(data):
+ return timestamp_in(data[1:-1])
+
+
+def tstz_in(data):
+ return timestamptz_in(data[1:-1])
+
+
+tsrange_in = _range_in(ts_in)
+tstzrange_in = _range_in(tstz_in)
+
+
+def _multirange_in(adapter):
+ def f(data):
+ in_range = False
+ result = []
+ val = []
+ for c in data:
+ if in_range:
+ val.append(c)
+ if c in "])":
+ value = "".join(val)
+ val.clear()
+ result.append(adapter(value))
+ in_range = False
+ elif c in "[(":
+ val.append(c)
+ in_range = True
+
+ return result
+
+ return f
+
+
+datemultirange_in = _multirange_in(daterange_in)
+int4multirange_in = _multirange_in(int4range_in)
+int8multirange_in = _multirange_in(int8range_in)
+nummultirange_in = _multirange_in(numrange_in)
+tsmultirange_in = _multirange_in(tsrange_in)
+tstzmultirange_in = _multirange_in(tstzrange_in)
+
+
+def record_in(data):
+ state = ParserState.Out
+ results = []
+ val = []
+ for c in data:
+ if state == ParserState.InValue:
+ if c in (")", ","):
+ value = "".join(val)
+ val.clear()
+ results.append(None if value == "" else value)
+ state = ParserState.Out
+ else:
+ val.append(c)
+
+ if state == ParserState.Out:
+ if c in "(),":
+ pass
+ elif c == '"':
+ state = ParserState.InString
+ else:
+ val.append(c)
+ state = ParserState.InValue
+
+ elif state == ParserState.InString:
+ if c == '"':
+ results.append("".join(val))
+ val.clear()
+ state = ParserState.Out
+ elif c == "\\":
+ state = ParserState.InEscape
+ else:
+ val.append(c)
+
+ elif state == ParserState.InEscape:
+ val.append(c)
+ state = ParserState.InString
+
+ return tuple(results)
+
+
+PY_PG = {
+ Date: DATE,
+ Decimal: NUMERIC,
+ IPv4Address: INET,
+ IPv6Address: INET,
+ IPv4Network: INET,
+ IPv6Network: INET,
+ PGInterval: INTERVAL,
+ Time: TIME,
+ Timedelta: INTERVAL,
+ UUID: UUID_TYPE,
+ bool: BOOLEAN,
+ bytearray: BYTES,
+ dict: JSONB,
+ float: FLOAT,
+ type(None): NULLTYPE,
+ bytes: BYTES,
+ str: TEXT,
+}
+
+
+PY_TYPES = {
+ Date: date_out, # date
+ Datetime: datetime_out,
+ Decimal: numeric_out, # numeric
+ Enum: enum_out, # enum
+ IPv4Address: inet_out, # inet
+ IPv6Address: inet_out, # inet
+ IPv4Network: inet_out, # inet
+ IPv6Network: inet_out, # inet
+ PGInterval: interval_out, # interval
+ Range: range_out, # range types
+ Time: time_out, # time
+ Timedelta: interval_out, # interval
+ UUID: uuid_out, # uuid
+ bool: bool_out, # bool
+ bytearray: bytes_out, # bytea
+ dict: json_out, # jsonb
+ float: float_out, # float8
+ type(None): null_out, # null
+ bytes: bytes_out, # bytea
+ str: string_out, # unknown
+ int: int_out,
+ list: array_out,
+ tuple: composite_out,
+}
+
+
+PG_TYPES = {
+ BIGINT: int, # int8
+ BIGINT_ARRAY: int_array_in, # int8[]
+ BOOLEAN: bool_in, # bool
+ BOOLEAN_ARRAY: bool_array_in, # bool[]
+ BYTES: bytes_in, # bytea
+ BYTES_ARRAY: bytes_array_in, # bytea[]
+ CHAR: string_in, # char
+ CHAR_ARRAY: string_array_in, # char[]
+ CIDR_ARRAY: cidr_array_in, # cidr[]
+ CSTRING: string_in, # cstring
+ CSTRING_ARRAY: string_array_in, # cstring[]
+ DATE: date_in, # date
+ DATE_ARRAY: date_array_in, # date[]
+ DATEMULTIRANGE: datemultirange_in, # datemultirange
+ DATERANGE: daterange_in, # daterange
+ FLOAT: float, # float8
+ FLOAT_ARRAY: float_array_in, # float8[]
+ INET: inet_in, # inet
+ INET_ARRAY: inet_array_in, # inet[]
+ INT4MULTIRANGE: int4multirange_in, # int4multirange
+ INT4RANGE: int4range_in, # int4range
+ INT8MULTIRANGE: int8multirange_in, # int8multirange
+ INT8RANGE: int8range_in, # int8range
+ INTEGER: int, # int4
+ INTEGER_ARRAY: int_array_in, # int4[]
+ JSON: json_in, # json
+ JSON_ARRAY: json_array_in, # json[]
+ JSONB: json_in, # jsonb
+ JSONB_ARRAY: json_array_in, # jsonb[]
+ MACADDR: string_in, # MACADDR type
+ MONEY: string_in, # money
+ MONEY_ARRAY: string_array_in, # money[]
+ NAME: string_in, # name
+ NAME_ARRAY: string_array_in, # name[]
+ NUMERIC: numeric_in, # numeric
+ NUMERIC_ARRAY: numeric_array_in, # numeric[]
+ NUMRANGE: numrange_in, # numrange
+ NUMMULTIRANGE: nummultirange_in, # nummultirange
+ OID: int, # oid
+ POINT: point_in, # point
+ INTERVAL: interval_in, # interval
+ INTERVAL_ARRAY: interval_array_in, # interval[]
+ REAL: float, # float4
+ REAL_ARRAY: float_array_in, # float4[]
+ RECORD: record_in, # record
+ SMALLINT: int, # int2
+ SMALLINT_ARRAY: int_array_in, # int2[]
+ SMALLINT_VECTOR: vector_in, # int2vector
+ TEXT: string_in, # text
+ TEXT_ARRAY: string_array_in, # text[]
+ TIME: time_in, # time
+ TIME_ARRAY: time_array_in, # time[]
+ INTERVAL: interval_in, # interval
+ TIMESTAMP: timestamp_in, # timestamp
+ TIMESTAMP_ARRAY: timestamp_array_in, # timestamp
+ TIMESTAMPTZ: timestamptz_in, # timestamptz
+ TIMESTAMPTZ_ARRAY: timestamptz_array_in, # timestamptz
+ TSMULTIRANGE: tsmultirange_in, # tsmultirange
+ TSRANGE: tsrange_in, # tsrange
+ TSTZMULTIRANGE: tstzmultirange_in, # tstzmultirange
+ TSTZRANGE: tstzrange_in, # tstzrange
+ UNKNOWN: string_in, # unknown
+ UUID_ARRAY: uuid_array_in, # uuid[]
+ UUID_TYPE: uuid_in, # uuid
+ VARCHAR: string_in, # varchar
+ VARCHAR_ARRAY: string_array_in, # varchar[]
+ XID: int, # xid
+}
+
+
+# PostgreSQL encodings:
+# https://www.postgresql.org/docs/current/multibyte.html
+#
+# Python encodings:
+# https://docs.python.org/3/library/codecs.html
+#
+# Commented out encodings don't require a name change between PostgreSQL and
+# Python. If the py side is None, then the encoding isn't supported.
+PG_PY_ENCODINGS = {
+ # Not supported:
+ "mule_internal": None,
+ "euc_tw": None,
+ # Name fine as-is:
+ # "euc_jp",
+ # "euc_jis_2004",
+ # "euc_kr",
+ # "gb18030",
+ # "gbk",
+ # "johab",
+ # "sjis",
+ # "shift_jis_2004",
+ # "uhc",
+ # "utf8",
+ # Different name:
+ "euc_cn": "gb2312",
+ "iso_8859_5": "is8859_5",
+ "iso_8859_6": "is8859_6",
+ "iso_8859_7": "is8859_7",
+ "iso_8859_8": "is8859_8",
+ "koi8": "koi8_r",
+ "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",
+ "sql_ascii": "ascii",
+ "win866": "cp886",
+ "win874": "cp874",
+ "win1250": "cp1250",
+ "win1251": "cp1251",
+ "win1252": "cp1252",
+ "win1253": "cp1253",
+ "win1254": "cp1254",
+ "win1255": "cp1255",
+ "win1256": "cp1256",
+ "win1257": "cp1257",
+ "win1258": "cp1258",
+ "unicode": "utf-8", # Needed for Amazon Redshift
+}
+
+
+def make_param(py_types, value):
+ try:
+ func = py_types[type(value)]
+ except KeyError:
+ func = str
+ for k, v in py_types.items():
+ try:
+ if isinstance(value, k):
+ func = v
+ break
+ except TypeError:
+ pass
+
+ return func(value)
+
+
+def make_params(py_types, values):
+ return tuple([make_param(py_types, v) for v in values])
+
+
+def identifier(sql):
+ if not isinstance(sql, str):
+ raise InterfaceError("identifier must be a str")
+
+ if len(sql) == 0:
+ raise InterfaceError("identifier must be > 0 characters in length")
+
+ quote = not sql[0].isalpha()
+
+ for c in sql[1:]:
+ if not (c.isalpha() or c.isdecimal() or c in "_$"):
+ if c == "\u0000":
+ raise InterfaceError(
+ "identifier cannot contain the code zero character"
+ )
+ quote = True
+ break
+
+ if quote:
+ sql = sql.replace('"', '""')
+ return f'"{sql}"'
+ else:
+ return sql
+
+
+def literal(value):
+ if value is None:
+ return "NULL"
+ elif isinstance(value, bool):
+ return "TRUE" if value else "FALSE"
+ elif isinstance(value, (int, float, Decimal)):
+ return str(value)
+ elif isinstance(value, (bytes, bytearray)):
+ return f"X'{value.hex()}'"
+ elif isinstance(value, Date):
+ return f"'{date_out(value)}'"
+ elif isinstance(value, Time):
+ return f"'{time_out(value)}'"
+ elif isinstance(value, Datetime):
+ return f"'{datetime_out(value)}'"
+ elif isinstance(value, Timedelta):
+ return f"'{interval_out(value)}'"
+ else:
+ val = str(value).replace("'", "''")
+ return f"'{val}'"
diff --git a/contrib/python/pg8000/pg8000/core.py b/contrib/python/pg8000/pg8000/core.py
new file mode 100644
index 00000000000..54ef496ddd9
--- /dev/null
+++ b/contrib/python/pg8000/pg8000/core.py
@@ -0,0 +1,870 @@
+import codecs
+import socket
+from collections import defaultdict, deque
+from hashlib import md5
+from importlib.metadata import version
+from io import IOBase, TextIOBase
+from itertools import count
+from struct import Struct
+
+import scramp
+
+from pg8000.converters import (
+ PG_PY_ENCODINGS,
+ PG_TYPES,
+ PY_TYPES,
+ make_params,
+ string_in,
+)
+from pg8000.exceptions import DatabaseError, Error, InterfaceError
+
+
+ver = version("pg8000")
+
+
+def pack_funcs(fmt):
+ struc = Struct(f"!{fmt}")
+ return struc.pack, struc.unpack_from
+
+
+i_pack, i_unpack = pack_funcs("i")
+h_pack, h_unpack = pack_funcs("h")
+ii_pack, ii_unpack = pack_funcs("ii")
+ihihih_pack, ihihih_unpack = pack_funcs("ihihih")
+ci_pack, ci_unpack = pack_funcs("ci")
+bh_pack, bh_unpack = pack_funcs("bh")
+cccc_pack, cccc_unpack = pack_funcs("cccc")
+
+
+# Copyright (c) 2007-2009, Mathieu Fenniak
+# Copyright (c) The Contributors
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+__author__ = "Mathieu Fenniak"
+
+
+NULL_BYTE = b"\x00"
+
+
+# Message codes
+NOTICE_RESPONSE = b"N"
+AUTHENTICATION_REQUEST = b"R"
+PARAMETER_STATUS = b"S"
+BACKEND_KEY_DATA = b"K"
+READY_FOR_QUERY = b"Z"
+ROW_DESCRIPTION = b"T"
+ERROR_RESPONSE = b"E"
+DATA_ROW = b"D"
+COMMAND_COMPLETE = b"C"
+PARSE_COMPLETE = b"1"
+BIND_COMPLETE = b"2"
+CLOSE_COMPLETE = b"3"
+PORTAL_SUSPENDED = b"s"
+NO_DATA = b"n"
+PARAMETER_DESCRIPTION = b"t"
+NOTIFICATION_RESPONSE = b"A"
+COPY_DONE = b"c"
+COPY_DATA = b"d"
+COPY_IN_RESPONSE = b"G"
+COPY_OUT_RESPONSE = b"H"
+EMPTY_QUERY_RESPONSE = b"I"
+
+BIND = b"B"
+PARSE = b"P"
+QUERY = b"Q"
+EXECUTE = b"E"
+FLUSH = b"H"
+SYNC = b"S"
+PASSWORD = b"p"
+DESCRIBE = b"D"
+TERMINATE = b"X"
+CLOSE = b"C"
+
+
+def _create_message(code, data=b""):
+ return code + i_pack(len(data) + 4) + data
+
+
+FLUSH_MSG = _create_message(FLUSH)
+SYNC_MSG = _create_message(SYNC)
+TERMINATE_MSG = _create_message(TERMINATE)
+COPY_DONE_MSG = _create_message(COPY_DONE)
+EXECUTE_MSG = _create_message(EXECUTE, NULL_BYTE + i_pack(0))
+
+# DESCRIBE constants
+STATEMENT = b"S"
+PORTAL = b"P"
+
+# ErrorResponse codes
+RESPONSE_SEVERITY = "S" # always present
+RESPONSE_SEVERITY = "V" # always present
+RESPONSE_CODE = "C" # always present
+RESPONSE_MSG = "M" # always present
+RESPONSE_DETAIL = "D"
+RESPONSE_HINT = "H"
+RESPONSE_POSITION = "P"
+RESPONSE__POSITION = "p"
+RESPONSE__QUERY = "q"
+RESPONSE_WHERE = "W"
+RESPONSE_FILE = "F"
+RESPONSE_LINE = "L"
+RESPONSE_ROUTINE = "R"
+
+IDLE = b"I"
+IN_TRANSACTION = b"T"
+IN_FAILED_TRANSACTION = b"E"
+
+
+def _flush(sock):
+ try:
+ sock.flush()
+ except OSError as e:
+ raise InterfaceError("network error") from e
+
+
+def _read(sock, size):
+ got = 0
+ buff = []
+ try:
+ while got < size:
+ block = sock.read(size - got)
+ if block == b"":
+ raise InterfaceError("network error")
+ buff.append(block)
+ got += len(block)
+ except OSError as e:
+ raise InterfaceError("network error") from e
+
+ return b"".join(buff)
+
+
+def _write(sock, d):
+ try:
+ sock.write(d)
+ except OSError as e:
+ raise InterfaceError("network error") from e
+
+
+def _make_socket(
+ unix_sock, sock, host, port, timeout, source_address, tcp_keepalive, ssl_context
+):
+ if unix_sock is not None:
+ if sock is not None:
+ raise InterfaceError("If unix_sock is provided, sock must be None")
+
+ try:
+ if not hasattr(socket, "AF_UNIX"):
+ raise InterfaceError(
+ "attempt to connect to unix socket on unsupported platform"
+ )
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ sock.settimeout(timeout)
+ sock.connect(unix_sock)
+ if tcp_keepalive:
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
+ except socket.error as e:
+ if sock is not None:
+ sock.close()
+ raise InterfaceError("communication error") from e
+
+ elif sock is not None:
+ pass
+
+ elif host is not None:
+ try:
+ sock = socket.create_connection((host, port), timeout, source_address)
+ except socket.error as e:
+ raise InterfaceError(
+ f"Can't create a connection to host {host} and port {port} "
+ f"(timeout is {timeout} and source_address is {source_address})."
+ ) from e
+
+ if tcp_keepalive:
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
+
+ else:
+ raise InterfaceError("one of host, sock or unix_sock must be provided")
+
+ channel_binding = None
+ if ssl_context is not None:
+ try:
+ import ssl
+
+ if ssl_context is True:
+ ssl_context = ssl.create_default_context()
+
+ request_ssl = getattr(ssl_context, "request_ssl", True)
+
+ if request_ssl:
+ # Int32(8) - Message length, including self.
+ # Int32(80877103) - The SSL request code.
+ sock.sendall(ii_pack(8, 80877103))
+ resp = sock.recv(1)
+ if resp != b"S":
+ raise InterfaceError("Server refuses SSL")
+
+ sock = ssl_context.wrap_socket(sock, server_hostname=host)
+
+ if request_ssl:
+ channel_binding = scramp.make_channel_binding(
+ "tls-server-end-point", sock
+ )
+
+ except ImportError:
+ raise InterfaceError(
+ "SSL required but ssl module not available in this python "
+ "installation."
+ )
+ return channel_binding, sock
+
+
+class CoreConnection:
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.close()
+
+ def __init__(
+ self,
+ user,
+ host="localhost",
+ database=None,
+ port=5432,
+ password=None,
+ source_address=None,
+ unix_sock=None,
+ ssl_context=None,
+ timeout=None,
+ tcp_keepalive=True,
+ application_name=None,
+ replication=None,
+ sock=None,
+ ):
+ self._client_encoding = "utf8"
+ self._commands_with_count = (
+ b"INSERT",
+ b"DELETE",
+ b"UPDATE",
+ b"MOVE",
+ b"FETCH",
+ b"COPY",
+ b"SELECT",
+ )
+ self.notifications = deque(maxlen=100)
+ self.notices = deque(maxlen=100)
+ self.parameter_statuses = deque(maxlen=100)
+
+ if user is None:
+ raise InterfaceError("The 'user' connection parameter cannot be None")
+
+ init_params = {
+ "user": user,
+ "database": database,
+ "application_name": application_name,
+ "replication": replication,
+ }
+
+ for k, v in tuple(init_params.items()):
+ if isinstance(v, str):
+ init_params[k] = v.encode("utf8")
+ elif v is None:
+ del init_params[k]
+ elif not isinstance(v, (bytes, bytearray)):
+ raise InterfaceError(f"The parameter {k} can't be of type {type(v)}.")
+
+ self.user = init_params["user"]
+
+ if isinstance(password, str):
+ self.password = password.encode("utf8")
+ else:
+ self.password = password
+
+ self._xid = None
+ self._statement_nums = set()
+
+ self._caches = {}
+
+ self.channel_binding, self._usock = _make_socket(
+ unix_sock,
+ sock,
+ host,
+ port,
+ timeout,
+ source_address,
+ tcp_keepalive,
+ ssl_context,
+ )
+
+ self._sock = self._usock.makefile(mode="rwb")
+
+ self._backend_key_data = None
+
+ self.pg_types = defaultdict(lambda: string_in, PG_TYPES)
+ self.py_types = dict(PY_TYPES)
+
+ self.message_types = {
+ NOTICE_RESPONSE: self.handle_NOTICE_RESPONSE,
+ AUTHENTICATION_REQUEST: self.handle_AUTHENTICATION_REQUEST,
+ PARAMETER_STATUS: self.handle_PARAMETER_STATUS,
+ BACKEND_KEY_DATA: self.handle_BACKEND_KEY_DATA,
+ READY_FOR_QUERY: self.handle_READY_FOR_QUERY,
+ ROW_DESCRIPTION: self.handle_ROW_DESCRIPTION,
+ ERROR_RESPONSE: self.handle_ERROR_RESPONSE,
+ EMPTY_QUERY_RESPONSE: self.handle_EMPTY_QUERY_RESPONSE,
+ DATA_ROW: self.handle_DATA_ROW,
+ COMMAND_COMPLETE: self.handle_COMMAND_COMPLETE,
+ PARSE_COMPLETE: self.handle_PARSE_COMPLETE,
+ BIND_COMPLETE: self.handle_BIND_COMPLETE,
+ CLOSE_COMPLETE: self.handle_CLOSE_COMPLETE,
+ PORTAL_SUSPENDED: self.handle_PORTAL_SUSPENDED,
+ NO_DATA: self.handle_NO_DATA,
+ PARAMETER_DESCRIPTION: self.handle_PARAMETER_DESCRIPTION,
+ NOTIFICATION_RESPONSE: self.handle_NOTIFICATION_RESPONSE,
+ COPY_DONE: self.handle_COPY_DONE,
+ COPY_DATA: self.handle_COPY_DATA,
+ COPY_IN_RESPONSE: self.handle_COPY_IN_RESPONSE,
+ COPY_OUT_RESPONSE: self.handle_COPY_OUT_RESPONSE,
+ }
+
+ # Int32 - Message length, including self.
+ # Int32(196608) - Protocol version number. Version 3.0.
+ # Any number of key/value pairs, terminated by a zero byte:
+ # String - A parameter name (user, database, or options)
+ # String - Parameter value
+ protocol = 196608
+ val = bytearray(i_pack(protocol))
+
+ for k, v in init_params.items():
+ val.extend(k.encode("ascii") + NULL_BYTE + v + NULL_BYTE)
+ val.append(0)
+ _write(self._sock, i_pack(len(val) + 4))
+ _write(self._sock, val)
+ _flush(self._sock)
+
+ try:
+ code = None
+ context = Context(None)
+ while code not in (READY_FOR_QUERY, ERROR_RESPONSE):
+ code, data_len = ci_unpack(_read(self._sock, 5))
+
+ self.message_types[code](_read(self._sock, data_len - 4), context)
+
+ if context.error is not None:
+ raise context.error
+
+ except Error as e:
+ self.close()
+ raise e
+
+ self._transaction_status = None
+
+ def register_out_adapter(self, typ, out_func):
+ self.py_types[typ] = out_func
+
+ def register_in_adapter(self, oid, in_func):
+ self.pg_types[oid] = in_func
+
+ def handle_ERROR_RESPONSE(self, data, context):
+ msg = {
+ s[:1].decode("ascii"): s[1:].decode(self._client_encoding, errors="replace")
+ for s in data.split(NULL_BYTE)
+ if s != b""
+ }
+
+ context.error = DatabaseError(msg)
+
+ def handle_EMPTY_QUERY_RESPONSE(self, data, context):
+ pass
+
+ def handle_CLOSE_COMPLETE(self, data, context):
+ pass
+
+ def handle_PARSE_COMPLETE(self, data, context):
+ # Byte1('1') - Identifier.
+ # Int32(4) - Message length, including self.
+ pass
+
+ def handle_BIND_COMPLETE(self, data, context):
+ pass
+
+ def handle_PORTAL_SUSPENDED(self, data, context):
+ pass
+
+ def handle_PARAMETER_DESCRIPTION(self, data, context):
+ """https://www.postgresql.org/docs/current/protocol-message-formats.html"""
+
+ # count = h_unpack(data)[0]
+ # context.parameter_oids = unpack_from("!" + "i" * count, data, 2)
+
+ def handle_COPY_DONE(self, data, context):
+ pass
+
+ def handle_COPY_OUT_RESPONSE(self, data, context):
+ """https://www.postgresql.org/docs/current/protocol-message-formats.html"""
+
+ is_binary, num_cols = bh_unpack(data)
+ # column_formats = unpack_from('!' + 'h' * num_cols, data, 3)
+
+ if context.stream is None:
+ raise InterfaceError(
+ "An output stream is required for the COPY OUT response."
+ )
+
+ elif isinstance(context.stream, TextIOBase):
+ if is_binary:
+ raise InterfaceError(
+ "The COPY OUT stream is binary, but the stream parameter is text."
+ )
+ else:
+ decode = codecs.getdecoder(self._client_encoding)
+
+ def w(data):
+ context.stream.write(decode(data)[0])
+
+ context.stream_write = w
+
+ else:
+ context.stream_write = context.stream.write
+
+ def handle_COPY_DATA(self, data, context):
+ context.stream_write(data)
+
+ def handle_COPY_IN_RESPONSE(self, data, context):
+ """https://www.postgresql.org/docs/current/protocol-message-formats.html"""
+ is_binary, num_cols = bh_unpack(data)
+ # column_formats = unpack_from('!' + 'h' * num_cols, data, 3)
+
+ if context.stream is None:
+ raise InterfaceError(
+ "The 'stream' parameter is required for the COPY IN response. The "
+ "'stream' parameter can be an I/O stream or an iterable."
+ )
+
+ if isinstance(context.stream, IOBase):
+ if isinstance(context.stream, TextIOBase):
+ if is_binary:
+ raise InterfaceError(
+ "The COPY IN stream is binary, but the stream parameter is a "
+ "text stream."
+ )
+
+ else:
+
+ def ri(bffr):
+ bffr.clear()
+ bffr.extend(
+ context.stream.read(4096).encode(self._client_encoding)
+ )
+ return len(bffr)
+
+ readinto = ri
+ else:
+ readinto = context.stream.readinto
+
+ bffr = bytearray(8192)
+ while True:
+ bytes_read = readinto(bffr)
+ if bytes_read == 0:
+ break
+ _write(self._sock, COPY_DATA)
+ _write(self._sock, i_pack(bytes_read + 4))
+ _write(self._sock, bffr[:bytes_read])
+ _flush(self._sock)
+
+ else:
+ for k in context.stream:
+ if isinstance(k, str):
+ if is_binary:
+ raise InterfaceError(
+ "The COPY IN stream is binary, but the stream parameter "
+ "is an iterable with str type items."
+ )
+ b = k.encode(self._client_encoding)
+ else:
+ b = k
+
+ self._send_message(COPY_DATA, b)
+ _flush(self._sock)
+
+ # Send CopyDone
+ _write(self._sock, COPY_DONE_MSG)
+ _write(self._sock, SYNC_MSG)
+ _flush(self._sock)
+
+ def handle_NOTIFICATION_RESPONSE(self, data, context):
+ """https://www.postgresql.org/docs/current/protocol-message-formats.html"""
+ backend_pid = i_unpack(data)[0]
+ idx = 4
+ null_idx = data.find(NULL_BYTE, idx)
+ channel = data[idx:null_idx].decode("ascii")
+ payload = data[null_idx + 1 : -1].decode("ascii")
+
+ self.notifications.append((backend_pid, channel, payload))
+
+ def close(self):
+ """Closes the database connection.
+
+ This function is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+
+ if self._usock is None:
+ raise InterfaceError("connection is closed")
+
+ try:
+ _write(self._sock, TERMINATE_MSG)
+ _flush(self._sock)
+ finally:
+ try:
+ self._usock.close()
+ except socket.error as e:
+ raise InterfaceError("network error") from e
+ finally:
+ self._sock = None
+ self._usock = None
+
+ def handle_AUTHENTICATION_REQUEST(self, data, context):
+ """https://www.postgresql.org/docs/current/protocol-message-formats.html"""
+
+ auth_code = i_unpack(data)[0]
+ if auth_code == 0:
+ pass
+ elif auth_code == 3:
+ if self.password is None:
+ raise InterfaceError(
+ "server requesting password authentication, but no password was "
+ "provided"
+ )
+ self._send_message(PASSWORD, self.password + NULL_BYTE)
+ _flush(self._sock)
+
+ elif auth_code == 5:
+ salt = b"".join(cccc_unpack(data, 4))
+ if self.password is None:
+ raise InterfaceError(
+ "server requesting MD5 password authentication, but no password "
+ "was provided"
+ )
+ pwd = b"md5" + md5(
+ md5(self.password + self.user).hexdigest().encode("ascii") + salt
+ ).hexdigest().encode("ascii")
+ self._send_message(PASSWORD, pwd + NULL_BYTE)
+ _flush(self._sock)
+
+ elif auth_code == 10:
+ # AuthenticationSASL
+ mechanisms = [m.decode("ascii") for m in data[4:-2].split(NULL_BYTE)]
+
+ self.auth = scramp.ScramClient(
+ mechanisms,
+ self.user.decode("utf8"),
+ self.password.decode("utf8"),
+ channel_binding=self.channel_binding,
+ )
+
+ init = self.auth.get_client_first().encode("utf8")
+ mech = self.auth.mechanism_name.encode("ascii") + NULL_BYTE
+
+ # SASLInitialResponse
+ self._send_message(PASSWORD, mech + i_pack(len(init)) + init)
+ _flush(self._sock)
+
+ elif auth_code == 11:
+ # AuthenticationSASLContinue
+ self.auth.set_server_first(data[4:].decode("utf8"))
+
+ # SASLResponse
+ msg = self.auth.get_client_final().encode("utf8")
+ self._send_message(PASSWORD, msg)
+ _flush(self._sock)
+
+ elif auth_code == 12:
+ # AuthenticationSASLFinal
+ self.auth.set_server_final(data[4:].decode("utf8"))
+
+ elif auth_code in (2, 4, 6, 7, 8, 9):
+ raise InterfaceError(
+ f"Authentication method {auth_code} not supported by pg8000."
+ )
+ else:
+ raise InterfaceError(
+ f"Authentication method {auth_code} not recognized by pg8000."
+ )
+
+ def handle_READY_FOR_QUERY(self, data, context):
+ self._transaction_status = data
+
+ def handle_BACKEND_KEY_DATA(self, data, context):
+ self._backend_key_data = data
+
+ def handle_ROW_DESCRIPTION(self, data, context):
+ count = h_unpack(data)[0]
+ idx = 2
+ columns = []
+ input_funcs = []
+ for i in range(count):
+ name = data[idx : data.find(NULL_BYTE, idx)]
+ idx += len(name) + 1
+ field = dict(
+ zip(
+ (
+ "table_oid",
+ "column_attrnum",
+ "type_oid",
+ "type_size",
+ "type_modifier",
+ "format",
+ ),
+ ihihih_unpack(data, idx),
+ )
+ )
+ field["name"] = name.decode(self._client_encoding)
+ idx += 18
+ columns.append(field)
+ input_funcs.append(self.pg_types[field["type_oid"]])
+
+ context.columns = columns
+ context.input_funcs = input_funcs
+ if context.rows is None:
+ context.rows = []
+
+ def send_PARSE(self, statement_name_bin, statement, oids=()):
+ val = bytearray(statement_name_bin)
+ val.extend(statement.encode(self._client_encoding) + NULL_BYTE)
+ val.extend(h_pack(len(oids)))
+ for oid in oids:
+ val.extend(i_pack(0 if oid == -1 else oid))
+
+ self._send_message(PARSE, val)
+ _write(self._sock, FLUSH_MSG)
+
+ def send_DESCRIBE_STATEMENT(self, statement_name_bin):
+ self._send_message(DESCRIBE, STATEMENT + statement_name_bin)
+ _write(self._sock, FLUSH_MSG)
+
+ def send_QUERY(self, sql):
+ self._send_message(QUERY, sql.encode(self._client_encoding) + NULL_BYTE)
+
+ def execute_simple(self, statement):
+ context = Context(statement)
+
+ self.send_QUERY(statement)
+ _flush(self._sock)
+ self.handle_messages(context)
+
+ return context
+
+ def execute_unnamed(self, statement, vals=(), oids=(), stream=None):
+ context = Context(statement, stream=stream)
+
+ self.send_PARSE(NULL_BYTE, statement, oids)
+ _write(self._sock, SYNC_MSG)
+ _flush(self._sock)
+ self.handle_messages(context)
+ self.send_DESCRIBE_STATEMENT(NULL_BYTE)
+
+ _write(self._sock, SYNC_MSG)
+
+ try:
+ _flush(self._sock)
+ except AttributeError as e:
+ if self._sock is None:
+ raise InterfaceError("connection is closed")
+ else:
+ raise e
+ params = make_params(self.py_types, vals)
+ self.send_BIND(NULL_BYTE, params)
+ self.handle_messages(context)
+ self.send_EXECUTE()
+
+ _write(self._sock, SYNC_MSG)
+ _flush(self._sock)
+ self.handle_messages(context)
+
+ return context
+
+ def prepare_statement(self, statement, oids=None):
+ for i in count():
+ statement_name = f"pg8000_statement_{i}"
+ statement_name_bin = statement_name.encode("ascii") + NULL_BYTE
+ if statement_name_bin not in self._statement_nums:
+ self._statement_nums.add(statement_name_bin)
+ break
+
+ self.send_PARSE(statement_name_bin, statement, oids)
+ self.send_DESCRIBE_STATEMENT(statement_name_bin)
+ _write(self._sock, SYNC_MSG)
+
+ try:
+ _flush(self._sock)
+ except AttributeError as e:
+ if self._sock is None:
+ raise InterfaceError("connection is closed")
+ else:
+ raise e
+
+ context = Context(statement)
+ self.handle_messages(context)
+
+ return statement_name_bin, context.columns, context.input_funcs
+
+ def execute_named(
+ self, statement_name_bin, params, columns, input_funcs, statement
+ ):
+ context = Context(columns=columns, input_funcs=input_funcs, statement=statement)
+
+ self.send_BIND(statement_name_bin, params)
+ self.send_EXECUTE()
+ _write(self._sock, SYNC_MSG)
+ _flush(self._sock)
+ self.handle_messages(context)
+ return context
+
+ def _send_message(self, code, data):
+ try:
+ _write(self._sock, code)
+ _write(self._sock, i_pack(len(data) + 4))
+ _write(self._sock, data)
+ except ValueError as e:
+ if str(e) == "write to closed file":
+ raise InterfaceError("connection is closed")
+ else:
+ raise e
+ except AttributeError:
+ raise InterfaceError("connection is closed")
+
+ def send_BIND(self, statement_name_bin, params):
+ """https://www.postgresql.org/docs/current/protocol-message-formats.html"""
+
+ retval = bytearray(
+ NULL_BYTE + statement_name_bin + h_pack(0) + h_pack(len(params))
+ )
+
+ for value in params:
+ if value is None:
+ retval.extend(i_pack(-1))
+ else:
+ val = value.encode(self._client_encoding)
+ retval.extend(i_pack(len(val)))
+ retval.extend(val)
+ retval.extend(h_pack(0))
+
+ self._send_message(BIND, retval)
+ _write(self._sock, FLUSH_MSG)
+
+ def send_EXECUTE(self):
+ """https://www.postgresql.org/docs/current/protocol-message-formats.html"""
+ _write(self._sock, EXECUTE_MSG)
+ _write(self._sock, FLUSH_MSG)
+
+ def handle_NO_DATA(self, msg, context):
+ pass
+
+ def handle_COMMAND_COMPLETE(self, data, context):
+ if self._transaction_status == IN_FAILED_TRANSACTION and context.error is None:
+ sql = context.statement.split()[0].rstrip(";").upper()
+ if sql != "ROLLBACK":
+ context.error = InterfaceError("in failed transaction block")
+
+ values = data[:-1].split(b" ")
+ try:
+ row_count = int(values[-1])
+ if context.row_count == -1:
+ context.row_count = row_count
+ else:
+ context.row_count += row_count
+ except ValueError:
+ pass
+
+ def handle_DATA_ROW(self, data, context):
+ idx = 2
+ row = []
+ for func in context.input_funcs:
+ vlen = i_unpack(data, idx)[0]
+ idx += 4
+ if vlen == -1:
+ v = None
+ else:
+ v = func(str(data[idx : idx + vlen], encoding=self._client_encoding))
+ idx += vlen
+ row.append(v)
+ context.rows.append(row)
+
+ def handle_messages(self, context):
+ code = None
+
+ while code != READY_FOR_QUERY:
+ code, data_len = ci_unpack(_read(self._sock, 5))
+
+ self.message_types[code](_read(self._sock, data_len - 4), context)
+
+ if context.error is not None:
+ raise context.error
+
+ def close_prepared_statement(self, statement_name_bin):
+ """https://www.postgresql.org/docs/current/protocol-message-formats.html"""
+ self._send_message(CLOSE, STATEMENT + statement_name_bin)
+ _write(self._sock, FLUSH_MSG)
+ _write(self._sock, SYNC_MSG)
+ _flush(self._sock)
+ context = Context(None)
+ self.handle_messages(context)
+ self._statement_nums.remove(statement_name_bin)
+
+ def handle_NOTICE_RESPONSE(self, data, context):
+ """https://www.postgresql.org/docs/current/protocol-message-formats.html"""
+ self.notices.append({s[0:1]: s[1:] for s in data.split(NULL_BYTE)})
+
+ def handle_PARAMETER_STATUS(self, data, context):
+ pos = data.find(NULL_BYTE)
+ key, value = data[:pos], data[pos + 1 : -1]
+ self.parameter_statuses.append((key, value))
+ if key == b"client_encoding":
+ encoding = value.decode("ascii").lower()
+ self._client_encoding = PG_PY_ENCODINGS.get(encoding, encoding)
+
+ elif key == b"integer_datetimes":
+ if value == b"on":
+ pass
+
+ else:
+ pass
+
+ elif key == b"server_version":
+ pass
+
+
+class Context:
+ def __init__(self, statement, stream=None, columns=None, input_funcs=None):
+ self.statement = statement
+ self.rows = None if columns is None else []
+ self.row_count = -1
+ self.columns = columns
+ self.stream = stream
+ self.input_funcs = [] if input_funcs is None else input_funcs
+ self.error = None
diff --git a/contrib/python/pg8000/pg8000/dbapi.py b/contrib/python/pg8000/pg8000/dbapi.py
new file mode 100644
index 00000000000..91abb684868
--- /dev/null
+++ b/contrib/python/pg8000/pg8000/dbapi.py
@@ -0,0 +1,976 @@
+from datetime import (
+ date as Date,
+ datetime as Datetime,
+ time as Time,
+)
+from itertools import count, islice
+from time import localtime
+from warnings import warn
+
+from pg8000.converters import (
+ BIGINT,
+ BOOLEAN,
+ BOOLEAN_ARRAY,
+ BYTES,
+ CHAR,
+ CHAR_ARRAY,
+ DATE,
+ FLOAT,
+ FLOAT_ARRAY,
+ INET,
+ INT2VECTOR,
+ INTEGER,
+ INTEGER_ARRAY,
+ INTERVAL,
+ JSON,
+ JSONB,
+ MACADDR,
+ NAME,
+ NAME_ARRAY,
+ NULLTYPE,
+ NUMERIC,
+ NUMERIC_ARRAY,
+ OID,
+ PGInterval,
+ PY_PG,
+ STRING,
+ TEXT,
+ TEXT_ARRAY,
+ TIME,
+ TIMESTAMP,
+ TIMESTAMPTZ,
+ UNKNOWN,
+ UUID_TYPE,
+ VARCHAR,
+ VARCHAR_ARRAY,
+ XID,
+)
+from pg8000.core import (
+ Context,
+ CoreConnection,
+ IN_FAILED_TRANSACTION,
+ IN_TRANSACTION,
+ ver,
+)
+from pg8000.exceptions import DatabaseError, Error, InterfaceError
+from pg8000.types import Range
+
+
+__version__ = ver
+
+# Copyright (c) 2007-2009, Mathieu Fenniak
+# Copyright (c) The Contributors
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+__author__ = "Mathieu Fenniak"
+
+
+ROWID = OID
+
+apilevel = "2.0"
+"""The DBAPI level supported, currently "2.0".
+
+This property is part of the `DBAPI 2.0 specification
+<http://www.python.org/dev/peps/pep-0249/>`_.
+"""
+
+threadsafety = 1
+"""Integer constant stating the level of thread safety the DBAPI interface
+supports. This DBAPI module supports sharing of the module only. Connections
+and cursors my not be shared between threads. This gives pg8000 a threadsafety
+value of 1.
+
+This property is part of the `DBAPI 2.0 specification
+<http://www.python.org/dev/peps/pep-0249/>`_.
+"""
+
+paramstyle = "format"
+
+
+BINARY = bytes
+
+
+def PgDate(year, month, day):
+ """Construct an object holding a date value.
+
+ This function is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+
+ :rtype: :class:`datetime.date`
+ """
+ return Date(year, month, day)
+
+
+def PgTime(hour, minute, second):
+ """Construct an object holding a time value.
+
+ This function is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+
+ :rtype: :class:`datetime.time`
+ """
+ return Time(hour, minute, second)
+
+
+def Timestamp(year, month, day, hour, minute, second):
+ """Construct an object holding a timestamp value.
+
+ This function is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+
+ :rtype: :class:`datetime.datetime`
+ """
+ return Datetime(year, month, day, hour, minute, second)
+
+
+def DateFromTicks(ticks):
+ """Construct an object holding a date value from the given ticks value
+ (number of seconds since the epoch).
+
+ This function is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+
+ :rtype: :class:`datetime.date`
+ """
+ return Date(*localtime(ticks)[:3])
+
+
+def TimeFromTicks(ticks):
+ """Construct an object holding a time value from the given ticks value
+ (number of seconds since the epoch).
+
+ This function is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+
+ :rtype: :class:`datetime.time`
+ """
+ return Time(*localtime(ticks)[3:6])
+
+
+def TimestampFromTicks(ticks):
+ """Construct an object holding a timestamp value from the given ticks value
+ (number of seconds since the epoch).
+
+ This function is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+
+ :rtype: :class:`datetime.datetime`
+ """
+ return Timestamp(*localtime(ticks)[:6])
+
+
+def Binary(value):
+ """Construct an object holding binary data.
+
+ This function is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+
+ """
+ return value
+
+
+def connect(
+ user,
+ host="localhost",
+ database=None,
+ port=5432,
+ password=None,
+ source_address=None,
+ unix_sock=None,
+ ssl_context=None,
+ timeout=None,
+ tcp_keepalive=True,
+ application_name=None,
+ replication=None,
+ sock=None,
+):
+ return Connection(
+ user,
+ host=host,
+ database=database,
+ port=port,
+ password=password,
+ source_address=source_address,
+ unix_sock=unix_sock,
+ ssl_context=ssl_context,
+ timeout=timeout,
+ tcp_keepalive=tcp_keepalive,
+ application_name=application_name,
+ replication=replication,
+ sock=sock,
+ )
+
+
+apilevel = "2.0"
+"""The DBAPI level supported, currently "2.0".
+
+This property is part of the `DBAPI 2.0 specification
+<http://www.python.org/dev/peps/pep-0249/>`_.
+"""
+
+threadsafety = 1
+"""Integer constant stating the level of thread safety the DBAPI interface
+supports. This DBAPI module supports sharing of the module only. Connections
+and cursors my not be shared between threads. This gives pg8000 a threadsafety
+value of 1.
+
+This property is part of the `DBAPI 2.0 specification
+<http://www.python.org/dev/peps/pep-0249/>`_.
+"""
+
+paramstyle = "format"
+
+
+def convert_paramstyle(style, query, args):
+ # I don't see any way to avoid scanning the query string char by char,
+ # so we might as well take that careful approach and create a
+ # state-based scanner. We'll use int variables for the state.
+ OUTSIDE = 0 # outside quoted string
+ INSIDE_SQ = 1 # inside single-quote string '...'
+ INSIDE_QI = 2 # inside quoted identifier "..."
+ INSIDE_ES = 3 # inside escaped single-quote string, E'...'
+ INSIDE_PN = 4 # inside parameter name eg. :name
+ INSIDE_CO = 5 # inside inline comment eg. --
+ INSIDE_DQ = 6 # inside escaped dollar-quote string, $$...$$
+
+ in_quote_escape = False
+ in_param_escape = False
+ placeholders = []
+ output_query = []
+ param_idx = map(lambda x: "$" + str(x), count(1))
+ state = OUTSIDE
+ prev_c = None
+ for i, c in enumerate(query):
+ next_c = query[i + 1] if i + 1 < len(query) else None
+
+ if state == OUTSIDE:
+ if c == "'":
+ output_query.append(c)
+ if prev_c == "E":
+ state = INSIDE_ES
+ else:
+ state = INSIDE_SQ
+ elif c == '"':
+ output_query.append(c)
+ state = INSIDE_QI
+ elif c == "-":
+ output_query.append(c)
+ if prev_c == "-":
+ state = INSIDE_CO
+ elif c == "$":
+ output_query.append(c)
+ if prev_c == "$":
+ state = INSIDE_DQ
+ elif style == "qmark" and c == "?":
+ output_query.append(next(param_idx))
+ elif (
+ style == "numeric" and c == ":" and next_c not in ":=" and prev_c != ":"
+ ):
+ # Treat : as beginning of parameter name if and only
+ # if it's the only : around
+ # Needed to properly process type conversions
+ # i.e. sum(x)::float
+ output_query.append("$")
+ elif style == "named" and c == ":" and next_c not in ":=" and prev_c != ":":
+ # Same logic for : as in numeric parameters
+ state = INSIDE_PN
+ placeholders.append("")
+ elif style == "pyformat" and c == "%" and next_c == "(":
+ state = INSIDE_PN
+ placeholders.append("")
+ elif style in ("format", "pyformat") and c == "%":
+ style = "format"
+ if in_param_escape:
+ in_param_escape = False
+ output_query.append(c)
+ else:
+ if next_c == "%":
+ in_param_escape = True
+ elif next_c == "s":
+ state = INSIDE_PN
+ output_query.append(next(param_idx))
+ else:
+ raise InterfaceError(
+ "Only %s and %% are supported in the query."
+ )
+ else:
+ output_query.append(c)
+
+ elif state == INSIDE_SQ:
+ if c == "'":
+ if in_quote_escape:
+ in_quote_escape = False
+ else:
+ if next_c == "'":
+ in_quote_escape = True
+ else:
+ state = OUTSIDE
+ output_query.append(c)
+
+ elif state == INSIDE_QI:
+ if c == '"':
+ state = OUTSIDE
+ output_query.append(c)
+
+ elif state == INSIDE_ES:
+ if c == "'" and prev_c != "\\":
+ # check for escaped single-quote
+ state = OUTSIDE
+ output_query.append(c)
+
+ elif state == INSIDE_DQ:
+ if c == "$" and prev_c == "$":
+ state = OUTSIDE
+ output_query.append(c)
+
+ elif state == INSIDE_PN:
+ if style == "named":
+ placeholders[-1] += c
+ if next_c is None or (not next_c.isalnum() and next_c != "_"):
+ state = OUTSIDE
+ try:
+ pidx = placeholders.index(placeholders[-1], 0, -1)
+ output_query.append("$" + str(pidx + 1))
+ del placeholders[-1]
+ except ValueError:
+ output_query.append("$" + str(len(placeholders)))
+ elif style == "pyformat":
+ if prev_c == ")" and c == "s":
+ state = OUTSIDE
+ try:
+ pidx = placeholders.index(placeholders[-1], 0, -1)
+ output_query.append("$" + str(pidx + 1))
+ del placeholders[-1]
+ except ValueError:
+ output_query.append("$" + str(len(placeholders)))
+ elif c in "()":
+ pass
+ else:
+ placeholders[-1] += c
+ elif style == "format":
+ state = OUTSIDE
+
+ elif state == INSIDE_CO:
+ output_query.append(c)
+ if c == "\n":
+ state = OUTSIDE
+
+ prev_c = c
+
+ if style in ("numeric", "qmark", "format"):
+ vals = args
+ else:
+ vals = tuple(args[p] for p in placeholders)
+
+ return "".join(output_query), vals
+
+
+class Cursor:
+ def __init__(self, connection):
+ self._c = connection
+ self.arraysize = 1
+
+ self._context = None
+ self._row_iter = None
+
+ self._input_oids = ()
+
+ @property
+ def connection(self):
+ warn("DB-API extension cursor.connection used", stacklevel=3)
+ return self._c
+
+ @property
+ def rowcount(self):
+ context = self._context
+ if context is None:
+ return -1
+
+ return context.row_count
+
+ @property
+ def description(self):
+ context = self._context
+ if context is None:
+ return None
+
+ row_desc = context.columns
+ if row_desc is None:
+ return None
+ if len(row_desc) == 0:
+ return None
+ columns = []
+ for col in row_desc:
+ columns.append((col["name"], col["type_oid"], None, None, None, None, None))
+ return columns
+
+ ##
+ # Executes a database operation. Parameters may be provided as a sequence
+ # or mapping and will be bound to variables in the operation.
+ # <p>
+ # Stability: Part of the DBAPI 2.0 specification.
+ def execute(self, operation, args=(), stream=None):
+ """Executes a database operation. Parameters may be provided as a
+ sequence, or as a mapping, depending upon the value of
+ :data:`pg8000.paramstyle`.
+
+ This method is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+
+ :param operation:
+ The SQL statement to execute.
+
+ :param args:
+ If :data:`paramstyle` is ``qmark``, ``numeric``, or ``format``,
+ this argument should be an array of parameters to bind into the
+ statement. If :data:`paramstyle` is ``named``, the argument should
+ be a dict mapping of parameters. If the :data:`paramstyle` is
+ ``pyformat``, the argument value may be either an array or a
+ mapping.
+
+ :param stream: This is a pg8000 extension for use with the PostgreSQL
+ `COPY
+ <http://www.postgresql.org/docs/current/static/sql-copy.html>`_
+ command. For a COPY FROM the parameter must be a readable file-like
+ object, and for COPY TO it must be writable.
+
+ .. versionadded:: 1.9.11
+ """
+ try:
+ if not self._c._in_transaction and not self._c.autocommit:
+ self._c.execute_simple("begin transaction")
+
+ if len(args) == 0 and stream is None:
+ self._context = self._c.execute_simple(operation)
+ else:
+ statement, vals = convert_paramstyle(paramstyle, operation, args)
+ self._context = self._c.execute_unnamed(
+ statement, vals=vals, oids=self._input_oids, stream=stream
+ )
+
+ if self._context.rows is None:
+ self._row_iter = None
+ else:
+ self._row_iter = iter(self._context.rows)
+ self._input_oids = ()
+ except AttributeError as e:
+ if self._c is None:
+ raise InterfaceError("Cursor closed")
+ elif self._c._sock is None:
+ raise InterfaceError("connection is closed")
+ else:
+ raise e
+
+ self.input_types = []
+
+ def executemany(self, operation, param_sets):
+ """Prepare a database operation, and then execute it against all
+ parameter sequences or mappings provided.
+
+ This method is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+
+ :param operation:
+ The SQL statement to execute
+ :param parameter_sets:
+ A sequence of parameters to execute the statement with. The values
+ in the sequence should be sequences or mappings of parameters, the
+ same as the args argument of the :meth:`execute` method.
+ """
+ rowcounts = []
+ input_oids = self._input_oids
+ for parameters in param_sets:
+ self._input_oids = input_oids
+ self.execute(operation, parameters)
+ rowcounts.append(self._context.row_count)
+
+ if len(rowcounts) == 0:
+ self._context = Context(None)
+ elif -1 in rowcounts:
+ self._context.row_count = -1
+ else:
+ self._context.row_count = sum(rowcounts)
+
+ def callproc(self, procname, parameters=None):
+ args = [] if parameters is None else parameters
+ operation = f"CALL {procname}(" + ", ".join(["%s" for _ in args]) + ")"
+
+ try:
+ statement, vals = convert_paramstyle("format", operation, args)
+
+ self._context = self._c.execute_unnamed(statement, vals=vals)
+
+ if self._context.rows is None:
+ self._row_iter = None
+ else:
+ self._row_iter = iter(self._context.rows)
+
+ except AttributeError as e:
+ if self._c is None:
+ raise InterfaceError("Cursor closed")
+ elif self._c._sock is None:
+ raise InterfaceError("connection is closed")
+ else:
+ raise e
+
+ def fetchone(self):
+ """Fetch the next row of a query result set.
+
+ This method is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+
+ :returns:
+ A row as a sequence of field values, or ``None`` if no more rows
+ are available.
+ """
+ try:
+ return next(self)
+ except StopIteration:
+ return None
+ except TypeError:
+ raise ProgrammingError("attempting to use unexecuted cursor")
+
+ def __iter__(self):
+ """A cursor object is iterable to retrieve the rows from a query.
+
+ This is a DBAPI 2.0 extension.
+ """
+ return self
+
+ def __next__(self):
+ try:
+ return next(self._row_iter)
+ except AttributeError:
+ if self._context is None:
+ raise ProgrammingError("A query hasn't been issued.")
+ else:
+ raise
+ except StopIteration as e:
+ if self._context is None:
+ raise ProgrammingError("A query hasn't been issued.")
+ elif len(self._context.columns) == 0:
+ raise ProgrammingError("no result set")
+ else:
+ raise e
+
+ def fetchmany(self, num=None):
+ """Fetches the next set of rows of a query result.
+
+ This method is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+
+ :param size:
+
+ The number of rows to fetch when called. If not provided, the
+ :attr:`arraysize` attribute value is used instead.
+
+ :returns:
+
+ A sequence, each entry of which is a sequence of field values
+ making up a row. If no more rows are available, an empty sequence
+ will be returned.
+ """
+ try:
+ return tuple(islice(self, self.arraysize if num is None else num))
+ except TypeError:
+ raise ProgrammingError("attempting to use unexecuted cursor")
+
+ def fetchall(self):
+ """Fetches all remaining rows of a query result.
+
+ This method is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+
+ :returns:
+
+ A sequence, each entry of which is a sequence of field values
+ making up a row.
+ """
+ try:
+ return tuple(self)
+ except TypeError:
+ raise ProgrammingError("attempting to use unexecuted cursor")
+
+ def close(self):
+ """Closes the cursor.
+
+ This method is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+ self._c = None
+
+ def setinputsizes(self, *sizes):
+ """This method is part of the `DBAPI 2.0 specification"""
+ oids = []
+ for size in sizes:
+ if isinstance(size, int):
+ oid = size
+ else:
+ try:
+ oid = PY_PG[size]
+ except KeyError:
+ oid = UNKNOWN
+ oids.append(oid)
+
+ self._input_oids = oids
+
+ def setoutputsize(self, size, column=None):
+ """This method is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_, however, it is not
+ implemented by pg8000.
+ """
+ pass
+
+
+class Connection(CoreConnection):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.autocommit = False
+
+ # DBAPI Extension: supply exceptions as attributes on the connection
+ Warning = property(lambda self: self._getError(Warning))
+ Error = property(lambda self: self._getError(Error))
+ InterfaceError = property(lambda self: self._getError(InterfaceError))
+ DatabaseError = property(lambda self: self._getError(DatabaseError))
+ OperationalError = property(lambda self: self._getError(OperationalError))
+ IntegrityError = property(lambda self: self._getError(IntegrityError))
+ InternalError = property(lambda self: self._getError(InternalError))
+ ProgrammingError = property(lambda self: self._getError(ProgrammingError))
+ NotSupportedError = property(lambda self: self._getError(NotSupportedError))
+
+ def _getError(self, error):
+ warn(f"DB-API extension connection.{error.__name__} used", stacklevel=3)
+ return error
+
+ @property
+ def _in_transaction(self):
+ return self._transaction_status in (IN_TRANSACTION, IN_FAILED_TRANSACTION)
+
+ def cursor(self):
+ """Creates a :class:`Cursor` object bound to this
+ connection.
+
+ This function is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+ return Cursor(self)
+
+ def commit(self):
+ """Commits the current database transaction.
+
+ This function is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+ self.execute_unnamed("commit")
+
+ def rollback(self):
+ """Rolls back the current database transaction.
+
+ This function is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+ if not self._in_transaction:
+ return
+ self.execute_unnamed("rollback")
+
+ def xid(self, format_id, global_transaction_id, branch_qualifier):
+ """Create a Transaction IDs (only global_transaction_id is used in pg)
+ format_id and branch_qualifier are not used in postgres
+ global_transaction_id may be any string identifier supported by
+ postgres returns a tuple
+ (format_id, global_transaction_id, branch_qualifier)"""
+ return (format_id, global_transaction_id, branch_qualifier)
+
+ def tpc_begin(self, xid):
+ """Begins a TPC transaction with the given transaction ID xid.
+
+ This method should be called outside of a transaction (i.e. nothing may
+ have executed since the last .commit() or .rollback()).
+
+ Furthermore, it is an error to call .commit() or .rollback() within the
+ TPC transaction. A ProgrammingError is raised, if the application calls
+ .commit() or .rollback() during an active TPC transaction.
+
+ This function is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+ self._xid = xid
+ if self.autocommit:
+ self.execute_unnamed("begin transaction")
+
+ def tpc_prepare(self):
+ """Performs the first phase of a transaction started with .tpc_begin().
+ A ProgrammingError is be raised if this method is called outside of a
+ TPC transaction.
+
+ After calling .tpc_prepare(), no statements can be executed until
+ .tpc_commit() or .tpc_rollback() have been called.
+
+ This function is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+ self.execute_unnamed("PREPARE TRANSACTION '%s';" % (self._xid[1],))
+
+ def tpc_commit(self, xid=None):
+ """When called with no arguments, .tpc_commit() commits a TPC
+ transaction previously prepared with .tpc_prepare().
+
+ If .tpc_commit() is called prior to .tpc_prepare(), a single phase
+ commit is performed. A transaction manager may choose to do this if
+ only a single resource is participating in the global transaction.
+
+ When called with a transaction ID xid, the database commits the given
+ transaction. If an invalid transaction ID is provided, a
+ ProgrammingError will be raised. This form should be called outside of
+ a transaction, and is intended for use in recovery.
+
+ On return, the TPC transaction is ended.
+
+ This function is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+ if xid is None:
+ xid = self._xid
+
+ if xid is None:
+ raise ProgrammingError("Cannot tpc_commit() without a TPC transaction!")
+
+ try:
+ previous_autocommit_mode = self.autocommit
+ self.autocommit = True
+ if xid in self.tpc_recover():
+ self.execute_unnamed("COMMIT PREPARED '%s';" % (xid[1],))
+ else:
+ # a single-phase commit
+ self.commit()
+ finally:
+ self.autocommit = previous_autocommit_mode
+ self._xid = None
+
+ def tpc_rollback(self, xid=None):
+ """When called with no arguments, .tpc_rollback() rolls back a TPC
+ transaction. It may be called before or after .tpc_prepare().
+
+ When called with a transaction ID xid, it rolls back the given
+ transaction. If an invalid transaction ID is provided, a
+ ProgrammingError is raised. This form should be called outside of a
+ transaction, and is intended for use in recovery.
+
+ On return, the TPC transaction is ended.
+
+ This function is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+ if xid is None:
+ xid = self._xid
+
+ if xid is None:
+ raise ProgrammingError(
+ "Cannot tpc_rollback() without a TPC prepared transaction!"
+ )
+
+ try:
+ previous_autocommit_mode = self.autocommit
+ self.autocommit = True
+ if xid in self.tpc_recover():
+ # a two-phase rollback
+ self.execute_unnamed("ROLLBACK PREPARED '%s';" % (xid[1],))
+ else:
+ # a single-phase rollback
+ self.rollback()
+ finally:
+ self.autocommit = previous_autocommit_mode
+ self._xid = None
+
+ def tpc_recover(self):
+ """Returns a list of pending transaction IDs suitable for use with
+ .tpc_commit(xid) or .tpc_rollback(xid).
+
+ This function is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+ try:
+ previous_autocommit_mode = self.autocommit
+ self.autocommit = True
+ curs = self.cursor()
+ curs.execute("select gid FROM pg_prepared_xacts")
+ return [self.xid(0, row[0], "") for row in curs.fetchall()]
+ finally:
+ self.autocommit = previous_autocommit_mode
+
+
+class Warning(Exception):
+ """Generic exception raised for important database warnings like data
+ truncations. This exception is not currently used by pg8000.
+
+ This exception is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+
+ pass
+
+
+class DataError(DatabaseError):
+ """Generic exception raised for errors that are due to problems with the
+ processed data. This exception is not currently raised by pg8000.
+
+ This exception is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+
+ pass
+
+
+class OperationalError(DatabaseError):
+ """
+ Generic exception raised for errors that are related to the database's
+ operation and not necessarily under the control of the programmer. This
+ exception is currently never raised by pg8000.
+
+ This exception is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+
+ pass
+
+
+class IntegrityError(DatabaseError):
+ """
+ Generic exception raised when the relational integrity of the database is
+ affected. This exception is not currently raised by pg8000.
+
+ This exception is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+
+ pass
+
+
+class InternalError(DatabaseError):
+ """Generic exception raised when the database encounters an internal error.
+ This is currently only raised when unexpected state occurs in the pg8000
+ interface itself, and is typically the result of a interface bug.
+
+ This exception is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+
+ pass
+
+
+class ProgrammingError(DatabaseError):
+ """Generic exception raised for programming errors. For example, this
+ exception is raised if more parameter fields are in a query string than
+ there are available parameters.
+
+ This exception is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+
+ pass
+
+
+class NotSupportedError(DatabaseError):
+ """Generic exception raised in case a method or database API was used which
+ is not supported by the database.
+
+ This exception is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+
+ pass
+
+
+class ArrayContentNotSupportedError(NotSupportedError):
+ """
+ Raised when attempting to transmit an array where the base type is not
+ supported for binary data transfer by the interface.
+ """
+
+ pass
+
+
+__all__ = [
+ "BIGINT",
+ "BINARY",
+ "BOOLEAN",
+ "BOOLEAN_ARRAY",
+ "BYTES",
+ "Binary",
+ "CHAR",
+ "CHAR_ARRAY",
+ "Connection",
+ "Cursor",
+ "DATE",
+ "DataError",
+ "DatabaseError",
+ "Date",
+ "DateFromTicks",
+ "Error",
+ "FLOAT",
+ "FLOAT_ARRAY",
+ "INET",
+ "INT2VECTOR",
+ "INTEGER",
+ "INTEGER_ARRAY",
+ "INTERVAL",
+ "IntegrityError",
+ "InterfaceError",
+ "InternalError",
+ "JSON",
+ "JSONB",
+ "MACADDR",
+ "NAME",
+ "NAME_ARRAY",
+ "NULLTYPE",
+ "NUMERIC",
+ "NUMERIC_ARRAY",
+ "NotSupportedError",
+ "OID",
+ "OperationalError",
+ "PGInterval",
+ "ProgrammingError",
+ "ROWID",
+ "Range",
+ "STRING",
+ "TEXT",
+ "TEXT_ARRAY",
+ "TIME",
+ "TIMESTAMP",
+ "TIMESTAMPTZ",
+ "Time",
+ "TimeFromTicks",
+ "Timestamp",
+ "TimestampFromTicks",
+ "UNKNOWN",
+ "UUID_TYPE",
+ "VARCHAR",
+ "VARCHAR_ARRAY",
+ "Warning",
+ "XID",
+ "connect",
+]
diff --git a/contrib/python/pg8000/pg8000/exceptions.py b/contrib/python/pg8000/pg8000/exceptions.py
new file mode 100644
index 00000000000..47f75cb54a9
--- /dev/null
+++ b/contrib/python/pg8000/pg8000/exceptions.py
@@ -0,0 +1,32 @@
+class Error(Exception):
+ """Generic exception that is the base exception of all other error
+ exceptions.
+
+ This exception is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+
+ pass
+
+
+class InterfaceError(Error):
+ """Generic exception raised for errors that are related to the database
+ interface rather than the database itself. For example, if the interface
+ attempts to use an SSL connection but the server refuses, an InterfaceError
+ will be raised.
+
+ This exception is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+
+ pass
+
+
+class DatabaseError(Error):
+ """Generic exception raised for errors that are related to the database.
+
+ This exception is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+
+ pass
diff --git a/contrib/python/pg8000/pg8000/legacy.py b/contrib/python/pg8000/pg8000/legacy.py
new file mode 100644
index 00000000000..6dddccd88d0
--- /dev/null
+++ b/contrib/python/pg8000/pg8000/legacy.py
@@ -0,0 +1,822 @@
+from datetime import date as Date, time as Time
+from itertools import islice
+from warnings import warn
+
+import pg8000
+from pg8000.converters import (
+ BIGINT,
+ BOOLEAN,
+ BOOLEAN_ARRAY,
+ BYTES,
+ CHAR,
+ CHAR_ARRAY,
+ DATE,
+ FLOAT,
+ FLOAT_ARRAY,
+ INET,
+ INT2VECTOR,
+ INTEGER,
+ INTEGER_ARRAY,
+ INTERVAL,
+ JSON,
+ JSONB,
+ MACADDR,
+ NAME,
+ NAME_ARRAY,
+ NULLTYPE,
+ NUMERIC,
+ NUMERIC_ARRAY,
+ OID,
+ PGInterval,
+ PY_PG,
+ Range,
+ STRING,
+ TEXT,
+ TEXT_ARRAY,
+ TIME,
+ TIMESTAMP,
+ TIMESTAMPTZ,
+ UNKNOWN,
+ UUID_TYPE,
+ VARCHAR,
+ VARCHAR_ARRAY,
+ XID,
+ interval_in as timedelta_in,
+ make_params,
+ pg_interval_in as pginterval_in,
+ pg_interval_out as pginterval_out,
+)
+from pg8000.core import (
+ Context,
+ CoreConnection,
+ IN_FAILED_TRANSACTION,
+ IN_TRANSACTION,
+ ver,
+)
+from pg8000.dbapi import (
+ BINARY,
+ Binary,
+ DataError,
+ DateFromTicks,
+ IntegrityError,
+ InternalError,
+ NotSupportedError,
+ OperationalError,
+ ProgrammingError,
+ TimeFromTicks,
+ Timestamp,
+ TimestampFromTicks,
+ Warning,
+ convert_paramstyle,
+)
+from pg8000.exceptions import DatabaseError, Error, InterfaceError
+
+__version__ = ver
+
+# Copyright (c) 2007-2009, Mathieu Fenniak
+# Copyright (c) The Contributors
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+__author__ = "Mathieu Fenniak"
+
+
+BIGINTEGER = BIGINT
+DATETIME = TIMESTAMP
+NUMBER = DECIMAL = NUMERIC
+DECIMAL_ARRAY = NUMERIC_ARRAY
+ROWID = OID
+TIMEDELTA = INTERVAL
+
+
+def connect(
+ user,
+ host="localhost",
+ database=None,
+ port=5432,
+ password=None,
+ source_address=None,
+ unix_sock=None,
+ ssl_context=None,
+ timeout=None,
+ tcp_keepalive=True,
+ application_name=None,
+ replication=None,
+):
+ return Connection(
+ user,
+ host=host,
+ database=database,
+ port=port,
+ password=password,
+ source_address=source_address,
+ unix_sock=unix_sock,
+ ssl_context=ssl_context,
+ timeout=timeout,
+ tcp_keepalive=tcp_keepalive,
+ application_name=application_name,
+ replication=replication,
+ )
+
+
+apilevel = "2.0"
+"""The DBAPI level supported, currently "2.0".
+
+This property is part of the `DBAPI 2.0 specification
+<http://www.python.org/dev/peps/pep-0249/>`_.
+"""
+
+threadsafety = 1
+"""Integer constant stating the level of thread safety the DBAPI interface
+supports. This DBAPI module supports sharing of the module only. Connections
+and cursors my not be shared between threads. This gives pg8000 a threadsafety
+value of 1.
+
+This property is part of the `DBAPI 2.0 specification
+<http://www.python.org/dev/peps/pep-0249/>`_.
+"""
+
+paramstyle = "format"
+
+
+class Cursor:
+ def __init__(self, connection, paramstyle=None):
+ self._c = connection
+ self.arraysize = 1
+ if paramstyle is None:
+ self.paramstyle = pg8000.paramstyle
+ else:
+ self.paramstyle = paramstyle
+
+ self._context = None
+ self._row_iter = None
+
+ self._input_oids = ()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.close()
+
+ @property
+ def connection(self):
+ warn("DB-API extension cursor.connection used", stacklevel=3)
+ return self._c
+
+ @property
+ def rowcount(self):
+ context = self._context
+ if context is None:
+ return -1
+
+ return context.row_count
+
+ description = property(lambda self: self._getDescription())
+
+ def _getDescription(self):
+ context = self._context
+ if context is None:
+ return None
+ row_desc = context.columns
+ if row_desc is None:
+ return None
+ if len(row_desc) == 0:
+ return None
+ columns = []
+ for col in row_desc:
+ columns.append((col["name"], col["type_oid"], None, None, None, None, None))
+ return columns
+
+ ##
+ # Executes a database operation. Parameters may be provided as a sequence
+ # or mapping and will be bound to variables in the operation.
+ # <p>
+ # Stability: Part of the DBAPI 2.0 specification.
+ def execute(self, operation, args=(), stream=None):
+ """Executes a database operation. Parameters may be provided as a
+ sequence, or as a mapping, depending upon the value of
+ :data:`pg8000.paramstyle`.
+
+ This method is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+
+ :param operation:
+ The SQL statement to execute.
+
+ :param args:
+ If :data:`paramstyle` is ``qmark``, ``numeric``, or ``format``,
+ this argument should be an array of parameters to bind into the
+ statement. If :data:`paramstyle` is ``named``, the argument should
+ be a dict mapping of parameters. If the :data:`paramstyle` is
+ ``pyformat``, the argument value may be either an array or a
+ mapping.
+
+ :param stream: This is a pg8000 extension for use with the PostgreSQL
+ `COPY
+ <http://www.postgresql.org/docs/current/static/sql-copy.html>`_
+ command. For a COPY FROM the parameter must be a readable file-like
+ object, and for COPY TO it must be writable.
+
+ .. versionadded:: 1.9.11
+ """
+ try:
+ if not self._c._in_transaction and not self._c.autocommit:
+ self._c.execute_simple("begin transaction")
+
+ if len(args) == 0 and stream is None:
+ self._context = self._c.execute_simple(operation)
+ else:
+ statement, vals = convert_paramstyle(self.paramstyle, operation, args)
+ self._context = self._c.execute_unnamed(
+ statement, vals=vals, oids=self._input_oids, stream=stream
+ )
+
+ rows = [] if self._context.rows is None else self._context.rows
+ self._row_iter = iter(rows)
+
+ self._input_oids = ()
+ except AttributeError as e:
+ if self._c is None:
+ raise InterfaceError("Cursor closed")
+ elif self._c._sock is None:
+ raise InterfaceError("connection is closed")
+ else:
+ raise e
+ except DatabaseError as e:
+ msg = e.args[0]
+ if isinstance(msg, dict):
+ response_code = msg["C"]
+
+ if response_code == "28000":
+ cls = InterfaceError
+ elif response_code == "23505":
+ cls = IntegrityError
+ else:
+ cls = ProgrammingError
+
+ raise cls(msg)
+ else:
+ raise ProgrammingError(msg)
+
+ self.input_types = []
+ return self
+
+ def executemany(self, operation, param_sets):
+ """Prepare a database operation, and then execute it against all
+ parameter sequences or mappings provided.
+
+ This method is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+
+ :param operation:
+ The SQL statement to execute
+ :param parameter_sets:
+ A sequence of parameters to execute the statement with. The values
+ in the sequence should be sequences or mappings of parameters, the
+ same as the args argument of the :meth:`execute` method.
+ """
+ rowcounts = []
+ input_oids = self._input_oids
+ for parameters in param_sets:
+ self._input_oids = input_oids
+ self.execute(operation, parameters)
+ rowcounts.append(self._context.row_count)
+
+ if len(rowcounts) == 0:
+ self._context = Context(None)
+ elif -1 in rowcounts:
+ self._context.row_count = -1
+ else:
+ self._context.row_count = sum(rowcounts)
+
+ return self
+
+ def fetchone(self):
+ """Fetch the next row of a query result set.
+
+ This method is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+
+ :returns:
+ A row as a sequence of field values, or ``None`` if no more rows
+ are available.
+ """
+ try:
+ return next(self)
+ except StopIteration:
+ return None
+ except TypeError:
+ raise ProgrammingError("attempting to use unexecuted cursor")
+ except AttributeError:
+ raise ProgrammingError("attempting to use unexecuted cursor")
+
+ def fetchmany(self, num=None):
+ """Fetches the next set of rows of a query result.
+
+ This method is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+
+ :param size:
+
+ The number of rows to fetch when called. If not provided, the
+ :attr:`arraysize` attribute value is used instead.
+
+ :returns:
+
+ A sequence, each entry of which is a sequence of field values
+ making up a row. If no more rows are available, an empty sequence
+ will be returned.
+ """
+ try:
+ return tuple(islice(self, self.arraysize if num is None else num))
+ except TypeError:
+ raise ProgrammingError("attempting to use unexecuted cursor")
+
+ def fetchall(self):
+ """Fetches all remaining rows of a query result.
+
+ This method is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+
+ :returns:
+
+ A sequence, each entry of which is a sequence of field values
+ making up a row.
+ """
+ try:
+ return tuple(self)
+ except TypeError:
+ raise ProgrammingError("attempting to use unexecuted cursor")
+
+ def close(self):
+ """Closes the cursor.
+
+ This method is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+ self._c = None
+
+ def __iter__(self):
+ """A cursor object is iterable to retrieve the rows from a query.
+
+ This is a DBAPI 2.0 extension.
+ """
+ return self
+
+ def setinputsizes(self, *sizes):
+ """This method is part of the `DBAPI 2.0 specification"""
+ oids = []
+ for size in sizes:
+ if isinstance(size, int):
+ oid = size
+ else:
+ try:
+ oid = PY_PG[size]
+ except KeyError:
+ oid = UNKNOWN
+ oids.append(oid)
+
+ self._input_oids = oids
+
+ def setoutputsize(self, size, column=None):
+ """This method is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_, however, it is not
+ implemented by pg8000.
+ """
+ pass
+
+ def __next__(self):
+ try:
+ return next(self._row_iter)
+ except AttributeError:
+ if self._context is None:
+ raise ProgrammingError("A query hasn't been issued.")
+ else:
+ raise
+ except StopIteration as e:
+ if self._context is None:
+ raise ProgrammingError("A query hasn't been issued.")
+ elif len(self._context.columns) == 0:
+ raise ProgrammingError("no result set")
+ else:
+ raise e
+
+
+class Connection(CoreConnection):
+ # DBAPI Extension: supply exceptions as attributes on the connection
+ Warning = property(lambda self: self._getError(Warning))
+ Error = property(lambda self: self._getError(Error))
+ InterfaceError = property(lambda self: self._getError(InterfaceError))
+ DatabaseError = property(lambda self: self._getError(DatabaseError))
+ OperationalError = property(lambda self: self._getError(OperationalError))
+ IntegrityError = property(lambda self: self._getError(IntegrityError))
+ InternalError = property(lambda self: self._getError(InternalError))
+ ProgrammingError = property(lambda self: self._getError(ProgrammingError))
+ NotSupportedError = property(lambda self: self._getError(NotSupportedError))
+
+ def __init__(self, *args, **kwargs):
+ try:
+ super().__init__(*args, **kwargs)
+ except DatabaseError as e:
+ msg = e.args[0]
+ if isinstance(msg, dict):
+ response_code = msg["C"]
+
+ if response_code == "28000":
+ cls = InterfaceError
+ elif response_code == "23505":
+ cls = IntegrityError
+ else:
+ cls = ProgrammingError
+
+ raise cls(msg)
+ else:
+ raise ProgrammingError(msg)
+
+ self._run_cursor = Cursor(self, paramstyle="named")
+ self.autocommit = False
+
+ def _getError(self, error):
+ warn("DB-API extension connection.%s used" % error.__name__, stacklevel=3)
+ return error
+
+ def cursor(self):
+ """Creates a :class:`Cursor` object bound to this
+ connection.
+
+ This function is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+ return Cursor(self)
+
+ @property
+ def description(self):
+ return self._run_cursor._getDescription()
+
+ @property
+ def _in_transaction(self):
+ return self._transaction_status in (IN_TRANSACTION, IN_FAILED_TRANSACTION)
+
+ def commit(self):
+ """Commits the current database transaction.
+
+ This function is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+ self.execute_unnamed("commit")
+
+ def rollback(self):
+ """Rolls back the current database transaction.
+
+ This function is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+ if not self._in_transaction:
+ return
+ self.execute_unnamed("rollback")
+
+ def run(self, sql, stream=None, **params):
+ self._run_cursor.execute(sql, params, stream=stream)
+ if self._run_cursor._context.rows is None:
+ return tuple()
+ else:
+ return tuple(self._run_cursor._context.rows)
+
+ def prepare(self, operation):
+ return PreparedStatement(self, operation)
+
+ def xid(self, format_id, global_transaction_id, branch_qualifier):
+ """Create a Transaction IDs (only global_transaction_id is used in pg)
+ format_id and branch_qualifier are not used in postgres
+ global_transaction_id may be any string identifier supported by
+ postgres returns a tuple
+ (format_id, global_transaction_id, branch_qualifier)"""
+ return (format_id, global_transaction_id, branch_qualifier)
+
+ def tpc_begin(self, xid):
+ """Begins a TPC transaction with the given transaction ID xid.
+
+ This method should be called outside of a transaction (i.e. nothing may
+ have executed since the last .commit() or .rollback()).
+
+ Furthermore, it is an error to call .commit() or .rollback() within the
+ TPC transaction. A ProgrammingError is raised, if the application calls
+ .commit() or .rollback() during an active TPC transaction.
+
+ This function is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+ self._xid = xid
+ if self.autocommit:
+ self.execute_unnamed("begin transaction")
+
+ def tpc_prepare(self):
+ """Performs the first phase of a transaction started with .tpc_begin().
+ A ProgrammingError is be raised if this method is called outside of a
+ TPC transaction.
+
+ After calling .tpc_prepare(), no statements can be executed until
+ .tpc_commit() or .tpc_rollback() have been called.
+
+ This function is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+ q = "PREPARE TRANSACTION '%s';" % (self._xid[1],)
+ self.execute_unnamed(q)
+
+ def tpc_commit(self, xid=None):
+ """When called with no arguments, .tpc_commit() commits a TPC
+ transaction previously prepared with .tpc_prepare().
+
+ If .tpc_commit() is called prior to .tpc_prepare(), a single phase
+ commit is performed. A transaction manager may choose to do this if
+ only a single resource is participating in the global transaction.
+
+ When called with a transaction ID xid, the database commits the given
+ transaction. If an invalid transaction ID is provided, a
+ ProgrammingError will be raised. This form should be called outside of
+ a transaction, and is intended for use in recovery.
+
+ On return, the TPC transaction is ended.
+
+ This function is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+ if xid is None:
+ xid = self._xid
+
+ if xid is None:
+ raise ProgrammingError("Cannot tpc_commit() without a TPC transaction!")
+
+ try:
+ previous_autocommit_mode = self.autocommit
+ self.autocommit = True
+ if xid in self.tpc_recover():
+ self.execute_unnamed("COMMIT PREPARED '%s';" % (xid[1],))
+ else:
+ # a single-phase commit
+ self.commit()
+ finally:
+ self.autocommit = previous_autocommit_mode
+ self._xid = None
+
+ def tpc_rollback(self, xid=None):
+ """When called with no arguments, .tpc_rollback() rolls back a TPC
+ transaction. It may be called before or after .tpc_prepare().
+
+ When called with a transaction ID xid, it rolls back the given
+ transaction. If an invalid transaction ID is provided, a
+ ProgrammingError is raised. This form should be called outside of a
+ transaction, and is intended for use in recovery.
+
+ On return, the TPC transaction is ended.
+
+ This function is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+ if xid is None:
+ xid = self._xid
+
+ if xid is None:
+ raise ProgrammingError(
+ "Cannot tpc_rollback() without a TPC prepared transaction!"
+ )
+
+ try:
+ previous_autocommit_mode = self.autocommit
+ self.autocommit = True
+ if xid in self.tpc_recover():
+ # a two-phase rollback
+ self.execute_unnamed("ROLLBACK PREPARED '%s';" % (xid[1],))
+ else:
+ # a single-phase rollback
+ self.rollback()
+ finally:
+ self.autocommit = previous_autocommit_mode
+ self._xid = None
+
+ def tpc_recover(self):
+ """Returns a list of pending transaction IDs suitable for use with
+ .tpc_commit(xid) or .tpc_rollback(xid).
+
+ This function is part of the `DBAPI 2.0 specification
+ <http://www.python.org/dev/peps/pep-0249/>`_.
+ """
+ try:
+ previous_autocommit_mode = self.autocommit
+ self.autocommit = True
+ curs = self.cursor()
+ curs.execute("select gid FROM pg_prepared_xacts")
+ return [self.xid(0, row[0], "") for row in curs]
+ finally:
+ self.autocommit = previous_autocommit_mode
+
+
+def to_statement(query):
+ OUTSIDE = 0 # outside quoted string
+ INSIDE_SQ = 1 # inside single-quote string '...'
+ INSIDE_QI = 2 # inside quoted identifier "..."
+ INSIDE_ES = 3 # inside escaped single-quote string, E'...'
+ INSIDE_PN = 4 # inside parameter name eg. :name
+ INSIDE_CO = 5 # inside inline comment eg. --
+
+ in_quote_escape = False
+ placeholders = []
+ output_query = []
+ state = OUTSIDE
+ prev_c = None
+ for i, c in enumerate(query):
+ if i + 1 < len(query):
+ next_c = query[i + 1]
+ else:
+ next_c = None
+
+ if state == OUTSIDE:
+ if c == "'":
+ output_query.append(c)
+ if prev_c == "E":
+ state = INSIDE_ES
+ else:
+ state = INSIDE_SQ
+ elif c == '"':
+ output_query.append(c)
+ state = INSIDE_QI
+ elif c == "-":
+ output_query.append(c)
+ if prev_c == "-":
+ state = INSIDE_CO
+ elif c == ":" and next_c not in ":=" and prev_c != ":":
+ state = INSIDE_PN
+ placeholders.append("")
+ else:
+ output_query.append(c)
+
+ elif state == INSIDE_SQ:
+ if c == "'":
+ if in_quote_escape:
+ in_quote_escape = False
+ else:
+ if next_c == "'":
+ in_quote_escape = True
+ else:
+ state = OUTSIDE
+ output_query.append(c)
+
+ elif state == INSIDE_QI:
+ if c == '"':
+ state = OUTSIDE
+ output_query.append(c)
+
+ elif state == INSIDE_ES:
+ if c == "'" and prev_c != "\\":
+ # check for escaped single-quote
+ state = OUTSIDE
+ output_query.append(c)
+
+ elif state == INSIDE_PN:
+ placeholders[-1] += c
+ if next_c is None or (not next_c.isalnum() and next_c != "_"):
+ state = OUTSIDE
+ try:
+ pidx = placeholders.index(placeholders[-1], 0, -1)
+ output_query.append("$" + str(pidx + 1))
+ del placeholders[-1]
+ except ValueError:
+ output_query.append("$" + str(len(placeholders)))
+
+ elif state == INSIDE_CO:
+ output_query.append(c)
+ if c == "\n":
+ state = OUTSIDE
+
+ prev_c = c
+
+ def make_vals(args):
+ return tuple(args[p] for p in placeholders)
+
+ return "".join(output_query), make_vals
+
+
+class PreparedStatement:
+ def __init__(self, con, operation):
+ self.con = con
+ self.operation = operation
+ statement, self.make_args = to_statement(operation)
+ self.name_bin, self.row_desc, self.input_funcs = con.prepare_statement(
+ statement, ()
+ )
+
+ def run(self, **vals):
+ params = make_params(self.con.py_types, self.make_args(vals))
+
+ try:
+ if not self.con._in_transaction and not self.con.autocommit:
+ self.con.execute_unnamed("begin transaction")
+ self._context = self.con.execute_named(
+ self.name_bin, params, self.row_desc, self.input_funcs, self.operation
+ )
+ except AttributeError as e:
+ if self.con is None:
+ raise InterfaceError("Cursor closed")
+ elif self.con._sock is None:
+ raise InterfaceError("connection is closed")
+ else:
+ raise e
+
+ return tuple() if self._context.rows is None else tuple(self._context.rows)
+
+ def close(self):
+ self.con.close_prepared_statement(self.name_bin)
+ self.con = None
+
+
+__all__ = [
+ "BIGINTEGER",
+ "BINARY",
+ "BOOLEAN",
+ "BOOLEAN_ARRAY",
+ "BYTES",
+ "Binary",
+ "CHAR",
+ "CHAR_ARRAY",
+ "Connection",
+ "Cursor",
+ "DATE",
+ "DATETIME",
+ "DECIMAL",
+ "DECIMAL_ARRAY",
+ "DataError",
+ "DatabaseError",
+ "Date",
+ "DateFromTicks",
+ "Error",
+ "FLOAT",
+ "FLOAT_ARRAY",
+ "INET",
+ "INT2VECTOR",
+ "INTEGER",
+ "INTEGER_ARRAY",
+ "INTERVAL",
+ "IntegrityError",
+ "InterfaceError",
+ "InternalError",
+ "JSON",
+ "JSONB",
+ "MACADDR",
+ "NAME",
+ "NAME_ARRAY",
+ "NULLTYPE",
+ "NUMBER",
+ "NotSupportedError",
+ "OID",
+ "OperationalError",
+ "PGInterval",
+ "ProgrammingError",
+ "ROWID",
+ "Range",
+ "STRING",
+ "TEXT",
+ "TEXT_ARRAY",
+ "TIME",
+ "TIMEDELTA",
+ "TIMESTAMP",
+ "TIMESTAMPTZ",
+ "Time",
+ "TimeFromTicks",
+ "Timestamp",
+ "TimestampFromTicks",
+ "UNKNOWN",
+ "UUID_TYPE",
+ "VARCHAR",
+ "VARCHAR_ARRAY",
+ "Warning",
+ "XID",
+ "connect",
+ "pginterval_in",
+ "pginterval_out",
+ "timedelta_in",
+]
diff --git a/contrib/python/pg8000/pg8000/native.py b/contrib/python/pg8000/pg8000/native.py
new file mode 100644
index 00000000000..77e70e9af1b
--- /dev/null
+++ b/contrib/python/pg8000/pg8000/native.py
@@ -0,0 +1,293 @@
+from collections import defaultdict
+from enum import Enum, auto
+
+from pg8000.converters import (
+ BIGINT,
+ BOOLEAN,
+ BOOLEAN_ARRAY,
+ BYTES,
+ CHAR,
+ CHAR_ARRAY,
+ DATE,
+ FLOAT,
+ FLOAT_ARRAY,
+ INET,
+ INT2VECTOR,
+ INTEGER,
+ INTEGER_ARRAY,
+ INTERVAL,
+ JSON,
+ JSONB,
+ MACADDR,
+ NAME,
+ NAME_ARRAY,
+ NULLTYPE,
+ NUMERIC,
+ NUMERIC_ARRAY,
+ OID,
+ PGInterval,
+ STRING,
+ TEXT,
+ TEXT_ARRAY,
+ TIME,
+ TIMESTAMP,
+ TIMESTAMPTZ,
+ UNKNOWN,
+ UUID_TYPE,
+ VARCHAR,
+ VARCHAR_ARRAY,
+ XID,
+ identifier,
+ literal,
+ make_params,
+)
+from pg8000.core import CoreConnection, ver
+from pg8000.exceptions import DatabaseError, Error, InterfaceError
+from pg8000.types import Range
+
+__version__ = ver
+
+# Copyright (c) 2007-2009, Mathieu Fenniak
+# Copyright (c) The Contributors
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+
+class State(Enum):
+ OUT = auto() # outside quoted string
+ IN_SQ = auto() # inside single-quote string '...'
+ IN_QI = auto() # inside quoted identifier "..."
+ IN_ES = auto() # inside escaped single-quote string, E'...'
+ IN_PN = auto() # inside parameter name eg. :name
+ IN_CO = auto() # inside inline comment eg. --
+ IN_DQ = auto() # inside dollar-quoted string eg. $$...$$
+
+
+def to_statement(query):
+ in_quote_escape = False
+ placeholders = []
+ output_query = []
+ state = State.OUT
+ prev_c = None
+ for i, c in enumerate(query):
+ if i + 1 < len(query):
+ next_c = query[i + 1]
+ else:
+ next_c = None
+
+ if state == State.OUT:
+ if c == "'":
+ output_query.append(c)
+ if prev_c == "E":
+ state = State.IN_ES
+ else:
+ state = State.IN_SQ
+ elif c == '"':
+ output_query.append(c)
+ state = State.IN_QI
+ elif c == "-":
+ output_query.append(c)
+ if prev_c == "-":
+ state = State.IN_CO
+ elif c == "$":
+ output_query.append(c)
+ if prev_c == "$":
+ state = State.IN_DQ
+ elif c == ":" and next_c not in ":=" and prev_c != ":":
+ state = State.IN_PN
+ placeholders.append("")
+ else:
+ output_query.append(c)
+
+ elif state == State.IN_SQ:
+ if c == "'":
+ if in_quote_escape:
+ in_quote_escape = False
+ elif next_c == "'":
+ in_quote_escape = True
+ else:
+ state = State.OUT
+ output_query.append(c)
+
+ elif state == State.IN_QI:
+ if c == '"':
+ state = State.OUT
+ output_query.append(c)
+
+ elif state == State.IN_ES:
+ if c == "'" and prev_c != "\\":
+ # check for escaped single-quote
+ state = State.OUT
+ output_query.append(c)
+
+ elif state == State.IN_PN:
+ placeholders[-1] += c
+ if next_c is None or (not next_c.isalnum() and next_c != "_"):
+ state = State.OUT
+ try:
+ pidx = placeholders.index(placeholders[-1], 0, -1)
+ output_query.append(f"${pidx + 1}")
+ del placeholders[-1]
+ except ValueError:
+ output_query.append(f"${len(placeholders)}")
+
+ elif state == State.IN_CO:
+ output_query.append(c)
+ if c == "\n":
+ state = State.OUT
+
+ elif state == State.IN_DQ:
+ output_query.append(c)
+ if c == "$" and prev_c == "$":
+ state = State.OUT
+
+ prev_c = c
+
+ for reserved in ("types", "stream"):
+ if reserved in placeholders:
+ raise InterfaceError(
+ f"The name '{reserved}' can't be used as a placeholder because it's "
+ f"used for another purpose."
+ )
+
+ def make_vals(args):
+ vals = []
+ for p in placeholders:
+ try:
+ vals.append(args[p])
+ except KeyError:
+ raise InterfaceError(
+ f"There's a placeholder '{p}' in the query, but no matching "
+ f"keyword argument."
+ )
+ return tuple(vals)
+
+ return "".join(output_query), make_vals
+
+
+class Connection(CoreConnection):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self._context = None
+
+ @property
+ def columns(self):
+ context = self._context
+ if context is None:
+ return None
+ return context.columns
+
+ @property
+ def row_count(self):
+ context = self._context
+ if context is None:
+ return None
+ return context.row_count
+
+ def run(self, sql, stream=None, types=None, **params):
+ if len(params) == 0 and stream is None:
+ self._context = self.execute_simple(sql)
+ else:
+ statement, make_vals = to_statement(sql)
+ oids = () if types is None else make_vals(defaultdict(lambda: None, types))
+ self._context = self.execute_unnamed(
+ statement, make_vals(params), oids=oids, stream=stream
+ )
+ return self._context.rows
+
+ def prepare(self, sql):
+ return PreparedStatement(self, sql)
+
+
+class PreparedStatement:
+ def __init__(self, con, sql, types=None):
+ self.con = con
+ self.statement, self.make_vals = to_statement(sql)
+ oids = () if types is None else self.make_vals(defaultdict(lambda: None, types))
+ self.name_bin, self.cols, self.input_funcs = con.prepare_statement(
+ self.statement, oids
+ )
+
+ @property
+ def columns(self):
+ return self._context.columns
+
+ def run(self, stream=None, **params):
+ params = make_params(self.con.py_types, self.make_vals(params))
+
+ self._context = self.con.execute_named(
+ self.name_bin, params, self.cols, self.input_funcs, self.statement
+ )
+
+ return self._context.rows
+
+ def close(self):
+ self.con.close_prepared_statement(self.name_bin)
+
+
+__all__ = [
+ "BIGINT",
+ "BOOLEAN",
+ "BOOLEAN_ARRAY",
+ "BYTES",
+ "CHAR",
+ "CHAR_ARRAY",
+ "DATE",
+ "DatabaseError",
+ "Error",
+ "FLOAT",
+ "FLOAT_ARRAY",
+ "INET",
+ "INT2VECTOR",
+ "INTEGER",
+ "INTEGER_ARRAY",
+ "INTERVAL",
+ "InterfaceError",
+ "JSON",
+ "JSONB",
+ "MACADDR",
+ "NAME",
+ "NAME_ARRAY",
+ "NULLTYPE",
+ "NUMERIC",
+ "NUMERIC_ARRAY",
+ "OID",
+ "PGInterval",
+ "Range",
+ "STRING",
+ "TEXT",
+ "TEXT_ARRAY",
+ "TIME",
+ "TIMESTAMP",
+ "TIMESTAMPTZ",
+ "UNKNOWN",
+ "UUID_TYPE",
+ "VARCHAR",
+ "VARCHAR_ARRAY",
+ "XID",
+ "identifier",
+ "literal",
+]
diff --git a/contrib/python/pg8000/pg8000/types.py b/contrib/python/pg8000/pg8000/types.py
new file mode 100644
index 00000000000..c04fba627a6
--- /dev/null
+++ b/contrib/python/pg8000/pg8000/types.py
@@ -0,0 +1,305 @@
+from datetime import timedelta as Timedelta
+
+
+class PGInterval:
+ UNIT_MAP = {
+ "millennia": "millennia",
+ "millennium": "millennia",
+ "centuries": "centuries",
+ "century": "centuries",
+ "decades": "decades",
+ "decade": "decades",
+ "years": "years",
+ "year": "years",
+ "months": "months",
+ "month": "months",
+ "mon": "months",
+ "mons": "months",
+ "weeks": "weeks",
+ "week": "weeks",
+ "days": "days",
+ "day": "days",
+ "hours": "hours",
+ "hour": "hours",
+ "minutes": "minutes",
+ "minute": "minutes",
+ "mins": "minutes",
+ "secs": "seconds",
+ "seconds": "seconds",
+ "second": "seconds",
+ "microseconds": "microseconds",
+ "microsecond": "microseconds",
+ }
+
+ ISO_LOOKUP = {
+ True: {
+ "Y": "years",
+ "M": "months",
+ "D": "days",
+ },
+ False: {
+ "H": "hours",
+ "M": "minutes",
+ "S": "seconds",
+ },
+ }
+
+ @classmethod
+ def from_str_iso_8601(cls, interval_str):
+ # P[n]Y[n]M[n]DT[n]H[n]M[n]S
+ kwargs = {}
+ lookup = cls.ISO_LOOKUP[True]
+ val = []
+
+ for c in interval_str[1:]:
+ if c == "T":
+ lookup = cls.ISO_LOOKUP[False]
+ elif c.isdigit() or c in ("-", "."):
+ val.append(c)
+ else:
+ val_str = "".join(val)
+ name = lookup[c]
+ v = float(val_str) if name == "seconds" else int(val_str)
+ kwargs[name] = v
+ val.clear()
+
+ return cls(**kwargs)
+
+ @classmethod
+ def from_str_postgres(cls, interval_str):
+ """Parses both the postgres and postgres_verbose formats"""
+
+ t = {}
+
+ curr_val = None
+ for k in interval_str.split():
+ if ":" in k:
+ hours_str, minutes_str, seconds_str = k.split(":")
+ hours = int(hours_str)
+ if hours != 0:
+ t["hours"] = hours
+ minutes = int(minutes_str)
+ if minutes != 0:
+ t["minutes"] = minutes
+
+ seconds = float(seconds_str)
+
+ if seconds != 0:
+ t["seconds"] = seconds
+
+ elif k == "@":
+ continue
+
+ elif k == "ago":
+ for k, v in tuple(t.items()):
+ t[k] = -1 * v
+
+ else:
+ try:
+ curr_val = int(k)
+ except ValueError:
+ t[cls.UNIT_MAP[k]] = curr_val
+
+ return cls(**t)
+
+ @classmethod
+ def from_str_sql_standard(cls, interval_str):
+ """YYYY-MM
+ or
+ DD HH:MM:SS.F
+ or
+ YYYY-MM DD HH:MM:SS.F
+ """
+ month_part = None
+ day_parts = None
+ parts = interval_str.split()
+
+ if len(parts) == 1:
+ month_part = parts[0]
+ elif len(parts) == 2:
+ day_parts = parts
+ else:
+ month_part = parts[0]
+ day_parts = parts[1:]
+
+ kwargs = {}
+
+ if month_part is not None:
+ if month_part.startswith("-"):
+ sign = -1
+ p = month_part[1:]
+ else:
+ sign = 1
+ p = month_part
+
+ kwargs["years"], kwargs["months"] = [int(v) * sign for v in p.split("-")]
+
+ if day_parts is not None:
+ kwargs["days"] = int(day_parts[0])
+ time_part = day_parts[1]
+
+ if time_part.startswith("-"):
+ sign = -1
+ p = time_part[1:]
+ else:
+ sign = 1
+ p = time_part
+
+ hours, minutes, seconds = p.split(":")
+ kwargs["hours"] = int(hours) * sign
+ kwargs["minutes"] = int(minutes) * sign
+ kwargs["seconds"] = float(seconds) * sign
+
+ return cls(**kwargs)
+
+ @classmethod
+ def from_str(cls, interval_str):
+ if interval_str.startswith("P"):
+ return cls.from_str_iso_8601(interval_str)
+ elif interval_str.startswith("@"):
+ return cls.from_str_postgres(interval_str)
+ else:
+ parts = interval_str.split()
+ if (len(parts) > 1 and parts[1][0].isalpha()) or (
+ len(parts) == 1 and ":" in parts[0]
+ ):
+ return cls.from_str_postgres(interval_str)
+ else:
+ return cls.from_str_sql_standard(interval_str)
+
+ def __init__(
+ self,
+ millennia=None,
+ centuries=None,
+ decades=None,
+ years=None,
+ months=None,
+ weeks=None,
+ days=None,
+ hours=None,
+ minutes=None,
+ seconds=None,
+ microseconds=None,
+ ):
+ self.millennia = millennia
+ self.centuries = centuries
+ self.decades = decades
+ self.years = years
+ self.months = months
+ self.weeks = weeks
+ self.days = days
+ self.hours = hours
+ self.minutes = minutes
+ self.seconds = seconds
+ self.microseconds = microseconds
+
+ def __repr__(self):
+ return f"<PGInterval {self}>"
+
+ def _value_dict(self):
+ return {
+ k: v
+ for k, v in (
+ ("millennia", self.millennia),
+ ("centuries", self.centuries),
+ ("decades", self.decades),
+ ("years", self.years),
+ ("months", self.months),
+ ("weeks", self.weeks),
+ ("days", self.days),
+ ("hours", self.hours),
+ ("minutes", self.minutes),
+ ("seconds", self.seconds),
+ ("microseconds", self.microseconds),
+ )
+ if v is not None
+ }
+
+ def __str__(self):
+ return " ".join(f"{v} {n}" for n, v in self._value_dict().items())
+
+ def normalize(self):
+ months = 0
+ if self.months is not None:
+ months += self.months
+ if self.years is not None:
+ months += self.years * 12
+
+ days = 0
+ if self.days is not None:
+ days += self.days
+ if self.weeks is not None:
+ days += self.weeks * 7
+
+ seconds = 0
+ if self.hours is not None:
+ seconds += self.hours * 60 * 60
+ if self.minutes is not None:
+ seconds += self.minutes * 60
+ if self.seconds is not None:
+ seconds += self.seconds
+ if self.microseconds is not None:
+ seconds += self.microseconds / 1000000
+
+ return PGInterval(months=months, days=days, seconds=seconds)
+
+ def __eq__(self, other):
+ if isinstance(other, PGInterval):
+ s = self.normalize()
+ o = other.normalize()
+ return s.months == o.months and s.days == o.days and s.seconds == o.seconds
+ else:
+ return False
+
+ def to_timedelta(self):
+ pairs = self._value_dict()
+ overlap = pairs.keys() & {
+ "weeks",
+ "months",
+ "years",
+ "decades",
+ "centuries",
+ "millennia",
+ }
+ if len(overlap) > 0:
+ raise ValueError(
+ "Can't fit the interval fields {overlap} into a datetime.timedelta."
+ )
+
+ return Timedelta(**pairs)
+
+
+class Range:
+ def __init__(
+ self,
+ lower=None,
+ upper=None,
+ bounds="[)",
+ is_empty=False,
+ ):
+ self.lower = lower
+ self.upper = upper
+ self.bounds = bounds
+ self.is_empty = is_empty
+
+ def __eq__(self, other):
+ if isinstance(other, Range):
+ if self.is_empty or other.is_empty:
+ return self.is_empty == other.is_empty
+ else:
+ return (
+ self.lower == other.lower
+ and self.upper == other.upper
+ and self.bounds == other.bounds
+ )
+ return False
+
+ def __str__(self):
+ if self.is_empty:
+ return "empty"
+ else:
+ le, ue = ["" if v is None else v for v in (self.lower, self.upper)]
+ return f"{self.bounds[0]}{le},{ue}{self.bounds[1]}"
+
+ def __repr__(self):
+ return f"<Range {self}>"
diff --git a/contrib/python/pg8000/ya.make b/contrib/python/pg8000/ya.make
new file mode 100644
index 00000000000..741227b3e43
--- /dev/null
+++ b/contrib/python/pg8000/ya.make
@@ -0,0 +1,34 @@
+# Generated by devtools/yamaker (pypi).
+
+PY3_LIBRARY()
+
+VERSION(1.30.2)
+
+LICENSE(BSD-3-Clause)
+
+PEERDIR(
+ contrib/python/python-dateutil
+ contrib/python/scramp
+)
+
+NO_LINT()
+
+PY_SRCS(
+ TOP_LEVEL
+ pg8000/__init__.py
+ pg8000/converters.py
+ pg8000/core.py
+ pg8000/dbapi.py
+ pg8000/exceptions.py
+ pg8000/legacy.py
+ pg8000/native.py
+ pg8000/types.py
+)
+
+RESOURCE_FILES(
+ PREFIX contrib/python/pg8000/
+ .dist-info/METADATA
+ .dist-info/top_level.txt
+)
+
+END()