From b049bec7cfe9b5854584d240addb44fa1e9375a5 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Fri, 24 May 2024 21:23:50 +0200 Subject: [PATCH] Fixed #35479 -- Dropped support for PostgreSQL 13 and PostGIS 3.0. --- .github/workflows/schedule_tests.yml | 4 +-- .github/workflows/selenium.yml | 2 +- .../gis/db/backends/postgis/operations.py | 2 +- django/contrib/postgres/constraints.py | 14 +--------- django/contrib/postgres/indexes.py | 8 ------ django/db/backends/postgresql/features.py | 8 +----- docs/ref/contrib/gis/install/geolibs.txt | 3 +-- docs/ref/contrib/gis/install/index.txt | 8 +++--- docs/ref/contrib/postgres/functions.txt | 6 ----- docs/ref/databases.txt | 2 +- docs/releases/5.2.txt | 11 ++++++++ tests/backends/postgresql/tests.py | 8 +++--- tests/postgres_tests/test_aggregates.py | 12 +++------ tests/postgres_tests/test_constraints.py | 27 +------------------ tests/postgres_tests/test_indexes.py | 20 +------------- 15 files changed, 32 insertions(+), 103 deletions(-) diff --git a/.github/workflows/schedule_tests.yml b/.github/workflows/schedule_tests.yml index c4523af4a0..8b1f01ad86 100644 --- a/.github/workflows/schedule_tests.yml +++ b/.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 diff --git a/.github/workflows/selenium.yml b/.github/workflows/selenium.yml index fa916a0ded..7e46e0cfb1 100644 --- a/.github/workflows/selenium.yml +++ b/.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 diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index 17d7b3213d..7a347c5287 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/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"] ) diff --git a/django/contrib/postgres/constraints.py b/django/contrib/postgres/constraints.py index a31f657183..a6351dc008 100644 --- a/django/contrib/postgres/constraints.py +++ b/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 diff --git a/django/contrib/postgres/indexes.py b/django/contrib/postgres/indexes.py index 05fdbeed5e..ce9e2cee14 100644 --- a/django/contrib/postgres/indexes.py +++ b/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" diff --git a/django/db/backends/postgresql/features.py b/django/db/backends/postgresql/features.py index ef697e85b0..6170b5501a 100644 --- a/django/db/backends/postgresql/features.py +++ b/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") diff --git a/docs/ref/contrib/gis/install/geolibs.txt b/docs/ref/contrib/gis/install/geolibs.txt index 041409d53c..a0a66c0dc6 100644 --- a/docs/ref/contrib/gis/install/geolibs.txt +++ b/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 diff --git a/docs/ref/contrib/gis/install/index.txt b/docs/ref/contrib/gis/install/index.txt index 7706790b2a..e7bc885d4b 100644 --- a/docs/ref/contrib/gis/install/index.txt +++ b/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 `. 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/ diff --git a/docs/ref/contrib/postgres/functions.txt b/docs/ref/contrib/postgres/functions.txt index f5d9cdd873..4602f7fd9d 100644 --- a/docs/ref/contrib/postgres/functions.txt +++ b/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 diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index c8e9f2ebff..3e50d2e46a 100644 --- a/docs/ref/databases.txt +++ b/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:: diff --git a/docs/releases/5.2.txt b/docs/releases/5.2.txt index 9d28415df1..fb2a1d0177 100644 --- a/docs/releases/5.2.txt +++ b/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 ------------- diff --git a/tests/backends/postgresql/tests.py b/tests/backends/postgresql/tests.py index 47f8d94004..0b4f580612 100644 --- a/tests/backends/postgresql/tests.py +++ b/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) diff --git a/tests/postgres_tests/test_aggregates.py b/tests/postgres_tests/test_aggregates.py index 7e1e16d0c0..b72310bdf1 100644 --- a/tests/postgres_tests/test_aggregates.py +++ b/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("")), "", ), + (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, diff --git a/tests/postgres_tests/test_constraints.py b/tests/postgres_tests/test_constraints.py index 3cc76cdcfe..770d4b1702 100644 --- a/tests/postgres_tests/test_constraints.py +++ b/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( diff --git a/tests/postgres_tests/test_indexes.py b/tests/postgres_tests/test_indexes.py index 8a7ee39a76..f98d03c6c1 100644 --- a/tests/postgres_tests/test_indexes.py +++ b/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"