Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions sql_server/pyodbc/introspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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
19 changes: 19 additions & 0 deletions sql_server/pyodbc/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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")
]