Kaydet (Commit) 0123b67f authored tarafından Paveł Tyślacki's avatar Paveł Tyślacki Kaydeden (comit) Tim Graham

Fixed #30060 -- Moved SQL generation for indexes and constraints to SchemaEditor.

üst e5ae9488
......@@ -114,7 +114,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
(old_type.startswith('citext') and not new_type.startswith('citext'))
):
index_name = self._create_index_name(model._meta.db_table, [old_field.column], suffix='_like')
self.execute(self._delete_constraint_sql(self.sql_delete_index, model, index_name))
self.execute(self._delete_index_sql(model, index_name))
super()._alter_field(
model, old_field, new_field, old_type, new_type, old_db_params,
......@@ -130,7 +130,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
# Removed an index? Drop any PostgreSQL-specific indexes.
if old_field.unique and not (new_field.db_index or new_field.unique):
index_to_remove = self._create_index_name(model._meta.db_table, [old_field.column], suffix='_like')
self.execute(self._delete_constraint_sql(self.sql_delete_index, model, index_to_remove))
self.execute(self._delete_index_sql(model, index_to_remove))
def _index_columns(self, table, columns, col_suffixes, opclasses):
if opclasses:
......
......@@ -11,10 +11,10 @@ from django.db.utils import NotSupportedError
class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
sql_delete_table = "DROP TABLE %(table)s"
sql_create_fk = None
sql_create_inline_fk = "REFERENCES %(to_table)s (%(to_column)s) DEFERRABLE INITIALLY DEFERRED"
sql_create_unique = "CREATE UNIQUE INDEX %(name)s ON %(table)s (%(columns)s)"
sql_delete_unique = "DROP INDEX %(name)s"
sql_foreign_key_constraint = None
def __enter__(self):
# Some SQLite schema alterations need foreign key constraints to be
......
......@@ -10,25 +10,11 @@ class BaseConstraint:
def constraint_sql(self, model, schema_editor):
raise NotImplementedError('This method must be implemented by a subclass.')
def full_constraint_sql(self, model, schema_editor):
return schema_editor.sql_constraint % {
'name': schema_editor.quote_name(self.name),
'constraint': self.constraint_sql(model, schema_editor),
}
def create_sql(self, model, schema_editor):
sql = self.full_constraint_sql(model, schema_editor)
return schema_editor.sql_create_constraint % {
'table': schema_editor.quote_name(model._meta.db_table),
'constraint': sql,
}
raise NotImplementedError('This method must be implemented by a subclass.')
def remove_sql(self, model, schema_editor):
quote_name = schema_editor.quote_name
return schema_editor.sql_delete_constraint % {
'table': quote_name(model._meta.db_table),
'name': quote_name(self.name),
}
raise NotImplementedError('This method must be implemented by a subclass.')
def deconstruct(self):
path = '%s.%s' % (self.__class__.__module__, self.__class__.__name__)
......@@ -45,14 +31,23 @@ class CheckConstraint(BaseConstraint):
self.check = check
super().__init__(name)
def constraint_sql(self, model, schema_editor):
query = Query(model)
def _get_check_sql(self, model, schema_editor):
query = Query(model=model)
where = query.build_where(self.check)
connection = schema_editor.connection
compiler = connection.ops.compiler('SQLCompiler')(query, connection, 'default')
sql, params = where.as_sql(compiler, connection)
params = tuple(schema_editor.quote_value(p) for p in params)
return schema_editor.sql_check_constraint % {'check': sql % params}
compiler = query.get_compiler(connection=schema_editor.connection)
sql, params = where.as_sql(compiler, schema_editor.connection)
return sql % tuple(schema_editor.quote_value(p) for p in params)
def constraint_sql(self, model, schema_editor):
check = self._get_check_sql(model, schema_editor)
return schema_editor._check_sql(self.name, check)
def create_sql(self, model, schema_editor):
check = self._get_check_sql(model, schema_editor)
return schema_editor._create_check_sql(model, self.name, check)
def remove_sql(self, model, schema_editor):
return schema_editor._delete_check_sql(model, self.name)
def __repr__(self):
return "<%s: check='%s' name=%r>" % (self.__class__.__name__, self.check, self.name)
......@@ -78,17 +73,15 @@ class UniqueConstraint(BaseConstraint):
super().__init__(name)
def constraint_sql(self, model, schema_editor):
columns = (
model._meta.get_field(field_name).column
for field_name in self.fields
)
return schema_editor.sql_unique_constraint % {
'columns': ', '.join(map(schema_editor.quote_name, columns)),
}
fields = [model._meta.get_field(field_name).column for field_name in self.fields]
return schema_editor._unique_sql(fields, self.name)
def create_sql(self, model, schema_editor):
columns = [model._meta.get_field(field_name).column for field_name in self.fields]
return schema_editor._create_unique_sql(model, columns, self.name)
fields = [model._meta.get_field(field_name).column for field_name in self.fields]
return schema_editor._create_unique_sql(model, fields, self.name)
def remove_sql(self, model, schema_editor):
return schema_editor._delete_unique_sql(model, self.name)
def __repr__(self):
return '<%s: fields=%r name=%r>' % (self.__class__.__name__, self.fields, self.name)
......
......@@ -65,7 +65,7 @@ class Index:
sql, params = query.where.as_sql(compiler=compiler, connection=schema_editor.connection)
# BaseDatabaseSchemaEditor does the same map on the params, but since
# it's handled outside of that class, the work is done here.
return ' WHERE ' + (sql % tuple(map(schema_editor.quote_value, params)))
return sql % tuple(map(schema_editor.quote_value, params))
def create_sql(self, model, schema_editor, using=''):
fields = [model._meta.get_field(field_name) for field_name, _ in self.fields_orders]
......@@ -77,11 +77,7 @@ class Index:
)
def remove_sql(self, model, schema_editor):
quote_name = schema_editor.quote_name
return schema_editor.sql_delete_index % {
'table': quote_name(model._meta.db_table),
'name': quote_name(self.name),
}
return schema_editor._delete_index_sql(model, self.name)
def deconstruct(self):
path = '%s.%s' % (self.__class__.__module__, self.__class__.__name__)
......
......@@ -322,13 +322,6 @@ backends.
* Third party database backends must implement support for partial indexes or
set ``DatabaseFeatures.supports_partial_indexes`` to ``False``.
* Several ``SchemaEditor`` attributes are changed:
* ``sql_create_check`` is replaced with ``sql_create_constraint``.
* ``sql_delete_check`` is replaced with ``sql_delete_constraint``.
* ``sql_create_fk`` is replaced with ``sql_foreign_key_constraint``,
``sql_constraint``, and ``sql_create_constraint``.
* ``DatabaseIntrospection.table_name_converter()`` and
``column_name_converter()`` are removed. Third party database backends may
need to instead implement ``DatabaseIntrospection.identifier_converter()``.
......@@ -336,6 +329,15 @@ backends.
``DatabaseIntrospection.get_constraints()`` returns must be normalized by
``identifier_converter()``.
* SQL generation for indexes is moved from :class:`~django.db.models.Index` to
``SchemaEditor`` and these ``SchemaEditor`` methods are added:
* ``_create_primary_key_sql()`` and ``_delete_primary_key_sql()``
* ``_delete_index_sql()`` (to pair with ``_create_index_sql()``)
* ``_delete_unique_sql`` (to pair with ``_create_unique_sql()``)
* ``_delete_fk_sql()`` (to pair with ``_create_fk_sql()``)
* ``_create_check_sql()`` and ``_delete_check_sql()``
Admin actions are no longer collected from base ``ModelAdmin`` classes
----------------------------------------------------------------------
......
......@@ -18,6 +18,18 @@ class BaseConstraintTests(SimpleTestCase):
with self.assertRaisesMessage(NotImplementedError, msg):
c.constraint_sql(None, None)
def test_create_sql(self):
c = BaseConstraint('name')
msg = 'This method must be implemented by a subclass.'
with self.assertRaisesMessage(NotImplementedError, msg):
c.create_sql(None, None)
def test_remove_sql(self):
c = BaseConstraint('name')
msg = 'This method must be implemented by a subclass.'
with self.assertRaisesMessage(NotImplementedError, msg):
c.remove_sql(None, None)
class CheckConstraintTests(TestCase):
def test_repr(self):
......
......@@ -2147,23 +2147,17 @@ class SchemaTests(TransactionTestCase):
editor.alter_field(model, get_field(unique=True), field, strict=True)
self.assertNotIn(expected_constraint_name, self.get_constraints(model._meta.db_table))
if editor.sql_foreign_key_constraint:
if editor.sql_create_fk:
constraint_name = 'CamelCaseFKConstraint'
expected_constraint_name = identifier_converter(constraint_name)
fk_sql = editor.sql_foreign_key_constraint % {
"column": editor.quote_name(column),
"to_table": editor.quote_name(table),
"to_column": editor.quote_name(model._meta.auto_field.column),
"deferrable": connection.ops.deferrable_sql(),
}
constraint_sql = editor.sql_constraint % {
"name": editor.quote_name(constraint_name),
"constraint": fk_sql,
}
editor.execute(
editor.sql_create_constraint % {
editor.sql_create_fk % {
"table": editor.quote_name(table),
"constraint": constraint_sql,
"name": editor.quote_name(constraint_name),
"column": editor.quote_name(column),
"to_table": editor.quote_name(table),
"to_column": editor.quote_name(model._meta.auto_field.column),
"deferrable": connection.ops.deferrable_sql(),
}
)
self.assertIn(expected_constraint_name, self.get_constraints(model._meta.db_table))
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment