`ClientCursor` cient-side client-side ``%s``, ``%(name)s`` :ref:`client-side-binding-cursors`
`ServerCursor` server-side server-side ``%s``, ``%(name)s`` :ref:`server-side-cursors`
`RawCursor` server-side client-side ``$1`` :ref:`raw-query-cursors`
+`RawServerCursor` server-side server-side ``$1`` :ref:`raw-query-cursors`
================= =========== =========== ==================== ==================================
If not specified by a `~Connection.cursor_factory`, `~Connection.cursor()`
in the form of a list or tuple. This means you cannot use named arguments
(i.e., dictionaries).
+`!RawCursor` behaves like `Cursor`, in returning the complete result from the
+server to the client. The `RawServerCursor` and `AsyncRawServerCursor`
+implement :ref:`server-side-cursors` with raw PostgreSQL placeholders.
+
There are two ways to use raw query cursors:
1. Using the cursor factory:
is text or binary.
-The `!RawCursor` class
-----------------------
-
-.. seealso:: See :ref:`raw-query-cursors` for details.
-
-.. autoclass:: RawCursor
-
- This `Cursor` subclass has the same interface of the parent class but
- supports placeholders in PostgreSQL format (``$1``, ``$2``...) rather than
- in Python format (``%s``). Only positional parameters are supported.
-
- .. versionadded:: 3.2
-
-
The `!ClientCursor` class
-------------------------
.. _MOVE: https://www.postgresql.org/docs/current/sql-fetch.html
+The `!RawCursor` and `!RawServerCursor` class
+---------------------------------------------
+
+.. seealso:: See :ref:`raw-query-cursors` for details.
+
+.. autoclass:: RawCursor
+
+ This `Cursor` subclass has the same interface of the parent class but
+ supports placeholders in PostgreSQL format (``$1``, ``$2``...) rather than
+ in Python format (``%s``). Only positional parameters are supported.
+
+ .. versionadded:: 3.2
+
+
+.. autoclass:: RawServerCursor
+
+ This `ServerCursor` subclass has the same interface of the parent class but
+ supports placeholders in PostgreSQL format (``$1``, ``$2``...) rather than
+ in Python format (``%s``). Only positional parameters are supported.
+
+ .. versionadded:: 3.2
+
+
+
Async cursor classes
--------------------
-.. autoclass:: AsyncRawCursor
-
- This class is the `!async` equivalent of `RawCursor`. The differences
- w.r.t. the sync counterpart are the same described in `AsyncCursor`.
-
- .. versionadded:: 3.2
-
-
.. autoclass:: AsyncClientCursor
This class is the `!async` equivalent of `ClientCursor`. The differences
...
.. automethod:: scroll
+
+
+.. autoclass:: AsyncRawCursor
+
+ This class is the `!async` equivalent of `RawCursor`. The differences
+ w.r.t. the sync counterpart are the same described in `AsyncCursor`.
+
+ .. versionadded:: 3.2
+
+
+.. autoclass:: AsyncRawServerCursor
+
+ This class is the `!async` equivalent of `RawServerCursor`. The differences
+ w.r.t. the sync counterpart are the same described in `AsyncServerCursor`.
+
+ .. versionadded:: 3.2
(:ticket:`340`).
- Allow dumpers to return `!None`, to be converted to NULL (:ticket:`#377`).
- Add :ref:`raw-query-cursors` to execute queries using placeholders in
- PostgreSQL format (`$1`, `$2`...) (:ticket:`#560`).
+ PostgreSQL format (`$1`, `$2`...) (:tickets:`#560, #839`).
- Add `psycopg.capabilities` object to :ref:`inspect the libpq capabilities
<capabilities>` (:ticket:`#772`).
- Add `~rows.scalar_row` to return scalar values from a query (:ticket:`#723`).
from .server_cursor import AsyncServerCursor, ServerCursor
from .client_cursor import AsyncClientCursor, ClientCursor
from .raw_cursor import AsyncRawCursor, RawCursor
+from .raw_cursor import AsyncRawServerCursor, RawServerCursor
from ._connection_base import BaseConnection, Notify
from ._connection_info import ConnectionInfo
from .connection_async import AsyncConnection
"AsyncCursor",
"AsyncPipeline",
"AsyncRawCursor",
+ "AsyncRawServerCursor",
"AsyncServerCursor",
"AsyncTransaction",
"BaseConnection",
"Notify",
"Pipeline",
"RawCursor",
+ "RawServerCursor",
"Rollback",
"ServerCursor",
"Transaction",
from ._enums import PyFormat
from .cursor import Cursor
from .cursor_async import AsyncCursor
+from .server_cursor import ServerCursor, AsyncServerCursor
from ._queries import PostgresQuery
from ._cursor_base import BaseCursor
class AsyncRawCursor(RawCursorMixin["AsyncConnection[Any]", Row], AsyncCursor[Row]):
__module__ = "psycopg"
+
+
+class RawServerCursor(RawCursorMixin["Connection[Any]", Row], ServerCursor[Row]):
+ __module__ = "psycopg"
+
+
+class AsyncRawServerCursor(
+ RawCursorMixin["AsyncConnection[Any]", Row], AsyncServerCursor[Row]
+):
+ __module__ = "psycopg"
def ph(cur: Any, query: str) -> str:
"""Change placeholders in a query from %s to $n if testing a raw cursor"""
- if not isinstance(cur, (psycopg.RawCursor, psycopg.AsyncRawCursor)):
+ from psycopg.raw_cursor import RawCursorMixin
+
+ if not isinstance(cur, RawCursorMixin):
return query
if "%(" in query:
# from the original file 'test_cursor_server_async.py'
# DO NOT CHANGE! Change the original file instead.
import pytest
+from packaging.version import parse as ver
import psycopg
from psycopg import pq, rows, errors as e
+from ._test_cursor import ph
pytestmark = pytest.mark.crdb_skip("server-side cursor")
+cursor_classes = [psycopg.ServerCursor]
+# Allow to import (not necessarily to run) the module with psycopg 3.1.
+if ver(psycopg.__version__) >= ver("3.2.0.dev0"):
+ cursor_classes.append(psycopg.RawServerCursor)
+
+
+@pytest.fixture(params=cursor_classes)
+def conn(conn, request, anyio_backend):
+ conn.server_cursor_factory = request.param
+ return conn
+
def test_init_row_factory(conn):
with psycopg.ServerCursor(conn, "foo") as cur:
def test_repr(conn):
cur = conn.cursor("my-name")
- assert "psycopg.%s" % psycopg.ServerCursor.__name__ in str(cur)
+ assert "psycopg.%s" % conn.server_cursor_factory.__name__ in str(cur)
assert "my-name" in repr(cur)
cur.close()
def test_query_params(conn):
with conn.cursor("foo") as cur:
assert cur._query is None
- cur.execute("select generate_series(1, %s) as bar", (3,))
+ cur.execute(ph(cur, "select generate_series(1, %s) as bar"), (3,))
assert cur._query is not None
assert b"declare" in cur._query.query.lower()
assert b"(1, $1)" in cur._query.query.lower()
def test_close_no_clobber(conn):
with pytest.raises(e.DivisionByZero):
with conn.cursor("foo") as cur:
- cur.execute("select 1 / %s", (0,))
+ cur.execute(ph(cur, "select 1 / %s"), (0,))
cur.fetchall()
def test_execute_reuse(conn):
with conn.cursor("foo") as cur:
- cur.execute("select generate_series(1, %s) as foo", (3,))
+ query = ph(cur, "select generate_series(1, %s) as foo")
+ cur.execute(query, (3,))
assert cur.fetchone() == (1,)
- cur.execute("select %s::text as bar, %s::text as baz", ("hello", "world"))
+ query = ph(cur, "select %s::text as bar, %s::text as baz")
+ cur.execute(query, ("hello", "world"))
assert cur.fetchone() == ("hello", "world")
assert cur.description[0].name == "bar"
assert cur.description[0].type_code == cur.adapters.types["text"].oid
def test_executemany(conn):
cur = conn.cursor("foo")
with pytest.raises(e.NotSupportedError):
- cur.executemany("select %s", [(1,), (2,)])
+ cur.executemany(ph(cur, "select %s"), [(1,), (2,)])
cur.close()
def test_fetchone(conn):
with conn.cursor("foo") as cur:
- cur.execute("select generate_series(1, %s) as bar", (2,))
+ cur.execute(ph(cur, "select generate_series(1, %s) as bar"), (2,))
assert cur.fetchone() == (1,)
assert cur.fetchone() == (2,)
assert cur.fetchone() is None
def test_fetchmany(conn):
with conn.cursor("foo") as cur:
- cur.execute("select generate_series(1, %s) as bar", (5,))
+ cur.execute(ph(cur, "select generate_series(1, %s) as bar"), (5,))
assert cur.fetchmany(3) == [(1,), (2,), (3,)]
assert cur.fetchone() == (4,)
assert cur.fetchmany(3) == [(5,)]
def test_fetchall(conn):
with conn.cursor("foo") as cur:
- cur.execute("select generate_series(1, %s) as bar", (3,))
+ cur.execute(ph(cur, "select generate_series(1, %s) as bar"), (3,))
assert cur.fetchall() == [(1,), (2,), (3,)]
assert cur.fetchall() == []
with conn.cursor("foo") as cur:
- cur.execute("select generate_series(1, %s) as bar", (3,))
+ cur.execute(ph(cur, "select generate_series(1, %s) as bar"), (3,))
assert cur.fetchone() == (1,)
assert cur.fetchall() == [(2,), (3,)]
assert cur.fetchall() == []
def test_nextset(conn):
with conn.cursor("foo") as cur:
- cur.execute("select generate_series(1, %s) as bar", (3,))
+ cur.execute(ph(cur, "select generate_series(1, %s) as bar"), (3,))
assert not cur.nextset()
def test_no_result(conn):
with conn.cursor("foo") as cur:
- cur.execute("select generate_series(1, %s) as bar where false", (3,))
+ query = ph(cur, "select generate_series(1, %s) as bar where false")
+ cur.execute(query, (3,))
assert len(cur.description) == 1
assert cur.fetchall() == []
def test_iter(conn):
with conn.cursor("foo") as cur:
- cur.execute("select generate_series(1, %s) as bar", (3,))
+ cur.execute(ph(cur, "select generate_series(1, %s) as bar"), (3,))
recs = list(cur)
assert recs == [(1,), (2,), (3,)]
with conn.cursor("foo") as cur:
- cur.execute("select generate_series(1, %s) as bar", (3,))
+ cur.execute(ph(cur, "select generate_series(1, %s) as bar"), (3,))
assert cur.fetchone() == (1,)
recs = list(cur)
assert recs == [(2,), (3,)]
def test_iter_rownumber(conn):
with conn.cursor("foo") as cur:
- cur.execute("select generate_series(1, %s) as bar", (3,))
+ cur.execute(ph(cur, "select generate_series(1, %s) as bar"), (3,))
for row in cur:
assert cur.rownumber == row[0]
with conn.cursor("foo") as cur:
assert cur.itersize == 100
cur.itersize = 2
- cur.execute("select generate_series(1, %s) as bar", (3,))
+ cur.execute(ph(cur, "select generate_series(1, %s) as bar"), (3,))
commands.popall() # flush begin and other noise
list(cur)
import pytest
+from packaging.version import parse as ver
import psycopg
from psycopg import pq, rows, errors as e
from .acompat import alist
+from ._test_cursor import ph
pytestmark = pytest.mark.crdb_skip("server-side cursor")
+cursor_classes = [psycopg.AsyncServerCursor]
+# Allow to import (not necessarily to run) the module with psycopg 3.1.
+if ver(psycopg.__version__) >= ver("3.2.0.dev0"):
+ cursor_classes.append(psycopg.AsyncRawServerCursor)
+
+
+@pytest.fixture(params=cursor_classes)
+async def aconn(aconn, request, anyio_backend):
+ aconn.server_cursor_factory = request.param
+ return aconn
+
async def test_init_row_factory(aconn):
async with psycopg.AsyncServerCursor(aconn, "foo") as cur:
async def test_repr(aconn):
cur = aconn.cursor("my-name")
- assert "psycopg.%s" % psycopg.AsyncServerCursor.__name__ in str(cur)
+ assert "psycopg.%s" % aconn.server_cursor_factory.__name__ in str(cur)
assert "my-name" in repr(cur)
await cur.close()
async def test_query_params(aconn):
async with aconn.cursor("foo") as cur:
assert cur._query is None
- await cur.execute("select generate_series(1, %s) as bar", (3,))
+ await cur.execute(ph(cur, "select generate_series(1, %s) as bar"), (3,))
assert cur._query is not None
assert b"declare" in cur._query.query.lower()
assert b"(1, $1)" in cur._query.query.lower()
async def test_close_no_clobber(aconn):
with pytest.raises(e.DivisionByZero):
async with aconn.cursor("foo") as cur:
- await cur.execute("select 1 / %s", (0,))
+ await cur.execute(ph(cur, "select 1 / %s"), (0,))
await cur.fetchall()
async def test_execute_reuse(aconn):
async with aconn.cursor("foo") as cur:
- await cur.execute("select generate_series(1, %s) as foo", (3,))
+ query = ph(cur, "select generate_series(1, %s) as foo")
+ await cur.execute(query, (3,))
assert await cur.fetchone() == (1,)
- await cur.execute("select %s::text as bar, %s::text as baz", ("hello", "world"))
+ query = ph(cur, "select %s::text as bar, %s::text as baz")
+ await cur.execute(query, ("hello", "world"))
assert await cur.fetchone() == ("hello", "world")
assert cur.description[0].name == "bar"
assert cur.description[0].type_code == cur.adapters.types["text"].oid
async def test_executemany(aconn):
cur = aconn.cursor("foo")
with pytest.raises(e.NotSupportedError):
- await cur.executemany("select %s", [(1,), (2,)])
+ await cur.executemany(ph(cur, "select %s"), [(1,), (2,)])
await cur.close()
async def test_fetchone(aconn):
async with aconn.cursor("foo") as cur:
- await cur.execute("select generate_series(1, %s) as bar", (2,))
+ await cur.execute(ph(cur, "select generate_series(1, %s) as bar"), (2,))
assert await cur.fetchone() == (1,)
assert await cur.fetchone() == (2,)
assert await cur.fetchone() is None
async def test_fetchmany(aconn):
async with aconn.cursor("foo") as cur:
- await cur.execute("select generate_series(1, %s) as bar", (5,))
+ await cur.execute(ph(cur, "select generate_series(1, %s) as bar"), (5,))
assert await cur.fetchmany(3) == [(1,), (2,), (3,)]
assert await cur.fetchone() == (4,)
assert await cur.fetchmany(3) == [(5,)]
async def test_fetchall(aconn):
async with aconn.cursor("foo") as cur:
- await cur.execute("select generate_series(1, %s) as bar", (3,))
+ await cur.execute(ph(cur, "select generate_series(1, %s) as bar"), (3,))
assert await cur.fetchall() == [(1,), (2,), (3,)]
assert await cur.fetchall() == []
async with aconn.cursor("foo") as cur:
- await cur.execute("select generate_series(1, %s) as bar", (3,))
+ await cur.execute(ph(cur, "select generate_series(1, %s) as bar"), (3,))
assert await cur.fetchone() == (1,)
assert await cur.fetchall() == [(2,), (3,)]
assert await cur.fetchall() == []
async def test_nextset(aconn):
async with aconn.cursor("foo") as cur:
- await cur.execute("select generate_series(1, %s) as bar", (3,))
+ await cur.execute(ph(cur, "select generate_series(1, %s) as bar"), (3,))
assert not cur.nextset()
async def test_no_result(aconn):
async with aconn.cursor("foo") as cur:
- await cur.execute("select generate_series(1, %s) as bar where false", (3,))
+ query = ph(cur, "select generate_series(1, %s) as bar where false")
+ await cur.execute(query, (3,))
assert len(cur.description) == 1
assert (await cur.fetchall()) == []
async def test_iter(aconn):
async with aconn.cursor("foo") as cur:
- await cur.execute("select generate_series(1, %s) as bar", (3,))
+ await cur.execute(ph(cur, "select generate_series(1, %s) as bar"), (3,))
recs = await alist(cur)
assert recs == [(1,), (2,), (3,)]
async with aconn.cursor("foo") as cur:
- await cur.execute("select generate_series(1, %s) as bar", (3,))
+ await cur.execute(ph(cur, "select generate_series(1, %s) as bar"), (3,))
assert await cur.fetchone() == (1,)
recs = await alist(cur)
assert recs == [(2,), (3,)]
async def test_iter_rownumber(aconn):
async with aconn.cursor("foo") as cur:
- await cur.execute("select generate_series(1, %s) as bar", (3,))
+ await cur.execute(ph(cur, "select generate_series(1, %s) as bar"), (3,))
async for row in cur:
assert cur.rownumber == row[0]
async with aconn.cursor("foo") as cur:
assert cur.itersize == 100
cur.itersize = 2
- await cur.execute("select generate_series(1, %s) as bar", (3,))
+ await cur.execute(ph(cur, "select generate_series(1, %s) as bar"), (3,))
acommands.popall() # flush begin and other noise
await alist(cur)
"AsyncPipeline": "Pipeline",
"AsyncQueuedLibpqWriter": "QueuedLibpqWriter",
"AsyncRawCursor": "RawCursor",
+ "AsyncRawServerCursor": "RawServerCursor",
"AsyncRowFactory": "RowFactory",
"AsyncScheduler": "Scheduler",
"AsyncServerCursor": "ServerCursor",