Compare commits

...

4 Commits

Author SHA1 Message Date
Florian Apolloner
a834e61820
Merge 2709ee9cf1 into 6c37a2fbb2 2025-08-30 23:22:25 -07:00
Clifford Gama
6c37a2fbb2
Removed outdated deprecation note in 6.0 release notes. 2025-08-31 08:18:23 +02:00
Clifford Gama
21603c5b50
Removed unused import in docs/ref/models/expressions.txt example. 2025-08-31 08:15:13 +02:00
Florian Apolloner
2709ee9cf1 Harmonize cursors used by psycopg3 and add more docs. 2025-03-18 22:02:59 +01:00
3 changed files with 53 additions and 42 deletions

View File

@ -269,20 +269,12 @@ class DatabaseWrapper(BaseDatabaseWrapper):
conn_params.pop("assume_role", None)
conn_params.pop("isolation_level", None)
conn_params.pop("server_side_binding", None)
pool_options = conn_params.pop("pool", None)
if pool_options and not is_psycopg3:
raise ImproperlyConfigured("Database pooling requires psycopg >= 3")
server_side_binding = conn_params.pop("server_side_binding", None)
conn_params.setdefault(
"cursor_factory",
(
ServerBindingCursor
if is_psycopg3 and server_side_binding is True
else Cursor
),
)
if settings_dict["USER"]:
conn_params["user"] = settings_dict["USER"]
if settings_dict["PASSWORD"]:
@ -297,9 +289,8 @@ class DatabaseWrapper(BaseDatabaseWrapper):
)
# Disable prepared statements by default to keep connection poolers
# working. Can be reenabled via OPTIONS in the settings dict.
conn_params["prepare_threshold"] = conn_params.pop(
"prepare_threshold", None
)
if "prepare_threshold" not in conn_params:
conn_params["prepare_threshold"] = None
return conn_params
@async_unsafe
@ -406,28 +397,20 @@ class DatabaseWrapper(BaseDatabaseWrapper):
@async_unsafe
def create_cursor(self, name=None):
ssb = self.settings_dict["OPTIONS"].get("server_side_binding")
cursor_factory = self.settings_dict["OPTIONS"].get("cursor_factory")
if name:
if is_psycopg3 and (
self.settings_dict["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
)
# In autocommit mode, the cursor will be used outside of a
# transaction, hence use a holdable cursor.
cursor = _server_cursor_factory(ssb, cursor_factory)(
self.connection,
name=name,
scrollable=False,
withhold=self.connection.autocommit,
)
else:
cursor = self.connection.cursor()
cursor = _cursor_factory(ssb, cursor_factory)(self.connection)
if is_psycopg3:
# Register the cursor timezone only if the connection disagrees, to
@ -572,15 +555,32 @@ if is_psycopg3:
return args
class ServerBindingCursor(CursorMixin, Database.Cursor):
pass
"""
Cursor that performs server-side parameter binding.
This is the default in psycopg3.
"""
class Cursor(CursorMixin, Database.ClientCursor):
pass
class ServerSideCursor(
CursorMixin, Database.client_cursor.ClientCursorMixin, Database.ServerCursor
):
"""
Cursor that performs client-side parameter binding.
This is the default in Django.
"""
class ServerCursor(CursorMixin, Database.ServerCursor):
"""
Cursor that performs server-side parameter binding and uses
server side cursors.
This is the default for named cursors in psycopg3.
"""
class ServerSideCursor(Database.client_cursor.ClientCursorMixin, ServerCursor):
"""
Cursor that performs client-side parameter binding and uses
server side cursors.
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).
@ -592,8 +592,18 @@ if is_psycopg3:
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.
This is the default for named cursors in Django.
"""
def _cursor_factory(server_side_binding, cursor_factory):
if cursor_factory:
return cursor_factory
return ServerBindingCursor if server_side_binding else Cursor
def _server_cursor_factory(server_side_binding, cursor_factory):
return ServerCursor if server_side_binding else ServerSideCursor
class CursorDebugWrapper(BaseCursorDebugWrapper):
def copy(self, statement):
with self.debug_sql(statement):
@ -602,6 +612,11 @@ if is_psycopg3:
else:
Cursor = psycopg2.extensions.cursor
def _cursor_factory(server_side_binding, cursor_factory):
return cursor_factory or Cursor
_server_cursor_factory = _cursor_factory
class CursorDebugWrapper(BaseCursorDebugWrapper):
def copy_expert(self, sql, file, *args):
with self.debug_sql(sql):

View File

@ -1220,7 +1220,6 @@ values. It will return the first column or value that isn't ``NULL``.
We'll start by defining the template to be used for SQL generation and
an ``__init__()`` method to set some attributes::
import copy
from django.db.models import Expression

View File

@ -583,9 +583,6 @@ Miscellaneous
* The undocumented ``django.core.mail.forbid_multi_line_headers()`` and
``django.core.mail.message.sanitize_address()`` functions are deprecated.
* The ``django.utils.crypto.constant_time_compare()`` function is deprecated
because it is merely an alias of :func:`hmac.compare_digest`.
Features removed in 6.0
=======================