Skip to content

Commit a6d08db

Browse files
committed
Use the general statement cache for type introspection
Type introspection queries now rely on the general statement cache instead of ad-hoc prepared statements. Fixes: #198.
1 parent eec98b0 commit a6d08db

File tree

2 files changed

+44
-29
lines changed

2 files changed

+44
-29
lines changed

asyncpg/connection.py

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ class Connection(metaclass=ConnectionMeta):
3838
Connections are created by calling :func:`~asyncpg.connection.connect`.
3939
"""
4040

41-
__slots__ = ('_protocol', '_transport', '_loop', '_types_stmt',
42-
'_type_by_name_stmt', '_top_xact', '_uid', '_aborted',
41+
__slots__ = ('_protocol', '_transport', '_loop',
42+
'_top_xact', '_uid', '_aborted',
4343
'_pool_release_ctr', '_stmt_cache', '_stmts_to_close',
4444
'_listeners', '_server_version', '_server_caps',
4545
'_intro_query', '_reset_query', '_proxy',
@@ -53,8 +53,6 @@ def __init__(self, protocol, transport, loop,
5353
self._protocol = protocol
5454
self._transport = transport
5555
self._loop = loop
56-
self._types_stmt = None
57-
self._type_by_name_stmt = None
5856
self._top_xact = None
5957
self._uid = 0
6058
self._aborted = False
@@ -274,7 +272,7 @@ async def _get_statement(self, query, timeout, *, named: bool=False):
274272
# Only use the cache when:
275273
# * `statement_cache_size` is greater than 0;
276274
# * query size is less than `max_cacheable_statement_size`.
277-
use_cache = self._stmt_cache.get_max_size() > 0
275+
use_cache = cache_enabled = self._stmt_cache.get_max_size() > 0
278276
if (use_cache and
279277
self._config.max_cacheable_statement_size and
280278
len(query) > self._config.max_cacheable_statement_size):
@@ -286,14 +284,17 @@ async def _get_statement(self, query, timeout, *, named: bool=False):
286284
stmt_name = ''
287285

288286
statement = await self._protocol.prepare(stmt_name, query, timeout)
289-
290287
ready = statement._init_types()
291288
if ready is not True:
292-
if self._types_stmt is None:
293-
self._types_stmt = await self.prepare(self._intro_query)
294-
295-
types = await self._types_stmt.fetch(list(ready))
289+
types = await self.__execute(self._intro_query, (list(ready),),
290+
0, timeout)
296291
self._protocol.get_settings().register_data_types(types)
292+
if not cache_enabled:
293+
# The execution of the introspection query with statement
294+
# cache turned off has blown away the anonymous statement
295+
# we've prepared for the query, so we need to re-prepare it.
296+
statement = await self._protocol.prepare(
297+
stmt_name, query, timeout)
297298

298299
if use_cache:
299300
self._stmt_cache.put(query, statement)
@@ -886,12 +887,8 @@ async def set_type_codec(self, typename, *,
886887
"asyncpg 0.13.0. Use the `format` keyword argument instead.",
887888
DeprecationWarning, stacklevel=2)
888889

889-
if self._type_by_name_stmt is None:
890-
self._type_by_name_stmt = await self.prepare(
891-
introspection.TYPE_BY_NAME)
892-
893-
typeinfo = await self._type_by_name_stmt.fetchrow(
894-
typename, schema)
890+
typeinfo = await self.fetchrow(
891+
introspection.TYPE_BY_NAME, typename, schema)
895892
if not typeinfo:
896893
raise ValueError('unknown type: {}.{}'.format(schema, typename))
897894

@@ -921,12 +918,8 @@ async def reset_type_codec(self, typename, *, schema='public'):
921918
.. versionadded:: 0.12.0
922919
"""
923920

924-
if self._type_by_name_stmt is None:
925-
self._type_by_name_stmt = await self.prepare(
926-
introspection.TYPE_BY_NAME)
927-
928-
typeinfo = await self._type_by_name_stmt.fetchrow(
929-
typename, schema)
921+
typeinfo = await self.fetchrow(
922+
introspection.TYPE_BY_NAME, typename, schema)
930923
if not typeinfo:
931924
raise ValueError('unknown type: {}.{}'.format(schema, typename))
932925

@@ -949,12 +942,8 @@ async def set_builtin_type_codec(self, typename, *,
949942
"""
950943
self._check_open()
951944

952-
if self._type_by_name_stmt is None:
953-
self._type_by_name_stmt = await self.prepare(
954-
introspection.TYPE_BY_NAME)
955-
956-
typeinfo = await self._type_by_name_stmt.fetchrow(
957-
typename, schema)
945+
typeinfo = await self.fetchrow(
946+
introspection.TYPE_BY_NAME, typename, schema)
958947
if not typeinfo:
959948
raise ValueError('unknown type: {}.{}'.format(schema, typename))
960949

@@ -1215,6 +1204,13 @@ async def _execute(self, query, args, limit, timeout, return_status=False):
12151204
with self._stmt_exclusive_section:
12161205
return await self._do_execute(query, executor, timeout)
12171206

1207+
async def __execute(self, query, args, limit, timeout,
1208+
return_status=False):
1209+
executor = lambda stmt, timeout: self._protocol.bind_execute(
1210+
stmt, args, '', limit, return_status, timeout)
1211+
timeout = self._protocol._get_timeout(timeout)
1212+
return await self._do_execute(query, executor, timeout)
1213+
12181214
async def _executemany(self, query, args, timeout):
12191215
executor = lambda stmt, timeout: self._protocol.bind_execute_many(
12201216
stmt, args, '', timeout)

tests/test_introspection.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
MAX_RUNTIME = 0.1
1212

1313

14-
class TestTimeout(tb.ConnectedTestCase):
14+
class TestIntrospection(tb.ConnectedTestCase):
1515
@classmethod
1616
def setUpClass(cls):
1717
super().setUpClass()
@@ -44,3 +44,22 @@ async def test_introspection_on_large_db(self):
4444

4545
with self.assertRunUnder(MAX_RUNTIME):
4646
await self.con.fetchval('SELECT $1::int[]', [1, 2])
47+
48+
@tb.with_connection_options(statement_cache_size=0)
49+
async def test_introspection_no_stmt_cache(self):
50+
# Check that type introspection does not use prepared statements
51+
# when the statement cache is disabled.
52+
self.assertEqual(self.con._stmt_cache.get_max_size(), 0)
53+
await self.con.fetchval('SELECT $1::int[]', [1, 2])
54+
55+
await self.con.execute('''
56+
CREATE EXTENSION IF NOT EXISTS hstore
57+
''')
58+
59+
try:
60+
await self.con.set_builtin_type_codec(
61+
'hstore', codec_name='pg_contrib.hstore')
62+
finally:
63+
await self.con.execute('''
64+
DROP EXTENSION hstore
65+
''')

0 commit comments

Comments
 (0)