Browse Source

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

Mariusz Felisiak 2 years ago
parent
commit
95a101a690

+ 46 - 96
django/db/backends/sqlite3/base.py

@@ -239,103 +239,53 @@ class DatabaseWrapper(BaseDatabaseWrapper):
         determine if rows with invalid references were entered while constraint
         checks were off.
         """
-        if self.features.supports_pragma_foreign_key_check:
-            with self.cursor() as cursor:
-                if table_names is None:
-                    violations = cursor.execute("PRAGMA foreign_key_check").fetchall()
-                else:
-                    violations = chain.from_iterable(
-                        cursor.execute(
-                            "PRAGMA foreign_key_check(%s)"
-                            % self.ops.quote_name(table_name)
-                        ).fetchall()
-                        for table_name in table_names
-                    )
-                # See https://www.sqlite.org/pragma.html#pragma_foreign_key_check
-                for (
-                    table_name,
-                    rowid,
-                    referenced_table_name,
-                    foreign_key_index,
-                ) in violations:
-                    foreign_key = cursor.execute(
-                        "PRAGMA foreign_key_list(%s)" % self.ops.quote_name(table_name)
-                    ).fetchall()[foreign_key_index]
-                    column_name, referenced_column_name = foreign_key[3:5]
-                    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"
-                        % (
-                            self.ops.quote_name(primary_key_column_name),
-                            self.ops.quote_name(column_name),
-                            self.ops.quote_name(table_name),
-                        ),
-                        (rowid,),
-                    ).fetchone()
-                    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,
-                            primary_key_value,
-                            table_name,
-                            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,
+        with self.cursor() as cursor:
+            if table_names is None:
+                violations = cursor.execute("PRAGMA foreign_key_check").fetchall()
+            else:
+                violations = chain.from_iterable(
+                    cursor.execute(
+                        "PRAGMA foreign_key_check(%s)" % self.ops.quote_name(table_name)
+                    ).fetchall()
+                    for table_name in table_names
+                )
+            # See https://www.sqlite.org/pragma.html#pragma_foreign_key_check
+            for (
+                table_name,
+                rowid,
+                referenced_table_name,
+                foreign_key_index,
+            ) in violations:
+                foreign_key = cursor.execute(
+                    "PRAGMA foreign_key_list(%s)" % self.ops.quote_name(table_name)
+                ).fetchall()[foreign_key_index]
+                column_name, referenced_column_name = foreign_key[3:5]
+                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"
+                    % (
+                        self.ops.quote_name(primary_key_column_name),
+                        self.ops.quote_name(column_name),
+                        self.ops.quote_name(table_name),
+                    ),
+                    (rowid,),
+                ).fetchone()
+                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,
+                        primary_key_value,
+                        table_name,
+                        column_name,
+                        bad_value,
                         referenced_table_name,
-                    ) in relations.items():
-                        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,
-                                )
-                            )
+                        referenced_column_name,
+                    )
+                )
 
     def is_usable(self):
         return True

+ 2 - 6
django/db/backends/sqlite3/features.py

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

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

@@ -61,7 +61,7 @@ Database            Library Requirements            Supported Versions  Notes
 PostgreSQL          GEOS, GDAL, PROJ, PostGIS       12+                 Requires PostGIS.
 MySQL               GEOS, GDAL                      8+                  :ref:`Limited functionality <mysql-spatial-limitations>`.
 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

+ 1 - 1
docs/ref/databases.txt

@@ -730,7 +730,7 @@ appropriate typecasting.
 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
 are predominantly read-only or require a smaller installation footprint. As

+ 2 - 0
docs/releases/4.2.txt

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

+ 2 - 2
tests/backends/sqlite/tests.py

@@ -106,9 +106,9 @@ class Tests(TestCase):
             connections["default"].close()
             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):
-        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):
             connection.check_database_version_supported()
         self.assertTrue(mocked_get_database_version.called)