1
0
mirror of https://github.com/django/django.git synced 2025-04-25 09:44:36 +00:00

Fixed #34201 -- Bumped minimum supported SQLite to 3.21.0.

This commit is contained in:
Mariusz Felisiak 2022-12-08 05:53:18 +01:00 committed by GitHub
parent 0036bcdcb6
commit 95a101a690
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 54 additions and 106 deletions

View File

@ -239,103 +239,53 @@ class DatabaseWrapper(BaseDatabaseWrapper):
determine if rows with invalid references were entered while constraint determine if rows with invalid references were entered while constraint
checks were off. checks were off.
""" """
if self.features.supports_pragma_foreign_key_check: with self.cursor() as cursor:
with self.cursor() as cursor: if table_names is None:
if table_names is None: violations = cursor.execute("PRAGMA foreign_key_check").fetchall()
violations = cursor.execute("PRAGMA foreign_key_check").fetchall() else:
else: violations = chain.from_iterable(
violations = chain.from_iterable( cursor.execute(
cursor.execute( "PRAGMA foreign_key_check(%s)" % self.ops.quote_name(table_name)
"PRAGMA foreign_key_check(%s)" ).fetchall()
% self.ops.quote_name(table_name) for table_name in table_names
).fetchall() )
for table_name in table_names # See https://www.sqlite.org/pragma.html#pragma_foreign_key_check
) for (
# See https://www.sqlite.org/pragma.html#pragma_foreign_key_check table_name,
for ( rowid,
table_name, referenced_table_name,
rowid, foreign_key_index,
referenced_table_name, ) in violations:
foreign_key_index, foreign_key = cursor.execute(
) in violations: "PRAGMA foreign_key_list(%s)" % self.ops.quote_name(table_name)
foreign_key = cursor.execute( ).fetchall()[foreign_key_index]
"PRAGMA foreign_key_list(%s)" % self.ops.quote_name(table_name) column_name, referenced_column_name = foreign_key[3:5]
).fetchall()[foreign_key_index] primary_key_column_name = self.introspection.get_primary_key_column(
column_name, referenced_column_name = foreign_key[3:5] cursor, table_name
primary_key_column_name = self.introspection.get_primary_key_column( )
cursor, table_name primary_key_value, bad_value = cursor.execute(
) "SELECT %s, %s FROM %s WHERE rowid = %%s"
primary_key_value, bad_value = cursor.execute( % (
"SELECT %s, %s FROM %s WHERE rowid = %%s" self.ops.quote_name(primary_key_column_name),
% ( self.ops.quote_name(column_name),
self.ops.quote_name(primary_key_column_name), self.ops.quote_name(table_name),
self.ops.quote_name(column_name), ),
self.ops.quote_name(table_name), (rowid,),
), ).fetchone()
(rowid,), raise IntegrityError(
).fetchone() "The row in table '%s' with primary key '%s' has an "
raise IntegrityError( "invalid foreign key: %s.%s contains a value '%s' that "
"The row in table '%s' with primary key '%s' has an " "does not have a corresponding value in %s.%s."
"invalid foreign key: %s.%s contains a value '%s' that " % (
"does not have a corresponding value in %s.%s." table_name,
% ( primary_key_value,
table_name, table_name,
primary_key_value, column_name,
table_name, bad_value,
column_name,
bad_value,
referenced_table_name,
referenced_column_name,
)
)
else:
with self.cursor() as cursor:
if table_names is None:
table_names = self.introspection.table_names(cursor)
for table_name in table_names:
primary_key_column_name = self.introspection.get_primary_key_column(
cursor, table_name
)
if not primary_key_column_name:
continue
relations = self.introspection.get_relations(cursor, table_name)
for column_name, (
referenced_column_name,
referenced_table_name, referenced_table_name,
) in relations.items(): referenced_column_name,
cursor.execute( )
""" )
SELECT REFERRING.`%s`, REFERRING.`%s` FROM `%s` as REFERRING
LEFT JOIN `%s` as REFERRED
ON (REFERRING.`%s` = REFERRED.`%s`)
WHERE REFERRING.`%s` IS NOT NULL AND REFERRED.`%s` IS NULL
"""
% (
primary_key_column_name,
column_name,
table_name,
referenced_table_name,
column_name,
referenced_column_name,
column_name,
referenced_column_name,
)
)
for bad_row in cursor.fetchall():
raise IntegrityError(
"The row in table '%s' with primary key '%s' has an "
"invalid foreign key: %s.%s contains a value '%s' that "
"does not have a corresponding value in %s.%s."
% (
table_name,
bad_row[0],
table_name,
column_name,
bad_row[1],
referenced_table_name,
referenced_column_name,
)
)
def is_usable(self): def is_usable(self):
return True return True

View File

@ -9,7 +9,7 @@ from .base import Database
class DatabaseFeatures(BaseDatabaseFeatures): class DatabaseFeatures(BaseDatabaseFeatures):
minimum_database_version = (3, 9) minimum_database_version = (3, 21)
test_db_allows_multiple_connections = False test_db_allows_multiple_connections = False
supports_unspecified_pk = True supports_unspecified_pk = True
supports_timezones = False supports_timezones = False
@ -31,11 +31,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
# Is "ALTER TABLE ... DROP COLUMN" supported? # Is "ALTER TABLE ... DROP COLUMN" supported?
can_alter_table_drop_column = Database.sqlite_version_info >= (3, 35, 5) can_alter_table_drop_column = Database.sqlite_version_info >= (3, 35, 5)
supports_parentheses_in_compound = False supports_parentheses_in_compound = False
# Deferred constraint checks can be emulated on SQLite < 3.20 but not in a can_defer_constraint_checks = True
# reasonably performant way.
supports_pragma_foreign_key_check = Database.sqlite_version_info >= (3, 20, 0)
can_defer_constraint_checks = supports_pragma_foreign_key_check
supports_functions_in_partial_indexes = Database.sqlite_version_info >= (3, 15, 0)
supports_over_clause = Database.sqlite_version_info >= (3, 25, 0) supports_over_clause = Database.sqlite_version_info >= (3, 25, 0)
supports_frame_range_fixed_distance = Database.sqlite_version_info >= (3, 28, 0) supports_frame_range_fixed_distance = Database.sqlite_version_info >= (3, 28, 0)
supports_aggregate_filter_clause = Database.sqlite_version_info >= (3, 30, 1) supports_aggregate_filter_clause = Database.sqlite_version_info >= (3, 30, 1)

View File

@ -61,7 +61,7 @@ Database Library Requirements Supported Versions Notes
PostgreSQL GEOS, GDAL, PROJ, PostGIS 12+ Requires PostGIS. PostgreSQL GEOS, GDAL, PROJ, PostGIS 12+ Requires PostGIS.
MySQL GEOS, GDAL 8+ :ref:`Limited functionality <mysql-spatial-limitations>`. MySQL GEOS, GDAL 8+ :ref:`Limited functionality <mysql-spatial-limitations>`.
Oracle GEOS, GDAL 19+ XE not supported. Oracle GEOS, GDAL 19+ XE not supported.
SQLite GEOS, GDAL, PROJ, SpatiaLite 3.9.0+ Requires SpatiaLite 4.3+ SQLite GEOS, GDAL, PROJ, SpatiaLite 3.21.0+ Requires SpatiaLite 4.3+
================== ============================== ================== ========================================= ================== ============================== ================== =========================================
See also `this comparison matrix`__ on the OSGeo Wiki for See also `this comparison matrix`__ on the OSGeo Wiki for

View File

@ -730,7 +730,7 @@ appropriate typecasting.
SQLite notes SQLite notes
============ ============
Django supports SQLite 3.9.0 and later. Django supports SQLite 3.21.0 and later.
SQLite_ provides an excellent development alternative for applications that SQLite_ provides an excellent development alternative for applications that
are predominantly read-only or require a smaller installation footprint. As are predominantly read-only or require a smaller installation footprint. As

View File

@ -407,6 +407,8 @@ Miscellaneous
* The ``is_summary`` argument of the undocumented ``Query.add_annotation()`` * The ``is_summary`` argument of the undocumented ``Query.add_annotation()``
method is removed. method is removed.
* The minimum supported version of SQLite is increased from 3.9.0 to 3.21.0.
.. _deprecated-features-4.2: .. _deprecated-features-4.2:
Features deprecated in 4.2 Features deprecated in 4.2

View File

@ -106,9 +106,9 @@ class Tests(TestCase):
connections["default"].close() connections["default"].close()
self.assertTrue(os.path.isfile(os.path.join(tmp, "test.db"))) self.assertTrue(os.path.isfile(os.path.join(tmp, "test.db")))
@mock.patch.object(connection, "get_database_version", return_value=(3, 8)) @mock.patch.object(connection, "get_database_version", return_value=(3, 20))
def test_check_database_version_supported(self, mocked_get_database_version): def test_check_database_version_supported(self, mocked_get_database_version):
msg = "SQLite 3.9 or later is required (found 3.8)." msg = "SQLite 3.21 or later is required (found 3.20)."
with self.assertRaisesMessage(NotSupportedError, msg): with self.assertRaisesMessage(NotSupportedError, msg):
connection.check_database_version_supported() connection.check_database_version_supported()
self.assertTrue(mocked_get_database_version.called) self.assertTrue(mocked_get_database_version.called)