diff options
| author | Simon Charette <charette.s@gmail.com> | 2023-12-15 18:30:35 -0500 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2024-01-12 21:40:18 +0100 |
| commit | 92d6cff6a2fee7a3f9244081b84fd82c50cc71aa (patch) | |
| tree | 4dc8b6bda31bb5efa5ee406aa64467fea3c43bad /django/db | |
| parent | 02eaee12095eebb3d07d02e7b0bdc3f64785d379 (diff) | |
Fixed #35028 -- Disabled server-side bindings for named cursors on psycopg >= 3.
While we provide a `cursor_factory` based on the value of the
`server_side_bindings` option to `psycopg.Connection` it is ignored by
the `cursor` method when `name` is specified for `QuerySet.iterator()`
usage and it causes the usage of `psycopg.ServerCursor` which performs
server-side bindings.
Since the ORM doesn't generates SQL that is suitable for server-side
bindings when dealing with parametrized expressions a specialized cursor
must be used to allow server-side cursors to be used with client-side
bindings.
Thanks Richard Ebeling for the report.
Thanks Florian Apolloner and Daniele Varrazzo for reviews.
Diffstat (limited to 'django/db')
| -rw-r--r-- | django/db/backends/postgresql/base.py | 42 |
1 files changed, 37 insertions, 5 deletions
diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py index d92ad58710..cba89e0cc7 100644 --- a/django/db/backends/postgresql/base.py +++ b/django/db/backends/postgresql/base.py @@ -321,11 +321,26 @@ class DatabaseWrapper(BaseDatabaseWrapper): @async_unsafe def create_cursor(self, name=None): if name: - # In autocommit mode, the cursor will be used outside of a - # transaction, hence use a holdable cursor. - cursor = self.connection.cursor( - name, scrollable=False, withhold=self.connection.autocommit - ) + if is_psycopg3 and ( + self.settings_dict.get("OPTIONS", {}).get("server_side_binding") + is not True + ): + # psycopg >= 3 forces the usage of server-side bindings for + # named cursors so a specialized class that implements + # server-side cursors while performing client-side bindings + # must be used if `server_side_binding` is disabled (default). + cursor = ServerSideCursor( + self.connection, + name=name, + scrollable=False, + withhold=self.connection.autocommit, + ) + else: + # In autocommit mode, the cursor will be used outside of a + # transaction, hence use a holdable cursor. + cursor = self.connection.cursor( + name, scrollable=False, withhold=self.connection.autocommit + ) else: cursor = self.connection.cursor() @@ -469,6 +484,23 @@ if is_psycopg3: class Cursor(CursorMixin, Database.ClientCursor): pass + class ServerSideCursor( + CursorMixin, Database.client_cursor.ClientCursorMixin, Database.ServerCursor + ): + """ + psycopg >= 3 forces the usage of server-side bindings when using named + cursors but the ORM doesn't yet support the systematic generation of + prepareable SQL (#20516). + + ClientCursorMixin forces the usage of client-side bindings while + ServerCursor implements the logic required to declare and scroll + through named cursors. + + Mixing ClientCursorMixin in wouldn't be necessary if Cursor allowed to + specify how parameters should be bound instead, which ServerCursor + would inherit, but that's not the case. + """ + class CursorDebugWrapper(BaseCursorDebugWrapper): def copy(self, statement): with self.debug_sql(statement): |
