diff --git a/sql_server/pyodbc/introspection.py b/sql_server/pyodbc/introspection.py index 275fd991..5e018ce9 100644 --- a/sql_server/pyodbc/introspection.py +++ b/sql_server/pyodbc/introspection.py @@ -258,6 +258,7 @@ def get_constraints(self, cursor, table_name): "foreign_key": (ref_table, ref_column) if kind.lower() == "foreign key" else None, "check": False, "index": False, + "default": False, } # Record the details constraints[constraint]['columns'].append(column) @@ -283,6 +284,7 @@ def get_constraints(self, cursor, table_name): "foreign_key": None, "check": True, "index": False, + "default": False, } # Record the details constraints[constraint]['columns'].append(column) @@ -321,9 +323,32 @@ def get_constraints(self, cursor, table_name): "foreign_key": None, "check": False, "index": True, + "default": False, } indexes[index]["columns"].append(column) for index, constraint in indexes.items(): if index not in constraints: constraints[index] = constraint + # Now get DEFAULT constraint columns + cursor.execute(""" + SELECT + [name], + COL_NAME([parent_object_id], [parent_column_id]) + FROM + [sys].[default_constraints] + WHERE + OBJECT_NAME([parent_object_id]) = %s + """, [table_name]) + for constraint, column in cursor.fetchall(): + if constraint not in constraints: + constraints[constraint] = { + "columns": [], + "primary_key": False, + "unique": False, + "foreign_key": None, + "check": False, + "index": False, + "default": True, + } + constraints[constraint]['columns'].append(column) return constraints diff --git a/sql_server/pyodbc/schema.py b/sql_server/pyodbc/schema.py index 3dab8f38..a0432a57 100644 --- a/sql_server/pyodbc/schema.py +++ b/sql_server/pyodbc/schema.py @@ -535,6 +535,15 @@ def remove_field(self, model, field): "name": self.quote_name(pk_name), } ) + # Drop default, SQL Server treats defaults as constraints requiring explicit deletion + for default_name in self._default_constraint_names(model, [field.column]): + sql = self.sql_alter_column % { + "table": self.quote_name(model._meta.db_table), + "changes": self.sql_alter_column_no_default % { + "name": self.quote_name(default_name), + } + } + self.execute(sql) # Delete the column sql = self.sql_delete_column % { "table": self.quote_name(model._meta.db_table), @@ -544,3 +553,13 @@ def remove_field(self, model, field): # Reset connection if required if self.connection.features.connection_persists_old_columns: self.connection.close() + + def _default_constraint_names(self, model, column_names): + """Returns default constraint names matching the columns""" + # As BaseDatabaseSchemaEditor._constraint_names doesn't know about defaults + with self.connection.cursor() as cursor: + constraints = self.connection.introspection.get_constraints(cursor, model._meta.db_table) + return [ + name for name, infodict in constraints.items() + if infodict["columns"] == column_names and infodict.get("default") + ]