Skip to content

Commit 8d2a5ae

Browse files
authored
Support PostgreSQL 19 (#1379)
1 parent 58c0c59 commit 8d2a5ae

10 files changed

Lines changed: 92 additions & 12 deletions

File tree

‎.github/workflows/install-postgres.sh‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@ if [[ $OSTYPE == linux* ]]; then
2222
apt-get install -y --no-install-recommends curl gnupg ca-certificates
2323
curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
2424
mkdir -p /etc/apt/sources.list.d/
25-
echo "deb https://apt.postgresql.org/pub/repos/apt/ ${VERSION_CODENAME}-pgdg main" \
25+
PGDG_COMPONENTS="main"
26+
if [[ ${PGVERSION} == 19 ]]; then
27+
# PostgreSQL beta packages live in a separate component.
28+
PGDG_COMPONENTS+=" 19"
29+
fi
30+
echo "deb https://apt.postgresql.org/pub/repos/apt/ ${VERSION_CODENAME}-pgdg ${PGDG_COMPONENTS}" \
2631
>> /etc/apt/sources.list.d/pgdg.list
2732
apt-get update
2833
apt-get install -y --no-install-recommends \

‎.github/workflows/tests.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ jobs:
141141
if: needs.classify-tests.outputs.is_release != 'true'
142142
strategy:
143143
matrix:
144-
postgres-version: ["9.5", "9.6", "10", "11", "12", "13", "14", "15", "16", "17", "18"]
144+
postgres-version: ["9.5", "9.6", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19"]
145145

146146
runs-on: ubuntu-24.04
147147

‎README.rst‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ framework. You can read more about asyncpg in an introductory
1414
`blog post <http://magic.io/blog/asyncpg-1m-rows-from-postgres-to-python/>`_.
1515

1616
asyncpg requires Python 3.9 or later and is supported for PostgreSQL
17-
versions 9.5 to 18. Other PostgreSQL versions or other databases
17+
versions 9.5 to 19. Other PostgreSQL versions or other databases
1818
implementing the PostgreSQL protocol *may* work, but are not being
1919
actively tested.
2020

‎asyncpg/protocol/codecs/pgproto.pyx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ cdef init_pseudo_codecs():
235235

236236
# 64-bit OID types
237237
oid8_types = [
238-
XID8OID,
238+
XID8OID, OID8OID,
239239
]
240240

241241
for oid_type in oid8_types:
@@ -253,6 +253,7 @@ cdef init_pseudo_codecs():
253253
REGPROCOID, REGPROCEDUREOID, REGOPEROID, REGOPERATOROID,
254254
REGCLASSOID, REGTYPEOID, REGCONFIGOID, REGDICTIONARYOID,
255255
REGNAMESPACEOID, REGROLEOID, REFCURSOROID, REGCOLLATIONOID,
256+
REGDATABASEOID,
256257
]
257258

258259
for reg_type in reg_types:

‎asyncpg/protocol/pgtypes.pxi‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
DEF INVALIDOID = 0
1212
DEF MAXBUILTINOID = 9999
13-
DEF MAXSUPPORTEDOID = 5080
13+
DEF MAXSUPPORTEDOID = 6490
1414

1515
DEF BOOLOID = 16
1616
DEF BYTEAOID = 17
@@ -112,6 +112,8 @@ DEF ANYCOMPATIBLEOID = 5077
112112
DEF ANYCOMPATIBLEARRAYOID = 5078
113113
DEF ANYCOMPATIBLENONARRAYOID = 5079
114114
DEF ANYCOMPATIBLERANGEOID = 5080
115+
DEF OID8OID = 6437
116+
DEF REGDATABASEOID = 6490
115117

116118
ARRAY_TYPES = {_TEXTOID, _OIDOID}
117119

@@ -164,6 +166,7 @@ BUILTIN_TYPE_OID_MAP = {
164166
MONEYOID: 'money',
165167
NAMEOID: 'name',
166168
NUMERICOID: 'numeric',
169+
OID8OID: 'oid8',
167170
OIDOID: 'oid',
168171
OPAQUEOID: 'opaque',
169172
PATHOID: 'path',
@@ -183,6 +186,7 @@ BUILTIN_TYPE_OID_MAP = {
183186
REGCLASSOID: 'regclass',
184187
REGCOLLATIONOID: 'regcollation',
185188
REGCONFIGOID: 'regconfig',
189+
REGDATABASEOID: 'regdatabase',
186190
REGDICTIONARYOID: 'regdictionary',
187191
REGNAMESPACEOID: 'regnamespace',
188192
REGOPERATOROID: 'regoperator',

‎docs/index.rst‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ of PostgreSQL server binary protocol for use with Python's ``asyncio``
1616
framework.
1717

1818
**asyncpg** requires Python 3.9 or later and is supported for PostgreSQL
19-
versions 9.5 to 18. Other PostgreSQL versions or other databases implementing
19+
versions 9.5 to 19. Other PostgreSQL versions or other databases implementing
2020
the PostgreSQL protocol *may* work, but are not being actively tested.
2121

2222
Contents

‎docs/usage.rst‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ The table below shows the correspondence between PostgreSQL and Python types.
138138
| ``integer``, | |
139139
| ``bigint`` | |
140140
+----------------------+-----------------------------------------------------+
141+
| ``oid8`` | :class:`int <python:int>` |
142+
+----------------------+-----------------------------------------------------+
143+
| ``regdatabase`` | :class:`str <python:str>` |
144+
+----------------------+-----------------------------------------------------+
141145
| ``numeric`` | :class:`Decimal <python:decimal.Decimal>` |
142146
+----------------------+-----------------------------------------------------+
143147
| ``json``, ``jsonb`` | :class:`str <python:str>` |

‎tests/test_codecs.py‎

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,21 @@ def _system_timezone():
461461
0,
462462
10,
463463
4294967295
464-
])
464+
]),
465+
('oid8', 'oid8', (
466+
0,
467+
1,
468+
2 ** 32,
469+
2 ** 63,
470+
2 ** 64 - 1,
471+
), (19, 0)),
472+
('regdatabase', 'regdatabase', (
473+
'postgres',
474+
'template0',
475+
'template1',
476+
'-',
477+
'4294967295',
478+
), (19, 0)),
465479
]
466480

467481

@@ -552,6 +566,38 @@ async def test_all_builtin_types_handled(self):
552566
codec,
553567
'core type {} ({}) is unhandled'.format(typename, oid))
554568

569+
async def test_oid8(self):
570+
if self.server_version < (19, 0):
571+
self.skipTest('oid8 requires PostgreSQL 19 or later')
572+
573+
values = [0, 2 ** 32, 2 ** 63, 2 ** 64 - 1, None]
574+
result = await self.con.fetchval('SELECT $1::oid8[]', values)
575+
self.assertEqual(result, values)
576+
577+
for value in (-1, 2 ** 64, 2 ** 256):
578+
with self.subTest(value=value):
579+
with self.assertRaisesRegex(
580+
asyncpg.DataError, 'value out of uint64 range'):
581+
await self.con.fetchval('SELECT $1::oid8', value)
582+
583+
# A text fallback would accept this; the integer codec must reject it.
584+
with self.assertRaises(asyncpg.DataError):
585+
await self.con.fetchval('SELECT $1::oid8', '42')
586+
587+
async def test_regdatabase(self):
588+
if self.server_version < (19, 0):
589+
self.skipTest('regdatabase requires PostgreSQL 19 or later')
590+
591+
oid = await self.con.fetchval(
592+
"SELECT oid FROM pg_database WHERE datname = 'postgres'")
593+
result = await self.con.fetchval('SELECT $1::regdatabase', str(oid))
594+
self.assertEqual(result, 'postgres')
595+
596+
result = await self.con.fetchval(
597+
"SELECT current_database()::regdatabase")
598+
self.assertEqual(result, await self.con.fetchval(
599+
'SELECT current_database()'))
600+
555601
async def test_void(self):
556602
res = await self.con.fetchval('select pg_sleep(0)')
557603
self.assertIsNone(res)

‎tools/generate_exceptions.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,15 @@ def main():
7676
with open(args.errcodesfile, 'r') as errcodes_f:
7777
errcodes = errcodes_f.read()
7878

79+
# Removed in PostgreSQL 17, but still raised by older supported servers.
80+
if not re.search(r'^72000\s', errcodes, re.MULTILINE):
81+
errcodes = errcodes.replace(
82+
'Section: Class F0',
83+
'Section: Class 72 - Snapshot Failure\n\n'
84+
'72000 E ERRCODE_SNAPSHOT_TOO_OLD snapshot_too_old\n\n'
85+
'Section: Class F0',
86+
)
87+
7988
section_re = re.compile(r'^Section: .*')
8089

8190
tpl = """\

‎tools/generate_type_map.py‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@
2323
# postgresql/src/include/access/transam.h: FirstBootstrapObjectId
2424
_MAXBUILTINOID = 10000 - 1
2525

26+
# Types removed from newer PostgreSQL versions that we still support.
27+
_LEGACY_TYPES = {
28+
210: 'smgr',
29+
702: 'abstime',
30+
703: 'reltime',
31+
704: 'tinterval',
32+
2282: 'opaque',
33+
}
34+
2635
# A list of alternative names for builtin types.
2736
_TYPE_ALIASES = {
2837
'smallint': 'int2',
@@ -78,9 +87,11 @@ async def runner(args):
7887
typemap = {}
7988
array_types = []
8089

81-
for pg_type in pg_types:
82-
typeoid = pg_type['oid']
83-
typename = pg_type['typname']
90+
pg_types = _LEGACY_TYPES | {
91+
pg_type['oid']: pg_type['typname'] for pg_type in pg_types
92+
}
93+
94+
for typeoid, typename in sorted(pg_types.items()):
8495

8596
defname = '{}OID'.format(typename.upper())
8697
defs.append('DEF {name} = {oid}'.format(name=defname, oid=typeoid))
@@ -91,11 +102,11 @@ async def runner(args):
91102

92103
typemap[defname] = typename
93104

94-
buf += 'DEF MAXSUPPORTEDOID = {}\n\n'.format(pg_types[-1]['oid'])
105+
buf += 'DEF MAXSUPPORTEDOID = {}\n\n'.format(max(pg_types))
95106

96107
buf += '\n'.join(defs)
97108

98-
buf += '\n\ncdef ARRAY_TYPES = ({},)'.format(', '.join(array_types))
109+
buf += '\n\nARRAY_TYPES = {{{}}}'.format(', '.join(array_types))
99110

100111
f_typemap = ('{}: {!r}'.format(dn, n) for dn, n in sorted(typemap.items()))
101112
buf += '\n\nBUILTIN_TYPE_OID_MAP = {{\n {}\n}}'.format(

0 commit comments

Comments
 (0)