diff --git a/sql_server/pyodbc/features.py b/sql_server/pyodbc/features.py index 03153f85..957d19ce 100644 --- a/sql_server/pyodbc/features.py +++ b/sql_server/pyodbc/features.py @@ -19,7 +19,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): requires_literal_defaults = True requires_sqlparse_for_splitting = False supports_index_on_text_field = False - supports_nullable_unique_constraints = False + supports_nullable_unique_constraints = True supports_paramstyle_pyformat = False supports_partially_nullable_unique_constraints = False supports_regex_backreferencing = False diff --git a/sql_server/pyodbc/schema.py b/sql_server/pyodbc/schema.py index 1999cd00..b3eddb29 100644 --- a/sql_server/pyodbc/schema.py +++ b/sql_server/pyodbc/schema.py @@ -52,6 +52,8 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): sql_delete_table = "DROP TABLE %(table)s" sql_rename_column = "EXEC sp_rename '%(table)s.%(old_column)s', %(new_column)s, 'COLUMN'" sql_rename_table = "EXEC sp_rename %(old_table)s, %(new_table)s" + sql_create_unique_null = "CREATE UNIQUE INDEX %(name)s ON %(table)s(%(columns)s) " \ + "WHERE %(columns)s IS NOT NULL" def _alter_column_default_sql(self, model, old_field, new_field, drop=False): """ @@ -320,8 +322,15 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type, self._delete_primary_key(model, strict) # Added a unique? if self._unique_should_be_added(old_field, new_field): - self.execute(self._create_unique_sql(model, [new_field.column])) - # Added an index? + if not new_field.many_to_many and new_field.null: + self.execute( + self._create_index_sql( + model, [new_field], sql=self.sql_create_unique_null, suffix="_uniq" + ) + ) + else: + self.execute(self._create_unique_sql(model, [new_field.column])) + # Added an index? # constraint will no longer be used in lieu of an index. The following # lines from the truth table show all True cases; the rest are False: # @@ -496,6 +505,13 @@ def add_field(self, model, field): # It might not actually have a column behind it if definition is None: return + + if not field.many_to_many and field.null and field.unique: + definition = definition.replace(' UNIQUE', '') + self.deferred_sql.append(self._create_index_sql( + model, [field], sql=self.sql_create_unique_null, suffix="_uniq" + )) + # Check constraints can go on the column SQL here db_params = field.db_parameters(connection=self.connection) if db_params['check']: @@ -538,6 +554,13 @@ def create_model(self, model): definition, extra_params = self.column_sql(model, field) if definition is None: continue + + if not field.many_to_many and field.null and field.unique: + definition = definition.replace(' UNIQUE', '') + self.deferred_sql.append(self._create_index_sql( + model, [field], sql=self.sql_create_unique_null, suffix="_uniq" + )) + # Check constraints can go on the column SQL here db_params = field.db_parameters(connection=self.connection) if db_params['check']: