@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
package/src/files/clickhouse.ts
CHANGED
|
@@ -21,7 +21,8 @@ import { isAccessStatement, parseAccess, type AccessNode } from "../clickhouse/a
|
|
|
21
21
|
import type { Token } from "../clickhouse/tokens";
|
|
22
22
|
import { CLICKHOUSE_TAG_OF } from "../clickhouse/import/generator";
|
|
23
23
|
import { describeStatement, type ImportedObject } from "../clickhouse/import/ir";
|
|
24
|
-
import { fileUnparsed, firstLine, lineAt, quoteBare, splitTokens, tokensText, undeclarableStatement, type FileLine, type ReadOptions } from "./common";
|
|
24
|
+
import { fileUnparsed, firstLine, lineAt, quoteBare, referencedStatement, splitTokens, tokensText, undeclarableStatement, type FileLine, type ReadOptions } from "./common";
|
|
25
|
+
import { createdCollectionName } from "../clickhouse/named-collections";
|
|
25
26
|
import { templateSafe, templateStrings } from "./postgres";
|
|
26
27
|
|
|
27
28
|
type Tag = (strings: TemplateStringsArray, ...values: unknown[]) => Declarable;
|
|
@@ -95,6 +96,12 @@ export function readClickHouse(ddl: string, options: ReadOptions, problems: stri
|
|
|
95
96
|
for (const stmt of splitTokens(tokenizeText(ddl, 0), isTrivia)) {
|
|
96
97
|
let sql = tokensText(stmt);
|
|
97
98
|
const line = lineAt(ddl, stmt[0]!.start);
|
|
99
|
+
const referenced = options.prerequisites ? referencedStatement("clickhouse", sql) : undefined;
|
|
100
|
+
const collection = referenced ? createdCollectionName(sql) : undefined;
|
|
101
|
+
if (referenced && collection !== undefined) {
|
|
102
|
+
options.prerequisites!.push({ kind: referenced.kind, name: collection });
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
98
105
|
const undeclarable = undeclarableStatement("clickhouse", sql);
|
|
99
106
|
if (undeclarable) {
|
|
100
107
|
(options.undeclarable ?? problems).push(undeclarable);
|
package/src/files/common.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import type { Token } from "../core/tokens";
|
|
4
4
|
import type { UnknownClause } from "../core/cursor";
|
|
5
5
|
import type { SqlDialect } from "../dialects";
|
|
6
|
-
import { statementKind } from "../coverage";
|
|
6
|
+
import { statementKind, type KindRow } from "../coverage";
|
|
7
7
|
|
|
8
8
|
export interface ReadOptions {
|
|
9
9
|
/** Names the DDL in errors: a file's path, a command. */
|
|
@@ -19,6 +19,19 @@ export interface ReadOptions {
|
|
|
19
19
|
* --parser-option skipUndeclarable` lists them as warnings.
|
|
20
20
|
*/
|
|
21
21
|
undeclarable?: string[];
|
|
22
|
+
/**
|
|
23
|
+
* Where a statement of a referenced kind goes (#4395), as the name it
|
|
24
|
+
* creates: `chant import` lists a named collection's `CREATE` as an
|
|
25
|
+
* environment prerequisite. Without it, such a statement is a problem, as
|
|
26
|
+
* in a build's `.sql` file, which never declares one.
|
|
27
|
+
*/
|
|
28
|
+
prerequisites?: Array<{ kind: string; name: string }>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The coverage row of a `CREATE` of a referenced kind (#4395), or undefined for any other statement. */
|
|
32
|
+
export function referencedStatement(dialect: SqlDialect, sql: string): KindRow | undefined {
|
|
33
|
+
const row = statementKind(dialect, sql);
|
|
34
|
+
return row?.status === "referenced" ? row : undefined;
|
|
22
35
|
}
|
|
23
36
|
|
|
24
37
|
/**
|
package/src/files/postgres.ts
CHANGED
|
@@ -138,7 +138,7 @@ export function readPostgres(ddl: string, options: ReadOptions, problems: string
|
|
|
138
138
|
};
|
|
139
139
|
switch (node.statement) {
|
|
140
140
|
case "comment":
|
|
141
|
-
if (!["SCHEMA", "EXTENSION", "ROLE", "TRIGGER", "POLICY", "CONSTRAINT", "COLUMN", "CAST", "EVENT TRIGGER"].includes(node.objectType)) mark(node.target);
|
|
141
|
+
if (!["SCHEMA", "EXTENSION", "ROLE", "TRIGGER", "POLICY", "CONSTRAINT", "COLUMN", "CAST", "EVENT TRIGGER", "FOREIGN DATA WRAPPER", "SERVER", "PUBLICATION"].includes(node.objectType)) mark(node.target);
|
|
142
142
|
if (node.objectType === "COLUMN" && nameOf(tokens, node.target).length === 2 && options.schema) {
|
|
143
143
|
const first = tokens.slice(node.target.span.from, node.target.span.to).find((t) => !isTrivia(t));
|
|
144
144
|
if (first) inserts.add(tokens.indexOf(first));
|
|
@@ -150,6 +150,10 @@ export function readPostgres(ddl: string, options: ReadOptions, problems: string
|
|
|
150
150
|
mark(node.table);
|
|
151
151
|
followOns.push({ tokens, node, inserts });
|
|
152
152
|
continue;
|
|
153
|
+
case "textSearchMapping":
|
|
154
|
+
mark(node.name);
|
|
155
|
+
followOns.push({ tokens, node, inserts });
|
|
156
|
+
continue;
|
|
153
157
|
case "index":
|
|
154
158
|
case "trigger":
|
|
155
159
|
case "policy":
|
|
@@ -177,7 +181,7 @@ export function readPostgres(ddl: string, options: ReadOptions, problems: string
|
|
|
177
181
|
problems.push(`an index with no name (its declaration needs one): ${firstLine(tokensText(tokens))}`);
|
|
178
182
|
continue;
|
|
179
183
|
}
|
|
180
|
-
const ownName = !["schema", "extension", "index", "trigger", "policy", "role", "grant", "cast", "eventTrigger"].includes(node.statement);
|
|
184
|
+
const ownName = !["schema", "extension", "index", "trigger", "policy", "role", "grant", "cast", "eventTrigger", "foreignDataWrapper", "foreignServer", "publication"].includes(node.statement);
|
|
181
185
|
if (ownName && "name" in node) mark(node.name);
|
|
182
186
|
const object: PgFileObject = { node, statements: [rewrite(tokens, inserts, options.schema)], constraints: [], line, unparsed: fileUnparsed("unknown" in node ? node.unknown : undefined, tokens, tokensText(tokens), line) };
|
|
183
187
|
objects.push(object);
|
|
@@ -189,7 +193,7 @@ export function readPostgres(ddl: string, options: ReadOptions, problems: string
|
|
|
189
193
|
const k = followOnOwner(f.node, f.tokens, nameOf, qualify);
|
|
190
194
|
const owner = k ? owners.get(k) : undefined;
|
|
191
195
|
if (!owner) {
|
|
192
|
-
const what = f.node.statement === "comment" ? `COMMENT ON ${f.node.objectType}` : "ALTER TABLE ... ROW LEVEL SECURITY";
|
|
196
|
+
const what = f.node.statement === "comment" ? `COMMENT ON ${f.node.objectType}` : f.node.statement === "textSearchMapping" ? "ALTER TEXT SEARCH CONFIGURATION ... ADD MAPPING" : "ALTER TABLE ... ROW LEVEL SECURITY";
|
|
193
197
|
problems.push(`${what} for an object the file does not create: ${firstLine(tokensText(f.tokens))}`);
|
|
194
198
|
continue;
|
|
195
199
|
}
|
|
@@ -279,11 +283,23 @@ function ownerKey(node: StatementNode, tokens: Token[], nameOf: NameOf, qualify:
|
|
|
279
283
|
return `${node.statement} ${nameOf(tokens, node.name).at(-1) ?? ""} ON ${qkey(qualify(nameOf(tokens, node.table)))}`;
|
|
280
284
|
case "eventTrigger":
|
|
281
285
|
return `eventTrigger ${nameOf(tokens, node.name).at(-1) ?? ""}`;
|
|
286
|
+
case "foreignDataWrapper":
|
|
287
|
+
case "foreignServer":
|
|
288
|
+
case "publication":
|
|
289
|
+
return `${node.statement} ${nameOf(tokens, node.name).at(-1) ?? ""}`;
|
|
290
|
+
// A class or family is named by its index method too, and a configuration and a dictionary are different namespaces.
|
|
291
|
+
case "operatorClass":
|
|
292
|
+
case "operatorFamily":
|
|
293
|
+
return `${node.statement} ${qkey(qualify(nameOf(tokens, node.name)))} USING ${node.method}`;
|
|
294
|
+
case "textSearchConfiguration":
|
|
295
|
+
case "textSearchDictionary":
|
|
296
|
+
return `${node.statement} ${qkey(qualify(nameOf(tokens, node.name)))}`;
|
|
282
297
|
case "cast":
|
|
283
298
|
return `cast ${castKey(tokens, node.source, node.target)}`;
|
|
284
299
|
case "grant":
|
|
285
300
|
case "comment":
|
|
286
301
|
case "rowSecurity":
|
|
302
|
+
case "textSearchMapping":
|
|
287
303
|
return undefined;
|
|
288
304
|
default:
|
|
289
305
|
return `rel ${qkey(qualify(nameOf(tokens, node.name)))}`;
|
|
@@ -293,6 +309,7 @@ function ownerKey(node: StatementNode, tokens: Token[], nameOf: NameOf, qualify:
|
|
|
293
309
|
/** The key of the object a COMMENT ON or ALTER TABLE ... ROW LEVEL SECURITY finishes. */
|
|
294
310
|
function followOnOwner(node: StatementNode, tokens: Token[], nameOf: NameOf, qualify: Qualify): string | undefined {
|
|
295
311
|
if (node.statement === "rowSecurity") return `rel ${qkey(qualify(nameOf(tokens, node.table)))}`;
|
|
312
|
+
if (node.statement === "textSearchMapping") return `textSearchConfiguration ${qkey(qualify(nameOf(tokens, node.name)))}`;
|
|
296
313
|
if (node.statement !== "comment") return undefined;
|
|
297
314
|
const target = nameOf(tokens, node.target);
|
|
298
315
|
switch (node.objectType) {
|
|
@@ -311,6 +328,20 @@ function followOnOwner(node: StatementNode, tokens: Token[], nameOf: NameOf, qua
|
|
|
311
328
|
return node.on ? `${node.objectType.toLowerCase()} ${target.at(-1) ?? ""} ON ${qkey(qualify(nameOf(tokens, node.on)))}` : undefined;
|
|
312
329
|
case "EVENT TRIGGER":
|
|
313
330
|
return `eventTrigger ${target.at(-1) ?? ""}`;
|
|
331
|
+
case "FOREIGN DATA WRAPPER":
|
|
332
|
+
return `foreignDataWrapper ${target.at(-1) ?? ""}`;
|
|
333
|
+
case "SERVER":
|
|
334
|
+
return `foreignServer ${target.at(-1) ?? ""}`;
|
|
335
|
+
case "PUBLICATION":
|
|
336
|
+
return `publication ${target.at(-1) ?? ""}`;
|
|
337
|
+
case "OPERATOR CLASS":
|
|
338
|
+
return `operatorClass ${qkey(qualify(target))} USING ${node.using ?? ""}`;
|
|
339
|
+
case "OPERATOR FAMILY":
|
|
340
|
+
return `operatorFamily ${qkey(qualify(target))} USING ${node.using ?? ""}`;
|
|
341
|
+
case "TEXT SEARCH CONFIGURATION":
|
|
342
|
+
return `textSearchConfiguration ${qkey(qualify(target))}`;
|
|
343
|
+
case "TEXT SEARCH DICTIONARY":
|
|
344
|
+
return `textSearchDictionary ${qkey(qualify(target))}`;
|
|
314
345
|
case "CAST":
|
|
315
346
|
return node.cast ? `cast ${castKey(tokens, node.cast.source, node.cast.target)}` : undefined;
|
|
316
347
|
case "CONSTRAINT":
|
|
@@ -2177,6 +2177,9 @@ export const AGGREGATE_COMBINATORS: readonly string[] = ["ArgMax","ArgMin","Arra
|
|
|
2177
2177
|
/** The table functions (`system.table_functions`): `numbers`, `remote`, `s3`. */
|
|
2178
2178
|
export const TABLE_FUNCTIONS: readonly string[] = ["SQLStandardValues","arrowFlight","arrowflight","azureBlobStorage","azureBlobStorageCluster","bigquery","cluster","clusterAllReplicas","cosn","deltaLake","deltaLakeAzure","deltaLakeAzureCluster","deltaLakeCluster","deltaLakeLocal","deltaLakeS3","deltaLakeS3Cluster","dictionary","eval","executable","file","fileCluster","filesystem","format","fuzzJSON","fuzzQuery","gcs","generateRandom","generateSeries","generate_series","hdfs","hdfsCluster","hive","hudi","hudiCluster","iceberg","icebergAzure","icebergAzureCluster","icebergCluster","icebergHDFS","icebergHDFSCluster","icebergLocal","icebergLocalCluster","icebergS3","icebergS3Cluster","input","jdbc","loop","merge","mergeTreeAnalyzeIndexes","mergeTreeAnalyzeIndexesUUID","mergeTreeCodecBlockCounts","mergeTreeIndex","mergeTreeProjection","mergeTreeTextIndex","mongodb","mysql","null","numbers","numbers_mt","odbc","oss","paimon","paimonAzure","paimonAzureCluster","paimonCluster","paimonHDFS","paimonHDFSCluster","paimonLocal","paimonS3","paimonS3Cluster","postgresql","primes","prometheusQuery","prometheusQueryRange","redis","remote","remoteSecure","s3","s3Cluster","sqlite","timeSeriesData","timeSeriesMetrics","timeSeriesSamples","timeSeriesSelector","timeSeriesTags","url","urlCluster","values","view","viewExplain","viewIfPermitted","ytsaurus","zeros","zeros_mt"];
|
|
2179
2179
|
|
|
2180
|
+
/** The input and output formats (`system.formats`): `Parquet`, `JSONEachRow`. */
|
|
2181
|
+
export const FORMATS: readonly string[] = ["Arrow","ArrowStream","Avro","AvroConfluent","BSONEachRow","Buffers","CSV","CSVWithNames","CSVWithNamesAndTypes","CapnProto","CustomSeparated","CustomSeparatedIgnoreSpaces","CustomSeparatedIgnoreSpacesWithNames","CustomSeparatedIgnoreSpacesWithNamesAndTypes","CustomSeparatedWithNames","CustomSeparatedWithNamesAndTypes","DWARF","Form","GeoJSON","Hash","HiveText","JSON","JSONAsObject","JSONAsString","JSONColumns","JSONColumnsWithMetadata","JSONCompact","JSONCompactColumns","JSONCompactEachRow","JSONCompactEachRowWithNames","JSONCompactEachRowWithNamesAndTypes","JSONCompactEachRowWithProgress","JSONCompactStrings","JSONCompactStringsEachRow","JSONCompactStringsEachRowWithNames","JSONCompactStringsEachRowWithNamesAndTypes","JSONCompactStringsEachRowWithProgress","JSONEachRow","JSONEachRowWithProgress","JSONL","JSONLines","JSONObjectEachRow","JSONStrings","JSONStringsEachRow","JSONStringsEachRowWithProgress","LineAsString","LineAsStringWithNames","LineAsStringWithNamesAndTypes","MD","Markdown","MsgPack","MySQLDump","MySQLWire","NDJSON","Native","Npy","Null","ODBCDriver2","ORC","One","PNG","Parquet","ParquetMetadata","PostgreSQLWire","Pretty","PrettyCompact","PrettyCompactMonoBlock","PrettyCompactNoEscapes","PrettyCompactNoEscapesMonoBlock","PrettyJSONEachRow","PrettyJSONLines","PrettyMonoBlock","PrettyNDJSON","PrettyNoEscapes","PrettyNoEscapesMonoBlock","PrettySpace","PrettySpaceMonoBlock","PrettySpaceNoEscapes","PrettySpaceNoEscapesMonoBlock","Prometheus","Protobuf","ProtobufList","ProtobufSingle","Puffin","PuffinMetadata","Raw","RawBLOB","RawWithNames","RawWithNamesAndTypes","Regexp","RowBinary","RowBinaryWithDefaults","RowBinaryWithNames","RowBinaryWithNamesAndTypes","RowBinaryWithNamesAndTypesAndDefaults","SQLInsert","TSKV","TSV","TSVRaw","TSVRawWithNames","TSVRawWithNamesAndTypes","TSVWithNames","TSVWithNamesAndTypes","TabSeparated","TabSeparatedRaw","TabSeparatedRawWithNames","TabSeparatedRawWithNamesAndTypes","TabSeparatedWithNames","TabSeparatedWithNamesAndTypes","Template","TemplateIgnoreSpaces","Values","Vertical","XML"];
|
|
2182
|
+
|
|
2180
2183
|
export const SKIP_INDEX_TYPES: Readonly<Record<SkipIndexType, SkipIndexSpec>> = {
|
|
2181
2184
|
bloom_filter: {"syntax":"INDEX name expr TYPE bloom_filter([false_positive_rate]) GRANULARITY g","args":[{"name":"false_positive_rate","position":0,"optional":true,"repeated":false,"named":false,"kind":"number","range":[0,1],"note":"Exclusive of 0 and 1; 0.025 when omitted."}],"summary":"A Bloom filter over the values of the index expression, for speeding up equality and IN conditions on columns, including Array and Map elements."},
|
|
2182
2185
|
hypothesis: {"syntax":"INDEX name expr TYPE hypothesis GRANULARITY g","args":[],"summary":"Deprecated and no longer supported. It is retained only so that tables which still reference it can be attached."},
|
|
@@ -129,6 +129,56 @@
|
|
|
129
129
|
"kind": "resource",
|
|
130
130
|
"lexicon": "sql"
|
|
131
131
|
},
|
|
132
|
+
"PostgresOperator": {
|
|
133
|
+
"resourceType": "Postgres::Operator",
|
|
134
|
+
"kind": "resource",
|
|
135
|
+
"lexicon": "sql"
|
|
136
|
+
},
|
|
137
|
+
"PostgresOperatorClass": {
|
|
138
|
+
"resourceType": "Postgres::OperatorClass",
|
|
139
|
+
"kind": "resource",
|
|
140
|
+
"lexicon": "sql"
|
|
141
|
+
},
|
|
142
|
+
"PostgresOperatorFamily": {
|
|
143
|
+
"resourceType": "Postgres::OperatorFamily",
|
|
144
|
+
"kind": "resource",
|
|
145
|
+
"lexicon": "sql"
|
|
146
|
+
},
|
|
147
|
+
"PostgresForeignTable": {
|
|
148
|
+
"resourceType": "Postgres::ForeignTable",
|
|
149
|
+
"kind": "resource",
|
|
150
|
+
"lexicon": "sql"
|
|
151
|
+
},
|
|
152
|
+
"PostgresTextSearchConfiguration": {
|
|
153
|
+
"resourceType": "Postgres::TextSearchConfiguration",
|
|
154
|
+
"kind": "resource",
|
|
155
|
+
"lexicon": "sql"
|
|
156
|
+
},
|
|
157
|
+
"PostgresTextSearchDictionary": {
|
|
158
|
+
"resourceType": "Postgres::TextSearchDictionary",
|
|
159
|
+
"kind": "resource",
|
|
160
|
+
"lexicon": "sql"
|
|
161
|
+
},
|
|
162
|
+
"PostgresStatistics": {
|
|
163
|
+
"resourceType": "Postgres::Statistics",
|
|
164
|
+
"kind": "resource",
|
|
165
|
+
"lexicon": "sql"
|
|
166
|
+
},
|
|
167
|
+
"PostgresForeignDataWrapper": {
|
|
168
|
+
"resourceType": "Postgres::ForeignDataWrapper",
|
|
169
|
+
"kind": "resource",
|
|
170
|
+
"lexicon": "sql"
|
|
171
|
+
},
|
|
172
|
+
"PostgresForeignServer": {
|
|
173
|
+
"resourceType": "Postgres::ForeignServer",
|
|
174
|
+
"kind": "resource",
|
|
175
|
+
"lexicon": "sql"
|
|
176
|
+
},
|
|
177
|
+
"PostgresPublication": {
|
|
178
|
+
"resourceType": "Postgres::Publication",
|
|
179
|
+
"kind": "resource",
|
|
180
|
+
"lexicon": "sql"
|
|
181
|
+
},
|
|
132
182
|
"PostgresExtension": {
|
|
133
183
|
"resourceType": "Postgres::Extension",
|
|
134
184
|
"kind": "resource",
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { resolveParserOptions } from "@intentius/chant/import/parser-options";
|
|
3
|
+
import { guessDialect, looksLikeTSql, sourceOption } from "./import-parser";
|
|
4
|
+
import { sqlPlugin } from "./plugin";
|
|
5
|
+
|
|
6
|
+
const TSQL = `SET ANSI_NULLS ON
|
|
7
|
+
GO
|
|
8
|
+
CREATE TABLE [dbo].[Orders] (
|
|
9
|
+
[OrderID] INT IDENTITY (1, 1) NOT NULL,
|
|
10
|
+
[Note] NVARCHAR (200) NULL,
|
|
11
|
+
CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED ([OrderID] ASC)
|
|
12
|
+
);
|
|
13
|
+
GO`;
|
|
14
|
+
|
|
15
|
+
const parse = (text: string, entries: string[] = []) => {
|
|
16
|
+
const resolved = resolveParserOptions(sqlPlugin, entries);
|
|
17
|
+
if ("error" in resolved) throw new Error(resolved.error);
|
|
18
|
+
return sqlPlugin.templateParser!(resolved.options).parse(text);
|
|
19
|
+
};
|
|
20
|
+
const types = (ir: { resources: Array<{ type: string }> }) => [...new Set(ir.resources.map((r) => r.type))];
|
|
21
|
+
|
|
22
|
+
describe("a T-SQL file is not read as Postgres", () => {
|
|
23
|
+
test("the dialect guess alone would have said Postgres, which is why the markers exist", () => {
|
|
24
|
+
expect(guessDialect(TSQL)).toBe("postgres");
|
|
25
|
+
expect(looksLikeTSql(TSQL)).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("with no option, a T-SQL file is read as SQL Server and gives ClickHouse objects and diagnostics", () => {
|
|
29
|
+
const ir = parse(TSQL);
|
|
30
|
+
expect(types(ir)).toEqual(["ClickHouse::Table"]);
|
|
31
|
+
expect(ir.diagnostics?.length).toBeGreaterThan(0);
|
|
32
|
+
expect(ir.metadata).toMatchObject({ source: "sqlserver" });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test.each([
|
|
36
|
+
["a GO line", "CREATE TABLE a (x int)\nGO\n"],
|
|
37
|
+
["CLUSTERED", "CREATE CLUSTERED INDEX ix ON a (x)"],
|
|
38
|
+
["COLUMNSTORE", "CREATE CLUSTERED COLUMNSTORE INDEX cci ON a"],
|
|
39
|
+
["a bracketed name", "CREATE TABLE [a] (x int)"],
|
|
40
|
+
["nvarchar", "CREATE TABLE a (x nvarchar(5))"],
|
|
41
|
+
["IDENTITY(1,1)", "CREATE TABLE a (x int IDENTITY(1,1))"],
|
|
42
|
+
["an SSMS preamble", "SET QUOTED_IDENTIFIER ON\nCREATE TABLE a (x int)"],
|
|
43
|
+
["sp_addextendedproperty", "EXEC sp_addextendedproperty @name = N'a'"],
|
|
44
|
+
])("%s marks a file as T-SQL", (_, text) => {
|
|
45
|
+
expect(looksLikeTSql(text)).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test.each([
|
|
49
|
+
["Postgres with arrays and a quoted name", 'CREATE TABLE "a" (x int[], y text[3], z int GENERATED ALWAYS AS IDENTITY, PRIMARY KEY (x))'],
|
|
50
|
+
["Postgres with a subscript", "CREATE FUNCTION f(a int[]) RETURNS int AS $$ SELECT a[1] $$ LANGUAGE sql"],
|
|
51
|
+
["ClickHouse", "CREATE TABLE a (x UInt8) ENGINE = MergeTree ORDER BY x"],
|
|
52
|
+
["ClickHouse naming a clustered column", "CREATE TABLE clustered (x UInt8) ENGINE = Memory"],
|
|
53
|
+
["a plain index", "CREATE INDEX ix ON a (x)"],
|
|
54
|
+
])("%s is not", (_, text) => {
|
|
55
|
+
expect(looksLikeTSql(text)).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("Postgres and ClickHouse files import as before", () => {
|
|
59
|
+
expect(types(parse("CREATE TABLE a (x int PRIMARY KEY, y text)"))).toEqual(["Postgres::Table"]);
|
|
60
|
+
expect(types(parse("CREATE TABLE a (x UInt8) ENGINE = MergeTree ORDER BY x"))).toEqual(["ClickHouse::Table"]);
|
|
61
|
+
expect(parse("CREATE TABLE a (x int PRIMARY KEY)").diagnostics).toBeUndefined();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("a dialect named by the author is kept: dialect=postgres on T-SQL fails as Postgres, it is not rerouted", () => {
|
|
65
|
+
expect(() => parse(TSQL, ["dialect=postgres"])).toThrow(/Postgres DDL/);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("--parser-option from=sqlserver", () => {
|
|
70
|
+
test("is declared with the other options", () => {
|
|
71
|
+
expect(sqlPlugin.parserOptions!().map((o) => o.name)).toEqual(["dialect", "from", "skipUndeclarable"]);
|
|
72
|
+
expect(resolveParserOptions(sqlPlugin, ["from=sqlserver"])).toEqual({ options: { from: "sqlserver" } });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("forces the T-SQL source on a file without markers", () => {
|
|
76
|
+
const ir = parse("CREATE TABLE a (x int NOT NULL)", ["from=sqlserver"]);
|
|
77
|
+
expect(types(ir)).toEqual(["ClickHouse::Table"]);
|
|
78
|
+
expect(ir.diagnostics!.map((d) => d.ruleId)).toEqual(["SQLRV001", "SQLRV002"]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("a source other than sqlserver is refused, naming what is accepted", () => {
|
|
82
|
+
expect(() => sourceOption({ from: "mysql" })).toThrow("expected sqlserver, got mysql");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("cannot be combined with dialect", () => {
|
|
86
|
+
expect(() => sourceOption({ from: "sqlserver", dialect: "clickhouse" })).toThrow(/cannot be combined with dialect/);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("a lexing error is reported as an import error naming the position", () => {
|
|
90
|
+
expect(() => parse("CREATE TABLE [a (x int)", ["from=sqlserver"])).toThrow(/unterminated \[identifier\] \(line 1, column 14\)/);
|
|
91
|
+
});
|
|
92
|
+
});
|
package/src/import-parser.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `chant import schema.sql`: a file of DDL into the import IR, in either
|
|
3
|
-
* dialect
|
|
3
|
+
* dialect, or a SQL Server (T-SQL) schema into ClickHouse declarations.
|
|
4
|
+
*
|
|
5
|
+
* `--parser-option from=sqlserver` (#4398) reads the file as T-SQL and
|
|
6
|
+
* translates it to ClickHouse (`./sqlserver/translate.ts`). SQL Server is an
|
|
7
|
+
* import source, not a dialect: nothing is declared in it, and a build never
|
|
8
|
+
* holds one. A file with T-SQL's own markers (`[bracketed]` names, a `GO`
|
|
9
|
+
* line, `CLUSTERED` or `COLUMNSTORE`, `nvarchar`, `IDENTITY(1,1)`) is read
|
|
10
|
+
* the same way without the option, so it is not taken for Postgres.
|
|
11
|
+
* `from=sqlserver` with `dialect=...` is refused: the target is ClickHouse.
|
|
12
|
+
*
|
|
13
|
+
* `--parser-option dialect=postgres` (or `clickhouse`) names the dialect;
|
|
4
14
|
* without one it is read off the statements: an `ENGINE =` clause, a
|
|
5
15
|
* `CREATE DATABASE`, a `CREATE DICTIONARY` or a lambda `CREATE FUNCTION f AS
|
|
6
16
|
* (x) -> ...` is ClickHouse, a statement
|
|
@@ -22,6 +32,9 @@ import { readPostgres } from "./files/postgres";
|
|
|
22
32
|
import { SqlFileError } from "./files/common";
|
|
23
33
|
import { objectsToIR as chObjectsToIR } from "./clickhouse/import/ir";
|
|
24
34
|
import { objectsToIR as pgObjectsToIR } from "./postgres/import/ir";
|
|
35
|
+
import { namedCollectionPrerequisites } from "./clickhouse/named-collections";
|
|
36
|
+
import { translateSqlServer } from "./sqlserver/translate";
|
|
37
|
+
import { TsqlLexError } from "./sqlserver/lexer";
|
|
25
38
|
|
|
26
39
|
const CLICKHOUSE_ONLY = [
|
|
27
40
|
/\bENGINE\s*=/i,
|
|
@@ -37,6 +50,31 @@ const POSTGRES_ONLY = [
|
|
|
37
50
|
/^\s*CREATE\s+TABLE\b/im,
|
|
38
51
|
];
|
|
39
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Markers only T-SQL has. Each is rare enough in Postgres and ClickHouse DDL
|
|
55
|
+
* that one is enough: a bracketed name after CREATE TABLE or ON, a `GO`
|
|
56
|
+
* batch line, `CLUSTERED`/`COLUMNSTORE`, T-SQL type names, `IDENTITY(n, n)`,
|
|
57
|
+
* the `SET ANSI_NULLS` preamble of an SSMS script.
|
|
58
|
+
*/
|
|
59
|
+
const TSQL_MARKERS = [
|
|
60
|
+
/^\s*GO\s*$/im,
|
|
61
|
+
/\bCREATE\s+(?:UNIQUE\s+)?(?:NONCLUSTERED\s+|CLUSTERED\s+)(?:COLUMNSTORE\s+)?INDEX\b/i,
|
|
62
|
+
/\bCREATE\s+(?:NONCLUSTERED\s+|CLUSTERED\s+)?COLUMNSTORE\s+INDEX\b/i,
|
|
63
|
+
/\bPRIMARY\s+KEY\s+(?:NON)?CLUSTERED\b/i,
|
|
64
|
+
/\b(?:CREATE\s+TABLE|ALTER\s+TABLE|\bON|REFERENCES)\s+\[[^\]\n]+\]/i,
|
|
65
|
+
/\b(?:NVARCHAR|NCHAR|UNIQUEIDENTIFIER|DATETIME2|DATETIMEOFFSET|SMALLDATETIME|SMALLMONEY|ROWVERSION)\b/i,
|
|
66
|
+
/\bIDENTITY\s*\(\s*\d+\s*,\s*\d+\s*\)/i,
|
|
67
|
+
/^\s*SET\s+(?:ANSI_NULLS|QUOTED_IDENTIFIER|NOCOUNT|ANSI_PADDING)\b/im,
|
|
68
|
+
/\bEXEC(?:UTE)?\s+sp_addextendedproperty\b/i,
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
/** True when the DDL has a marker only T-SQL has and no ClickHouse-only clause. */
|
|
72
|
+
export function looksLikeTSql(content: string): boolean {
|
|
73
|
+
const text = content.replace(/--[^\n]*/g, "");
|
|
74
|
+
if (CLICKHOUSE_ONLY.some((re) => re.test(text))) return false;
|
|
75
|
+
return TSQL_MARKERS.some((re) => re.test(text));
|
|
76
|
+
}
|
|
77
|
+
|
|
40
78
|
/** The dialect a file of DDL is written in, read off its statements. */
|
|
41
79
|
export function guessDialect(content: string): SqlDialect {
|
|
42
80
|
const text = content.replace(/--[^\n]*/g, "");
|
|
@@ -53,20 +91,56 @@ export function dialectOption(options: ParserOptions | undefined): SqlDialect |
|
|
|
53
91
|
return d as SqlDialect;
|
|
54
92
|
}
|
|
55
93
|
|
|
94
|
+
/** Sources other than a dialect that `--parser-option from=` names. */
|
|
95
|
+
export const IMPORT_SOURCES = ["sqlserver"] as const;
|
|
96
|
+
export type ImportSource = (typeof IMPORT_SOURCES)[number];
|
|
97
|
+
|
|
98
|
+
/** The `from` parser option, checked. */
|
|
99
|
+
export function sourceOption(options: ParserOptions | undefined): ImportSource | undefined {
|
|
100
|
+
const f = options?.from;
|
|
101
|
+
if (f === undefined) return undefined;
|
|
102
|
+
if (typeof f !== "string" || !(IMPORT_SOURCES as readonly string[]).includes(f)) throw new Error(`--parser-option from: expected ${IMPORT_SOURCES.join(" or ")}, got ${String(f)}`);
|
|
103
|
+
if (options?.dialect !== undefined) throw new Error(`--parser-option from=${f} cannot be combined with dialect: a ${f} schema is translated to ClickHouse`);
|
|
104
|
+
return f as ImportSource;
|
|
105
|
+
}
|
|
106
|
+
|
|
56
107
|
export class SqlFileParser implements TemplateParser {
|
|
57
108
|
constructor(
|
|
58
109
|
private readonly dialect?: SqlDialect,
|
|
59
|
-
private readonly options: { skipUndeclarable?: boolean } = {},
|
|
110
|
+
private readonly options: { skipUndeclarable?: boolean; from?: ImportSource } = {},
|
|
60
111
|
) {}
|
|
61
112
|
|
|
62
113
|
parse(content: string): TemplateIR {
|
|
114
|
+
if (this.options.from === "sqlserver" || (this.dialect === undefined && looksLikeTSql(content))) return this.parseSqlServer(content);
|
|
63
115
|
const dialect = this.dialect ?? guessDialect(content);
|
|
64
116
|
const problems: string[] = [];
|
|
65
117
|
const skipped: string[] = [];
|
|
66
|
-
const
|
|
118
|
+
const prerequisites: Array<{ kind: string; name: string }> = [];
|
|
119
|
+
const read = { origin: "the DDL", prerequisites, ...(this.options.skipUndeclarable ? { undeclarable: skipped } : {}) };
|
|
67
120
|
const ir = dialect === "postgres" ? pgObjectsToIR(readPostgres(content, read, problems)) : chObjectsToIR(readClickHouse(content, read, problems));
|
|
68
121
|
if (problems.length > 0) throw new SqlFileError(`the ${dialect === "postgres" ? "Postgres" : "ClickHouse"} DDL`, problems);
|
|
69
|
-
|
|
70
|
-
|
|
122
|
+
// A named collection is the environment's (#4395): its CREATE, or a declaration naming one, is a prerequisite, its values never imported.
|
|
123
|
+
const objects = ir.resources.map((r) => r.properties as { database?: string; name: string; ddl: string });
|
|
124
|
+
const prereq =
|
|
125
|
+
dialect === "clickhouse"
|
|
126
|
+
? namedCollectionPrerequisites(prerequisites, objects, { found: "the DDL creates it", unseen: "the DDL does not create it" })
|
|
127
|
+
: { prerequisites: [], warnings: [] };
|
|
128
|
+
const warnings = [...(ir.warnings ?? []), ...prereq.warnings, ...skipped.map((s) => `left out (skipUndeclarable): ${s}`)];
|
|
129
|
+
const out = prereq.prerequisites.length > 0 ? { ...ir, metadata: { ...ir.metadata, prerequisites: prereq.prerequisites } } : ir;
|
|
130
|
+
return warnings.length > 0 ? { ...out, warnings } : out;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** A T-SQL schema as ClickHouse objects, with a finding for each judgment call (#4398). */
|
|
134
|
+
private parseSqlServer(content: string): TemplateIR {
|
|
135
|
+
let t;
|
|
136
|
+
try {
|
|
137
|
+
t = translateSqlServer(content, { skipUnreadable: this.options.skipUndeclarable === true });
|
|
138
|
+
} catch (e) {
|
|
139
|
+
if (e instanceof TsqlLexError) throw new SqlFileError("the SQL Server DDL", [e.message]);
|
|
140
|
+
throw e;
|
|
141
|
+
}
|
|
142
|
+
if (t.problems.length > 0) throw new SqlFileError("the SQL Server DDL", t.problems);
|
|
143
|
+
const ir = chObjectsToIR(t.objects);
|
|
144
|
+
return { ...ir, diagnostics: t.diagnostics, metadata: { ...ir.metadata, source: "sqlserver" } };
|
|
71
145
|
}
|
|
72
146
|
}
|
package/src/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ export { database, table, view, dictionary, user, role, policy, quota, settingsP
|
|
|
18
18
|
// A tag folds as the function its file imports, so `table` imported from
|
|
19
19
|
// `@intentius/chant-lexicon-sql/postgres` is Postgres's. Declarations import
|
|
20
20
|
// them from the dialect subpath.
|
|
21
|
-
export { schema, index, sequence, type, domain, extension, func, procedure, aggregate, trigger, eventTrigger, collation, cast } from "./postgres/entities";
|
|
21
|
+
export { schema, index, sequence, type, domain, extension, func, procedure, aggregate, trigger, eventTrigger, operator, operatorClass, operatorFamily, foreignTable, textSearchConfiguration, textSearchDictionary, statistics, foreignDataWrapper, foreignServer, publication, collation, cast } from "./postgres/entities";
|
|
22
22
|
|
|
23
23
|
// A schema change's statements between two builds, rendered offline for a
|
|
24
24
|
// migration file (#3644); also at `@intentius/chant-lexicon-sql/migration-statements`.
|
|
@@ -33,4 +33,5 @@ export const sqlPostSynthAuditEntries: Record<string, RuleMeta> = {
|
|
|
33
33
|
SQLCH125: auditRule("SQLCH125", "merge-worthy", "guidance", "A codec parameter is outside what the codec takes", "Give the codec a parameter within its range or set, or drop the parameter for the default.", { category: "correctness" }),
|
|
34
34
|
SQLCH126: auditRule("SQLCH126", "merge-worthy", "guidance", "A GRANT column list names a column the table does not declare", "Fix the column name, or declare the column on the table.", { category: "correctness" }),
|
|
35
35
|
SQLCH127: auditRule("SQLCH127", "merge-worthy", "guidance", "An expression calls a function the pinned server does not have", "Fix the function name, use one from the pinned server's system.functions, or declare it with func.", { category: "correctness" }),
|
|
36
|
+
SQLCH128: auditRule("SQLCH128", "merge-worthy", "guidance", "An integration engine or table function argument holds a literal credential", "Create a named collection with the credentials on the server and name it in place of the arguments: ENGINE = S3(lake_creds, format = 'Parquet').", { category: "security" }),
|
|
36
37
|
};
|
|
@@ -29,6 +29,7 @@ import { sqlch124 } from "./sqlch124";
|
|
|
29
29
|
import { sqlch125 } from "./sqlch125";
|
|
30
30
|
import { sqlch126 } from "./sqlch126";
|
|
31
31
|
import { sqlch127 } from "./sqlch127";
|
|
32
|
+
import { sqlch128 } from "./sqlch128";
|
|
32
33
|
import { sqlpg101 } from "./sqlpg101";
|
|
33
34
|
import { sqlpg102 } from "./sqlpg102";
|
|
34
35
|
import { sqlpg103 } from "./sqlpg103";
|
|
@@ -86,6 +87,7 @@ export const postSynthChecks: PostSynthCheck[] = [
|
|
|
86
87
|
sqlch125,
|
|
87
88
|
sqlch126,
|
|
88
89
|
sqlch127,
|
|
90
|
+
sqlch128,
|
|
89
91
|
sqlpg101,
|
|
90
92
|
sqlpg102,
|
|
91
93
|
sqlpg103,
|
|
@@ -4,7 +4,7 @@ import type { PostSynthCheck } from "@intentius/chant/lint/post-synth";
|
|
|
4
4
|
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
5
5
|
import { sqlSerializer } from "../../serializer";
|
|
6
6
|
import { table as chTable } from "../../clickhouse/entities";
|
|
7
|
-
import { domain, extension, func, grant, index, role, schema, sequence, table, type as pgType, view } from "../../postgres/entities";
|
|
7
|
+
import { domain, extension, func, grant, index, operatorClass, role, schema, sequence, table, type as pgType, view } from "../../postgres/entities";
|
|
8
8
|
import { sqlAuditCatalog } from "../audit-catalog";
|
|
9
9
|
import { postSynthChecks } from "./index";
|
|
10
10
|
|
|
@@ -444,6 +444,13 @@ describe("SQLPG125: access methods and operator classes", () => {
|
|
|
444
444
|
clean("SQLPG125", { t, i, e: extension`CREATE EXTENSION pg_trgm` });
|
|
445
445
|
});
|
|
446
446
|
test("an operator class in an undeclared schema is left alone", () => clean("SQLPG125", { t, i: index`CREATE INDEX t_x ON ${t} (email ext.my_ops)` }));
|
|
447
|
+
test("an operator class the project declares passes for its own method only (#4406)", () => {
|
|
448
|
+
const app = schema`CREATE SCHEMA app`;
|
|
449
|
+
const ops = operatorClass`CREATE OPERATOR CLASS ${app}.shop_ops FOR TYPE text USING btree AS OPERATOR 1 <, OPERATOR 2 <=, OPERATOR 3 =, OPERATOR 4 >=, OPERATOR 5 >, FUNCTION 1 bttextcmp(text, text)`;
|
|
450
|
+
clean("SQLPG125", { app, t, ops, i: index`CREATE INDEX t_x ON ${t} (email shop_ops)` });
|
|
451
|
+
clean("SQLPG125", { app, t, ops, i: index`CREATE INDEX t_x ON ${t} (email app.shop_ops)` });
|
|
452
|
+
flagged("SQLPG125", { app, t, ops, i: index`CREATE INDEX t_x ON ${t} USING hash (email shop_ops)` }, /shop_ops, which Postgres 18 does not have for hash/);
|
|
453
|
+
});
|
|
447
454
|
});
|
|
448
455
|
|
|
449
456
|
describe("SQLPG126: functions", () => {
|
|
@@ -474,6 +474,8 @@ export type OpClassResolution = { ok: true } | { ok: false; miss: Miss; otherMet
|
|
|
474
474
|
/** An operator class for `method` at the target major. */
|
|
475
475
|
export function resolveOpClass(p: PgProject, method: string, opclass: { schema?: string; name: string }): OpClassResolution {
|
|
476
476
|
if (opclass.schema !== undefined && outsideProject(p, opclass.schema)) return { ok: true };
|
|
477
|
+
// A class the project declares (an `operatorClass` tag), for this method.
|
|
478
|
+
if (p.objects.some((o) => o.type === "Postgres::OperatorClass" && o.name === opclass.name && o.method === method && (opclass.schema === undefined || o.schema === opclass.schema))) return { ok: true };
|
|
477
479
|
const classes = OPERATOR_CLASSES as Readonly<Record<string, Readonly<Record<string, Presence>>>>;
|
|
478
480
|
if (presentAt(classes[method]?.[opclass.name], p.major)) return { ok: true };
|
|
479
481
|
const ext = extensionFor(p, (e) => e.opclasses?.[method]?.includes(opclass.name) ?? false);
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLCH128 (#4395): a literal credential in an integration engine's or table
|
|
3
|
+
* function's arguments. Each flagged case builds today and writes its secret
|
|
4
|
+
* into the build output and `clickhouse.sql`; each clean case keeps it out.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, expect, test } from "vitest";
|
|
7
|
+
import { makePostSynthCtx } from "@intentius/chant-test-utils";
|
|
8
|
+
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
9
|
+
import { sqlSerializer } from "../../serializer";
|
|
10
|
+
import { database, dictionary, table, view } from "../../clickhouse/entities";
|
|
11
|
+
import { sqlAuditCatalog } from "../audit-catalog";
|
|
12
|
+
import { postSynthChecks } from "./index";
|
|
13
|
+
|
|
14
|
+
function run(entities: Record<string, unknown>) {
|
|
15
|
+
const check = postSynthChecks.find((c) => c.id === "SQLCH128")!;
|
|
16
|
+
const out = sqlSerializer.serialize(new Map(Object.entries(entities)) as never) as SerializerResult;
|
|
17
|
+
return check.check(makePostSynthCtx("sql", out.primary));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The findings' messages, in the build's apply order (by export name here). */
|
|
21
|
+
const messages = (entities: Record<string, unknown>) => {
|
|
22
|
+
const diags = run(entities);
|
|
23
|
+
for (const d of diags) expect(d).toMatchObject({ checkId: "SQLCH128", lexicon: "sql", severity: "error" });
|
|
24
|
+
return diags.map((d) => d.message);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
test("SQLCH128 is registered with an audit entry", () => {
|
|
28
|
+
expect(postSynthChecks.map((c) => c.id)).toContain("SQLCH128");
|
|
29
|
+
expect(sqlAuditCatalog.SQLCH128).toBeDefined();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("an inline secret fails", () => {
|
|
33
|
+
test("S3: the secret access key", () => {
|
|
34
|
+
const t = table`CREATE TABLE lake (a Int32) ENGINE = S3('https://bucket.s3.amazonaws.com/x.parquet', 'AKIAEXAMPLE', 'wJalrXUtnFEMI', 'Parquet')`;
|
|
35
|
+
expect(messages({ t })).toEqual([
|
|
36
|
+
expect.stringMatching(
|
|
37
|
+
/^t \(lake\): ENGINE = S3 argument secret_access_key holds a literal credential, which is the environment's\. Create a named collection .*ENGINE = S3\(lake_creds, format = 'Parquet'\)/,
|
|
38
|
+
),
|
|
39
|
+
]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("Kafka: kafka_sasl_password, a setting", () => {
|
|
43
|
+
const t = table`CREATE TABLE k (a String) ENGINE = Kafka() SETTINGS kafka_broker_list = 'kafka:9092', kafka_topic_list = 'events', kafka_group_name = 'g', kafka_format = 'JSONEachRow', kafka_sasl_username = 'app', kafka_sasl_password = 'hunter2'`;
|
|
44
|
+
expect(messages({ t })).toEqual([expect.stringMatching(/^t \(k\): Kafka setting kafka_sasl_password holds a literal credential/)]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("PostgreSQL and MySQL: the password", () => {
|
|
48
|
+
const pg = table`CREATE TABLE pg (id Int32) ENGINE = PostgreSQL('db:5432', 'shop', 'orders', 'app', 'hunter2')`;
|
|
49
|
+
const my = table`CREATE TABLE my (id Int32) ENGINE = MySQL('db:3306', 'shop', 'orders', 'app', 'hunter2')`;
|
|
50
|
+
expect(messages({ pg, my })).toEqual([
|
|
51
|
+
expect.stringMatching(/^my \(my\): ENGINE = MySQL argument password holds a literal credential/),
|
|
52
|
+
expect.stringMatching(/^pg \(pg\): ENGINE = PostgreSQL argument password holds a literal credential/),
|
|
53
|
+
]);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("a database engine, a key = value override after a collection, a URL with a password", () => {
|
|
57
|
+
const db = database`CREATE DATABASE mirror ENGINE = MySQL('db:3306', 'shop', 'app', 'hunter2')`;
|
|
58
|
+
const s3 = table`CREATE TABLE s (a Int32) ENGINE = S3(lake_creds, secret_access_key = 'override', format = 'Parquet')`;
|
|
59
|
+
const u = table`CREATE TABLE u (a Int32) ENGINE = URL('https://app:hunter2@example.com/data.csv', 'CSV')`;
|
|
60
|
+
expect(messages({ db, s3, u })).toEqual([
|
|
61
|
+
expect.stringMatching(/^db \(mirror\): ENGINE = MySQL argument password holds a literal credential/),
|
|
62
|
+
expect.stringMatching(/^s3 \(s\): ENGINE = S3 argument secret_access_key holds a literal credential/),
|
|
63
|
+
expect.stringMatching(/^u \(u\): ENGINE = URL argument 1 holds a URL with a password in it/),
|
|
64
|
+
]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("a table function, in a table over it and in a view's SELECT", () => {
|
|
68
|
+
const over = table`CREATE TABLE over AS s3('https://bucket/x.csv', 'AKIAEXAMPLE', 'wJalrXUtnFEMI', 'CSV')`;
|
|
69
|
+
const v = view`CREATE VIEW v AS SELECT * FROM mysql('db:3306', 'shop', 'orders', 'app', 'hunter2')`;
|
|
70
|
+
const r = view`CREATE VIEW r AS SELECT * FROM remote('h:9000', 'shop', 'orders', 'app', 'hunter2')`;
|
|
71
|
+
expect(messages({ over, v, r })).toEqual([
|
|
72
|
+
expect.stringMatching(/^over \(over\): s3\(\) argument secret_access_key holds a literal credential/),
|
|
73
|
+
expect.stringMatching(/^r \(r\): remote\(\) argument password holds a literal credential/),
|
|
74
|
+
expect.stringMatching(/^v \(v\): mysql\(\) argument password holds a literal credential/),
|
|
75
|
+
]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("a dictionary source's PASSWORD", () => {
|
|
79
|
+
const d = dictionary`CREATE DICTIONARY d (id UInt64, name String) PRIMARY KEY id SOURCE(MYSQL(HOST 'db' PORT 3306 USER 'app' PASSWORD 'hunter2' DB 'shop' TABLE 'names')) LAYOUT(HASHED()) LIFETIME(300)`;
|
|
80
|
+
expect(messages({ d })).toEqual([expect.stringMatching(/^d \(d\): SOURCE PASSWORD holds a literal credential/)]);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("the server's '[HIDDEN]' mask, as an import of SHOW CREATE writes it, is named as such", () => {
|
|
84
|
+
const t = table`CREATE TABLE lake (a Int32) ENGINE = S3('https://bucket/x.parquet', 'AKIAEXAMPLE', '[HIDDEN]')`;
|
|
85
|
+
expect(messages({ t })).toEqual([expect.stringMatching(/secret_access_key holds '\[HIDDEN\]', the server's mask for a credential, in place of one/)]);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("no secret, no finding", () => {
|
|
90
|
+
test("a named collection in place of the arguments", () => {
|
|
91
|
+
const s3 = table`CREATE TABLE lake (a Int32) ENGINE = S3(lake_creds, url = 'https://bucket/x.parquet', format = 'Parquet')`;
|
|
92
|
+
const k = table`CREATE TABLE k (a String) ENGINE = Kafka(kafka_creds, kafka_format = 'JSONEachRow')`;
|
|
93
|
+
const pg = table`CREATE TABLE pg (id Int32) ENGINE = PostgreSQL(pg_creds, table = 'orders')`;
|
|
94
|
+
const v = view`CREATE VIEW v AS SELECT * FROM mysql(mysql_creds, table = 'orders')`;
|
|
95
|
+
const d = dictionary`CREATE DICTIONARY d (id UInt64, name String) PRIMARY KEY id SOURCE(MYSQL(NAME mysql_creds)) LAYOUT(HASHED()) LIFETIME(300)`;
|
|
96
|
+
expect(messages({ s3, k, pg, v, d })).toEqual([]);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("an S3 table with a format and a compression where the keys would be, NOSIGN, or an empty password", () => {
|
|
100
|
+
const fmt = table`CREATE TABLE f (a Int32) ENGINE = S3('https://bucket/x.csv.gz', 'CSV', 'gzip')`;
|
|
101
|
+
const nosign = table`CREATE TABLE n (a Int32) ENGINE = S3('https://bucket/x.csv', NOSIGN, 'CSV')`;
|
|
102
|
+
const az = table`CREATE TABLE az (a Int32) ENGINE = AzureBlobStorage('https://acct.blob.core.windows.net', 'c', 'p.csv', 'CSV', 'gzip')`;
|
|
103
|
+
const d = dictionary`CREATE DICTIONARY d (id UInt64, name String) PRIMARY KEY id SOURCE(CLICKHOUSE(TABLE 'names' USER 'default' PASSWORD '')) LAYOUT(HASHED()) LIFETIME(300)`;
|
|
104
|
+
const mt = table`CREATE TABLE m (password String) ENGINE = MergeTree ORDER BY tuple()`;
|
|
105
|
+
expect(messages({ fmt, nosign, az, d, mt })).toEqual([]);
|
|
106
|
+
});
|
|
107
|
+
});
|