diff --git a/CHANGELOG.md b/CHANGELOG.md index 25049f3..7332967 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +Bug: +- Do not report a required column added to a table unconditionally created earlier in the same migration. + Raw SQL, data migrations, and required columns added to existing tables remain linted. + ## 6.0.0 Feature: diff --git a/DECISIONS.md b/DECISIONS.md new file mode 100644 index 0000000..649104c --- /dev/null +++ b/DECISIONS.md @@ -0,0 +1,12 @@ +# Architecture Decisions + +## Required columns on newly created tables + +**Decision.** The `NOT_NULL` SQL check accepts `ALTER TABLE ... ADD COLUMN ... NOT NULL` +only when the same migration unconditionally created that exact table. `CREATE TABLE IF +NOT EXISTS` does not prove an empty table. The migration remains +fully linted, so `RunSQL`, `RunPython`, and operations on existing tables retain their +checks. + +**Rejected.** Ignoring all initial migrations or naming them as exemptions. Either option +skips unsafe operations that the linter must inspect. diff --git a/src/django_migration_linter/sql_analyser/base.py b/src/django_migration_linter/sql_analyser/base.py index 2ad34bb..3f14476 100644 --- a/src/django_migration_linter/sql_analyser/base.py +++ b/src/django_migration_linter/sql_analyser/base.py @@ -30,16 +30,41 @@ def update_migration_checks( return new_checks +_IDENTIFIER = r'"[^"]+"|`[^`]+`|\'[^\']+\'|\[[^]]+\]|[A-Za-z_][A-Za-z0-9_$]*' +_TABLE_NAME = rf"(?:{_IDENTIFIER})(?:\.(?:{_IDENTIFIER}))*" +_CREATE_TABLE = re.compile( + rf"^CREATE TABLE (?!IF NOT EXISTS )(?P{_TABLE_NAME})", re.IGNORECASE +) +_ADD_COLUMN = re.compile( + rf"^ALTER TABLE (?:ONLY )?(?P
{_TABLE_NAME}) ADD (?:COLUMN )?", + re.IGNORECASE, +) + + def has_not_null_column(sql_statements: list[str], **kwargs) -> bool: - # TODO: improve to detect that the same column is concerned + """Detect a required column added to a table that already exists.""" not_null_column = False has_default_value = False + created_tables = set() for sql in sql_statements: - if re.search("(?