diff --git a/asyncpg/protocol/codecs/array.pyx b/asyncpg/protocol/codecs/array.pyx index f8f9b8dd..203d81ac 100644 --- a/asyncpg/protocol/codecs/array.pyx +++ b/asyncpg/protocol/codecs/array.pyx @@ -858,8 +858,17 @@ cdef arraytext_decode(ConnectionSettings settings, FRBuffer *buf): return array_decode(settings, buf, &text_decode_ex, NULL) +cdef arrayvarchar_encode(ConnectionSettings settings, WriteBuffer buf, items): + array_encode(settings, buf, items, VARCHAROID, + &text_encode_ex, NULL) + + +cdef arrayvarchar_decode(ConnectionSettings settings, FRBuffer *buf): + return array_decode(settings, buf, &text_decode_ex, NULL) + + cdef init_array_codecs(): - # oid[] and text[] are registered as core codecs + # oid[], text[], and varchar[] are registered as core codecs # to make type introspection query work # register_core_codec(_OIDOID, @@ -872,4 +881,9 @@ cdef init_array_codecs(): &arraytext_decode, PG_FORMAT_BINARY) + register_core_codec(_VARCHAROID, + &arrayvarchar_encode, + &arrayvarchar_decode, + PG_FORMAT_BINARY) + init_array_codecs() diff --git a/asyncpg/protocol/pgtypes.pxi b/asyncpg/protocol/pgtypes.pxi index 86f8e663..e6eb429e 100644 --- a/asyncpg/protocol/pgtypes.pxi +++ b/asyncpg/protocol/pgtypes.pxi @@ -51,6 +51,7 @@ DEF MONEYOID = 790 DEF MACADDROID = 829 DEF INETOID = 869 DEF _TEXTOID = 1009 +DEF _VARCHAROID = 1015 DEF _OIDOID = 1028 DEF ACLITEMOID = 1033 DEF BPCHAROID = 1042 @@ -113,7 +114,7 @@ DEF ANYCOMPATIBLEARRAYOID = 5078 DEF ANYCOMPATIBLENONARRAYOID = 5079 DEF ANYCOMPATIBLERANGEOID = 5080 -ARRAY_TYPES = {_TEXTOID, _OIDOID} +ARRAY_TYPES = {_TEXTOID, _VARCHAROID, _OIDOID} BUILTIN_TYPE_OID_MAP = { ABSTIMEOID: 'abstime', @@ -215,7 +216,8 @@ BUILTIN_TYPE_OID_MAP = { XIDOID: 'xid', XMLOID: 'xml', _OIDOID: 'oid[]', - _TEXTOID: 'text[]' + _TEXTOID: 'text[]', + _VARCHAROID: 'varchar[]' } BUILTIN_TYPE_NAME_MAP = {v: k for k, v in BUILTIN_TYPE_OID_MAP.items()} diff --git a/tests/test_introspection.py b/tests/test_introspection.py index bf95537a..06e7929d 100644 --- a/tests/test_introspection.py +++ b/tests/test_introspection.py @@ -25,6 +25,15 @@ async def _introspect_types(self, *args, **kwargs): return await super()._introspect_types(*args, **kwargs) +class CountingIntrospectionConnection(apg_con.Connection): + """Connection class to assert when type introspection is skipped.""" + introspect_count = 0 + + async def _introspect_types(self, *args, **kwargs): + self.introspect_count += 1 + return await super()._introspect_types(*args, **kwargs) + + class TestIntrospection(tb.ConnectedTestCase): @classmethod def setUpClass(cls): @@ -78,6 +87,29 @@ async def test_introspection_on_large_db(self): with self.assertRunUnder(MAX_RUNTIME): await self.con.fetchval('SELECT $1::int[]', [1, 2]) + async def test_varchar_array_does_not_introspect(self): + conn = await self.connect( + connection_class=CountingIntrospectionConnection) + try: + cases = [ + ['a', 'b'], + [None, 'b'], + [], + [['a', 'b'], ['c', 'd']], + ] + + for case in cases: + result = await conn.fetchval('SELECT $1::varchar[]', case) + self.assertEqual(result, case) + + result = await conn.fetchval( + "SELECT ARRAY['a', 'b']::varchar[]") + self.assertEqual(result, ['a', 'b']) + + self.assertEqual(conn.introspect_count, 0) + finally: + await conn.close() + @tb.with_connection_options(statement_cache_size=0) async def test_introspection_no_stmt_cache_01(self): old_uid = apg_con._uid diff --git a/tools/generate_type_map.py b/tools/generate_type_map.py index b4e90664..b9d02f2f 100755 --- a/tools/generate_type_map.py +++ b/tools/generate_type_map.py @@ -16,7 +16,7 @@ # Array types with builtin codecs, necessary for codec # bootstrap to work # -_BUILTIN_ARRAYS = ('_text', '_oid') +_BUILTIN_ARRAYS = ('_text', '_varchar', '_oid') _INVALIDOID = 0