summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMariusz Felisiak <felisiak.mariusz@gmail.com>2020-06-03 06:44:05 +0200
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2020-06-04 12:26:25 +0200
commitf83b44075dafa429d59e8755aa47e15577cc49f9 (patch)
tree71191d9568e2360527292b8f0b358aa71597c7da
parent8c7992f658e1125f8dc20b19206e8bbbe4187372 (diff)
Refs #30913 -- Added system checks for covering indexes and unique constraints support.
-rw-r--r--django/db/models/base.py41
-rw-r--r--docs/ref/checks.txt4
-rw-r--r--tests/constraints/models.py1
-rw-r--r--tests/invalid_models_tests/test_models.py96
4 files changed, 138 insertions, 4 deletions
diff --git a/django/db/models/base.py b/django/db/models/base.py
index d082c35917..fcda6e202f 100644
--- a/django/db/models/base.py
+++ b/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)
diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt
index d5aeca2d8c..792c3936f6 100644
--- a/docs/ref/checks.txt
+++ b/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
--------
diff --git a/tests/constraints/models.py b/tests/constraints/models.py
index 947d1b4ff1..1460673a18 100644
--- a/tests/constraints/models.py
+++ b/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(
diff --git a/tests/invalid_models_tests/test_models.py b/tests/invalid_models_tests/test_models.py
index 374eff6028..7875705860 100644
--- a/tests/invalid_models_tests/test_models.py
+++ b/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