@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,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `StreamIngest` (#4396): the stream table, the view and the MergeTree
|
|
3
|
+
* target it declares, the references between them, and what the lexicon's own
|
|
4
|
+
* checks say about a chain that names a named collection and about one that
|
|
5
|
+
* writes a password.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, expect, test } from "vitest";
|
|
9
|
+
import { expandComposite } from "@intentius/chant/composite";
|
|
10
|
+
import type { Declarable } from "@intentius/chant/declarable";
|
|
11
|
+
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
12
|
+
import { makePostSynthCtx } from "@intentius/chant-test-utils";
|
|
13
|
+
import { StreamIngest } from "./index";
|
|
14
|
+
import { CLICKHOUSE_ENTITY_TYPES, type ClickHouseObject } from "../clickhouse/entities";
|
|
15
|
+
import { sqlSerializer } from "../serializer";
|
|
16
|
+
import { postSynthChecks } from "../lint/post-synth";
|
|
17
|
+
import { diffStatements } from "../migration-statements";
|
|
18
|
+
|
|
19
|
+
const flat = (ddl: string) => ddl.replace(/\s+/g, " ").trim();
|
|
20
|
+
const ddlOf = (entity: unknown) => flat((entity as ClickHouseObject & { props: { ddl: string } }).props.ddl);
|
|
21
|
+
|
|
22
|
+
const ingest = (over: Partial<Parameters<typeof StreamIngest>[0]> = {}) =>
|
|
23
|
+
StreamIngest({
|
|
24
|
+
name: "events",
|
|
25
|
+
columns: "ts DateTime, user_id UInt64, kind LowCardinality(String)",
|
|
26
|
+
engine: "Kafka(kafka_creds)",
|
|
27
|
+
settings: "kafka_topic_list = 'events', kafka_group_name = 'ingest', kafka_format = 'JSONEachRow'",
|
|
28
|
+
orderBy: "(kind, user_id, ts)",
|
|
29
|
+
...over,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("StreamIngest", () => {
|
|
33
|
+
test("a target, a stream table named after it, and a view from the one into the other", () => {
|
|
34
|
+
const { target, stream, view } = ingest();
|
|
35
|
+
expect(target.entityType).toBe(CLICKHOUSE_ENTITY_TYPES.table);
|
|
36
|
+
expect(stream.entityType).toBe(CLICKHOUSE_ENTITY_TYPES.table);
|
|
37
|
+
expect(view.entityType).toBe(CLICKHOUSE_ENTITY_TYPES.materializedView);
|
|
38
|
+
expect([target.props.name, stream.props.name, view.props.name]).toEqual(["events", "events_stream", "events_mv"]);
|
|
39
|
+
expect(target.props.engine).toEqual({ name: "MergeTree" });
|
|
40
|
+
expect(stream.props.engine).toMatchObject({ name: "Kafka" });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("the DDL: the named collection is the engine's argument, the settings follow, the view selects everything", () => {
|
|
44
|
+
const { stream, view, target } = ingest();
|
|
45
|
+
expect(ddlOf(stream)).toBe(
|
|
46
|
+
"CREATE TABLE events_stream (ts DateTime, user_id UInt64, kind LowCardinality(String)) ENGINE = Kafka(kafka_creds) SETTINGS kafka_topic_list = 'events', kafka_group_name = 'ingest', kafka_format = 'JSONEachRow'",
|
|
47
|
+
);
|
|
48
|
+
expect(ddlOf(view)).toBe("CREATE MATERIALIZED VIEW events_mv TO events AS SELECT * FROM events_stream");
|
|
49
|
+
expect(ddlOf(target)).toBe("CREATE TABLE events (ts DateTime, user_id UInt64, kind LowCardinality(String)) ENGINE = MergeTree ORDER BY (kind, user_id, ts)");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("the view writes TO the target and reads the stream table, by reference, so the build orders all three", () => {
|
|
53
|
+
const { target, stream, view } = ingest();
|
|
54
|
+
expect(view.props.to).toBe(target);
|
|
55
|
+
expect(view.props.reads).toEqual([stream]);
|
|
56
|
+
expect(view.dependsOn).toEqual([target, stream]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("the message columns, the select list, the target engine and the partition key are parameters", () => {
|
|
60
|
+
const { stream, view, target } = ingest({
|
|
61
|
+
streamColumns: "payload String",
|
|
62
|
+
select: "parseDateTimeBestEffort(JSONExtractString(payload, 'ts')) AS ts, JSONExtractUInt(payload, 'user_id') AS user_id, JSONExtractString(payload, 'kind') AS kind",
|
|
63
|
+
targetEngine: "ReplacingMergeTree",
|
|
64
|
+
partitionBy: "toYYYYMM(ts)",
|
|
65
|
+
engine: "S3Queue(lake_creds)",
|
|
66
|
+
settings: "mode = 'unordered', keeper_path = '/chant/events'",
|
|
67
|
+
});
|
|
68
|
+
expect(ddlOf(stream)).toBe("CREATE TABLE events_stream (payload String) ENGINE = S3Queue(lake_creds) SETTINGS mode = 'unordered', keeper_path = '/chant/events'");
|
|
69
|
+
expect(ddlOf(view)).toMatch(/^CREATE MATERIALIZED VIEW events_mv TO events AS SELECT parseDateTimeBestEffort\(/);
|
|
70
|
+
expect(target.props.engine).toEqual({ name: "ReplacingMergeTree" });
|
|
71
|
+
expect(target.props.partitionBy).toBe("toYYYYMM(ts)");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("an engine with no settings declares none", () => {
|
|
75
|
+
expect(ddlOf(ingest({ engine: "Kafka(kafka_creds)", settings: undefined }).stream)).toMatch(/ENGINE = Kafka\(kafka_creds\)$/);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
function build(props: Parameters<typeof ingest>[0]) {
|
|
79
|
+
const entities = new Map<string, Declarable>();
|
|
80
|
+
for (const [k, v] of expandComposite("events", ingest(props))) entities.set(k, v);
|
|
81
|
+
const out = sqlSerializer.serialize(entities) as SerializerResult;
|
|
82
|
+
return { out, doc: JSON.parse(out.primary) as { applyOrder: string[]; namedCollections?: string[] }, entities };
|
|
83
|
+
}
|
|
84
|
+
const findings = (b: ReturnType<typeof build>) => {
|
|
85
|
+
const ctx = makePostSynthCtx("sql", b.out.primary, b.entities);
|
|
86
|
+
return postSynthChecks.filter((c) => c.id.startsWith("SQLCH") || c.id === "SQL101").flatMap((check) => check.check(ctx));
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
test("the build applies the target and the stream table before the view, records the collection, and the lexicon's checks find nothing", () => {
|
|
90
|
+
const b = build({ columns: "ts DateTime, user_id UInt64, kind LowCardinality(String)", select: "ts, user_id, kind", streamColumns: "ts DateTime, user_id UInt64, kind LowCardinality(String)" });
|
|
91
|
+
const order = b.doc.applyOrder;
|
|
92
|
+
expect(order.indexOf("eventsTarget")).toBeLessThan(order.indexOf("eventsView"));
|
|
93
|
+
expect(order.indexOf("eventsStream")).toBeLessThan(order.indexOf("eventsView"));
|
|
94
|
+
expect(b.doc.namedCollections).toEqual(["kafka_creds"]);
|
|
95
|
+
expect(findings(b)).toEqual([]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("a view that writes a column the target lacks is SQLCH110, and a password in the engine is SQLCH128", () => {
|
|
99
|
+
const wrong = findings(build({ select: "ts, user_id, kind, extra" }));
|
|
100
|
+
expect(wrong.map((f) => f.checkId)).toContain("SQLCH110");
|
|
101
|
+
const inline = findings(build({ engine: "Kafka('broker:9092', 'events', 'ingest', 'JSONEachRow')", settings: "kafka_sasl_password = 'hunter2'" }));
|
|
102
|
+
expect(inline.map((f) => f.checkId)).toContain("SQLCH128");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("a change to the stream table between two builds is a replacement in the migration's statements, not an Op", () => {
|
|
106
|
+
const settings = (topic: string) => `kafka_topic_list = '${topic}', kafka_group_name = 'ingest', kafka_format = 'JSONEachRow'`;
|
|
107
|
+
const doc = diffStatements(build({ settings: settings("events") }).out.primary, build({ settings: settings("events,clicks") }).out.primary);
|
|
108
|
+
expect(doc.refused).toBeFalsy();
|
|
109
|
+
const steps = doc.steps.filter((s) => s.kind === "statement") as { sql: string; rule: string; class: string }[];
|
|
110
|
+
expect(steps.map((s) => s.sql.replace(/\s+/g, " ").split(" ").slice(0, 2).join(" "))).toEqual(["DETACH TABLE", "DROP TABLE", "CREATE TABLE", "ATTACH TABLE"]);
|
|
111
|
+
expect(steps.every((s) => s.rule === "SQLCH225" && s.class === "replace")).toBe(true);
|
|
112
|
+
expect(doc.steps.every((s) => s.kind === "statement")).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `StreamIngest`: the usual ClickHouse ingestion chain, a stream table, a materialized view reading it and a MergeTree target.
|
|
3
|
+
*
|
|
4
|
+
* The stream table (Kafka, S3Queue, AzureQueue, RabbitMQ, NATS or FileLog)
|
|
5
|
+
* stores no rows: it is the consumer. The materialized view pulls each batch
|
|
6
|
+
* through it with `TO` and writes the target, which is an ordinary MergeTree
|
|
7
|
+
* table that can be queried, altered and backfilled on its own. Every column
|
|
8
|
+
* the view's select list writes has to be one the target declares (SQLCH110).
|
|
9
|
+
*
|
|
10
|
+
* The stream engine usually carries credentials. Name a named collection as
|
|
11
|
+
* its first argument (`Kafka(kafka_creds)`, `S3Queue(lake_creds)`) and keep
|
|
12
|
+
* the broker, bucket and keys in the collection on the server: chant never
|
|
13
|
+
* declares or reads one (#4395), and SQLCH128 fails a build that writes a
|
|
14
|
+
* password in the engine's arguments.
|
|
15
|
+
*
|
|
16
|
+
* A change to the stream table is planned as SQLCH225, not as a rebuild: the
|
|
17
|
+
* view is detached, the table replaced and the view attached again. A
|
|
18
|
+
* different `kafka_group_name` or S3Queue `keeper_path` loses the table's
|
|
19
|
+
* position, and the plan says so (#4396).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { Composite } from "@intentius/chant/composite";
|
|
23
|
+
import { table, view, type ClickHouseTable, type ClickHouseView } from "../clickhouse/entities";
|
|
24
|
+
import type { ClickHouseMemberName } from "../core/typed";
|
|
25
|
+
import type { Instance, TypedComposite } from "./typed";
|
|
26
|
+
|
|
27
|
+
export interface StreamIngestProps {
|
|
28
|
+
/** The target table's name. The stream table is `<name>_stream` and the view `<name>_mv`. */
|
|
29
|
+
name: string;
|
|
30
|
+
/** The target's columns, as SQL: `ts DateTime, user_id UInt64, kind LowCardinality(String)`. */
|
|
31
|
+
columns: string;
|
|
32
|
+
/**
|
|
33
|
+
* The stream table's engine, with a named collection as its first argument
|
|
34
|
+
* rather than inline credentials: `Kafka(kafka_creds)`, `S3Queue(lake_creds)`.
|
|
35
|
+
*/
|
|
36
|
+
engine: string;
|
|
37
|
+
/**
|
|
38
|
+
* The engine's settings, the body of a `SETTINGS` clause:
|
|
39
|
+
* `kafka_topic_list = 'events', kafka_group_name = 'ingest', kafka_format = 'JSONEachRow'`.
|
|
40
|
+
* Settings the named collection already holds are left out.
|
|
41
|
+
*/
|
|
42
|
+
settings?: string;
|
|
43
|
+
/** The stream table's columns when they differ from the target's: the message as it arrives. Default: `columns`. */
|
|
44
|
+
streamColumns?: string;
|
|
45
|
+
/** The view's select list over the stream table, each item aliased to a target column. Default: `*`. */
|
|
46
|
+
select?: string;
|
|
47
|
+
/** The target's sort key (a column list or an expression). */
|
|
48
|
+
orderBy: string;
|
|
49
|
+
/** The target's engine (default `MergeTree`). */
|
|
50
|
+
targetEngine?: string;
|
|
51
|
+
/** The target's partition key. Left out by default. */
|
|
52
|
+
partitionBy?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** `Target`, `Stream` and `View` are the members' names, when the call's props give them (chant#3787). */
|
|
56
|
+
export type StreamIngestMembers<Target extends string = string, Stream extends string = string, View extends string = string> = {
|
|
57
|
+
target: ClickHouseTable<Target>;
|
|
58
|
+
stream: ClickHouseTable<Stream>;
|
|
59
|
+
view: ClickHouseView<View>;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
type StreamIngestCall = <const N extends string>(
|
|
63
|
+
props: StreamIngestProps & { name: N },
|
|
64
|
+
) => Instance<StreamIngestMembers<ClickHouseMemberName<N>, ClickHouseMemberName<N, "_stream">, ClickHouseMemberName<N, "_mv">>>;
|
|
65
|
+
|
|
66
|
+
/** A stream table, the materialized view that reads it, and the MergeTree target the view writes. */
|
|
67
|
+
export const StreamIngest = Composite<StreamIngestProps, StreamIngestMembers, TypedComposite<StreamIngestProps, StreamIngestMembers, StreamIngestCall>>((props) => {
|
|
68
|
+
const target = table`
|
|
69
|
+
CREATE TABLE ${props.name} (${props.columns})
|
|
70
|
+
ENGINE = ${props.targetEngine ?? "MergeTree"}
|
|
71
|
+
${props.partitionBy ? `PARTITION BY ${props.partitionBy}` : ""}
|
|
72
|
+
ORDER BY ${props.orderBy}`;
|
|
73
|
+
const stream = table`
|
|
74
|
+
CREATE TABLE ${`${props.name}_stream`} (${props.streamColumns ?? props.columns})
|
|
75
|
+
ENGINE = ${props.engine}
|
|
76
|
+
${props.settings ? `SETTINGS ${props.settings}` : ""}`;
|
|
77
|
+
const ingest = view`
|
|
78
|
+
CREATE MATERIALIZED VIEW ${`${props.name}_mv`} TO ${target} AS
|
|
79
|
+
SELECT ${props.select ?? "*"}
|
|
80
|
+
FROM ${stream}`;
|
|
81
|
+
return { target, stream, view: ingest };
|
|
82
|
+
}, "StreamIngest");
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the statement classifiers (`../clickhouse/plan/statements.ts`,
|
|
3
|
+
* `../postgres/plan/statements.ts`) answer, in one shape for both dialects
|
|
4
|
+
* (#4407). They read a migration file's statements offline and give each the
|
|
5
|
+
* class the schema-diff classifier gives the same change, citing its rule.
|
|
6
|
+
* Nothing runs against a server.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ClassifierRule } from "./classifier";
|
|
10
|
+
|
|
11
|
+
/** One change a statement makes, under one rule. */
|
|
12
|
+
export interface StatementClassified {
|
|
13
|
+
rule: string;
|
|
14
|
+
/** The rule's class, e.g. `rebuild`, `concurrently`. */
|
|
15
|
+
class: string;
|
|
16
|
+
/** The class as a report names it, e.g. `REBUILD`. */
|
|
17
|
+
label: string;
|
|
18
|
+
title: string;
|
|
19
|
+
/** Postgres: the lock the rule's restriction names, e.g. `SHARE`. */
|
|
20
|
+
lock?: string;
|
|
21
|
+
/** Data is removed and not recoverable. */
|
|
22
|
+
destructive?: boolean;
|
|
23
|
+
note?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* - `classified`: every change has a rule.
|
|
28
|
+
* - `undecided`: the statement alone cannot say which of `candidates` applies
|
|
29
|
+
* (a column type change is a rewrite, or a rebuild when the column is in a
|
|
30
|
+
* key); the answer `classifyDisruption()` calls `unknown`.
|
|
31
|
+
* - `unclassified`: a schema change no rule covers; `note` says what.
|
|
32
|
+
* - `data`: the statement reads or writes rows or the session, not the schema.
|
|
33
|
+
*/
|
|
34
|
+
export type StatementOutcome = "classified" | "undecided" | "unclassified" | "data";
|
|
35
|
+
|
|
36
|
+
export interface StatementVerdict {
|
|
37
|
+
/** 1-based position among the file's up statements. */
|
|
38
|
+
index: number;
|
|
39
|
+
/** The statement's first line, cut to 120 characters. */
|
|
40
|
+
sql: string;
|
|
41
|
+
outcome: StatementOutcome;
|
|
42
|
+
/** `classified`: one entry per change in the statement (an `ALTER TABLE` can hold several). */
|
|
43
|
+
changes: StatementClassified[];
|
|
44
|
+
/** `undecided`: the rules that could apply, in the order a reader should weigh them. */
|
|
45
|
+
candidates?: StatementClassified[];
|
|
46
|
+
note?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** A statement's own text cut for a note: the first line, at most 120 characters. */
|
|
50
|
+
export function sqlHead(sql: string): string {
|
|
51
|
+
const line = sql.split(/\r?\n/).find((l) => l.trim() !== "" && !l.trim().startsWith("--")) ?? sql;
|
|
52
|
+
const t = line.trim().replace(/\s+/g, " ");
|
|
53
|
+
return t.length > 120 ? `${t.slice(0, 117)}...` : t;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The pieces of a classified change from a dialect's rule table. */
|
|
57
|
+
export function classifiedOf<C extends string>(
|
|
58
|
+
rules: Readonly<Record<string, ClassifierRule<C>>>,
|
|
59
|
+
labels: Readonly<Record<C, string>>,
|
|
60
|
+
id: string,
|
|
61
|
+
extra: Partial<StatementClassified> = {},
|
|
62
|
+
): StatementClassified {
|
|
63
|
+
const r = rules[id];
|
|
64
|
+
if (!r) throw new Error(`no classifier rule ${id}`);
|
|
65
|
+
return { rule: id, class: r.class, label: labels[r.class], title: r.title, ...extra };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Lock names, longest first so `SHARE UPDATE EXCLUSIVE` is not read as `SHARE`. */
|
|
69
|
+
const LOCK = /\b(ACCESS EXCLUSIVE|SHARE UPDATE EXCLUSIVE|SHARE ROW EXCLUSIVE|ROW EXCLUSIVE|ROW SHARE|EXCLUSIVE|SHARE)\b/;
|
|
70
|
+
|
|
71
|
+
/** The first lock a rule's restriction names, or undefined when it names none. */
|
|
72
|
+
export const lockOf = (restriction: string): string | undefined => LOCK.exec(restriction)?.[1];
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Split a statement's tokens' text into the pieces between top-level commas
|
|
76
|
+
* (an `ALTER TABLE`'s actions). Parentheses and brackets nest.
|
|
77
|
+
*/
|
|
78
|
+
export function splitTopLevel(words: readonly string[]): string[][] {
|
|
79
|
+
const out: string[][] = [[]];
|
|
80
|
+
let depth = 0;
|
|
81
|
+
for (const w of words) {
|
|
82
|
+
if (w === "(" || w === "[") depth++;
|
|
83
|
+
else if (w === ")" || w === "]") depth--;
|
|
84
|
+
if (w === "," && depth === 0) out.push([]);
|
|
85
|
+
else out[out.length - 1]!.push(w);
|
|
86
|
+
}
|
|
87
|
+
return out.filter((a) => a.length > 0);
|
|
88
|
+
}
|
|
@@ -100,14 +100,15 @@ describe("ClickHouse live import", () => {
|
|
|
100
100
|
afterAll(() => server.close());
|
|
101
101
|
const run = (extra: Record<string, unknown> = {}) => exportClickHouse({ environment: "x", config: {}, env: { CLICKHOUSE_URL: server.url }, ...extra });
|
|
102
102
|
|
|
103
|
-
test("fails naming them", async () => {
|
|
104
|
-
await expect(run()).rejects.toThrow(
|
|
103
|
+
test("fails naming them; a named collection is a prerequisite, not among them (#4395)", async () => {
|
|
104
|
+
await expect(run()).rejects.toThrow(/:\n - window view analytics\.wv\n - workload all\nWhy/);
|
|
105
105
|
});
|
|
106
106
|
|
|
107
107
|
test("with skipUndeclarable, warns, and the window view is not imported as a table", async () => {
|
|
108
108
|
const ir = await run({ skipUndeclarable: true });
|
|
109
109
|
expect(ir.resources.map((r) => r.properties.name)).toEqual(["analytics", "events"]);
|
|
110
|
-
expect((ir.warnings ?? []).filter((w) => w.includes("--skip-undeclarable"))).toHaveLength(
|
|
110
|
+
expect((ir.warnings ?? []).filter((w) => w.includes("--skip-undeclarable"))).toHaveLength(2);
|
|
111
|
+
expect((ir.warnings ?? []).filter((w) => w.startsWith("named collection nc1 is a prerequisite"))).toHaveLength(1);
|
|
111
112
|
});
|
|
112
113
|
});
|
|
113
114
|
|
|
@@ -134,8 +135,8 @@ CREATE COLLATION app.ci (provider = icu, locale = 'und');`;
|
|
|
134
135
|
]);
|
|
135
136
|
});
|
|
136
137
|
|
|
137
|
-
test("ClickHouse: a named collection in a file is
|
|
138
|
-
expect(
|
|
138
|
+
test("ClickHouse: a named collection in a file is a prerequisite (#4395); a quota is declared (#3911)", () => {
|
|
139
|
+
expect(new SqlFileParser("clickhouse").parse("CREATE DATABASE a;\nCREATE NAMED COLLECTION c AS url = 'x'").warnings).toEqual([expect.stringMatching(/^named collection c is a prerequisite/)]);
|
|
139
140
|
expect(new SqlFileParser("clickhouse").parse("CREATE DATABASE a;\nCREATE QUOTA q1").resources.map((r) => r.type)).toEqual(["ClickHouse::Database", "ClickHouse::Quota"]);
|
|
140
141
|
});
|
|
141
142
|
});
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
{ "command": "CREATE HANDLER", "kind": "HTTP handler", "status": "refused", "reason": "an HTTP handler is server-level configuration, not a schema object" },
|
|
9
9
|
{ "command": "CREATE INDEX", "kind": "index", "status": "declared", "entity": "ClickHouse::Table", "note": "an index is part of its table: SHOW CREATE TABLE prints it, and the table's declaration holds it" },
|
|
10
10
|
{ "command": "CREATE MASKING POLICY", "kind": "masking policy", "status": "refused", "reason": "masking policies are available only in ClickHouse Cloud, not on the pinned server" },
|
|
11
|
-
{ "command": "CREATE NAMED COLLECTION", "kind": "named collection", "status": "
|
|
11
|
+
{ "command": "CREATE NAMED COLLECTION", "kind": "named collection", "status": "referenced", "reason": "a named collection holds credentials (a password, a secret key), which are the environment's: chant never declares one or reads its values. An engine, a table function or a dictionary SOURCE names it, a plan checks that it exists, and import lists the server's as prerequisites" },
|
|
12
12
|
{ "command": "CREATE QUOTA", "kind": "quota", "status": "declared", "entity": "ClickHouse::Quota", "note": "import reads only the access objects a build declares" },
|
|
13
13
|
{ "command": "CREATE RESOURCE", "kind": "workload resource", "status": "refused", "reason": "a resource is server-level scheduling (a disk's reads or writes), configured with the server, not a schema object" },
|
|
14
14
|
{ "command": "CREATE ROLE", "kind": "role", "status": "declared", "entity": "ClickHouse::Role", "note": "import reads only the access objects a build declares" },
|
|
@@ -10,32 +10,32 @@
|
|
|
10
10
|
{ "command": "CREATE DOMAIN", "kind": "domain", "status": "declared", "entity": "Postgres::Domain" },
|
|
11
11
|
{ "command": "CREATE EVENT TRIGGER", "kind": "event trigger", "status": "declared", "entity": "Postgres::EventTrigger", "note": "the database's, not a schema's; creating one takes a superuser" },
|
|
12
12
|
{ "command": "CREATE EXTENSION", "kind": "extension", "status": "declared", "entity": "Postgres::Extension" },
|
|
13
|
-
{ "command": "CREATE FOREIGN DATA WRAPPER", "kind": "foreign data wrapper", "status": "
|
|
14
|
-
{ "command": "CREATE FOREIGN TABLE", "kind": "foreign table", "status": "
|
|
13
|
+
{ "command": "CREATE FOREIGN DATA WRAPPER", "kind": "foreign data wrapper", "status": "declared", "entity": "Postgres::ForeignDataWrapper", "note": "the database's, not a schema's; creating one takes a superuser; one an extension installs comes with the extension; an option that holds a credential is refused" },
|
|
14
|
+
{ "command": "CREATE FOREIGN TABLE", "kind": "foreign table", "status": "declared", "entity": "Postgres::ForeignTable", "note": "its own tag, not table: another CREATE and ALTER, no key, index or storage clauses; its server cannot be altered; one that is a partition, inherits or has a generated column is not declared; an option that holds a credential is refused" },
|
|
15
15
|
{ "command": "CREATE FUNCTION", "kind": "function", "status": "declared", "entity": "Postgres::Function" },
|
|
16
16
|
{ "command": "CREATE FUNCTION", "form": "RETURN or BEGIN ATOMIC body", "match": "\\b(?:RETURN|BEGIN\\s+ATOMIC)\\b", "kind": "routine with a SQL-standard body", "status": "refused", "reason": "the server stores a SQL-standard body parsed and prints it back rewritten, so a declaration cannot be compared with it; declare the routine with a string body (AS $$ ... $$)" },
|
|
17
17
|
{ "command": "CREATE GROUP", "kind": "group", "status": "declared", "entity": "Postgres::Role", "note": "an alias of CREATE ROLE" },
|
|
18
18
|
{ "command": "CREATE INDEX", "kind": "index", "status": "declared", "entity": "Postgres::Index" },
|
|
19
19
|
{ "command": "CREATE LANGUAGE", "kind": "procedural language", "status": "refused", "reason": "no tag declares a procedural language; an extension installs one, and the extension is declared instead" },
|
|
20
20
|
{ "command": "CREATE MATERIALIZED VIEW", "kind": "materialized view", "status": "declared", "entity": "Postgres::MaterializedView" },
|
|
21
|
-
{ "command": "CREATE OPERATOR", "kind": "operator", "status": "
|
|
22
|
-
{ "command": "CREATE OPERATOR CLASS", "kind": "operator class", "status": "
|
|
23
|
-
{ "command": "CREATE OPERATOR FAMILY", "kind": "operator family", "status": "
|
|
21
|
+
{ "command": "CREATE OPERATOR", "kind": "operator", "status": "declared", "entity": "Postgres::Operator", "note": "identified by its name and operand types; RESTRICT and JOIN change in place, and from 17 so does setting a commutator or negator or turning on HASHES or MERGES; anything else is a drop and a create" },
|
|
22
|
+
{ "command": "CREATE OPERATOR CLASS", "kind": "operator class", "status": "declared", "entity": "Postgres::OperatorClass", "note": "identified by its name and index method; a change is a drop and a create, since ALTER OPERATOR CLASS only renames or moves it; its own family, named as the class, comes with it" },
|
|
23
|
+
{ "command": "CREATE OPERATOR FAMILY", "kind": "operator family", "status": "declared", "entity": "Postgres::OperatorFamily", "note": "identified by its name and index method; its members come with the classes that name it, and loose members added with ALTER OPERATOR FAMILY are not declared" },
|
|
24
24
|
{ "command": "CREATE POLICY", "kind": "policy", "status": "declared", "entity": "Postgres::Policy", "note": "read where the profile manages access (sql.profiles.<env>.access)" },
|
|
25
25
|
{ "command": "CREATE PROCEDURE", "kind": "procedure", "status": "declared", "entity": "Postgres::Procedure" },
|
|
26
|
-
{ "command": "CREATE PUBLICATION", "kind": "publication", "status": "
|
|
26
|
+
{ "command": "CREATE PUBLICATION", "kind": "publication", "status": "declared", "entity": "Postgres::Publication", "note": "the database's, not a schema's; a table of it may carry a column list and row filter from 15" },
|
|
27
27
|
{ "command": "CREATE ROLE", "kind": "role", "status": "declared", "entity": "Postgres::Role", "note": "import reads only the roles a build declares, since a role is the cluster's" },
|
|
28
28
|
{ "command": "CREATE RULE", "kind": "rule", "status": "refused", "reason": "no tag declares a rewrite rule; a view's own rule is the view's" },
|
|
29
29
|
{ "command": "CREATE SCHEMA", "kind": "schema", "status": "declared", "entity": "Postgres::Schema" },
|
|
30
30
|
{ "command": "CREATE SEQUENCE", "kind": "sequence", "status": "declared", "entity": "Postgres::Sequence" },
|
|
31
|
-
{ "command": "CREATE SERVER", "kind": "foreign server", "status": "
|
|
32
|
-
{ "command": "CREATE STATISTICS", "kind": "extended statistics", "status": "
|
|
33
|
-
{ "command": "CREATE SUBSCRIPTION", "kind": "subscription", "status": "refused", "reason": "
|
|
31
|
+
{ "command": "CREATE SERVER", "kind": "foreign server", "status": "declared", "entity": "Postgres::ForeignServer", "note": "the database's, not a schema's; its user mappings are refused, so credentials stay out of the declaration" },
|
|
32
|
+
{ "command": "CREATE STATISTICS", "kind": "extended statistics", "status": "declared", "entity": "Postgres::Statistics" },
|
|
33
|
+
{ "command": "CREATE SUBSCRIPTION", "kind": "subscription", "status": "refused", "reason": "a subscription's connection string carries a credential, and creating one opens a replication slot on the publisher, outside this database; the environment makes it" },
|
|
34
34
|
{ "command": "CREATE TABLE", "kind": "table", "status": "declared", "entity": "Postgres::Table" },
|
|
35
35
|
{ "command": "CREATE TABLE AS", "kind": "table from a query", "status": "declared", "entity": "Postgres::Table", "note": "the server keeps no query, so the table it made reads as a CREATE TABLE" },
|
|
36
36
|
{ "command": "CREATE TABLESPACE", "kind": "tablespace", "status": "out-of-scope", "reason": "a tablespace is the cluster's, a directory on the server; a table's TABLESPACE clause is kept" },
|
|
37
|
-
{ "command": "CREATE TEXT SEARCH CONFIGURATION", "kind": "text search configuration", "status": "
|
|
38
|
-
{ "command": "CREATE TEXT SEARCH DICTIONARY", "kind": "text search dictionary", "status": "
|
|
37
|
+
{ "command": "CREATE TEXT SEARCH CONFIGURATION", "kind": "text search configuration", "status": "declared", "entity": "Postgres::TextSearchConfiguration", "note": "PARSER and its ADD MAPPING statements; COPY is refused, since it copies mappings chant does not read; mappings change in place, the parser by drop and create" },
|
|
38
|
+
{ "command": "CREATE TEXT SEARCH DICTIONARY", "kind": "text search dictionary", "status": "declared", "entity": "Postgres::TextSearchDictionary", "note": "a template and its options; options change in place, the template by drop and create" },
|
|
39
39
|
{ "command": "CREATE TEXT SEARCH PARSER", "kind": "text search parser", "status": "refused", "reason": "no tag declares a text search parser; one is C code an extension installs" },
|
|
40
40
|
{ "command": "CREATE TEXT SEARCH TEMPLATE", "kind": "text search template", "status": "refused", "reason": "no tag declares a text search template; one is C code an extension installs" },
|
|
41
41
|
{ "command": "CREATE TRANSFORM", "kind": "transform", "status": "refused", "reason": "no tag declares a transform; an extension installs one" },
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
{ "command": "CREATE TYPE", "form": "AS RANGE", "match": "\\bAS\\s+RANGE\\b", "kind": "range type", "status": "declared", "entity": "Postgres::RangeType", "note": "its multirange type comes with it (MULTIRANGE_TYPE_NAME)" },
|
|
46
46
|
{ "command": "CREATE TYPE", "form": "INPUT, OUTPUT, or a shell type", "kind": "base type", "status": "refused", "reason": "no tag declares a base type; its input and output functions are C code an extension installs" },
|
|
47
47
|
{ "command": "CREATE USER", "kind": "user", "status": "declared", "entity": "Postgres::Role", "note": "an alias of CREATE ROLE ... LOGIN" },
|
|
48
|
-
{ "command": "CREATE USER MAPPING", "kind": "user mapping", "status": "refused", "reason": "
|
|
48
|
+
{ "command": "CREATE USER MAPPING", "kind": "user mapping", "status": "refused", "reason": "a user mapping carries the remote user and password, which the environment supplies and a declaration never holds" },
|
|
49
49
|
{ "command": "CREATE VIEW", "kind": "view", "status": "declared", "entity": "Postgres::View" }
|
|
50
50
|
]
|
|
51
51
|
}
|
package/src/coverage.test.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { KIND_COVERAGE, PINNED_COMMANDS, kindCoverageProblems, pinnedCommandsCur
|
|
|
3
3
|
import { POSTGRES_PROBES } from "./postgres/live/undeclarable";
|
|
4
4
|
import { CLICKHOUSE_PROBES } from "./clickhouse/live/undeclarable";
|
|
5
5
|
import { SQL_DIALECTS } from "./dialects";
|
|
6
|
+
import { POSTGRES_ENTITY_TYPES } from "./postgres/entity-types";
|
|
7
|
+
import { CLICKHOUSE_ENTITY_TYPES } from "./clickhouse/entities";
|
|
6
8
|
|
|
7
9
|
describe("the object coverage table (#3911)", () => {
|
|
8
10
|
test.each(SQL_DIALECTS)("%s: every CREATE command a pinned server has is marked, and nothing else", (dialect) => {
|
|
@@ -29,10 +31,32 @@ describe("the object coverage table (#3911)", () => {
|
|
|
29
31
|
]);
|
|
30
32
|
});
|
|
31
33
|
|
|
32
|
-
test("
|
|
34
|
+
test("a declared row names an entity type the dialect has, and every entity type but a grant has a declared row", () => {
|
|
35
|
+
// A grant is a GRANT statement, not a CREATE command, so no row is about it.
|
|
36
|
+
const types = {
|
|
37
|
+
postgres: { all: Object.values(POSTGRES_ENTITY_TYPES) as string[], notCreate: ["Postgres::Grant", "Postgres::DefaultPrivileges"] },
|
|
38
|
+
clickhouse: { all: Object.values(CLICKHOUSE_ENTITY_TYPES) as string[], notCreate: ["ClickHouse::Grant"] },
|
|
39
|
+
};
|
|
40
|
+
for (const dialect of SQL_DIALECTS) {
|
|
41
|
+
const entities = KIND_COVERAGE[dialect].filter((r) => r.status === "declared").map((r) => r.entity!);
|
|
42
|
+
for (const e of entities) expect(types[dialect].all, `${dialect} row entity ${e}`).toContain(e);
|
|
43
|
+
for (const e of types[dialect].all.filter((t) => !types[dialect].notCreate.includes(t))) expect(entities, `${dialect} entity type ${e} has no declared row`).toContain(e);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("a kind that is declared, referenced, refused or out of scope is told apart: only a declared row names an entity", () => {
|
|
48
|
+
for (const dialect of SQL_DIALECTS) {
|
|
49
|
+
for (const r of KIND_COVERAGE[dialect]) {
|
|
50
|
+
if (r.status === "declared") expect(r.reason, `${dialect} ${r.kind}`).toBeUndefined();
|
|
51
|
+
else expect(r.entity, `${dialect} ${r.kind}`).toBeUndefined();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("every refused or referenced kind has the live query that finds it, and every query a row", () => {
|
|
33
57
|
const probes = { postgres: POSTGRES_PROBES, clickhouse: CLICKHOUSE_PROBES };
|
|
34
58
|
for (const dialect of SQL_DIALECTS) {
|
|
35
|
-
const refused = KIND_COVERAGE[dialect].filter((r) => r.status === "refused").map((r) => r.kind);
|
|
59
|
+
const refused = KIND_COVERAGE[dialect].filter((r) => r.status === "refused" || r.status === "referenced").map((r) => r.kind);
|
|
36
60
|
for (const kind of refused) expect(Object.keys(probes[dialect]), `${dialect} ${kind}`).toContain(kind);
|
|
37
61
|
const kinds = KIND_COVERAGE[dialect].map((r) => r.kind);
|
|
38
62
|
for (const kind of Object.keys(probes[dialect])) expect(kinds, `${dialect} probe ${kind}`).toContain(kind);
|
|
@@ -68,6 +92,7 @@ describe("a statement's kind", () => {
|
|
|
68
92
|
["CREATE SETTINGS PROFILE p SETTINGS max_threads = 1", "settings profile"],
|
|
69
93
|
["CREATE OR REPLACE FUNCTION f AS (x) -> x + 1", "SQL function"],
|
|
70
94
|
["CREATE QUOTA q FOR INTERVAL 1 hour MAX queries = 10", "quota"],
|
|
95
|
+
["CREATE NAMED COLLECTION IF NOT EXISTS c AS url = 'x'", "named collection"],
|
|
71
96
|
])("clickhouse: %s is a %s", (sql, kind) => {
|
|
72
97
|
expect(statementKind("clickhouse", sql)?.kind).toBe(kind);
|
|
73
98
|
});
|
package/src/coverage.ts
CHANGED
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* Which kinds of object each dialect declares (#3911): every `CREATE` command
|
|
5
5
|
* a pinned server has (`./coverage/<dialect>-commands.json`, written by
|
|
6
6
|
* `scripts/sql-create-commands.ts` from the server's own sources) has a row
|
|
7
|
-
* in `./coverage/<dialect>.json` marking it declared, refused
|
|
8
|
-
* or out of scope. A
|
|
7
|
+
* in `./coverage/<dialect>.json` marking it declared, referenced, refused
|
|
8
|
+
* with a reason, or out of scope. A referenced kind (a ClickHouse named
|
|
9
|
+
* collection, #4395) is never declared: a declaration names one, a plan
|
|
10
|
+
* checks it exists, and import lists the server's as prerequisites. A command with several forms that differ in what chant can
|
|
9
11
|
* hold (`CREATE TYPE ... AS ENUM` and `CREATE TYPE ... AS RANGE`) has a row
|
|
10
12
|
* per form. Import refuses an object of a refused kind by that row's `kind`
|
|
11
13
|
* (`./core/undeclarable.ts`), and the docs page is rendered from the rows
|
|
@@ -31,8 +33,16 @@ import clickhouseCommands from "./coverage/clickhouse-commands.json" with { type
|
|
|
31
33
|
|
|
32
34
|
// ── Object kinds ───────────────────────────────────────────────────────
|
|
33
35
|
|
|
34
|
-
/**
|
|
35
|
-
|
|
36
|
+
/**
|
|
37
|
+
* `declared`: a tag holds it. `referenced`: the environment's, never declared
|
|
38
|
+
* and its values never read; a declaration names it, a plan checks it exists,
|
|
39
|
+
* and import lists the server's as prerequisites (#4395). `refused`: import
|
|
40
|
+
* names it and stops. `out-of-scope`: the environment's, never imported.
|
|
41
|
+
*/
|
|
42
|
+
export type KindStatus = "declared" | "referenced" | "refused" | "out-of-scope";
|
|
43
|
+
|
|
44
|
+
/** Every status a coverage row may have. */
|
|
45
|
+
export const KIND_STATUSES: readonly KindStatus[] = ["declared", "referenced", "refused", "out-of-scope"];
|
|
36
46
|
|
|
37
47
|
/** One row of a dialect's coverage table: a `CREATE` command, or one form of it. */
|
|
38
48
|
export interface KindRow {
|
|
@@ -51,7 +61,7 @@ export interface KindRow {
|
|
|
51
61
|
status: KindStatus;
|
|
52
62
|
/** The entity type that declares it, when declared. */
|
|
53
63
|
entity?: string;
|
|
54
|
-
/** Why it is refused or out of scope. */
|
|
64
|
+
/** Why it is referenced, refused or out of scope. */
|
|
55
65
|
reason?: string;
|
|
56
66
|
/** Anything else a reader should know. */
|
|
57
67
|
note?: string;
|
|
@@ -80,7 +90,8 @@ export function kindRow(dialect: SqlDialect, kind: string): KindRow {
|
|
|
80
90
|
* What is wrong with a dialect's coverage table, against its pinned servers'
|
|
81
91
|
* commands: a command a pinned server has and no row covers, a row for a
|
|
82
92
|
* command no pinned server has, a kind named twice, a declared row with no
|
|
83
|
-
* entity, a refused or out-of-scope row with no reason. Empty
|
|
93
|
+
* entity, a referenced, refused or out-of-scope row with no reason. Empty
|
|
94
|
+
* when complete.
|
|
84
95
|
*/
|
|
85
96
|
export function kindCoverageProblems(dialect: SqlDialect, opts: { pins?: ReadonlyArray<{ version: string; commands: readonly string[] }>; rows?: readonly KindRow[] } = {}): string[] {
|
|
86
97
|
const rows = opts.rows ?? KIND_COVERAGE[dialect];
|
|
@@ -96,7 +107,7 @@ export function kindCoverageProblems(dialect: SqlDialect, opts: { pins?: Readonl
|
|
|
96
107
|
for (const r of rows) {
|
|
97
108
|
if (seen.has(r.kind)) problems.push(`the kind "${r.kind}" has two rows`);
|
|
98
109
|
seen.add(r.kind);
|
|
99
|
-
if (!
|
|
110
|
+
if (!KIND_STATUSES.includes(r.status)) problems.push(`${r.kind}: status "${r.status}" is not ${KIND_STATUSES.slice(0, -1).join(", ")} or ${KIND_STATUSES.at(-1)}`);
|
|
100
111
|
if (r.status === "declared" && !r.entity) problems.push(`${r.kind} is declared, and names no entity type`);
|
|
101
112
|
if (r.status !== "declared" && !r.reason) problems.push(`${r.kind} is ${r.status}, and gives no reason`);
|
|
102
113
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant sql drizzle [--output <path>] [--check] [--bigint number|bigint]`
|
|
3
|
+
* (#4401): build the project the way `chant build` does, folded, and write the
|
|
4
|
+
* Drizzle schema module {@link renderDrizzle} renders from its entities.
|
|
5
|
+
*
|
|
6
|
+
* The file goes to `<project>/drizzle-schema.ts` by default, outside the
|
|
7
|
+
* source directory the build reads, and starts with chant's generated-file
|
|
8
|
+
* marker either way. `-o` and `--output` put it elsewhere, which is also the
|
|
9
|
+
* flag a workspace `generated` entry's generator passes. `--check` renders the
|
|
10
|
+
* same text and compares it with the file instead of writing it: exit 1,
|
|
11
|
+
* naming the file, when they differ.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
16
|
+
import type { CommandGroupContext } from "@intentius/chant/cli/command-group";
|
|
17
|
+
import { DrizzleDialectError, renderDrizzle } from "./drizzle";
|
|
18
|
+
import { buildFolded, loadProject } from "./sql-types-command";
|
|
19
|
+
|
|
20
|
+
/** The file `chant sql drizzle` writes by default, in the project directory. */
|
|
21
|
+
export const DRIZZLE_SCHEMA_FILE = "drizzle-schema.ts";
|
|
22
|
+
|
|
23
|
+
const USAGE = "usage: chant sql drizzle [--output <path>] [--check] [--bigint number|bigint]";
|
|
24
|
+
|
|
25
|
+
interface Args {
|
|
26
|
+
output?: string;
|
|
27
|
+
check: boolean;
|
|
28
|
+
bigint?: "number" | "bigint";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseArgs(args: readonly string[]): Args | { error: string } {
|
|
32
|
+
const out: Args = { check: false };
|
|
33
|
+
for (let i = 0; i < args.length; i++) {
|
|
34
|
+
const a = args[i]!;
|
|
35
|
+
if (a === "--output" || a === "-o") {
|
|
36
|
+
const v = args[++i];
|
|
37
|
+
if (!v) return { error: `${a} needs a path` };
|
|
38
|
+
out.output = v;
|
|
39
|
+
} else if (a.startsWith("--output=")) out.output = a.slice("--output=".length);
|
|
40
|
+
else if (a === "--check") out.check = true;
|
|
41
|
+
else if (a === "--bigint" || a.startsWith("--bigint=")) {
|
|
42
|
+
const v = a === "--bigint" ? args[++i] : a.slice("--bigint=".length);
|
|
43
|
+
if (v !== "number" && v !== "bigint") return { error: "--bigint takes number or bigint" };
|
|
44
|
+
out.bigint = v;
|
|
45
|
+
} else return { error: `unknown argument ${a}` };
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function runDrizzle(ctx: CommandGroupContext): Promise<number> {
|
|
51
|
+
const args = parseArgs(ctx.rawArgs);
|
|
52
|
+
if ("error" in args) {
|
|
53
|
+
console.error(`${args.error}\n${USAGE}`);
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
const cwd = process.cwd();
|
|
57
|
+
const project = await loadProject(cwd);
|
|
58
|
+
const output = args.output !== undefined ? resolve(cwd, args.output) : join(project.projectDir, DRIZZLE_SCHEMA_FILE);
|
|
59
|
+
const result = await buildFolded(project, "chant sql drizzle", "nothing was written");
|
|
60
|
+
if (!result) return 1;
|
|
61
|
+
|
|
62
|
+
let rendered;
|
|
63
|
+
try {
|
|
64
|
+
rendered = renderDrizzle(result.entities, { config: project.config as unknown as Record<string, unknown>, ...(args.bigint ? { bigintMode: args.bigint } : {}) });
|
|
65
|
+
} catch (err) {
|
|
66
|
+
if (!(err instanceof DrizzleDialectError)) throw err;
|
|
67
|
+
console.error(`chant sql drizzle: ${err.message}`);
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
const shown = relative(cwd, output);
|
|
71
|
+
const named = shown && !shown.startsWith("..") ? shown : output;
|
|
72
|
+
if (args.check) {
|
|
73
|
+
// Compares only; the file is never written.
|
|
74
|
+
const onDisk = existsSync(output) ? readFileSync(output, "utf8") : undefined;
|
|
75
|
+
if (onDisk === rendered.text) {
|
|
76
|
+
console.log(`${named} is current`);
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
console.error(
|
|
80
|
+
onDisk === undefined
|
|
81
|
+
? `chant sql drizzle: ${named} does not exist; run \`chant sql drizzle\` to write it`
|
|
82
|
+
: `chant sql drizzle: ${named} differs from what the build declares; run \`chant sql drizzle\` to rewrite it`,
|
|
83
|
+
);
|
|
84
|
+
return 1;
|
|
85
|
+
}
|
|
86
|
+
mkdirSync(dirname(output), { recursive: true });
|
|
87
|
+
writeFileSync(output, rendered.text);
|
|
88
|
+
const { tables, views, enums, sequences } = rendered.counts;
|
|
89
|
+
const plural = (n: number, word: string) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
90
|
+
console.log(`wrote ${named}: ${[plural(tables, "table"), plural(views, "view"), plural(enums, "enum"), plural(sequences, "sequence")].join(", ")}`);
|
|
91
|
+
for (const s of rendered.skipped) console.log(`not written: ${s.kind} ${s.names.join(", ")} (${s.reason})`);
|
|
92
|
+
for (const n of rendered.approximated) console.log(`partly written: ${n}`);
|
|
93
|
+
return 0;
|
|
94
|
+
}
|