Procházet zdrojové kódy

Fixed #35479 -- Dropped support for PostgreSQL 13 and PostGIS 3.0.

Mariusz Felisiak před 10 měsíci
rodič
revize
b049bec7cf

+ 2 - 2
.github/workflows/schedule_tests.yml

@@ -90,7 +90,7 @@ jobs:
     continue-on-error: true
     services:
       postgres:
-        image: postgres:13-alpine
+        image: postgres:14-alpine
         env:
           POSTGRES_DB: django
           POSTGRES_USER: user
@@ -163,7 +163,7 @@ jobs:
     name: Selenium tests, PostgreSQL
     services:
       postgres:
-        image: postgres:13-alpine
+        image: postgres:14-alpine
         env:
           POSTGRES_DB: django
           POSTGRES_USER: user

+ 1 - 1
.github/workflows/selenium.yml

@@ -43,7 +43,7 @@ jobs:
     name: PostgreSQL
     services:
       postgres:
-        image: postgres:13-alpine
+        image: postgres:14-alpine
         env:
           POSTGRES_DB: django
           POSTGRES_USER: user

+ 1 - 1
django/contrib/gis/db/backends/postgis/operations.py

@@ -203,7 +203,7 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations):
                 raise ImproperlyConfigured(
                     'Cannot determine PostGIS version for database "%s" '
                     'using command "SELECT postgis_lib_version()". '
-                    "GeoDjango requires at least PostGIS version 3.0. "
+                    "GeoDjango requires at least PostGIS version 3.1. "
                     "Was the database created from a spatial database "
                     "template?" % self.connection.settings_dict["NAME"]
                 )

+ 1 - 13
django/contrib/postgres/constraints.py

@@ -1,7 +1,7 @@
 from types import NoneType
 
 from django.core.exceptions import ValidationError
-from django.db import DEFAULT_DB_ALIAS, NotSupportedError
+from django.db import DEFAULT_DB_ALIAS
 from django.db.backends.ddl_references import Expressions, Statement, Table
 from django.db.models import BaseConstraint, Deferrable, F, Q
 from django.db.models.expressions import Exists, ExpressionList
@@ -114,7 +114,6 @@ class ExclusionConstraint(BaseConstraint):
         )
 
     def create_sql(self, model, schema_editor):
-        self.check_supported(schema_editor)
         return Statement(
             "ALTER TABLE %(table)s ADD %(constraint)s",
             table=Table(model._meta.db_table, schema_editor.quote_name),
@@ -128,17 +127,6 @@ class ExclusionConstraint(BaseConstraint):
             schema_editor.quote_name(self.name),
         )
 
-    def check_supported(self, schema_editor):
-        if (
-            self.include
-            and self.index_type.lower() == "spgist"
-            and not schema_editor.connection.features.supports_covering_spgist_indexes
-        ):
-            raise NotSupportedError(
-                "Covering exclusion constraints using an SP-GiST index "
-                "require PostgreSQL 14+."
-            )
-
     def deconstruct(self):
         path, args, kwargs = super().deconstruct()
         kwargs["expressions"] = self.expressions

+ 0 - 8
django/contrib/postgres/indexes.py

@@ -1,4 +1,3 @@
-from django.db import NotSupportedError
 from django.db.models import Func, Index
 from django.utils.functional import cached_property
 
@@ -234,13 +233,6 @@ class SpGistIndex(PostgresIndex):
             with_params.append("fillfactor = %d" % self.fillfactor)
         return with_params
 
-    def check_supported(self, schema_editor):
-        if (
-            self.include
-            and not schema_editor.connection.features.supports_covering_spgist_indexes
-        ):
-            raise NotSupportedError("Covering SP-GiST indexes require PostgreSQL 14+.")
-
 
 class OpClass(Func):
     template = "%(expressions)s %(name)s"

+ 1 - 7
django/db/backends/postgresql/features.py

@@ -7,7 +7,7 @@ from django.utils.functional import cached_property
 
 
 class DatabaseFeatures(BaseDatabaseFeatures):
-    minimum_database_version = (13,)
+    minimum_database_version = (14,)
     allows_group_by_selected_pks = True
     can_return_columns_from_insert = True
     can_return_rows_from_bulk_insert = True
@@ -152,10 +152,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
             "PositiveSmallIntegerField": "SmallIntegerField",
         }
 
-    @cached_property
-    def is_postgresql_14(self):
-        return self.connection.pg_version >= 140000
-
     @cached_property
     def is_postgresql_15(self):
         return self.connection.pg_version >= 150000
@@ -164,8 +160,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
     def is_postgresql_16(self):
         return self.connection.pg_version >= 160000
 
-    has_bit_xor = property(operator.attrgetter("is_postgresql_14"))
-    supports_covering_spgist_indexes = property(operator.attrgetter("is_postgresql_14"))
     supports_unlimited_charfield = True
     supports_nulls_distinct_unique_constraints = property(
         operator.attrgetter("is_postgresql_15")

+ 1 - 2
docs/ref/contrib/gis/install/geolibs.txt

@@ -12,7 +12,7 @@ Program                   Description                           Required
 `PROJ`_                   Cartographic Projections library      Yes (PostgreSQL and SQLite only)  9.x, 8.x, 7.x, 6.x
 :doc:`GDAL <../gdal>`     Geospatial Data Abstraction Library   Yes                               3.8, 3.7, 3.6, 3.5, 3.4, 3.3, 3.2, 3.1, 3.0
 :doc:`GeoIP <../geoip2>`  IP-based geolocation library          No                                2
-`PostGIS`__               Spatial extensions for PostgreSQL     Yes (PostgreSQL only)             3.4, 3.3, 3.2, 3.1, 3.0
+`PostGIS`__               Spatial extensions for PostgreSQL     Yes (PostgreSQL only)             3.4, 3.3, 3.2, 3.1
 `SpatiaLite`__            Spatial extensions for SQLite         Yes (SQLite only)                 5.1, 5.0, 4.3
 ========================  ====================================  ================================  ===========================================
 
@@ -35,7 +35,6 @@ totally fine with GeoDjango. Your mileage may vary.
     GDAL 3.6.0 2022-11-03
     GDAL 3.7.0 2023-05-10
     GDAL 3.8.0 2023-11-13
-    PostGIS 3.0.0 2019-10-20
     PostGIS 3.1.0 2020-12-18
     PostGIS 3.2.0 2021-12-18
     PostGIS 3.3.0 2022-08-27

+ 4 - 4
docs/ref/contrib/gis/install/index.txt

@@ -56,7 +56,7 @@ supported versions, and any notes for each of the supported database backends:
 ==================  ==============================  ==================  =========================================
 Database            Library Requirements            Supported Versions  Notes
 ==================  ==============================  ==================  =========================================
-PostgreSQL          GEOS, GDAL, PROJ, PostGIS       13+                 Requires PostGIS.
+PostgreSQL          GEOS, GDAL, PROJ, PostGIS       14+                 Requires PostGIS.
 MySQL               GEOS, GDAL                      8.0.11+             :ref:`Limited functionality <mysql-spatial-limitations>`.
 Oracle              GEOS, GDAL                      19+                 XE not supported.
 SQLite              GEOS, GDAL, PROJ, SpatiaLite    3.31.0+             Requires SpatiaLite 4.3+
@@ -300,7 +300,7 @@ Summary:
 
 .. code-block:: shell
 
-    $ sudo port install postgresql13-server
+    $ sudo port install postgresql14-server
     $ sudo port install geos
     $ sudo port install proj6
     $ sudo port install postgis3
@@ -314,14 +314,14 @@ Summary:
 
     .. code-block:: shell
 
-        export PATH=/opt/local/bin:/opt/local/lib/postgresql13/bin
+        export PATH=/opt/local/bin:/opt/local/lib/postgresql14/bin
 
     In addition, add the ``DYLD_FALLBACK_LIBRARY_PATH`` setting so that
     the libraries can be found by Python:
 
     .. code-block:: shell
 
-        export DYLD_FALLBACK_LIBRARY_PATH=/opt/local/lib:/opt/local/lib/postgresql13
+        export DYLD_FALLBACK_LIBRARY_PATH=/opt/local/lib:/opt/local/lib/postgresql14
 
 __ https://www.macports.org/
 

+ 0 - 6
docs/ref/contrib/postgres/functions.txt

@@ -14,12 +14,6 @@ All of these functions are available from the
 
 Returns a version 4 UUID.
 
-On PostgreSQL < 13, the `pgcrypto extension`_ must be installed. You can use
-the :class:`~django.contrib.postgres.operations.CryptoExtension` migration
-operation to install it.
-
-.. _pgcrypto extension: https://www.postgresql.org/docs/current/pgcrypto.html
-
 Usage example:
 
 .. code-block:: pycon

+ 1 - 1
docs/ref/databases.txt

@@ -115,7 +115,7 @@ below for information on how to set up your database correctly.
 PostgreSQL notes
 ================
 
-Django supports PostgreSQL 13 and higher. `psycopg`_ 3.1.8+ or `psycopg2`_
+Django supports PostgreSQL 14 and higher. `psycopg`_ 3.1.8+ or `psycopg2`_
 2.8.4+ is required, though the latest `psycopg`_ 3.1.8+ is recommended.
 
 .. note::

+ 11 - 0
docs/releases/5.2.txt

@@ -238,6 +238,17 @@ backends.
 
 * ...
 
+:mod:`django.contrib.gis`
+-------------------------
+
+* Support for PostGIS 3.0 is removed.
+
+Dropped support for PostgreSQL 13
+---------------------------------
+
+Upstream support for PostgreSQL 13 ends in November 2025. Django 5.2 supports
+PostgreSQL 14 and higher.
+
 Miscellaneous
 -------------
 

+ 4 - 4
tests/backends/postgresql/tests.py

@@ -548,12 +548,12 @@ class Tests(TestCase):
 
     def test_get_database_version(self):
         new_connection = no_pool_connection()
-        new_connection.pg_version = 130009
-        self.assertEqual(new_connection.get_database_version(), (13, 9))
+        new_connection.pg_version = 140009
+        self.assertEqual(new_connection.get_database_version(), (14, 9))
 
-    @mock.patch.object(connection, "get_database_version", return_value=(12,))
+    @mock.patch.object(connection, "get_database_version", return_value=(13,))
     def test_check_database_version_supported(self, mocked_get_database_version):
-        msg = "PostgreSQL 13 or later is required (found 12)."
+        msg = "PostgreSQL 14 or later is required (found 13)."
         with self.assertRaisesMessage(NotSupportedError, msg):
             connection.check_database_version_supported()
         self.assertTrue(mocked_get_database_version.called)

+ 3 - 9
tests/postgres_tests/test_aggregates.py

@@ -1,4 +1,4 @@
-from django.db import connection, transaction
+from django.db import transaction
 from django.db.models import (
     CharField,
     F,
@@ -13,7 +13,6 @@ from django.db.models import (
 )
 from django.db.models.fields.json import KeyTextTransform, KeyTransform
 from django.db.models.functions import Cast, Concat, LPad, Substr
-from django.test import skipUnlessDBFeature
 from django.test.utils import Approximate
 from django.utils import timezone
 
@@ -95,9 +94,8 @@ class TestGeneralAggregate(PostgreSQLTestCase):
             BoolOr("boolean_field"),
             JSONBAgg("integer_field"),
             StringAgg("char_field", delimiter=";"),
+            BitXor("integer_field"),
         ]
-        if connection.features.has_bit_xor:
-            tests.append(BitXor("integer_field"))
         for aggregation in tests:
             with self.subTest(aggregation=aggregation):
                 # Empty result with non-execution optimization.
@@ -133,9 +131,8 @@ class TestGeneralAggregate(PostgreSQLTestCase):
                 StringAgg("char_field", delimiter=";", default=Value("<empty>")),
                 "<empty>",
             ),
+            (BitXor("integer_field", default=0), 0),
         ]
-        if connection.features.has_bit_xor:
-            tests.append((BitXor("integer_field", default=0), 0))
         for aggregation, expected_result in tests:
             with self.subTest(aggregation=aggregation):
                 # Empty result with non-execution optimization.
@@ -348,7 +345,6 @@ class TestGeneralAggregate(PostgreSQLTestCase):
         )
         self.assertEqual(values, {"bitor": 0})
 
-    @skipUnlessDBFeature("has_bit_xor")
     def test_bit_xor_general(self):
         AggregateTestModel.objects.create(integer_field=3)
         values = AggregateTestModel.objects.filter(
@@ -356,14 +352,12 @@ class TestGeneralAggregate(PostgreSQLTestCase):
         ).aggregate(bitxor=BitXor("integer_field"))
         self.assertEqual(values, {"bitxor": 2})
 
-    @skipUnlessDBFeature("has_bit_xor")
     def test_bit_xor_on_only_true_values(self):
         values = AggregateTestModel.objects.filter(
             integer_field=1,
         ).aggregate(bitxor=BitXor("integer_field"))
         self.assertEqual(values, {"bitxor": 1})
 
-    @skipUnlessDBFeature("has_bit_xor")
     def test_bit_xor_on_only_false_values(self):
         values = AggregateTestModel.objects.filter(
             integer_field=0,

+ 1 - 26
tests/postgres_tests/test_constraints.py

@@ -4,7 +4,7 @@ from unittest import mock
 from django.contrib.postgres.indexes import OpClass
 from django.core.checks import Error
 from django.core.exceptions import ValidationError
-from django.db import IntegrityError, NotSupportedError, connection, transaction
+from django.db import IntegrityError, connection, transaction
 from django.db.models import (
     CASCADE,
     CharField,
@@ -997,7 +997,6 @@ class ExclusionConstraintTests(PostgreSQLTestCase):
         RangesModel.objects.create(ints=(10, 19))
         RangesModel.objects.create(ints=(51, 60))
 
-    @skipUnlessDBFeature("supports_covering_spgist_indexes")
     def test_range_adjacent_spgist_include(self):
         constraint_name = "ints_adjacent_spgist_include"
         self.assertNotIn(
@@ -1034,7 +1033,6 @@ class ExclusionConstraintTests(PostgreSQLTestCase):
             editor.add_constraint(RangesModel, constraint)
         self.assertIn(constraint_name, self.get_constraints(RangesModel._meta.db_table))
 
-    @skipUnlessDBFeature("supports_covering_spgist_indexes")
     def test_range_adjacent_spgist_include_condition(self):
         constraint_name = "ints_adjacent_spgist_include_condition"
         self.assertNotIn(
@@ -1067,7 +1065,6 @@ class ExclusionConstraintTests(PostgreSQLTestCase):
             editor.add_constraint(RangesModel, constraint)
         self.assertIn(constraint_name, self.get_constraints(RangesModel._meta.db_table))
 
-    @skipUnlessDBFeature("supports_covering_spgist_indexes")
     def test_range_adjacent_spgist_include_deferrable(self):
         constraint_name = "ints_adjacent_spgist_include_deferrable"
         self.assertNotIn(
@@ -1084,27 +1081,6 @@ class ExclusionConstraintTests(PostgreSQLTestCase):
             editor.add_constraint(RangesModel, constraint)
         self.assertIn(constraint_name, self.get_constraints(RangesModel._meta.db_table))
 
-    def test_spgist_include_not_supported(self):
-        constraint_name = "ints_adjacent_spgist_include_not_supported"
-        constraint = ExclusionConstraint(
-            name=constraint_name,
-            expressions=[("ints", RangeOperators.ADJACENT_TO)],
-            index_type="spgist",
-            include=["id"],
-        )
-        msg = (
-            "Covering exclusion constraints using an SP-GiST index require "
-            "PostgreSQL 14+."
-        )
-        with connection.schema_editor() as editor:
-            with mock.patch(
-                "django.db.backends.postgresql.features.DatabaseFeatures."
-                "supports_covering_spgist_indexes",
-                False,
-            ):
-                with self.assertRaisesMessage(NotSupportedError, msg):
-                    editor.add_constraint(RangesModel, constraint)
-
     def test_range_adjacent_opclass(self):
         constraint_name = "ints_adjacent_opclass"
         self.assertNotIn(
@@ -1187,7 +1163,6 @@ class ExclusionConstraintTests(PostgreSQLTestCase):
             editor.add_constraint(RangesModel, constraint)
         self.assertIn(constraint_name, self.get_constraints(RangesModel._meta.db_table))
 
-    @skipUnlessDBFeature("supports_covering_spgist_indexes")
     def test_range_adjacent_spgist_opclass_include(self):
         constraint_name = "ints_adjacent_spgist_opclass_include"
         self.assertNotIn(

+ 1 - 19
tests/postgres_tests/test_indexes.py

@@ -1,5 +1,3 @@
-from unittest import mock
-
 from django.contrib.postgres.indexes import (
     BloomIndex,
     BrinIndex,
@@ -11,10 +9,9 @@ from django.contrib.postgres.indexes import (
     PostgresIndex,
     SpGistIndex,
 )
-from django.db import NotSupportedError, connection
+from django.db import connection
 from django.db.models import CharField, F, Index, Q
 from django.db.models.functions import Cast, Collate, Length, Lower
-from django.test import skipUnlessDBFeature
 from django.test.utils import register_lookup
 
 from . import PostgreSQLSimpleTestCase, PostgreSQLTestCase
@@ -640,7 +637,6 @@ class SchemaTests(PostgreSQLTestCase):
             index_name, self.get_constraints(TextFieldModel._meta.db_table)
         )
 
-    @skipUnlessDBFeature("supports_covering_spgist_indexes")
     def test_spgist_include(self):
         index_name = "scene_spgist_include_setting"
         index = SpGistIndex(name=index_name, fields=["scene"], include=["setting"])
@@ -654,20 +650,6 @@ class SchemaTests(PostgreSQLTestCase):
             editor.remove_index(Scene, index)
         self.assertNotIn(index_name, self.get_constraints(Scene._meta.db_table))
 
-    def test_spgist_include_not_supported(self):
-        index_name = "spgist_include_exception"
-        index = SpGistIndex(fields=["scene"], name=index_name, include=["setting"])
-        msg = "Covering SP-GiST indexes require PostgreSQL 14+."
-        with self.assertRaisesMessage(NotSupportedError, msg):
-            with mock.patch(
-                "django.db.backends.postgresql.features.DatabaseFeatures."
-                "supports_covering_spgist_indexes",
-                False,
-            ):
-                with connection.schema_editor() as editor:
-                    editor.add_index(Scene, index)
-        self.assertNotIn(index_name, self.get_constraints(Scene._meta.db_table))
-
     def test_custom_suffix(self):
         class CustomSuffixIndex(PostgresIndex):
             suffix = "sfx"