@intentius/chant-lexicon-sql 0.124.46 → 0.124.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/change-set.d.ts +1 -1
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +2 -1
- package/dist/clickhouse/apply/statements.d.ts +9 -0
- package/dist/clickhouse/apply/statements.d.ts.map +1 -1
- package/dist/clickhouse/apply/statements.js +24 -0
- package/dist/clickhouse/import/live-export.d.ts +7 -3
- package/dist/clickhouse/import/live-export.d.ts.map +1 -1
- package/dist/clickhouse/import/live-export.js +21 -5
- package/dist/clickhouse/live/named-collections.d.ts +27 -0
- package/dist/clickhouse/live/named-collections.d.ts.map +1 -0
- package/dist/clickhouse/live/named-collections.js +53 -0
- package/dist/clickhouse/live/undeclarable.d.ts +15 -3
- package/dist/clickhouse/live/undeclarable.d.ts.map +1 -1
- package/dist/clickhouse/live/undeclarable.js +18 -4
- package/dist/clickhouse/named-collections.d.ts +83 -0
- package/dist/clickhouse/named-collections.d.ts.map +1 -0
- package/dist/clickhouse/named-collections.js +203 -0
- package/dist/clickhouse/parser.d.ts.map +1 -1
- package/dist/clickhouse/parser.js +1 -1
- package/dist/clickhouse/plan/commands.d.ts +2 -1
- package/dist/clickhouse/plan/commands.d.ts.map +1 -1
- package/dist/clickhouse/plan/commands.js +17 -2
- package/dist/clickhouse/plan/diff.d.ts +15 -0
- package/dist/clickhouse/plan/diff.d.ts.map +1 -1
- package/dist/clickhouse/plan/diff.js +43 -1
- package/dist/clickhouse/plan/disruption.d.ts.map +1 -1
- package/dist/clickhouse/plan/disruption.js +5 -0
- package/dist/clickhouse/plan/rebuild-handoff.d.ts.map +1 -1
- package/dist/clickhouse/plan/rebuild-handoff.js +4 -0
- package/dist/clickhouse/plan/report.d.ts.map +1 -1
- package/dist/clickhouse/plan/report.js +6 -1
- package/dist/clickhouse/plan/rules.d.ts +5 -1
- package/dist/clickhouse/plan/rules.d.ts.map +1 -1
- package/dist/clickhouse/plan/rules.js +7 -3
- package/dist/clickhouse/plan/statements.d.ts +29 -0
- package/dist/clickhouse/plan/statements.d.ts.map +1 -0
- package/dist/clickhouse/plan/statements.js +334 -0
- package/dist/clickhouse/plan/stream.d.ts +40 -0
- package/dist/clickhouse/plan/stream.d.ts.map +1 -0
- package/dist/clickhouse/plan/stream.js +107 -0
- package/dist/clickhouse/rebuild/observe.d.ts +3 -1
- package/dist/clickhouse/rebuild/observe.d.ts.map +1 -1
- package/dist/clickhouse/rebuild/observe.js +14 -1
- package/dist/clickhouse/review-catalog.d.ts +213 -0
- package/dist/clickhouse/review-catalog.d.ts.map +1 -0
- package/dist/clickhouse/review-catalog.js +330 -0
- package/dist/codegen/classifier-page.d.ts.map +1 -1
- package/dist/codegen/classifier-page.js +44 -4
- package/dist/codegen/clickhouse-module.d.ts.map +1 -1
- package/dist/codegen/clickhouse-module.js +3 -0
- package/dist/codegen/coverage-page.d.ts.map +1 -1
- package/dist/codegen/coverage-page.js +5 -3
- package/dist/codegen/docs.js +1 -1
- package/dist/codegen/postgres-classifier-page.js +2 -2
- package/dist/codegen/registry.d.ts.map +1 -1
- package/dist/codegen/registry.js +10 -0
- package/dist/composites/catalog.d.ts.map +1 -1
- package/dist/composites/catalog.js +100 -0
- package/dist/composites/clickhouse.d.ts +2 -0
- package/dist/composites/clickhouse.d.ts.map +1 -1
- package/dist/composites/clickhouse.js +2 -0
- package/dist/composites/otel-tables.d.ts +100 -0
- package/dist/composites/otel-tables.d.ts.map +1 -0
- package/dist/composites/otel-tables.js +282 -0
- package/dist/composites/stream-ingest.d.ts +63 -0
- package/dist/composites/stream-ingest.d.ts.map +1 -0
- package/dist/composites/stream-ingest.js +39 -0
- package/dist/core/statement-class.d.ts +55 -0
- package/dist/core/statement-class.d.ts.map +1 -0
- package/dist/core/statement-class.js +43 -0
- package/dist/coverage/clickhouse.json +1 -1
- package/dist/coverage/postgres.json +12 -12
- package/dist/coverage.d.ts +16 -6
- package/dist/coverage.d.ts.map +1 -1
- package/dist/coverage.js +10 -5
- package/dist/drizzle-command.d.ts +17 -0
- package/dist/drizzle-command.d.ts.map +1 -0
- package/dist/drizzle-command.js +91 -0
- package/dist/drizzle.d.ts +70 -0
- package/dist/drizzle.d.ts.map +1 -0
- package/dist/drizzle.js +689 -0
- package/dist/files/clickhouse.d.ts.map +1 -1
- package/dist/files/clickhouse.js +8 -1
- package/dist/files/common.d.ts +13 -0
- package/dist/files/common.d.ts.map +1 -1
- package/dist/files/common.js +5 -0
- package/dist/files/postgres.d.ts.map +1 -1
- package/dist/files/postgres.js +35 -3
- package/dist/generated/clickhouse.d.ts +2 -0
- package/dist/generated/clickhouse.d.ts.map +1 -1
- package/dist/generated/clickhouse.js +2 -0
- package/dist/import-parser.d.ts +21 -1
- package/dist/import-parser.d.ts.map +1 -1
- package/dist/import-parser.js +79 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/integrity.json +7 -6
- package/dist/lint/post-synth/audit-entries.d.ts.map +1 -1
- package/dist/lint/post-synth/audit-entries.js +1 -0
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/post-synth/index.js +2 -0
- package/dist/lint/post-synth/postgres-names.d.ts.map +1 -1
- package/dist/lint/post-synth/postgres-names.js +3 -0
- package/dist/lint/post-synth/sqlch128.d.ts +18 -0
- package/dist/lint/post-synth/sqlch128.d.ts.map +1 -0
- package/dist/lint/post-synth/sqlch128.js +222 -0
- package/dist/lint/rules/postgres-templates.js +2 -2
- package/dist/lsp/postgres-template.d.ts.map +1 -1
- package/dist/lsp/postgres-template.js +21 -0
- package/dist/manifest.json +1 -1
- package/dist/mcp/postgres.js +1 -1
- package/dist/meta.json +50 -0
- package/dist/migrate/from-postgres/detect.d.ts +4 -0
- package/dist/migrate/from-postgres/detect.d.ts.map +1 -0
- package/dist/migrate/from-postgres/detect.js +7 -0
- package/dist/migrate/from-postgres/emit.d.ts +26 -0
- package/dist/migrate/from-postgres/emit.d.ts.map +1 -0
- package/dist/migrate/from-postgres/emit.js +86 -0
- package/dist/migrate/from-postgres/index.d.ts +51 -0
- package/dist/migrate/from-postgres/index.d.ts.map +1 -0
- package/dist/migrate/from-postgres/index.js +115 -0
- package/dist/migrate/from-postgres/translate.d.ts +73 -0
- package/dist/migrate/from-postgres/translate.d.ts.map +1 -0
- package/dist/migrate/from-postgres/translate.js +261 -0
- package/dist/migrate/from-postgres/types.d.ts +51 -0
- package/dist/migrate/from-postgres/types.d.ts.map +1 -0
- package/dist/migrate/from-postgres/types.js +211 -0
- package/dist/migration-classify.d.ts +23 -0
- package/dist/migration-classify.d.ts.map +1 -0
- package/dist/migration-classify.js +44 -0
- package/dist/migration-replay.d.ts +73 -0
- package/dist/migration-replay.d.ts.map +1 -0
- package/dist/migration-replay.js +294 -0
- package/dist/okf/index.md +11 -0
- package/dist/okf/rules/SQLCH128.md +11 -0
- package/dist/okf/types/PostgresForeignDataWrapper.md +9 -0
- package/dist/okf/types/PostgresForeignServer.md +9 -0
- package/dist/okf/types/PostgresForeignTable.md +9 -0
- package/dist/okf/types/PostgresOperator.md +9 -0
- package/dist/okf/types/PostgresOperatorClass.md +9 -0
- package/dist/okf/types/PostgresOperatorFamily.md +9 -0
- package/dist/okf/types/PostgresPublication.md +9 -0
- package/dist/okf/types/PostgresStatistics.md +9 -0
- package/dist/okf/types/PostgresTextSearchConfiguration.md +9 -0
- package/dist/okf/types/PostgresTextSearchDictionary.md +9 -0
- package/dist/op/activities/postgres-migration.d.ts.map +1 -1
- package/dist/op/activities/postgres-migration.js +4 -0
- package/dist/op/activity-contracts.d.ts +20 -0
- package/dist/op/activity-contracts.d.ts.map +1 -1
- package/dist/op/activity-contracts.js +2 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +49 -2
- package/dist/postgres/access/acl.js +1 -1
- package/dist/postgres/access/import.js +1 -1
- package/dist/postgres/apply/apply.js +13 -1
- package/dist/postgres/apply/statements.d.ts.map +1 -1
- package/dist/postgres/apply/statements.js +243 -4
- package/dist/postgres/constraint-names.d.ts +30 -0
- package/dist/postgres/constraint-names.d.ts.map +1 -0
- package/dist/postgres/constraint-names.js +72 -0
- package/dist/postgres/entities.d.ts +229 -2
- package/dist/postgres/entities.d.ts.map +1 -1
- package/dist/postgres/entities.js +326 -5
- package/dist/postgres/entity-types.d.ts +10 -0
- package/dist/postgres/entity-types.d.ts.map +1 -1
- package/dist/postgres/entity-types.js +10 -0
- package/dist/postgres/import/generator.d.ts.map +1 -1
- package/dist/postgres/import/generator.js +65 -2
- package/dist/postgres/import/live-export.d.ts.map +1 -1
- package/dist/postgres/import/live-export.js +2 -0
- package/dist/postgres/live/catalog-kinds.d.ts +20 -0
- package/dist/postgres/live/catalog-kinds.d.ts.map +1 -1
- package/dist/postgres/live/catalog-kinds.js +452 -0
- package/dist/postgres/live/catalog.d.ts +2 -0
- package/dist/postgres/live/catalog.d.ts.map +1 -1
- package/dist/postgres/live/catalog.js +3 -1
- package/dist/postgres/live/describe-resources.d.ts.map +1 -1
- package/dist/postgres/live/describe-resources.js +12 -2
- package/dist/postgres/migrate/backfill.d.ts +2 -0
- package/dist/postgres/migrate/backfill.d.ts.map +1 -1
- package/dist/postgres/migrate/backfill.js +7 -1
- package/dist/postgres/migrate/carry.d.ts +45 -1
- package/dist/postgres/migrate/carry.d.ts.map +1 -1
- package/dist/postgres/migrate/carry.js +72 -8
- package/dist/postgres/migrate/observe.d.ts +28 -4
- package/dist/postgres/migrate/observe.d.ts.map +1 -1
- package/dist/postgres/migrate/observe.js +45 -13
- package/dist/postgres/migrate/op.d.ts +26 -1
- package/dist/postgres/migrate/op.d.ts.map +1 -1
- package/dist/postgres/migrate/op.js +3 -1
- package/dist/postgres/migrate/replication.d.ts +42 -0
- package/dist/postgres/migrate/replication.d.ts.map +1 -1
- package/dist/postgres/migrate/replication.js +53 -0
- package/dist/postgres/migrate/steps.d.ts +35 -2
- package/dist/postgres/migrate/steps.d.ts.map +1 -1
- package/dist/postgres/migrate/steps.js +158 -12
- package/dist/postgres/parser.d.ts +144 -1
- package/dist/postgres/parser.d.ts.map +1 -1
- package/dist/postgres/parser.js +370 -4
- package/dist/postgres/plan/deep.d.ts.map +1 -1
- package/dist/postgres/plan/deep.js +43 -4
- package/dist/postgres/plan/diff.d.ts.map +1 -1
- package/dist/postgres/plan/diff.js +154 -1
- package/dist/postgres/plan/normalize.d.ts +28 -2
- package/dist/postgres/plan/normalize.d.ts.map +1 -1
- package/dist/postgres/plan/normalize.js +260 -1
- package/dist/postgres/plan/rules.d.ts +4 -0
- package/dist/postgres/plan/rules.d.ts.map +1 -1
- package/dist/postgres/plan/rules.js +6 -2
- package/dist/postgres/plan/schema.js +1 -1
- package/dist/postgres/plan/statements.d.ts +26 -0
- package/dist/postgres/plan/statements.d.ts.map +1 -0
- package/dist/postgres/plan/statements.js +364 -0
- package/dist/postgres/secret-options.d.ts +10 -0
- package/dist/postgres/secret-options.d.ts.map +1 -0
- package/dist/postgres/secret-options.js +9 -0
- package/dist/postgres/testing/roundtrip-schema.d.ts +4 -2
- package/dist/postgres/testing/roundtrip-schema.d.ts.map +1 -1
- package/dist/postgres/testing/roundtrip-schema.js +24 -1
- package/dist/postgres.d.ts +1 -1
- package/dist/postgres.d.ts.map +1 -1
- package/dist/postgres.js +1 -1
- package/dist/rules/audit-entries.ts +1 -0
- package/dist/rules/postgres-names.ts +2 -0
- package/dist/rules/postgres-templates.ts +2 -2
- package/dist/rules/sqlch128.ts +230 -0
- package/dist/serializer.d.ts +5 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/serializer.js +13 -2
- package/dist/sql-types-command.d.ts +13 -0
- package/dist/sql-types-command.d.ts.map +1 -1
- package/dist/sql-types-command.js +31 -16
- package/dist/sql-types.d.ts +22 -0
- package/dist/sql-types.d.ts.map +1 -1
- package/dist/sql-types.js +10 -6
- package/dist/sqlserver/lexer.d.ts +37 -0
- package/dist/sqlserver/lexer.d.ts.map +1 -0
- package/dist/sqlserver/lexer.js +254 -0
- package/dist/sqlserver/parse.d.ts +185 -0
- package/dist/sqlserver/parse.d.ts.map +1 -0
- package/dist/sqlserver/parse.js +767 -0
- package/dist/sqlserver/translate.d.ts +34 -0
- package/dist/sqlserver/translate.d.ts.map +1 -0
- package/dist/sqlserver/translate.js +495 -0
- package/dist/sqlserver/types.d.ts +44 -0
- package/dist/sqlserver/types.d.ts.map +1 -0
- package/dist/sqlserver/types.js +133 -0
- package/dist/sqlserver/validate.d.ts +31 -0
- package/dist/sqlserver/validate.d.ts.map +1 -0
- package/dist/sqlserver/validate.js +67 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +10 -0
- package/package.json +2 -2
- package/src/change-set.ts +2 -1
- package/src/clickhouse/apply/statements.ts +25 -0
- package/src/clickhouse/import/live-export.ts +20 -5
- package/src/clickhouse/live/named-collections.e2e.test.ts +88 -0
- package/src/clickhouse/live/named-collections.ts +61 -0
- package/src/clickhouse/live/undeclarable.e2e.test.ts +5 -3
- package/src/clickhouse/live/undeclarable.ts +20 -4
- package/src/clickhouse/named-collections.test.ts +187 -0
- package/src/clickhouse/named-collections.ts +224 -0
- package/src/clickhouse/parser.ts +2 -1
- package/src/clickhouse/plan/commands.ts +17 -2
- package/src/clickhouse/plan/diff.ts +57 -1
- package/src/clickhouse/plan/disruption.ts +5 -0
- package/src/clickhouse/plan/rebuild-handoff.ts +3 -0
- package/src/clickhouse/plan/report.ts +6 -1
- package/src/clickhouse/plan/rules.ts +14 -4
- package/src/clickhouse/plan/statements.test.ts +61 -0
- package/src/clickhouse/plan/statements.ts +306 -0
- package/src/clickhouse/plan/stream-s3queue.e2e.test.ts +148 -0
- package/src/clickhouse/plan/stream.test.ts +237 -0
- package/src/clickhouse/plan/stream.ts +111 -0
- package/src/clickhouse/rebuild/observe.ts +15 -1
- package/src/clickhouse/review-catalog.test.ts +181 -0
- package/src/clickhouse/review-catalog.ts +489 -0
- package/src/codegen/classifier-page.ts +44 -4
- package/src/codegen/clickhouse-module.ts +3 -0
- package/src/codegen/coverage-page.ts +5 -3
- package/src/codegen/docs.ts +1 -1
- package/src/codegen/postgres-classifier-page.ts +2 -2
- package/src/codegen/registry.ts +10 -0
- package/src/composites/catalog.test.ts +6 -1
- package/src/composites/catalog.ts +100 -0
- package/src/composites/clickhouse.ts +2 -0
- package/src/composites/composites.test.ts +1 -1
- package/src/composites/otel-tables.test.ts +118 -0
- package/src/composites/otel-tables.ts +339 -0
- package/src/composites/stream-ingest.test.ts +114 -0
- package/src/composites/stream-ingest.ts +82 -0
- package/src/core/statement-class.ts +88 -0
- package/src/core/undeclarable.test.ts +6 -5
- package/src/coverage/clickhouse.json +1 -1
- package/src/coverage/postgres.json +12 -12
- package/src/coverage.test.ts +27 -2
- package/src/coverage.ts +18 -7
- package/src/drizzle-command.ts +94 -0
- package/src/drizzle-kit.e2e.test.ts +89 -0
- package/src/drizzle-types.e2e.test.ts +67 -0
- package/src/drizzle.test.ts +358 -0
- package/src/drizzle.ts +742 -0
- package/src/files/clickhouse.ts +8 -1
- package/src/files/common.ts +14 -1
- package/src/files/postgres.ts +34 -3
- package/src/generated/clickhouse.ts +3 -0
- package/src/generated/lexicon-sql.json +50 -0
- package/src/import-parser.sqlserver.test.ts +92 -0
- package/src/import-parser.ts +79 -5
- package/src/index.ts +1 -1
- package/src/lint/post-synth/audit-entries.ts +1 -0
- package/src/lint/post-synth/index.ts +2 -0
- package/src/lint/post-synth/postgres-checks.test.ts +8 -1
- package/src/lint/post-synth/postgres-names.ts +2 -0
- package/src/lint/post-synth/sqlch128.test.ts +107 -0
- package/src/lint/post-synth/sqlch128.ts +230 -0
- package/src/lint/rules/postgres-templates.ts +2 -2
- package/src/lsp/postgres-template.ts +20 -0
- package/src/mcp/postgres.ts +1 -1
- package/src/migrate/from-postgres/cli.test.ts +165 -0
- package/src/migrate/from-postgres/detect.ts +7 -0
- package/src/migrate/from-postgres/emit.ts +96 -0
- package/src/migrate/from-postgres/example.test.ts +205 -0
- package/src/migrate/from-postgres/index.ts +122 -0
- package/src/migrate/from-postgres/translate.test.ts +242 -0
- package/src/migrate/from-postgres/translate.ts +359 -0
- package/src/migrate/from-postgres/types.test.ts +116 -0
- package/src/migrate/from-postgres/types.ts +236 -0
- package/src/migrate/from-postgres/validate.e2e.test.ts +44 -0
- package/src/migrate/from-postgres/workspace.e2e.test.ts +114 -0
- package/src/migration-classify.test.ts +150 -0
- package/src/migration-classify.ts +45 -0
- package/src/migration-replay.e2e.test.ts +155 -0
- package/src/migration-replay.test.ts +173 -0
- package/src/migration-replay.ts +289 -0
- package/src/op/__snapshots__/watch-pipeline.test.ts.snap +2 -2
- package/src/op/activities/postgres-migration.ts +4 -0
- package/src/op/activity-contracts.ts +2 -0
- package/src/op/watch-pipeline.test.ts +8 -1
- package/src/plugin.ts +51 -2
- package/src/postgres/access/acl.ts +1 -1
- package/src/postgres/access/import.ts +1 -1
- package/src/postgres/apply/apply.ts +14 -1
- package/src/postgres/apply/statements.ts +225 -4
- package/src/postgres/constraint-names.ts +71 -0
- package/src/postgres/database-kinds.test.ts +865 -0
- package/src/postgres/entities.ts +541 -5
- package/src/postgres/entity-types.ts +10 -0
- package/src/postgres/import/generator.ts +54 -2
- package/src/postgres/import/live-export.ts +1 -0
- package/src/postgres/live/catalog-kinds.ts +494 -0
- package/src/postgres/live/catalog.ts +6 -1
- package/src/postgres/live/database-kinds.e2e.test.ts +164 -0
- package/src/postgres/live/describe-resources.ts +13 -2
- package/src/postgres/live/undeclarable.e2e.test.ts +13 -6
- package/src/postgres/migrate/backfill.ts +13 -1
- package/src/postgres/migrate/carry.ts +103 -8
- package/src/postgres/migrate/followups.e2e.test.ts +218 -0
- package/src/postgres/migrate/followups.test.ts +199 -0
- package/src/postgres/migrate/kinds.e2e.test.ts +5 -5
- package/src/postgres/migrate/observe.ts +56 -15
- package/src/postgres/migrate/op.ts +28 -1
- package/src/postgres/migrate/partition.e2e.test.ts +129 -0
- package/src/postgres/migrate/partition.test.ts +206 -0
- package/src/postgres/migrate/replication.ts +66 -0
- package/src/postgres/migrate/steps.ts +157 -12
- package/src/postgres/migrate/subscriber.e2e.test.ts +141 -0
- package/src/postgres/migrate/subscriber.test.ts +59 -0
- package/src/postgres/parser.ts +455 -4
- package/src/postgres/plan/deep.ts +42 -4
- package/src/postgres/plan/diff.ts +137 -1
- package/src/postgres/plan/normalize.ts +277 -1
- package/src/postgres/plan/rules.ts +32 -4
- package/src/postgres/plan/schema.ts +1 -1
- package/src/postgres/plan/statements.test.ts +78 -0
- package/src/postgres/plan/statements.ts +299 -0
- package/src/postgres/secret-options.ts +10 -0
- package/src/postgres/testing/roundtrip-schema.ts +24 -1
- package/src/postgres.ts +33 -0
- package/src/serializer.ts +13 -2
- package/src/skills/chant-sql-postgres-migration.md +4 -3
- package/src/skills/chant-sql-postgres.md +11 -1
- package/src/skills/chant-sql.md +2 -1
- package/src/sql-files.test.ts +33 -0
- package/src/sql-types-command.ts +38 -16
- package/src/sql-types.ts +11 -6
- package/src/sqlserver/fixture.test.ts +186 -0
- package/src/sqlserver/import-cli.test.ts +61 -0
- package/src/sqlserver/lexer.ts +260 -0
- package/src/sqlserver/parse.ts +865 -0
- package/src/sqlserver/translate.test.ts +396 -0
- package/src/sqlserver/translate.ts +558 -0
- package/src/sqlserver/types.test.ts +102 -0
- package/src/sqlserver/types.ts +154 -0
- package/src/sqlserver/validate.e2e.test.ts +32 -0
- package/src/sqlserver/validate.test.ts +49 -0
- package/src/sqlserver/validate.ts +76 -0
- package/src/validate.ts +10 -0
- package/src/workspace-kinds.test.ts +11 -0
- package/workspace-kinds.json +100 -2
|
@@ -215,7 +215,7 @@ function diffUnparsed(key: string, before: PgDiffObject, after: PgDiffObject, ou
|
|
|
215
215
|
}
|
|
216
216
|
|
|
217
217
|
/** The kinds (#3911) whose definition has no ALTER: a change is a drop and a create, or (an aggregate's) CREATE OR REPLACE. */
|
|
218
|
-
const REPLACED_KINDS: ReadonlySet<string> = new Set(["composite", "range", "aggregate", "collation", "cast", "eventTrigger"]);
|
|
218
|
+
const REPLACED_KINDS: ReadonlySet<string> = new Set(["composite", "range", "aggregate", "collation", "cast", "eventTrigger", "operator", "operatorClass", "operatorFamily", "statistics"]);
|
|
219
219
|
|
|
220
220
|
/** What each of {@link REPLACED_KINDS} compares besides its name and comment. */
|
|
221
221
|
const DEFINITION: Readonly<Record<string, readonly string[]>> = {
|
|
@@ -225,8 +225,126 @@ const DEFINITION: Readonly<Record<string, readonly string[]>> = {
|
|
|
225
225
|
collation: ["provider", "lcCollate", "lcCtype", "locale", "nondeterministic", "rules"],
|
|
226
226
|
cast: ["castMethod", "function", "context"],
|
|
227
227
|
eventTrigger: ["event", "tags", "function"],
|
|
228
|
+
operator: ["definition"],
|
|
229
|
+
// A class's members, type, family and default flag have no ALTER; a family has no definition besides its name and method (its identity).
|
|
230
|
+
operatorClass: ["default", "dataType", "family", "items"],
|
|
231
|
+
operatorFamily: [],
|
|
232
|
+
statistics: ["table", "kinds", "targets"],
|
|
228
233
|
};
|
|
229
234
|
|
|
235
|
+
/** The kinds (#3911) the server alters in place, with the fields that cannot be altered (the object is dropped and created) and those that can. */
|
|
236
|
+
const ALTERED_KINDS: Readonly<Record<string, { recreate: readonly string[]; inPlace: readonly string[]; rule?: PgClassifierRuleId }>> = {
|
|
237
|
+
foreignDataWrapper: { recreate: [], inPlace: ["handler", "validator", "options"] },
|
|
238
|
+
foreignServer: { recreate: ["serverType", "wrapper"], inPlace: ["version", "options"] },
|
|
239
|
+
publication: { recreate: ["allTables"], inPlace: ["tables", "schemas", "publish", "viaRoot", "generatedColumns"] },
|
|
240
|
+
// ALTER TEXT SEARCH DICTIONARY sets options and cannot change the template; ALTER TEXT SEARCH CONFIGURATION maps token types and cannot change the parser (SQLPG305).
|
|
241
|
+
textSearchDictionary: { recreate: ["template"], inPlace: ["options"], rule: "SQLPG305" },
|
|
242
|
+
textSearchConfiguration: { recreate: ["parser"], inPlace: ["mappings"], rule: "SQLPG305" },
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* A foreign data wrapper's, foreign server's or publication's change: the
|
|
247
|
+
* comment and each field ALTER reaches in place (SQLPG303); a field it does
|
|
248
|
+
* not reach (a server's type and wrapper, a publication's FOR ALL TABLES) by
|
|
249
|
+
* dropping and creating the object (SQLPG300), refused while something
|
|
250
|
+
* depends on it (SQLPG301).
|
|
251
|
+
*/
|
|
252
|
+
function diffAltered(key: string, before: PgDiffObject, after: PgDiffObject, out: PgChange[]): void {
|
|
253
|
+
if (!same(before.fields.comment, after.fields.comment)) out.push(change(key, "comment", "SQLPG216", show(before.fields.comment), show(after.fields.comment)));
|
|
254
|
+
const { recreate, inPlace, rule = "SQLPG303" } = ALTERED_KINDS[after.kind]!;
|
|
255
|
+
const replaced = recreate.filter((k) => !same(before.fields[k], after.fields[k]));
|
|
256
|
+
// An object that lives in a schema is another object under another name: a rename between builds is a drop and a create.
|
|
257
|
+
if (after.schema !== undefined && (before.schema !== after.schema || before.name !== after.name)) replaced.unshift("name");
|
|
258
|
+
if (replaced.length > 0) {
|
|
259
|
+
const value = (o: PgDiffObject, k: string) => (k === "name" ? qualified(o) : (show(o.fields[k]) ?? "-"));
|
|
260
|
+
const was = replaced.map((k) => `${k} ${value(before, k)}`).join("; ");
|
|
261
|
+
const now = replaced.map((k) => `${k} ${value(after, k)}`).join("; ");
|
|
262
|
+
if (before.dependents && before.dependents.length > 0) {
|
|
263
|
+
out.push(change(key, replaced.join(", "), "SQLPG301", was, now, { note: `${before.dependents.join(", ")} depend${before.dependents.length === 1 ? "s" : ""} on it` }));
|
|
264
|
+
} else out.push(change(key, replaced.join(", "), "SQLPG300", was, now));
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
for (const k of inPlace) if (!same(before.fields[k], after.fields[k])) out.push(change(key, k, rule, show(before.fields[k]), show(after.fields[k])));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Which of an operator's attributes `ALTER OPERATOR ... SET` can change from
|
|
272
|
+
* what the server holds (the Postgres 18 page, with the supported majors):
|
|
273
|
+
* RESTRICT and JOIN on every major, set or removed; from 17 COMMUTATOR and
|
|
274
|
+
* NEGATOR when the operator has none, and HASHES and MERGES when off, never
|
|
275
|
+
* back. Anything else about the definition is dropped and created.
|
|
276
|
+
*/
|
|
277
|
+
function operatorAlterable(key: string, before: Record<string, unknown>, after: Record<string, unknown>, major: number): boolean {
|
|
278
|
+
if (key === "restrict" || key === "join") return true;
|
|
279
|
+
if (major < 17) return false;
|
|
280
|
+
if (key === "commutator" || key === "negator") return before[key] === undefined && after[key] !== undefined;
|
|
281
|
+
if (key === "hashes" || key === "merges") return before[key] === undefined && after[key] === "true";
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** An operator's change: in place (SQLPG304) when every attribute that changed is one ALTER OPERATOR reaches, else dropped and created like the other kinds without an ALTER. */
|
|
286
|
+
function diffOperator(key: string, before: PgDiffObject, after: PgDiffObject, out: PgChange[], major: number): void {
|
|
287
|
+
const bo = (before.fields.definition ?? {}) as Record<string, unknown>;
|
|
288
|
+
const ao = (after.fields.definition ?? {}) as Record<string, unknown>;
|
|
289
|
+
const renamed = before.schema !== after.schema || before.name !== after.name || before.signature !== after.signature;
|
|
290
|
+
// In the order the page lists them, so the statement reads the same whatever the order the options were written in.
|
|
291
|
+
const ORDER = ["restrict", "join", "commutator", "negator", "hashes", "merges"];
|
|
292
|
+
const rank = (k: string) => (ORDER.includes(k) ? ORDER.indexOf(k) : ORDER.length);
|
|
293
|
+
const changed = [...new Set([...Object.keys(bo), ...Object.keys(ao)])].filter((k) => !same(bo[k], ao[k])).sort((a, b) => rank(a) - rank(b) || (a < b ? -1 : 1));
|
|
294
|
+
if (renamed || !changed.every((k) => operatorAlterable(k, bo, ao, major))) {
|
|
295
|
+
diffReplaced(key, before, after, out);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (!same(before.fields.comment, after.fields.comment)) out.push(change(key, "comment", "SQLPG216", show(before.fields.comment), show(after.fields.comment)));
|
|
299
|
+
const note = major < 17 ? undefined : "from 17 ALTER OPERATOR also sets a commutator's or negator's own commutator or negator";
|
|
300
|
+
for (const k of changed) out.push(change(key, `definition.${k}`, "SQLPG304", show(bo[k]), show(ao[k]), note && (k === "commutator" || k === "negator") ? { note } : {}));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* A foreign table's columns, as ALTER FOREIGN TABLE changes them (SQLPG306):
|
|
305
|
+
* all catalog changes, none reading the remote side. A column is its name
|
|
306
|
+
* (a rename is a drop and an add); its comment is the generic comment rule.
|
|
307
|
+
*/
|
|
308
|
+
function diffForeignColumns(key: string, before: PgDiffObject, after: PgDiffObject, out: PgChange[]): void {
|
|
309
|
+
const byName = new Map(before.columns.map((c) => [c.name, c]));
|
|
310
|
+
const afterNames = new Set(after.columns.map((c) => c.name));
|
|
311
|
+
for (const b of before.columns) if (!afterNames.has(b.name)) out.push(change(key, `columns.${b.name}`, "SQLPG306", `${b.name} ${b.type ?? ""}`.trim(), undefined));
|
|
312
|
+
for (const a of after.columns) {
|
|
313
|
+
const b = byName.get(a.name);
|
|
314
|
+
if (!b) {
|
|
315
|
+
out.push(change(key, `columns.${a.name}`, "SQLPG306", undefined, `${a.name} ${a.type ?? ""}`.trim()));
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
const at = `columns.${a.name}`;
|
|
319
|
+
if (b.type !== a.type) out.push(change(key, `${at}.type`, "SQLPG306", b.type, a.type));
|
|
320
|
+
if (b.collate !== a.collate) out.push(change(key, `${at}.collate`, "SQLPG306", b.collate, a.collate));
|
|
321
|
+
if (b.default !== a.default) out.push(change(key, `${at}.default`, "SQLPG306", b.default, a.default));
|
|
322
|
+
if (b.notNull !== a.notNull) out.push(change(key, `${at}.notNull`, "SQLPG306", b.notNull ? "NOT NULL" : "NULL", a.notNull ? "NOT NULL" : "NULL"));
|
|
323
|
+
if (!same(b.options, a.options)) out.push(change(key, `${at}.options`, "SQLPG306", show(b.options), show(a.options)));
|
|
324
|
+
if (b.comment !== a.comment) out.push(change(key, `${at}.comment`, "SQLPG216", b.comment, a.comment));
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** A foreign table's CHECK constraints: a dropped one first, so a constraint replaced under its name is dropped before it is added. */
|
|
329
|
+
function diffForeignConstraints(key: string, before: PgDiffObject, after: PgDiffObject, out: PgChange[]): void {
|
|
330
|
+
const label = (c: CanonicalConstraint) => (c.name ? `${c.name}: ${c.body}` : c.body);
|
|
331
|
+
const remaining = [...before.constraints];
|
|
332
|
+
const added: CanonicalConstraint[] = [];
|
|
333
|
+
const comments: PgChange[] = [];
|
|
334
|
+
for (const a of after.constraints) {
|
|
335
|
+
const i = remaining.findIndex((b) => sameConstraint(a, b));
|
|
336
|
+
if (i < 0) {
|
|
337
|
+
added.push(a);
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const b = remaining.splice(i, 1)[0]!;
|
|
341
|
+
if (b.comment !== a.comment) comments.push(change(key, `constraints.${a.name ?? b.name}.comment`, "SQLPG216", b.comment, a.comment));
|
|
342
|
+
}
|
|
343
|
+
for (const b of remaining) out.push(change(key, `constraints.${b.name ?? b.kind.toLowerCase()}`, "SQLPG306", label(b), undefined));
|
|
344
|
+
for (const a of added) out.push(change(key, `constraints.${a.name ?? a.kind.toLowerCase()}`, "SQLPG306", undefined, label(a)));
|
|
345
|
+
out.push(...comments);
|
|
346
|
+
}
|
|
347
|
+
|
|
230
348
|
/**
|
|
231
349
|
* A type's, aggregate's, collation's, cast's or event trigger's change: its
|
|
232
350
|
* comment in place; its definition by CREATE OR REPLACE AGGREGATE where that
|
|
@@ -261,6 +379,15 @@ function diffObject(key: string, before: PgDiffObject, after: PgDiffObject, out:
|
|
|
261
379
|
const [b, a] = f(name);
|
|
262
380
|
if (!same(b, a)) out.push(change(key, name, rule, show(b), show(a), extra));
|
|
263
381
|
};
|
|
382
|
+
if (after.kind === "operator") {
|
|
383
|
+
diffOperator(key, before, after, out, major);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
// A foreign table moved to another server is another table: ALTER FOREIGN TABLE has no SERVER action.
|
|
387
|
+
if (after.kind === "foreignTable" && before.schema === after.schema && before.name === after.name && !same(before.fields.server, after.fields.server)) {
|
|
388
|
+
out.push(change(key, "server", before.dependents && before.dependents.length > 0 ? "SQLPG301" : "SQLPG300", show(before.fields.server), show(after.fields.server), before.dependents && before.dependents.length > 0 ? { note: `${before.dependents.join(", ")} depend${before.dependents.length === 1 ? "s" : ""} on it` } : {}));
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
264
391
|
if (REPLACED_KINDS.has(after.kind)) {
|
|
265
392
|
// A cast is named by its types, and an aggregate's argument types are its identity: either changed, between two builds, is another object.
|
|
266
393
|
if (after.kind === "cast" && before.name !== after.name) {
|
|
@@ -270,6 +397,10 @@ function diffObject(key: string, before: PgDiffObject, after: PgDiffObject, out:
|
|
|
270
397
|
diffReplaced(key, before, after, out);
|
|
271
398
|
return;
|
|
272
399
|
}
|
|
400
|
+
if (after.kind in ALTERED_KINDS) {
|
|
401
|
+
diffAltered(key, before, after, out);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
273
404
|
if (before.schema !== after.schema || before.name !== after.name) {
|
|
274
405
|
out.push(change(key, "name", after.kind === "index" ? "SQLPG229" : "SQLPG228", qualified(before), qualified(after)));
|
|
275
406
|
}
|
|
@@ -386,6 +517,11 @@ function diffObject(key: string, before: PgDiffObject, after: PgDiffObject, out:
|
|
|
386
517
|
field("attributes", "SQLPG294");
|
|
387
518
|
field("connectionLimit", "SQLPG294");
|
|
388
519
|
break;
|
|
520
|
+
case "foreignTable":
|
|
521
|
+
diffForeignColumns(key, before, after, out);
|
|
522
|
+
diffForeignConstraints(key, before, after, out);
|
|
523
|
+
field("options", "SQLPG306");
|
|
524
|
+
break;
|
|
389
525
|
case "table":
|
|
390
526
|
if (!same(before.fields.rowSecurity, after.fields.rowSecurity) || !same(before.fields.forceRowSecurity, after.fields.forceRowSecurity)) {
|
|
391
527
|
const state = (o: PgDiffObject) => (o.fields.rowSecurity ? (o.fields.forceRowSecurity ? "enabled, forced" : "enabled") : o.fields.forceRowSecurity ? "disabled, forced" : "disabled");
|
|
@@ -51,6 +51,12 @@ import { stripMarker } from "../../core/ownership";
|
|
|
51
51
|
import { sequenceDefaults } from "../live/catalog";
|
|
52
52
|
import type {
|
|
53
53
|
AggregateArgDef,
|
|
54
|
+
OperatorProps,
|
|
55
|
+
OperatorClassProps,
|
|
56
|
+
OperatorFamilyProps,
|
|
57
|
+
ForeignTableProps,
|
|
58
|
+
TextSearchConfigurationProps,
|
|
59
|
+
TextSearchDictionaryProps,
|
|
54
60
|
AggregateProps,
|
|
55
61
|
CastProps,
|
|
56
62
|
CheckDef,
|
|
@@ -59,6 +65,10 @@ import type {
|
|
|
59
65
|
CompositeTypeProps,
|
|
60
66
|
DomainProps,
|
|
61
67
|
EventTriggerProps,
|
|
68
|
+
StatisticsProps,
|
|
69
|
+
ForeignDataWrapperProps,
|
|
70
|
+
ForeignServerProps,
|
|
71
|
+
PublicationProps,
|
|
62
72
|
RangeTypeProps,
|
|
63
73
|
EnumProps,
|
|
64
74
|
ExclusionDef,
|
|
@@ -318,6 +328,8 @@ export interface CanonicalColumn {
|
|
|
318
328
|
collate?: string;
|
|
319
329
|
compression?: string;
|
|
320
330
|
storage?: string;
|
|
331
|
+
/** A foreign table column's `OPTIONS`, sorted by name. */
|
|
332
|
+
options?: Record<string, string>;
|
|
321
333
|
comment?: string;
|
|
322
334
|
}
|
|
323
335
|
|
|
@@ -348,6 +360,16 @@ export type CanonicalKind =
|
|
|
348
360
|
| "collation"
|
|
349
361
|
| "cast"
|
|
350
362
|
| "eventTrigger"
|
|
363
|
+
| "operator"
|
|
364
|
+
| "operatorClass"
|
|
365
|
+
| "operatorFamily"
|
|
366
|
+
| "foreignTable"
|
|
367
|
+
| "textSearchConfiguration"
|
|
368
|
+
| "textSearchDictionary"
|
|
369
|
+
| "statistics"
|
|
370
|
+
| "foreignDataWrapper"
|
|
371
|
+
| "foreignServer"
|
|
372
|
+
| "publication"
|
|
351
373
|
| "function"
|
|
352
374
|
| "procedure"
|
|
353
375
|
| "trigger"
|
|
@@ -393,6 +415,16 @@ const kindOf: Record<string, CanonicalKind> = {
|
|
|
393
415
|
"Postgres::Collation": "collation",
|
|
394
416
|
"Postgres::Cast": "cast",
|
|
395
417
|
"Postgres::EventTrigger": "eventTrigger",
|
|
418
|
+
"Postgres::Operator": "operator",
|
|
419
|
+
"Postgres::OperatorClass": "operatorClass",
|
|
420
|
+
"Postgres::OperatorFamily": "operatorFamily",
|
|
421
|
+
"Postgres::ForeignTable": "foreignTable",
|
|
422
|
+
"Postgres::TextSearchConfiguration": "textSearchConfiguration",
|
|
423
|
+
"Postgres::TextSearchDictionary": "textSearchDictionary",
|
|
424
|
+
"Postgres::Statistics": "statistics",
|
|
425
|
+
"Postgres::ForeignDataWrapper": "foreignDataWrapper",
|
|
426
|
+
"Postgres::ForeignServer": "foreignServer",
|
|
427
|
+
"Postgres::Publication": "publication",
|
|
396
428
|
"Postgres::Extension": "extension",
|
|
397
429
|
"Postgres::Function": "function",
|
|
398
430
|
"Postgres::Procedure": "procedure",
|
|
@@ -617,7 +649,7 @@ function triggerFields(p: TriggerProps, defaultSchema: string, fields: Record<st
|
|
|
617
649
|
// ── Types, aggregates, collations, casts, event triggers (#3911) ──────
|
|
618
650
|
|
|
619
651
|
/** The kinds that live in no schema. */
|
|
620
|
-
const UNPLACED: ReadonlySet<CanonicalKind> = new Set(["schema", "extension", "role", "cast", "eventTrigger"]);
|
|
652
|
+
const UNPLACED: ReadonlySet<CanonicalKind> = new Set(["schema", "extension", "role", "cast", "eventTrigger", "foreignDataWrapper", "foreignServer", "publication"]);
|
|
621
653
|
|
|
622
654
|
/**
|
|
623
655
|
* A function named where an object calls it (an aggregate's SFUNC, a cast's
|
|
@@ -700,6 +732,179 @@ function collationFields(p: CollationProps, fields: Record<string, unknown>): vo
|
|
|
700
732
|
fields.rules = o.rules;
|
|
701
733
|
}
|
|
702
734
|
|
|
735
|
+
/** An operator's identity among its overloads: its operand types, `(integer,integer)`, `NONE` for a missing side. */
|
|
736
|
+
export function operatorSignature(p: Pick<OperatorProps, "leftArg" | "rightArg">, defaultSchema: string): string {
|
|
737
|
+
const side = (t: string | undefined) => (t === undefined ? "NONE" : canonicalArgType(t, defaultSchema));
|
|
738
|
+
return `(${side(p.leftArg)},${side(p.rightArg)})`;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* An operator named where another object names it (a COMMUTATOR or NEGATOR):
|
|
743
|
+
* `OPERATOR(app.===)`, `app.===` or a bare symbol, which the server looks for
|
|
744
|
+
* in the new operator's own schema, as `schema.symbol`.
|
|
745
|
+
*/
|
|
746
|
+
export function canonicalOperatorRef(text: string, ownSchema: string): string {
|
|
747
|
+
const inner = text.trim().replace(/^OPERATOR\s*\(\s*(.*?)\s*\)$/is, "$1");
|
|
748
|
+
const m = /^("(?:[^"]|"")+"|[A-Za-z_][A-Za-z0-9_$]*)\s*\.\s*(\S.*)$/s.exec(inner);
|
|
749
|
+
return `${quoteIdent(m ? identValue(m[1]!) : ownSchema)}.${(m ? m[2]! : inner).trim()}`;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/** An operator's definition as compared: each function and operator named canonically, a flag only when set. */
|
|
753
|
+
function operatorOptions(p: OperatorProps, ownSchema: string, defaultSchema: string): Record<string, string> {
|
|
754
|
+
const out: Record<string, string> = {};
|
|
755
|
+
for (const [k, v] of Object.entries(p.options)) {
|
|
756
|
+
if (k === "function" || k === "restrict" || k === "join") out[k] = canonicalFunctionName(v, defaultSchema);
|
|
757
|
+
else if (k === "commutator" || k === "negator") out[k] = canonicalOperatorRef(v, ownSchema);
|
|
758
|
+
else if (k === "hashes" || k === "merges") {
|
|
759
|
+
if (isTrue(v)) out[k] = "true";
|
|
760
|
+
} else out[k] = defValue(v);
|
|
761
|
+
}
|
|
762
|
+
return Object.fromEntries(Object.entries(out).sort(([a], [b]) => (a < b ? -1 : 1)));
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* A name where an object is named (a text search parser, template or
|
|
767
|
+
* dictionary, an operator family): the schema left off when it is
|
|
768
|
+
* `pg_catalog` or the default schema, since a bare name is looked for in both.
|
|
769
|
+
* The catalog prints every schema, so both sides are brought to this form.
|
|
770
|
+
*/
|
|
771
|
+
export function canonicalRef(text: string, defaultSchema: string): string {
|
|
772
|
+
const pieces = canonicalName(text).split(/\.(?=(?:[^"]*"[^"]*")*[^"]*$)/);
|
|
773
|
+
if (pieces.length >= 2) {
|
|
774
|
+
const schema = identValue(pieces[0]!);
|
|
775
|
+
return schema === "pg_catalog" || schema === defaultSchema ? pieces.slice(1).join(".") : pieces.join(".");
|
|
776
|
+
}
|
|
777
|
+
return pieces[0]!;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/** An operator named in an operator class: `OPERATOR(app.<<)`, `app.<<` or a bare symbol, by `canonicalRef`'s rule for the schema. */
|
|
781
|
+
export function canonicalOperatorName(text: string, defaultSchema: string): string {
|
|
782
|
+
const inner = text.trim().replace(/^OPERATOR\s*\(\s*(.*?)\s*\)$/is, "$1").trim();
|
|
783
|
+
const m = /^("(?:[^"]|"")+"|[A-Za-z_][A-Za-z0-9_$]*)\s*\.\s*(\S.*)$/s.exec(inner);
|
|
784
|
+
if (!m) return inner;
|
|
785
|
+
const schema = identValue(m[1]!);
|
|
786
|
+
return schema === "pg_catalog" || schema === defaultSchema ? m[2]!.trim() : `${quoteIdent(schema)}.${m[2]!.trim()}`;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/** A comma list split at its top level: `numeric(12,2), text` is two items. */
|
|
790
|
+
function splitTopLevel(text: string): string[] {
|
|
791
|
+
const out: string[] = [];
|
|
792
|
+
let depth = 0;
|
|
793
|
+
let current = "";
|
|
794
|
+
for (const ch of text) {
|
|
795
|
+
if (ch === "(" || ch === "[") depth++;
|
|
796
|
+
else if (ch === ")" || ch === "]") depth--;
|
|
797
|
+
if (ch === "," && depth === 0) {
|
|
798
|
+
out.push(current.trim());
|
|
799
|
+
current = "";
|
|
800
|
+
} else current += ch;
|
|
801
|
+
}
|
|
802
|
+
if (current.trim() !== "") out.push(current.trim());
|
|
803
|
+
return out;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* The operand types a support function of an operator class is registered
|
|
808
|
+
* for when `FUNCTION n (left, right)` does not say (sql-createopclass): a
|
|
809
|
+
* B-tree comparison function's (1) and a hash function's (1, 2) are the
|
|
810
|
+
* function's own input types, a B-tree `in_range` function's (3) its first
|
|
811
|
+
* and third, and every other support function's the class's type. The catalog
|
|
812
|
+
* always stores both, so a declaration is brought to them.
|
|
813
|
+
*/
|
|
814
|
+
function supportTypes(method: string, number: number, classType: string, args: readonly string[]): [string, string] {
|
|
815
|
+
if (method === "btree" && number === 1) return [args[0] ?? classType, args[1] ?? args[0] ?? classType];
|
|
816
|
+
if (method === "btree" && number === 3) return [args[0] ?? classType, args[2] ?? classType];
|
|
817
|
+
if (method === "hash" && (number === 1 || number === 2)) return [args[0] ?? classType, args[0] ?? classType];
|
|
818
|
+
return [classType, classType];
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/** An operator class as compared: its type, family, default flag and members, each member canonical and the list sorted. */
|
|
822
|
+
function operatorClassFields(p: OperatorClassProps, schema: string, defaultSchema: string, fields: Record<string, unknown>): void {
|
|
823
|
+
const type = canonicalType(p.dataType, defaultSchema)!;
|
|
824
|
+
fields.default = p.default === true;
|
|
825
|
+
fields.dataType = type;
|
|
826
|
+
// A class with no FAMILY is in a family of its own name; so is one that names that family.
|
|
827
|
+
const own = canonicalRef(`${quoteIdent(schema)}.${quoteIdent(p.name)}`, defaultSchema);
|
|
828
|
+
const named = p.family === undefined ? own : /^\s*("(?:[^"]|"")+"|[A-Za-z_][A-Za-z0-9_$]*)\s*$/.test(p.family) && identValue(p.family.trim()) === p.name ? own : canonicalRef(p.family, defaultSchema);
|
|
829
|
+
fields.family = named === own ? undefined : named;
|
|
830
|
+
const side = (t: string) => (t.trim().toUpperCase() === "NONE" ? "NONE" : canonicalArgType(t, defaultSchema));
|
|
831
|
+
const members = p.items.map((i): { rank: number; number: number; text: string } => {
|
|
832
|
+
if (i.kind === "storage") return { rank: 2, number: 0, text: `storage ${canonicalType(i.type, defaultSchema)}` };
|
|
833
|
+
const n = Number(i.number);
|
|
834
|
+
if (i.kind === "operator") {
|
|
835
|
+
const [l, r] = i.types !== undefined ? splitTopLevel(i.types) : [type, type];
|
|
836
|
+
const sorted = i.orderBy !== undefined ? ` for order by ${canonicalRef(i.orderBy, defaultSchema)}` : "";
|
|
837
|
+
return { rank: 0, number: n, text: `operator ${n} ${canonicalOperatorName(i.operator, defaultSchema)} (${side(l ?? type)},${side(r ?? l ?? type)})${sorted}` };
|
|
838
|
+
}
|
|
839
|
+
const args = splitTopLevel(i.args).map((a) => canonicalArgType(a, defaultSchema));
|
|
840
|
+
const given = i.types !== undefined ? splitTopLevel(i.types).map(side) : undefined;
|
|
841
|
+
const [l, r] = given ? [given[0]!, given[1] ?? given[0]!] : supportTypes(p.method, n, type, args);
|
|
842
|
+
return { rank: 1, number: n, text: `function ${n} (${l},${r}) ${canonicalFunctionName(i.function, defaultSchema)}(${args.join(",")})` };
|
|
843
|
+
});
|
|
844
|
+
members.sort((a, b) => a.rank - b.rank || a.number - b.number || (a.text < b.text ? -1 : 1));
|
|
845
|
+
fields.items = members.map((m) => m.text);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/** A dictionary option's value as compared: a string constant's value, a quoted name's, a bare word folded, a number as written. */
|
|
849
|
+
function dictionaryValue(v: string): string {
|
|
850
|
+
const t = v.trim();
|
|
851
|
+
if (/^([Ee]?'|\$)/.test(t)) return stringValue(t) ?? t;
|
|
852
|
+
if (/^"/.test(t)) return identValue(t);
|
|
853
|
+
return t.toLowerCase();
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/** A text search configuration's mappings as compared: each token type to its dictionaries in order, sorted by token type. */
|
|
857
|
+
function mappingFields(p: TextSearchConfigurationProps, defaultSchema: string): Record<string, string[]> | undefined {
|
|
858
|
+
const out: Record<string, string[]> = {};
|
|
859
|
+
for (const m of p.mappings) for (const t of m.tokens) out[t.toLowerCase()] = m.dictionaries.map((d) => canonicalRef(d, defaultSchema));
|
|
860
|
+
const entries = Object.entries(out).sort(([a], [b]) => (a < b ? -1 : 1));
|
|
861
|
+
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/** An `OPTIONS (...)` list as compared: by name, none as nothing. */
|
|
865
|
+
function sortedOptions(options: Record<string, string> | undefined): Record<string, string> | undefined {
|
|
866
|
+
const entries = Object.entries(options ?? {}).sort(([a], [b]) => (a < b ? -1 : 1));
|
|
867
|
+
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
const STATISTICS_ALL = ["dependencies", "mcv", "ndistinct"];
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* The kinds of statistics gathered, sorted. Left out, the server builds all
|
|
874
|
+
* three, except for a lone expression, which gets none of them (only the
|
|
875
|
+
* expression's own statistics); `pg_get_statisticsobjdef` prints the list only
|
|
876
|
+
* when it is not that default.
|
|
877
|
+
*/
|
|
878
|
+
export function statisticsKinds(kinds: readonly string[] | undefined, targets: readonly string[]): string[] | undefined {
|
|
879
|
+
if (kinds && kinds.length > 0) {
|
|
880
|
+
const sorted = [...new Set(kinds.map((k) => k.toLowerCase()))].sort();
|
|
881
|
+
return sorted.join() === STATISTICS_ALL.join() ? undefined : sorted;
|
|
882
|
+
}
|
|
883
|
+
const lone = targets.length === 1 && !/^"?[A-Za-z_][A-Za-z0-9_$]*"?$/.test(targets[0]!.trim());
|
|
884
|
+
return lone ? ["none"] : undefined;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/** Every action a publication publishes when `publish` is not given. */
|
|
888
|
+
const PUBLISH_ALL = ["delete", "insert", "truncate", "update"];
|
|
889
|
+
|
|
890
|
+
/** A publication as compared: its tables with their column lists and row filters, its schemas, and each option at its default left out. */
|
|
891
|
+
function publicationFields(p: PublicationProps, defaultSchema: string, fields: Record<string, unknown>): void {
|
|
892
|
+
fields.allTables = p.allTables === true;
|
|
893
|
+
const tables = p.tables.map((t) => ({
|
|
894
|
+
table: canonicalName(t.tableName, defaultSchema),
|
|
895
|
+
...(t.columns && t.columns.length > 0 ? { columns: [...t.columns].sort() } : {}),
|
|
896
|
+
...(t.where ? { where: canonicalExpr(t.where) } : {}),
|
|
897
|
+
}));
|
|
898
|
+
fields.tables = tables.length > 0 ? tables.sort((a, b) => (a.table < b.table ? -1 : 1)) : undefined;
|
|
899
|
+
fields.schemas = p.schemas.length > 0 ? p.schemas.map((s) => quoteIdent(identValue(s))).sort() : undefined;
|
|
900
|
+
const o = p.options;
|
|
901
|
+
const publish = o.publish === undefined ? PUBLISH_ALL : [...new Set(defValue(o.publish).split(",").map((x) => x.trim().toLowerCase()).filter(Boolean))].sort();
|
|
902
|
+
fields.publish = publish.join() === PUBLISH_ALL.join() ? undefined : publish;
|
|
903
|
+
fields.viaRoot = isTrue(o.publish_via_partition_root);
|
|
904
|
+
const generated = o.publish_generated_columns === undefined ? "none" : defValue(o.publish_generated_columns);
|
|
905
|
+
fields.generatedColumns = generated === "none" ? undefined : generated;
|
|
906
|
+
}
|
|
907
|
+
|
|
703
908
|
/** A cast's name, `(source AS target)`, its types canonical. */
|
|
704
909
|
export const castName = (p: Pick<CastProps, "sourceType" | "targetType">, defaultSchema: string): string =>
|
|
705
910
|
`(${canonicalType(p.sourceType, defaultSchema)} AS ${canonicalType(p.targetType, defaultSchema)})`;
|
|
@@ -774,6 +979,77 @@ export function canonicalPgObject(entityType: string, props: Record<string, unkn
|
|
|
774
979
|
fields.function = canonicalFunctionName(p.functionName, defaultSchema);
|
|
775
980
|
break;
|
|
776
981
|
}
|
|
982
|
+
case "operator": {
|
|
983
|
+
const p = props as unknown as OperatorProps;
|
|
984
|
+
base.signature = operatorSignature(p, defaultSchema);
|
|
985
|
+
fields.definition = operatorOptions(p, base.schema!, defaultSchema);
|
|
986
|
+
break;
|
|
987
|
+
}
|
|
988
|
+
case "operatorClass":
|
|
989
|
+
operatorClassFields(props as unknown as OperatorClassProps, base.schema!, defaultSchema, fields);
|
|
990
|
+
base.signature = ` USING ${(props as unknown as OperatorClassProps).method}`;
|
|
991
|
+
break;
|
|
992
|
+
case "operatorFamily":
|
|
993
|
+
base.signature = ` USING ${(props as unknown as OperatorFamilyProps).method}`;
|
|
994
|
+
break;
|
|
995
|
+
case "textSearchDictionary": {
|
|
996
|
+
const p = props as unknown as TextSearchDictionaryProps;
|
|
997
|
+
fields.template = canonicalRef(p.template, defaultSchema);
|
|
998
|
+
fields.options = sortedOptions(Object.fromEntries(Object.entries(p.options).map(([k, v]) => [k.toLowerCase(), dictionaryValue(v)])));
|
|
999
|
+
break;
|
|
1000
|
+
}
|
|
1001
|
+
case "textSearchConfiguration": {
|
|
1002
|
+
const p = props as unknown as TextSearchConfigurationProps;
|
|
1003
|
+
fields.parser = canonicalRef(p.parser, defaultSchema);
|
|
1004
|
+
fields.mappings = mappingFields(p, defaultSchema);
|
|
1005
|
+
break;
|
|
1006
|
+
}
|
|
1007
|
+
case "foreignTable": {
|
|
1008
|
+
const p = props as unknown as ForeignTableProps;
|
|
1009
|
+
p.columns.forEach((c: ColumnDef, position) => {
|
|
1010
|
+
const options = sortedOptions(c.options);
|
|
1011
|
+
columns.push({
|
|
1012
|
+
name: c.name,
|
|
1013
|
+
position,
|
|
1014
|
+
type: canonicalType(c.type, defaultSchema),
|
|
1015
|
+
notNull: c.notNull === true,
|
|
1016
|
+
default: canonicalExpr(c.default),
|
|
1017
|
+
collate: c.collate ? canonicalName(c.collate) : undefined,
|
|
1018
|
+
comment: comment(c.comment),
|
|
1019
|
+
...(options ? { options } : {}),
|
|
1020
|
+
});
|
|
1021
|
+
});
|
|
1022
|
+
for (const c of p.checks) constraints.push({ kind: "CHECK", name: c.name, body: ["check", canonicalExpr(c.expr), c.noInherit ? "no inherit" : ""].filter(Boolean).join(" "), ...(c.notValid ? { notValid: true } : {}), comment: comment(c.comment) });
|
|
1023
|
+
fields.server = quoteIdent(p.serverName);
|
|
1024
|
+
fields.options = sortedOptions(p.options);
|
|
1025
|
+
break;
|
|
1026
|
+
}
|
|
1027
|
+
case "statistics": {
|
|
1028
|
+
const p = props as unknown as StatisticsProps;
|
|
1029
|
+
fields.table = canonicalName(p.tableName, defaultSchema);
|
|
1030
|
+
const targets = p.targets.map((t) => canonicalExpr(t)!).sort();
|
|
1031
|
+
fields.targets = targets;
|
|
1032
|
+
fields.kinds = statisticsKinds(p.kinds, p.targets);
|
|
1033
|
+
break;
|
|
1034
|
+
}
|
|
1035
|
+
case "foreignDataWrapper": {
|
|
1036
|
+
const p = props as unknown as ForeignDataWrapperProps;
|
|
1037
|
+
fields.handler = p.handlerName && !p.noHandler ? canonicalFunctionName(p.handlerName, defaultSchema) : undefined;
|
|
1038
|
+
fields.validator = p.validatorName && !p.noValidator ? canonicalFunctionName(p.validatorName, defaultSchema) : undefined;
|
|
1039
|
+
fields.options = sortedOptions(p.options);
|
|
1040
|
+
break;
|
|
1041
|
+
}
|
|
1042
|
+
case "foreignServer": {
|
|
1043
|
+
const p = props as unknown as ForeignServerProps;
|
|
1044
|
+
fields.serverType = p.serverType;
|
|
1045
|
+
fields.version = p.version;
|
|
1046
|
+
fields.wrapper = quoteIdent(identValue(p.wrapperName));
|
|
1047
|
+
fields.options = sortedOptions(p.options);
|
|
1048
|
+
break;
|
|
1049
|
+
}
|
|
1050
|
+
case "publication":
|
|
1051
|
+
publicationFields(props as unknown as PublicationProps, defaultSchema, fields);
|
|
1052
|
+
break;
|
|
777
1053
|
case "function":
|
|
778
1054
|
case "procedure": {
|
|
779
1055
|
const p = props as unknown as RoutineProps;
|
|
@@ -360,15 +360,15 @@ export const PG_CLASSIFIER_RULES = {
|
|
|
360
360
|
SQLPG300: rule(
|
|
361
361
|
"SQLPG300",
|
|
362
362
|
"metadata",
|
|
363
|
-
"Drop and create a type, aggregate, collation, cast or
|
|
364
|
-
"A composite or range type's definition, a collation's, a cast's
|
|
363
|
+
"Drop and create a type, aggregate, collation, cast, event trigger, operator, operator class or family, foreign table's server, text search object, statistics object, foreign server or publication",
|
|
364
|
+
"A composite or range type's definition, a collation's, a cast's, an event trigger's, an operator's function and operands, and any attribute of it that SQLPG304 does not alter, an operator class's members, type, family and default flag, an operator family's index method, and extended statistics' have no ALTER (ALTER OPERATOR CLASS and ALTER OPERATOR FAMILY change a name, an owner or a schema only), an aggregate's argument types, state type and final function cannot be replaced, and neither can a foreign server's type or wrapper, a foreign table's server, a text search configuration's parser, a text search dictionary's template or a publication's FOR ALL TABLES: the object is dropped and created in one transaction. Nothing on the server depends on it (a column, a view, an index, a function or a cast would make the DROP fail); the locks are on its catalog rows only. Dropped statistics are empty until the next ANALYZE, and a publication that is dropped stops its subscribers until it is created again.",
|
|
365
365
|
`${DOCS}/sql-createtype.html`,
|
|
366
366
|
),
|
|
367
367
|
SQLPG301: rule(
|
|
368
368
|
"SQLPG301",
|
|
369
369
|
"expand",
|
|
370
|
-
"Change a type, aggregate, collation or
|
|
371
|
-
"No ALTER makes the change, and DROP refuses while a column, a view, a function or another object depends on the object. Create the new definition under another name, move what depends on it, then drop the old.",
|
|
370
|
+
"Change a type, aggregate, collation, cast, operator, operator class, text search object, foreign table's server, foreign server or publication that other objects depend on",
|
|
371
|
+
"No ALTER makes the change, and DROP refuses while a column, a view, an index, a function or another object depends on the object. Create the new definition under another name, move what depends on it, then drop the old.",
|
|
372
372
|
`${DOCS}/sql-droptype.html`,
|
|
373
373
|
),
|
|
374
374
|
SQLPG302: rule(
|
|
@@ -378,6 +378,34 @@ export const PG_CLASSIFIER_RULES = {
|
|
|
378
378
|
"CREATE OR REPLACE AGGREGATE (14 and later) replaces the aggregate's functions and options in the catalog; no table is locked. Its argument types, its result and its kind (plain, ordered-set, hypothetical-set) have to stay the same, so a change of the state type or the final function is dropped and created instead (SQLPG300).",
|
|
379
379
|
`${DOCS}/sql-createaggregate.html`,
|
|
380
380
|
),
|
|
381
|
+
SQLPG303: rule(
|
|
382
|
+
"SQLPG303",
|
|
383
|
+
"metadata",
|
|
384
|
+
"Alter a foreign data wrapper, foreign server or publication in place",
|
|
385
|
+
"ALTER FOREIGN DATA WRAPPER (handler, validator, options), ALTER SERVER (version, options) and ALTER PUBLICATION (SET options, ADD and DROP TABLE and TABLES IN SCHEMA) change the object's catalog rows under a brief lock; no table is rewritten. A publication's change reaches its subscribers as the walsender next reads it: a table added is not copied to a subscription until the subscriber runs REFRESH PUBLICATION.",
|
|
386
|
+
`${DOCS}/sql-alterpublication.html`,
|
|
387
|
+
),
|
|
388
|
+
SQLPG304: rule(
|
|
389
|
+
"SQLPG304",
|
|
390
|
+
"metadata",
|
|
391
|
+
"Alter an operator in place",
|
|
392
|
+
"ALTER OPERATOR ... SET changes the operator's catalog row under a brief lock; no index or table is read. RESTRICT and JOIN can be set or removed (NONE) on every supported major. From 17, COMMUTATOR and NEGATOR can be set when the operator has none, and HASHES and MERGES turned on, never off (the Postgres 18 page: \"Can only be changed if the operator does not have an existing commutator\", \"Can only be enabled and not disabled\"); setting a commutator or negator also sets the other operator's. On 14 to 16 only RESTRICT and JOIN are alterable, so any other change drops and creates the operator (SQLPG300), as does a change of function or operands on any major.",
|
|
393
|
+
`${DOCS}/sql-alteroperator.html`,
|
|
394
|
+
),
|
|
395
|
+
SQLPG305: rule(
|
|
396
|
+
"SQLPG305",
|
|
397
|
+
"metadata",
|
|
398
|
+
"Alter a text search configuration's mappings or a dictionary's options in place",
|
|
399
|
+
"ALTER TEXT SEARCH CONFIGURATION ADD MAPPING, ALTER MAPPING and DROP MAPPING change the token types mapped to dictionaries; ALTER TEXT SEARCH DICTIONARY sets a template's options, and an option named with no value is removed. Both change catalog rows under a brief lock and leave the parser or template as it is, so a parser or template change is dropped and created (SQLPG300). A tsvector already stored keeps the tokens it was built with: rebuild the columns and indexes that hold one when the mappings or options change what a word becomes. The same applies on 14 to 18.",
|
|
400
|
+
`${DOCS}/sql-altertsconfig.html`,
|
|
401
|
+
),
|
|
402
|
+
SQLPG306: rule(
|
|
403
|
+
"SQLPG306",
|
|
404
|
+
"metadata",
|
|
405
|
+
"Alter a foreign table in place",
|
|
406
|
+
"ALTER FOREIGN TABLE adds and drops columns, changes a column's type, default, NOT NULL and OPTIONS, adds and drops CHECK and NOT NULL constraints, and sets the table's OPTIONS, all in the catalog. Nothing is checked against the remote side and no row is rewritten or scanned (\"It is the user's responsibility to ensure that the table definition matches the remote side\"), so a CHECK is added with no validation pass. A foreign table's server cannot be altered, so a change of server is dropped and created (SQLPG300).",
|
|
407
|
+
`${DOCS}/sql-alterforeigntable.html`,
|
|
408
|
+
),
|
|
381
409
|
SQLPG270: rule("SQLPG270", "drop", "Drop an object", "The object and, for a table, a materialized view or a sequence, its data are gone.", `${DOCS}/sql-droptable.html`),
|
|
382
410
|
} as const satisfies Record<string, PgClassifierRule>;
|
|
383
411
|
|
|
@@ -106,7 +106,7 @@ export function pgSchemaFromBuildFile(path: string, defaultSchema = "public"): P
|
|
|
106
106
|
*/
|
|
107
107
|
export function pgNamespace(kind: string): string {
|
|
108
108
|
if (kind === "schema" || kind === "extension" || kind === "trigger" || kind === "policy" || kind === "role") return kind;
|
|
109
|
-
if (kind === "collation" || kind === "cast" || kind === "eventTrigger") return kind;
|
|
109
|
+
if (kind === "collation" || kind === "cast" || kind === "eventTrigger" || kind === "operator" || kind === "operatorClass" || kind === "operatorFamily" || kind === "textSearchConfiguration" || kind === "textSearchDictionary" || kind === "statistics" || kind === "foreignDataWrapper" || kind === "foreignServer" || kind === "publication") return kind;
|
|
110
110
|
if (kind === "grant" || kind === "defaultPrivileges") return "grant";
|
|
111
111
|
if (kind === "enum" || kind === "domain" || kind === "composite" || kind === "range") return "type";
|
|
112
112
|
if (kind === "function" || kind === "procedure" || kind === "aggregate") return "routine";
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { classifyPgStatements } from "./statements";
|
|
3
|
+
|
|
4
|
+
const one = (sql: string) => classifyPgStatements([sql])[0]!;
|
|
5
|
+
const rules = (sql: string) => one(sql).changes.map((c) => c.rule);
|
|
6
|
+
|
|
7
|
+
describe("Postgres statements", () => {
|
|
8
|
+
test("CREATE INDEX without CONCURRENTLY is SQLPG241 with its SHARE lock", () => {
|
|
9
|
+
const v = one("CREATE INDEX orders_status_idx ON app.orders (status)");
|
|
10
|
+
expect(v.outcome).toBe("classified");
|
|
11
|
+
expect(v.changes).toMatchObject([{ rule: "SQLPG241", class: "concurrently", lock: "SHARE" }]);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("CREATE INDEX CONCURRENTLY is SQLPG240", () => {
|
|
15
|
+
expect(one("CREATE UNIQUE INDEX CONCURRENTLY i ON t (a)").changes).toMatchObject([{ rule: "SQLPG240", lock: "SHARE UPDATE EXCLUSIVE" }]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("an index on a table the file creates needs no CONCURRENTLY", () => {
|
|
19
|
+
const [t, i] = classifyPgStatements(["CREATE TABLE t (id bigint PRIMARY KEY, a int)", "CREATE INDEX i ON t (a)"]);
|
|
20
|
+
expect(t!.changes[0]!.rule).toBe("SQLPG200");
|
|
21
|
+
expect(i!.changes[0]!.rule).toBe("SQLPG200");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("ADD COLUMN splits by what the table must compute", () => {
|
|
25
|
+
expect(rules("ALTER TABLE t ADD COLUMN a int")).toEqual(["SQLPG201"]);
|
|
26
|
+
expect(rules("ALTER TABLE t ADD COLUMN a int NOT NULL DEFAULT 0")).toEqual(["SQLPG201"]);
|
|
27
|
+
expect(rules("ALTER TABLE t ADD COLUMN a int NOT NULL")).toEqual(["SQLPG203"]);
|
|
28
|
+
expect(rules("ALTER TABLE t ADD COLUMN a uuid DEFAULT gen_random_uuid()")).toEqual(["SQLPG202"]);
|
|
29
|
+
expect(rules("ALTER TABLE t ADD COLUMN a int GENERATED ALWAYS AS (b + 1) STORED")).toEqual(["SQLPG202"]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("a column type change is undecided between SQLPG206 and SQLPG207", () => {
|
|
33
|
+
const v = one("ALTER TABLE t ALTER COLUMN a TYPE bigint");
|
|
34
|
+
expect(v.outcome).toBe("undecided");
|
|
35
|
+
expect(v.candidates!.map((c) => c.rule)).toEqual(["SQLPG206", "SQLPG207"]);
|
|
36
|
+
expect(one("ALTER TABLE t ALTER COLUMN a TYPE int USING a::int").candidates!.map((c) => c.rule)).toEqual(["SQLPG206", "SQLPG207", "SQLPG208"]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("several actions in one ALTER TABLE are each classified", () => {
|
|
40
|
+
expect(rules("ALTER TABLE t ADD COLUMN a int, DROP COLUMN b, ALTER COLUMN c SET NOT NULL")).toEqual(["SQLPG201", "SQLPG204", "SQLPG210"]);
|
|
41
|
+
expect(one("ALTER TABLE t DROP COLUMN b").changes[0]!.destructive).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("constraints", () => {
|
|
45
|
+
expect(rules("ALTER TABLE t ADD CONSTRAINT fk FOREIGN KEY (a) REFERENCES u (id) NOT VALID")).toEqual(["SQLPG217"]);
|
|
46
|
+
expect(rules("ALTER TABLE t ADD CONSTRAINT fk FOREIGN KEY (a) REFERENCES u (id)")).toEqual(["SQLPG219"]);
|
|
47
|
+
expect(rules("ALTER TABLE t ADD CONSTRAINT c CHECK (a > 0)")).toEqual(["SQLPG218"]);
|
|
48
|
+
expect(rules("ALTER TABLE t ADD PRIMARY KEY (id)")).toEqual(["SQLPG221"]);
|
|
49
|
+
expect(rules("ALTER TABLE t VALIDATE CONSTRAINT fk")).toEqual(["SQLPG220"]);
|
|
50
|
+
expect(rules("ALTER TABLE t DROP CONSTRAINT fk")).toEqual(["SQLPG223"]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("renames", () => {
|
|
54
|
+
expect(rules("ALTER TABLE t RENAME COLUMN a TO b")).toEqual(["SQLPG205"]);
|
|
55
|
+
expect(rules("ALTER TABLE t RENAME TO u")).toEqual(["SQLPG228"]);
|
|
56
|
+
expect(rules("ALTER INDEX i RENAME TO j")).toEqual(["SQLPG229"]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("DROP, GRANT, COMMENT and row security go through the parser or the verb", () => {
|
|
60
|
+
expect(rules("DROP INDEX CONCURRENTLY i")).toEqual(["SQLPG242"]);
|
|
61
|
+
expect(rules("DROP TABLE t")).toEqual(["SQLPG270"]);
|
|
62
|
+
expect(rules("GRANT SELECT ON TABLE t TO reader")).toEqual(["SQLPG296"]);
|
|
63
|
+
expect(rules("REVOKE SELECT ON TABLE t FROM reader")).toEqual(["SQLPG297"]);
|
|
64
|
+
expect(rules("COMMENT ON TABLE t IS 'x'")).toEqual(["SQLPG216"]);
|
|
65
|
+
expect(rules("ALTER TABLE t ENABLE ROW LEVEL SECURITY")).toEqual(["SQLPG293"]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("functions and views", () => {
|
|
69
|
+
expect(rules("CREATE OR REPLACE FUNCTION f() RETURNS int LANGUAGE sql AS $$ select 1; $$")).toEqual(["SQLPG280"]);
|
|
70
|
+
expect(one("CREATE OR REPLACE VIEW v AS SELECT 1 AS a").candidates!.map((c) => c.rule)).toEqual(["SQLPG250", "SQLPG251"]);
|
|
71
|
+
expect(rules("CREATE VIEW v AS SELECT 1 AS a")).toEqual(["SQLPG200"]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("data statements and unknown ALTERs", () => {
|
|
75
|
+
expect(one("UPDATE t SET a = 1").outcome).toBe("data");
|
|
76
|
+
expect(one("ALTER TABLE t OWNER TO app").outcome).toBe("unclassified");
|
|
77
|
+
});
|
|
78
|
+
});
|