mirror of
https://github.com/django/django.git
synced 2025-06-08 21:19:13 +00:00
Fixed #36273 -- Moved Index system checks from Model to Index.check().
This commit is contained in:
parent
8620a3b0c7
commit
8638d8bf74
@ -2115,93 +2115,13 @@ class Model(AltersData, metaclass=ModelBase):
|
||||
|
||||
@classmethod
|
||||
def _check_indexes(cls, databases):
|
||||
"""Check fields, names, and conditions of indexes."""
|
||||
errors = []
|
||||
references = set()
|
||||
for index in cls._meta.indexes:
|
||||
# Index name can't start with an underscore or a number, restricted
|
||||
# for cross-database compatibility with Oracle.
|
||||
if index.name[0] == "_" or index.name[0].isdigit():
|
||||
errors.append(
|
||||
checks.Error(
|
||||
"The index name '%s' cannot start with an underscore "
|
||||
"or a number." % index.name,
|
||||
obj=cls,
|
||||
id="models.E033",
|
||||
),
|
||||
)
|
||||
if len(index.name) > index.max_name_length:
|
||||
errors.append(
|
||||
checks.Error(
|
||||
"The index name '%s' cannot be longer than %d "
|
||||
"characters." % (index.name, index.max_name_length),
|
||||
obj=cls,
|
||||
id="models.E034",
|
||||
),
|
||||
)
|
||||
if index.contains_expressions:
|
||||
for expression in index.expressions:
|
||||
references.update(
|
||||
ref[0] for ref in cls._get_expr_references(expression)
|
||||
)
|
||||
for db in databases:
|
||||
if not router.allow_migrate_model(db, cls):
|
||||
continue
|
||||
connection = connections[db]
|
||||
if not (
|
||||
connection.features.supports_partial_indexes
|
||||
or "supports_partial_indexes" in cls._meta.required_db_features
|
||||
) and 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",
|
||||
)
|
||||
)
|
||||
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",
|
||||
)
|
||||
)
|
||||
if not (
|
||||
connection.features.supports_expression_indexes
|
||||
or "supports_expression_indexes" in cls._meta.required_db_features
|
||||
) and any(index.contains_expressions for index in cls._meta.indexes):
|
||||
errors.append(
|
||||
checks.Warning(
|
||||
"%s does not support indexes on expressions."
|
||||
% connection.display_name,
|
||||
hint=(
|
||||
"An index won't be created. Silence this warning "
|
||||
"if you don't care about it."
|
||||
),
|
||||
obj=cls,
|
||||
id="models.W043",
|
||||
)
|
||||
)
|
||||
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 += references
|
||||
errors.extend(cls._check_local_fields(fields, "indexes"))
|
||||
for index in cls._meta.indexes:
|
||||
errors.extend(index.check(cls, connection))
|
||||
return errors
|
||||
|
||||
@classmethod
|
||||
|
@ -1,5 +1,6 @@
|
||||
from types import NoneType
|
||||
|
||||
from django.core import checks
|
||||
from django.db.backends.utils import names_digest, split_identifier
|
||||
from django.db.models.expressions import Col, ExpressionList, F, Func, OrderBy
|
||||
from django.db.models.functions import Collate
|
||||
@ -82,6 +83,92 @@ class Index:
|
||||
def contains_expressions(self):
|
||||
return bool(self.expressions)
|
||||
|
||||
def check(self, model, connection):
|
||||
"""Check fields, names, and conditions of indexes."""
|
||||
errors = []
|
||||
# Index name can't start with an underscore or a number (restricted
|
||||
# for cross-database compatibility with Oracle)
|
||||
if self.name[0] == "_" or self.name[0].isdigit():
|
||||
errors.append(
|
||||
checks.Error(
|
||||
"The index name '%s' cannot start with an underscore "
|
||||
"or a number." % self.name,
|
||||
obj=model,
|
||||
id="models.E033",
|
||||
),
|
||||
)
|
||||
if len(self.name) > self.max_name_length:
|
||||
errors.append(
|
||||
checks.Error(
|
||||
"The index name '%s' cannot be longer than %d "
|
||||
"characters." % (self.name, self.max_name_length),
|
||||
obj=model,
|
||||
id="models.E034",
|
||||
),
|
||||
)
|
||||
references = set()
|
||||
if self.contains_expressions:
|
||||
for expression in self.expressions:
|
||||
references.update(
|
||||
ref[0] for ref in model._get_expr_references(expression)
|
||||
)
|
||||
errors.extend(
|
||||
model._check_local_fields(
|
||||
{*self.fields, *self.include, *references}, "indexes"
|
||||
)
|
||||
)
|
||||
# Database-feature checks:
|
||||
required_db_features = model._meta.required_db_features
|
||||
if not (
|
||||
connection.features.supports_partial_indexes
|
||||
or "supports_partial_indexes" in required_db_features
|
||||
) and any(self.condition is not None for index in model._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=model,
|
||||
id="models.W037",
|
||||
)
|
||||
)
|
||||
if not (
|
||||
connection.features.supports_covering_indexes
|
||||
or "supports_covering_indexes" in required_db_features
|
||||
) and any(index.include for index in model._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=model,
|
||||
id="models.W040",
|
||||
)
|
||||
)
|
||||
if not (
|
||||
connection.features.supports_expression_indexes
|
||||
or "supports_expression_indexes" in required_db_features
|
||||
) and any(index.contains_expressions for index in model._meta.indexes):
|
||||
errors.append(
|
||||
checks.Warning(
|
||||
"%s does not support indexes on expressions."
|
||||
% connection.display_name,
|
||||
hint=(
|
||||
"An index won't be created. Silence this warning "
|
||||
"if you don't care about it."
|
||||
),
|
||||
obj=model,
|
||||
id="models.W043",
|
||||
)
|
||||
)
|
||||
return errors
|
||||
|
||||
def _get_condition_sql(self, model, schema_editor):
|
||||
if self.condition is None:
|
||||
return None
|
||||
|
@ -175,7 +175,7 @@ class IndexesTests(TestCase):
|
||||
indexes = [models.Index(fields=["missing_field"], name="name")]
|
||||
|
||||
self.assertEqual(
|
||||
Model.check(),
|
||||
Model.check(databases=self.databases),
|
||||
[
|
||||
Error(
|
||||
"'indexes' refers to the nonexistent field 'missing_field'.",
|
||||
@ -193,7 +193,7 @@ class IndexesTests(TestCase):
|
||||
indexes = [models.Index(fields=["m2m"], name="name")]
|
||||
|
||||
self.assertEqual(
|
||||
Model.check(),
|
||||
Model.check(databases=self.databases),
|
||||
[
|
||||
Error(
|
||||
"'indexes' refers to a ManyToManyField 'm2m', but "
|
||||
@ -215,7 +215,7 @@ class IndexesTests(TestCase):
|
||||
indexes = [models.Index(fields=["field2", "field1"], name="name")]
|
||||
|
||||
self.assertEqual(
|
||||
Bar.check(),
|
||||
Bar.check(databases=self.databases),
|
||||
[
|
||||
Error(
|
||||
"'indexes' refers to field 'field1' which is not local to "
|
||||
@ -244,7 +244,7 @@ class IndexesTests(TestCase):
|
||||
models.Index(fields=["foo_1_id", "foo_2"], name="index_name")
|
||||
]
|
||||
|
||||
self.assertEqual(Bar.check(), [])
|
||||
self.assertEqual(Bar.check(databases=self.databases), [])
|
||||
|
||||
def test_pointing_to_composite_primary_key(self):
|
||||
class Model(models.Model):
|
||||
@ -256,7 +256,7 @@ class IndexesTests(TestCase):
|
||||
indexes = [models.Index(fields=["pk", "name"], name="name")]
|
||||
|
||||
self.assertEqual(
|
||||
Model.check(),
|
||||
Model.check(databases=self.databases),
|
||||
[
|
||||
Error(
|
||||
"'indexes' refers to a CompositePrimaryKey 'pk', but "
|
||||
@ -276,7 +276,7 @@ class IndexesTests(TestCase):
|
||||
]
|
||||
|
||||
self.assertEqual(
|
||||
Model.check(),
|
||||
Model.check(databases=self.databases),
|
||||
[
|
||||
Error(
|
||||
"The index name '%sindex_name' cannot start with an "
|
||||
@ -296,7 +296,7 @@ class IndexesTests(TestCase):
|
||||
indexes = [models.Index(fields=["id"], name=index_name)]
|
||||
|
||||
self.assertEqual(
|
||||
Model.check(),
|
||||
Model.check(databases=self.databases),
|
||||
[
|
||||
Error(
|
||||
"The index name '%s' cannot be longer than 30 characters."
|
||||
@ -499,7 +499,7 @@ class IndexesTests(TestCase):
|
||||
indexes = [models.Index(fields=["name"], include=["pk"], name="name")]
|
||||
|
||||
self.assertEqual(
|
||||
Model.check(),
|
||||
Model.check(databases=self.databases),
|
||||
[
|
||||
Error(
|
||||
"'indexes' refers to a CompositePrimaryKey 'pk', but "
|
||||
@ -539,6 +539,7 @@ class IndexesTests(TestCase):
|
||||
|
||||
self.assertEqual(Model.check(databases=self.databases), [])
|
||||
|
||||
@skipUnlessDBFeature("supports_expression_indexes")
|
||||
def test_func_index_complex_expression_custom_lookup(self):
|
||||
class Model(models.Model):
|
||||
height = models.IntegerField()
|
||||
@ -554,15 +555,16 @@ class IndexesTests(TestCase):
|
||||
]
|
||||
|
||||
with register_lookup(models.IntegerField, Abs):
|
||||
self.assertEqual(Model.check(), [])
|
||||
self.assertEqual(Model.check(databases=self.databases), [])
|
||||
|
||||
@skipUnlessDBFeature("supports_expression_indexes")
|
||||
def test_func_index_pointing_to_missing_field(self):
|
||||
class Model(models.Model):
|
||||
class Meta:
|
||||
indexes = [models.Index(Lower("missing_field").desc(), name="name")]
|
||||
|
||||
self.assertEqual(
|
||||
Model.check(),
|
||||
Model.check(databases=self.databases),
|
||||
[
|
||||
Error(
|
||||
"'indexes' refers to the nonexistent field 'missing_field'.",
|
||||
@ -572,6 +574,7 @@ class IndexesTests(TestCase):
|
||||
],
|
||||
)
|
||||
|
||||
@skipUnlessDBFeature("supports_expression_indexes")
|
||||
def test_func_index_pointing_to_missing_field_nested(self):
|
||||
class Model(models.Model):
|
||||
class Meta:
|
||||
@ -580,7 +583,7 @@ class IndexesTests(TestCase):
|
||||
]
|
||||
|
||||
self.assertEqual(
|
||||
Model.check(),
|
||||
Model.check(databases=self.databases),
|
||||
[
|
||||
Error(
|
||||
"'indexes' refers to the nonexistent field 'missing_field'.",
|
||||
@ -590,6 +593,7 @@ class IndexesTests(TestCase):
|
||||
],
|
||||
)
|
||||
|
||||
@skipUnlessDBFeature("supports_expression_indexes")
|
||||
def test_func_index_pointing_to_m2m_field(self):
|
||||
class Model(models.Model):
|
||||
m2m = models.ManyToManyField("self")
|
||||
@ -598,7 +602,7 @@ class IndexesTests(TestCase):
|
||||
indexes = [models.Index(Lower("m2m"), name="name")]
|
||||
|
||||
self.assertEqual(
|
||||
Model.check(),
|
||||
Model.check(databases=self.databases),
|
||||
[
|
||||
Error(
|
||||
"'indexes' refers to a ManyToManyField 'm2m', but "
|
||||
@ -609,6 +613,7 @@ class IndexesTests(TestCase):
|
||||
],
|
||||
)
|
||||
|
||||
@skipUnlessDBFeature("supports_expression_indexes")
|
||||
def test_func_index_pointing_to_non_local_field(self):
|
||||
class Foo(models.Model):
|
||||
field1 = models.CharField(max_length=15)
|
||||
@ -618,7 +623,7 @@ class IndexesTests(TestCase):
|
||||
indexes = [models.Index(Lower("field1"), name="name")]
|
||||
|
||||
self.assertEqual(
|
||||
Bar.check(),
|
||||
Bar.check(databases=self.databases),
|
||||
[
|
||||
Error(
|
||||
"'indexes' refers to field 'field1' which is not local to "
|
||||
@ -630,6 +635,7 @@ class IndexesTests(TestCase):
|
||||
],
|
||||
)
|
||||
|
||||
@skipUnlessDBFeature("supports_expression_indexes")
|
||||
def test_func_index_pointing_to_fk(self):
|
||||
class Foo(models.Model):
|
||||
pass
|
||||
@ -643,8 +649,9 @@ class IndexesTests(TestCase):
|
||||
models.Index(Lower("foo_1_id"), Lower("foo_2"), name="index_name"),
|
||||
]
|
||||
|
||||
self.assertEqual(Bar.check(), [])
|
||||
self.assertEqual(Bar.check(databases=self.databases), [])
|
||||
|
||||
@skipUnlessDBFeature("supports_expression_indexes")
|
||||
def test_func_index_pointing_to_composite_primary_key(self):
|
||||
class Model(models.Model):
|
||||
pk = models.CompositePrimaryKey("version", "name")
|
||||
@ -655,7 +662,7 @@ class IndexesTests(TestCase):
|
||||
indexes = [models.Index(Abs("pk"), name="name")]
|
||||
|
||||
self.assertEqual(
|
||||
Model.check(),
|
||||
Model.check(databases=self.databases),
|
||||
[
|
||||
Error(
|
||||
"'indexes' refers to a CompositePrimaryKey 'pk', but "
|
||||
|
Loading…
x
Reference in New Issue
Block a user