summaryrefslogtreecommitdiff
path: root/django/db/backends/postgresql/base.py
diff options
context:
space:
mode:
authorSimon Charette <charette.s@gmail.com>2023-12-15 18:30:35 -0500
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2024-01-12 21:40:18 +0100
commit92d6cff6a2fee7a3f9244081b84fd82c50cc71aa (patch)
tree4dc8b6bda31bb5efa5ee406aa64467fea3c43bad /django/db/backends/postgresql/base.py
parent02eaee12095eebb3d07d02e7b0bdc3f64785d379 (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/backends/postgresql/base.py')
-rw-r--r--django/db/backends/postgresql/base.py42
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):