Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/install-postgres.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ framework. You can read more about asyncpg in an introductory
`blog post <http://magic.io/blog/asyncpg-1m-rows-from-postgres-to-python/>`_.

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.

Expand Down
3 changes: 2 additions & 1 deletion asyncpg/protocol/codecs/pgproto.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ cdef init_pseudo_codecs():

# 64-bit OID types
oid8_types = [
XID8OID,
XID8OID, OID8OID,
]

for oid_type in oid8_types:
Expand All @@ -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:
Expand Down
6 changes: 5 additions & 1 deletion asyncpg/protocol/pgtypes.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

DEF INVALIDOID = 0
DEF MAXBUILTINOID = 9999
DEF MAXSUPPORTEDOID = 5080
DEF MAXSUPPORTEDOID = 6490

DEF BOOLOID = 16
DEF BYTEAOID = 17
Expand Down Expand Up @@ -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}

Expand Down Expand Up @@ -164,6 +166,7 @@ BUILTIN_TYPE_OID_MAP = {
MONEYOID: 'money',
NAMEOID: 'name',
NUMERICOID: 'numeric',
OID8OID: 'oid8',
OIDOID: 'oid',
OPAQUEOID: 'opaque',
PATHOID: 'path',
Expand All @@ -183,6 +186,7 @@ BUILTIN_TYPE_OID_MAP = {
REGCLASSOID: 'regclass',
REGCOLLATIONOID: 'regcollation',
REGCONFIGOID: 'regconfig',
REGDATABASEOID: 'regdatabase',
REGDICTIONARYOID: 'regdictionary',
REGNAMESPACEOID: 'regnamespace',
REGOPERATOROID: 'regoperator',
Expand Down
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ The table below shows the correspondence between PostgreSQL and Python types.
| ``integer``, | |
| ``bigint`` | |
+----------------------+-----------------------------------------------------+
| ``oid8`` | :class:`int <python:int>` |
+----------------------+-----------------------------------------------------+
| ``regdatabase`` | :class:`str <python:str>` |
+----------------------+-----------------------------------------------------+
| ``numeric`` | :class:`Decimal <python:decimal.Decimal>` |
+----------------------+-----------------------------------------------------+
| ``json``, ``jsonb`` | :class:`str <python:str>` |
Expand Down
48 changes: 47 additions & 1 deletion tests/test_codecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
]


Expand Down Expand Up @@ -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)
Expand Down
9 changes: 9 additions & 0 deletions tools/generate_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = """\
Expand Down
21 changes: 16 additions & 5 deletions tools/generate_type_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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))
Expand All @@ -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(
Expand Down
Loading