@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,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Postgres statement classifier (#4407): a migration file's statements,
|
|
3
|
+
* each given the class and rule the schema-diff classifier (`./rules.ts`)
|
|
4
|
+
* gives the same change, with the lock the rule's restriction names. It reads
|
|
5
|
+
* text and contacts no server.
|
|
6
|
+
*
|
|
7
|
+
* `CREATE`, `GRANT`, `REVOKE`, `COMMENT ON` and `ALTER TABLE ... ROW LEVEL
|
|
8
|
+
* SECURITY` go through the declaration parser (`../parser.ts`). The parser
|
|
9
|
+
* has no `ALTER` grammar, so the other `ALTER` and `DROP` statements are read
|
|
10
|
+
* word by word from the same tokens.
|
|
11
|
+
*
|
|
12
|
+
* A statement alone cannot decide some changes. `ALTER COLUMN ... TYPE` is a
|
|
13
|
+
* catalog change or a table rewrite by the column's old type, which the file
|
|
14
|
+
* does not state: it is `undecided`, naming SQLPG206 and SQLPG207 (and
|
|
15
|
+
* SQLPG208 with a `USING` expression). `CREATE OR REPLACE VIEW` is SQLPG250
|
|
16
|
+
* or SQLPG251 by whether the columns stay. A statement no rule covers is
|
|
17
|
+
* `unclassified` with a note.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { isTrivia, tokenizeText, type Token } from "../tokens";
|
|
21
|
+
import { parseStatements, type StatementNode } from "../parser";
|
|
22
|
+
import { classifiedOf, lockOf, splitTopLevel, sqlHead, type StatementClassified, type StatementVerdict } from "../../core/statement-class";
|
|
23
|
+
import { PG_CHANGE_CLASSES, PG_CLASSIFIER_RULES, type PgClassifierRuleId } from "./rules";
|
|
24
|
+
|
|
25
|
+
const classified = (id: PgClassifierRuleId, extra: Partial<StatementClassified> = {}): StatementClassified => {
|
|
26
|
+
const lock = lockOf(PG_CLASSIFIER_RULES[id].restriction);
|
|
27
|
+
return classifiedOf(PG_CLASSIFIER_RULES, PG_CHANGE_CLASSES.label, id, { ...(lock ? { lock } : {}), ...extra });
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** What one clause of a statement comes to. */
|
|
31
|
+
type Reading = { rules: Array<{ id: PgClassifierRuleId; extra?: Partial<StatementClassified> }> } | { undecided: PgClassifierRuleId[]; note: string } | { none: string } | { data: string };
|
|
32
|
+
|
|
33
|
+
const rule = (id: PgClassifierRuleId, extra?: Partial<StatementClassified>): Reading => ({ rules: [{ id, ...(extra ? { extra } : {}) }] });
|
|
34
|
+
|
|
35
|
+
const VOLATILE = /\b(?:clock_timestamp|random|gen_random_uuid|uuid_generate_v\d\w*|nextval|timeofday|txid_current|setseed)\s*\(/i;
|
|
36
|
+
|
|
37
|
+
/** The words from `i` that are one possibly qualified name, and the index after it. */
|
|
38
|
+
function nameAt(words: readonly string[], i: number): { name: string; next: number } {
|
|
39
|
+
let name = words[i] ?? "";
|
|
40
|
+
let j = i + 1;
|
|
41
|
+
while (words[j] === "." && words[j + 1] !== undefined) {
|
|
42
|
+
name += `.${words[j + 1]}`;
|
|
43
|
+
j += 2;
|
|
44
|
+
}
|
|
45
|
+
return { name, next: j };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const bare = (name: string): string => (name.split(".").pop() ?? name).replace(/^"|"$/g, "").toLowerCase();
|
|
49
|
+
|
|
50
|
+
const has = (u: readonly string[], ...seq: string[]): boolean => u.some((_, i) => seq.every((w, k) => u[i + k] === w));
|
|
51
|
+
|
|
52
|
+
/** One `ALTER TABLE` action (the words between top-level commas). */
|
|
53
|
+
function tableAction(a: readonly string[]): Reading {
|
|
54
|
+
const u = a.map((w) => w.toUpperCase());
|
|
55
|
+
const first = u[0];
|
|
56
|
+
if (first === "ADD") {
|
|
57
|
+
let i = 1;
|
|
58
|
+
if (u[i] === "COLUMN") i++;
|
|
59
|
+
if (u[i] === "CONSTRAINT" || ["PRIMARY", "UNIQUE", "CHECK", "FOREIGN", "EXCLUDE"].includes(u[i] ?? "")) {
|
|
60
|
+
let k = i;
|
|
61
|
+
if (u[k] === "CONSTRAINT") k += 2;
|
|
62
|
+
const kind = u[k];
|
|
63
|
+
const notValid = has(u, "NOT", "VALID");
|
|
64
|
+
if (kind === "NOT" && u[k + 1] === "NULL") return rule("SQLPG210");
|
|
65
|
+
if (notValid) return rule("SQLPG217", { note: `${kind ?? "constraint"} added NOT VALID; VALIDATE CONSTRAINT is SQLPG220` });
|
|
66
|
+
if (kind === "PRIMARY" || kind === "UNIQUE") return rule("SQLPG221", has(u, "USING", "INDEX") ? { note: "from an existing unique index, so no build under this lock" } : {});
|
|
67
|
+
if (kind === "CHECK") return rule("SQLPG218");
|
|
68
|
+
if (kind === "FOREIGN") return rule("SQLPG219");
|
|
69
|
+
if (kind === "EXCLUDE") return rule("SQLPG222");
|
|
70
|
+
return { none: "an ADD CONSTRAINT of a kind no rule names" };
|
|
71
|
+
}
|
|
72
|
+
if (u[i] === "IF" && u[i + 1] === "NOT" && u[i + 2] === "EXISTS") i += 3;
|
|
73
|
+
const hasDefault = u.includes("DEFAULT");
|
|
74
|
+
const defaultText = hasDefault ? a.slice(u.indexOf("DEFAULT") + 1).join(" ") : "";
|
|
75
|
+
const generated = has(u, "GENERATED");
|
|
76
|
+
const stored = u.includes("STORED") && generated;
|
|
77
|
+
const identity = u.includes("IDENTITY");
|
|
78
|
+
const notNull = has(u, "NOT", "NULL");
|
|
79
|
+
if (stored || identity) return rule("SQLPG202", { note: stored ? "a STORED generated column" : "an identity column" });
|
|
80
|
+
if (VOLATILE.test(defaultText)) return rule("SQLPG202", { note: "a volatile default" });
|
|
81
|
+
if (notNull && !hasDefault && !generated) return rule("SQLPG203");
|
|
82
|
+
return rule("SQLPG201");
|
|
83
|
+
}
|
|
84
|
+
if (first === "DROP") {
|
|
85
|
+
if (u[1] === "CONSTRAINT") return rule("SQLPG223");
|
|
86
|
+
return rule("SQLPG204", { destructive: true });
|
|
87
|
+
}
|
|
88
|
+
if (first === "RENAME") {
|
|
89
|
+
if (u[1] === "COLUMN" || (u[1] !== "CONSTRAINT" && u[1] !== "TO")) return rule("SQLPG205");
|
|
90
|
+
return rule("SQLPG228");
|
|
91
|
+
}
|
|
92
|
+
if (first === "ALTER") {
|
|
93
|
+
let i = 1;
|
|
94
|
+
if (u[i] === "COLUMN") i++;
|
|
95
|
+
if (u[i] === "CONSTRAINT") return { none: "ALTER CONSTRAINT (deferrability)" };
|
|
96
|
+
const rest = u.slice(i + 1);
|
|
97
|
+
const col = a[i] ?? "column";
|
|
98
|
+
if (rest[0] === "TYPE" || (rest[0] === "SET" && rest[1] === "DATA" && rest[2] === "TYPE")) {
|
|
99
|
+
const using = rest.includes("USING");
|
|
100
|
+
return {
|
|
101
|
+
undecided: using ? ["SQLPG206", "SQLPG207", "SQLPG208"] : ["SQLPG206", "SQLPG207"],
|
|
102
|
+
note: `the new type of ${col} is a catalog change or a table rewrite by its old type${using ? ", or a change across kinds with this USING expression," : ""}, which the file does not state`,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (rest[0] === "SET" && rest[1] === "DEFAULT") return rule("SQLPG209");
|
|
106
|
+
if (rest[0] === "DROP" && rest[1] === "DEFAULT") return rule("SQLPG209");
|
|
107
|
+
if (rest[0] === "SET" && rest[1] === "NOT" && rest[2] === "NULL") return rule("SQLPG210");
|
|
108
|
+
if (rest[0] === "DROP" && rest[1] === "NOT" && rest[2] === "NULL") return rule("SQLPG211");
|
|
109
|
+
if (rest[0] === "SET" && rest[1] === "EXPRESSION") return rule("SQLPG212");
|
|
110
|
+
if ((rest[0] === "ADD" && rest[1] === "GENERATED") || (rest[0] === "SET" && (rest[1] === "GENERATED" || rest[1] === "INCREMENT" || rest[1] === "START" || rest[1] === "RESTART")) || rest[0] === "RESTART" || (rest[0] === "DROP" && rest[1] === "IDENTITY")) {
|
|
111
|
+
return rule("SQLPG213");
|
|
112
|
+
}
|
|
113
|
+
if (rest[0] === "SET" && (rest[1] === "STORAGE" || rest[1] === "COMPRESSION")) return rule("SQLPG215");
|
|
114
|
+
return { none: `ALTER COLUMN ${rest.slice(0, 2).join(" ")}` };
|
|
115
|
+
}
|
|
116
|
+
if (first === "VALIDATE" && u[1] === "CONSTRAINT") return rule("SQLPG220");
|
|
117
|
+
if (first === "SET" || first === "RESET") {
|
|
118
|
+
if (u[1] === "LOGGED" || u[1] === "UNLOGGED") return rule("SQLPG225");
|
|
119
|
+
if (u[1] === "ACCESS" || u[1] === "TABLESPACE") return rule("SQLPG226");
|
|
120
|
+
if (u[1] === "SCHEMA") return rule("SQLPG228");
|
|
121
|
+
if (u[1] === "(" || first === "RESET") return rule("SQLPG224");
|
|
122
|
+
return { none: `ALTER TABLE ${u.slice(0, 2).join(" ")}` };
|
|
123
|
+
}
|
|
124
|
+
if (first === "ATTACH" || first === "DETACH" || first === "INHERIT" || (first === "NO" && u[1] === "INHERIT") || first === "OF" || (first === "NOT" && u[1] === "OF")) return rule("SQLPG227");
|
|
125
|
+
if ((first === "ENABLE" || first === "DISABLE" || first === "FORCE" || (first === "NO" && u[1] === "FORCE")) && has(u, "ROW", "LEVEL", "SECURITY")) return rule("SQLPG293");
|
|
126
|
+
if ((first === "ENABLE" || first === "DISABLE") && (u[1] === "TRIGGER" || u[1] === "REPLICA" || u[1] === "ALWAYS")) return rule("SQLPG284");
|
|
127
|
+
return { none: `ALTER TABLE ${u.slice(0, 2).join(" ")}` };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** `ALTER <object> ...` other than `TABLE`, by its second word. */
|
|
131
|
+
function alterOther(u: readonly string[], a: readonly string[]): Reading {
|
|
132
|
+
const obj = u[1];
|
|
133
|
+
const rename = u.includes("RENAME");
|
|
134
|
+
if (obj === "INDEX") return rename ? rule("SQLPG229") : rule("SQLPG243");
|
|
135
|
+
if (obj === "TYPE") {
|
|
136
|
+
if (has(u, "ADD", "VALUE")) return rule("SQLPG260");
|
|
137
|
+
if (has(u, "RENAME", "TO")) return rule("SQLPG228");
|
|
138
|
+
return { none: "ALTER TYPE other than ADD VALUE and RENAME TO" };
|
|
139
|
+
}
|
|
140
|
+
if (obj === "SEQUENCE") return rename || has(u, "SET", "SCHEMA") ? rule("SQLPG228") : has(u, "OWNER", "TO") ? { none: "ALTER SEQUENCE OWNER TO" } : rule("SQLPG265");
|
|
141
|
+
if (obj === "VIEW") return rename || has(u, "SET", "SCHEMA") ? rule("SQLPG228") : has(u, "OWNER", "TO") ? { none: "ALTER VIEW OWNER TO" } : rule("SQLPG253");
|
|
142
|
+
if (obj === "MATERIALIZED") return rename || has(u, "SET", "SCHEMA") ? rule("SQLPG228") : { none: "ALTER MATERIALIZED VIEW other than RENAME" };
|
|
143
|
+
if (obj === "SCHEMA") return rename ? rule("SQLPG228") : has(u, "OWNER", "TO") ? rule("SQLPG267") : { none: "ALTER SCHEMA" };
|
|
144
|
+
if (obj === "EXTENSION") return has(u, "UPDATE") || has(u, "SET", "SCHEMA") ? rule("SQLPG266") : { none: "ALTER EXTENSION ADD or DROP member" };
|
|
145
|
+
if (obj === "FUNCTION" || obj === "PROCEDURE" || obj === "ROUTINE") return rename || has(u, "SET", "SCHEMA") ? rule("SQLPG228") : { none: `ALTER ${obj} other than RENAME` };
|
|
146
|
+
if (obj === "TRIGGER") return rule("SQLPG284");
|
|
147
|
+
if (obj === "POLICY") return rename ? rule("SQLPG228") : rule("SQLPG291");
|
|
148
|
+
if (obj === "ROLE" || obj === "USER" || obj === "GROUP") return rule("SQLPG294");
|
|
149
|
+
if (obj === "DOMAIN") {
|
|
150
|
+
if (rename || has(u, "SET", "SCHEMA")) return rule("SQLPG228");
|
|
151
|
+
if (has(u, "ADD", "CONSTRAINT") || (u[3] === "ADD" && ["CHECK", "NOT"].includes(u[4] ?? ""))) return rule("SQLPG262");
|
|
152
|
+
if (has(u, "OWNER", "TO")) return { none: "ALTER DOMAIN OWNER TO" };
|
|
153
|
+
return rule("SQLPG263");
|
|
154
|
+
}
|
|
155
|
+
if (obj === "SERVER" || obj === "PUBLICATION" || (obj === "FOREIGN" && u[2] === "DATA")) return rename ? rule("SQLPG228") : rule("SQLPG303");
|
|
156
|
+
void a;
|
|
157
|
+
return { none: `ALTER ${obj ?? ""}`.trim() };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function dropStatement(u: readonly string[]): Reading {
|
|
161
|
+
const obj = u[1] === "MATERIALIZED" || u[1] === "FOREIGN" || u[1] === "EVENT" ? `${u[1]} ${u[2]}` : u[1];
|
|
162
|
+
if (obj === "INDEX") return rule("SQLPG242", u[2] === "CONCURRENTLY" ? { note: "DROP INDEX CONCURRENTLY waits for running queries instead of blocking them" } : {});
|
|
163
|
+
if (obj === "TRIGGER") return rule("SQLPG285", { destructive: false });
|
|
164
|
+
if (obj === "POLICY") return rule("SQLPG292", { destructive: false });
|
|
165
|
+
return rule("SQLPG270", { destructive: true });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** How a `CREATE` that the parser could not read starts: its kind by key words. */
|
|
169
|
+
function createByWords(u: readonly string[]): Reading {
|
|
170
|
+
let i = 1;
|
|
171
|
+
const orReplace = u[i] === "OR" && u[i + 1] === "REPLACE";
|
|
172
|
+
if (orReplace) i += 2;
|
|
173
|
+
while (["GLOBAL", "LOCAL", "TEMPORARY", "TEMP", "UNLOGGED", "RECURSIVE", "UNIQUE", "CONSTRAINT"].includes(u[i] ?? "") && u[i + 1] !== "INDEX" && u[i] !== "UNIQUE") i++;
|
|
174
|
+
if (u[i] === "UNIQUE") i++;
|
|
175
|
+
if (u[i] === "INDEX") {
|
|
176
|
+
const concurrently = u[i + 1] === "CONCURRENTLY";
|
|
177
|
+
return concurrently ? rule("SQLPG240") : rule("SQLPG241");
|
|
178
|
+
}
|
|
179
|
+
if (orReplace && (u[i] === "FUNCTION" || u[i] === "PROCEDURE")) return rule("SQLPG280");
|
|
180
|
+
if (orReplace && u[i] === "VIEW") return { undecided: ["SQLPG250", "SQLPG251"], note: "whether the view keeps its columns depends on the view it replaces" };
|
|
181
|
+
if (orReplace && u[i] === "AGGREGATE") return rule("SQLPG302");
|
|
182
|
+
if (u[i] === "TRIGGER") return rule("SQLPG283");
|
|
183
|
+
if (u[i] === "POLICY") return rule("SQLPG290");
|
|
184
|
+
return rule("SQLPG200");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** A statement the parser read: its rule, given the tables this file created earlier. */
|
|
188
|
+
function fromNode(n: StatementNode, created: Set<string>): Reading {
|
|
189
|
+
switch (n.statement) {
|
|
190
|
+
case "table":
|
|
191
|
+
created.add(bare(n.name.pieces.join(".")));
|
|
192
|
+
return rule("SQLPG200");
|
|
193
|
+
case "index":
|
|
194
|
+
if (n.concurrently) return rule("SQLPG240");
|
|
195
|
+
if (created.has(bare(n.table.pieces.join(".")))) return rule("SQLPG200", { note: "an index on a table this file creates, which has no rows to block" });
|
|
196
|
+
return rule("SQLPG241");
|
|
197
|
+
case "view":
|
|
198
|
+
if (n.orReplace && !n.materialized) return { undecided: ["SQLPG250", "SQLPG251"], note: "whether the view keeps its columns depends on the view it replaces" };
|
|
199
|
+
return rule("SQLPG200");
|
|
200
|
+
case "function":
|
|
201
|
+
case "procedure":
|
|
202
|
+
return n.orReplace ? rule("SQLPG280") : rule("SQLPG200");
|
|
203
|
+
case "aggregate":
|
|
204
|
+
return n.orReplace ? rule("SQLPG302") : rule("SQLPG200");
|
|
205
|
+
case "trigger":
|
|
206
|
+
return created.has(bare(n.table.pieces.join("."))) ? rule("SQLPG200", { note: "a trigger on a table this file creates" }) : rule("SQLPG283");
|
|
207
|
+
case "policy":
|
|
208
|
+
return created.has(bare(n.table.pieces.join("."))) ? rule("SQLPG200", { note: "a policy on a table this file creates" }) : rule("SQLPG290");
|
|
209
|
+
case "grant":
|
|
210
|
+
return n.defaults ? rule("SQLPG298") : n.revoke ? rule("SQLPG297") : rule("SQLPG296");
|
|
211
|
+
case "rowSecurity":
|
|
212
|
+
return rule("SQLPG293");
|
|
213
|
+
case "comment":
|
|
214
|
+
return rule("SQLPG216");
|
|
215
|
+
case "role":
|
|
216
|
+
return rule("SQLPG200");
|
|
217
|
+
default:
|
|
218
|
+
return rule("SQLPG200");
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const DATA_WORDS = new Set(["INSERT", "UPDATE", "DELETE", "SELECT", "WITH", "COPY", "CALL", "DO", "SET", "BEGIN", "START", "COMMIT", "END", "ROLLBACK", "SAVEPOINT", "RELEASE", "ANALYZE", "ANALYSE", "VACUUM", "REINDEX", "LOCK", "NOTIFY", "LISTEN", "RESET", "SHOW", "EXPLAIN", "VALUES", "MERGE", "REFRESH", "CLUSTER", "CHECKPOINT", "DISCARD", "PREPARE", "EXECUTE", "DEALLOCATE"]);
|
|
223
|
+
|
|
224
|
+
/** Classify one statement. `created` is the tables the file's earlier statements created. */
|
|
225
|
+
function read(sql: string, created: Set<string>): Reading[] {
|
|
226
|
+
let tokens: Token[];
|
|
227
|
+
try {
|
|
228
|
+
tokens = tokenizeText(sql, 0);
|
|
229
|
+
} catch {
|
|
230
|
+
return [{ none: "text the tokenizer could not read" }];
|
|
231
|
+
}
|
|
232
|
+
const words = tokens.filter((t) => !isTrivia(t) && t.text !== ";").map((t) => t.text);
|
|
233
|
+
const u = words.map((w) => w.toUpperCase());
|
|
234
|
+
const head = u[0];
|
|
235
|
+
if (head === undefined) return [{ data: "an empty statement" }];
|
|
236
|
+
|
|
237
|
+
if (head === "CREATE" || head === "GRANT" || head === "REVOKE" || head === "COMMENT" || (head === "ALTER" && (u[1] === "DEFAULT" || (u[1] === "TABLE" && has(u, "ROW", "LEVEL", "SECURITY"))))) {
|
|
238
|
+
try {
|
|
239
|
+
const nodes = parseStatements(tokens);
|
|
240
|
+
return nodes.map((n) => fromNode(n, created));
|
|
241
|
+
} catch {
|
|
242
|
+
if (head === "CREATE") return [createByWords(u)];
|
|
243
|
+
if (head === "GRANT") return [rule("SQLPG296")];
|
|
244
|
+
if (head === "REVOKE") return [rule("SQLPG297")];
|
|
245
|
+
if (head === "COMMENT") return [rule("SQLPG216")];
|
|
246
|
+
if (head === "ALTER" && u[1] === "DEFAULT") return [rule("SQLPG298")];
|
|
247
|
+
return [rule("SQLPG293")];
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (head === "ALTER" && u[1] === "TABLE") {
|
|
251
|
+
let i = 2;
|
|
252
|
+
if (u[i] === "IF" && u[i + 1] === "EXISTS") i += 2;
|
|
253
|
+
if (u[i] === "ONLY") i++;
|
|
254
|
+
const { name, next } = nameAt(words, i);
|
|
255
|
+
if (u[next] === "*") i = next + 1;
|
|
256
|
+
else i = next;
|
|
257
|
+
const actions = splitTopLevel(words.slice(i));
|
|
258
|
+
if (actions.length === 0) return [{ none: `ALTER TABLE ${name} with no action` }];
|
|
259
|
+
void name;
|
|
260
|
+
return actions.map(tableAction);
|
|
261
|
+
}
|
|
262
|
+
if (head === "ALTER") return [alterOther(u, words)];
|
|
263
|
+
if (head === "DROP") return [dropStatement(u)];
|
|
264
|
+
if (head === "TRUNCATE") return [{ data: "TRUNCATE removes every row of the table" }];
|
|
265
|
+
if (DATA_WORDS.has(head)) return [{ data: `${head} reads or writes rows or the session, not the schema` }];
|
|
266
|
+
return [{ none: `${head} is not a statement the classifier reads` }];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Classify a migration's up statements, in order. The statements are the
|
|
271
|
+
* file's own text, one per entry (a Flyway file after placeholder
|
|
272
|
+
* substitution).
|
|
273
|
+
*/
|
|
274
|
+
export function classifyPgStatements(statements: readonly string[]): StatementVerdict[] {
|
|
275
|
+
const created = new Set<string>();
|
|
276
|
+
return statements.map((sql, k) => {
|
|
277
|
+
const readings = read(sql, created);
|
|
278
|
+
const out: StatementVerdict = { index: k + 1, sql: sqlHead(sql), outcome: "classified", changes: [] };
|
|
279
|
+
const none: string[] = [];
|
|
280
|
+
for (const r of readings) {
|
|
281
|
+
if ("rules" in r) out.changes.push(...r.rules.map((x) => classified(x.id, x.extra)));
|
|
282
|
+
else if ("undecided" in r) {
|
|
283
|
+
out.candidates = r.undecided.map((id) => classified(id));
|
|
284
|
+
out.note = r.note;
|
|
285
|
+
} else if ("none" in r) none.push(r.none);
|
|
286
|
+
else out.note = r.data;
|
|
287
|
+
}
|
|
288
|
+
if (out.candidates && out.changes.length === 0) out.outcome = "undecided";
|
|
289
|
+
else if (out.candidates) {
|
|
290
|
+
// A statement with some decided clauses and one undecided: report undecided, and keep the decided ones.
|
|
291
|
+
out.outcome = "undecided";
|
|
292
|
+
} else if (out.changes.length === 0 && none.length === 0) out.outcome = "data";
|
|
293
|
+
else if (none.length > 0) {
|
|
294
|
+
out.outcome = out.changes.length > 0 ? "classified" : "unclassified";
|
|
295
|
+
out.note = [out.note, `no rule covers ${none.join("; ")}`].filter(Boolean).join("; ");
|
|
296
|
+
}
|
|
297
|
+
return out;
|
|
298
|
+
});
|
|
299
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options that hold a credential. A foreign data wrapper's or server's
|
|
3
|
+
* `OPTIONS (...)` can carry any name, and a password in one would be written
|
|
4
|
+
* into a schema file by import and sent to every reader of the plan. The
|
|
5
|
+
* environment supplies credentials, in a user mapping (which is not declared)
|
|
6
|
+
* or its own secret store, as for a role's password (`./entities.ts`).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Whether an option name reads as a credential: `password`, `sslpassword`, `secret_key`, `api_token`. */
|
|
10
|
+
export const isSecretOption = (name: string): boolean => /pass(word|wd)?|secret|token|api[_-]?key|credential/i.test(name);
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* sequence options, storage parameters, comments), an extension and the
|
|
6
6
|
* domain over its type, a collation, composite and range types (and a
|
|
7
7
|
* multirange column), plain and ordered-set aggregates, a cast and an event
|
|
8
|
-
* trigger
|
|
8
|
+
* trigger, an operator, extended statistics, a foreign data wrapper and server, and a
|
|
9
|
+
* publication (#3911), a foreign table, an operator class and family, a text
|
|
10
|
+
* search dictionary and configuration (#4406), a partitioned table and a partition, an ORM's
|
|
9
11
|
* revision table that import leaves out, and an object carrying chant's
|
|
10
12
|
* ownership trailer.
|
|
11
13
|
*/
|
|
@@ -60,5 +62,26 @@ CREATE CAST (app.pair AS text) WITH FUNCTION app.pair_text(app.pair) AS ASSIGNME
|
|
|
60
62
|
COMMENT ON CAST (app.pair AS text) IS 'a:b';
|
|
61
63
|
CREATE FUNCTION app.audit() RETURNS event_trigger LANGUAGE plpgsql AS $$ BEGIN END $$;
|
|
62
64
|
CREATE EVENT TRIGGER app_audit ON ddl_command_end WHEN TAG IN ('CREATE TABLE') EXECUTE FUNCTION app.audit();
|
|
65
|
+
CREATE FUNCTION app.int_same(a integer, b integer) RETURNS boolean LANGUAGE sql IMMUTABLE AS $$ SELECT a = b $$;
|
|
66
|
+
CREATE OPERATOR app.=== (FUNCTION = app.int_same, LEFTARG = integer, RIGHTARG = integer, COMMUTATOR = OPERATOR(app.===), RESTRICT = eqsel, JOIN = eqjoinsel, HASHES, MERGES);
|
|
67
|
+
COMMENT ON OPERATOR app.===(integer, integer) IS 'Same value';
|
|
68
|
+
CREATE STATISTICS app.orders_stats (dependencies) ON user_id, status FROM app.orders;
|
|
69
|
+
COMMENT ON STATISTICS app.orders_stats IS 'Users and their order states';
|
|
70
|
+
CREATE FOREIGN DATA WRAPPER app_fdw OPTIONS (debug 'true');
|
|
71
|
+
COMMENT ON FOREIGN DATA WRAPPER app_fdw IS 'Remote tables';
|
|
72
|
+
CREATE SERVER app_replica TYPE 'pg' VERSION '16' FOREIGN DATA WRAPPER app_fdw OPTIONS (host 'db.internal', port '5432');
|
|
73
|
+
COMMENT ON SERVER app_replica IS 'The read replica';
|
|
74
|
+
CREATE PUBLICATION app_pub FOR TABLE app.orders, app.users WITH (publish = 'insert, update');
|
|
75
|
+
COMMENT ON PUBLICATION app_pub IS 'Order changes';
|
|
76
|
+
CREATE FOREIGN TABLE app.remote_orders (id integer OPTIONS (column_name 'order_id') NOT NULL, status text DEFAULT 'placed', CONSTRAINT status_known CHECK (status IS NOT NULL)) SERVER app_replica OPTIONS (table_name 'orders');
|
|
77
|
+
COMMENT ON FOREIGN TABLE app.remote_orders IS 'Orders on the replica';
|
|
78
|
+
CREATE OPERATOR CLASS app.int_hash_ops FOR TYPE integer USING hash AS OPERATOR 1 =, FUNCTION 1 hashint4(integer);
|
|
79
|
+
COMMENT ON OPERATOR CLASS app.int_hash_ops USING hash IS 'Hash by value';
|
|
80
|
+
CREATE OPERATOR FAMILY app.spare_ops USING btree;
|
|
81
|
+
CREATE TEXT SEARCH DICTIONARY app.shop_stop (TEMPLATE = simple, StopWords = english);
|
|
82
|
+
CREATE TEXT SEARCH CONFIGURATION app.shop (PARSER = pg_catalog."default");
|
|
83
|
+
ALTER TEXT SEARCH CONFIGURATION app.shop ADD MAPPING FOR asciiword, word WITH app.shop_stop, english_stem;
|
|
84
|
+
ALTER TEXT SEARCH CONFIGURATION app.shop ADD MAPPING FOR int, uint WITH simple;
|
|
85
|
+
COMMENT ON TEXT SEARCH CONFIGURATION app.shop IS 'Shop search';
|
|
63
86
|
CREATE TABLE public._prisma_migrations (id varchar(36) PRIMARY KEY, checksum varchar(64) NOT NULL);
|
|
64
87
|
`;
|
package/src/postgres.ts
CHANGED
|
@@ -20,6 +20,16 @@ export {
|
|
|
20
20
|
aggregate,
|
|
21
21
|
trigger,
|
|
22
22
|
eventTrigger,
|
|
23
|
+
operator,
|
|
24
|
+
operatorClass,
|
|
25
|
+
operatorFamily,
|
|
26
|
+
foreignTable,
|
|
27
|
+
textSearchConfiguration,
|
|
28
|
+
textSearchDictionary,
|
|
29
|
+
statistics,
|
|
30
|
+
foreignDataWrapper,
|
|
31
|
+
foreignServer,
|
|
32
|
+
publication,
|
|
23
33
|
collation,
|
|
24
34
|
cast,
|
|
25
35
|
policy,
|
|
@@ -49,6 +59,16 @@ export {
|
|
|
49
59
|
type PostgresCollation,
|
|
50
60
|
type PostgresCast,
|
|
51
61
|
type PostgresEventTrigger,
|
|
62
|
+
type PostgresOperator,
|
|
63
|
+
type PostgresOperatorClass,
|
|
64
|
+
type PostgresOperatorFamily,
|
|
65
|
+
type PostgresForeignTable,
|
|
66
|
+
type PostgresTextSearchConfiguration,
|
|
67
|
+
type PostgresTextSearchDictionary,
|
|
68
|
+
type PostgresStatistics,
|
|
69
|
+
type PostgresForeignDataWrapper,
|
|
70
|
+
type PostgresForeignServer,
|
|
71
|
+
type PostgresPublication,
|
|
52
72
|
type PostgresExtension,
|
|
53
73
|
type PostgresFunction,
|
|
54
74
|
type PostgresProcedure,
|
|
@@ -75,6 +95,19 @@ export {
|
|
|
75
95
|
type CollationProps,
|
|
76
96
|
type CastProps,
|
|
77
97
|
type EventTriggerProps,
|
|
98
|
+
type OperatorProps,
|
|
99
|
+
type OperatorClassProps,
|
|
100
|
+
type OperatorClassItemDef,
|
|
101
|
+
type OperatorFamilyProps,
|
|
102
|
+
type ForeignTableProps,
|
|
103
|
+
type TextSearchConfigurationProps,
|
|
104
|
+
type TextSearchDictionaryProps,
|
|
105
|
+
type TextSearchMappingDef,
|
|
106
|
+
type StatisticsProps,
|
|
107
|
+
type ForeignDataWrapperProps,
|
|
108
|
+
type ForeignServerProps,
|
|
109
|
+
type PublicationProps,
|
|
110
|
+
type PublicationTableDef,
|
|
78
111
|
type DomainProps,
|
|
79
112
|
type ExtensionProps,
|
|
80
113
|
type RoutineProps,
|
package/src/serializer.ts
CHANGED
|
@@ -21,6 +21,11 @@
|
|
|
21
21
|
* of each. Two databases of different dialects are two projects (two
|
|
22
22
|
* workspace members, #3047 question 1).
|
|
23
23
|
*
|
|
24
|
+
* A ClickHouse object that names a named collection (an engine's first
|
|
25
|
+
* argument, a table function's, a dictionary source's `NAME`) carries
|
|
26
|
+
* `namedCollections`, and the document lists every one the build names, so
|
|
27
|
+
* a plan can check each exists on the server (#4395).
|
|
28
|
+
*
|
|
24
29
|
* A Postgres document also carries `postgresMajor`, the major the project
|
|
25
30
|
* targets (`sql.postgresMajor`, else the newest pinned major). Post-synth
|
|
26
31
|
* checks and the lock classifier read it; ClickHouse output has no such field.
|
|
@@ -43,6 +48,7 @@ import { sqlFileLineOf, sqlFileOf, sqlFileReferences } from "./sql-files";
|
|
|
43
48
|
import { relative } from "node:path";
|
|
44
49
|
import { getProvenance } from "@intentius/chant/provenance";
|
|
45
50
|
import type { UnparsedClause } from "./core/interpolation";
|
|
51
|
+
import { namedCollectionReferences, type CollectionReferrer } from "./clickhouse/named-collections";
|
|
46
52
|
|
|
47
53
|
export { applyOrder } from "./core/references";
|
|
48
54
|
|
|
@@ -89,8 +95,9 @@ function objectJson(name: string, obj: ClickHouseObject, names: Map<Declarable,
|
|
|
89
95
|
props.lineage = lineageJson(props.lineage as LineageEdge[], names);
|
|
90
96
|
}
|
|
91
97
|
const dependsOn = [...new Set([...obj.dependsOn.map((r) => referenceName(r, names)).filter((n) => n !== undefined), ...extra])];
|
|
98
|
+
const collections = namedCollectionReferences(props as CollectionReferrer);
|
|
92
99
|
return JSON.parse(
|
|
93
|
-
JSON.stringify({ export: name, type: obj.entityType, sqlName: obj.sqlName, ...props, dependsOn, ddl }),
|
|
100
|
+
JSON.stringify({ export: name, type: obj.entityType, sqlName: obj.sqlName, ...props, dependsOn, ...(collections.length > 0 ? { namedCollections: collections } : {}), ddl }),
|
|
94
101
|
) as Record<string, unknown>;
|
|
95
102
|
}
|
|
96
103
|
|
|
@@ -170,10 +177,14 @@ export const sqlSerializer: Serializer = {
|
|
|
170
177
|
|
|
171
178
|
const extra = sqlFileReferences("clickhouse", objects);
|
|
172
179
|
const order = applyOrder(objects, names, extra);
|
|
180
|
+
const json = order.map((n) => objectJson(n, objects.get(n)!, names, extra.get(n)));
|
|
181
|
+
// The named collections the build references (#4395), each once, by name: a plan checks each exists.
|
|
182
|
+
const collections = [...new Set(json.flatMap((o) => (o.namedCollections as string[] | undefined) ?? []))].sort();
|
|
173
183
|
const doc = {
|
|
174
184
|
dialect: "clickhouse",
|
|
175
185
|
applyOrder: order,
|
|
176
|
-
|
|
186
|
+
...(collections.length > 0 ? { namedCollections: collections } : {}),
|
|
187
|
+
objects: json,
|
|
177
188
|
};
|
|
178
189
|
const ddl = order
|
|
179
190
|
.map((n) => `${(objects.get(n)!.props as { ddl: string }).ddl};`)
|
|
@@ -114,13 +114,14 @@ or set `replicationLag: false` to run without the check.
|
|
|
114
114
|
The Plan phase refuses, naming each object and what to do, a table or column with:
|
|
115
115
|
|
|
116
116
|
- something on the column it does not carry over: an exclusion constraint, a constraint trigger, a materialized view, a rule, a trigger, a policy, a statistics object, a generated column computed from it, an INVALID index, or a view the build does not declare;
|
|
117
|
-
- on a partitioned table,
|
|
117
|
+
- on a partitioned table, a key or foreign key on the column, or the column in the partition key;
|
|
118
118
|
- a partition (run the Op on its partitioned table) or an inheritance tree (chant #3333);
|
|
119
119
|
- a generated column itself, which the applier changes in place;
|
|
120
120
|
- no primary key;
|
|
121
|
-
- a rename combined with a type change
|
|
122
|
-
- a rename of a column with a default, an identity or a serial sequence:
|
|
121
|
+
- a rename combined with a type change, unless the Op gives `reverse` (the old column's value from the new one); with a default, a serial or an identity as well, run the Op twice, the rename first;
|
|
122
|
+
- a rename of a column with a default, an identity or a serial sequence, unless the Op gives `defaultAtContract: true` (the default lands on the new column at the contract; an explicit NULL into the new name gets the default until then);
|
|
123
123
|
- a column a logical replication publication sends; a column list that leaves it out does not stop the Op (chant #3332);
|
|
124
|
+
- a table that is the target of a logical replication subscription, named in the refusal (chant #4405);
|
|
124
125
|
- a column that needs no migration, where the message says to use the applier.
|
|
125
126
|
|
|
126
127
|
## Checking it is done
|
|
@@ -42,6 +42,16 @@ The export name (`users`) is the object's identity in chant. The name in the SQL
|
|
|
42
42
|
| `aggregate` | `CREATE AGGREGATE` | `Postgres::Aggregate` |
|
|
43
43
|
| `trigger` | `CREATE TRIGGER` | `Postgres::Trigger` |
|
|
44
44
|
| `eventTrigger` | `CREATE EVENT TRIGGER` | `Postgres::EventTrigger` |
|
|
45
|
+
| `operator` | `CREATE OPERATOR` | `Postgres::Operator` |
|
|
46
|
+
| `operatorClass` | `CREATE OPERATOR CLASS` | `Postgres::OperatorClass` |
|
|
47
|
+
| `operatorFamily` | `CREATE OPERATOR FAMILY` | `Postgres::OperatorFamily` |
|
|
48
|
+
| `foreignTable` | `CREATE FOREIGN TABLE` | `Postgres::ForeignTable` |
|
|
49
|
+
| `textSearchConfiguration` | `CREATE TEXT SEARCH CONFIGURATION`, then `ALTER TEXT SEARCH CONFIGURATION ... ADD MAPPING` | `Postgres::TextSearchConfiguration` |
|
|
50
|
+
| `textSearchDictionary` | `CREATE TEXT SEARCH DICTIONARY` | `Postgres::TextSearchDictionary` |
|
|
51
|
+
| `statistics` | `CREATE STATISTICS` | `Postgres::Statistics` |
|
|
52
|
+
| `foreignDataWrapper` | `CREATE FOREIGN DATA WRAPPER` | `Postgres::ForeignDataWrapper` |
|
|
53
|
+
| `foreignServer` | `CREATE SERVER` | `Postgres::ForeignServer` |
|
|
54
|
+
| `publication` | `CREATE PUBLICATION` | `Postgres::Publication` |
|
|
45
55
|
| `collation` | `CREATE COLLATION` | `Postgres::Collation` |
|
|
46
56
|
| `cast` | `CREATE CAST` | `Postgres::Cast` |
|
|
47
57
|
| `policy` | `CREATE POLICY` | `Postgres::Policy` |
|
|
@@ -189,4 +199,4 @@ chant build src --lexicon sql -o dist/schema.json
|
|
|
189
199
|
|
|
190
200
|
## Not covered
|
|
191
201
|
|
|
192
|
-
`ALTER` (other than a table's row-level security), role memberships, rules,
|
|
202
|
+
`ALTER` (other than a table's row-level security), role memberships, rules, operator classes and families, foreign tables, user mappings, subscriptions, text search objects and base types are not declared with the tags. Import names an object of such a kind and stops; `docs/pages/object-coverage.mdx` lists every `CREATE` command and why. An aggregate is identified by its argument types; a cast is named `(source AS target)` and an event trigger lives in no schema. A definition change to a composite or range type, collation, cast or event trigger is a drop and a create, refused while something depends on it. Planning a schema change (`chant sql diff`, `chant sql plan`, the lock classes) is the `chant-sql-postgres-plan` skill. Apply and the expand-and-contract migration Op have their own skills, which arrive with the code they describe.
|
package/src/skills/chant-sql.md
CHANGED
|
@@ -114,6 +114,7 @@ When chant interprets a composite's factory at build time, each field records th
|
|
|
114
114
|
| SQLCH002 | lint | a `Nullable` column in the sort key or primary key, which ClickHouse refuses without `allow_nullable_key` |
|
|
115
115
|
| SQLCH003 | lint | `${events.kind}` where `${events.columns.kind}` was meant |
|
|
116
116
|
| SQLCH101 | build | an engine the pinned ClickHouse server does not have |
|
|
117
|
+
| SQLCH128 | build | a literal credential in an integration engine's or table function's arguments; name a named collection instead |
|
|
117
118
|
|
|
118
119
|
## Commands
|
|
119
120
|
|
|
@@ -126,6 +127,6 @@ chant build src --lexicon sql -o dist/schema.json
|
|
|
126
127
|
|
|
127
128
|
## Not covered
|
|
128
129
|
|
|
129
|
-
`CREATE TABLE ... AS` and `CREATE TABLE ... AS SELECT` are not declared with the tags.
|
|
130
|
+
`CREATE TABLE ... AS` and `CREATE TABLE ... AS SELECT` are not declared with the tags. A named collection is never declared: name one in an integration engine, a table function or a dictionary `SOURCE` (`ENGINE = S3(lake_creds, format = 'Parquet')`), and `chant sql plan` checks that the server has it. A second dialect is not supported.
|
|
130
131
|
|
|
131
132
|
Note that `chant migrate` does not run schema migrations. Schema changes go through `chant sql plan`, apply and the rebuild Op, described in the `chant-sql-plan` and `chant-sql-rebuild` skills.
|
package/src/sql-files.test.ts
CHANGED
|
@@ -42,6 +42,39 @@ describe("reading a file of Postgres DDL", () => {
|
|
|
42
42
|
);
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
+
test("a foreign table, an operator class and a text search configuration with its mappings and comments are each one object (#4406)", () => {
|
|
46
|
+
const ddl = [
|
|
47
|
+
"CREATE FOREIGN TABLE app.remote_orders (id int NOT NULL) SERVER replica OPTIONS (table_name 'orders');",
|
|
48
|
+
"COMMENT ON FOREIGN TABLE app.remote_orders IS 'Orders';",
|
|
49
|
+
"CREATE OPERATOR CLASS app.int_hash_ops FOR TYPE int USING hash AS OPERATOR 1 =, FUNCTION 1 hashint4(int);",
|
|
50
|
+
"CREATE OPERATOR FAMILY app.int_hash_ops USING btree;",
|
|
51
|
+
"COMMENT ON OPERATOR CLASS app.int_hash_ops USING hash IS 'Hash';",
|
|
52
|
+
"CREATE TEXT SEARCH DICTIONARY app.shop_stop (TEMPLATE = simple, StopWords = english);",
|
|
53
|
+
"CREATE TEXT SEARCH CONFIGURATION app.shop (PARSER = pg_catalog.default);",
|
|
54
|
+
"CREATE TEXT SEARCH CONFIGURATION app.shop2 (PARSER = pg_catalog.default);",
|
|
55
|
+
"ALTER TEXT SEARCH CONFIGURATION app.shop ADD MAPPING FOR word WITH app.shop_stop;",
|
|
56
|
+
"COMMENT ON TEXT SEARCH CONFIGURATION app.shop2 IS 'Second';",
|
|
57
|
+
].join("\n");
|
|
58
|
+
const objects = readSqlFile("postgres", ddl, { origin: "schema.sql" });
|
|
59
|
+
expect(objects.map((o) => [o.export, o.type, o.name])).toEqual([
|
|
60
|
+
["remoteOrders", "Postgres::ForeignTable", "app.remote_orders"],
|
|
61
|
+
["appIntHashOps", "Postgres::OperatorClass", "app.int_hash_ops"],
|
|
62
|
+
["appIntHashOps2", "Postgres::OperatorFamily", "app.int_hash_ops"],
|
|
63
|
+
["shopStop", "Postgres::TextSearchDictionary", "app.shop_stop"],
|
|
64
|
+
["shop", "Postgres::TextSearchConfiguration", "app.shop"],
|
|
65
|
+
["shop2", "Postgres::TextSearchConfiguration", "app.shop2"],
|
|
66
|
+
]);
|
|
67
|
+
expect(objects[0]!.ddl).toContain("COMMENT ON FOREIGN TABLE app.remote_orders IS 'Orders'");
|
|
68
|
+
// A class and a family of one name and two methods are two objects, and each takes only its own comment.
|
|
69
|
+
expect(objects[1]!.ddl).toContain("COMMENT ON OPERATOR CLASS app.int_hash_ops USING hash IS 'Hash'");
|
|
70
|
+
expect(objects[2]!.ddl).not.toContain("COMMENT");
|
|
71
|
+
expect(objects[4]!.ddl).toContain("ADD MAPPING FOR word WITH app.shop_stop");
|
|
72
|
+
expect(objects[5]!.ddl).toContain("COMMENT ON TEXT SEARCH CONFIGURATION app.shop2 IS 'Second'");
|
|
73
|
+
expect(objects[5]!.ddl).not.toContain("ADD MAPPING");
|
|
74
|
+
// A mapping for a configuration the file does not create is named.
|
|
75
|
+
expect(() => readSqlFile("postgres", "ALTER TEXT SEARCH CONFIGURATION app.x ADD MAPPING FOR word WITH simple;", { origin: "x.sql" })).toThrow(/ALTER TEXT SEARCH CONFIGURATION \.\.\. ADD MAPPING for an object the file does not create/);
|
|
76
|
+
});
|
|
77
|
+
|
|
45
78
|
test("a statement that declares nothing or does not parse is an error naming it, and every one is named", () => {
|
|
46
79
|
const read = () => readSqlFile("postgres", "CREATE TABLE a (id int);\nINSERT INTO a VALUES (1);\nCREATE TABLE b (id int,);\nCOMMENT ON TABLE c IS 'x';", { origin: "db/schema.sql" });
|
|
47
80
|
expect(read).toThrow(SqlFileError);
|
package/src/sql-types-command.ts
CHANGED
|
@@ -18,6 +18,12 @@ import { basename, dirname, join, relative, resolve } from "node:path";
|
|
|
18
18
|
import type { CommandGroupContext } from "@intentius/chant/cli/command-group";
|
|
19
19
|
import { renderSqlTypes, SQL_TYPES_FILE } from "./sql-types";
|
|
20
20
|
|
|
21
|
+
export interface ProjectLocation {
|
|
22
|
+
config: Awaited<ReturnType<typeof import("@intentius/chant/config").loadChantConfigUpward>>["config"];
|
|
23
|
+
projectDir: string;
|
|
24
|
+
sourceDir: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
21
27
|
const USAGE = "usage: chant sql types [--output <path>] [--check]";
|
|
22
28
|
|
|
23
29
|
/** The `--output` value, or an error message. */
|
|
@@ -43,37 +49,53 @@ export function shadowingSource(path: string): string | undefined {
|
|
|
43
49
|
return existsSync(source) ? source : undefined;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if ("error" in args) {
|
|
49
|
-
console.error(`${args.error}\n${USAGE}`);
|
|
50
|
-
return 1;
|
|
51
|
-
}
|
|
52
|
-
const cwd = process.cwd();
|
|
52
|
+
/** The project the working directory belongs to: its config, its root and its source directory. */
|
|
53
|
+
export async function loadProject(cwd: string): Promise<ProjectLocation> {
|
|
53
54
|
const { loadChantConfigUpward } = await import("@intentius/chant/config");
|
|
54
55
|
const { config, configPath } = await loadChantConfigUpward(cwd);
|
|
55
56
|
const projectDir = configPath ? dirname(configPath) : cwd;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const shadow = shadowingSource(output);
|
|
59
|
-
if (shadow) {
|
|
60
|
-
console.error(`chant sql types: ${relative(cwd, shadow) || basename(shadow)} sits beside ${relative(cwd, output) || basename(output)}, and tsc reads it instead of the declarations; rename one`);
|
|
61
|
-
return 1;
|
|
62
|
-
}
|
|
57
|
+
return { config, projectDir, sourceDir: resolve(projectDir, config.sourceDir ?? ".") };
|
|
58
|
+
}
|
|
63
59
|
|
|
60
|
+
/**
|
|
61
|
+
* The fold build's entities: what `chant build` declares, `.sql` files and
|
|
62
|
+
* composite members included. Prints the build's errors and returns
|
|
63
|
+
* undefined when it fails.
|
|
64
|
+
*/
|
|
65
|
+
export async function buildFolded(project: ProjectLocation, command: string, consequence: string) {
|
|
66
|
+
const { config, projectDir, sourceDir } = project;
|
|
64
67
|
const { build } = await import("@intentius/chant/build");
|
|
65
68
|
const { resolveBuildModes, resolveProjectBuildOptions } = await import("@intentius/chant/cli/build-options");
|
|
66
69
|
const { loadPlugins, resolveProjectLexicons } = await import("@intentius/chant/cli/plugins");
|
|
67
70
|
const { sqlSerializer } = await import("./serializer");
|
|
68
71
|
const plugins = await loadPlugins(await resolveProjectLexicons(projectDir, { lexicon: "sql" }));
|
|
69
|
-
// The fold build's entities: what `chant build` declares, `.sql` files and composite members included.
|
|
70
72
|
const options = resolveProjectBuildOptions({ config, configDir: projectDir, plugins, modes: resolveBuildModes(config, { fold: true }) });
|
|
71
73
|
const result = await build(sourceDir, [sqlSerializer], undefined, options);
|
|
72
74
|
if (result.errors.length > 0) {
|
|
73
75
|
for (const e of result.errors) console.error(e instanceof Error ? e.message : String(e));
|
|
74
|
-
console.error(
|
|
76
|
+
console.error(`${command}: the build failed, so ${consequence}`);
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function runTypes(ctx: CommandGroupContext): Promise<number> {
|
|
83
|
+
const args = parseArgs(ctx.rawArgs);
|
|
84
|
+
if ("error" in args) {
|
|
85
|
+
console.error(`${args.error}\n${USAGE}`);
|
|
75
86
|
return 1;
|
|
76
87
|
}
|
|
88
|
+
const cwd = process.cwd();
|
|
89
|
+
const { config, projectDir, sourceDir } = await loadProject(cwd);
|
|
90
|
+
const output = args.output !== undefined ? resolve(cwd, args.output) : join(sourceDir, SQL_TYPES_FILE);
|
|
91
|
+
const shadow = shadowingSource(output);
|
|
92
|
+
if (shadow) {
|
|
93
|
+
console.error(`chant sql types: ${relative(cwd, shadow) || basename(shadow)} sits beside ${relative(cwd, output) || basename(output)}, and tsc reads it instead of the declarations; rename one`);
|
|
94
|
+
return 1;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const result = await buildFolded({ config, projectDir, sourceDir }, "chant sql types", "nothing was written");
|
|
98
|
+
if (!result) return 1;
|
|
77
99
|
const text = renderSqlTypes(result.entities, { projectDir, config: config as unknown as Record<string, unknown> });
|
|
78
100
|
const shown = relative(cwd, output);
|
|
79
101
|
const named = shown && !shown.startsWith("..") ? shown : output;
|