Skip to content

Commit 302ba49

Browse files
authored
Reparse unnamed statements before cursor binding (#1364)
With `statement_cache_size=0`, type introspection can replace the unnamed statement created by `prepare()`. The statement is marked unprepared, but awaiting `PreparedStatement.cursor()` binds it without reparsing, so the server receives arguments for the introspection query instead. Make `bind()` honor the unprepared flag, as `bind_execute()` already does to fix this. Fixes #1335. Closes #1345.
1 parent 1511dd6 commit 302ba49

3 files changed

Lines changed: 26 additions & 0 deletions

File tree

‎asyncpg/protocol/coreproto.pyx‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,10 @@ cdef class CoreProtocol:
313313
# ErrorResponse
314314
self._parse_msg_error_response(True)
315315

316+
elif mtype == b'1':
317+
# ParseComplete, in case `_bind()` is reparsing
318+
self.buffer.discard_message()
319+
316320
elif mtype == b'2':
317321
# BindComplete
318322
self.buffer.discard_message()

‎asyncpg/protocol/protocol.pyx‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,9 @@ cdef class BaseProtocol(CoreProtocol):
280280

281281
waiter = self._new_waiter(timeout)
282282
try:
283+
if not state.prepared:
284+
self._send_parse_message(state.name, state.query)
285+
283286
self._bind(
284287
portal_name,
285288
state.name,

‎tests/test_cursor.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,22 @@ async def test_cursor_04(self):
159159
st = await self.con.cursor('SELECT generate_series(0, 100)')
160160
await st.forward(42)
161161
self.assertEqual(await st.fetchrow(), (42,))
162+
163+
@tb.with_connection_options(statement_cache_size=0)
164+
async def test_cursor_05_unnamed_statement_reparsed(self):
165+
await self.con.execute(
166+
"CREATE TYPE cursor_05_t AS ENUM ('foo', 'bar')"
167+
)
168+
try:
169+
async with self.con.transaction():
170+
# Enum introspection replaces the unnamed statement on the
171+
# server, so opening the cursor must re-parse it first.
172+
st = await self.con.prepare('''
173+
SELECT $1::int, $2::int, 'foo'::cursor_05_t
174+
''')
175+
self.assertEqual(st.get_name(), '')
176+
177+
cur = await st.cursor(1, 2)
178+
self.assertEqual(await cur.fetch(1), [(1, 2, 'foo')])
179+
finally:
180+
await self.con.execute('DROP TYPE cursor_05_t')

0 commit comments

Comments
 (0)