]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
docs: add preview of template strings query documentation
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sun, 25 May 2025 22:22:13 +0000 (23:22 +0100)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Mon, 26 May 2025 01:20:40 +0000 (02:20 +0100)
docs/basic/index.rst
docs/basic/tstrings.rst [new file with mode: 0644]

index bf9e27da4cb53d789a7375c5c3373893709bf339..df1d8e0dbf70f254bc5105d5691a5883d6ad2691 100644 (file)
@@ -19,6 +19,7 @@ the database <usage>` or :ref:`loading data using COPY <copy>`.
     install
     usage
     params
+    tstrings
     adapt
     pgtypes
     transactions
diff --git a/docs/basic/tstrings.rst b/docs/basic/tstrings.rst
new file mode 100644 (file)
index 0000000..9adabd2
--- /dev/null
@@ -0,0 +1,169 @@
+.. currentmodule:: psycopg
+
+.. index::
+    pair: Query; Template strings
+
+.. _template-strings:
+
+Template string queries
+=======================
+
+.. versionadded:: 3.3
+
+.. warning::
+
+    This is an experimental feature, still `under active development`__ and
+    only documented here for preview. Details may change before Psycopg 3.3
+    release.
+
+    .. __: https://github.com/psycopg/psycopg/pull/1054
+
+    Template strings are a Python language feature under active development
+    too, planned for release in Python 3.14. Template string queries are
+    currently tested in Python 3.14 beta 1.
+
+Psycopg can process queries expressed as `template strings`__ defined in
+:pep:`750` and implemented for the first time in Python 3.14.
+
+.. __: https://docs.python.org/3.14/whatsnew/3.14.html#pep-750-template-strings
+
+Template strings are similar to f-strings__: they are string literals
+interspersed with variables or expressions marked by ``{}``. They use a ``t``
+prefix instead of ``f``, and can be used to express queries:
+
+.. __: https://docs.python.org/3/tutorial/inputoutput.html#tut-f-strings
+
+.. code:: python
+
+    cursor.execute(t"SELECT * FROM mytable WHERE id = {id}")
+
+The difference between the two is that f-strings are immediately evaluated by
+Python and passed to the rest of the program as an already formatted regular
+string; t-strings, instead, are evaluated by Psycopg, which has a chance to
+process query parameters in a safe way.
+
+For example you can pass to a query some strings parameters, which may contain
+unsafe characters such as ``'``, or come from untrusted sources such as web
+form, and leave Psycopg to perform the right processing:
+
+.. code:: python
+
+    cursor.execute(
+        t"INSERT INTO mytable (first_name, last_name) VALUES ({first_name}, {last_name})"
+    )
+
+This statement has the same effect as a classic:
+
+.. code:: python
+
+    cursor.execute(
+        "INSERT INTO mytable (first_name, last_name) VALUES (%s, %s)",
+        (first_name, last_name),
+    )
+
+as Psycopg will be able to send parameters separately from the query, or to
+compose the query on the client side using safe escaping rules, according to
+the :ref:`type of cursor <cursor-types>` used, but it is has the same
+readability of f-strings.
+
+Format specifiers
+-----------------
+
+Format specifiers can be associated to template strings interpolation using a
+``:`` in the placeholder, for example in ``{id:b}``. Psycopg supports a few
+format specifiers specifying `how to pass a parameter to the server`__ and a few
+format specifiers specifying how to compose query parts on the client, in a
+way similar to what can be obtained using the `psycopg.sql` objects.
+
+.. __: http://localhost:8000/basic/params.html#binary-parameters-and-results
+
+The supported specifiers for parameter formats are:
+
+- ``s``: automatic parameter format, similar to using ``%s`` in a classic
+  query. This is the same effect of using no format specifier.
+- ``b``: use the binary format to pass the parameter, similar to using ``%b``
+  in a classic query.
+- ``t``: use the text format to pass the parameter, similar to using ``%t``
+  in a classic query.
+
+The supported specifiers for query composition are:
+
+- ``i``: the parameter is an identifier_, for example a table or column name.
+  The parameter must be a string or a `sql.Identifier` instance.
+- ``l``: the parameter is a literal value, which will be composed to the
+  query on the client. This allows to parametrize statements that :ref:`don't
+  support parametrization in PostgreSQL <server-side-binding>`.
+- ``q``: the parameter is a snippet of statement to be included verbatim in
+  the query. The parameter must be another template string or a `sql.SQL`
+  instance.
+
+.. _identifier: https://www.postgresql.org/docs/current/sql-syntax-lexical.html
+                #SQL-SYNTAX-IDENTIFIERS
+
+
+.. _tstring-template-notify:
+
+Example: NOTIFY
+^^^^^^^^^^^^^^^
+
+The NOTIFY_ command takes a *channel* parameter (an identifier, so it must be
+quoted with double quotes if it contains any non-alphanumeric character), and
+a *payload* parameter as a string (which must be escaped with string syntax,
+hence with single quotes).
+
+.. _NOTIFY: https://www.postgresql.org/docs/current/sql-notify.html
+
+The :sql:`NOTIFY` command cannot be parametrized by PostgreSQL, so it must be
+composed entirely on the server side. Using template strings this could be as
+simple as:
+
+.. code:: python
+
+    def send_notify(conn: Connection, channel: str, payload: str) -> None:
+        conn.execute(t"NOTIFY {channel:i}, {payload:l}")
+
+Calling the function with channel ``foo.bar`` and payload ``O'Reilly`` will
+result in executing the statement ``NOTIFY "foo.bar", 'O''Reilly'``.
+
+
+.. _tstring-template-nested:
+
+Example: nested templates
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+A string template merges literal parts of the query with parameter. It is also
+possible to pass templates to templates in order to compose more and more
+complex and dynamic SQL statements.
+
+For example let's say we have an `!User` Python object mapping to an ``users``
+database table. We might want to implement a `!search()` function to return
+users by a list of ids, by names pattern, by group. This function might
+be written as:
+
+.. code:: python
+
+    def search_users(
+        conn: Connection,
+        ids: Sequence[int] | None = None,
+        name_pattern: str | None = None,
+        group_id: int | None = None,
+    ) -> list[User]:
+        filters = []
+        if ids is not None:
+            filters.append(t"u.id = any({list(ids)})")
+        if name_pattern is not None:
+            filters.append(t"u.name ~* {name_pattern}")
+        if group_id is not None:
+            filters.append(t"u.group_id = {group_id}")
+
+        if not filters:
+            raise TypeError("please specify at least one search parameter")
+        joined = sql.SQL(" AND ").join(filters)
+
+        cur = conn.cursor(row_factory=class_row(User))
+        cur.execute(t"SELECT * FROM users AS u WHERE {joined:q}")
+        return cur.fetchall()
+
+In this example we have used the `sql.SQL.join()` method overload that takes a
+list of templates and returns a joined template in order to create an AND-ed
+sequence of conditions.