summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIchlasul Affan <ichlaffterlalu@gmail.com>2020-03-12 02:01:32 +0700
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2020-04-10 12:00:27 +0200
commit53d229ff632c2a3e547f2820a94239f38ba4d4ac (patch)
tree8bdd286062729f58e4508e51bb16734a1ab027a8
parente8d308892597e6abee5b94a77543a7ef7d34a655 (diff)
Fixed #31351 -- Added system checks for partial indexes and unique constraints support.
-rw-r--r--django/db/models/base.py56
-rw-r--r--docs/ref/checks.txt3
-rw-r--r--tests/invalid_models_tests/test_models.py89
3 files changed, 140 insertions, 8 deletions
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 919371540d..0f8a8c50f3 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -19,7 +19,7 @@ from django.db.models import (
NOT_PROVIDED, ExpressionWrapper, IntegerField, Max, Value,
)
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.fields.related import (
ForeignObjectRel, OneToOneField, lazy_related_operation, resolve_relation,
@@ -1276,7 +1276,7 @@ class Model(metaclass=ModelBase):
errors += [
*cls._check_index_together(),
*cls._check_unique_together(),
- *cls._check_indexes(),
+ *cls._check_indexes(databases),
*cls._check_ordering(),
*cls._check_constraints(databases),
]
@@ -1585,8 +1585,8 @@ class Model(metaclass=ModelBase):
return errors
@classmethod
- def _check_indexes(cls):
- """Check the fields and names of indexes."""
+ def _check_indexes(cls, databases):
+ """Check fields, names, and conditions of indexes."""
errors = []
for index in cls._meta.indexes:
# Index name can't start with an underscore or a number, restricted
@@ -1609,6 +1609,28 @@ class Model(metaclass=ModelBase):
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]
errors.extend(cls._check_local_fields(fields, 'indexes'))
return errors
@@ -1845,12 +1867,13 @@ class Model(metaclass=ModelBase):
if not router.allow_migrate_model(db, cls):
continue
connection = connections[db]
- if (
+ if not (
connection.features.supports_table_check_constraints or
'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(
checks.Warning(
'%s does not support check constraints.' % connection.display_name,
@@ -1862,6 +1885,25 @@ class Model(metaclass=ModelBase):
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
diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt
index ec3735598a..d9bfdb168c 100644
--- a/docs/ref/checks.txt
+++ b/docs/ref/checks.txt
@@ -349,6 +349,9 @@ Models
``<max_length>`` characters.
* **models.W035**: ``db_table`` ``<db_table>`` is used by multiple models:
``<model list>``.
+* **models.W036**: ``<database>`` does not support unique constraints with
+ conditions.
+* **models.W037**: ``<database>`` does not support indexes with conditions.
Security
--------
diff --git a/tests/invalid_models_tests/test_models.py b/tests/invalid_models_tests/test_models.py
index 6090724adb..f3f0bc8bd5 100644
--- a/tests/invalid_models_tests/test_models.py
+++ b/tests/invalid_models_tests/test_models.py
@@ -234,7 +234,7 @@ class UniqueTogetherTests(SimpleTestCase):
@isolate_apps('invalid_models_tests')
-class IndexesTests(SimpleTestCase):
+class IndexesTests(TestCase):
def test_pointing_to_missing_field(self):
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')
class FieldNamesTests(TestCase):
@@ -1325,5 +1369,48 @@ class ConstraintsTests(TestCase):
class Meta:
required_db_features = {'supports_table_check_constraints'}
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), [])