@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
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extended statistics, foreign data wrappers, foreign servers and
|
|
3
|
+
* publications against a live server (#3911): applied as declared, they plan
|
|
4
|
+
* with no change and read back with no drift; a definition changed in the
|
|
5
|
+
* declaration is altered in place (SQLPG303) or dropped and created
|
|
6
|
+
* (SQLPG300); an import writes each back as the declaration it came from,
|
|
7
|
+
* which plans with no change.
|
|
8
|
+
*
|
|
9
|
+
* The server: a throwaway one at the pin, skipped cleanly when Docker is not
|
|
10
|
+
* available. A foreign data wrapper takes a superuser, which the throwaway
|
|
11
|
+
* server's user is.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
18
|
+
import { dockerAvailable, startTestPostgres, type TestPostgres } from "../testing/server";
|
|
19
|
+
import { connectPostgres, type PostgresClient, type PostgresEndpoint } from "./client";
|
|
20
|
+
import { readLiveSchema } from "./catalog";
|
|
21
|
+
import { planPgAgainstServer } from "../plan/commands";
|
|
22
|
+
import { observeResourcesDeep, liveProps } from "../plan/deep";
|
|
23
|
+
import { postgresApply } from "../../op/activities/postgres-apply";
|
|
24
|
+
import { objectsToIR } from "../import/ir";
|
|
25
|
+
import { PostgresGenerator, postgresTemplates } from "../import/generator";
|
|
26
|
+
import { declaredObjects } from "../apply/apply";
|
|
27
|
+
|
|
28
|
+
const SCHEMA = "chant_e2e_3911b";
|
|
29
|
+
const enabled = await dockerAvailable();
|
|
30
|
+
const dir = mkdtempSync(join(tmpdir(), "chant-pg-database-kinds-"));
|
|
31
|
+
let server: TestPostgres | undefined;
|
|
32
|
+
let endpoint: PostgresEndpoint;
|
|
33
|
+
let admin: PostgresClient;
|
|
34
|
+
|
|
35
|
+
beforeAll(async () => {
|
|
36
|
+
if (!enabled) return;
|
|
37
|
+
server = await startTestPostgres();
|
|
38
|
+
endpoint = server.endpoint();
|
|
39
|
+
admin = await connectPostgres(endpoint);
|
|
40
|
+
}, 600_000);
|
|
41
|
+
|
|
42
|
+
afterAll(async () => {
|
|
43
|
+
await admin?.end();
|
|
44
|
+
await server?.stop();
|
|
45
|
+
rmSync(dir, { recursive: true, force: true });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
interface Obj {
|
|
49
|
+
export: string;
|
|
50
|
+
type: string;
|
|
51
|
+
ddl: string;
|
|
52
|
+
dependsOn?: string[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const MARKER = { stack: "e2e3911b", env: "test" };
|
|
56
|
+
const profile = () => ({
|
|
57
|
+
config: { ownership: MARKER, sql: { profiles: { e2e: { url: endpoint.url, password: { env: "PG_E2E_PASSWORD" }, schemas: [SCHEMA] } } } },
|
|
58
|
+
env: { PG_E2E_PASSWORD: endpoint.password ?? "" },
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
function writeBuild(file: string, objects: Obj[]): string {
|
|
62
|
+
const path = join(dir, file);
|
|
63
|
+
writeFileSync(path, JSON.stringify({ dialect: "postgres", postgresMajor: 18, objects: objects.map((o) => ({ dependsOn: [], ...o })) }));
|
|
64
|
+
return path;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const apply = (path: string) => postgresApply({ buildPath: path, environment: "e2e" }, undefined, { ...profile(), log: () => undefined });
|
|
68
|
+
const plan = (path: string) => planPgAgainstServer("e2e", path, profile());
|
|
69
|
+
|
|
70
|
+
const S = SCHEMA;
|
|
71
|
+
interface Version {
|
|
72
|
+
/** The statistics' kinds. */
|
|
73
|
+
kinds: string;
|
|
74
|
+
/** The wrapper's options. */
|
|
75
|
+
fdwOptions: string;
|
|
76
|
+
/** The server's version and options. */
|
|
77
|
+
server: string;
|
|
78
|
+
/** The publication's tables and options. */
|
|
79
|
+
publication: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const objects = (v: Version): Obj[] => [
|
|
83
|
+
{ export: "app", type: "Postgres::Schema", ddl: `CREATE SCHEMA ${S}` },
|
|
84
|
+
{ export: "orders", type: "Postgres::Table", dependsOn: ["app"], ddl: `CREATE TABLE ${S}.orders (id int PRIMARY KEY, user_id int, status text)` },
|
|
85
|
+
{ export: "events", type: "Postgres::Table", dependsOn: ["app"], ddl: `CREATE TABLE ${S}.events (id int PRIMARY KEY)` },
|
|
86
|
+
{ export: "ordersStats", type: "Postgres::Statistics", dependsOn: ["orders"], ddl: `CREATE STATISTICS ${S}.orders_stats ${v.kinds} ON user_id, status FROM ${S}.orders;\nCOMMENT ON STATISTICS ${S}.orders_stats IS 'Correlated'` },
|
|
87
|
+
{ export: "appFdw", type: "Postgres::ForeignDataWrapper", ddl: `CREATE FOREIGN DATA WRAPPER app_fdw_3911 ${v.fdwOptions};\nCOMMENT ON FOREIGN DATA WRAPPER app_fdw_3911 IS 'Remote'` },
|
|
88
|
+
{ export: "replica", type: "Postgres::ForeignServer", dependsOn: ["appFdw"], ddl: `CREATE SERVER replica_3911 ${v.server} FOREIGN DATA WRAPPER app_fdw_3911 OPTIONS (host 'db.internal');\nCOMMENT ON SERVER replica_3911 IS 'The replica'` },
|
|
89
|
+
{ export: "pub", type: "Postgres::Publication", dependsOn: ["orders", "events"], ddl: `CREATE PUBLICATION pub_3911 ${v.publication};\nCOMMENT ON PUBLICATION pub_3911 IS 'Order changes'` },
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
const V1: Version = {
|
|
93
|
+
kinds: "(dependencies)",
|
|
94
|
+
fdwOptions: "OPTIONS (debug 'true')",
|
|
95
|
+
server: "TYPE 'pg' VERSION '16'",
|
|
96
|
+
publication: `FOR TABLE ${S}.orders WITH (publish = 'insert, update')`,
|
|
97
|
+
};
|
|
98
|
+
// Each kind changed: the statistics' kinds (drop and create), the wrapper's options, the server's version and the publication's tables and options (in place).
|
|
99
|
+
const V2: Version = {
|
|
100
|
+
kinds: "(ndistinct)",
|
|
101
|
+
fdwOptions: "OPTIONS (debug 'false', trace 'on')",
|
|
102
|
+
server: "TYPE 'pg' VERSION '17'",
|
|
103
|
+
publication: `FOR TABLE ${S}.events WITH (publish = 'insert')`,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
describe.skipIf(!enabled)("statistics, foreign servers and publications on a live server", () => {
|
|
107
|
+
test("apply, plan, drift, change and import", async () => {
|
|
108
|
+
const v1 = writeBuild("v1.json", objects(V1));
|
|
109
|
+
const first = await apply(v1);
|
|
110
|
+
expect(first.failed).toEqual([]);
|
|
111
|
+
expect(first.applied.map((a) => [a.name, a.action])).toEqual([
|
|
112
|
+
[S, "created"],
|
|
113
|
+
[`${S}.orders`, "created"],
|
|
114
|
+
[`${S}.events`, "created"],
|
|
115
|
+
[`${S}.orders_stats`, "created"],
|
|
116
|
+
["app_fdw_3911", "created"],
|
|
117
|
+
["replica_3911", "created"],
|
|
118
|
+
["pub_3911", "created"],
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
// As declared: no change, and no drift.
|
|
122
|
+
expect((await plan(v1)).changes).toEqual([]);
|
|
123
|
+
const declared = declaredObjects(JSON.stringify({ dialect: "postgres", objects: objects(V1).map((o) => ({ dependsOn: [], ...o })) }));
|
|
124
|
+
const entities = new Map(declared.map((o) => [o.exportName, { entityType: o.type, props: o.props }]));
|
|
125
|
+
const deep = await observeResourcesDeep({ environment: "e2e", entityNames: [...entities.keys()], entities, ...profile() });
|
|
126
|
+
expect(deep.unobserved ?? {}).toEqual({});
|
|
127
|
+
|
|
128
|
+
// Changed by hand: drift, in the plan.
|
|
129
|
+
await admin.query(`ALTER SERVER replica_3911 OPTIONS (SET host 'elsewhere')`);
|
|
130
|
+
expect((await plan(v1)).changes.map((c) => [c.object, c.field, c.rule])).toEqual([["replica (replica_3911)", "options", "SQLPG303"]]);
|
|
131
|
+
await admin.query(`ALTER SERVER replica_3911 OPTIONS (SET host 'db.internal')`);
|
|
132
|
+
|
|
133
|
+
// Changed in the declaration: the statistics are dropped and created; the rest altered in place.
|
|
134
|
+
const v2 = writeBuild("v2.json", objects(V2));
|
|
135
|
+
const planned = await plan(v2);
|
|
136
|
+
expect(planned.changes.map((c) => [c.object, c.rule])).toEqual([
|
|
137
|
+
[`ordersStats (${S}.orders_stats)`, "SQLPG300"],
|
|
138
|
+
["appFdw (app_fdw_3911)", "SQLPG303"],
|
|
139
|
+
["replica (replica_3911)", "SQLPG303"],
|
|
140
|
+
["pub (pub_3911)", "SQLPG303"],
|
|
141
|
+
["pub (pub_3911)", "SQLPG303"],
|
|
142
|
+
["pub (pub_3911)", "SQLPG303"],
|
|
143
|
+
]);
|
|
144
|
+
expect(planned.refused).toEqual([]);
|
|
145
|
+
const second = await apply(v2);
|
|
146
|
+
expect(second.failed).toEqual([]);
|
|
147
|
+
expect((await plan(v2)).changes).toEqual([]);
|
|
148
|
+
expect((await admin.query<{ n: number }>(`SELECT count(*)::int AS n FROM pg_publication_rel r JOIN pg_publication p ON p.oid = r.prpubid WHERE p.pubname = 'pub_3911'`))[0]!.n).toBe(1);
|
|
149
|
+
|
|
150
|
+
// Import: the server's own printing, written back as declarations that parse and plan with no change.
|
|
151
|
+
const live = await readLiveSchema(admin, { schemas: [S] });
|
|
152
|
+
for (const o of live) liveProps(o);
|
|
153
|
+
const ir = objectsToIR(live.map((o) => ({ type: o.type, ...(o.schema ? { schema: o.schema } : {}), name: o.name, ddl: o.statement })));
|
|
154
|
+
const [file] = new PostgresGenerator().generate(ir);
|
|
155
|
+
expect(file!.content).toContain("export const ordersStats = statistics`");
|
|
156
|
+
expect(file!.content).toContain("export const appFdw3911 = foreignDataWrapper`");
|
|
157
|
+
expect(file!.content).toContain("FOREIGN DATA WRAPPER ${appFdw3911}");
|
|
158
|
+
expect(file!.content).toContain("export const pub3911 = publication`");
|
|
159
|
+
const items = ir.resources.map((r) => ({ exportName: r.logicalId, type: r.type, ...(r.properties.schema ? { schema: String(r.properties.schema) } : {}), name: String(r.properties.name), ddl: String(r.properties.ddl) }));
|
|
160
|
+
const { order } = postgresTemplates(items);
|
|
161
|
+
const imported = writeBuild("imported.json", order.map((it) => ({ export: it.exportName, type: it.type, ddl: it.ddl })));
|
|
162
|
+
expect((await plan(imported)).changes).toEqual([]);
|
|
163
|
+
}, 600_000);
|
|
164
|
+
});
|
|
@@ -34,6 +34,14 @@ interface Bound {
|
|
|
34
34
|
byKey: Map<string, LivePgObject>;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/** The entity types that belong to the database, not a schema. */
|
|
38
|
+
const UNPLACED: ReadonlySet<string> = new Set([
|
|
39
|
+
POSTGRES_ENTITY_TYPES.eventTrigger,
|
|
40
|
+
POSTGRES_ENTITY_TYPES.foreignDataWrapper,
|
|
41
|
+
POSTGRES_ENTITY_TYPES.foreignServer,
|
|
42
|
+
POSTGRES_ENTITY_TYPES.publication,
|
|
43
|
+
]);
|
|
44
|
+
|
|
37
45
|
/**
|
|
38
46
|
* The schema and name a declared entity is created as; a bare name is in the
|
|
39
47
|
* default schema. A routine's parameter types and a trigger's table tell it
|
|
@@ -41,7 +49,7 @@ interface Bound {
|
|
|
41
49
|
*/
|
|
42
50
|
export function declaredAddress(entity: { type: string; props: Record<string, unknown> }, defaultSchema: string): { schema?: string; name: string; signature?: string } {
|
|
43
51
|
const name = String(entity.props.name ?? "");
|
|
44
|
-
if (entity.type === POSTGRES_ENTITY_TYPES.schema || entity.type === POSTGRES_ENTITY_TYPES.extension || entity.type === POSTGRES_ENTITY_TYPES.role || entity.type
|
|
52
|
+
if (entity.type === POSTGRES_ENTITY_TYPES.schema || entity.type === POSTGRES_ENTITY_TYPES.extension || entity.type === POSTGRES_ENTITY_TYPES.role || UNPLACED.has(entity.type)) return { name };
|
|
45
53
|
// A cast is named by its two types, as the catalog prints them.
|
|
46
54
|
if (entity.type === POSTGRES_ENTITY_TYPES.cast) return { name: canonicalPgObject(entity.type, entity.props, defaultSchema).name };
|
|
47
55
|
const schema = typeof entity.props.schema === "string" ? entity.props.schema : identValue(defaultSchema);
|
|
@@ -49,6 +57,9 @@ export function declaredAddress(entity: { type: string; props: Record<string, un
|
|
|
49
57
|
entity.type === POSTGRES_ENTITY_TYPES.function ||
|
|
50
58
|
entity.type === POSTGRES_ENTITY_TYPES.procedure ||
|
|
51
59
|
entity.type === POSTGRES_ENTITY_TYPES.aggregate ||
|
|
60
|
+
entity.type === POSTGRES_ENTITY_TYPES.operator ||
|
|
61
|
+
entity.type === POSTGRES_ENTITY_TYPES.operatorClass ||
|
|
62
|
+
entity.type === POSTGRES_ENTITY_TYPES.operatorFamily ||
|
|
52
63
|
entity.type === POSTGRES_ENTITY_TYPES.trigger ||
|
|
53
64
|
entity.type === POSTGRES_ENTITY_TYPES.policy
|
|
54
65
|
) {
|
|
@@ -79,7 +90,7 @@ export function scopeFor(target: PostgresTarget, entities: ReadonlyArray<{ type:
|
|
|
79
90
|
if (entities.length === 0) return undefined;
|
|
80
91
|
const out = new Set<string>([target.defaultSchema]);
|
|
81
92
|
// The schema alone: the rest of an address (a routine's parameter types) needs props a caller may not have.
|
|
82
|
-
const unplaced = new Set<string>([POSTGRES_ENTITY_TYPES.extension, POSTGRES_ENTITY_TYPES.role, POSTGRES_ENTITY_TYPES.cast,
|
|
93
|
+
const unplaced = new Set<string>([POSTGRES_ENTITY_TYPES.extension, POSTGRES_ENTITY_TYPES.role, POSTGRES_ENTITY_TYPES.cast, ...UNPLACED]);
|
|
83
94
|
for (const e of entities) {
|
|
84
95
|
if (e.type === POSTGRES_ENTITY_TYPES.schema) out.add(String(e.props.name ?? ""));
|
|
85
96
|
else if (!unplaced.has(e.type)) out.add(typeof e.props.schema === "string" ? e.props.schema : identValue(target.defaultSchema));
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Import against a real Postgres server never leaves an object out without a
|
|
3
3
|
* word (#3911): a database holding a rewrite rule, an encoding conversion, an
|
|
4
|
-
* access method
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* access method, a shell type, a user mapping and a subscription (the last
|
|
5
|
+
* two carry credentials) beside a table fails the import, which names all
|
|
6
|
+
* six; with `skipUndeclarable` the rest is imported and the six are warnings.
|
|
7
|
+
* A composite type, an aggregate, a collation, a cast, an event trigger, a
|
|
8
|
+
* foreign data wrapper, a foreign server and a publication are declared
|
|
9
|
+
* kinds, so they are imported, not refused. What
|
|
8
10
|
* an extension brings (hstore's casts and operators) is the extension's, and
|
|
9
11
|
* is not refused.
|
|
10
12
|
*
|
|
@@ -36,6 +38,11 @@ CREATE RULE users_notify AS ON INSERT TO app.users DO ALSO NOTIFY users;
|
|
|
36
38
|
CREATE CONVERSION app.latin1_utf8 FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8;
|
|
37
39
|
CREATE ACCESS METHOD heap2 TYPE TABLE HANDLER heap_tableam_handler;
|
|
38
40
|
CREATE TYPE app.later;
|
|
41
|
+
CREATE FOREIGN DATA WRAPPER app_fdw;
|
|
42
|
+
CREATE SERVER app_remote FOREIGN DATA WRAPPER app_fdw OPTIONS (host 'db.internal');
|
|
43
|
+
CREATE USER MAPPING FOR PUBLIC SERVER app_remote OPTIONS (user 'reader');
|
|
44
|
+
CREATE PUBLICATION app_pub FOR TABLE app.users;
|
|
45
|
+
CREATE SUBSCRIPTION app_sub CONNECTION 'host=127.0.0.1 dbname=elsewhere' PUBLICATION elsewhere WITH (connect = false);
|
|
39
46
|
`;
|
|
40
47
|
|
|
41
48
|
beforeAll(async () => {
|
|
@@ -58,7 +65,7 @@ const profile = () => ({
|
|
|
58
65
|
env: { PG_UD_PASSWORD: server!.endpoint("source").password },
|
|
59
66
|
});
|
|
60
67
|
|
|
61
|
-
const NAMED = ["rule app.users_notify ON users", "conversion app.latin1_utf8", "access method heap2", "base type app.later"];
|
|
68
|
+
const NAMED = ["rule app.users_notify ON users", "conversion app.latin1_utf8", "access method heap2", "base type app.later", "user mapping for public on server app_remote", "subscription app_sub"];
|
|
62
69
|
|
|
63
70
|
describe.skipIf(!enabled)("importing a database with objects chant cannot declare", () => {
|
|
64
71
|
test("fails, naming every one of them", async () => {
|
|
@@ -75,7 +82,7 @@ describe.skipIf(!enabled)("importing a database with objects chant cannot declar
|
|
|
75
82
|
test("with skipUndeclarable, imports the rest and lists them as warnings", async () => {
|
|
76
83
|
const ir = await exportResources({ environment: "src", skipUndeclarable: true, ...profile() });
|
|
77
84
|
expect(ir.resources.map((r) => r.properties.name)).toEqual(
|
|
78
|
-
expect.arrayContaining(["users", "hstore", "add2", "pair_text", "on_ddl", "pair", "total", "ci", "(app.pair AS text)", "audit_ddl"]),
|
|
85
|
+
expect.arrayContaining(["users", "hstore", "add2", "pair_text", "on_ddl", "pair", "total", "ci", "(app.pair AS text)", "audit_ddl", "app_fdw", "app_remote", "app_pub"]),
|
|
79
86
|
);
|
|
80
87
|
const left = (ir.warnings ?? []).filter((w) => w.includes("is left out (--skip-undeclarable)"));
|
|
81
88
|
expect(left.map((w) => w.split(" is left out")[0]).sort()).toEqual([...NAMED].sort());
|
|
@@ -169,6 +169,8 @@ export interface VerifyResult {
|
|
|
169
169
|
mismatched: number;
|
|
170
170
|
/** Rows whose new column is NULL. */
|
|
171
171
|
nulls: number;
|
|
172
|
+
/** A rename with a type change: rows whose old column differs from `reverse` over the new one. */
|
|
173
|
+
unreversed?: number;
|
|
172
174
|
/** The sum of a 64-bit hash of every new value, and of every value the expression gives. */
|
|
173
175
|
checksum: string;
|
|
174
176
|
expected: string;
|
|
@@ -201,6 +203,7 @@ export async function verifyMigration(run: MigrationRun): Promise<VerifyResult>
|
|
|
201
203
|
exec(
|
|
202
204
|
`SELECT count(*)::text AS rows, count(*) FILTER (WHERE ${nw} IS DISTINCT FROM (${o.expression}))::text AS mismatched,
|
|
203
205
|
count(*) FILTER (WHERE ${nw} IS NULL)::text AS nulls,
|
|
206
|
+
${o.reverse !== undefined ? `count(*) FILTER (WHERE (${o.reverse}) IS DISTINCT FROM ${col(n.source)})::text` : "'0'"} AS unreversed,
|
|
204
207
|
COALESCE(sum(pg_catalog.hashtextextended(${nw}::text, 0)), 0)::text AS checksum,
|
|
205
208
|
COALESCE(sum(pg_catalog.hashtextextended((${o.expression})::text, 0)), 0)::text AS expected
|
|
206
209
|
FROM ${n.qualifiedTable}`,
|
|
@@ -211,6 +214,14 @@ export async function verifyMigration(run: MigrationRun): Promise<VerifyResult>
|
|
|
211
214
|
if (r.mismatched > 0 || r.checksum !== r.expected) {
|
|
212
215
|
throw new MigrationVerificationError(n.key, { state: o.state, ...r }, `${r.mismatched} of ${r.rows} row(s) have ${n.newColumn} different from ${o.expression} (checksums ${r.checksum} and ${r.expected})`);
|
|
213
216
|
}
|
|
217
|
+
const unreversed = Number(row?.unreversed ?? 0);
|
|
218
|
+
if (o.reverse !== undefined && unreversed > 0) {
|
|
219
|
+
throw new MigrationVerificationError(
|
|
220
|
+
n.key,
|
|
221
|
+
{ state: o.state, ...r, unreversed },
|
|
222
|
+
`${unreversed} of ${r.rows} row(s) have ${n.source} different from ${o.reverse}, which must give the old value back from the new one (it would write ${n.source} wrongly for a writer of ${n.column})`,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
214
225
|
if (requiresNotNull(o.column, o.carried) && r.nulls > 0) {
|
|
215
226
|
throw new MigrationVerificationError(n.key, { state: o.state, ...r }, `${r.nulls} of ${r.rows} row(s) have ${n.newColumn} NULL, and ${n.column} is declared NOT NULL or is in the primary key`);
|
|
216
227
|
}
|
|
@@ -222,12 +233,13 @@ export async function verifyMigration(run: MigrationRun): Promise<VerifyResult>
|
|
|
222
233
|
o.carried.length > 0 || o.views.length > 0
|
|
223
234
|
? `; ${o.carried.map((c) => `${c.kind} ${c.name}${c.target !== c.name ? ` (as ${c.target})` : ""}`).join(", ") || "no index or constraint"} carried over${o.views.length > 0 ? `, view(s) ${o.views.map((v) => v.name).join(", ")} made again at the switch` : ""}`
|
|
224
235
|
: "";
|
|
225
|
-
const summary = `${n.key}: ${r.rows} row(s), ${n.newColumn} equal to ${o.expression} in every one (checksum ${r.checksum})${carried}`;
|
|
236
|
+
const summary = `${n.key}: ${r.rows} row(s), ${n.newColumn} equal to ${o.expression} in every one (checksum ${r.checksum})${o.reverse !== undefined ? `, ${n.source} equal to ${o.reverse} in every one` : ""}${carried}`;
|
|
226
237
|
run.log(`-- ${summary}`);
|
|
227
238
|
return {
|
|
228
239
|
state: o.state,
|
|
229
240
|
planDigest: computePlanDigest("postgres-migration", { plan: migrationPlanSubject(o), table: o.oid, column: o.newColumn.attnum, verified: { mismatched: 0 } }),
|
|
230
241
|
...r,
|
|
242
|
+
...(o.reverse !== undefined ? { unreversed } : {}),
|
|
231
243
|
summary,
|
|
232
244
|
};
|
|
233
245
|
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* | On the old column | Before the switch | In the switch's transaction |
|
|
11
11
|
* |---|---|---|
|
|
12
12
|
* | an index | `CREATE INDEX CONCURRENTLY` on the new column | the old one dropped (a type change) or kept as `<name>__chant_old` (a rename), the new one renamed to the name |
|
|
13
|
+
* | an index on a partitioned table | `CREATE INDEX ... ON ONLY` the table, `CONCURRENTLY` on each partition, `ALTER INDEX ... ATTACH PARTITION` | as an index; the partitions' indexes go with the old one |
|
|
13
14
|
* | a primary key or unique constraint | its unique index built `CONCURRENTLY` | the old constraint dropped (or a rename's unique one kept as `__chant_old`), `ADD CONSTRAINT ... USING INDEX` |
|
|
14
15
|
* | a check or foreign key, this table's or another's that references the column | added `NOT VALID`, then `VALIDATE` | the old one dropped, the new one renamed to the name |
|
|
15
16
|
* | a view (and the views that read it) | nothing | dropped, then created again from its declaration once the columns are swapped, with its grants and owner |
|
|
@@ -33,7 +34,8 @@
|
|
|
33
34
|
* key or index the build declares on the new column takes the declared name.
|
|
34
35
|
*
|
|
35
36
|
* Refused, each named with what to do: exclusion constraints and constraint
|
|
36
|
-
* triggers (no `USING INDEX` form),
|
|
37
|
+
* triggers (no `USING INDEX` form), a key or foreign key on a partitioned table,
|
|
38
|
+
* a partitioned index over a foreign-table partition, materialized views (made again only by
|
|
37
39
|
* a full scan in the switch), rules, triggers, policies and statistics on the
|
|
38
40
|
* column, a generated column computed from it, an INVALID index on it, a view
|
|
39
41
|
* the build does not declare, a view something other than a view depends on.
|
|
@@ -84,6 +86,12 @@ export interface CarriedObject {
|
|
|
84
86
|
indexComment?: string;
|
|
85
87
|
replicaIdentity?: boolean;
|
|
86
88
|
clustered?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* An index on a partitioned table (#3333): `definition` is the `CREATE INDEX ... ON ONLY` that makes the
|
|
91
|
+
* partitioned index without building anything. Each partition's index is built `CONCURRENTLY` on its own and
|
|
92
|
+
* attached (`partitionIndexStatement`, `carryPartitionedIndex` in `./steps.ts`).
|
|
93
|
+
*/
|
|
94
|
+
partitioned?: boolean;
|
|
87
95
|
}
|
|
88
96
|
|
|
89
97
|
/** A view on the old column (or on such a view), made again at the switch from its declaration. */
|
|
@@ -187,6 +195,73 @@ export function carriedIndexStatement(def: string, working: string, from: string
|
|
|
187
195
|
return `CREATE ${m[1] ?? ""}INDEX CONCURRENTLY ${quoteIdent(working)} ON ${replaceColumn(m[2]!, from, to)}`;
|
|
188
196
|
}
|
|
189
197
|
|
|
198
|
+
/**
|
|
199
|
+
* A partitioned index (`pg_get_indexdef` prints it `ON ONLY`) as the
|
|
200
|
+
* `CREATE INDEX ... ON ONLY` of `working` on the new column: it builds nothing
|
|
201
|
+
* and stays INVALID until every partition's index is attached.
|
|
202
|
+
*/
|
|
203
|
+
export function carriedPartitionedIndexStatement(def: string, working: string, from: string, to: string): string {
|
|
204
|
+
const m = /^CREATE (UNIQUE )?INDEX (?:"(?:[^"]|"")+"|\S+) ON (?:ONLY )?(.*)$/s.exec(def);
|
|
205
|
+
if (!m) throw new Error(`unexpected index definition: ${def}`);
|
|
206
|
+
return `CREATE ${m[1] ?? ""}INDEX ${quoteIdent(working)} ON ONLY ${replaceColumn(m[2]!, from, to)}`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** One table of a partition tree. */
|
|
210
|
+
export interface PartitionNode {
|
|
211
|
+
oid: string;
|
|
212
|
+
schema: string;
|
|
213
|
+
name: string;
|
|
214
|
+
/** Quoted and qualified. */
|
|
215
|
+
ident: string;
|
|
216
|
+
/** The partitioned table (or partition) it is a partition of; absent for the root. */
|
|
217
|
+
parent?: string;
|
|
218
|
+
/** Not partitioned further: the tables that hold rows. */
|
|
219
|
+
leaf: boolean;
|
|
220
|
+
/** `r` (ordinary), `p` (partitioned) or `f` (foreign). */
|
|
221
|
+
kind: string;
|
|
222
|
+
level: number;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** The partition tree under a partitioned table, the table itself first (level 0). */
|
|
226
|
+
export async function partitionTree(client: Pick<PostgresClient, "query">, tableOid: string): Promise<PartitionNode[]> {
|
|
227
|
+
const rows = await client.query<{ oid: string; schema: string; name: string; parent: string | null; leaf: boolean; kind: string; level: number }>(
|
|
228
|
+
`SELECT t.relid::oid::text AS oid, n.nspname AS schema, c.relname AS name, t.parentrelid::oid::text AS parent, t.isleaf AS leaf, c.relkind::text AS kind, t.level
|
|
229
|
+
FROM pg_catalog.pg_partition_tree($1::oid::pg_catalog.regclass) t JOIN pg_catalog.pg_class c ON c.oid = t.relid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
230
|
+
ORDER BY t.level, n.nspname, c.relname`,
|
|
231
|
+
[tableOid],
|
|
232
|
+
);
|
|
233
|
+
return rows.map((r) => ({
|
|
234
|
+
oid: String(r.oid),
|
|
235
|
+
schema: r.schema,
|
|
236
|
+
name: r.name,
|
|
237
|
+
ident: `${quoteIdent(r.schema)}.${quoteIdent(r.name)}`,
|
|
238
|
+
...(r.parent !== null ? { parent: String(r.parent) } : {}),
|
|
239
|
+
leaf: r.leaf === true,
|
|
240
|
+
kind: r.kind,
|
|
241
|
+
level: Number(r.level),
|
|
242
|
+
}));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** The name of the index a partitioned index's working copy has on one partition. */
|
|
246
|
+
export const partitionIndexName = (partition: Pick<PartitionNode, "name">, working: string): string => pgName(`${partition.name}_${working}`);
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* The statement that makes a partitioned index's working copy on one
|
|
250
|
+
* partition. A partition that is partitioned further gets `ON ONLY` (it is
|
|
251
|
+
* valid once its own partitions are attached); one that holds rows gets the
|
|
252
|
+
* `CONCURRENTLY` build, so writes go on.
|
|
253
|
+
*/
|
|
254
|
+
export function partitionIndexStatement(c: Pick<CarriedObject, "definition">, node: Pick<PartitionNode, "ident" | "leaf">, indexName: string): string {
|
|
255
|
+
const m = /^CREATE (UNIQUE )?INDEX (?:"(?:[^"]|"")+"|\S+) ON ONLY (.*)$/s.exec(c.definition);
|
|
256
|
+
if (!m) throw new Error(`unexpected partitioned index definition: ${c.definition}`);
|
|
257
|
+
const using = m[2]!.indexOf(" USING ");
|
|
258
|
+
if (using < 0) throw new Error(`unexpected partitioned index definition: ${c.definition}`);
|
|
259
|
+
const tail = m[2]!.slice(using);
|
|
260
|
+
return node.leaf
|
|
261
|
+
? `CREATE ${m[1] ?? ""}INDEX CONCURRENTLY ${quoteIdent(indexName)} ON ${node.ident}${tail}`
|
|
262
|
+
: `CREATE ${m[1] ?? ""}INDEX ${quoteIdent(indexName)} ON ONLY ${node.ident}${tail}`;
|
|
263
|
+
}
|
|
264
|
+
|
|
190
265
|
/**
|
|
191
266
|
* The name Postgres gives an object it names after its columns
|
|
192
267
|
* (`users_email_key`, `orders_user_id_fkey`, `users_email_idx`), when `name`
|
|
@@ -214,12 +289,26 @@ interface DiscoverInput {
|
|
|
214
289
|
defaultSchema: string;
|
|
215
290
|
/** The table is partitioned: its indexes cannot be built CONCURRENTLY, nor its keys attached USING INDEX. */
|
|
216
291
|
partitioned?: boolean;
|
|
292
|
+
/** A rename keeps the column's sequence on the old column until the contract (`defaultAtContract`), instead of being refused. */
|
|
293
|
+
keepSequence?: boolean;
|
|
217
294
|
}
|
|
218
295
|
|
|
219
296
|
interface Row {
|
|
220
297
|
[key: string]: unknown;
|
|
221
298
|
}
|
|
222
299
|
|
|
300
|
+
/** The sequence that belongs to a column (an identity's, or a `serial`'s), if it has one. */
|
|
301
|
+
export async function columnSequence(client: PostgresClient, tableOid: string, attnum: number): Promise<ColumnSequence | undefined> {
|
|
302
|
+
const [row] = await client.query<{ schema: string; name: string; deptype: string }>(
|
|
303
|
+
`SELECT n.nspname AS schema, c.relname AS name, d.deptype::text AS deptype
|
|
304
|
+
FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_class c ON c.oid = d.objid AND d.classid = 'pg_catalog.pg_class'::pg_catalog.regclass JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
305
|
+
WHERE d.refclassid = 'pg_catalog.pg_class'::pg_catalog.regclass AND d.refobjid = $1::oid AND d.refobjsubid = $2 AND d.deptype IN ('a', 'i') AND c.relkind = 'S'`,
|
|
306
|
+
[tableOid, attnum],
|
|
307
|
+
);
|
|
308
|
+
if (!row) return undefined;
|
|
309
|
+
return { kind: row.deptype === "i" ? "identity" : "serial", schema: row.schema, name: row.name, ident: `${quoteIdent(row.schema)}.${quoteIdent(row.name)}` };
|
|
310
|
+
}
|
|
311
|
+
|
|
223
312
|
/** What uses the column the values come from, sorted into what the Op carries over and what it refuses. */
|
|
224
313
|
export async function discoverDependents(input: DiscoverInput): Promise<Dependents> {
|
|
225
314
|
const { client, names: n, oid, attnum } = input;
|
|
@@ -343,10 +432,6 @@ export async function discoverDependents(input: DiscoverInput): Promise<Dependen
|
|
|
343
432
|
const kind = String(r.kind);
|
|
344
433
|
if (kind === "i" || kind === "I") {
|
|
345
434
|
if (keyIndexes.has(String(r.oid))) continue;
|
|
346
|
-
if (kind === "I") {
|
|
347
|
-
refused.push(`index ${r.schema}.${name} is a partitioned index, which cannot be built CONCURRENTLY; drop it, migrate, then declare it again (chant #3333)`);
|
|
348
|
-
continue;
|
|
349
|
-
}
|
|
350
435
|
if (r.valid !== true) {
|
|
351
436
|
refused.push(`index ${r.schema}.${name} is INVALID (a failed CONCURRENTLY build); drop it, or REINDEX it, first`);
|
|
352
437
|
continue;
|
|
@@ -375,6 +460,14 @@ export async function discoverDependents(input: DiscoverInput): Promise<Dependen
|
|
|
375
460
|
});
|
|
376
461
|
target = declaredIndex?.canonical.name ?? (r.has_exprs === true ? undefined : renamedDefault(name, table, oldCols, cols, "idx")) ?? name;
|
|
377
462
|
}
|
|
463
|
+
if (kind === "I") {
|
|
464
|
+
// A partition the index cannot be built on (a foreign table) stops the carry before anything is made.
|
|
465
|
+
const foreign = (await partitionTree(client, oid)).filter((p) => p.kind === "f");
|
|
466
|
+
if (foreign.length > 0) {
|
|
467
|
+
refused.push(`partitioned index ${r.schema}.${name}: ${foreign.map((p) => `${p.schema}.${p.name}`).join(", ")} is a foreign table, which holds no index to attach; drop the index, migrate, then declare it again`);
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
378
471
|
carried.push({
|
|
379
472
|
kind: "index",
|
|
380
473
|
name,
|
|
@@ -384,7 +477,8 @@ export async function discoverDependents(input: DiscoverInput): Promise<Dependen
|
|
|
384
477
|
schema: String(r.schema),
|
|
385
478
|
working,
|
|
386
479
|
target,
|
|
387
|
-
definition: carriedIndexStatement(String(r.def), working, from, to),
|
|
480
|
+
definition: kind === "I" ? carriedPartitionedIndexStatement(String(r.def), working, from, to) : carriedIndexStatement(String(r.def), working, from, to),
|
|
481
|
+
...(kind === "I" ? { partitioned: true } : {}),
|
|
388
482
|
validated: true,
|
|
389
483
|
...(r.comment !== null ? { comment: String(r.comment) } : {}),
|
|
390
484
|
...(r.replident === true ? { replicaIdentity: true } : {}),
|
|
@@ -393,9 +487,10 @@ export async function discoverDependents(input: DiscoverInput): Promise<Dependen
|
|
|
393
487
|
} else if (kind === "S") {
|
|
394
488
|
const dep = deps.find((d) => d.kind === "class" && d.objid === String(r.oid));
|
|
395
489
|
const seq: ColumnSequence = { kind: dep?.deptype === "i" ? "identity" : "serial", schema: String(r.schema), name, ident: `${quoteIdent(String(r.schema))}.${quoteIdent(name)}` };
|
|
396
|
-
if (rename) {
|
|
490
|
+
if (rename && !input.keepSequence) {
|
|
397
491
|
refused.push(
|
|
398
|
-
`sequence ${r.schema}.${name} gives the column its values (${seq.kind === "identity" ? "an identity column" : "a serial column's default"}); during a rename a writer that names the new column would
|
|
492
|
+
`sequence ${r.schema}.${name} gives the column its values (${seq.kind === "identity" ? "an identity column" : "a serial column's default"}); during a rename a writer that names the new column would get the old one's next value, and the trigger would take it over the writer's, ` +
|
|
493
|
+
`because it cannot tell a defaulted value from one the writer gave. Set defaultAtContract: true to leave the new column without the default until the contract (see the Op's reference), or drop the default first`,
|
|
399
494
|
);
|
|
400
495
|
} else sequence = seq;
|
|
401
496
|
} else {
|