diff --git a/mssql/functions.py b/mssql/functions.py index 4749c313..f8cb17dc 100644 --- a/mssql/functions.py +++ b/mssql/functions.py @@ -238,7 +238,7 @@ def bulk_update_with_default(self, objs, fields, batch_size=None, default=0): if any(f.primary_key for f in fields): raise ValueError('bulk_update() cannot be used with primary key fields.') if not objs: - return + return 0 # PK is used twice in the resulting update query, once in the filter # and once in the WHEN. Each field will also have one CAST. max_batch_size = connections[self.db].ops.bulk_batch_size(['pk', 'pk'] + fields, objs) @@ -266,9 +266,11 @@ def bulk_update_with_default(self, objs, fields, batch_size=None, default=0): case_statement = Cast(case_statement, output_field=field) update_kwargs[field.attname] = case_statement updates.append(([obj.pk for obj in batch_objs], update_kwargs)) + rows_updated = 0 with transaction.atomic(using=self.db, savepoint=False): for pks, update_kwargs in updates: - self.filter(pk__in=pks).update(**update_kwargs) + rows_updated += self.filter(pk__in=pks).update(**update_kwargs) + return rows_updated ATan2.as_microsoft = sqlserver_atan2 In.split_parameter_list_as_sql = split_parameter_list_as_sql diff --git a/mssql/introspection.py b/mssql/introspection.py index 4cf063ab..66aefce3 100644 --- a/mssql/introspection.py +++ b/mssql/introspection.py @@ -14,7 +14,7 @@ SQL_BIGAUTOFIELD = -777444 def get_schema_name(): - return getattr(settings, 'SCHEMA_TO_INSPECT', 'dbo') + return getattr(settings, 'SCHEMA_TO_INSPECT', 'SCHEMA_NAME()') class DatabaseIntrospection(BaseDatabaseIntrospection): # Map type codes to Django Field types. @@ -66,7 +66,7 @@ def get_table_list(self, cursor): """ Returns a list of table and view names in the current database. """ - sql = 'SELECT TABLE_NAME, TABLE_TYPE FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ' + "'" + get_schema_name() + "'" + sql = f'SELECT TABLE_NAME, TABLE_TYPE FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = {get_schema_name()}' cursor.execute(sql) types = {'BASE TABLE': 't', 'VIEW': 'v'} return [TableInfo(row[0], types.get(row[1])) @@ -129,10 +129,10 @@ def get_table_description(self, cursor, table_name, identity_check=True): return items def get_sequences(self, cursor, table_name, table_fields=()): - cursor.execute(""" + cursor.execute(f""" SELECT c.name FROM sys.columns c INNER JOIN sys.tables t ON c.object_id = t.object_id - WHERE t.schema_id = SCHEMA_ID(""" + "'" + get_schema_name() + "'" + """) AND t.name = %s AND c.is_identity = 1""", + WHERE t.schema_id = SCHEMA_ID({get_schema_name()}) AND t.name = %s AND c.is_identity = 1""", [table_name]) # SQL Server allows only one identity column per table # https://docs.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql-identity-property @@ -148,7 +148,7 @@ def get_relations(self, cursor, table_name): # CONSTRAINT_TABLE_USAGE: http://msdn2.microsoft.com/en-us/library/ms179883.aspx # REFERENTIAL_CONSTRAINTS: http://msdn2.microsoft.com/en-us/library/ms179987.aspx # TABLE_CONSTRAINTS: http://msdn2.microsoft.com/en-us/library/ms181757.aspx - sql = """ + sql = f""" SELECT e.COLUMN_NAME AS column_name, c.TABLE_NAME AS referenced_table_name, d.COLUMN_NAME AS referenced_column_name @@ -161,7 +161,7 @@ def get_relations(self, cursor, table_name): ON c.CONSTRAINT_NAME = d.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = d.CONSTRAINT_SCHEMA INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS e ON a.CONSTRAINT_NAME = e.CONSTRAINT_NAME AND a.TABLE_SCHEMA = e.TABLE_SCHEMA -WHERE a.TABLE_SCHEMA = """ + "'" + get_schema_name() + "'" + """ AND a.TABLE_NAME = %s AND a.CONSTRAINT_TYPE = 'FOREIGN KEY'""" +WHERE a.TABLE_SCHEMA = {get_schema_name()} AND a.TABLE_NAME = %s AND a.CONSTRAINT_TYPE = 'FOREIGN KEY'""" cursor.execute(sql, (table_name,)) return dict([[item[0], (item[2], item[1])] for item in cursor.fetchall()]) @@ -171,14 +171,14 @@ def get_key_columns(self, cursor, table_name): key columns in given table. """ key_columns = [] - cursor.execute(""" + cursor.execute(f""" SELECT c.name AS column_name, rt.name AS referenced_table_name, rc.name AS referenced_column_name FROM sys.foreign_key_columns fk INNER JOIN sys.tables t ON t.object_id = fk.parent_object_id INNER JOIN sys.columns c ON c.object_id = t.object_id AND c.column_id = fk.parent_column_id INNER JOIN sys.tables rt ON rt.object_id = fk.referenced_object_id INNER JOIN sys.columns rc ON rc.object_id = rt.object_id AND rc.column_id = fk.referenced_column_id - WHERE t.schema_id = SCHEMA_ID(""" + "'" + get_schema_name() + "'" + """) AND t.name = %s""", [table_name]) + WHERE t.schema_id = SCHEMA_ID({get_schema_name()}) AND t.name = %s""", [table_name]) key_columns.extend([tuple(row) for row in cursor.fetchall()]) return key_columns @@ -201,7 +201,7 @@ def get_constraints(self, cursor, table_name): constraints = {} # Loop over the key table, collecting things as constraints # This will get PKs, FKs, and uniques, but not CHECK - cursor.execute(""" + cursor.execute(f""" SELECT kc.constraint_name, kc.column_name, @@ -241,9 +241,7 @@ def get_constraints(self, cursor, table_name): kc.table_name = fk.table_name AND kc.column_name = fk.column_name WHERE - kc.table_schema = """ - + "'" + get_schema_name() + "'" + - """ AND + kc.table_schema = {get_schema_name()} AND kc.table_name = %s ORDER BY kc.constraint_name ASC, @@ -263,7 +261,7 @@ def get_constraints(self, cursor, table_name): # Record the details constraints[constraint]['columns'].append(column) # Now get CHECK constraint columns - cursor.execute(""" + cursor.execute(f""" SELECT kc.constraint_name, kc.column_name FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS kc JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS c ON @@ -272,7 +270,7 @@ def get_constraints(self, cursor, table_name): kc.constraint_name = c.constraint_name WHERE c.constraint_type = 'CHECK' AND - kc.table_schema = """ + "'" + get_schema_name() + "'" + """ AND + kc.table_schema = {get_schema_name()} AND kc.table_name = %s """, [table_name]) for constraint, column in cursor.fetchall(): @@ -289,7 +287,7 @@ def get_constraints(self, cursor, table_name): # Record the details constraints[constraint]['columns'].append(column) # Now get indexes - cursor.execute(""" + cursor.execute(f""" SELECT i.name AS index_name, i.is_unique, @@ -311,7 +309,7 @@ def get_constraints(self, cursor, table_name): ic.object_id = c.object_id AND ic.column_id = c.column_id WHERE - t.schema_id = SCHEMA_ID(""" + "'" + get_schema_name() + "'" + """) AND + t.schema_id = SCHEMA_ID({get_schema_name()}) AND t.name = %s ORDER BY i.index_id ASC, diff --git a/mssql/management/commands/inspectdb.py b/mssql/management/commands/inspectdb.py index f04cb4c5..d509edfb 100644 --- a/mssql/management/commands/inspectdb.py +++ b/mssql/management/commands/inspectdb.py @@ -15,5 +15,5 @@ def add_arguments(self, parser): def handle(self, *args, **options): if options["schema"]: - settings.SCHEMA_TO_INSPECT = options["schema"] + settings.SCHEMA_TO_INSPECT = "'" + options["schema"] + "'" return super().handle(*args, **options) diff --git a/mssql/schema.py b/mssql/schema.py index 8a28cca3..435369a7 100644 --- a/mssql/schema.py +++ b/mssql/schema.py @@ -225,14 +225,6 @@ def _model_indexes_sql(self, model): output.append(index.create_sql(model, self)) return output - def _alter_many_to_many(self, model, old_field, new_field, strict): - """Alter M2Ms to repoint their to= endpoints.""" - - for idx in self._constraint_names(old_field.remote_field.through, index=True, unique=True): - self.execute(self.sql_delete_index % {'name': idx, 'table': old_field.remote_field.through._meta.db_table}) - - return super()._alter_many_to_many(model, old_field, new_field, strict) - def _db_table_constraint_names(self, db_table, column_names=None, unique=None, primary_key=None, index=None, foreign_key=None, check=None, type_=None, exclude=None): @@ -442,9 +434,17 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type, # Drop unique constraint, SQL Server requires explicit deletion self._delete_unique_constraints(model, old_field, new_field, strict) # Drop indexes, SQL Server requires explicit deletion - self._delete_indexes(model, old_field, new_field) - if not new_field.get_internal_type() in ("JSONField", "TextField") and not (old_field.db_index and new_field.db_index): - post_actions.append((self._create_index_sql(model, [new_field]), ())) + indexes_dropped = self._delete_indexes(model, old_field, new_field) + if ( + new_field.get_internal_type() not in ("JSONField", "TextField") and + (old_field.db_index or not new_field.db_index) and + new_field.db_index or + (indexes_dropped and sorted(indexes_dropped) == sorted( + [index.name for index in model._meta.indexes])) + ): + create_index_sql_statement = self._create_index_sql(model, [new_field]) + if create_index_sql_statement.__str__() not in [sql.__str__() for sql in self.deferred_sql]: + post_actions.append((create_index_sql_statement, ())) # Only if we have a default and there is a change from NULL to NOT NULL four_way_default_alteration = ( new_field.has_default() and @@ -566,7 +566,9 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type, if index_columns: for columns in index_columns: create_index_sql_statement = self._create_index_sql(model, columns) - if create_index_sql_statement.__str__() not in [sql.__str__() for sql in self.deferred_sql]: + if (create_index_sql_statement.__str__() + not in [sql.__str__() for sql in self.deferred_sql] + [statement[0].__str__() for statement in post_actions] + ): self.execute(create_index_sql_statement) # Type alteration on primary key? Then we need to alter the column @@ -653,6 +655,7 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type, def _delete_indexes(self, model, old_field, new_field): index_columns = [] + index_names = [] if old_field.db_index and new_field.db_index: index_columns.append([old_field.column]) elif old_field.null != new_field.null: @@ -671,6 +674,7 @@ def _delete_indexes(self, model, old_field, new_field): index_names = self._constraint_names(model, columns, index=True) for index_name in index_names: self.execute(self._delete_constraint_sql(self.sql_delete_index, model, index_name)) + return index_names def _delete_unique_constraints(self, model, old_field, new_field, strict=False): unique_columns = [] diff --git a/setup.py b/setup.py index b102ed18..a3cdc416 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ setup( name='mssql-django', - version='1.1.1', + version='1.1.2', description='Django backend for Microsoft SQL Server', long_description=long_description, long_description_content_type='text/markdown', diff --git a/testapp/migrations/0002_test_unique_nullable_part1.py b/testapp/migrations/0002_test_unique_nullable_part1.py index 33ab86a6..911a5952 100644 --- a/testapp/migrations/0002_test_unique_nullable_part1.py +++ b/testapp/migrations/0002_test_unique_nullable_part1.py @@ -8,7 +8,7 @@ class Migration(migrations.Migration): ] operations = [ - # Issue #38 test prep + # Prep test for issue https://github.com/ESSolutions/django-mssql-backend/issues/38 # Create with a field that is unique *and* nullable so it is implemented with a filtered unique index. migrations.CreateModel( name='TestUniqueNullableModel', diff --git a/testapp/migrations/0003_test_unique_nullable_part2.py b/testapp/migrations/0003_test_unique_nullable_part2.py index ade35429..14280266 100644 --- a/testapp/migrations/0003_test_unique_nullable_part2.py +++ b/testapp/migrations/0003_test_unique_nullable_part2.py @@ -8,7 +8,7 @@ class Migration(migrations.Migration): ] operations = [ - # Issue #38 test + # Run test for issue https://github.com/ESSolutions/django-mssql-backend/issues/38 # Now remove the null=True to check this transition is correctly handled. migrations.AlterField( model_name='testuniquenullablemodel', diff --git a/testapp/migrations/0004_test_issue45_unique_type_change_part1.py b/testapp/migrations/0004_test_unique_type_change_part1.py similarity index 91% rename from testapp/migrations/0004_test_issue45_unique_type_change_part1.py rename to testapp/migrations/0004_test_unique_type_change_part1.py index 2f3b9fba..cb820669 100644 --- a/testapp/migrations/0004_test_issue45_unique_type_change_part1.py +++ b/testapp/migrations/0004_test_unique_type_change_part1.py @@ -7,7 +7,7 @@ class Migration(migrations.Migration): ('testapp', '0003_test_unique_nullable_part2'), ] - # Issue #45 test prep + # Prep test for issue https://github.com/ESSolutions/django-mssql-backend/issues/45 operations = [ # for case 1: migrations.AddField( diff --git a/testapp/migrations/0005_test_issue45_unique_type_change_part2.py b/testapp/migrations/0005_test_unique_type_change_part2.py similarity index 89% rename from testapp/migrations/0005_test_issue45_unique_type_change_part2.py rename to testapp/migrations/0005_test_unique_type_change_part2.py index a938fe2a..47f48d49 100644 --- a/testapp/migrations/0005_test_issue45_unique_type_change_part2.py +++ b/testapp/migrations/0005_test_unique_type_change_part2.py @@ -4,10 +4,10 @@ class Migration(migrations.Migration): dependencies = [ - ('testapp', '0004_test_issue45_unique_type_change_part1'), + ('testapp', '0004_test_unique_type_change_part1'), ] - # Issue #45 test + # Run test for issue https://github.com/ESSolutions/django-mssql-backend/issues/45 operations = [ # Case 1: changing max_length changes the column type - the filtered UNIQUE INDEX which implements # the nullable unique constraint, should be correctly reinstated after this change of column type diff --git a/testapp/migrations/0006_test_remove_onetoone_field_part1.py b/testapp/migrations/0006_test_remove_onetoone_field_part1.py index e7e61473..f3aaed73 100644 --- a/testapp/migrations/0006_test_remove_onetoone_field_part1.py +++ b/testapp/migrations/0006_test_remove_onetoone_field_part1.py @@ -7,7 +7,7 @@ class Migration(migrations.Migration): dependencies = [ - ('testapp', '0005_test_issue45_unique_type_change_part2'), + ('testapp', '0005_test_unique_type_change_part2'), ] operations = [ diff --git a/testapp/migrations/0012_test_indexes_retained_part1.py b/testapp/migrations/0012_test_indexes_retained_part1.py new file mode 100644 index 00000000..288e3dff --- /dev/null +++ b/testapp/migrations/0012_test_indexes_retained_part1.py @@ -0,0 +1,21 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('testapp', '0011_test_unique_constraints'), + ] + + # Prep test for issue https://github.com/ESSolutions/django-mssql-backend/issues/58 + operations = [ + migrations.CreateModel( + name='TestIndexesRetained', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('a', models.IntegerField(db_index=True)), + ('b', models.IntegerField(db_index=True)), + ('c', models.IntegerField(db_index=True)), + ], + ), + ] diff --git a/testapp/migrations/0013_test_indexes_retained_part2.py b/testapp/migrations/0013_test_indexes_retained_part2.py new file mode 100644 index 00000000..1fb53039 --- /dev/null +++ b/testapp/migrations/0013_test_indexes_retained_part2.py @@ -0,0 +1,27 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('testapp', '0012_test_indexes_retained_part1'), + ] + + # Run test for issue https://github.com/ESSolutions/django-mssql-backend/issues/58 + # where the following operations should leave indexes intact + operations = [ + migrations.AlterField( + model_name='testindexesretained', + name='a', + field=models.IntegerField(db_index=True, null=True), + ), + migrations.RenameField( + model_name='testindexesretained', + old_name='b', + new_name='b_renamed', + ), + migrations.RenameModel( + old_name='TestIndexesRetained', + new_name='TestIndexesRetainedRenamed', + ), + ] diff --git a/testapp/migrations/0014_test_rename_m2mfield_part1.py b/testapp/migrations/0014_test_rename_m2mfield_part1.py new file mode 100644 index 00000000..ca2cf969 --- /dev/null +++ b/testapp/migrations/0014_test_rename_m2mfield_part1.py @@ -0,0 +1,26 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('testapp', '0013_test_indexes_retained_part2'), + ] + + operations = [ + # Prep test for issue https://github.com/microsoft/mssql-django/issues/86 + migrations.CreateModel( + name='M2MOtherModel', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=10)), + ], + ), + migrations.CreateModel( + name='TestRenameManyToManyFieldModel', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('others', models.ManyToManyField(to='testapp.M2MOtherModel')), + ], + ), + ] diff --git a/testapp/migrations/0015_test_rename_m2mfield_part2.py b/testapp/migrations/0015_test_rename_m2mfield_part2.py new file mode 100644 index 00000000..cfc60bb6 --- /dev/null +++ b/testapp/migrations/0015_test_rename_m2mfield_part2.py @@ -0,0 +1,19 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('testapp', '0014_test_rename_m2mfield_part1'), + ] + + operations = [ + # Run test for issue https://github.com/microsoft/mssql-django/issues/86 + # Must be in a separate migration so that the unique index was created + # (deferred after the previous migration) before we do the rename. + migrations.RenameField( + model_name='testrenamemanytomanyfieldmodel', + old_name='others', + new_name='others_renamed', + ), + ] diff --git a/testapp/models.py b/testapp/models.py index 8049309e..cec74519 100644 --- a/testapp/models.py +++ b/testapp/models.py @@ -48,13 +48,13 @@ def __str__(self): class TestUniqueNullableModel(models.Model): - # Issue #38: + # Issue https://github.com/ESSolutions/django-mssql-backend/issues/38: # This field started off as unique=True *and* null=True so it is implemented with a filtered unique index # Then it is made non-nullable by a subsequent migration, to check this is correctly handled (the index # should be dropped, then a normal unique constraint should be added, now that the column is not nullable) test_field = models.CharField(max_length=100, unique=True) - # Issue #45 (case 1) + # Issue https://github.com/ESSolutions/django-mssql-backend/issues/45 (case 1) # Field used for testing changing the 'type' of a field that's both unique & nullable x = models.CharField(max_length=11, null=True, unique=True) @@ -63,7 +63,7 @@ class TestNullableUniqueTogetherModel(models.Model): class Meta: unique_together = (('a', 'b', 'c'),) - # Issue #45 (case 2) + # Issue https://github.com/ESSolutions/django-mssql-backend/issues/45 (case 2) # Fields used for testing changing the 'type of a field that is in a `unique_together` a = models.CharField(max_length=51, null=True) b = models.CharField(max_length=50) @@ -71,12 +71,33 @@ class Meta: class TestRemoveOneToOneFieldModel(models.Model): - # Fields used for testing removing OneToOne field. Verifies that delete_unique do not try to remove indexes - # thats already is removed. + # Issue https://github.com/ESSolutions/django-mssql-backend/pull/51 + # Fields used for testing removing OneToOne field. Verifies that delete_unique + # does not try to remove indexes that have already been removed # b = models.OneToOneField('self', on_delete=models.SET_NULL, null=True) a = models.CharField(max_length=50) +class TestIndexesRetainedRenamed(models.Model): + # Issue https://github.com/ESSolutions/django-mssql-backend/issues/58 + # In all these cases the column index should still exist afterwards + # case (a) `a` starts out not nullable, but then is changed to be nullable + a = models.IntegerField(db_index=True, null=True) + # case (b) column originally called `b` is renamed + b_renamed = models.IntegerField(db_index=True) + # case (c) this entire model is renamed - this is just a column whose index can be checked afterwards + c = models.IntegerField(db_index=True) + + +class M2MOtherModel(models.Model): + name = models.CharField(max_length=10) + + +class TestRenameManyToManyFieldModel(models.Model): + # Issue https://github.com/microsoft/mssql-django/issues/86 + others_renamed = models.ManyToManyField(M2MOtherModel) + + class Topping(models.Model): name = models.UUIDField(primary_key=True, default=uuid.uuid4) diff --git a/testapp/settings.py b/testapp/settings.py index af93d97f..35c13904 100644 --- a/testapp/settings.py +++ b/testapp/settings.py @@ -43,197 +43,195 @@ TEST_RUNNER = "testapp.runners.ExcludedTestSuiteRunner" EXCLUDED_TESTS = [ - 'aggregation.tests.AggregateTestCase.test_expression_on_aggregation', - 'aggregation_regress.tests.AggregationTests.test_annotated_conditional_aggregate', - 'aggregation_regress.tests.AggregationTests.test_annotation_with_value', - 'aggregation.tests.AggregateTestCase.test_distinct_on_aggregate', - 'annotations.tests.NonAggregateAnnotationTestCase.test_annotate_exists', - 'custom_lookups.tests.BilateralTransformTests.test_transform_order_by', - 'expressions.tests.BasicExpressionsTests.test_filtering_on_annotate_that_uses_q', - 'expressions.tests.BasicExpressionsTests.test_order_by_exists', - 'expressions.tests.ExpressionOperatorTests.test_righthand_power', - 'expressions.tests.FTimeDeltaTests.test_datetime_subtraction_microseconds', - 'expressions.tests.FTimeDeltaTests.test_duration_with_datetime_microseconds', - 'expressions.tests.IterableLookupInnerExpressionsTests.test_expressions_in_lookups_join_choice', - 'expressions_case.tests.CaseExpressionTests.test_annotate_with_in_clause', - 'expressions_window.tests.WindowFunctionTests.test_nth_returns_null', - 'expressions_window.tests.WindowFunctionTests.test_nthvalue', - 'expressions_window.tests.WindowFunctionTests.test_range_n_preceding_and_following', - 'field_deconstruction.tests.FieldDeconstructionTests.test_binary_field', - 'ordering.tests.OrderingTests.test_orders_nulls_first_on_filtered_subquery', - 'get_or_create.tests.UpdateOrCreateTransactionTests.test_creation_in_transaction', - 'indexes.tests.PartialIndexTests.test_multiple_conditions', - 'introspection.tests.IntrospectionTests.test_get_constraints', - 'migrations.test_executor.ExecutorTests.test_alter_id_type_with_fk', - 'migrations.test_operations.OperationTests.test_add_constraint_percent_escaping', - 'migrations.test_operations.OperationTests.test_alter_field_pk', - 'migrations.test_operations.OperationTests.test_alter_field_reloads_state_on_fk_with_to_field_target_changes', - 'migrations.test_operations.OperationTests.test_autofield_foreignfield_growth', - 'schema.tests.SchemaTests.test_alter_auto_field_to_char_field', - 'schema.tests.SchemaTests.test_alter_auto_field_to_integer_field', - 'schema.tests.SchemaTests.test_alter_implicit_id_to_explicit', - 'schema.tests.SchemaTests.test_alter_int_pk_to_autofield_pk', - 'schema.tests.SchemaTests.test_alter_int_pk_to_bigautofield_pk', - 'schema.tests.SchemaTests.test_alter_pk_with_self_referential_field', - 'schema.tests.SchemaTests.test_no_db_constraint_added_during_primary_key_change', - 'schema.tests.SchemaTests.test_remove_field_check_does_not_remove_meta_constraints', - 'schema.tests.SchemaTests.test_remove_field_unique_does_not_remove_meta_constraints', - 'schema.tests.SchemaTests.test_text_field_with_db_index', - 'schema.tests.SchemaTests.test_unique_together_with_fk', - 'schema.tests.SchemaTests.test_unique_together_with_fk_with_existing_index', - 'aggregation.tests.AggregateTestCase.test_count_star', - 'aggregation_regress.tests.AggregationTests.test_values_list_annotation_args_ordering', - 'datatypes.tests.DataTypesTestCase.test_error_on_timezone', - 'db_functions.math.test_degrees.DegreesTests.test_integer', - 'db_functions.math.test_power.PowerTests.test_integer', - 'db_functions.math.test_radians.RadiansTests.test_integer', - 'db_functions.text.test_pad.PadTests.test_pad', - 'db_functions.text.test_replace.ReplaceTests.test_case_sensitive', - 'expressions.tests.ExpressionOperatorTests.test_lefthand_bitwise_right_shift_operator', - 'expressions.tests.FTimeDeltaTests.test_invalid_operator', - 'fixtures_regress.tests.TestFixtures.test_loaddata_raises_error_when_fixture_has_invalid_foreign_key', - 'invalid_models_tests.test_ordinary_fields.TextFieldTests.test_max_length_warning', - 'model_indexes.tests.IndexesTests.test_db_tablespace', - 'ordering.tests.OrderingTests.test_deprecated_values_annotate', - 'queries.test_qs_combinators.QuerySetSetOperationTests.test_limits', - 'backends.tests.BackendTestCase.test_unicode_password', - 'introspection.tests.IntrospectionTests.test_get_table_description_types', - 'migrations.test_commands.MigrateTests.test_migrate_syncdb_app_label', - 'migrations.test_commands.MigrateTests.test_migrate_syncdb_deferred_sql_executed_with_schemaeditor', - 'migrations.test_operations.OperationTests.test_alter_field_pk_fk', - 'schema.tests.SchemaTests.test_add_foreign_key_quoted_db_table', - 'schema.tests.SchemaTests.test_unique_and_reverse_m2m', - 'schema.tests.SchemaTests.test_unique_no_unnecessary_fk_drops', - 'select_for_update.tests.SelectForUpdateTests.test_for_update_after_from', - 'backends.tests.LastExecutedQueryTest.test_last_executed_query', - 'db_functions.datetime.test_now.NowTests.test_basic', - 'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_year_exact_lookup', - 'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_year_greaterthan_lookup', - 'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_year_lessthan_lookup', - 'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_extract_year_exact_lookup', - 'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_extract_year_greaterthan_lookup', - 'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_extract_year_lessthan_lookup', - 'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_trunc_ambiguous_and_invalid_times', - 'delete.tests.DeletionTests.test_only_referenced_fields_selected', - 'queries.test_db_returning.ReturningValuesTests.test_insert_returning', - 'queries.test_db_returning.ReturningValuesTests.test_insert_returning_non_integer', - 'backends.tests.BackendTestCase.test_queries', - 'introspection.tests.IntrospectionTests.test_smallautofield', - 'schema.tests.SchemaTests.test_inline_fk', - 'aggregation.tests.AggregateTestCase.test_aggregation_subquery_annotation_exists', - 'aggregation.tests.AggregateTestCase.test_aggregation_subquery_annotation_values_collision', - 'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_extract_func_with_timezone', - 'db_functions.text.test_md5.MD5Tests.test_basic', - 'db_functions.text.test_md5.MD5Tests.test_transform', - 'db_functions.text.test_sha1.SHA1Tests.test_basic', - 'db_functions.text.test_sha1.SHA1Tests.test_transform', - 'db_functions.text.test_sha224.SHA224Tests.test_basic', - 'db_functions.text.test_sha224.SHA224Tests.test_transform', - 'db_functions.text.test_sha256.SHA256Tests.test_basic', - 'db_functions.text.test_sha256.SHA256Tests.test_transform', - 'db_functions.text.test_sha384.SHA384Tests.test_basic', - 'db_functions.text.test_sha384.SHA384Tests.test_transform', - 'db_functions.text.test_sha512.SHA512Tests.test_basic', - 'db_functions.text.test_sha512.SHA512Tests.test_transform', - 'expressions.tests.BasicExpressionsTests.test_case_in_filter_if_boolean_output_field', - 'expressions.tests.BasicExpressionsTests.test_subquery_in_filter', - 'expressions.tests.FTimeDeltaTests.test_date_subquery_subtraction', - 'expressions.tests.FTimeDeltaTests.test_datetime_subquery_subtraction', - 'expressions.tests.FTimeDeltaTests.test_time_subquery_subtraction', - 'expressions.tests.BasicExpressionsTests.test_filtering_on_q_that_is_boolean', - 'migrations.test_operations.OperationTests.test_alter_field_reloads_state_on_fk_with_to_field_target_type_change', - 'migrations.test_operations.OperationTests.test_autofield__bigautofield_foreignfield_growth', - 'migrations.test_operations.OperationTests.test_smallfield_autofield_foreignfield_growth', - 'migrations.test_operations.OperationTests.test_smallfield_bigautofield_foreignfield_growth', - 'schema.tests.SchemaTests.test_alter_auto_field_quoted_db_column', - 'schema.tests.SchemaTests.test_alter_autofield_pk_to_bigautofield_pk_sequence_owner', - 'schema.tests.SchemaTests.test_alter_autofield_pk_to_smallautofield_pk_sequence_owner', - 'schema.tests.SchemaTests.test_alter_primary_key_quoted_db_table', - 'schema.tests.SchemaTests.test_alter_smallint_pk_to_smallautofield_pk', + 'aggregation.tests.AggregateTestCase.test_expression_on_aggregation', + 'aggregation_regress.tests.AggregationTests.test_annotated_conditional_aggregate', + 'aggregation_regress.tests.AggregationTests.test_annotation_with_value', + 'aggregation.tests.AggregateTestCase.test_distinct_on_aggregate', + 'annotations.tests.NonAggregateAnnotationTestCase.test_annotate_exists', + 'custom_lookups.tests.BilateralTransformTests.test_transform_order_by', + 'expressions.tests.BasicExpressionsTests.test_filtering_on_annotate_that_uses_q', + 'expressions.tests.BasicExpressionsTests.test_order_by_exists', + 'expressions.tests.ExpressionOperatorTests.test_righthand_power', + 'expressions.tests.FTimeDeltaTests.test_datetime_subtraction_microseconds', + 'expressions.tests.FTimeDeltaTests.test_duration_with_datetime_microseconds', + 'expressions.tests.IterableLookupInnerExpressionsTests.test_expressions_in_lookups_join_choice', + 'expressions_case.tests.CaseExpressionTests.test_annotate_with_in_clause', + 'expressions_window.tests.WindowFunctionTests.test_nth_returns_null', + 'expressions_window.tests.WindowFunctionTests.test_nthvalue', + 'expressions_window.tests.WindowFunctionTests.test_range_n_preceding_and_following', + 'field_deconstruction.tests.FieldDeconstructionTests.test_binary_field', + 'ordering.tests.OrderingTests.test_orders_nulls_first_on_filtered_subquery', + 'get_or_create.tests.UpdateOrCreateTransactionTests.test_creation_in_transaction', + 'indexes.tests.PartialIndexTests.test_multiple_conditions', + 'introspection.tests.IntrospectionTests.test_get_constraints', + 'migrations.test_executor.ExecutorTests.test_alter_id_type_with_fk', + 'migrations.test_operations.OperationTests.test_add_constraint_percent_escaping', + 'migrations.test_operations.OperationTests.test_alter_field_pk', + 'migrations.test_operations.OperationTests.test_alter_field_reloads_state_on_fk_with_to_field_target_changes', + 'migrations.test_operations.OperationTests.test_autofield_foreignfield_growth', + 'schema.tests.SchemaTests.test_alter_auto_field_to_char_field', + 'schema.tests.SchemaTests.test_alter_auto_field_to_integer_field', + 'schema.tests.SchemaTests.test_alter_implicit_id_to_explicit', + 'schema.tests.SchemaTests.test_alter_int_pk_to_autofield_pk', + 'schema.tests.SchemaTests.test_alter_int_pk_to_bigautofield_pk', + 'schema.tests.SchemaTests.test_alter_pk_with_self_referential_field', + 'schema.tests.SchemaTests.test_no_db_constraint_added_during_primary_key_change', + 'schema.tests.SchemaTests.test_remove_field_check_does_not_remove_meta_constraints', + 'schema.tests.SchemaTests.test_remove_field_unique_does_not_remove_meta_constraints', + 'schema.tests.SchemaTests.test_text_field_with_db_index', + 'schema.tests.SchemaTests.test_unique_together_with_fk', + 'schema.tests.SchemaTests.test_unique_together_with_fk_with_existing_index', + 'aggregation.tests.AggregateTestCase.test_count_star', + 'aggregation_regress.tests.AggregationTests.test_values_list_annotation_args_ordering', + 'datatypes.tests.DataTypesTestCase.test_error_on_timezone', + 'db_functions.math.test_degrees.DegreesTests.test_integer', + 'db_functions.math.test_power.PowerTests.test_integer', + 'db_functions.math.test_radians.RadiansTests.test_integer', + 'db_functions.text.test_pad.PadTests.test_pad', + 'db_functions.text.test_replace.ReplaceTests.test_case_sensitive', + 'expressions.tests.ExpressionOperatorTests.test_lefthand_bitwise_right_shift_operator', + 'expressions.tests.FTimeDeltaTests.test_invalid_operator', + 'fixtures_regress.tests.TestFixtures.test_loaddata_raises_error_when_fixture_has_invalid_foreign_key', + 'invalid_models_tests.test_ordinary_fields.TextFieldTests.test_max_length_warning', + 'model_indexes.tests.IndexesTests.test_db_tablespace', + 'ordering.tests.OrderingTests.test_deprecated_values_annotate', + 'queries.test_qs_combinators.QuerySetSetOperationTests.test_limits', + 'backends.tests.BackendTestCase.test_unicode_password', + 'introspection.tests.IntrospectionTests.test_get_table_description_types', + 'migrations.test_commands.MigrateTests.test_migrate_syncdb_app_label', + 'migrations.test_commands.MigrateTests.test_migrate_syncdb_deferred_sql_executed_with_schemaeditor', + 'migrations.test_operations.OperationTests.test_alter_field_pk_fk', + 'schema.tests.SchemaTests.test_add_foreign_key_quoted_db_table', + 'schema.tests.SchemaTests.test_unique_and_reverse_m2m', + 'schema.tests.SchemaTests.test_unique_no_unnecessary_fk_drops', + 'select_for_update.tests.SelectForUpdateTests.test_for_update_after_from', + 'backends.tests.LastExecutedQueryTest.test_last_executed_query', + 'db_functions.datetime.test_now.NowTests.test_basic', + 'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_year_exact_lookup', + 'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_year_greaterthan_lookup', + 'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_year_lessthan_lookup', + 'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_extract_year_exact_lookup', + 'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_extract_year_greaterthan_lookup', + 'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_extract_year_lessthan_lookup', + 'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_trunc_ambiguous_and_invalid_times', + 'delete.tests.DeletionTests.test_only_referenced_fields_selected', + 'queries.test_db_returning.ReturningValuesTests.test_insert_returning', + 'queries.test_db_returning.ReturningValuesTests.test_insert_returning_non_integer', + 'backends.tests.BackendTestCase.test_queries', + 'introspection.tests.IntrospectionTests.test_smallautofield', + 'schema.tests.SchemaTests.test_inline_fk', + 'aggregation.tests.AggregateTestCase.test_aggregation_subquery_annotation_exists', + 'aggregation.tests.AggregateTestCase.test_aggregation_subquery_annotation_values_collision', + 'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_extract_func_with_timezone', + 'db_functions.text.test_md5.MD5Tests.test_basic', + 'db_functions.text.test_md5.MD5Tests.test_transform', + 'db_functions.text.test_sha1.SHA1Tests.test_basic', + 'db_functions.text.test_sha1.SHA1Tests.test_transform', + 'db_functions.text.test_sha224.SHA224Tests.test_basic', + 'db_functions.text.test_sha224.SHA224Tests.test_transform', + 'db_functions.text.test_sha256.SHA256Tests.test_basic', + 'db_functions.text.test_sha256.SHA256Tests.test_transform', + 'db_functions.text.test_sha384.SHA384Tests.test_basic', + 'db_functions.text.test_sha384.SHA384Tests.test_transform', + 'db_functions.text.test_sha512.SHA512Tests.test_basic', + 'db_functions.text.test_sha512.SHA512Tests.test_transform', + 'expressions.tests.BasicExpressionsTests.test_case_in_filter_if_boolean_output_field', + 'expressions.tests.BasicExpressionsTests.test_subquery_in_filter', + 'expressions.tests.FTimeDeltaTests.test_date_subquery_subtraction', + 'expressions.tests.FTimeDeltaTests.test_datetime_subquery_subtraction', + 'expressions.tests.FTimeDeltaTests.test_time_subquery_subtraction', + 'expressions.tests.BasicExpressionsTests.test_filtering_on_q_that_is_boolean', + 'migrations.test_operations.OperationTests.test_alter_field_reloads_state_on_fk_with_to_field_target_type_change', + 'migrations.test_operations.OperationTests.test_autofield__bigautofield_foreignfield_growth', + 'migrations.test_operations.OperationTests.test_smallfield_autofield_foreignfield_growth', + 'migrations.test_operations.OperationTests.test_smallfield_bigautofield_foreignfield_growth', + 'schema.tests.SchemaTests.test_alter_auto_field_quoted_db_column', + 'schema.tests.SchemaTests.test_alter_autofield_pk_to_bigautofield_pk_sequence_owner', + 'schema.tests.SchemaTests.test_alter_autofield_pk_to_smallautofield_pk_sequence_owner', + 'schema.tests.SchemaTests.test_alter_primary_key_quoted_db_table', + 'schema.tests.SchemaTests.test_alter_smallint_pk_to_smallautofield_pk', - 'annotations.tests.NonAggregateAnnotationTestCase.test_combined_expression_annotation_with_aggregation', - 'bulk_create.tests.BulkCreateTests.test_bulk_insert_nullable_fields', - 'db_functions.comparison.test_cast.CastTests.test_cast_to_integer', - 'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_func', - 'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_iso_weekday_func', - 'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_extract_func', - 'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_extract_iso_weekday_func', - 'datetimes.tests.DateTimesTests.test_datetimes_ambiguous_and_invalid_times', - 'expressions.tests.ExpressionOperatorTests.test_lefthand_bitwise_xor', - 'expressions.tests.ExpressionOperatorTests.test_lefthand_bitwise_xor_null', - 'inspectdb.tests.InspectDBTestCase.test_number_field_types', - 'inspectdb.tests.InspectDBTestCase.test_json_field', - 'ordering.tests.OrderingTests.test_default_ordering_by_f_expression', - 'ordering.tests.OrderingTests.test_order_by_nulls_first', - 'ordering.tests.OrderingTests.test_order_by_nulls_last', - 'queries.test_qs_combinators.QuerySetSetOperationTests.test_ordering_by_f_expression_and_alias', - 'queries.test_db_returning.ReturningValuesTests.test_insert_returning_multiple', - 'dbshell.tests.DbshellCommandTestCase.test_command_missing', - 'schema.tests.SchemaTests.test_char_field_pk_to_auto_field', - 'datetimes.tests.DateTimesTests.test_21432', + 'annotations.tests.NonAggregateAnnotationTestCase.test_combined_expression_annotation_with_aggregation', + 'bulk_create.tests.BulkCreateTests.test_bulk_insert_nullable_fields', + 'db_functions.comparison.test_cast.CastTests.test_cast_to_integer', + 'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_func', + 'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_iso_weekday_func', + 'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_extract_func', + 'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_extract_iso_weekday_func', + 'datetimes.tests.DateTimesTests.test_datetimes_ambiguous_and_invalid_times', + 'expressions.tests.ExpressionOperatorTests.test_lefthand_bitwise_xor', + 'expressions.tests.ExpressionOperatorTests.test_lefthand_bitwise_xor_null', + 'inspectdb.tests.InspectDBTestCase.test_number_field_types', + 'inspectdb.tests.InspectDBTestCase.test_json_field', + 'ordering.tests.OrderingTests.test_default_ordering_by_f_expression', + 'ordering.tests.OrderingTests.test_order_by_nulls_first', + 'ordering.tests.OrderingTests.test_order_by_nulls_last', + 'queries.test_qs_combinators.QuerySetSetOperationTests.test_ordering_by_f_expression_and_alias', + 'queries.test_db_returning.ReturningValuesTests.test_insert_returning_multiple', + 'dbshell.tests.DbshellCommandTestCase.test_command_missing', + 'schema.tests.SchemaTests.test_char_field_pk_to_auto_field', + 'datetimes.tests.DateTimesTests.test_21432', - # JSONFields - 'model_fields.test_jsonfield.TestQuerying.test_has_key_list', - 'model_fields.test_jsonfield.TestQuerying.test_has_key_null_value', - 'model_fields.test_jsonfield.TestQuerying.test_key_quoted_string', - 'model_fields.test_jsonfield.TestQuerying.test_lookups_with_key_transform', - 'model_fields.test_jsonfield.TestQuerying.test_ordering_grouping_by_count', - 'model_fields.test_jsonfield.TestQuerying.test_isnull_key', - 'model_fields.test_jsonfield.TestQuerying.test_none_key', - 'model_fields.test_jsonfield.TestQuerying.test_none_key_and_exact_lookup', - 'model_fields.test_jsonfield.TestQuerying.test_key_escape', - 'model_fields.test_jsonfield.TestQuerying.test_ordering_by_transform', - 'expressions_window.tests.WindowFunctionTests.test_key_transform', + # JSONFields + 'model_fields.test_jsonfield.TestQuerying.test_has_key_list', + 'model_fields.test_jsonfield.TestQuerying.test_has_key_null_value', + 'model_fields.test_jsonfield.TestQuerying.test_key_quoted_string', + 'model_fields.test_jsonfield.TestQuerying.test_lookups_with_key_transform', + 'model_fields.test_jsonfield.TestQuerying.test_ordering_grouping_by_count', + 'model_fields.test_jsonfield.TestQuerying.test_isnull_key', + 'model_fields.test_jsonfield.TestQuerying.test_none_key', + 'model_fields.test_jsonfield.TestQuerying.test_none_key_and_exact_lookup', + 'model_fields.test_jsonfield.TestQuerying.test_key_escape', + 'model_fields.test_jsonfield.TestQuerying.test_ordering_by_transform', + 'expressions_window.tests.WindowFunctionTests.test_key_transform', - # Django 3.2 - 'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_trunc_func_with_timezone', - 'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_trunc_timezone_applied_before_truncation', - 'expressions.tests.ExistsTests.test_optimizations', - 'expressions.tests.FTimeDeltaTests.test_delta_add', - 'expressions.tests.FTimeDeltaTests.test_delta_subtract', - 'expressions.tests.FTimeDeltaTests.test_delta_update', - 'expressions.tests.FTimeDeltaTests.test_exclude', - 'expressions.tests.FTimeDeltaTests.test_mixed_comparisons1', - 'expressions.tests.FTimeDeltaTests.test_negative_timedelta_update', - 'inspectdb.tests.InspectDBTestCase.test_field_types', - 'lookup.tests.LookupTests.test_in_ignore_none', - 'lookup.tests.LookupTests.test_in_ignore_none_with_unhashable_items', - 'queries.test_qs_combinators.QuerySetSetOperationTests.test_exists_union', - 'introspection.tests.IntrospectionTests.test_get_constraints_unique_indexes_orders', - 'schema.tests.SchemaTests.test_ci_cs_db_collation', - 'select_for_update.tests.SelectForUpdateTests.test_unsuported_no_key_raises_error', + # Django 3.2 + 'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_trunc_func_with_timezone', + 'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_trunc_timezone_applied_before_truncation', + 'expressions.tests.ExistsTests.test_optimizations', + 'expressions.tests.FTimeDeltaTests.test_delta_add', + 'expressions.tests.FTimeDeltaTests.test_delta_subtract', + 'expressions.tests.FTimeDeltaTests.test_delta_update', + 'expressions.tests.FTimeDeltaTests.test_exclude', + 'expressions.tests.FTimeDeltaTests.test_mixed_comparisons1', + 'expressions.tests.FTimeDeltaTests.test_negative_timedelta_update', + 'inspectdb.tests.InspectDBTestCase.test_field_types', + 'lookup.tests.LookupTests.test_in_ignore_none', + 'lookup.tests.LookupTests.test_in_ignore_none_with_unhashable_items', + 'queries.test_qs_combinators.QuerySetSetOperationTests.test_exists_union', + 'introspection.tests.IntrospectionTests.test_get_constraints_unique_indexes_orders', + 'schema.tests.SchemaTests.test_ci_cs_db_collation', + 'select_for_update.tests.SelectForUpdateTests.test_unsuported_no_key_raises_error', - # Django 4.0 - 'aggregation.tests.AggregateTestCase.test_aggregation_default_using_date_from_database', - 'aggregation.tests.AggregateTestCase.test_aggregation_default_using_datetime_from_database', - 'aggregation.tests.AggregateTestCase.test_aggregation_default_using_time_from_database', - 'expressions.tests.FTimeDeltaTests.test_durationfield_multiply_divide', - 'lookup.tests.LookupQueryingTests.test_alias', - 'lookup.tests.LookupQueryingTests.test_filter_exists_lhs', - 'lookup.tests.LookupQueryingTests.test_filter_lookup_lhs', - 'lookup.tests.LookupQueryingTests.test_filter_subquery_lhs', - 'lookup.tests.LookupQueryingTests.test_filter_wrapped_lookup_lhs', - 'lookup.tests.LookupQueryingTests.test_lookup_in_order_by', - 'lookup.tests.LookupTests.test_lookup_rhs', - 'order_with_respect_to.tests.OrderWithRespectToBaseTests.test_previous_and_next_in_order', - 'ordering.tests.OrderingTests.test_default_ordering_does_not_affect_group_by', - 'queries.test_explain.ExplainUnsupportedTests.test_message', - 'aggregation.tests.AggregateTestCase.test_coalesced_empty_result_set', - 'aggregation.tests.AggregateTestCase.test_empty_result_optimization', - 'queries.test_bulk_update.BulkUpdateTests.test_empty_objects', - 'queries.test_bulk_update.BulkUpdateTests.test_large_batch', - 'queries.test_bulk_update.BulkUpdateTests.test_updated_rows_when_passing_duplicates', - 'queries.tests.Queries6Tests.test_col_alias_quoted', - 'backends.tests.BackendTestCase.test_queries_logger', - 'migrations.test_operations.OperationTests.test_alter_field_pk_mti_fk', - 'migrations.test_operations.OperationTests.test_run_sql_add_missing_semicolon_on_collect_sql', - ] + # Django 4.0 + 'aggregation.tests.AggregateTestCase.test_aggregation_default_using_date_from_database', + 'aggregation.tests.AggregateTestCase.test_aggregation_default_using_datetime_from_database', + 'aggregation.tests.AggregateTestCase.test_aggregation_default_using_time_from_database', + 'expressions.tests.FTimeDeltaTests.test_durationfield_multiply_divide', + 'lookup.tests.LookupQueryingTests.test_alias', + 'lookup.tests.LookupQueryingTests.test_filter_exists_lhs', + 'lookup.tests.LookupQueryingTests.test_filter_lookup_lhs', + 'lookup.tests.LookupQueryingTests.test_filter_subquery_lhs', + 'lookup.tests.LookupQueryingTests.test_filter_wrapped_lookup_lhs', + 'lookup.tests.LookupQueryingTests.test_lookup_in_order_by', + 'lookup.tests.LookupTests.test_lookup_rhs', + 'order_with_respect_to.tests.OrderWithRespectToBaseTests.test_previous_and_next_in_order', + 'ordering.tests.OrderingTests.test_default_ordering_does_not_affect_group_by', + 'queries.test_explain.ExplainUnsupportedTests.test_message', + 'aggregation.tests.AggregateTestCase.test_coalesced_empty_result_set', + 'aggregation.tests.AggregateTestCase.test_empty_result_optimization', + 'queries.tests.Queries6Tests.test_col_alias_quoted', + 'backends.tests.BackendTestCase.test_queries_logger', + 'migrations.test_operations.OperationTests.test_alter_field_pk_mti_fk', + 'migrations.test_operations.OperationTests.test_run_sql_add_missing_semicolon_on_collect_sql', +] -REGEX_TESTS = ['lookup.tests.LookupTests.test_regex', - 'lookup.tests.LookupTests.test_regex_backreferencing', - 'lookup.tests.LookupTests.test_regex_non_ascii', - 'lookup.tests.LookupTests.test_regex_non_string', - 'lookup.tests.LookupTests.test_regex_null', - 'model_fields.test_jsonfield.TestQuerying.test_key_iregex', - 'model_fields.test_jsonfield.TestQuerying.test_key_regex', - ] +REGEX_TESTS = [ + 'lookup.tests.LookupTests.test_regex', + 'lookup.tests.LookupTests.test_regex_backreferencing', + 'lookup.tests.LookupTests.test_regex_non_ascii', + 'lookup.tests.LookupTests.test_regex_non_string', + 'lookup.tests.LookupTests.test_regex_null', + 'model_fields.test_jsonfield.TestQuerying.test_key_iregex', + 'model_fields.test_jsonfield.TestQuerying.test_key_regex', +] diff --git a/testapp/tests/test_constraints.py b/testapp/tests/test_constraints.py index 1030e9f8..dca4e456 100644 --- a/testapp/tests/test_constraints.py +++ b/testapp/tests/test_constraints.py @@ -10,18 +10,21 @@ from ..models import ( Author, Editor, + M2MOtherModel, Post, TestUniqueNullableModel, TestNullableUniqueTogetherModel, + TestRenameManyToManyFieldModel, ) @skipUnlessDBFeature('supports_nullable_unique_constraints') class TestNullableUniqueColumn(TestCase): def test_multiple_nulls(self): - # Issue #45 (case 1) - after field `x` has had its type changed, the filtered UNIQUE - # INDEX which is implementing the nullable unique constraint should still be correctly - # in place - i.e. allowing multiple NULLs but still enforcing uniqueness of non-NULLs + # Issue https://github.com/ESSolutions/django-mssql-backend/issues/45 (case 1) + # After field `x` has had its type changed, the filtered UNIQUE INDEX which is + # implementing the nullable unique constraint should still be correctly in place + # i.e. allowing multiple NULLs but still enforcing uniqueness of non-NULLs # Allowed TestUniqueNullableModel.objects.create(x=None, test_field='randomness') @@ -50,8 +53,9 @@ def test_partially_nullable(self): Post.objects.create(title="foo", author=author, alt_editor=editor) def test_after_type_change(self): - # Issue #45 (case 2) - after one of the fields in the `unique_together` has had its - # type changed in a migration, the constraint should still be correctly enforced + # Issue https://github.com/ESSolutions/django-mssql-backend/issues/45 (case 2) + # After one of the fields in the `unique_together` has had its type changed + # in a migration, the constraint should still be correctly enforced # Multiple rows with a=NULL are considered different TestNullableUniqueTogetherModel.objects.create(a=None, b='bbb', c='ccc') @@ -63,6 +67,21 @@ def test_after_type_change(self): TestNullableUniqueTogetherModel.objects.create(a='aaa', b='bbb', c='ccc') +class TestRenameManyToManyField(TestCase): + def test_uniqueness_still_enforced_afterwards(self): + # Issue https://github.com/microsoft/mssql-django/issues/86 + # Prep + thing1 = TestRenameManyToManyFieldModel.objects.create() + other1 = M2MOtherModel.objects.create(name='1') + other2 = M2MOtherModel.objects.create(name='2') + thing1.others_renamed.set([other1, other2]) + # Check that the unique_together on the through table is still enforced + ThroughModel = TestRenameManyToManyFieldModel.others_renamed.through + with self.assertRaises(IntegrityError, msg='Through model fails to enforce uniqueness after m2m rename'): + # This should fail due to the unique_together because (thing1, other1) is already in the through table + ThroughModel.objects.create(testrenamemanytomanyfieldmodel=thing1, m2mothermodel=other1) + + class TestUniqueConstraints(TransactionTestCase): def test_unsupportable_unique_constraint(self): # Only execute tests when running against SQL Server diff --git a/testapp/tests/test_indexes.py b/testapp/tests/test_indexes.py new file mode 100644 index 00000000..1d7a8f83 --- /dev/null +++ b/testapp/tests/test_indexes.py @@ -0,0 +1,48 @@ +import django.db +from django.test import TestCase + +from ..models import ( + TestIndexesRetainedRenamed +) + + +class TestIndexesRetained(TestCase): + """ + Issue https://github.com/ESSolutions/django-mssql-backend/issues/58 + Indexes dropped during a migration should be re-created afterwards + assuming the field still has `db_index=True` + """ + + @classmethod + def setUpClass(cls): + super().setUpClass() + # Pre-fetch which indexes exist for the relevant test model + # now that all the test migrations have run + connection = django.db.connections[django.db.DEFAULT_DB_ALIAS] + cls.constraints = connection.introspection.get_constraints( + connection.cursor(), + table_name=TestIndexesRetainedRenamed._meta.db_table + ) + cls.indexes = {k: v for k, v in cls.constraints.items() if v['index'] is True} + + def _assert_index_exists(self, columns): + matching = {k: v for k, v in self.indexes.items() if set(v['columns']) == columns} + assert len(matching) == 1, ( + "Expected 1 index for columns %s but found %d %s" % ( + columns, + len(matching), + ', '.join(matching.keys()) + ) + ) + + def test_field_made_nullable(self): + # case (a) of https://github.com/ESSolutions/django-mssql-backend/issues/58 + self._assert_index_exists({'a'}) + + def test_field_renamed(self): + # case (b) of https://github.com/ESSolutions/django-mssql-backend/issues/58 + self._assert_index_exists({'b_renamed'}) + + def test_table_renamed(self): + # case (c) of https://github.com/ESSolutions/django-mssql-backend/issues/58 + self._assert_index_exists({'c'})