Fixed #31351 -- Added system checks for partial indexes and unique constraints support.
This commit is contained in:
parent
e8d3088925
commit
53d229ff63
|
@ -19,7 +19,7 @@ from django.db.models import (
|
||||||
NOT_PROVIDED, ExpressionWrapper, IntegerField, Max, Value,
|
NOT_PROVIDED, ExpressionWrapper, IntegerField, Max, Value,
|
||||||
)
|
)
|
||||||
from django.db.models.constants import LOOKUP_SEP
|
from django.db.models.constants import LOOKUP_SEP
|
||||||
from django.db.models.constraints import CheckConstraint
|
from django.db.models.constraints import CheckConstraint, UniqueConstraint
|
||||||
from django.db.models.deletion import CASCADE, Collector
|
from django.db.models.deletion import CASCADE, Collector
|
||||||
from django.db.models.fields.related import (
|
from django.db.models.fields.related import (
|
||||||
ForeignObjectRel, OneToOneField, lazy_related_operation, resolve_relation,
|
ForeignObjectRel, OneToOneField, lazy_related_operation, resolve_relation,
|
||||||
|
@ -1276,7 +1276,7 @@ class Model(metaclass=ModelBase):
|
||||||
errors += [
|
errors += [
|
||||||
*cls._check_index_together(),
|
*cls._check_index_together(),
|
||||||
*cls._check_unique_together(),
|
*cls._check_unique_together(),
|
||||||
*cls._check_indexes(),
|
*cls._check_indexes(databases),
|
||||||
*cls._check_ordering(),
|
*cls._check_ordering(),
|
||||||
*cls._check_constraints(databases),
|
*cls._check_constraints(databases),
|
||||||
]
|
]
|
||||||
|
@ -1585,8 +1585,8 @@ class Model(metaclass=ModelBase):
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _check_indexes(cls):
|
def _check_indexes(cls, databases):
|
||||||
"""Check the fields and names of indexes."""
|
"""Check fields, names, and conditions of indexes."""
|
||||||
errors = []
|
errors = []
|
||||||
for index in cls._meta.indexes:
|
for index in cls._meta.indexes:
|
||||||
# Index name can't start with an underscore or a number, restricted
|
# Index name can't start with an underscore or a number, restricted
|
||||||
|
@ -1609,6 +1609,28 @@ class Model(metaclass=ModelBase):
|
||||||
id='models.E034',
|
id='models.E034',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
for db in databases:
|
||||||
|
if not router.allow_migrate_model(db, cls):
|
||||||
|
continue
|
||||||
|
connection = connections[db]
|
||||||
|
if (
|
||||||
|
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):
|
||||||
|
errors.append(
|
||||||
|
checks.Warning(
|
||||||
|
'%s does not support indexes with conditions.'
|
||||||
|
% connection.display_name,
|
||||||
|
hint=(
|
||||||
|
"Conditions will be ignored. Silence this warning "
|
||||||
|
"if you don't care about it."
|
||||||
|
),
|
||||||
|
obj=cls,
|
||||||
|
id='models.W037',
|
||||||
|
)
|
||||||
|
)
|
||||||
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]
|
||||||
errors.extend(cls._check_local_fields(fields, 'indexes'))
|
errors.extend(cls._check_local_fields(fields, 'indexes'))
|
||||||
return errors
|
return errors
|
||||||
|
@ -1845,12 +1867,13 @@ 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_table_check_constraints or
|
connection.features.supports_table_check_constraints or
|
||||||
'supports_table_check_constraints' in cls._meta.required_db_features
|
'supports_table_check_constraints' in cls._meta.required_db_features
|
||||||
|
) and any(
|
||||||
|
isinstance(constraint, CheckConstraint)
|
||||||
|
for constraint in cls._meta.constraints
|
||||||
):
|
):
|
||||||
continue
|
|
||||||
if any(isinstance(constraint, CheckConstraint) for constraint in cls._meta.constraints):
|
|
||||||
errors.append(
|
errors.append(
|
||||||
checks.Warning(
|
checks.Warning(
|
||||||
'%s does not support check constraints.' % connection.display_name,
|
'%s does not support check constraints.' % connection.display_name,
|
||||||
|
@ -1862,6 +1885,25 @@ class Model(metaclass=ModelBase):
|
||||||
id='models.W027',
|
id='models.W027',
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
if not (
|
||||||
|
connection.features.supports_partial_indexes or
|
||||||
|
'supports_partial_indexes' in cls._meta.required_db_features
|
||||||
|
) and any(
|
||||||
|
isinstance(constraint, UniqueConstraint) and constraint.condition is not None
|
||||||
|
for constraint in cls._meta.constraints
|
||||||
|
):
|
||||||
|
errors.append(
|
||||||
|
checks.Warning(
|
||||||
|
'%s does not support unique constraints with '
|
||||||
|
'conditions.' % connection.display_name,
|
||||||
|
hint=(
|
||||||
|
"A constraint won't be created. Silence this "
|
||||||
|
"warning if you don't care about it."
|
||||||
|
),
|
||||||
|
obj=cls,
|
||||||
|
id='models.W036',
|
||||||
|
)
|
||||||
|
)
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -349,6 +349,9 @@ Models
|
||||||
``<max_length>`` characters.
|
``<max_length>`` characters.
|
||||||
* **models.W035**: ``db_table`` ``<db_table>`` is used by multiple models:
|
* **models.W035**: ``db_table`` ``<db_table>`` is used by multiple models:
|
||||||
``<model list>``.
|
``<model list>``.
|
||||||
|
* **models.W036**: ``<database>`` does not support unique constraints with
|
||||||
|
conditions.
|
||||||
|
* **models.W037**: ``<database>`` does not support indexes with conditions.
|
||||||
|
|
||||||
Security
|
Security
|
||||||
--------
|
--------
|
||||||
|
|
|
@ -234,7 +234,7 @@ class UniqueTogetherTests(SimpleTestCase):
|
||||||
|
|
||||||
|
|
||||||
@isolate_apps('invalid_models_tests')
|
@isolate_apps('invalid_models_tests')
|
||||||
class IndexesTests(SimpleTestCase):
|
class IndexesTests(TestCase):
|
||||||
|
|
||||||
def test_pointing_to_missing_field(self):
|
def test_pointing_to_missing_field(self):
|
||||||
class Model(models.Model):
|
class Model(models.Model):
|
||||||
|
@ -331,6 +331,50 @@ class IndexesTests(SimpleTestCase):
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
def test_index_with_condition(self):
|
||||||
|
class Model(models.Model):
|
||||||
|
age = models.IntegerField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
indexes = [
|
||||||
|
models.Index(
|
||||||
|
fields=['age'],
|
||||||
|
name='index_age_gte_10',
|
||||||
|
condition=models.Q(age__gte=10),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
errors = Model.check(databases=self.databases)
|
||||||
|
expected = [] if connection.features.supports_partial_indexes else [
|
||||||
|
Warning(
|
||||||
|
'%s does not support indexes with conditions.'
|
||||||
|
% connection.display_name,
|
||||||
|
hint=(
|
||||||
|
"Conditions will be ignored. Silence this warning if you "
|
||||||
|
"don't care about it."
|
||||||
|
),
|
||||||
|
obj=Model,
|
||||||
|
id='models.W037',
|
||||||
|
)
|
||||||
|
]
|
||||||
|
self.assertEqual(errors, expected)
|
||||||
|
|
||||||
|
def test_index_with_condition_required_db_features(self):
|
||||||
|
class Model(models.Model):
|
||||||
|
age = models.IntegerField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
required_db_features = {'supports_partial_indexes'}
|
||||||
|
indexes = [
|
||||||
|
models.Index(
|
||||||
|
fields=['age'],
|
||||||
|
name='index_age_gte_10',
|
||||||
|
condition=models.Q(age__gte=10),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assertEqual(Model.check(databases=self.databases), [])
|
||||||
|
|
||||||
|
|
||||||
@isolate_apps('invalid_models_tests')
|
@isolate_apps('invalid_models_tests')
|
||||||
class FieldNamesTests(TestCase):
|
class FieldNamesTests(TestCase):
|
||||||
|
@ -1325,5 +1369,48 @@ class ConstraintsTests(TestCase):
|
||||||
class Meta:
|
class Meta:
|
||||||
required_db_features = {'supports_table_check_constraints'}
|
required_db_features = {'supports_table_check_constraints'}
|
||||||
constraints = [models.CheckConstraint(check=models.Q(age__gte=18), name='is_adult')]
|
constraints = [models.CheckConstraint(check=models.Q(age__gte=18), name='is_adult')]
|
||||||
|
self.assertEqual(Model.check(databases=self.databases), [])
|
||||||
|
|
||||||
|
def test_unique_constraint_with_condition(self):
|
||||||
|
class Model(models.Model):
|
||||||
|
age = models.IntegerField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=['age'],
|
||||||
|
name='unique_age_gte_100',
|
||||||
|
condition=models.Q(age__gte=100),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
errors = Model.check(databases=self.databases)
|
||||||
|
expected = [] if connection.features.supports_partial_indexes else [
|
||||||
|
Warning(
|
||||||
|
'%s does not support unique constraints with conditions.'
|
||||||
|
% connection.display_name,
|
||||||
|
hint=(
|
||||||
|
"A constraint won't be created. Silence this warning if "
|
||||||
|
"you don't care about it."
|
||||||
|
),
|
||||||
|
obj=Model,
|
||||||
|
id='models.W036',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
self.assertEqual(errors, expected)
|
||||||
|
|
||||||
|
def test_unique_constraint_with_condition_required_db_features(self):
|
||||||
|
class Model(models.Model):
|
||||||
|
age = models.IntegerField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
required_db_features = {'supports_partial_indexes'}
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=['age'],
|
||||||
|
name='unique_age_gte_100',
|
||||||
|
condition=models.Q(age__gte=100),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
self.assertEqual(Model.check(databases=self.databases), [])
|
self.assertEqual(Model.check(databases=self.databases), [])
|
||||||
|
|
Loading…
Reference in New Issue