mirror of
https://github.com/django/django.git
synced 2025-06-07 20:49:11 +00:00
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
|
return skips
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
|
@ -223,6 +223,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|||||||
|
|
||||||
conn_params.pop("assume_role", None)
|
conn_params.pop("assume_role", None)
|
||||||
conn_params.pop("isolation_level", None)
|
conn_params.pop("isolation_level", None)
|
||||||
|
conn_params.pop("server_side_binding", None)
|
||||||
if settings_dict["USER"]:
|
if settings_dict["USER"]:
|
||||||
conn_params["user"] = settings_dict["USER"]
|
conn_params["user"] = settings_dict["USER"]
|
||||||
if settings_dict["PASSWORD"]:
|
if settings_dict["PASSWORD"]:
|
||||||
@ -268,7 +269,13 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|||||||
connection = self.Database.connect(**conn_params)
|
connection = self.Database.connect(**conn_params)
|
||||||
if set_isolation_level:
|
if set_isolation_level:
|
||||||
connection.isolation_level = self.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
|
# Register dummy loads() to avoid a round trip from psycopg2's
|
||||||
# decode to json.dumps() to json.loads(), when using a custom
|
# decode to json.dumps() to json.loads(), when using a custom
|
||||||
# decoder in JSONField.
|
# decoder in JSONField.
|
||||||
@ -436,7 +443,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|||||||
|
|
||||||
if is_psycopg3:
|
if is_psycopg3:
|
||||||
|
|
||||||
class Cursor(Database.Cursor):
|
class CursorMixin:
|
||||||
"""
|
"""
|
||||||
A subclass of psycopg cursor implementing callproc.
|
A subclass of psycopg cursor implementing callproc.
|
||||||
"""
|
"""
|
||||||
@ -457,6 +464,12 @@ if is_psycopg3:
|
|||||||
self.execute(stmt)
|
self.execute(stmt)
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
class ServerBindingCursor(CursorMixin, Database.Cursor):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Cursor(CursorMixin, Database.ClientCursor):
|
||||||
|
pass
|
||||||
|
|
||||||
class CursorDebugWrapper(BaseCursorDebugWrapper):
|
class CursorDebugWrapper(BaseCursorDebugWrapper):
|
||||||
def copy(self, statement):
|
def copy(self, statement):
|
||||||
with self.debug_sql(statement):
|
with self.debug_sql(statement):
|
||||||
|
@ -117,9 +117,6 @@ PostgreSQL notes
|
|||||||
Django supports PostgreSQL 12 and higher. `psycopg`_ 3.1.8+ or `psycopg2`_
|
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.
|
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::
|
.. note::
|
||||||
|
|
||||||
Support for ``psycopg2`` is likely to be deprecated and removed at some
|
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
|
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
|
a development machine where you can easily restore the entire contents of
|
||||||
all databases in the cluster.
|
all databases in the cluster.
|
||||||
|
|
||||||
|
.. _psycopg: https://www.psycopg.org/psycopg3/
|
||||||
|
.. _psycopg2: https://www.psycopg.org/
|
||||||
|
|
||||||
.. _mariadb-notes:
|
.. _mariadb-notes:
|
||||||
|
|
||||||
MariaDB notes
|
MariaDB notes
|
||||||
|
@ -254,6 +254,10 @@ Database backends
|
|||||||
* The new ``"assume_role"`` option is now supported in :setting:`OPTIONS` on
|
* The new ``"assume_role"`` option is now supported in :setting:`OPTIONS` on
|
||||||
PostgreSQL to allow specifying the :ref:`session role <database-role>`.
|
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
|
Decorators
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ from django.db.models.functions import (
|
|||||||
Cast,
|
Cast,
|
||||||
Coalesce,
|
Coalesce,
|
||||||
Greatest,
|
Greatest,
|
||||||
|
Least,
|
||||||
Lower,
|
Lower,
|
||||||
Now,
|
Now,
|
||||||
Pi,
|
Pi,
|
||||||
@ -1614,6 +1615,20 @@ class AggregateTestCase(TestCase):
|
|||||||
).annotate(total=Count("*"))
|
).annotate(total=Count("*"))
|
||||||
self.assertEqual(dict(has_long_books_breakdown), {True: 2, False: 3})
|
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")
|
@skipUnlessDBFeature("supports_subqueries_in_group_by")
|
||||||
def test_aggregation_subquery_annotation_related_field(self):
|
def test_aggregation_subquery_annotation_related_field(self):
|
||||||
publisher = Publisher.objects.create(name=self.a9.name, num_awards=2)
|
publisher = Publisher.objects.create(name=self.a9.name, num_awards=2)
|
||||||
|
@ -277,6 +277,25 @@ class Tests(TestCase):
|
|||||||
finally:
|
finally:
|
||||||
new_connection.close()
|
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):
|
def test_connect_no_is_usable_checks(self):
|
||||||
new_connection = connection.copy()
|
new_connection = connection.copy()
|
||||||
try:
|
try:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user