2
0
Эх сурвалжийг харах

Refs #30913 -- Added system checks for covering indexes and unique constraints support.

Mariusz Felisiak 4 жил өмнө
parent
commit
f83b44075d

+ 37 - 4
django/db/models/base.py

@@ -1614,12 +1614,10 @@ class Model(metaclass=ModelBase):
             if not router.allow_migrate_model(db, cls):
                 continue
             connection = connections[db]
-            if (
+            if not (
                 connection.features.supports_partial_indexes or
                 'supports_partial_indexes' in cls._meta.required_db_features
-            ):
-                continue
-            if any(index.condition is not None for index in cls._meta.indexes):
+            ) and any(index.condition is not None for index in cls._meta.indexes):
                 errors.append(
                     checks.Warning(
                         '%s does not support indexes with conditions.'
@@ -1632,6 +1630,22 @@ class Model(metaclass=ModelBase):
                         id='models.W037',
                     )
                 )
+            if not (
+                connection.features.supports_covering_indexes or
+                'supports_covering_indexes' in cls._meta.required_db_features
+            ) and any(index.include for index in cls._meta.indexes):
+                errors.append(
+                    checks.Warning(
+                        '%s does not support indexes with non-key columns.'
+                        % connection.display_name,
+                        hint=(
+                            "Non-key columns will be ignored. Silence this "
+                            "warning if you don't care about it."
+                        ),
+                        obj=cls,
+                        id='models.W040',
+                    )
+                )
         fields = [field for index in cls._meta.indexes for field, _ in index.fields_orders]
         fields += [include for index in cls._meta.indexes for include in index.include]
         errors.extend(cls._check_local_fields(fields, 'indexes'))
@@ -1927,6 +1941,25 @@ class Model(metaclass=ModelBase):
                         id='models.W038',
                     )
                 )
+            if not (
+                connection.features.supports_covering_indexes or
+                'supports_covering_indexes' in cls._meta.required_db_features
+            ) and any(
+                isinstance(constraint, UniqueConstraint) and constraint.include
+                for constraint in cls._meta.constraints
+            ):
+                errors.append(
+                    checks.Warning(
+                        '%s does not support unique constraints with non-key '
+                        'columns.' % connection.display_name,
+                        hint=(
+                            "A constraint won't be created. Silence this "
+                            "warning if you don't care about it."
+                        ),
+                        obj=cls,
+                        id='models.W039',
+                    )
+                )
             fields = chain.from_iterable(
                 (*constraint.fields, *constraint.include)
                 for constraint in cls._meta.constraints if isinstance(constraint, UniqueConstraint)

+ 4 - 0
docs/ref/checks.txt

@@ -360,6 +360,10 @@ Models
 * **models.W037**: ``<database>`` does not support indexes with conditions.
 * **models.W038**: ``<database>`` does not support deferrable unique
   constraints.
+* **models.W039**: ``<database>`` does not support unique constraints with
+  non-key columns.
+* **models.W040**: ``<database>`` does not support indexes with non-key
+  columns.
 
 Security
 --------

+ 1 - 0
tests/constraints/models.py

@@ -88,6 +88,7 @@ class UniqueConstraintInclude(models.Model):
     class Meta:
         required_db_features = {
             'supports_table_check_constraints',
+            'supports_covering_indexes',
         }
         constraints = [
             models.UniqueConstraint(

+ 96 - 0
tests/invalid_models_tests/test_models.py

@@ -375,6 +375,51 @@ class IndexesTests(TestCase):
 
         self.assertEqual(Model.check(databases=self.databases), [])
 
+    def test_index_with_include(self):
+        class Model(models.Model):
+            age = models.IntegerField()
+
+            class Meta:
+                indexes = [
+                    models.Index(
+                        fields=['age'],
+                        name='index_age_include_id',
+                        include=['id'],
+                    ),
+                ]
+
+        errors = Model.check(databases=self.databases)
+        expected = [] if connection.features.supports_covering_indexes else [
+            Warning(
+                '%s does not support indexes with non-key columns.'
+                % connection.display_name,
+                hint=(
+                    "Non-key columns will be ignored. Silence this warning if "
+                    "you don't care about it."
+                ),
+                obj=Model,
+                id='models.W040',
+            )
+        ]
+        self.assertEqual(errors, expected)
+
+    def test_index_with_include_required_db_features(self):
+        class Model(models.Model):
+            age = models.IntegerField()
+
+            class Meta:
+                required_db_features = {'supports_covering_indexes'}
+                indexes = [
+                    models.Index(
+                        fields=['age'],
+                        name='index_age_include_id',
+                        include=['id'],
+                    ),
+                ]
+
+        self.assertEqual(Model.check(databases=self.databases), [])
+
+    @skipUnlessDBFeature('supports_covering_indexes')
     def test_index_include_pointing_to_missing_field(self):
         class Model(models.Model):
             class Meta:
@@ -390,6 +435,7 @@ class IndexesTests(TestCase):
             ),
         ])
 
+    @skipUnlessDBFeature('supports_covering_indexes')
     def test_index_include_pointing_to_m2m_field(self):
         class Model(models.Model):
             m2m = models.ManyToManyField('self')
@@ -406,6 +452,7 @@ class IndexesTests(TestCase):
             ),
         ])
 
+    @skipUnlessDBFeature('supports_covering_indexes')
     def test_index_include_pointing_to_non_local_field(self):
         class Parent(models.Model):
             field1 = models.IntegerField()
@@ -428,6 +475,7 @@ class IndexesTests(TestCase):
             ),
         ])
 
+    @skipUnlessDBFeature('supports_covering_indexes')
     def test_index_include_pointing_to_fk(self):
         class Target(models.Model):
             pass
@@ -1641,6 +1689,51 @@ class ConstraintsTests(TestCase):
 
         self.assertEqual(Model.check(databases=self.databases), [])
 
+    def test_unique_constraint_with_include(self):
+        class Model(models.Model):
+            age = models.IntegerField()
+
+            class Meta:
+                constraints = [
+                    models.UniqueConstraint(
+                        fields=['age'],
+                        name='unique_age_include_id',
+                        include=['id'],
+                    ),
+                ]
+
+        errors = Model.check(databases=self.databases)
+        expected = [] if connection.features.supports_covering_indexes else [
+            Warning(
+                '%s does not support unique constraints with non-key columns.'
+                % connection.display_name,
+                hint=(
+                    "A constraint won't be created. Silence this warning if "
+                    "you don't care about it."
+                ),
+                obj=Model,
+                id='models.W039',
+            ),
+        ]
+        self.assertEqual(errors, expected)
+
+    def test_unique_constraint_with_include_required_db_features(self):
+        class Model(models.Model):
+            age = models.IntegerField()
+
+            class Meta:
+                required_db_features = {'supports_covering_indexes'}
+                constraints = [
+                    models.UniqueConstraint(
+                        fields=['age'],
+                        name='unique_age_include_id',
+                        include=['id'],
+                    ),
+                ]
+
+        self.assertEqual(Model.check(databases=self.databases), [])
+
+    @skipUnlessDBFeature('supports_covering_indexes')
     def test_unique_constraint_include_pointing_to_missing_field(self):
         class Model(models.Model):
             class Meta:
@@ -1661,6 +1754,7 @@ class ConstraintsTests(TestCase):
             ),
         ])
 
+    @skipUnlessDBFeature('supports_covering_indexes')
     def test_unique_constraint_include_pointing_to_m2m_field(self):
         class Model(models.Model):
             m2m = models.ManyToManyField('self')
@@ -1683,6 +1777,7 @@ class ConstraintsTests(TestCase):
             ),
         ])
 
+    @skipUnlessDBFeature('supports_covering_indexes')
     def test_unique_constraint_include_pointing_to_non_local_field(self):
         class Parent(models.Model):
             field1 = models.IntegerField()
@@ -1709,6 +1804,7 @@ class ConstraintsTests(TestCase):
             ),
         ])
 
+    @skipUnlessDBFeature('supports_covering_indexes')
     def test_unique_constraint_include_pointing_to_fk(self):
         class Target(models.Model):
             pass