Skip to content

Commit 7252dbe

Browse files
committed
Raise proper error on anonymous composite input (tuple arguments) (#664)
Currently asyncpg would crash with an arcane "could not resolve query result and/or argument types in 6 attempts", which isn't helpful. Do the right thing by raising an `UnsupportedClientFeatureError` explicitly instead. Fixes #476.
1 parent 50f964f commit 7252dbe

File tree

4 files changed

+33
-4
lines changed

4 files changed

+33
-4
lines changed

asyncpg/exceptions/_base.py

+9
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,15 @@ def __init__(self, msg, *, detail=None, hint=None):
210210
InterfaceMessage.__init__(self, detail=detail, hint=hint)
211211
Exception.__init__(self, msg)
212212

213+
def with_msg(self, msg):
214+
return type(self)(
215+
msg,
216+
detail=self.detail,
217+
hint=self.hint,
218+
).with_traceback(
219+
self.__traceback__
220+
)
221+
213222

214223
class DataError(InterfaceError, ValueError):
215224
"""An error caused by invalid query input."""

asyncpg/protocol/codecs/record.pyx

+12-1
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,20 @@ cdef anonymous_record_decode(ConnectionSettings settings, FRBuffer *buf):
5151
return result
5252

5353

54+
cdef anonymous_record_encode(ConnectionSettings settings, WriteBuffer buf, obj):
55+
raise exceptions.UnsupportedClientFeatureError(
56+
'input of anonymous composite types is not supported',
57+
hint=(
58+
'Consider declaring an explicit composite type and '
59+
'using it to cast the argument.'
60+
),
61+
detail='PostgreSQL does not implement anonymous composite type input.'
62+
)
63+
64+
5465
cdef init_record_codecs():
5566
register_core_codec(RECORDOID,
56-
<encode_func>NULL,
67+
<encode_func>anonymous_record_encode,
5768
<decode_func>anonymous_record_decode,
5869
PG_FORMAT_BINARY)
5970

asyncpg/protocol/prepared_stmt.pyx

+5-3
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,11 @@ cdef class PreparedStatementState:
156156
except (AssertionError, exceptions.InternalClientError):
157157
# These are internal errors and should raise as-is.
158158
raise
159-
except exceptions.InterfaceError:
160-
# This is already a descriptive error.
161-
raise
159+
except exceptions.InterfaceError as e:
160+
# This is already a descriptive error, but annotate
161+
# with argument name for clarity.
162+
raise e.with_msg(
163+
f'query argument ${idx + 1}: {e.args[0]}') from None
162164
except Exception as e:
163165
# Everything else is assumed to be an encoding error
164166
# due to invalid input.

tests/test_codecs.py

+7
Original file line numberDiff line numberDiff line change
@@ -892,6 +892,13 @@ async def test_composites(self):
892892

893893
self.assertEqual(res, (None, 1234, '5678', (42, '42')))
894894

895+
with self.assertRaisesRegex(
896+
asyncpg.UnsupportedClientFeatureError,
897+
'query argument \\$1: input of anonymous '
898+
'composite types is not supported',
899+
):
900+
await self.con.fetchval("SELECT (1, 'foo') = $1", (1, 'foo'))
901+
895902
try:
896903
st = await self.con.prepare('''
897904
SELECT ROW(

0 commit comments

Comments
 (0)