diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py index 61ab3038c44..48d69050925 100644 --- a/django/db/backends/mysql/introspection.py +++ b/django/db/backends/mysql/introspection.py @@ -105,32 +105,42 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): def get_constraints(self, cursor, table_name): """ Retrieves any constraints (unique, pk, fk, check) across one or more columns. - Returns {'cnname': {'columns': set(columns), 'primary_key': bool, 'unique': bool}} + Returns {'cnname': {'columns': set(columns), 'primary_key': bool, 'unique': bool, 'foreign_key': None|(tbl, col)}} """ constraints = {} - # Loop over the constraint tables, collecting things as constraints - ifsc_tables = ["constraint_column_usage", "key_column_usage"] - for ifsc_table in ifsc_tables: - cursor.execute(""" - SELECT kc.constraint_name, kc.column_name, c.constraint_type - FROM information_schema.%s AS kc - JOIN information_schema.table_constraints AS c ON - kc.table_schema = c.table_schema AND - kc.table_name = c.table_name AND - kc.constraint_name = c.constraint_name - WHERE - kc.table_schema = %%s AND - kc.table_name = %%s - """ % ifsc_table, [self.connection.settings_dict['NAME'], table_name]) - for constraint, column, kind in cursor.fetchall(): - # If we're the first column, make the record - if constraint not in constraints: - constraints[constraint] = { - "columns": set(), - "primary_key": kind.lower() == "primary key", - "foreign_key": kind.lower() == "foreign key", - "unique": kind.lower() in ["primary key", "unique"], - } - # Record the details - constraints[constraint]['columns'].add(column) + # Get the actual constraint names and columns + name_query = """ + SELECT kc.`constraint_name`, kc.`column_name`, + kc.`referenced_table_name`, kc.`referenced_column_name` + FROM information_schema.key_column_usage AS kc + WHERE + kc.table_schema = %s AND + kc.table_name = %s + """ + cursor.execute(name_query, [self.connection.settings_dict['NAME'], table_name]) + for constraint, column, ref_table, ref_column in cursor.fetchall(): + if constraint not in constraints: + constraints[constraint] = { + 'columns': set(), + 'primary_key': False, + 'unique': False, + 'foreign_key': (ref_table, ref_column) if ref_column else None, + } + constraints[constraint]['columns'].add(column) + # Now get the constraint types + type_query = """ + SELECT c.constraint_name, c.constraint_type + FROM information_schema.table_constraints AS c + WHERE + c.table_schema = %s AND + c.table_name = %s + """ + cursor.execute(type_query, [self.connection.settings_dict['NAME'], table_name]) + for constraint, kind in cursor.fetchall(): + if kind.lower() == "primary key": + constraints[constraint]['primary_key'] = True + constraints[constraint]['unique'] = True + elif kind.lower() == "unique": + constraints[constraint]['unique'] = True + # Return return constraints diff --git a/django/db/backends/mysql/schema.py b/django/db/backends/mysql/schema.py index 2c0ad5b8af0..efc469d9fb7 100644 --- a/django/db/backends/mysql/schema.py +++ b/django/db/backends/mysql/schema.py @@ -7,6 +7,8 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): sql_alter_column_null = "MODIFY %(column)s %(type)s NULL" sql_alter_column_not_null = "MODIFY %(column)s %(type)s NULL" + sql_alter_column_type = "MODIFY %(column)s %(type)s" + sql_rename_column = "ALTER TABLE %(table)s CHANGE %(old_column)s %(new_column)s %(type)s" sql_delete_unique = "ALTER TABLE %(table)s DROP INDEX %(name)s" @@ -17,8 +19,5 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): sql_delete_pk = "ALTER TABLE %(table)s DROP PRIMARY KEY" - - - alter_string_set_null = 'MODIFY %(column)s %(type)s NULL;' alter_string_drop_null = 'MODIFY %(column)s %(type)s NOT NULL;' diff --git a/django/db/backends/schema.py b/django/db/backends/schema.py index bae0b1d71d4..788be3be35b 100644 --- a/django/db/backends/schema.py +++ b/django/db/backends/schema.py @@ -25,6 +25,7 @@ class BaseDatabaseSchemaEditor(object): - Repointing of M2Ms - Check constraints (PosIntField) - PK changing + - db_index on alter field """ # Overrideable SQL templates @@ -358,7 +359,7 @@ class BaseDatabaseSchemaEditor(object): """ # Ensure this field is even column-based old_type = old_field.db_type(connection=self.connection) - new_type = new_field.db_type(connection=self.connection) + new_type = self._type_for_alter(new_field) if old_type is None and new_type is None: # TODO: Handle M2M fields being repointed return @@ -389,6 +390,7 @@ class BaseDatabaseSchemaEditor(object): "table": self.quote_name(model._meta.db_table), "old_column": self.quote_name(old_field.column), "new_column": self.quote_name(new_field.column), + "type": new_type, }) # Next, start accumulating actions to do actions = [] @@ -426,6 +428,7 @@ class BaseDatabaseSchemaEditor(object): actions.append(( self.sql_alter_column_null % { "column": self.quote_name(new_field.column), + "type": new_type, }, [], )) @@ -433,6 +436,7 @@ class BaseDatabaseSchemaEditor(object): actions.append(( self.sql_alter_column_null % { "column": self.quote_name(new_field.column), + "type": new_type, }, [], )) @@ -460,6 +464,14 @@ class BaseDatabaseSchemaEditor(object): } ) + def _type_for_alter(self, field): + """ + Returns a field's type suitable for ALTER COLUMN. + By default it just returns field.db_type(). + To be overriden by backend specific subclasses + """ + return field.db_type(connection=self.connection) + def _create_index_name(self, model, column_names, suffix=""): "Generates a unique name for an index/unique constraint." # If there is just one column in the index, use a default algorithm from Django diff --git a/tests/modeltests/schema/tests.py b/tests/modeltests/schema/tests.py index 7b1de268a41..70311382804 100644 --- a/tests/modeltests/schema/tests.py +++ b/tests/modeltests/schema/tests.py @@ -39,6 +39,7 @@ class SchemaTests(TestCase): connection.rollback() # Delete any tables made for our models cursor = connection.cursor() + connection.disable_constraint_checking() for model in self.models: # Remove any M2M tables first for field in model._meta.local_many_to_many: @@ -59,6 +60,7 @@ class SchemaTests(TestCase): connection.rollback() else: connection.commit() + connection.enable_constraint_checking() # Unhook our models for model in self.models: model._meta.managed = False