diff --git a/CHANGELOG.md b/CHANGELOG.md index f306ed3..6fa867c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Drop support for Django 1.11, 2.0, 2.1, 3.0 and 3.1 - Add support for Django 4.0 - Fix index creation detection when table is being created in the transaction (issue #178) +- Handle unique index creation as adding a unique constraint (issue #183) ## 3.0.1 diff --git a/django_migration_linter/sql_analyser/base.py b/django_migration_linter/sql_analyser/base.py index a74b40f..d399ab7 100644 --- a/django_migration_linter/sql_analyser/base.py +++ b/django_migration_linter/sql_analyser/base.py @@ -26,7 +26,9 @@ def has_not_null_column(sql_statements, **kwargs): def has_add_unique(sql_statements, **kwargs): regex_result = None for sql in sql_statements: - regex_result = re.search("ALTER TABLE (.*) ADD CONSTRAINT .* UNIQUE", sql) + regex_result = re.search( + "ALTER TABLE (.*) ADD CONSTRAINT .* UNIQUE", sql + ) or re.search('CREATE UNIQUE INDEX .* ON (".*?")', sql) if regex_result: break if not regex_result: diff --git a/django_migration_linter/sql_analyser/sqlite.py b/django_migration_linter/sql_analyser/sqlite.py index 9a947d1..530788a 100644 --- a/django_migration_linter/sql_analyser/sqlite.py +++ b/django_migration_linter/sql_analyser/sqlite.py @@ -3,23 +3,6 @@ import re from .base import BaseAnalyser -def has_add_unique(sql_statements, **kwargs): - regex_result = None - for sql in sql_statements: - regex_result = re.search('CREATE UNIQUE INDEX .* ON (".*?")', sql) - if regex_result: - break - if not regex_result: - return False - - concerned_table = regex_result.group(1) - table_is_added_in_transaction = any( - sql.startswith("CREATE TABLE {}".format(concerned_table)) - for sql in sql_statements - ) - return not table_is_added_in_transaction - - class SqliteAnalyser(BaseAnalyser): migration_tests = [ { @@ -54,12 +37,6 @@ class SqliteAnalyser(BaseAnalyser): "mode": "transaction", "type": "error", }, - { - "code": "ADD_UNIQUE", - "fn": has_add_unique, - "mode": "transaction", - "type": "error", - }, ] @staticmethod diff --git a/tests/unit/test_sql_analyser.py b/tests/unit/test_sql_analyser.py index 65ab7f0..00929b2 100644 --- a/tests/unit/test_sql_analyser.py +++ b/tests/unit/test_sql_analyser.py @@ -21,9 +21,16 @@ class SqlAnalyserTestCase(unittest.TestCase): 0, len(warnings), "Found warnings in sql: {}".format(errors) ) - def assertBackwardIncompatibleSql(self, sql): + def assertBackwardIncompatibleSql(self, sql, code=None): errors, _, _ = self.analyse_sql(sql) self.assertNotEqual(0, len(errors), "Found no errors in sql") + if code: + self.assertTrue( + any(err["code"] == code for err in errors), + "Didn't find error code {} in returned errors ({})".format( + code, [err["code"] for err in errors] + ), + ) def assertWarningSql(self, sql): _, _, warnings = self.analyse_sql(sql) @@ -63,6 +70,16 @@ class MySqlAnalyserTestCase(SqlAnalyserTestCase): sql = "ALTER TABLE `app_unique_together_a` DROP INDEX `app_unique_together_a_int_field_char_field_979ac7d8_uniq`;" self.assertValidSql(sql) + def test_unique_index(self): + sql = 'CREATE UNIQUE INDEX "index_name" ON "table" ("col1", "col2");' + self.assertBackwardIncompatibleSql(sql, "ADD_UNIQUE") + + sql = [ + 'CREATE TABLE "table" ("col1" integer, "col2" integer);', + 'CREATE UNIQUE INDEX "index_name" ON "table" ("col1", "col2");', + ] + self.assertValidSql(sql) + def test_add_many_to_many_field(self): sql = [ "CREATE TABLE `app_add_manytomany_field_b_many_to_many`(`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `b_id` integer NOT NULL, `a_id` integer NOT NULL);", @@ -146,6 +163,16 @@ class SqliteAnalyserTestCase(SqlAnalyserTestCase): sql = 'DROP INDEX "app_unique_together_a_int_field_char_field_979ac7d8_uniq";' self.assertValidSql(sql) + def test_unique_index(self): + sql = 'CREATE UNIQUE INDEX "index_name" ON "table" ("col1", "col2");' + self.assertBackwardIncompatibleSql(sql, "ADD_UNIQUE") + + sql = [ + 'CREATE TABLE "table" ("col1" integer, "col2" integer);', + 'CREATE UNIQUE INDEX "index_name" ON "table" ("col1", "col2");', + ] + self.assertValidSql(sql) + def test_add_many_to_many_field(self): sql = [ 'CREATE TABLE "app_add_manytomany_field_b_many_to_many"("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "b_id" integer NOT NULL REFERENCES "app_add_manytomany_field_b"("id") DEFERRABLE INITIALLY DEFERRED, "a_id" integer NOT NULL REFERENCES "app_add_manytomany_field_a"("id") DEFERRABLE INITIALLY DEFERRED);', @@ -197,6 +224,16 @@ class PostgresqlAnalyserTestCase(SqlAnalyserTestCase): sql = 'ALTER TABLE "app_unique_together_a" DROP CONSTRAINT "app_unique_together_a_int_field_char_field_979ac7d8_uniq";' self.assertValidSql(sql) + def test_unique_index(self): + sql = 'CREATE UNIQUE INDEX "index_name" ON "table" ("col1", "col2");' + self.assertBackwardIncompatibleSql(sql, "ADD_UNIQUE") + + sql = [ + 'CREATE TABLE "table" ("col1" integer, "col2" integer);', + 'CREATE UNIQUE INDEX "index_name" ON "table" ("col1", "col2");', + ] + self.assertValidSql(sql) + def test_add_many_to_many_field(self): sql = [ 'CREATE TABLE "app_add_manytomany_field_b_many_to_many"("id" serial NOT NULL PRIMARY KEY, "b_id" integer NOT NULL, "a_id" integer NOT NULL);',