diff --git a/.github/workflows/install-postgres.sh b/.github/workflows/install-postgres.sh
index 733c7033..ad0cc4ac 100755
--- a/.github/workflows/install-postgres.sh
+++ b/.github/workflows/install-postgres.sh
@@ -22,7 +22,12 @@ if [[ $OSTYPE == linux* ]]; then
apt-get install -y --no-install-recommends curl gnupg ca-certificates
curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
mkdir -p /etc/apt/sources.list.d/
- echo "deb https://apt.postgresql.org/pub/repos/apt/ ${VERSION_CODENAME}-pgdg main" \
+ PGDG_COMPONENTS="main"
+ if [[ ${PGVERSION} == 19 ]]; then
+ # PostgreSQL beta packages live in a separate component.
+ PGDG_COMPONENTS+=" 19"
+ fi
+ echo "deb https://apt.postgresql.org/pub/repos/apt/ ${VERSION_CODENAME}-pgdg ${PGDG_COMPONENTS}" \
>> /etc/apt/sources.list.d/pgdg.list
apt-get update
apt-get install -y --no-install-recommends \
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index a8a91bdf..aaff688c 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -141,7 +141,7 @@ jobs:
if: needs.classify-tests.outputs.is_release != 'true'
strategy:
matrix:
- postgres-version: ["9.5", "9.6", "10", "11", "12", "13", "14", "15", "16", "17", "18"]
+ postgres-version: ["9.5", "9.6", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19"]
runs-on: ubuntu-24.04
diff --git a/README.rst b/README.rst
index 1a37296d..73e795fc 100644
--- a/README.rst
+++ b/README.rst
@@ -14,7 +14,7 @@ framework. You can read more about asyncpg in an introductory
`blog post `_.
asyncpg requires Python 3.9 or later and is supported for PostgreSQL
-versions 9.5 to 18. Other PostgreSQL versions or other databases
+versions 9.5 to 19. Other PostgreSQL versions or other databases
implementing the PostgreSQL protocol *may* work, but are not being
actively tested.
diff --git a/asyncpg/protocol/codecs/pgproto.pyx b/asyncpg/protocol/codecs/pgproto.pyx
index 51d650d0..4bcdd743 100644
--- a/asyncpg/protocol/codecs/pgproto.pyx
+++ b/asyncpg/protocol/codecs/pgproto.pyx
@@ -235,7 +235,7 @@ cdef init_pseudo_codecs():
# 64-bit OID types
oid8_types = [
- XID8OID,
+ XID8OID, OID8OID,
]
for oid_type in oid8_types:
@@ -253,6 +253,7 @@ cdef init_pseudo_codecs():
REGPROCOID, REGPROCEDUREOID, REGOPEROID, REGOPERATOROID,
REGCLASSOID, REGTYPEOID, REGCONFIGOID, REGDICTIONARYOID,
REGNAMESPACEOID, REGROLEOID, REFCURSOROID, REGCOLLATIONOID,
+ REGDATABASEOID,
]
for reg_type in reg_types:
diff --git a/asyncpg/protocol/pgtypes.pxi b/asyncpg/protocol/pgtypes.pxi
index 86f8e663..f5f3ed1f 100644
--- a/asyncpg/protocol/pgtypes.pxi
+++ b/asyncpg/protocol/pgtypes.pxi
@@ -10,7 +10,7 @@
DEF INVALIDOID = 0
DEF MAXBUILTINOID = 9999
-DEF MAXSUPPORTEDOID = 5080
+DEF MAXSUPPORTEDOID = 6490
DEF BOOLOID = 16
DEF BYTEAOID = 17
@@ -112,6 +112,8 @@ DEF ANYCOMPATIBLEOID = 5077
DEF ANYCOMPATIBLEARRAYOID = 5078
DEF ANYCOMPATIBLENONARRAYOID = 5079
DEF ANYCOMPATIBLERANGEOID = 5080
+DEF OID8OID = 6437
+DEF REGDATABASEOID = 6490
ARRAY_TYPES = {_TEXTOID, _OIDOID}
@@ -164,6 +166,7 @@ BUILTIN_TYPE_OID_MAP = {
MONEYOID: 'money',
NAMEOID: 'name',
NUMERICOID: 'numeric',
+ OID8OID: 'oid8',
OIDOID: 'oid',
OPAQUEOID: 'opaque',
PATHOID: 'path',
@@ -183,6 +186,7 @@ BUILTIN_TYPE_OID_MAP = {
REGCLASSOID: 'regclass',
REGCOLLATIONOID: 'regcollation',
REGCONFIGOID: 'regconfig',
+ REGDATABASEOID: 'regdatabase',
REGDICTIONARYOID: 'regdictionary',
REGNAMESPACEOID: 'regnamespace',
REGOPERATOROID: 'regoperator',
diff --git a/docs/index.rst b/docs/index.rst
index 5ca0bef0..f2ac5695 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -16,7 +16,7 @@ of PostgreSQL server binary protocol for use with Python's ``asyncio``
framework.
**asyncpg** requires Python 3.9 or later and is supported for PostgreSQL
-versions 9.5 to 18. Other PostgreSQL versions or other databases implementing
+versions 9.5 to 19. Other PostgreSQL versions or other databases implementing
the PostgreSQL protocol *may* work, but are not being actively tested.
Contents
diff --git a/docs/usage.rst b/docs/usage.rst
index e490a2ef..e9475653 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -138,6 +138,10 @@ The table below shows the correspondence between PostgreSQL and Python types.
| ``integer``, | |
| ``bigint`` | |
+----------------------+-----------------------------------------------------+
+| ``oid8`` | :class:`int ` |
++----------------------+-----------------------------------------------------+
+| ``regdatabase`` | :class:`str ` |
++----------------------+-----------------------------------------------------+
| ``numeric`` | :class:`Decimal ` |
+----------------------+-----------------------------------------------------+
| ``json``, ``jsonb`` | :class:`str ` |
diff --git a/tests/test_codecs.py b/tests/test_codecs.py
index f466349a..456d83fa 100644
--- a/tests/test_codecs.py
+++ b/tests/test_codecs.py
@@ -461,7 +461,21 @@ def _system_timezone():
0,
10,
4294967295
- ])
+ ]),
+ ('oid8', 'oid8', (
+ 0,
+ 1,
+ 2 ** 32,
+ 2 ** 63,
+ 2 ** 64 - 1,
+ ), (19, 0)),
+ ('regdatabase', 'regdatabase', (
+ 'postgres',
+ 'template0',
+ 'template1',
+ '-',
+ '4294967295',
+ ), (19, 0)),
]
@@ -552,6 +566,38 @@ async def test_all_builtin_types_handled(self):
codec,
'core type {} ({}) is unhandled'.format(typename, oid))
+ async def test_oid8(self):
+ if self.server_version < (19, 0):
+ self.skipTest('oid8 requires PostgreSQL 19 or later')
+
+ values = [0, 2 ** 32, 2 ** 63, 2 ** 64 - 1, None]
+ result = await self.con.fetchval('SELECT $1::oid8[]', values)
+ self.assertEqual(result, values)
+
+ for value in (-1, 2 ** 64, 2 ** 256):
+ with self.subTest(value=value):
+ with self.assertRaisesRegex(
+ asyncpg.DataError, 'value out of uint64 range'):
+ await self.con.fetchval('SELECT $1::oid8', value)
+
+ # A text fallback would accept this; the integer codec must reject it.
+ with self.assertRaises(asyncpg.DataError):
+ await self.con.fetchval('SELECT $1::oid8', '42')
+
+ async def test_regdatabase(self):
+ if self.server_version < (19, 0):
+ self.skipTest('regdatabase requires PostgreSQL 19 or later')
+
+ oid = await self.con.fetchval(
+ "SELECT oid FROM pg_database WHERE datname = 'postgres'")
+ result = await self.con.fetchval('SELECT $1::regdatabase', str(oid))
+ self.assertEqual(result, 'postgres')
+
+ result = await self.con.fetchval(
+ "SELECT current_database()::regdatabase")
+ self.assertEqual(result, await self.con.fetchval(
+ 'SELECT current_database()'))
+
async def test_void(self):
res = await self.con.fetchval('select pg_sleep(0)')
self.assertIsNone(res)
diff --git a/tools/generate_exceptions.py b/tools/generate_exceptions.py
index 0b626558..a36b65e4 100755
--- a/tools/generate_exceptions.py
+++ b/tools/generate_exceptions.py
@@ -76,6 +76,15 @@ def main():
with open(args.errcodesfile, 'r') as errcodes_f:
errcodes = errcodes_f.read()
+ # Removed in PostgreSQL 17, but still raised by older supported servers.
+ if not re.search(r'^72000\s', errcodes, re.MULTILINE):
+ errcodes = errcodes.replace(
+ 'Section: Class F0',
+ 'Section: Class 72 - Snapshot Failure\n\n'
+ '72000 E ERRCODE_SNAPSHOT_TOO_OLD snapshot_too_old\n\n'
+ 'Section: Class F0',
+ )
+
section_re = re.compile(r'^Section: .*')
tpl = """\
diff --git a/tools/generate_type_map.py b/tools/generate_type_map.py
index b4e90664..d19f261f 100755
--- a/tools/generate_type_map.py
+++ b/tools/generate_type_map.py
@@ -23,6 +23,15 @@
# postgresql/src/include/access/transam.h: FirstBootstrapObjectId
_MAXBUILTINOID = 10000 - 1
+# Types removed from newer PostgreSQL versions that we still support.
+_LEGACY_TYPES = {
+ 210: 'smgr',
+ 702: 'abstime',
+ 703: 'reltime',
+ 704: 'tinterval',
+ 2282: 'opaque',
+}
+
# A list of alternative names for builtin types.
_TYPE_ALIASES = {
'smallint': 'int2',
@@ -78,9 +87,11 @@ async def runner(args):
typemap = {}
array_types = []
- for pg_type in pg_types:
- typeoid = pg_type['oid']
- typename = pg_type['typname']
+ pg_types = _LEGACY_TYPES | {
+ pg_type['oid']: pg_type['typname'] for pg_type in pg_types
+ }
+
+ for typeoid, typename in sorted(pg_types.items()):
defname = '{}OID'.format(typename.upper())
defs.append('DEF {name} = {oid}'.format(name=defname, oid=typeoid))
@@ -91,11 +102,11 @@ async def runner(args):
typemap[defname] = typename
- buf += 'DEF MAXSUPPORTEDOID = {}\n\n'.format(pg_types[-1]['oid'])
+ buf += 'DEF MAXSUPPORTEDOID = {}\n\n'.format(max(pg_types))
buf += '\n'.join(defs)
- buf += '\n\ncdef ARRAY_TYPES = ({},)'.format(', '.join(array_types))
+ buf += '\n\nARRAY_TYPES = {{{}}}'.format(', '.join(array_types))
f_typemap = ('{}: {!r}'.format(dn, n) for dn, n in sorted(typemap.items()))
buf += '\n\nBUILTIN_TYPE_OID_MAP = {{\n {}\n}}'.format(