@intentius/chant-lexicon-sql 0.124.45 → 0.124.48
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/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 +21 -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 +1 -1
- package/dist/codegen/registry.d.ts.map +1 -1
- package/dist/codegen/registry.js +5 -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/coverage/clickhouse.json +1 -1
- package/dist/coverage/postgres.json +7 -7
- 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 +684 -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.js +12 -2
- 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 +6 -5
- 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/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 +11 -0
- package/dist/manifest.json +1 -1
- package/dist/mcp/postgres.js +1 -1
- package/dist/meta.json +25 -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-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 +6 -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/PostgresOperator.md +9 -0
- package/dist/okf/types/PostgresPublication.md +9 -0
- package/dist/okf/types/PostgresStatistics.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 +36 -2
- package/dist/postgres/apply/apply.js +8 -1
- package/dist/postgres/apply/statements.d.ts.map +1 -1
- package/dist/postgres/apply/statements.js +87 -2
- 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 +110 -1
- package/dist/postgres/entities.d.ts.map +1 -1
- package/dist/postgres/entities.js +146 -0
- package/dist/postgres/entity-types.d.ts +5 -0
- package/dist/postgres/entity-types.d.ts.map +1 -1
- package/dist/postgres/entity-types.js +5 -0
- package/dist/postgres/import/generator.d.ts.map +1 -1
- package/dist/postgres/import/generator.js +50 -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 +14 -0
- package/dist/postgres/live/catalog-kinds.d.ts.map +1 -1
- package/dist/postgres/live/catalog-kinds.js +206 -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 +10 -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 +41 -12
- 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/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 +72 -1
- package/dist/postgres/parser.d.ts.map +1 -1
- package/dist/postgres/parser.js +194 -2
- package/dist/postgres/plan/deep.d.ts.map +1 -1
- package/dist/postgres/plan/deep.js +29 -1
- package/dist/postgres/plan/diff.d.ts.map +1 -1
- package/dist/postgres/plan/diff.js +39 -1
- package/dist/postgres/plan/normalize.d.ts +17 -2
- package/dist/postgres/plan/normalize.d.ts.map +1 -1
- package/dist/postgres/plan/normalize.js +109 -1
- package/dist/postgres/plan/rules.d.ts +1 -0
- package/dist/postgres/plan/rules.d.ts.map +1 -1
- package/dist/postgres/plan/rules.js +3 -2
- package/dist/postgres/plan/schema.js +1 -1
- 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 +3 -2
- package/dist/postgres/testing/roundtrip-schema.d.ts.map +1 -1
- package/dist/postgres/testing/roundtrip-schema.js +13 -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-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 +5 -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/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 +21 -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 +1 -1
- package/src/codegen/registry.ts +5 -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/undeclarable.test.ts +6 -5
- package/src/coverage/clickhouse.json +1 -1
- package/src/coverage/postgres.json +7 -7
- 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 +737 -0
- package/src/files/clickhouse.ts +8 -1
- package/src/files/common.ts +14 -1
- package/src/files/postgres.ts +12 -2
- package/src/generated/clickhouse.ts +3 -0
- package/src/generated/lexicon-sql.json +25 -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/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 +11 -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-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/plugin.ts +37 -2
- package/src/postgres/apply/apply.ts +9 -1
- package/src/postgres/apply/statements.ts +74 -1
- package/src/postgres/constraint-names.ts +71 -0
- package/src/postgres/database-kinds.test.ts +384 -0
- package/src/postgres/entities.ts +266 -1
- package/src/postgres/entity-types.ts +5 -0
- package/src/postgres/import/generator.ts +42 -2
- package/src/postgres/import/live-export.ts +1 -0
- package/src/postgres/live/catalog-kinds.ts +223 -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 +11 -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 +53 -14
- 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/steps.ts +157 -12
- package/src/postgres/parser.ts +234 -2
- package/src/postgres/plan/deep.ts +28 -1
- package/src/postgres/plan/diff.ts +36 -1
- package/src/postgres/plan/normalize.ts +122 -1
- package/src/postgres/plan/rules.ts +10 -3
- package/src/postgres/plan/schema.ts +1 -1
- package/src/postgres/secret-options.ts +10 -0
- package/src/postgres/testing/roundtrip-schema.ts +13 -1
- package/src/postgres.ts +16 -0
- package/src/serializer.ts +13 -2
- package/src/skills/chant-sql-postgres-migration.md +3 -3
- package/src/skills/chant-sql-postgres.md +6 -1
- package/src/skills/chant-sql.md +2 -1
- 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 +5 -0
- package/src/workspace-kinds.test.ts +11 -0
- package/workspace-kinds.json +100 -2
|
@@ -25,7 +25,7 @@ import { col, migrationNames, MIGRATION_TRAILER_KEY, type MigrationNames } from
|
|
|
25
25
|
import { MigrationRefusal, observeMigration, type MigrationObservation } from "./observe";
|
|
26
26
|
import { postgresReceiptStore, receiptAddress } from "./receipts";
|
|
27
27
|
import type { ReplicationLagBound } from "./replication";
|
|
28
|
-
import { addCarriedConstraint, carriedNotReady, carriedSwitchStatements, requiresNotNull, SERIAL_TYPES, storedType, type CarriedObject } from "./carry";
|
|
28
|
+
import { addCarriedConstraint, carriedNotReady, carriedSwitchStatements, partitionIndexName, partitionIndexStatement, partitionTree, requiresNotNull, SERIAL_TYPES, storedType, type CarriedObject, type CarriedState } from "./carry";
|
|
29
29
|
|
|
30
30
|
/** What every step is given. */
|
|
31
31
|
export interface MigrationRun {
|
|
@@ -39,6 +39,10 @@ export interface MigrationRun {
|
|
|
39
39
|
column: string;
|
|
40
40
|
/** The type change's expression over the old row's columns. */
|
|
41
41
|
using?: string;
|
|
42
|
+
/** A rename with a type change: the expression that gives the old column's value from the new column's. */
|
|
43
|
+
reverse?: string;
|
|
44
|
+
/** A rename of a column with a default, serial or identity: the declared default lands on the new column at the contract. */
|
|
45
|
+
defaultAtContract?: boolean;
|
|
42
46
|
/** This project's ownership marker, stamped on every working object. */
|
|
43
47
|
marker?: OwnershipMarker;
|
|
44
48
|
/** The major the build targets. */
|
|
@@ -70,6 +74,8 @@ export const observe = (run: MigrationRun): Promise<MigrationObservation> =>
|
|
|
70
74
|
...(run.objects ? { objects: run.objects } : {}),
|
|
71
75
|
...(run.marker ? { marker: run.marker } : {}),
|
|
72
76
|
...(run.using !== undefined ? { using: run.using } : {}),
|
|
77
|
+
...(run.reverse !== undefined ? { reverse: run.reverse } : {}),
|
|
78
|
+
...(run.defaultAtContract ? { defaultAtContract: true } : {}),
|
|
73
79
|
...(run.major !== undefined ? { major: run.major } : {}),
|
|
74
80
|
});
|
|
75
81
|
|
|
@@ -174,7 +180,7 @@ export interface ExpandResult {
|
|
|
174
180
|
}
|
|
175
181
|
|
|
176
182
|
/** The new column's type: the declared one, with its collation when it declares one. */
|
|
177
|
-
const newColumnType = (o: MigrationObservation): string => `${o.names.change === "rename" ? o.source!.type : storedType(o.column.type)}${o.column.collate ? ` COLLATE ${o.column.collate}` : ""}`;
|
|
183
|
+
const newColumnType = (o: MigrationObservation): string => `${o.names.change === "rename" && o.reverse === undefined ? o.source!.type : storedType(o.column.type)}${o.column.collate ? ` COLLATE ${o.column.collate}` : ""}`;
|
|
178
184
|
|
|
179
185
|
/**
|
|
180
186
|
* Expand: add the new column, nullable and with no default, which changes
|
|
@@ -211,10 +217,27 @@ export async function expand(run: MigrationRun): Promise<ExpandResult> {
|
|
|
211
217
|
* An insert takes the new column's value when it is given and the old one's
|
|
212
218
|
* otherwise; an update takes whichever of the two it changed.
|
|
213
219
|
*/
|
|
214
|
-
export function dualWriteBody(o: Pick<MigrationObservation, "names" | "expression">): string {
|
|
220
|
+
export function dualWriteBody(o: Pick<MigrationObservation, "names" | "expression"> & { reverse?: string }): string {
|
|
215
221
|
const n = o.names;
|
|
216
222
|
const nw = `NEW.${col(n.newColumn)}`;
|
|
217
223
|
const src = `NEW.${col(n.source)}`;
|
|
224
|
+
if (n.change === "rename" && o.reverse !== undefined) {
|
|
225
|
+
// A rename with a type change: a writer of the old name has `expression` applied, a writer of the new name `reverse`, each over the row as it is.
|
|
226
|
+
const fwd = `(SELECT ${o.expression} FROM (SELECT NEW.*) AS ${col(n.table)})`;
|
|
227
|
+
const back = `(SELECT ${o.reverse} FROM (SELECT NEW.*) AS ${col(n.table)})`;
|
|
228
|
+
return [
|
|
229
|
+
`BEGIN`,
|
|
230
|
+
` IF TG_OP = 'INSERT' THEN`,
|
|
231
|
+
` IF ${nw} IS NULL THEN ${nw} := ${fwd}; ELSE ${src} := ${back}; END IF;`,
|
|
232
|
+
` ELSIF ${nw} IS DISTINCT FROM OLD.${col(n.newColumn)} THEN`,
|
|
233
|
+
` ${src} := ${back};`,
|
|
234
|
+
` ELSE`,
|
|
235
|
+
` ${nw} := ${fwd};`,
|
|
236
|
+
` END IF;`,
|
|
237
|
+
` RETURN NEW;`,
|
|
238
|
+
`END`,
|
|
239
|
+
].join("\n");
|
|
240
|
+
}
|
|
218
241
|
if (n.change === "type") {
|
|
219
242
|
return [`BEGIN`, ` ${nw} := (SELECT ${o.expression} FROM (SELECT NEW.*) AS ${col(n.table)});`, ` RETURN NEW;`, `END`].join("\n");
|
|
220
243
|
}
|
|
@@ -233,7 +256,7 @@ export function dualWriteBody(o: Pick<MigrationObservation, "names" | "expressio
|
|
|
233
256
|
}
|
|
234
257
|
|
|
235
258
|
/** The statements that start the dual write: the function, the trigger, their comments. */
|
|
236
|
-
export function dualWriteStatements(o: Pick<MigrationObservation, "names" | "expression"
|
|
259
|
+
export function dualWriteStatements(o: Pick<MigrationObservation, "names" | "expression"> & { reverse?: string }, marker: OwnershipMarker | undefined, defaultSchema: string): string[] {
|
|
237
260
|
const n = o.names;
|
|
238
261
|
return [
|
|
239
262
|
`CREATE OR REPLACE FUNCTION ${n.qualifiedFn}() RETURNS trigger LANGUAGE plpgsql SET search_path = ${quoteIdent(defaultSchema)} AS $chant$\n${dualWriteBody(o)}\n$chant$`,
|
|
@@ -304,6 +327,11 @@ export async function carryOver(run: MigrationRun): Promise<CarryResult> {
|
|
|
304
327
|
result.carried.push(`${c.kind} ${c.tableName}.${c.name} -> ${c.target}`);
|
|
305
328
|
const state = o.carriedStates.get(c.working);
|
|
306
329
|
const comment = workingComment(n, run.marker, "carry", `${c.kind} ${c.name} on the new column`, { of: c.name });
|
|
330
|
+
if (c.kind === "index" && c.partitioned) {
|
|
331
|
+
if (await carryPartitionedIndex(run, n, c, comment, state)) result.built++;
|
|
332
|
+
else result.ready++;
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
307
335
|
if (c.kind === "index" || c.kind === "key") {
|
|
308
336
|
const ident = qualifiedIndex(c, c.working);
|
|
309
337
|
if (state?.ready) {
|
|
@@ -340,6 +368,85 @@ export async function carryOver(run: MigrationRun): Promise<CarryResult> {
|
|
|
340
368
|
return result;
|
|
341
369
|
}
|
|
342
370
|
|
|
371
|
+
/**
|
|
372
|
+
* Carry over one index of a partitioned table (#3333). A partitioned index
|
|
373
|
+
* cannot be built `CONCURRENTLY`, so it is made the way the Postgres docs
|
|
374
|
+
* give for adding one without blocking writes: the partitioned index
|
|
375
|
+
* `ON ONLY` the table (nothing is built, and it is INVALID), each leaf
|
|
376
|
+
* partition's index built `CONCURRENTLY` on its own, then
|
|
377
|
+
* `ALTER INDEX ... ATTACH PARTITION` for each, deepest first. The partitioned
|
|
378
|
+
* index becomes valid when its last partition is attached. A partition
|
|
379
|
+
* attached to the table meanwhile gets its index from Postgres, and one that
|
|
380
|
+
* already has an index attached to this one is left alone, so a run after a
|
|
381
|
+
* crash goes on from where the last stopped. Returns whether anything was
|
|
382
|
+
* made.
|
|
383
|
+
*/
|
|
384
|
+
export async function carryPartitionedIndex(run: MigrationRun, n: MigrationNames, c: CarriedObject, comment: string, state: CarriedState | undefined): Promise<boolean> {
|
|
385
|
+
if (state?.ready && state.marked) return false;
|
|
386
|
+
const root = qualifiedIndex(c, c.working);
|
|
387
|
+
const one = async <T>(sql: string, params: unknown[]): Promise<T | undefined> => (await run.client.query<T>(sql, params))[0];
|
|
388
|
+
const oidOf = async (ident: string): Promise<string | undefined> => (await one<{ oid: string | null }>("SELECT pg_catalog.to_regclass($1)::oid::text AS oid", [ident]))?.oid ?? undefined;
|
|
389
|
+
/** The index on `partitionOid` that is attached to `parentIndexOid`, whoever made it. */
|
|
390
|
+
const attachedIndex = async (parentIndexOid: string, partitionOid: string): Promise<{ oid: string; ident: string } | undefined> => {
|
|
391
|
+
const row = await one<{ oid: string; schema: string; name: string }>(
|
|
392
|
+
`SELECT x.oid::text AS oid, s.nspname AS schema, x.relname AS name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_index i ON i.indexrelid = h.inhrelid
|
|
393
|
+
JOIN pg_catalog.pg_class x ON x.oid = i.indexrelid JOIN pg_catalog.pg_namespace s ON s.oid = x.relnamespace WHERE h.inhparent = $1::oid AND i.indrelid = $2::oid`,
|
|
394
|
+
[parentIndexOid, partitionOid],
|
|
395
|
+
);
|
|
396
|
+
return row ? { oid: String(row.oid), ident: `${quoteIdent(row.schema)}.${quoteIdent(row.name)}` } : undefined;
|
|
397
|
+
};
|
|
398
|
+
if (!state?.present) {
|
|
399
|
+
await inTransaction(run, async (exec) => {
|
|
400
|
+
await exec(c.definition);
|
|
401
|
+
await exec(`COMMENT ON INDEX ${root} IS ${pgString(comment)}`);
|
|
402
|
+
});
|
|
403
|
+
} else if (!state.marked) {
|
|
404
|
+
await inTransaction(run, (exec) => exec(`COMMENT ON INDEX ${root} IS ${pgString(comment)}`));
|
|
405
|
+
}
|
|
406
|
+
const tree = (await partitionTree(run.client, c.tableOid)).filter((p) => p.oid !== c.tableOid);
|
|
407
|
+
const byOid = new Map(tree.map((p) => [p.oid, p]));
|
|
408
|
+
const ours = (p: { schema: string; name: string }) => `${quoteIdent(p.schema)}.${quoteIdent(partitionIndexName(p, c.working))}`;
|
|
409
|
+
const rootIndex = { oid: (await oidOf(root))!, ident: root };
|
|
410
|
+
/** The index a partition's index attaches to: the partitioned index above it, as it is now. */
|
|
411
|
+
const above = async (p: { parent?: string }): Promise<{ oid: string; ident: string }> => {
|
|
412
|
+
if (p.parent === c.tableOid || p.parent === undefined) return rootIndex;
|
|
413
|
+
const parent = byOid.get(p.parent)!;
|
|
414
|
+
const up = await above(parent);
|
|
415
|
+
return (await attachedIndex(up.oid, parent.oid)) ?? { oid: (await oidOf(ours(parent)))!, ident: ours(parent) };
|
|
416
|
+
};
|
|
417
|
+
// Top down: each partition without an index attached to its parent's gets a standalone one.
|
|
418
|
+
const pending: Array<{ p: (typeof tree)[number]; parent: { oid: string; ident: string } }> = [];
|
|
419
|
+
for (const p of tree) {
|
|
420
|
+
const parent = await above(p);
|
|
421
|
+
if (await attachedIndex(parent.oid, p.oid)) continue;
|
|
422
|
+
const ident = ours(p);
|
|
423
|
+
const cur = await one<{ valid: boolean; comment: string | null }>(
|
|
424
|
+
"SELECT i.indisvalid AS valid, pg_catalog.obj_description(i.indexrelid, 'pg_class') AS comment FROM pg_catalog.pg_index i WHERE i.indexrelid = pg_catalog.to_regclass($1)",
|
|
425
|
+
[ident],
|
|
426
|
+
);
|
|
427
|
+
if (cur && !cur.valid) await outsideTransaction(run, `DROP INDEX CONCURRENTLY IF EXISTS ${ident}`);
|
|
428
|
+
if (!cur || !cur.valid) {
|
|
429
|
+
const statement = partitionIndexStatement(c, p, partitionIndexName(p, c.working));
|
|
430
|
+
const dropInvalid = async () => {
|
|
431
|
+
const row = await one<{ invalid: boolean }>("SELECT NOT i.indisvalid AS invalid FROM pg_catalog.pg_index i WHERE i.indexrelid = pg_catalog.to_regclass($1)", [ident]);
|
|
432
|
+
if (row?.invalid) await run.client.query(`DROP INDEX CONCURRENTLY IF EXISTS ${ident}`);
|
|
433
|
+
};
|
|
434
|
+
if (p.leaf) await outsideTransaction(run, statement, { scan: true, cleanup: dropInvalid });
|
|
435
|
+
else await inTransaction(run, (exec) => exec(statement));
|
|
436
|
+
}
|
|
437
|
+
if (!cur || !cur.valid || cur.comment == null) {
|
|
438
|
+
const partitionComment = workingComment(n, run.marker, "carry", `${c.kind} ${c.name} on the new column, for partition ${p.name}`, { of: c.name });
|
|
439
|
+
await inTransaction(run, (exec) => exec(`COMMENT ON INDEX ${ident} IS ${pgString(partitionComment)}`));
|
|
440
|
+
}
|
|
441
|
+
pending.push({ p, parent });
|
|
442
|
+
}
|
|
443
|
+
// Bottom up: a partition's index is valid once its own partitions are attached.
|
|
444
|
+
for (const { p, parent } of [...pending].sort((a, b) => b.p.level - a.p.level)) {
|
|
445
|
+
await inTransaction(run, (exec) => exec(`ALTER INDEX ${parent.ident} ATTACH PARTITION ${ours(p)}`));
|
|
446
|
+
}
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
|
|
343
450
|
// ── switch ─────────────────────────────────────────────────────────────
|
|
344
451
|
|
|
345
452
|
export interface SwitchResult {
|
|
@@ -373,13 +480,17 @@ export interface SequenceState {
|
|
|
373
480
|
* `serial` column's sequence is handed over with `OWNED BY`, takes the
|
|
374
481
|
* declared integer type, and becomes the new column's default.
|
|
375
482
|
*/
|
|
376
|
-
function sequenceStatements(o: MigrationObservation, state: SequenceState | undefined): { before: string[]; after: string[] } {
|
|
483
|
+
function sequenceStatements(o: MigrationObservation, state: SequenceState | undefined, at: "switch" | "contract" = "switch"): { before: string[]; after: string[] } {
|
|
377
484
|
const seq = o.sequence;
|
|
378
485
|
const n = o.names;
|
|
379
486
|
const t = n.qualifiedTable;
|
|
380
|
-
if (!seq || n.change !== "type") return { before: [], after: [] };
|
|
487
|
+
if (!seq || n.change !== (at === "switch" ? "type" : "rename")) return { before: [], after: [] };
|
|
488
|
+
if (at === "contract" && seq.kind === "serial") {
|
|
489
|
+
// The sequence goes to the new column before the old one is dropped, which would drop it; the default is set after.
|
|
490
|
+
return { before: [`ALTER SEQUENCE ${seq.ident} OWNED BY ${t}.${col(n.column)}`], after: [] };
|
|
491
|
+
}
|
|
381
492
|
if (seq.kind === "identity") {
|
|
382
|
-
if (!state) throw new Error(`${n.key}: the
|
|
493
|
+
if (!state) throw new Error(`${n.key}: the ${at} moves the identity of ${n.source} and needs its sequence's state, read under the switch's lock`);
|
|
383
494
|
const g = o.column.generated;
|
|
384
495
|
const options = [`SEQUENCE NAME ${seq.ident}`, ...(g?.options ? [g.options.trim().replace(/^\(|\)$/g, "")] : [])].join(" ");
|
|
385
496
|
return {
|
|
@@ -540,6 +651,31 @@ export const identityOf = (marker: OwnershipMarker | undefined): { stack?: strin
|
|
|
540
651
|
* catalog change under ACCESS EXCLUSIVE). The migration's receipts go with
|
|
541
652
|
* them. Before the date, say until when and drop nothing.
|
|
542
653
|
*/
|
|
654
|
+
/**
|
|
655
|
+
* The contract's statements. Always: the trigger and its function, then the
|
|
656
|
+
* old column. A rename of a column with a default, serial or identity
|
|
657
|
+
* (`defaultAtContract`) also gives the declared default to the new column
|
|
658
|
+
* here, in the same transaction, so no writer sees both columns defaulted:
|
|
659
|
+
* a serial's sequence is handed over before the old column is dropped (which
|
|
660
|
+
* would drop it), an identity is dropped from the old column and added to
|
|
661
|
+
* the new one under the same sequence name, set to where the old one stood.
|
|
662
|
+
*/
|
|
663
|
+
export function contractStatements(o: MigrationObservation, sequenceState?: SequenceState): string[] {
|
|
664
|
+
const n = o.names;
|
|
665
|
+
const t = n.qualifiedTable;
|
|
666
|
+
const old = o.old!;
|
|
667
|
+
const sequence = sequenceStatements(o, sequenceState, "contract");
|
|
668
|
+
const dflt = o.column.default ?? (o.sequence?.kind === "serial" ? old.default : undefined);
|
|
669
|
+
const out: string[] = [];
|
|
670
|
+
if (o.trigger) out.push(`DROP TRIGGER ${col(n.trigger)} ON ${t}`);
|
|
671
|
+
if (o.fn) out.push(`DROP FUNCTION ${n.qualifiedFn}()`);
|
|
672
|
+
out.push(...sequence.before);
|
|
673
|
+
out.push(`ALTER TABLE ${t} DROP COLUMN ${col(old.name)}`);
|
|
674
|
+
if (dflt !== undefined && n.change === "rename" && o.sequence?.kind !== "identity") out.push(`ALTER TABLE ${t} ALTER COLUMN ${col(n.column)} SET DEFAULT ${dflt}`);
|
|
675
|
+
out.push(...sequence.after);
|
|
676
|
+
return out;
|
|
677
|
+
}
|
|
678
|
+
|
|
543
679
|
export async function contract(run: MigrationRun): Promise<ContractResult> {
|
|
544
680
|
const plan = await retainPlan(run);
|
|
545
681
|
if (!plan.oldColumn) return { state: plan.state, dropped: false };
|
|
@@ -550,9 +686,14 @@ export async function contract(run: MigrationRun): Promise<ContractResult> {
|
|
|
550
686
|
const o = await observe(run);
|
|
551
687
|
const n = o.names;
|
|
552
688
|
await inTransaction(run, async (exec) => {
|
|
553
|
-
|
|
554
|
-
if (o.
|
|
555
|
-
|
|
689
|
+
let state: SequenceState | undefined;
|
|
690
|
+
if (o.sequence?.kind === "identity" && n.change === "rename") {
|
|
691
|
+
// Writers wait from here, so the sequence's position read next is where the moved identity carries on.
|
|
692
|
+
await exec(`LOCK TABLE ${n.qualifiedTable} IN ACCESS EXCLUSIVE MODE`);
|
|
693
|
+
const [row] = await exec(`SELECT last_value::text AS last, is_called AS called FROM ${o.sequence.ident}`);
|
|
694
|
+
state = { lastValue: String(row?.last), isCalled: row?.called === true };
|
|
695
|
+
}
|
|
696
|
+
for (const sql of contractStatements(o, state)) await exec(sql);
|
|
556
697
|
});
|
|
557
698
|
await postgresReceiptStore(run.client, n.schema, identityOf(run.marker)).forget(batchPrefix(identityOf(run.marker), n.key));
|
|
558
699
|
return { state: "done", dropped: true, oldColumn: plan.oldColumn };
|
|
@@ -619,7 +760,11 @@ export async function compensate(run: Pick<MigrationRun, "client" | "target" | "
|
|
|
619
760
|
).filter((c) => ours(c.comment, "carry"));
|
|
620
761
|
const carriedIndexes = (
|
|
621
762
|
await run.client.query<{ name: string; comment: string | null }>(
|
|
622
|
-
|
|
763
|
+
// The table's own indexes, and those on its partitions that no partitioned index has taken yet (an attached one goes with the partitioned index).
|
|
764
|
+
`SELECT i.indexrelid::pg_catalog.regclass::text AS name, pg_catalog.obj_description(i.indexrelid, 'pg_class') AS comment
|
|
765
|
+
FROM pg_catalog.pg_index i
|
|
766
|
+
WHERE (i.indrelid = $1::oid OR (i.indrelid IN (SELECT t.relid FROM pg_catalog.pg_partition_tree($1::oid::pg_catalog.regclass) t) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_inherits h WHERE h.inhrelid = i.indexrelid)))
|
|
767
|
+
ORDER BY i.indrelid <> $1::oid, 1`,
|
|
623
768
|
[rel.oid],
|
|
624
769
|
)
|
|
625
770
|
).filter((c) => ours(c.comment, "carry"));
|
|
@@ -643,7 +788,7 @@ export async function compensate(run: Pick<MigrationRun, "client" | "target" | "
|
|
|
643
788
|
dropped.push(`constraint ${c.name} on ${c.rel}`);
|
|
644
789
|
}
|
|
645
790
|
for (const c of carriedIndexes) {
|
|
646
|
-
await exec(`DROP INDEX ${c.name}`);
|
|
791
|
+
await exec(`DROP INDEX IF EXISTS ${c.name}`);
|
|
647
792
|
dropped.push(`index ${c.name}`);
|
|
648
793
|
}
|
|
649
794
|
for (const c of newColumns) {
|
package/src/postgres/parser.ts
CHANGED
|
@@ -271,6 +271,69 @@ export interface EventTriggerNode {
|
|
|
271
271
|
function: NameNode;
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
+
/** `CREATE OPERATOR [schema.]symbol (FUNCTION = f, LEFTARG = type, RIGHTARG = type, ...)`. */
|
|
275
|
+
export interface OperatorNode {
|
|
276
|
+
statement: "operator";
|
|
277
|
+
/** The schema, when written, then the operator's symbol. */
|
|
278
|
+
name: NameNode;
|
|
279
|
+
options: DefElem[];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** `CREATE STATISTICS [IF NOT EXISTS] name [(kind, ...)] ON target, ... FROM table`. */
|
|
283
|
+
export interface StatisticsNode {
|
|
284
|
+
statement: "statistics";
|
|
285
|
+
ifNotExists: boolean;
|
|
286
|
+
name: NameNode;
|
|
287
|
+
/** `ndistinct`, `dependencies`, `mcv`, folded; empty when the server picks them all. */
|
|
288
|
+
kinds: string[];
|
|
289
|
+
/** Each column or parenthesized expression. */
|
|
290
|
+
targets: Span[];
|
|
291
|
+
table: NameNode;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** One `name 'value'` of an `OPTIONS (...)` list. */
|
|
295
|
+
export interface GenericOption {
|
|
296
|
+
name: string;
|
|
297
|
+
nameSpan: Span;
|
|
298
|
+
value: Span;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** `CREATE FOREIGN DATA WRAPPER name [HANDLER f | NO HANDLER] [VALIDATOR f | NO VALIDATOR] [OPTIONS (...)]`. */
|
|
302
|
+
export interface ForeignDataWrapperNode {
|
|
303
|
+
statement: "foreignDataWrapper";
|
|
304
|
+
name: NameNode;
|
|
305
|
+
/** The handler function; `null` for `NO HANDLER`. */
|
|
306
|
+
handler?: NameNode | null;
|
|
307
|
+
/** The validator function; `null` for `NO VALIDATOR`. */
|
|
308
|
+
validator?: NameNode | null;
|
|
309
|
+
options: GenericOption[];
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** `CREATE SERVER [IF NOT EXISTS] name [TYPE 't'] [VERSION 'v'] FOREIGN DATA WRAPPER w [OPTIONS (...)]`. */
|
|
313
|
+
export interface ForeignServerNode {
|
|
314
|
+
statement: "foreignServer";
|
|
315
|
+
ifNotExists: boolean;
|
|
316
|
+
name: NameNode;
|
|
317
|
+
type?: Span;
|
|
318
|
+
version?: Span;
|
|
319
|
+
wrapper: NameNode;
|
|
320
|
+
options: GenericOption[];
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** One member of a publication: a table (with its column list and row filter) or every table of a schema. */
|
|
324
|
+
export type PublicationItem =
|
|
325
|
+
| { kind: "table"; table: NameNode; columns?: Array<{ name: string; span: Span }>; where?: Span }
|
|
326
|
+
| { kind: "schema"; schema: NameNode };
|
|
327
|
+
|
|
328
|
+
/** `CREATE PUBLICATION name [FOR ALL TABLES | FOR TABLE ... | FOR TABLES IN SCHEMA ...] [WITH (...)]`. */
|
|
329
|
+
export interface PublicationNode {
|
|
330
|
+
statement: "publication";
|
|
331
|
+
name: NameNode;
|
|
332
|
+
allTables: boolean;
|
|
333
|
+
items: PublicationItem[];
|
|
334
|
+
options: DefElem[];
|
|
335
|
+
}
|
|
336
|
+
|
|
274
337
|
export interface DomainNode {
|
|
275
338
|
statement: "domain";
|
|
276
339
|
name: NameNode;
|
|
@@ -418,6 +481,11 @@ export type StatementNode =
|
|
|
418
481
|
| CollationNode
|
|
419
482
|
| CastNode
|
|
420
483
|
| EventTriggerNode
|
|
484
|
+
| OperatorNode
|
|
485
|
+
| StatisticsNode
|
|
486
|
+
| ForeignDataWrapperNode
|
|
487
|
+
| ForeignServerNode
|
|
488
|
+
| PublicationNode
|
|
421
489
|
| DomainNode
|
|
422
490
|
| ExtensionNode
|
|
423
491
|
| RoutineNode
|
|
@@ -503,6 +571,15 @@ export const AGGREGATE_OPTIONS = [
|
|
|
503
571
|
/** `CREATE COLLATION`'s options (sql-createcollation). */
|
|
504
572
|
export const COLLATION_OPTIONS = ["locale", "lc_collate", "lc_ctype", "provider", "deterministic", "rules", "version"];
|
|
505
573
|
|
|
574
|
+
/** `CREATE OPERATOR`'s options (sql-createoperator); `procedure` is the old spelling of `function`. */
|
|
575
|
+
export const OPERATOR_OPTIONS = ["function", "procedure", "leftarg", "rightarg", "commutator", "negator", "restrict", "join", "hashes", "merges"];
|
|
576
|
+
|
|
577
|
+
/** `CREATE STATISTICS`'s kinds (sql-createstatistics). */
|
|
578
|
+
export const STATISTICS_KINDS = ["ndistinct", "dependencies", "mcv"];
|
|
579
|
+
|
|
580
|
+
/** `CREATE PUBLICATION ... WITH (...)`'s options (sql-createpublication); `publish_generated_columns` is 18's. */
|
|
581
|
+
export const PUBLICATION_OPTIONS = ["publish", "publish_via_partition_root", "publish_generated_columns"];
|
|
582
|
+
|
|
506
583
|
/** What may follow `IS` inside a `b_expr`. */
|
|
507
584
|
const B_EXPR_IS = [["DISTINCT", "FROM"], ["NOT", "DISTINCT", "FROM"], ["DOCUMENT"], ["NOT", "DOCUMENT"]];
|
|
508
585
|
|
|
@@ -1012,6 +1089,11 @@ class PgParser extends SqlCursor {
|
|
|
1012
1089
|
if (this.accept("COLLATION")) return this.collation();
|
|
1013
1090
|
if (this.accept("CAST")) return this.cast();
|
|
1014
1091
|
if (this.acceptSeq("EVENT", "TRIGGER")) return this.eventTrigger();
|
|
1092
|
+
if (this.accept("OPERATOR")) return this.operator();
|
|
1093
|
+
if (this.accept("STATISTICS")) return this.statistics();
|
|
1094
|
+
if (this.acceptSeq("FOREIGN", "DATA", "WRAPPER")) return this.foreignDataWrapper();
|
|
1095
|
+
if (this.accept("SERVER")) return this.foreignServer();
|
|
1096
|
+
if (this.accept("PUBLICATION")) return this.publication();
|
|
1015
1097
|
if (kw(this.peek(), "UNIQUE", "INDEX")) return this.index();
|
|
1016
1098
|
}
|
|
1017
1099
|
const persistence = this.persistence();
|
|
@@ -1021,7 +1103,7 @@ class PgParser extends SqlCursor {
|
|
|
1021
1103
|
const recursive = this.accept("RECURSIVE");
|
|
1022
1104
|
if (!orReplace && !recursive && persistence === undefined && this.acceptSeq("MATERIALIZED", "VIEW")) return this.view(true, false, false, false);
|
|
1023
1105
|
if (this.accept("VIEW")) return this.view(false, orReplace, persistence === "temporary", recursive);
|
|
1024
|
-
return this.fail("expected SCHEMA, TABLE, INDEX, VIEW, MATERIALIZED VIEW, SEQUENCE, TYPE, DOMAIN, EXTENSION, FUNCTION, PROCEDURE, AGGREGATE, TRIGGER, EVENT TRIGGER, COLLATION, CAST, POLICY or ROLE");
|
|
1106
|
+
return this.fail("expected SCHEMA, TABLE, INDEX, VIEW, MATERIALIZED VIEW, SEQUENCE, TYPE, DOMAIN, EXTENSION, FUNCTION, PROCEDURE, AGGREGATE, TRIGGER, EVENT TRIGGER, COLLATION, CAST, OPERATOR, STATISTICS, FOREIGN DATA WRAPPER, SERVER, PUBLICATION, POLICY or ROLE");
|
|
1025
1107
|
}
|
|
1026
1108
|
|
|
1027
1109
|
private schema(): SchemaNode {
|
|
@@ -1239,6 +1321,145 @@ class PgParser extends SqlCursor {
|
|
|
1239
1321
|
return node;
|
|
1240
1322
|
}
|
|
1241
1323
|
|
|
1324
|
+
/** An unqualified, database-wide object's name (a wrapper, a server, a publication). */
|
|
1325
|
+
private databaseName(what: string): NameNode {
|
|
1326
|
+
const { text, span } = this.nameTok(what);
|
|
1327
|
+
if (this.isPunct(".")) this.fail(`expected ${what} without a schema (it belongs to the database)`);
|
|
1328
|
+
return { pieces: [text], span };
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
/** `( name 'value' [, ...] )`: a foreign data wrapper's or server's options. */
|
|
1332
|
+
private genericOptions(): GenericOption[] {
|
|
1333
|
+
this.expectPunct("(");
|
|
1334
|
+
const out: GenericOption[] = [];
|
|
1335
|
+
const seen = new Set<string>();
|
|
1336
|
+
for (;;) {
|
|
1337
|
+
const t = this.next();
|
|
1338
|
+
if (t.kind !== "ident" && t.kind !== "qident") this.fail("expected an option name", t);
|
|
1339
|
+
const nameSpan = this.span(this.idx(-1), []);
|
|
1340
|
+
const name = identValue(t);
|
|
1341
|
+
if (seen.has(name)) this.fail(`${name} is given twice`, t);
|
|
1342
|
+
seen.add(name);
|
|
1343
|
+
const v = this.next();
|
|
1344
|
+
if (v.kind !== "string" && v.kind !== "ref") this.fail("expected the option's value, a string", v);
|
|
1345
|
+
out.push({ name, nameSpan, value: this.span(this.idx(-1), v.kind === "ref" ? [v.part] : []) });
|
|
1346
|
+
if (this.acceptPunct(",")) continue;
|
|
1347
|
+
this.expectPunct(")");
|
|
1348
|
+
return out;
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
/** `[schema.]symbol`: an operator's name, its symbol made of operator characters. */
|
|
1353
|
+
private operatorName(): NameNode {
|
|
1354
|
+
const from = this.idx();
|
|
1355
|
+
const refs: number[] = [];
|
|
1356
|
+
const pieces: string[] = [];
|
|
1357
|
+
const symbol = (): void => {
|
|
1358
|
+
const t = this.next();
|
|
1359
|
+
if (t.kind !== "op") this.fail("expected the operator's symbol (operator characters, such as ===)", t);
|
|
1360
|
+
pieces.push(t.text);
|
|
1361
|
+
};
|
|
1362
|
+
if (this.peek()?.kind === "op") symbol();
|
|
1363
|
+
else {
|
|
1364
|
+
const t = this.next();
|
|
1365
|
+
this.colId(t, "a schema name");
|
|
1366
|
+
if (t.kind === "ref") refs.push(t.part);
|
|
1367
|
+
pieces.push(t.kind === "ref" ? "" : identValue(t));
|
|
1368
|
+
this.expectPunct(".");
|
|
1369
|
+
symbol();
|
|
1370
|
+
}
|
|
1371
|
+
return { pieces, span: this.span(from, refs) };
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
private operator(): OperatorNode {
|
|
1375
|
+
if (kw(this.peek(), "CLASS", "FAMILY") && !this.isPunct(".", 1)) this.fail("expected the operator's name (CREATE OPERATOR CLASS and CREATE OPERATOR FAMILY are not declared)");
|
|
1376
|
+
const name = this.operatorName();
|
|
1377
|
+
const options = this.definition("an operator option");
|
|
1378
|
+
this.checkDefinition(options, OPERATOR_OPTIONS, [], "an operator");
|
|
1379
|
+
if (!options.some((o) => o.name === "function" || o.name === "procedure")) this.fail("expected FUNCTION (an operator needs the function it calls)", this.lastSeen());
|
|
1380
|
+
return { statement: "operator", name, options };
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
private statistics(): StatisticsNode {
|
|
1384
|
+
const ifNotExists = this.ifNotExists();
|
|
1385
|
+
const name = this.qualifiedName("a statistics name");
|
|
1386
|
+
const kinds: string[] = [];
|
|
1387
|
+
if (this.isPunct("(")) {
|
|
1388
|
+
for (const k of this.nameList("a statistics kind")) {
|
|
1389
|
+
if (!STATISTICS_KINDS.includes(k.name)) this.fail(`expected a statistics kind (${STATISTICS_KINDS.join(", ")})`, this.tokens[k.span.from]);
|
|
1390
|
+
kinds.push(k.name);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
this.expect("ON");
|
|
1394
|
+
const targets: Span[] = [];
|
|
1395
|
+
do targets.push(this.expr(["FROM"]));
|
|
1396
|
+
while (this.acceptPunct(","));
|
|
1397
|
+
this.expect("FROM");
|
|
1398
|
+
const table = this.qualifiedName("a table name");
|
|
1399
|
+
return { statement: "statistics", ifNotExists, name, kinds, targets, table };
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
private foreignDataWrapper(): ForeignDataWrapperNode {
|
|
1403
|
+
const node: ForeignDataWrapperNode = { statement: "foreignDataWrapper", name: this.databaseName("a foreign data wrapper name"), options: [] };
|
|
1404
|
+
for (;;) {
|
|
1405
|
+
if (this.accept("HANDLER")) node.handler = this.qualifiedName("a handler function");
|
|
1406
|
+
else if (this.acceptSeq("NO", "HANDLER")) node.handler = null;
|
|
1407
|
+
else if (this.accept("VALIDATOR")) node.validator = this.qualifiedName("a validator function");
|
|
1408
|
+
else if (this.acceptSeq("NO", "VALIDATOR")) node.validator = null;
|
|
1409
|
+
else if (this.accept("OPTIONS")) node.options = this.genericOptions();
|
|
1410
|
+
else return node;
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
private foreignServer(): ForeignServerNode {
|
|
1415
|
+
const ifNotExists = this.ifNotExists();
|
|
1416
|
+
const name = this.databaseName("a server name");
|
|
1417
|
+
const node: Partial<ForeignServerNode> & { statement: "foreignServer" } = { statement: "foreignServer", ifNotExists, name, options: [] };
|
|
1418
|
+
const string = (what: string): Span => {
|
|
1419
|
+
const t = this.next();
|
|
1420
|
+
if (t.kind !== "string" && t.kind !== "ref") this.fail(`expected ${what} as a string`, t);
|
|
1421
|
+
return this.span(this.idx(-1), t.kind === "ref" ? [t.part] : []);
|
|
1422
|
+
};
|
|
1423
|
+
if (this.accept("TYPE")) node.type = string("the server type");
|
|
1424
|
+
if (this.accept("VERSION")) node.version = string("the server version");
|
|
1425
|
+
if (!this.acceptSeq("FOREIGN", "DATA", "WRAPPER")) this.fail("expected FOREIGN DATA WRAPPER (a server names the wrapper it is for)");
|
|
1426
|
+
node.wrapper = this.qualifiedName("a foreign data wrapper name");
|
|
1427
|
+
if (this.accept("OPTIONS")) node.options = this.genericOptions();
|
|
1428
|
+
return node as ForeignServerNode;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
private publication(): PublicationNode {
|
|
1432
|
+
const node: PublicationNode = { statement: "publication", name: this.databaseName("a publication name"), allTables: false, items: [], options: [] };
|
|
1433
|
+
if (this.accept("FOR")) {
|
|
1434
|
+
if (this.acceptSeq("ALL", "TABLES")) node.allTables = true;
|
|
1435
|
+
else {
|
|
1436
|
+
let mode: "table" | "schema" | undefined;
|
|
1437
|
+
do {
|
|
1438
|
+
if (this.accept("TABLE")) mode = "table";
|
|
1439
|
+
else if (this.acceptSeq("TABLES", "IN", "SCHEMA")) mode = "schema";
|
|
1440
|
+
else if (mode === undefined) this.fail("expected ALL TABLES, TABLE or TABLES IN SCHEMA");
|
|
1441
|
+
if (mode === "schema") {
|
|
1442
|
+
node.items.push({ kind: "schema", schema: this.qualifiedName("a schema name") });
|
|
1443
|
+
continue;
|
|
1444
|
+
}
|
|
1445
|
+
this.accept("ONLY");
|
|
1446
|
+
const table = this.qualifiedName("a table name");
|
|
1447
|
+
if (this.isOp("*")) this.p++;
|
|
1448
|
+
const item: PublicationItem = { kind: "table", table };
|
|
1449
|
+
if (this.isPunct("(")) item.columns = this.nameList("a column name");
|
|
1450
|
+
if (this.accept("WHERE")) item.where = this.checkedParens();
|
|
1451
|
+
node.items.push(item);
|
|
1452
|
+
} while (this.acceptPunct(","));
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
if (kw(this.peek(), "WITH") && this.isPunct("(", 1)) {
|
|
1456
|
+
this.p++;
|
|
1457
|
+
node.options = this.definition("a publication option");
|
|
1458
|
+
this.checkDefinition(node.options, PUBLICATION_OPTIONS, [], "a publication");
|
|
1459
|
+
}
|
|
1460
|
+
return node;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1242
1463
|
private table(persistence: TableNode["persistence"]): TableNode {
|
|
1243
1464
|
const ifNotExists = this.ifNotExists();
|
|
1244
1465
|
const node: TableNode = { statement: "table", persistence, ifNotExists, name: this.qualifiedName("a table name"), columns: [], constraints: [], like: [] };
|
|
@@ -1850,6 +2071,17 @@ class PgParser extends SqlCursor {
|
|
|
1850
2071
|
const node: CommentNode = { statement: "comment", objectType, target, cast: { source, target: castTarget } };
|
|
1851
2072
|
if (!this.accept("NULL")) node.text = this.commentText();
|
|
1852
2073
|
return node;
|
|
2074
|
+
} else if (this.acceptSeq("FOREIGN", "DATA", "WRAPPER")) {
|
|
2075
|
+
objectType = "FOREIGN DATA WRAPPER";
|
|
2076
|
+
target = this.qualifiedName("a foreign data wrapper name");
|
|
2077
|
+
} else if (this.accept("OPERATOR")) {
|
|
2078
|
+
objectType = "OPERATOR";
|
|
2079
|
+
target = this.operatorName();
|
|
2080
|
+
if (!this.isPunct("(")) this.fail("expected '(' and the operator's operand types");
|
|
2081
|
+
args = this.parenthesized();
|
|
2082
|
+
} else if (this.accept("SERVER", "PUBLICATION", "STATISTICS")) {
|
|
2083
|
+
objectType = this.lastSeen()!.text.toUpperCase();
|
|
2084
|
+
target = this.qualifiedName(`a ${objectType.toLowerCase()} name`);
|
|
1853
2085
|
} else if (this.accept("AGGREGATE", "COLLATION")) {
|
|
1854
2086
|
objectType = this.lastSeen()!.text.toUpperCase();
|
|
1855
2087
|
target = this.qualifiedName(`an ${objectType.toLowerCase()} name`);
|
|
@@ -1861,7 +2093,7 @@ class PgParser extends SqlCursor {
|
|
|
1861
2093
|
if (this.acceptSeq("MATERIALIZED", "VIEW")) objectType = "MATERIALIZED VIEW";
|
|
1862
2094
|
else if (this.accept("TABLE", "COLUMN", "INDEX", "VIEW", "SEQUENCE", "TYPE", "DOMAIN", "SCHEMA", "EXTENSION", "FUNCTION", "PROCEDURE")) {
|
|
1863
2095
|
objectType = this.tokens[this.idx(-1)]!.text.toUpperCase();
|
|
1864
|
-
} else return this.fail("expected TABLE, COLUMN, INDEX, VIEW, MATERIALIZED VIEW, SEQUENCE, TYPE, DOMAIN, SCHEMA, EXTENSION, FUNCTION, PROCEDURE, AGGREGATE, COLLATION, CAST, TRIGGER, EVENT TRIGGER, POLICY, ROLE or CONSTRAINT");
|
|
2096
|
+
} else return this.fail("expected TABLE, COLUMN, INDEX, VIEW, MATERIALIZED VIEW, SEQUENCE, TYPE, DOMAIN, SCHEMA, EXTENSION, FUNCTION, PROCEDURE, AGGREGATE, COLLATION, CAST, OPERATOR, TRIGGER, EVENT TRIGGER, STATISTICS, FOREIGN DATA WRAPPER, SERVER, PUBLICATION, POLICY, ROLE or CONSTRAINT");
|
|
1865
2097
|
target = this.qualifiedName("an object name");
|
|
1866
2098
|
if ((objectType === "FUNCTION" || objectType === "PROCEDURE") && this.isPunct("(")) args = this.parenthesized();
|
|
1867
2099
|
}
|
|
@@ -43,6 +43,11 @@ const TAG: Record<string, (s: TemplateStringsArray) => { props: object }> = {
|
|
|
43
43
|
[POSTGRES_ENTITY_TYPES.collation]: tags.collation,
|
|
44
44
|
[POSTGRES_ENTITY_TYPES.cast]: tags.cast,
|
|
45
45
|
[POSTGRES_ENTITY_TYPES.eventTrigger]: tags.eventTrigger,
|
|
46
|
+
[POSTGRES_ENTITY_TYPES.operator]: tags.operator,
|
|
47
|
+
[POSTGRES_ENTITY_TYPES.statistics]: tags.statistics,
|
|
48
|
+
[POSTGRES_ENTITY_TYPES.foreignDataWrapper]: tags.foreignDataWrapper,
|
|
49
|
+
[POSTGRES_ENTITY_TYPES.foreignServer]: tags.foreignServer,
|
|
50
|
+
[POSTGRES_ENTITY_TYPES.publication]: tags.publication,
|
|
46
51
|
[POSTGRES_ENTITY_TYPES.extension]: tags.extension,
|
|
47
52
|
[POSTGRES_ENTITY_TYPES.function]: tags.func,
|
|
48
53
|
[POSTGRES_ENTITY_TYPES.procedure]: tags.procedure,
|
|
@@ -137,6 +142,18 @@ const PROP_OF_FIELD: Record<string, string[]> = {
|
|
|
137
142
|
context: ["context"],
|
|
138
143
|
event: ["event"],
|
|
139
144
|
tags: ["tags"],
|
|
145
|
+
targets: ["targets"],
|
|
146
|
+
kinds: ["kinds"],
|
|
147
|
+
handler: ["handler", "handlerName", "noHandler"],
|
|
148
|
+
validator: ["validator", "validatorName", "noValidator"],
|
|
149
|
+
serverType: ["serverType"],
|
|
150
|
+
wrapper: ["wrapper", "wrapperName"],
|
|
151
|
+
allTables: ["allTables"],
|
|
152
|
+
tables: ["tables"],
|
|
153
|
+
schemas: ["schemas"],
|
|
154
|
+
publish: [],
|
|
155
|
+
viaRoot: [],
|
|
156
|
+
generatedColumns: [],
|
|
140
157
|
};
|
|
141
158
|
|
|
142
159
|
/** Props keys that hold constraints, by constraint kind. */
|
|
@@ -159,6 +176,7 @@ export function inDeclaredVocabulary(declared: Props, live: Props, d: CanonicalP
|
|
|
159
176
|
if (d.schema === l.schema) adopt("schema");
|
|
160
177
|
// An aggregate's argument types and a cast's two types are its identity: the same, they are the declaration's.
|
|
161
178
|
if (d.kind === "aggregate" && d.signature === l.signature) for (const key of ["args", "orderBy"]) adopt(key);
|
|
179
|
+
if (d.kind === "operator" && d.signature === l.signature) for (const key of ["leftArg", "rightArg"]) adopt(key);
|
|
162
180
|
if (d.kind === "cast" && d.name === l.name) for (const key of ["sourceType", "targetType"]) adopt(key);
|
|
163
181
|
|
|
164
182
|
const fieldKeys = new Set([...Object.keys(d.fields), ...Object.keys(l.fields)]);
|
|
@@ -193,7 +211,16 @@ export function inDeclaredVocabulary(declared: Props, live: Props, d: CanonicalP
|
|
|
193
211
|
].map((c) => c.value);
|
|
194
212
|
}
|
|
195
213
|
// An aggregate's or a collation's `options` is the declaration's only when every field read from it is the same (a role's `options` shares the key).
|
|
196
|
-
const optionFields =
|
|
214
|
+
const optionFields =
|
|
215
|
+
d.kind === "aggregate" || d.kind === "operator"
|
|
216
|
+
? ["definition"]
|
|
217
|
+
: d.kind === "collation"
|
|
218
|
+
? ["provider", "lcCollate", "lcCtype", "locale", "nondeterministic", "rules"]
|
|
219
|
+
: d.kind === "foreignDataWrapper" || d.kind === "foreignServer"
|
|
220
|
+
? ["options"]
|
|
221
|
+
: d.kind === "publication"
|
|
222
|
+
? ["publish", "viaRoot", "generatedColumns"]
|
|
223
|
+
: undefined;
|
|
197
224
|
if (optionFields) {
|
|
198
225
|
if (optionFields.every((f) => JSON.stringify(d.fields[f]) === JSON.stringify(l.fields[f]))) adopt("options");
|
|
199
226
|
else out.options = live.options;
|