Skip to content

Commit b049bec

Browse files
felixxmsarahboyce
authored andcommitted
Fixed #35479 -- Dropped support for PostgreSQL 13 and PostGIS 3.0.
1 parent bcbc4b9 commit b049bec

File tree

15 files changed

+32
-103
lines changed

15 files changed

+32
-103
lines changed

.github/workflows/schedule_tests.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ jobs:
9090
continue-on-error: true
9191
services:
9292
postgres:
93-
image: postgres:13-alpine
93+
image: postgres:14-alpine
9494
env:
9595
POSTGRES_DB: django
9696
POSTGRES_USER: user
@@ -163,7 +163,7 @@ jobs:
163163
name: Selenium tests, PostgreSQL
164164
services:
165165
postgres:
166-
image: postgres:13-alpine
166+
image: postgres:14-alpine
167167
env:
168168
POSTGRES_DB: django
169169
POSTGRES_USER: user

.github/workflows/selenium.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
name: PostgreSQL
4444
services:
4545
postgres:
46-
image: postgres:13-alpine
46+
image: postgres:14-alpine
4747
env:
4848
POSTGRES_DB: django
4949
POSTGRES_USER: user

django/contrib/gis/db/backends/postgis/operations.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ def spatial_version(self):
203203
raise ImproperlyConfigured(
204204
'Cannot determine PostGIS version for database "%s" '
205205
'using command "SELECT postgis_lib_version()". '
206-
"GeoDjango requires at least PostGIS version 3.0. "
206+
"GeoDjango requires at least PostGIS version 3.1. "
207207
"Was the database created from a spatial database "
208208
"template?" % self.connection.settings_dict["NAME"]
209209
)

django/contrib/postgres/constraints.py

+1-13
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from types import NoneType
22

33
from django.core.exceptions import ValidationError
4-
from django.db import DEFAULT_DB_ALIAS, NotSupportedError
4+
from django.db import DEFAULT_DB_ALIAS
55
from django.db.backends.ddl_references import Expressions, Statement, Table
66
from django.db.models import BaseConstraint, Deferrable, F, Q
77
from django.db.models.expressions import Exists, ExpressionList
@@ -114,7 +114,6 @@ def constraint_sql(self, model, schema_editor):
114114
)
115115

116116
def create_sql(self, model, schema_editor):
117-
self.check_supported(schema_editor)
118117
return Statement(
119118
"ALTER TABLE %(table)s ADD %(constraint)s",
120119
table=Table(model._meta.db_table, schema_editor.quote_name),
@@ -128,17 +127,6 @@ def remove_sql(self, model, schema_editor):
128127
schema_editor.quote_name(self.name),
129128
)
130129

131-
def check_supported(self, schema_editor):
132-
if (
133-
self.include
134-
and self.index_type.lower() == "spgist"
135-
and not schema_editor.connection.features.supports_covering_spgist_indexes
136-
):
137-
raise NotSupportedError(
138-
"Covering exclusion constraints using an SP-GiST index "
139-
"require PostgreSQL 14+."
140-
)
141-
142130
def deconstruct(self):
143131
path, args, kwargs = super().deconstruct()
144132
kwargs["expressions"] = self.expressions

django/contrib/postgres/indexes.py

-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from django.db import NotSupportedError
21
from django.db.models import Func, Index
32
from django.utils.functional import cached_property
43

@@ -234,13 +233,6 @@ def get_with_params(self):
234233
with_params.append("fillfactor = %d" % self.fillfactor)
235234
return with_params
236235

237-
def check_supported(self, schema_editor):
238-
if (
239-
self.include
240-
and not schema_editor.connection.features.supports_covering_spgist_indexes
241-
):
242-
raise NotSupportedError("Covering SP-GiST indexes require PostgreSQL 14+.")
243-
244236

245237
class OpClass(Func):
246238
template = "%(expressions)s %(name)s"

django/db/backends/postgresql/features.py

+1-7
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88

99
class DatabaseFeatures(BaseDatabaseFeatures):
10-
minimum_database_version = (13,)
10+
minimum_database_version = (14,)
1111
allows_group_by_selected_pks = True
1212
can_return_columns_from_insert = True
1313
can_return_rows_from_bulk_insert = True
@@ -152,10 +152,6 @@ def introspected_field_types(self):
152152
"PositiveSmallIntegerField": "SmallIntegerField",
153153
}
154154

155-
@cached_property
156-
def is_postgresql_14(self):
157-
return self.connection.pg_version >= 140000
158-
159155
@cached_property
160156
def is_postgresql_15(self):
161157
return self.connection.pg_version >= 150000
@@ -164,8 +160,6 @@ def is_postgresql_15(self):
164160
def is_postgresql_16(self):
165161
return self.connection.pg_version >= 160000
166162

167-
has_bit_xor = property(operator.attrgetter("is_postgresql_14"))
168-
supports_covering_spgist_indexes = property(operator.attrgetter("is_postgresql_14"))
169163
supports_unlimited_charfield = True
170164
supports_nulls_distinct_unique_constraints = property(
171165
operator.attrgetter("is_postgresql_15")

docs/ref/contrib/gis/install/geolibs.txt

+1-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Program Description Required
1212
`PROJ`_ Cartographic Projections library Yes (PostgreSQL and SQLite only) 9.x, 8.x, 7.x, 6.x
1313
: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
1414
:doc:`GeoIP <../geoip2>` IP-based geolocation library No 2
15-
`PostGIS`__ Spatial extensions for PostgreSQL Yes (PostgreSQL only) 3.4, 3.3, 3.2, 3.1, 3.0
15+
`PostGIS`__ Spatial extensions for PostgreSQL Yes (PostgreSQL only) 3.4, 3.3, 3.2, 3.1
1616
`SpatiaLite`__ Spatial extensions for SQLite Yes (SQLite only) 5.1, 5.0, 4.3
1717
======================== ==================================== ================================ ===========================================
1818

@@ -35,7 +35,6 @@ totally fine with GeoDjango. Your mileage may vary.
3535
GDAL 3.6.0 2022-11-03
3636
GDAL 3.7.0 2023-05-10
3737
GDAL 3.8.0 2023-11-13
38-
PostGIS 3.0.0 2019-10-20
3938
PostGIS 3.1.0 2020-12-18
4039
PostGIS 3.2.0 2021-12-18
4140
PostGIS 3.3.0 2022-08-27

docs/ref/contrib/gis/install/index.txt

+4-4
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ supported versions, and any notes for each of the supported database backends:
5656
================== ============================== ================== =========================================
5757
Database Library Requirements Supported Versions Notes
5858
================== ============================== ================== =========================================
59-
PostgreSQL GEOS, GDAL, PROJ, PostGIS 13+ Requires PostGIS.
59+
PostgreSQL GEOS, GDAL, PROJ, PostGIS 14+ Requires PostGIS.
6060
MySQL GEOS, GDAL 8.0.11+ :ref:`Limited functionality <mysql-spatial-limitations>`.
6161
Oracle GEOS, GDAL 19+ XE not supported.
6262
SQLite GEOS, GDAL, PROJ, SpatiaLite 3.31.0+ Requires SpatiaLite 4.3+
@@ -300,7 +300,7 @@ Summary:
300300

301301
.. code-block:: shell
302302

303-
$ sudo port install postgresql13-server
303+
$ sudo port install postgresql14-server
304304
$ sudo port install geos
305305
$ sudo port install proj6
306306
$ sudo port install postgis3
@@ -314,14 +314,14 @@ Summary:
314314

315315
.. code-block:: shell
316316

317-
export PATH=/opt/local/bin:/opt/local/lib/postgresql13/bin
317+
export PATH=/opt/local/bin:/opt/local/lib/postgresql14/bin
318318

319319
In addition, add the ``DYLD_FALLBACK_LIBRARY_PATH`` setting so that
320320
the libraries can be found by Python:
321321

322322
.. code-block:: shell
323323

324-
export DYLD_FALLBACK_LIBRARY_PATH=/opt/local/lib:/opt/local/lib/postgresql13
324+
export DYLD_FALLBACK_LIBRARY_PATH=/opt/local/lib:/opt/local/lib/postgresql14
325325

326326
__ https://www.macports.org/
327327

docs/ref/contrib/postgres/functions.txt

-6
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,6 @@ All of these functions are available from the
1414

1515
Returns a version 4 UUID.
1616

17-
On PostgreSQL < 13, the `pgcrypto extension`_ must be installed. You can use
18-
the :class:`~django.contrib.postgres.operations.CryptoExtension` migration
19-
operation to install it.
20-
21-
.. _pgcrypto extension: https://www.postgresql.org/docs/current/pgcrypto.html
22-
2317
Usage example:
2418

2519
.. code-block:: pycon

docs/ref/databases.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ below for information on how to set up your database correctly.
115115
PostgreSQL notes
116116
================
117117

118-
Django supports PostgreSQL 13 and higher. `psycopg`_ 3.1.8+ or `psycopg2`_
118+
Django supports PostgreSQL 14 and higher. `psycopg`_ 3.1.8+ or `psycopg2`_
119119
2.8.4+ is required, though the latest `psycopg`_ 3.1.8+ is recommended.
120120

121121
.. note::

docs/releases/5.2.txt

+11
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,17 @@ backends.
238238

239239
* ...
240240

241+
:mod:`django.contrib.gis`
242+
-------------------------
243+
244+
* Support for PostGIS 3.0 is removed.
245+
246+
Dropped support for PostgreSQL 13
247+
---------------------------------
248+
249+
Upstream support for PostgreSQL 13 ends in November 2025. Django 5.2 supports
250+
PostgreSQL 14 and higher.
251+
241252
Miscellaneous
242253
-------------
243254

tests/backends/postgresql/tests.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -548,12 +548,12 @@ def test_copy_cursors(self):
548548

549549
def test_get_database_version(self):
550550
new_connection = no_pool_connection()
551-
new_connection.pg_version = 130009
552-
self.assertEqual(new_connection.get_database_version(), (13, 9))
551+
new_connection.pg_version = 140009
552+
self.assertEqual(new_connection.get_database_version(), (14, 9))
553553

554-
@mock.patch.object(connection, "get_database_version", return_value=(12,))
554+
@mock.patch.object(connection, "get_database_version", return_value=(13,))
555555
def test_check_database_version_supported(self, mocked_get_database_version):
556-
msg = "PostgreSQL 13 or later is required (found 12)."
556+
msg = "PostgreSQL 14 or later is required (found 13)."
557557
with self.assertRaisesMessage(NotSupportedError, msg):
558558
connection.check_database_version_supported()
559559
self.assertTrue(mocked_get_database_version.called)

tests/postgres_tests/test_aggregates.py

+3-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from django.db import connection, transaction
1+
from django.db import transaction
22
from django.db.models import (
33
CharField,
44
F,
@@ -13,7 +13,6 @@
1313
)
1414
from django.db.models.fields.json import KeyTextTransform, KeyTransform
1515
from django.db.models.functions import Cast, Concat, LPad, Substr
16-
from django.test import skipUnlessDBFeature
1716
from django.test.utils import Approximate
1817
from django.utils import timezone
1918

@@ -95,9 +94,8 @@ def test_empty_result_set(self):
9594
BoolOr("boolean_field"),
9695
JSONBAgg("integer_field"),
9796
StringAgg("char_field", delimiter=";"),
97+
BitXor("integer_field"),
9898
]
99-
if connection.features.has_bit_xor:
100-
tests.append(BitXor("integer_field"))
10199
for aggregation in tests:
102100
with self.subTest(aggregation=aggregation):
103101
# Empty result with non-execution optimization.
@@ -133,9 +131,8 @@ def test_default_argument(self):
133131
StringAgg("char_field", delimiter=";", default=Value("<empty>")),
134132
"<empty>",
135133
),
134+
(BitXor("integer_field", default=0), 0),
136135
]
137-
if connection.features.has_bit_xor:
138-
tests.append((BitXor("integer_field", default=0), 0))
139136
for aggregation, expected_result in tests:
140137
with self.subTest(aggregation=aggregation):
141138
# Empty result with non-execution optimization.
@@ -348,22 +345,19 @@ def test_bit_or_on_only_false_values(self):
348345
)
349346
self.assertEqual(values, {"bitor": 0})
350347

351-
@skipUnlessDBFeature("has_bit_xor")
352348
def test_bit_xor_general(self):
353349
AggregateTestModel.objects.create(integer_field=3)
354350
values = AggregateTestModel.objects.filter(
355351
integer_field__in=[1, 3],
356352
).aggregate(bitxor=BitXor("integer_field"))
357353
self.assertEqual(values, {"bitxor": 2})
358354

359-
@skipUnlessDBFeature("has_bit_xor")
360355
def test_bit_xor_on_only_true_values(self):
361356
values = AggregateTestModel.objects.filter(
362357
integer_field=1,
363358
).aggregate(bitxor=BitXor("integer_field"))
364359
self.assertEqual(values, {"bitxor": 1})
365360

366-
@skipUnlessDBFeature("has_bit_xor")
367361
def test_bit_xor_on_only_false_values(self):
368362
values = AggregateTestModel.objects.filter(
369363
integer_field=0,

tests/postgres_tests/test_constraints.py

+1-26
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from django.contrib.postgres.indexes import OpClass
55
from django.core.checks import Error
66
from django.core.exceptions import ValidationError
7-
from django.db import IntegrityError, NotSupportedError, connection, transaction
7+
from django.db import IntegrityError, connection, transaction
88
from django.db.models import (
99
CASCADE,
1010
CharField,
@@ -997,7 +997,6 @@ def test_range_adjacent_gist_include(self):
997997
RangesModel.objects.create(ints=(10, 19))
998998
RangesModel.objects.create(ints=(51, 60))
999999

1000-
@skipUnlessDBFeature("supports_covering_spgist_indexes")
10011000
def test_range_adjacent_spgist_include(self):
10021001
constraint_name = "ints_adjacent_spgist_include"
10031002
self.assertNotIn(
@@ -1034,7 +1033,6 @@ def test_range_adjacent_gist_include_condition(self):
10341033
editor.add_constraint(RangesModel, constraint)
10351034
self.assertIn(constraint_name, self.get_constraints(RangesModel._meta.db_table))
10361035

1037-
@skipUnlessDBFeature("supports_covering_spgist_indexes")
10381036
def test_range_adjacent_spgist_include_condition(self):
10391037
constraint_name = "ints_adjacent_spgist_include_condition"
10401038
self.assertNotIn(
@@ -1067,7 +1065,6 @@ def test_range_adjacent_gist_include_deferrable(self):
10671065
editor.add_constraint(RangesModel, constraint)
10681066
self.assertIn(constraint_name, self.get_constraints(RangesModel._meta.db_table))
10691067

1070-
@skipUnlessDBFeature("supports_covering_spgist_indexes")
10711068
def test_range_adjacent_spgist_include_deferrable(self):
10721069
constraint_name = "ints_adjacent_spgist_include_deferrable"
10731070
self.assertNotIn(
@@ -1084,27 +1081,6 @@ def test_range_adjacent_spgist_include_deferrable(self):
10841081
editor.add_constraint(RangesModel, constraint)
10851082
self.assertIn(constraint_name, self.get_constraints(RangesModel._meta.db_table))
10861083

1087-
def test_spgist_include_not_supported(self):
1088-
constraint_name = "ints_adjacent_spgist_include_not_supported"
1089-
constraint = ExclusionConstraint(
1090-
name=constraint_name,
1091-
expressions=[("ints", RangeOperators.ADJACENT_TO)],
1092-
index_type="spgist",
1093-
include=["id"],
1094-
)
1095-
msg = (
1096-
"Covering exclusion constraints using an SP-GiST index require "
1097-
"PostgreSQL 14+."
1098-
)
1099-
with connection.schema_editor() as editor:
1100-
with mock.patch(
1101-
"django.db.backends.postgresql.features.DatabaseFeatures."
1102-
"supports_covering_spgist_indexes",
1103-
False,
1104-
):
1105-
with self.assertRaisesMessage(NotSupportedError, msg):
1106-
editor.add_constraint(RangesModel, constraint)
1107-
11081084
def test_range_adjacent_opclass(self):
11091085
constraint_name = "ints_adjacent_opclass"
11101086
self.assertNotIn(
@@ -1187,7 +1163,6 @@ def test_range_adjacent_gist_opclass_include(self):
11871163
editor.add_constraint(RangesModel, constraint)
11881164
self.assertIn(constraint_name, self.get_constraints(RangesModel._meta.db_table))
11891165

1190-
@skipUnlessDBFeature("supports_covering_spgist_indexes")
11911166
def test_range_adjacent_spgist_opclass_include(self):
11921167
constraint_name = "ints_adjacent_spgist_opclass_include"
11931168
self.assertNotIn(

0 commit comments

Comments
 (0)