reasonably small result sets.
+.. index::
+ double: Cursor; Client-binding
+
+.. _client-side-binding-cursors:
+
+Client-side-binding cursors
+---------------------------
+
+.. versionadded:: 3.1
+
+The previously described :ref:`client-side cursors <client-side-cursors>` send
+the query and the parameters separately to the server. This is the most
+efficient way to process parametrised queries and allows to build several
+features and optimizations. However, not all types of queries can be bound
+server-side; in particular no Data Definition Language query can. See
+:ref:`server-side-binding` for the description of these problems.
+
+The `ClientCursor` (and its `AsyncClientCursor` async counterpart) merge the
+query on the client and send the query and the parameters merged together to
+the server. This allows to parametrize any type of PostgreSQL statement, not
+only queries (:sql:`SELECT`) and Data Manipulation statements (:sql:`INSERT`,
+:sql:`UPDATE`, :sql:`DELETE`).
+
+Using `!ClientCursor`, Psycopg 3 behaviour will be more similar to `psycopg2`
+(which only implements client-side binding) and could be useful to port
+Psycopg 2 programs more easily to Psycopg 3. The objects in the `sql` module
+allow for greater flexibility (for instance to parametrize a table name too,
+not only values); however, for simple cases, a `!ClientCursor` could be the
+right object.
+
+In order to obtain `!ClientCursor` from a connection, you can its
+`~Connection.cursor_factory` (at init time or changing its attribute
+afterwards):
+
+.. code:: python
+
+ from psycopg import connect, ClientCursor
+
+ conn = psycopg.connect(DSN, cursor_factory=ClientCursor)
+ cur = conn.cursor()
+ # <psycopg.ClientCursor [no result] [IDLE] (database=piro) at 0x7fd977ae2880>
+
+If you need to create a one-off client-side-binding cursor out of a normal
+connection, you can just use the `~ClientCursor` class passing the connection
+as argument.
+
+.. code:: python
+
+ conn = psycopg.connect(DSN)
+ cur = psycopg.ClientCursor(conn)
+
+
.. index::
double: Cursor; Server-side
single: Portal
.. _cursor-steal:
"Stealing" an existing cursor
------------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A Psycopg `ServerCursor` can be also used to consume a cursor which was
created in other ways than the :sql:`DECLARE` that `ServerCursor.execute()`
The `!Cursor` class
-------------------
-.. autoclass:: Cursor()
+.. autoclass:: Cursor
This class implements a `DBAPI-compliant interface`__. It is what the
classic `Connection.cursor()` method returns. `AsyncConnection.cursor()`
is text or binary.
+The `!ClientCursor` class
+-------------------------
+
+.. seealso:: See :ref:`client-side-binding-cursors` for details.
+
+.. autoclass:: ClientCursor
+
+ This `Cursor` subclass has exactly the same interface of its parent class,
+ but, instead of sending query and parameters separately to the server, it
+ merges them on the client and sends them as a non-parametric query on the
+ server. This allows, for instance, to execute parametrized data definition
+ statements and other :ref:`problematic queries <server-side-binding>`.
+
+ .. versionadded:: 3.1
+
+ .. automethod:: mogrify
+
+ :param query: The query to execute.
+ :type query: `!str`, `!bytes`, or `sql.Composable`
+ :param params: The parameters to pass to the query, if any.
+ :type params: Sequence or Mapping
+
+
The `!ServerCursor` class
--------------------------
-.. autoclass:: ServerCursor()
+.. seealso:: See :ref:`server-side-cursors` for details.
+
+.. autoclass:: ServerCursor
This class also implements a `DBAPI-compliant interface`__. It is created
by `Connection.cursor()` specifying the ``name`` parameter. Using this
The `!AsyncCursor` class
------------------------
-.. autoclass:: AsyncCursor()
+.. autoclass:: AsyncCursor
This class implements a DBAPI-inspired interface, with all the blocking
methods implemented as coroutines. Unless specified otherwise,
to iterate on the async cursor results.
+The `!AsyncClientCursor` class
+------------------------------
+
+.. autoclass:: AsyncClientCursor
+
+ This class is the `!async` equivalent of the `ClientCursor`. The
+ difference are the same shown in `AsyncCursor`.
+
+ .. versionadded:: 3.1
+
+
+
The `!AsyncServerCursor` class
------------------------------
-.. autoclass:: AsyncServerCursor()
+.. autoclass:: AsyncServerCursor
This class implements a DBAPI-inspired interface as the `AsyncCursor`
does, but wraps a server-side cursor like the `ServerCursor` class. It is
.. __: https://www.postgresql.org/docs/current/sql-notify.html
#id-1.9.3.157.7.5
-If this is not possible, you can use client-side binding using the objects
-from the `sql` module::
+If this is not possible, you must merge the query and the parameter on the
+client side. You can do so using a :ref:`client-side binding cursor
+<client-side-binding-cursors>` such as `ClientCursor`::
- >>> from psycopg import sql
+ >>> cur = ClientCursor(conn)
+ >>> cur.execute("CREATE TABLE foo (id int DEFAULT %s)", [42])
+
+if you need `!ClientCursor` often you can set the `Connection.cursor_factory`
+to have them created by default by `Connection.cursor()`. This way, Psycopg 3
+will behave largely the same way of Psycopg 2.
- >>> conn.execute(sql.SQL("CREATE TABLE foo (id int DEFAULT {})").format(42))
+Note that, using parameters, you can only specify values. If you need to
+parametrize different parts of a statement (aka *the ones that don't go in
+single quotes*) you can use the objects from the `psycopg.sql` module::
- # This will correctly quote the password
- >>> conn.execute(sql.SQL("ALTER USER john SET PASSWORD {}").format(password))
+ >>> from psycopg import sql
+
+ # This will quote the user and the password using the right quotes
+ >>> conn.execute(
+ ... sql.SQL("ALTER USER {} SET PASSWORD {}")
+ ... .format(sql.Identifier(username), password))
.. _multi-statements:
the same `!execute()` call, separating them with a semicolon::
>>> conn.execute(
- ... "insert into foo values (%s); insert into foo values (%s)",
+ ... "INSERT INTO foo VALUES (%s); INSERT INTO foo VALUES (%s)",
... (10, 20))
Traceback (most recent call last):
...
One obvious way to work around the problem is to use several `!execute()`
calls.
-There is no such limitation if no parameters are used. This allows one to generate
-batches of statements entirely on the client side (for instance using the
-`psycopg.sql` objects) and to run them in the same `!execute()` call::
+There is no such limitation if no parameters are used. As a consequence, you
+can use a :ref:`client-side binding cursor <client-side-binding-cursors>` or
+the `psycopg.sql` objects to compose a multiple query on the client side and
+run them in the same `!execute()` call::
+
+
+ >>> cur = psycopg.ClientCursor(conn)
+ >>> cur.execute(
+ ... "INSERT INTO foo VALUES (%s); INSERT INTO foo VALUES (%s)",
+ ... (10, 20))
>>> from psycopg import sql
- >>> query = sql.SQL(
- ... "insert into foo values ({}); insert into foo values ({})"
- ... ).format(10, 20))
- >>> conn.execute(query)
+ >>> conn.execute(
+ ... sql.SQL("INSERT INTO foo VALUES ({}); INSERT INTO foo values ({})"
+ ... .format(10, 20))
Note that statements that must be run outside a transaction (such as
:sql:`CREATE DATABASE`) can never be executed in batch with other statements,
even if the connection is in autocommit mode::
>>> conn.autocommit = True
- >>> conn.execute("create database foo; select 1")
+ >>> conn.execute("CREATE DATABASE foo; SELECT 1")
Traceback (most recent call last):
...
psycopg.errors.ActiveSqlTransaction: CREATE DATABASE cannot run inside a transaction block
^^^^^^^^^^^^^^^^^^^^^^^^
- Add :ref:`Pipeline mode <pipeline-mode>` (:ticket:`#74`).
+- Add :ref:`client-side-binding-cursors` (:ticket:`#101`).
- Add :ref:`Two-Phase Commit <two-phase-commit>` support (:ticket:`#72`).
- Add :ref:`adapt-enum` (:ticket:`#274`).
- Add ``returning`` parameter to `~Cursor.executemany()` to retrieve query
class ClientCursorMixin(BaseCursor[ConnectionType, Row]):
def mogrify(self, query: Query, params: Optional[Params] = None) -> str:
"""
- Return the query to be executed with parameters merged.
+ Return the query and parameters merged.
+
+ Parameters are adapted and merged to the query the same way that
+ `!execute()` would do.
+
"""
self._tx = adapt.Transformer(self)
pgq = self._convert_query(query, params)