From 8e7740819031469284e63c440e3add9e1f34661e Mon Sep 17 00:00:00 2001 From: HardMax71 Date: Tue, 13 Oct 2026 11:19:31 +0200 Subject: [PATCH] Allow up to 65535 query arguments PostgreSQL reads the 16-bit parameter counts in Bind and ParameterDescription as unsigned and documents a limit of 65,535 query parameters, but asyncpg stored them in int16_t and refused more than 32767 arguments. Keep the count as uint16_t and raise the limit to 65535. --- asyncpg/protocol/prepared_stmt.pxd | 2 +- asyncpg/protocol/prepared_stmt.pyx | 15 ++++++++------- tests/test_prepare.py | 9 +++++++-- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/asyncpg/protocol/prepared_stmt.pxd b/asyncpg/protocol/prepared_stmt.pxd index 369db733..5791df32 100644 --- a/asyncpg/protocol/prepared_stmt.pxd +++ b/asyncpg/protocol/prepared_stmt.pxd @@ -21,7 +21,7 @@ cdef class PreparedStatementState: ConnectionSettings settings - int16_t args_num + uint16_t args_num bint have_text_args tuple args_codecs diff --git a/asyncpg/protocol/prepared_stmt.pyx b/asyncpg/protocol/prepared_stmt.pyx index 4145c664..85c8005a 100644 --- a/asyncpg/protocol/prepared_stmt.pyx +++ b/asyncpg/protocol/prepared_stmt.pyx @@ -127,9 +127,10 @@ cdef class PreparedStatementState: raise exceptions.InternalClientError( f'Bind: expected a sequence, got {type(args).__name__}') - if len(args) > 32767: + # PostgreSQL reads the 16-bit parameter count as unsigned + if len(args) > 65535: raise exceptions.InterfaceError( - 'the number of query arguments cannot exceed 32767') + 'the number of query arguments cannot exceed 65535') writer = WriteBuffer.new() @@ -155,7 +156,7 @@ cdef class PreparedStatementState: hint=hint) if self.have_text_args: - writer.write_int16(self.args_num) + writer.write_int16(self.args_num) for idx in range(self.args_num): codec = (self.args_codecs[idx]) writer.write_int16(codec.format) @@ -163,7 +164,7 @@ cdef class PreparedStatementState: # All arguments are in binary format writer.write_int32(0x00010001) - writer.write_int16(self.args_num) + writer.write_int16(self.args_num) for idx in range(self.args_num): arg = args[idx] @@ -286,7 +287,7 @@ cdef class PreparedStatementState: cdef _set_args_desc(self, object desc): self.parameters_desc = _decode_parameters_desc(desc) - self.args_num = (len(self.parameters_desc)) + self.args_num = (len(self.parameters_desc)) cdef _decode_row(self, const char* cbuf, ssize_t buf_len): cdef: @@ -345,12 +346,12 @@ cdef class PreparedStatementState: cdef _decode_parameters_desc(object desc): cdef: ReadBuffer reader - int16_t nparams + uint16_t nparams uint32_t p_oid list result = [] reader = ReadBuffer.new_message_parser(desc) - nparams = reader.read_int16() + nparams = reader.read_int16() for i from 0 <= i < nparams: p_oid = reader.read_int32() diff --git a/tests/test_prepare.py b/tests/test_prepare.py index 661021bd..8262c455 100644 --- a/tests/test_prepare.py +++ b/tests/test_prepare.py @@ -536,13 +536,18 @@ async def test_prepare_27_max_cacheable_statement_size(self): self.assertEqual(len(cache), 2) async def test_prepare_28_max_args(self): - N = 32768 + N = 65535 + args = ','.join('${}::int4'.format(i) for i in range(1, N + 1)) + query = 'SELECT cardinality(ARRAY[{}])'.format(args) + self.assertEqual(await self.con.fetchval(query, *range(1, N + 1)), N) + + N = 65536 args = ','.join('${}'.format(i) for i in range(1, N + 1)) query = 'SELECT ARRAY[{}]'.format(args) with self.assertRaisesRegex( exceptions.InterfaceError, - 'the number of query arguments cannot exceed 32767'): + 'the number of query arguments cannot exceed 65535'): await self.con.fetchval(query, *range(1, N + 1)) async def test_prepare_29_duplicates(self):