Skip to content

Commit 821f279

Browse files
committed
Disable custom data codec for internal introspection
Fixes: #617
1 parent 98dcf96 commit 821f279

File tree

9 files changed

+78
-37
lines changed

9 files changed

+78
-37
lines changed

asyncpg/connection.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ async def _get_statement(
340340
*,
341341
named: bool=False,
342342
use_cache: bool=True,
343+
disable_custom_codec=False,
343344
record_class=None
344345
):
345346
if record_class is None:
@@ -401,7 +402,7 @@ async def _get_statement(
401402

402403
# Now that types have been resolved, populate the codec pipeline
403404
# for the statement.
404-
statement._init_codecs()
405+
statement._init_codecs(disable_custom_codec)
405406

406407
if need_reprepare:
407408
await self._protocol.prepare(
@@ -424,7 +425,12 @@ async def _get_statement(
424425

425426
async def _introspect_types(self, typeoids, timeout):
426427
return await self.__execute(
427-
self._intro_query, (list(typeoids),), 0, timeout)
428+
self._intro_query,
429+
(list(typeoids),),
430+
0,
431+
timeout,
432+
disable_custom_codec=True,
433+
)
428434

429435
async def _introspect_type(self, typename, schema):
430436
if (
@@ -437,20 +443,22 @@ async def _introspect_type(self, typename, schema):
437443
[typeoid],
438444
limit=0,
439445
timeout=None,
446+
disable_custom_codec=True,
440447
)
441-
if rows:
442-
typeinfo = rows[0]
443-
else:
444-
typeinfo = None
445448
else:
446-
typeinfo = await self.fetchrow(
447-
introspection.TYPE_BY_NAME, typename, schema)
449+
rows = await self._execute(
450+
introspection.TYPE_BY_NAME,
451+
[typename, schema],
452+
limit=1,
453+
timeout=None,
454+
disable_custom_codec=True,
455+
)
448456

449-
if not typeinfo:
457+
if not rows:
450458
raise ValueError(
451459
'unknown type: {}.{}'.format(schema, typename))
452460

453-
return typeinfo
461+
return rows[0]
454462

455463
def cursor(
456464
self,
@@ -1587,6 +1595,7 @@ async def _execute(
15871595
timeout,
15881596
*,
15891597
return_status=False,
1598+
disable_custom_codec=False,
15901599
record_class=None
15911600
):
15921601
with self._stmt_exclusive_section:
@@ -1597,6 +1606,7 @@ async def _execute(
15971606
timeout,
15981607
return_status=return_status,
15991608
record_class=record_class,
1609+
disable_custom_codec=disable_custom_codec,
16001610
)
16011611
return result
16021612

@@ -1608,6 +1618,7 @@ async def __execute(
16081618
timeout,
16091619
*,
16101620
return_status=False,
1621+
disable_custom_codec=False,
16111622
record_class=None
16121623
):
16131624
executor = lambda stmt, timeout: self._protocol.bind_execute(
@@ -1618,6 +1629,7 @@ async def __execute(
16181629
executor,
16191630
timeout,
16201631
record_class=record_class,
1632+
disable_custom_codec=disable_custom_codec,
16211633
)
16221634

16231635
async def _executemany(self, query, args, timeout):
@@ -1635,20 +1647,23 @@ async def _do_execute(
16351647
timeout,
16361648
retry=True,
16371649
*,
1650+
disable_custom_codec=False,
16381651
record_class=None
16391652
):
16401653
if timeout is None:
16411654
stmt = await self._get_statement(
16421655
query,
16431656
None,
16441657
record_class=record_class,
1658+
disable_custom_codec=disable_custom_codec,
16451659
)
16461660
else:
16471661
before = time.monotonic()
16481662
stmt = await self._get_statement(
16491663
query,
16501664
timeout,
16511665
record_class=record_class,
1666+
disable_custom_codec=disable_custom_codec,
16521667
)
16531668
after = time.monotonic()
16541669
timeout -= after - before

asyncpg/protocol/codecs/base.pxd

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,5 +166,6 @@ cdef class DataCodecConfig:
166166
dict _derived_type_codecs
167167
dict _custom_type_codecs
168168

169-
cdef inline Codec get_codec(self, uint32_t oid, ServerDataFormat format)
169+
cdef inline Codec get_codec(self, uint32_t oid, ServerDataFormat format,
170+
bint disable_custom_codec=*)
170171
cdef inline Codec get_any_local_codec(self, uint32_t oid)

asyncpg/protocol/codecs/base.pyx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -692,18 +692,20 @@ cdef class DataCodecConfig:
692692

693693
return codec
694694

695-
cdef inline Codec get_codec(self, uint32_t oid, ServerDataFormat format):
695+
cdef inline Codec get_codec(self, uint32_t oid, ServerDataFormat format,
696+
bint disable_custom_codec=False):
696697
cdef Codec codec
697698

698-
codec = self.get_any_local_codec(oid)
699-
if codec is not None:
700-
if codec.format != format:
701-
# The codec for this OID has been overridden by
702-
# set_{builtin}_type_codec with a different format.
703-
# We must respect that and not return a core codec.
704-
return None
705-
else:
706-
return codec
699+
if not disable_custom_codec:
700+
codec = self.get_any_local_codec(oid)
701+
if codec is not None:
702+
if codec.format != format:
703+
# The codec for this OID has been overridden by
704+
# set_{builtin}_type_codec with a different format.
705+
# We must respect that and not return a core codec.
706+
return None
707+
else:
708+
return codec
707709

708710
codec = get_core_codec(oid, format)
709711
if codec is not None:

asyncpg/protocol/prepared_stmt.pxd

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ cdef class PreparedStatementState:
2929
tuple rows_codecs
3030

3131
cdef _encode_bind_msg(self, args)
32-
cpdef _init_codecs(self)
33-
cdef _ensure_rows_decoder(self)
34-
cdef _ensure_args_encoder(self)
32+
cpdef _init_codecs(self, bint disable_custom_codec)
33+
cdef _ensure_rows_decoder(self, bint disable_custom_codec)
34+
cdef _ensure_args_encoder(self, bint disable_custom_codec)
3535
cdef _set_row_desc(self, object desc)
3636
cdef _set_args_desc(self, object desc)
3737
cdef _decode_row(self, const char* cbuf, ssize_t buf_len)

asyncpg/protocol/prepared_stmt.pyx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,10 @@ cdef class PreparedStatementState:
8686

8787
return missing
8888

89-
cpdef _init_codecs(self):
90-
self._ensure_args_encoder()
91-
self._ensure_rows_decoder()
89+
cpdef _init_codecs(self, bint disable_custom_codec):
90+
91+
self._ensure_args_encoder(disable_custom_codec)
92+
self._ensure_rows_decoder(disable_custom_codec)
9293

9394
def attach(self):
9495
self.refs += 1
@@ -180,7 +181,7 @@ cdef class PreparedStatementState:
180181

181182
return writer
182183

183-
cdef _ensure_rows_decoder(self):
184+
cdef _ensure_rows_decoder(self, bint disable_custom_codec):
184185
cdef:
185186
list cols_names
186187
object cols_mapping
@@ -205,7 +206,8 @@ cdef class PreparedStatementState:
205206
cols_mapping[col_name] = i
206207
cols_names.append(col_name)
207208
oid = row[3]
208-
codec = self.settings.get_data_codec(oid)
209+
codec = self.settings.get_data_codec(
210+
oid, disable_custom_codec=disable_custom_codec)
209211
if codec is None or not codec.has_decoder():
210212
raise exceptions.InternalClientError(
211213
'no decoder for OID {}'.format(oid))
@@ -219,7 +221,7 @@ cdef class PreparedStatementState:
219221

220222
self.rows_codecs = tuple(codecs)
221223

222-
cdef _ensure_args_encoder(self):
224+
cdef _ensure_args_encoder(self, bint disable_custom_codec):
223225
cdef:
224226
uint32_t p_oid
225227
Codec codec
@@ -230,7 +232,8 @@ cdef class PreparedStatementState:
230232

231233
for i from 0 <= i < self.args_num:
232234
p_oid = self.parameters_desc[i]
233-
codec = self.settings.get_data_codec(p_oid)
235+
codec = self.settings.get_data_codec(
236+
p_oid, disable_custom_codec=disable_custom_codec)
234237
if codec is None or not codec.has_encoder():
235238
raise exceptions.InternalClientError(
236239
'no encoder for OID {}'.format(p_oid))

asyncpg/protocol/protocol.pyx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ cdef class BaseProtocol(CoreProtocol):
411411
# No header extension
412412
wbuf.write_int32(0)
413413

414-
record_stmt._ensure_rows_decoder()
414+
record_stmt._ensure_rows_decoder(False)
415415
codecs = record_stmt.rows_codecs
416416
num_cols = len(codecs)
417417
settings = self.settings

asyncpg/protocol/settings.pxd

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@ cdef class ConnectionSettings(pgproto.CodecContext):
2626
cpdef inline set_builtin_type_codec(
2727
self, typeoid, typename, typeschema, typekind, alias_to, format)
2828
cpdef inline Codec get_data_codec(
29-
self, uint32_t oid, ServerDataFormat format=*)
29+
self, uint32_t oid, ServerDataFormat format=*,
30+
bint disable_custom_codec=*)

asyncpg/protocol/settings.pyx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,18 @@ cdef class ConnectionSettings(pgproto.CodecContext):
8787
typekind, alias_to, _format)
8888

8989
cpdef inline Codec get_data_codec(self, uint32_t oid,
90-
ServerDataFormat format=PG_FORMAT_ANY):
90+
ServerDataFormat format=PG_FORMAT_ANY,
91+
bint disable_custom_codec=False):
9192
if format == PG_FORMAT_ANY:
92-
codec = self._data_codecs.get_codec(oid, PG_FORMAT_BINARY)
93+
codec = self._data_codecs.get_codec(
94+
oid, PG_FORMAT_BINARY, disable_custom_codec)
9395
if codec is None:
94-
codec = self._data_codecs.get_codec(oid, PG_FORMAT_TEXT)
96+
codec = self._data_codecs.get_codec(
97+
oid, PG_FORMAT_TEXT, disable_custom_codec)
9598
return codec
9699
else:
97-
return self._data_codecs.get_codec(oid, format)
100+
return self._data_codecs.get_codec(
101+
oid, format, disable_custom_codec)
98102

99103
def __getattr__(self, name):
100104
if not name.startswith('_'):

tests/test_introspection.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,20 @@ def tearDownClass(cls):
4343

4444
super().tearDownClass()
4545

46+
def setUp(self):
47+
super().setUp()
48+
self.loop.run_until_complete(self._add_custom_codec(self.con))
49+
50+
async def _add_custom_codec(self, conn):
51+
# mess up with the codec - builtin introspection shouldn't be affected
52+
await conn.set_type_codec(
53+
"oid",
54+
schema="pg_catalog",
55+
encoder=lambda value: None,
56+
decoder=lambda value: None,
57+
format="text",
58+
)
59+
4660
@tb.with_connection_options(database='asyncpg_intro_test')
4761
async def test_introspection_on_large_db(self):
4862
await self.con.execute(
@@ -142,6 +156,7 @@ async def test_introspection_retries_after_cache_bust(self):
142156
# query would cause introspection to retry.
143157
slow_intro_conn = await self.connect(
144158
connection_class=SlowIntrospectionConnection)
159+
await self._add_custom_codec(slow_intro_conn)
145160
try:
146161
await self.con.execute('''
147162
CREATE DOMAIN intro_1_t AS int;

0 commit comments

Comments
 (0)