diff --git a/django/db/backends/postgresql/features.py b/django/db/backends/postgresql/features.py index 6170b5501a..16653a0519 100644 --- a/django/db/backends/postgresql/features.py +++ b/django/db/backends/postgresql/features.py @@ -160,6 +160,10 @@ class DatabaseFeatures(BaseDatabaseFeatures): def is_postgresql_16(self): return self.connection.pg_version >= 160000 + @cached_property + def is_postgresql_17(self): + return self.connection.pg_version >= 170000 + supports_unlimited_charfield = True supports_nulls_distinct_unique_constraints = property( operator.attrgetter("is_postgresql_15") diff --git a/django/db/backends/postgresql/operations.py b/django/db/backends/postgresql/operations.py index 4b179ca83f..d89f81bf7e 100644 --- a/django/db/backends/postgresql/operations.py +++ b/django/db/backends/postgresql/operations.py @@ -32,7 +32,9 @@ class DatabaseOperations(BaseDatabaseOperations): "BUFFERS", "COSTS", "GENERIC_PLAN", + "MEMORY", "SETTINGS", + "SERIALIZE", "SUMMARY", "TIMING", "VERBOSE", @@ -365,6 +367,9 @@ class DatabaseOperations(BaseDatabaseOperations): def explain_query_prefix(self, format=None, **options): extra = {} + if serialize := options.pop("serialize", None): + if serialize.upper() in {"TEXT", "BINARY"}: + extra["SERIALIZE"] = serialize.upper() # Normalize options. if options: options = { diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index c6af3dd7f0..ec27936cdb 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -3110,6 +3110,11 @@ there are triggers or if a function is called, even for a ``SELECT`` query. Support for the ``generic_plan`` option on PostgreSQL 16+ was added. +.. versionchanged:: 5.2 + + Support for the ``memory`` and ``serialize`` options on PostgreSQL 17+ was + added. + .. _field-lookups: ``Field`` lookups diff --git a/docs/releases/5.2.txt b/docs/releases/5.2.txt index e103de847b..96007887bc 100644 --- a/docs/releases/5.2.txt +++ b/docs/releases/5.2.txt @@ -277,6 +277,9 @@ Models longer required to be set on SQLite, which supports unlimited ``VARCHAR`` columns. +* :meth:`.QuerySet.explain` now supports the ``memory`` and ``serialize`` + options on PostgreSQL 17+. + Requests and Responses ~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/queries/test_explain.py b/tests/queries/test_explain.py index 67440cb502..95ca913cfc 100644 --- a/tests/queries/test_explain.py +++ b/tests/queries/test_explain.py @@ -90,13 +90,24 @@ class ExplainTests(TestCase): ] if connection.features.is_postgresql_16: test_options.append({"generic_plan": True}) + if connection.features.is_postgresql_17: + test_options.append({"memory": True}) + test_options.append({"serialize": "TEXT", "analyze": True}) + test_options.append({"serialize": "text", "analyze": True}) + test_options.append({"serialize": "BINARY", "analyze": True}) + test_options.append({"serialize": "binary", "analyze": True}) for options in test_options: with self.subTest(**options), transaction.atomic(): with CaptureQueriesContext(connection) as captured_queries: qs.explain(format="text", **options) self.assertEqual(len(captured_queries), 1) for name, value in options.items(): - option = "{} {}".format(name.upper(), "true" if value else "false") + if isinstance(value, str): + option = "{} {}".format(name.upper(), value.upper()) + else: + option = "{} {}".format( + name.upper(), "true" if value else "false" + ) self.assertIn(option, captured_queries[0]["sql"]) @skipUnlessDBFeature("supports_select_union")