mirror of https://github.com/django/django.git
Refs #30913 -- Added system checks for covering indexes and unique constraints support.
This commit is contained in:
parent
8c7992f658
commit
f83b44075d
|
@ -1614,12 +1614,10 @@ class Model(metaclass=ModelBase):
|
||||||
if not router.allow_migrate_model(db, cls):
|
if not router.allow_migrate_model(db, cls):
|
||||||
continue
|
continue
|
||||||
connection = connections[db]
|
connection = connections[db]
|
||||||
if (
|
if not (
|
||||||
connection.features.supports_partial_indexes or
|
connection.features.supports_partial_indexes or
|
||||||
'supports_partial_indexes' in cls._meta.required_db_features
|
'supports_partial_indexes' in cls._meta.required_db_features
|
||||||
):
|
) and any(index.condition is not None for index in cls._meta.indexes):
|
||||||
continue
|
|
||||||
if any(index.condition is not None for index in cls._meta.indexes):
|
|
||||||
errors.append(
|
errors.append(
|
||||||
checks.Warning(
|
checks.Warning(
|
||||||
'%s does not support indexes with conditions.'
|
'%s does not support indexes with conditions.'
|
||||||
|
@ -1632,6 +1630,22 @@ class Model(metaclass=ModelBase):
|
||||||
id='models.W037',
|
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 = [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]
|
fields += [include for index in cls._meta.indexes for include in index.include]
|
||||||
errors.extend(cls._check_local_fields(fields, 'indexes'))
|
errors.extend(cls._check_local_fields(fields, 'indexes'))
|
||||||
|
@ -1927,6 +1941,25 @@ class Model(metaclass=ModelBase):
|
||||||
id='models.W038',
|
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(
|
fields = chain.from_iterable(
|
||||||
(*constraint.fields, *constraint.include)
|
(*constraint.fields, *constraint.include)
|
||||||
for constraint in cls._meta.constraints if isinstance(constraint, UniqueConstraint)
|
for constraint in cls._meta.constraints if isinstance(constraint, UniqueConstraint)
|
||||||
|
|
|
@ -360,6 +360,10 @@ Models
|
||||||
* **models.W037**: ``<database>`` does not support indexes with conditions.
|
* **models.W037**: ``<database>`` does not support indexes with conditions.
|
||||||
* **models.W038**: ``<database>`` does not support deferrable unique
|
* **models.W038**: ``<database>`` does not support deferrable unique
|
||||||
constraints.
|
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
|
Security
|
||||||
--------
|
--------
|
||||||
|
|
|
@ -88,6 +88,7 @@ class UniqueConstraintInclude(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
required_db_features = {
|
required_db_features = {
|
||||||
'supports_table_check_constraints',
|
'supports_table_check_constraints',
|
||||||
|
'supports_covering_indexes',
|
||||||
}
|
}
|
||||||
constraints = [
|
constraints = [
|
||||||
models.UniqueConstraint(
|
models.UniqueConstraint(
|
||||||
|
|
|
@ -375,6 +375,51 @@ class IndexesTests(TestCase):
|
||||||
|
|
||||||
self.assertEqual(Model.check(databases=self.databases), [])
|
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):
|
def test_index_include_pointing_to_missing_field(self):
|
||||||
class Model(models.Model):
|
class Model(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -390,6 +435,7 @@ class IndexesTests(TestCase):
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_covering_indexes')
|
||||||
def test_index_include_pointing_to_m2m_field(self):
|
def test_index_include_pointing_to_m2m_field(self):
|
||||||
class Model(models.Model):
|
class Model(models.Model):
|
||||||
m2m = models.ManyToManyField('self')
|
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):
|
def test_index_include_pointing_to_non_local_field(self):
|
||||||
class Parent(models.Model):
|
class Parent(models.Model):
|
||||||
field1 = models.IntegerField()
|
field1 = models.IntegerField()
|
||||||
|
@ -428,6 +475,7 @@ class IndexesTests(TestCase):
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_covering_indexes')
|
||||||
def test_index_include_pointing_to_fk(self):
|
def test_index_include_pointing_to_fk(self):
|
||||||
class Target(models.Model):
|
class Target(models.Model):
|
||||||
pass
|
pass
|
||||||
|
@ -1641,6 +1689,51 @@ class ConstraintsTests(TestCase):
|
||||||
|
|
||||||
self.assertEqual(Model.check(databases=self.databases), [])
|
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):
|
def test_unique_constraint_include_pointing_to_missing_field(self):
|
||||||
class Model(models.Model):
|
class Model(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -1661,6 +1754,7 @@ class ConstraintsTests(TestCase):
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_covering_indexes')
|
||||||
def test_unique_constraint_include_pointing_to_m2m_field(self):
|
def test_unique_constraint_include_pointing_to_m2m_field(self):
|
||||||
class Model(models.Model):
|
class Model(models.Model):
|
||||||
m2m = models.ManyToManyField('self')
|
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):
|
def test_unique_constraint_include_pointing_to_non_local_field(self):
|
||||||
class Parent(models.Model):
|
class Parent(models.Model):
|
||||||
field1 = models.IntegerField()
|
field1 = models.IntegerField()
|
||||||
|
@ -1709,6 +1804,7 @@ class ConstraintsTests(TestCase):
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_covering_indexes')
|
||||||
def test_unique_constraint_include_pointing_to_fk(self):
|
def test_unique_constraint_include_pointing_to_fk(self):
|
||||||
class Target(models.Model):
|
class Target(models.Model):
|
||||||
pass
|
pass
|
||||||
|
|
Loading…
Reference in New Issue