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

This commit is contained in:
Mariusz Felisiak 2024-05-24 21:23:50 +02:00 committed by Sarah Boyce
parent bcbc4b9b8a
commit b049bec7cf
15 changed files with 32 additions and 103 deletions

View File

@ -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

View File

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

View File

@ -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"]
)

View File

@ -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

View File

@ -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"

View File

@ -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")

View File

@ -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

View File

@ -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/

View File

@ -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

View File

@ -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::

View File

@ -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
-------------

View File

@ -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)

View File

@ -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,

View File

@ -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(

View File

@ -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"