Skip to content

Commit e2c0607

Browse files
authored
Merge pull request #65 from sqlcj-dev/feature/ignore-column-neutral-alter-table-actions
feat(schema): ignore column-neutral ALTER TABLE actions and EXCLUDE
2 parents 296f4f8 + ee4f58f commit e2c0607

4 files changed

Lines changed: 472 additions & 83 deletions

File tree

‎docs/postgresql.md‎

Lines changed: 78 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ are recorded with their declared type; see
362362
| Column-level `CHECK (...)` | Accepted and ignored. |
363363
| Table-level `FOREIGN KEY (...) REFERENCES ...`, named or unnamed | Accepted and ignored. |
364364
| Table-level `CHECK (...)`, named or unnamed | Accepted and ignored. |
365+
| Table-level `EXCLUDE USING ... (...)`, named or unnamed | Accepted and ignored. |
365366
| A table statement sqlcj's parser cannot read, such as `ALTER FOREIGN TABLE` | Rejected; see [Ignored Statements](#ignored-statements). |
366367
| Any other table-constraint kind | Rejected. |
367368
| Unparsable SQL | Rejected. |
@@ -395,7 +396,7 @@ statements and files before them left; the enum-type statements are listed in
395396
| `ALTER TABLE ... ALTER COLUMN ... SET NOT NULL` | Models the column non-null. |
396397
| `ALTER TABLE ... ALTER COLUMN ... DROP NOT NULL` | Models the column nullable. |
397398
| `ALTER TABLE IF EXISTS ...` | Does nothing when the table is not modeled. It covers only the table, so a missing column of a modeled table still fails. |
398-
| Any other `ALTER TABLE` action outside the constraint actions listed in [Ignored Statements](#ignored-statements), such as `SET DEFAULT` | Rejected as `Unsupported ALTER TABLE action at line <n>`. |
399+
| Any other `ALTER TABLE` action outside the ignored actions listed in [Ignored Statements](#ignored-statements), such as `SET SCHEMA` | Rejected as `Unsupported ALTER TABLE action: <action> at line <n>`, quoting the action's own SQL. |
399400
| `DROP` of any object other than a table, such as `DROP VIEW` or `DROP INDEX` | Ignored; see [Ignored Statements](#ignored-statements). |
400401

401402
One `ALTER TABLE` may state several actions; they are applied in the written
@@ -462,7 +463,7 @@ what they would do to the schema model:
462463

463464
| Statement | Diagnostic |
464465
| --- | --- |
465-
| An `ALTER TABLE` action outside [Ordered Table DDL](#ordered-table-ddl) and the constraint actions below | `Unsupported ALTER TABLE action at line <n>` |
466+
| An `ALTER TABLE` action outside [Ordered Table DDL](#ordered-table-ddl) and the ignored actions below | `Unsupported ALTER TABLE action: <action> at line <n>`, quoting the action's own SQL |
466467
| An `ALTER TYPE` action other than `ADD VALUE` | `Unsupported ALTER TYPE action at line <n>` |
467468
| A statement sqlcj's parser cannot read that opens as table or type DDL, outside the `ALTER TABLE` actions below | The parser's own reason with the line and column of the unexpected token, as in `Encountered unexpected token: "DATA" at line 18, column 41` |
468469
| A statement sqlcj's parser reports only as opaque text, or as several statements, and that opens as table or type DDL | `Unsupported schema statement: ALTER FOREIGN TABLE at line <n>`, quoting the opening words |
@@ -475,27 +476,60 @@ in upper case, single-spaced, so both
475476
`ALTER FOREIGN TABLE ft ADD COLUMN x integer` and its lower-case spelling are
476477
rejected as `Unsupported schema statement: ALTER FOREIGN TABLE at line <n>`.
477478

478-
These `ALTER TABLE` actions are ignored like any other unmodeled statement,
479-
except that the statement still resolves its table:
479+
Every `ALTER TABLE` action that cannot change a column's existence, name, type,
480+
or nullability is ignored like any other unmodeled statement, except that the
481+
statement still resolves its table:
480482

481483
| Action | Notes |
482484
| --- | --- |
483-
| `ALTER TABLE ... ADD [CONSTRAINT name] PRIMARY KEY (...)` | Named or unnamed. |
484-
| `ALTER TABLE ... ADD [CONSTRAINT name] UNIQUE (...)` | Named or unnamed. |
485-
| `ALTER TABLE ... ADD [CONSTRAINT name] FOREIGN KEY (...) REFERENCES ...` | Named or unnamed. |
486-
| `ALTER TABLE ... ADD CONSTRAINT name CHECK (...)` | |
487-
| `ALTER TABLE ... DROP CONSTRAINT [IF EXISTS] name` | |
488-
| `ALTER TABLE ... RENAME CONSTRAINT` | |
485+
| `ADD [CONSTRAINT name] PRIMARY KEY (...)` | Named or unnamed. |
486+
| `ADD [CONSTRAINT name] UNIQUE (...)` | Named or unnamed. |
487+
| `ADD [CONSTRAINT name] FOREIGN KEY (...) REFERENCES ...` | Named or unnamed. |
488+
| `ADD CONSTRAINT name CHECK (...)` | |
489+
| `DROP CONSTRAINT [IF EXISTS] name` | |
490+
| `RENAME CONSTRAINT` | |
491+
| `ALTER [COLUMN] c SET DEFAULT ...` and `DROP DEFAULT` | A `DEFAULT` is accepted and ignored in a `CREATE TABLE` as well. |
492+
| `ALTER [COLUMN] c SET STATISTICS`, `SET STORAGE`, and `SET COMPRESSION` | Column storage, not the column's type. |
493+
| `ALTER [COLUMN] c DROP EXPRESSION` | Only the readable spelling; `DROP EXPRESSION IF EXISTS` is unreadable. |
494+
| `ALTER [COLUMN] c ADD GENERATED ... AS IDENTITY`, `SET GENERATED ...`, a sequence option such as `SET INCREMENT BY 2`, `RESTART`, and `DROP IDENTITY [IF EXISTS]` | PostgreSQL requires the column to be `NOT NULL` already, so an identity never changes nullability. |
495+
| `ENABLE`, `DISABLE`, `FORCE`, and `NO FORCE ROW LEVEL SECURITY` | |
496+
| `ATTACH PARTITION` and `DETACH PARTITION` | The partition's own table is modeled by its own statements. |
497+
| `OWNER TO` | |
498+
| `ENABLE TRIGGER`, `DISABLE TRIGGER`, `ENABLE REPLICA TRIGGER`, and `ENABLE ALWAYS TRIGGER`, and the same four for `RULE` | |
499+
| `VALIDATE CONSTRAINT` | |
500+
| `REPLICA IDENTITY ...` | |
501+
| `CLUSTER ON` and `SET WITHOUT CLUSTER` | |
502+
| `SET WITHOUT OIDS` | |
503+
| `SET (...)` and `RESET (...)` | Storage parameters. |
504+
| `SET TABLESPACE`, `SET LOGGED`, `SET UNLOGGED`, and `SET ACCESS METHOD` | |
505+
| `INHERIT` and `NO INHERIT` | A table's columns are modeled from its own statements; inherited columns are not modeled. |
506+
| `OF type` and `NOT OF` | |
507+
508+
An ignored action resolves its table but not its column, so
509+
`ALTER TABLE users ALTER COLUMN nickname SET STATISTICS 100` is accepted for a
510+
column that is not modeled, while `ALTER TABLE payments OWNER TO app` fails as
511+
`Table not found in schema: payments`, exactly as any other `ALTER TABLE` of a
512+
missing table does, and `ALTER TABLE IF EXISTS payments OWNER TO app` does
513+
nothing.
489514

490515
A constraint an `ALTER TABLE` adds is not recorded, so it is not carried into
491-
the schema model the way a `CREATE TABLE` constraint is. Because the table is
492-
still resolved,
493-
`ALTER TABLE payments ADD CONSTRAINT payments_pkey PRIMARY KEY (id)` fails when
494-
`payments` is not modeled, exactly as any other `ALTER TABLE` of a missing
495-
table does. Such an action may stand alone or beside modeled actions of one
496-
`ALTER TABLE`, so
497-
`ALTER TABLE users ADD COLUMN age INTEGER, ADD CONSTRAINT users_age_check CHECK (age > 0)`
498-
appends `age` and records nothing for the constraint.
516+
the schema model the way a `CREATE TABLE` constraint is. An ignored action may
517+
stand alone or beside modeled actions of one `ALTER TABLE`, which are applied in
518+
the written order, so
519+
`ALTER TABLE users ADD COLUMN age INTEGER, ALTER COLUMN name SET DEFAULT 'new', OWNER TO app`
520+
appends `age` and records nothing else.
521+
522+
sqlcj's parser has no form of its own for the table-level actions listed from
523+
`OWNER TO` down in the table above. It reports such an action as the text that
524+
runs to the end of the statement, so sqlcj splits that text at the commas
525+
outside parentheses, with its own SQL lexer, and every action in it must be one
526+
of those. `ALTER TABLE users SET SCHEMA archive` is therefore rejected as
527+
`Unsupported ALTER TABLE action: SET SCHEMA archive at line <n>`, because it
528+
moves the table out of the one namespace sqlcj models, and a modeled action
529+
written after such an action is rejected rather than skipped, so
530+
`ALTER TABLE users OWNER TO app, ADD COLUMN x int` is rejected as
531+
`Unsupported ALTER TABLE action: ADD COLUMN x int at line <n>`. Written the
532+
other way round, `ALTER TABLE users ADD COLUMN x int, OWNER TO app` appends `x`.
499533

500534
An `ALTER TABLE` sqlcj's parser cannot read is classified by its words instead,
501535
and ignored when every one of its top-level actions — the parts separated by
@@ -654,7 +688,7 @@ and recorded type:
654688

655689
```text
656690
sqlcj: Invalid schema source /home/dev/project/schema.sql: Unsupported schema statement: ALTER FOREIGN TABLE at line 12
657-
sqlcj: Invalid schema source /home/dev/project/schema.sql: Unsupported ALTER TABLE action at line 18
691+
sqlcj: Invalid schema source /home/dev/project/schema.sql: Unsupported ALTER TABLE action: SET SCHEMA archive at line 18
658692
sqlcj: Invalid schema source /home/dev/project/schema.sql: Encountered unexpected token: ";" at line 4, column 1
659693
sqlcj: Invalid query 'ListTags' in /home/dev/project/queries.sql at line 5: Column 'tags' has unsupported type JSONB[]
660694
```
@@ -803,9 +837,11 @@ Enum types:
803837
`DefaultSchemaParserTest.shouldParseTableWithoutConstraints`,
804838
`DefaultSchemaParserTest.shouldCanonicalizeQuotedTableAndColumnNames`,
805839
`DefaultSchemaParserTest.shouldParseColumnSpecificationsThatDoNotAffectTypes`,
806-
`DefaultSchemaParserTest.shouldIgnoreForeignKeyAndCheckTableConstraints`, and
840+
`DefaultSchemaParserTest.shouldIgnoreForeignKeyAndCheckTableConstraints`,
841+
`DefaultSchemaParserTest.shouldIgnoreExcludeTableConstraints`, and
807842
`DefaultSchemaParserTest.shouldParseNamedTableConstraintsAlongsideIgnoredOnes`
808-
cover the modeled and ignored constructs.
843+
cover the modeled and ignored constructs, including a named and an unnamed
844+
`EXCLUDE` beside a recorded `PRIMARY KEY`.
809845
- `DefaultSchemaParserTest.shouldRejectUnsupportedSchemaStatement` and
810846
`DefaultSchemaParserTest.shouldThrowSchemaParseExceptionForInvalidSql` cover
811847
the rejected input.
@@ -837,11 +873,9 @@ Ordered table DDL:
837873
- `DefaultSchemaParserTest.shouldReportTheMissingTableOfAStatement`,
838874
`DefaultSchemaParserTest.shouldReportTheMissingColumnOfAnAlterTableAction`,
839875
`DefaultSchemaParserTest.shouldReportAStatementThatRepeatsATableName`,
840-
`DefaultSchemaParserTest.shouldReportAStatementThatRepeatsAColumnName`,
841-
`DefaultSchemaParserTest.shouldRejectUnsupportedAlterTableAction`,
842-
`DefaultSchemaParserTest.shouldRejectAlterColumnSetStatistics`, and
843-
`DefaultSchemaParserTest.shouldRejectAlterColumnAddIdentity` cover the
844-
rejected statements and their messages.
876+
`DefaultSchemaParserTest.shouldReportAStatementThatRepeatsAColumnName`, and
877+
`DefaultSchemaParserTest.shouldReportTheLineOfARejectedAlterTableAction`
878+
cover the rejected statements and their messages.
845879
- `SqlcjCompilerIntegrationTest.shouldGenerateTheSameRepositoryFromAlteringMigrationsAndASnapshot`
846880
generates one compilable repository from migrations that rename, add, drop, and
847881
alter columns, byte-identical to the one generated from the equivalent
@@ -860,13 +894,24 @@ Ignored statements:
860894
`ALTER FUNCTION`, `ALTER SCHEMA`, `CREATE AGGREGATE`, and `CREATE CAST`; and
861895
`DefaultSchemaParserTest.shouldIgnoreConstraintAlterTableActions` covers the
862896
named and unnamed constraint actions.
897+
- `DefaultSchemaParserTest.shouldIgnoreTheAlterTableActionsThatCannotChangeAColumn`
898+
covers one spelling per ignored `ALTER TABLE` action, including the lower-case
899+
and `ALTER TABLE ONLY public.users` spellings and two actions classified by
900+
their words in one statement.
863901
- `DefaultSchemaParserTest.shouldNotResolveAnIgnoredStatementAgainstTheSchema`
864902
covers that an ignored statement, including a view over an unmodeled table and
865-
a `TRUNCATE` of one, may name an unmodeled table or column, and
903+
a `TRUNCATE` of one, may name an unmodeled table or column;
866904
`DefaultSchemaParserTest.shouldReportTheMissingTableOfAnIgnoredConstraintAction`
867-
covers that an ignored `ALTER TABLE` action still resolves its table.
868-
- `DefaultSchemaParserTest.shouldApplyAModeledActionBesideAnIgnoredConstraintAction`
869-
covers an ignored action beside a modeled one.
905+
and
906+
`DefaultSchemaParserTest.shouldReportTheMissingTableOfAnIgnoredAction`
907+
cover that an ignored `ALTER TABLE` action still resolves its table and that
908+
`IF EXISTS` skips it; and
909+
`DefaultSchemaParserTest.shouldIgnoreAnAlterColumnActionOnAMissingColumn`
910+
covers that it does not resolve its column.
911+
- `DefaultSchemaParserTest.shouldApplyAModeledActionBesideAnIgnoredConstraintAction`,
912+
`DefaultSchemaParserTest.shouldApplyAnAddedColumnBesideIgnoredActions`, and
913+
`DefaultSchemaParserTest.shouldApplyAnAddedColumnAfterARowLevelSecurityAction`
914+
cover an ignored action beside a modeled one, in both written orders.
870915
- `DefaultSchemaParserTest.shouldIgnoreEveryUnreadableStatementThatIsNotTableOrTypeDdl`
871916
covers one spelling per statement kind sqlcj's parser cannot read, including
872917
the untagged, tagged, and `LANGUAGE` forms of `DO`, `DROP TYPE`, every
@@ -881,7 +926,9 @@ Ignored statements:
881926
the schema does not model, including the multi-line `pg_dump` identity form.
882927
- `DefaultSchemaParserTest.shouldRejectUnsupportedStatementsInSqlTerms` covers
883928
the exact message and line of each statement and action rejected in SQL terms,
884-
including the upper- and lower-case `ALTER FOREIGN TABLE`, and
929+
including `SET SCHEMA` alone and after a modeled action, a modeled action
930+
written after an ignored one, an unrecognized `ALTER COLUMN` action, and the
931+
upper- and lower-case `ALTER FOREIGN TABLE`, and
885932
`DefaultSchemaParserTest.shouldRejectUnsupportedSchemaStatement`,
886933
`DefaultSchemaParserTest.shouldReportTheLineOfARejectedStatementAfterCommentsAndAFunctionBody`,
887934
and

0 commit comments

Comments
 (0)