1
0
mirror of https://github.com/django/django.git synced 2025-10-27 07:36:08 +00:00

Fixed #36273 -- Moved Index system checks from Model to Index.check().

This commit is contained in:
Tim Graham
2025-04-19 20:32:58 -04:00
committed by Sarah Boyce
parent 8620a3b0c7
commit 8638d8bf74
3 changed files with 111 additions and 97 deletions

View File

@@ -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

View File

@@ -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