Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions web/pgadmin/tools/sqleditor/tests/test_server_cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ def runTest(self):
self.assertEqual(response.status_code, 200)
_resp = json.loads(response.data.decode())
self.assertTrue(_resp['data']['server_cursor'])
# The query must actually have executed under the server cursor,
# not merely echoed the server_cursor flag back.
self.assertEqual(_resp['data']['status'], 'Success')
self.assertEqual(len(_resp['data']['result']), 1)
self.assertEqual(_resp['data']['result'][0][0], 1)

self.set_server_cursor(False)

Expand Down
16 changes: 16 additions & 0 deletions web/pgadmin/utils/driver/psycopg3/cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,5 +418,21 @@ def __init__(self, *args, name=None, **kwargs):
_async_server_cursor.__init__(self, name=name, *args, **kwargs)
self.cursor = _async_server_cursor

async def _execute(self, query, params=None, *,
prepare=None, binary=None):
"""
Execute function

Unlike ``AsyncDictCursor``, this does not forward ``prepare`` to
the underlying cursor: ``psycopg``'s ``AsyncServerCursor.execute``
never accepts it (a server-side ``DECLARE CURSOR`` can't be a
prepared statement) and raises ``TypeError`` on any unexpected
keyword, even one whose value is ``None``.
"""
if params is not None and len(params) == 0:
params = None

return await self.cursor.execute(self, query, params, binary=binary)

def get_rowcount(self):
return 1
40 changes: 39 additions & 1 deletion web/pgadmin/utils/tests/test_psycopg3_cursor_signature.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@
here for full ``psycopg.Cursor`` signature parity.
"""

import asyncio
import inspect

from pgadmin.utils.driver.psycopg3.cursor import AsyncDictCursor, DictCursor
from pgadmin.utils.driver.psycopg3.cursor import AsyncDictCursor, \
AsyncDictServerCursor, DictCursor
from pgadmin.utils.route import BaseTestGenerator


Expand All @@ -49,3 +51,39 @@ def runTest(self):
inspect.Parameter.KEYWORD_ONLY)
self.assertEqual(params['binary'].kind,
inspect.Parameter.KEYWORD_ONLY)


class TestAsyncDictServerCursorDropsPrepare(BaseTestGenerator):
"""
``AsyncDictServerCursor`` accepts ``prepare`` too (it inherits
``AsyncDictCursor.execute`` for ``psycopg.AsyncCursor`` substitutability),
but must NOT forward it any further: the underlying
``psycopg.AsyncServerCursor.execute`` never accepts ``prepare`` (a
server-side ``DECLARE CURSOR`` can't be a prepared statement) and raises
``TypeError`` on any unexpected keyword, even one whose value is
``None``. Without this, every server-cursor query fails with
``TypeError: keyword not supported: prepare``.
"""

def runTest(self):
captured = {}

async def fake_execute(_self, query, params, **kwargs):
captured['query'] = query
captured['params'] = params
captured.update(kwargs)
return _self

fake_underlying_cursor = type(
'FakeServerCursor', (), {'execute': fake_execute})

cur = AsyncDictServerCursor.__new__(AsyncDictServerCursor)
cur.cursor = fake_underlying_cursor

asyncio.run(
cur._execute('SELECT 1', None, prepare=None, binary=None)
)

self.assertNotIn('prepare', captured)
self.assertIn('binary', captured)
self.assertEqual(captured['query'], 'SELECT 1')
Loading