mirror of https://github.com/django/django.git
Fixed #34255 -- Made PostgreSQL backend use client-side parameters binding with psycopg version 3.
Thanks Guillaume Andreu Sabater for the report. Co-authored-by: Florian Apolloner <apollo13@users.noreply.github.com>
This commit is contained in:
parent
c8a76059ff
commit
0e2649fdf4
|
@ -144,6 +144,16 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
|||
},
|
||||
}
|
||||
)
|
||||
if "ONLY_FULL_GROUP_BY" in self.connection.sql_mode:
|
||||
skips.update(
|
||||
{
|
||||
"GROUP BY cannot contain nonaggregated column when "
|
||||
"ONLY_FULL_GROUP_BY mode is enabled on MySQL, see #34262.": {
|
||||
"aggregation.tests.AggregateTestCase."
|
||||
"test_group_by_nested_expression_with_params",
|
||||
},
|
||||
}
|
||||
)
|
||||
return skips
|
||||
|
||||
@cached_property
|
||||
|
|
|
@ -223,6 +223,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|||
|
||||
conn_params.pop("assume_role", None)
|
||||
conn_params.pop("isolation_level", None)
|
||||
conn_params.pop("server_side_binding", None)
|
||||
if settings_dict["USER"]:
|
||||
conn_params["user"] = settings_dict["USER"]
|
||||
if settings_dict["PASSWORD"]:
|
||||
|
@ -268,7 +269,13 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|||
connection = self.Database.connect(**conn_params)
|
||||
if set_isolation_level:
|
||||
connection.isolation_level = self.isolation_level
|
||||
if not is_psycopg3:
|
||||
if is_psycopg3:
|
||||
connection.cursor_factory = (
|
||||
ServerBindingCursor
|
||||
if options.get("server_side_binding") is True
|
||||
else Cursor
|
||||
)
|
||||
else:
|
||||
# Register dummy loads() to avoid a round trip from psycopg2's
|
||||
# decode to json.dumps() to json.loads(), when using a custom
|
||||
# decoder in JSONField.
|
||||
|
@ -436,7 +443,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|||
|
||||
if is_psycopg3:
|
||||
|
||||
class Cursor(Database.Cursor):
|
||||
class CursorMixin:
|
||||
"""
|
||||
A subclass of psycopg cursor implementing callproc.
|
||||
"""
|
||||
|
@ -457,6 +464,12 @@ if is_psycopg3:
|
|||
self.execute(stmt)
|
||||
return args
|
||||
|
||||
class ServerBindingCursor(CursorMixin, Database.Cursor):
|
||||
pass
|
||||
|
||||
class Cursor(CursorMixin, Database.ClientCursor):
|
||||
pass
|
||||
|
||||
class CursorDebugWrapper(BaseCursorDebugWrapper):
|
||||
def copy(self, statement):
|
||||
with self.debug_sql(statement):
|
||||
|
|
|
@ -117,9 +117,6 @@ PostgreSQL notes
|
|||
Django supports PostgreSQL 12 and higher. `psycopg`_ 3.1.8+ or `psycopg2`_
|
||||
2.8.4+ is required, though the latest `psycopg`_ 3.1.8+ is recommended.
|
||||
|
||||
.. _psycopg: https://www.psycopg.org/psycopg3/
|
||||
.. _psycopg2: https://www.psycopg.org/
|
||||
|
||||
.. note::
|
||||
|
||||
Support for ``psycopg2`` is likely to be deprecated and removed at some
|
||||
|
@ -251,6 +248,31 @@ database configuration in :setting:`DATABASES`::
|
|||
},
|
||||
}
|
||||
|
||||
.. _database-server-side-parameters-binding:
|
||||
|
||||
Server-side parameters binding
|
||||
------------------------------
|
||||
|
||||
.. versionadded:: 4.2
|
||||
|
||||
With `psycopg`_ 3.1.8+, Django defaults to the :ref:`client-side binding
|
||||
cursors <psycopg:client-side-binding-cursors>`. If you want to use the
|
||||
:ref:`server-side binding <psycopg:server-side-binding>` set it in the
|
||||
:setting:`OPTIONS` part of your database configuration in
|
||||
:setting:`DATABASES`::
|
||||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql",
|
||||
# ...
|
||||
"OPTIONS": {
|
||||
"server_side_binding": True,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
This option is ignored with ``psycopg2``.
|
||||
|
||||
Indexes for ``varchar`` and ``text`` columns
|
||||
--------------------------------------------
|
||||
|
||||
|
@ -373,6 +395,9 @@ non-durable <https://www.postgresql.org/docs/current/non-durability.html>`_.
|
|||
a development machine where you can easily restore the entire contents of
|
||||
all databases in the cluster.
|
||||
|
||||
.. _psycopg: https://www.psycopg.org/psycopg3/
|
||||
.. _psycopg2: https://www.psycopg.org/
|
||||
|
||||
.. _mariadb-notes:
|
||||
|
||||
MariaDB notes
|
||||
|
|
|
@ -254,6 +254,10 @@ Database backends
|
|||
* The new ``"assume_role"`` option is now supported in :setting:`OPTIONS` on
|
||||
PostgreSQL to allow specifying the :ref:`session role <database-role>`.
|
||||
|
||||
* The new ``"server_side_binding"`` option is now supported in
|
||||
:setting:`OPTIONS` on PostgreSQL with ``psycopg`` 3.1.8+ to allow using
|
||||
:ref:`server-side binding cursors <database-server-side-parameters-binding>`.
|
||||
|
||||
Decorators
|
||||
~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ from django.db.models.functions import (
|
|||
Cast,
|
||||
Coalesce,
|
||||
Greatest,
|
||||
Least,
|
||||
Lower,
|
||||
Now,
|
||||
Pi,
|
||||
|
@ -1614,6 +1615,20 @@ class AggregateTestCase(TestCase):
|
|||
).annotate(total=Count("*"))
|
||||
self.assertEqual(dict(has_long_books_breakdown), {True: 2, False: 3})
|
||||
|
||||
def test_group_by_nested_expression_with_params(self):
|
||||
books_qs = (
|
||||
Book.objects.annotate(greatest_pages=Greatest("pages", Value(600)))
|
||||
.values(
|
||||
"greatest_pages",
|
||||
)
|
||||
.annotate(
|
||||
min_pages=Min("pages"),
|
||||
least=Least("min_pages", "greatest_pages"),
|
||||
)
|
||||
.values_list("least", flat=True)
|
||||
)
|
||||
self.assertCountEqual(books_qs, [300, 946, 1132])
|
||||
|
||||
@skipUnlessDBFeature("supports_subqueries_in_group_by")
|
||||
def test_aggregation_subquery_annotation_related_field(self):
|
||||
publisher = Publisher.objects.create(name=self.a9.name, num_awards=2)
|
||||
|
|
|
@ -277,6 +277,25 @@ class Tests(TestCase):
|
|||
finally:
|
||||
new_connection.close()
|
||||
|
||||
@unittest.skipUnless(is_psycopg3, "psycopg3 specific test")
|
||||
def test_connect_server_side_binding(self):
|
||||
"""
|
||||
The server-side parameters binding role can be enabled with DATABASES
|
||||
["OPTIONS"]["server_side_binding"].
|
||||
"""
|
||||
from django.db.backends.postgresql.base import ServerBindingCursor
|
||||
|
||||
new_connection = connection.copy()
|
||||
new_connection.settings_dict["OPTIONS"]["server_side_binding"] = True
|
||||
try:
|
||||
new_connection.connect()
|
||||
self.assertEqual(
|
||||
new_connection.connection.cursor_factory,
|
||||
ServerBindingCursor,
|
||||
)
|
||||
finally:
|
||||
new_connection.close()
|
||||
|
||||
def test_connect_no_is_usable_checks(self):
|
||||
new_connection = connection.copy()
|
||||
try:
|
||||
|
|
Loading…
Reference in New Issue