diff --git a/asyncpg/exceptions/_base.py b/asyncpg/exceptions/_base.py index fa96a595..783b5eb5 100644 --- a/asyncpg/exceptions/_base.py +++ b/asyncpg/exceptions/_base.py @@ -210,6 +210,15 @@ def __init__(self, msg, *, detail=None, hint=None): InterfaceMessage.__init__(self, detail=detail, hint=hint) Exception.__init__(self, msg) + def with_msg(self, msg): + return type(self)( + msg, + detail=self.detail, + hint=self.hint, + ).with_traceback( + self.__traceback__ + ) + class DataError(InterfaceError, ValueError): """An error caused by invalid query input.""" diff --git a/asyncpg/protocol/codecs/record.pyx b/asyncpg/protocol/codecs/record.pyx index 5326a8c6..6446f2da 100644 --- a/asyncpg/protocol/codecs/record.pyx +++ b/asyncpg/protocol/codecs/record.pyx @@ -51,9 +51,20 @@ cdef anonymous_record_decode(ConnectionSettings settings, FRBuffer *buf): return result +cdef anonymous_record_encode(ConnectionSettings settings, WriteBuffer buf, obj): + raise exceptions.UnsupportedClientFeatureError( + 'input of anonymous composite types is not supported', + hint=( + 'Consider declaring an explicit composite type and ' + 'using it to cast the argument.' + ), + detail='PostgreSQL does not implement anonymous composite type input.' + ) + + cdef init_record_codecs(): register_core_codec(RECORDOID, - NULL, + anonymous_record_encode, anonymous_record_decode, PG_FORMAT_BINARY) diff --git a/asyncpg/protocol/prepared_stmt.pyx b/asyncpg/protocol/prepared_stmt.pyx index fd9f5a26..5f1820de 100644 --- a/asyncpg/protocol/prepared_stmt.pyx +++ b/asyncpg/protocol/prepared_stmt.pyx @@ -156,9 +156,11 @@ cdef class PreparedStatementState: except (AssertionError, exceptions.InternalClientError): # These are internal errors and should raise as-is. raise - except exceptions.InterfaceError: - # This is already a descriptive error. - raise + except exceptions.InterfaceError as e: + # This is already a descriptive error, but annotate + # with argument name for clarity. + raise e.with_msg( + f'query argument ${idx + 1}: {e.args[0]}') from None except Exception as e: # Everything else is assumed to be an encoding error # due to invalid input. diff --git a/tests/test_codecs.py b/tests/test_codecs.py index dfe26de0..4c86b8b0 100644 --- a/tests/test_codecs.py +++ b/tests/test_codecs.py @@ -876,6 +876,13 @@ async def test_composites(self): self.assertEqual(res, (None, 1234, '5678', (42, '42'))) + with self.assertRaisesRegex( + asyncpg.UnsupportedClientFeatureError, + 'query argument \\$1: input of anonymous ' + 'composite types is not supported', + ): + await self.con.fetchval("SELECT (1, 'foo') = $1", (1, 'foo')) + try: st = await self.con.prepare(''' SELECT ROW(