@intentius/chant-lexicon-sql 0.124.45 → 0.124.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/change-set.d.ts +1 -1
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +2 -1
- package/dist/clickhouse/apply/statements.d.ts +9 -0
- package/dist/clickhouse/apply/statements.d.ts.map +1 -1
- package/dist/clickhouse/apply/statements.js +24 -0
- package/dist/clickhouse/import/live-export.d.ts +7 -3
- package/dist/clickhouse/import/live-export.d.ts.map +1 -1
- package/dist/clickhouse/import/live-export.js +21 -5
- package/dist/clickhouse/live/named-collections.d.ts +27 -0
- package/dist/clickhouse/live/named-collections.d.ts.map +1 -0
- package/dist/clickhouse/live/named-collections.js +53 -0
- package/dist/clickhouse/live/undeclarable.d.ts +15 -3
- package/dist/clickhouse/live/undeclarable.d.ts.map +1 -1
- package/dist/clickhouse/live/undeclarable.js +18 -4
- package/dist/clickhouse/named-collections.d.ts +83 -0
- package/dist/clickhouse/named-collections.d.ts.map +1 -0
- package/dist/clickhouse/named-collections.js +203 -0
- package/dist/clickhouse/parser.d.ts.map +1 -1
- package/dist/clickhouse/parser.js +1 -1
- package/dist/clickhouse/plan/commands.d.ts +2 -1
- package/dist/clickhouse/plan/commands.d.ts.map +1 -1
- package/dist/clickhouse/plan/commands.js +17 -2
- package/dist/clickhouse/plan/diff.d.ts +15 -0
- package/dist/clickhouse/plan/diff.d.ts.map +1 -1
- package/dist/clickhouse/plan/diff.js +43 -1
- package/dist/clickhouse/plan/disruption.d.ts.map +1 -1
- package/dist/clickhouse/plan/disruption.js +5 -0
- package/dist/clickhouse/plan/rebuild-handoff.d.ts.map +1 -1
- package/dist/clickhouse/plan/rebuild-handoff.js +4 -0
- package/dist/clickhouse/plan/report.d.ts.map +1 -1
- package/dist/clickhouse/plan/report.js +6 -1
- package/dist/clickhouse/plan/rules.d.ts +5 -1
- package/dist/clickhouse/plan/rules.d.ts.map +1 -1
- package/dist/clickhouse/plan/rules.js +7 -3
- package/dist/clickhouse/plan/stream.d.ts +40 -0
- package/dist/clickhouse/plan/stream.d.ts.map +1 -0
- package/dist/clickhouse/plan/stream.js +107 -0
- package/dist/clickhouse/rebuild/observe.d.ts +3 -1
- package/dist/clickhouse/rebuild/observe.d.ts.map +1 -1
- package/dist/clickhouse/rebuild/observe.js +14 -1
- package/dist/clickhouse/review-catalog.d.ts +213 -0
- package/dist/clickhouse/review-catalog.d.ts.map +1 -0
- package/dist/clickhouse/review-catalog.js +330 -0
- package/dist/codegen/classifier-page.d.ts.map +1 -1
- package/dist/codegen/classifier-page.js +21 -4
- package/dist/codegen/clickhouse-module.d.ts.map +1 -1
- package/dist/codegen/clickhouse-module.js +3 -0
- package/dist/codegen/coverage-page.d.ts.map +1 -1
- package/dist/codegen/coverage-page.js +5 -3
- package/dist/codegen/docs.js +1 -1
- package/dist/codegen/postgres-classifier-page.js +1 -1
- package/dist/codegen/registry.d.ts.map +1 -1
- package/dist/codegen/registry.js +5 -0
- package/dist/composites/catalog.d.ts.map +1 -1
- package/dist/composites/catalog.js +100 -0
- package/dist/composites/clickhouse.d.ts +2 -0
- package/dist/composites/clickhouse.d.ts.map +1 -1
- package/dist/composites/clickhouse.js +2 -0
- package/dist/composites/otel-tables.d.ts +100 -0
- package/dist/composites/otel-tables.d.ts.map +1 -0
- package/dist/composites/otel-tables.js +282 -0
- package/dist/composites/stream-ingest.d.ts +63 -0
- package/dist/composites/stream-ingest.d.ts.map +1 -0
- package/dist/composites/stream-ingest.js +39 -0
- package/dist/coverage/clickhouse.json +1 -1
- package/dist/coverage/postgres.json +7 -7
- package/dist/coverage.d.ts +16 -6
- package/dist/coverage.d.ts.map +1 -1
- package/dist/coverage.js +10 -5
- package/dist/drizzle-command.d.ts +17 -0
- package/dist/drizzle-command.d.ts.map +1 -0
- package/dist/drizzle-command.js +91 -0
- package/dist/drizzle.d.ts +70 -0
- package/dist/drizzle.d.ts.map +1 -0
- package/dist/drizzle.js +684 -0
- package/dist/files/clickhouse.d.ts.map +1 -1
- package/dist/files/clickhouse.js +8 -1
- package/dist/files/common.d.ts +13 -0
- package/dist/files/common.d.ts.map +1 -1
- package/dist/files/common.js +5 -0
- package/dist/files/postgres.js +12 -2
- package/dist/generated/clickhouse.d.ts +2 -0
- package/dist/generated/clickhouse.d.ts.map +1 -1
- package/dist/generated/clickhouse.js +2 -0
- package/dist/import-parser.d.ts +21 -1
- package/dist/import-parser.d.ts.map +1 -1
- package/dist/import-parser.js +79 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/integrity.json +6 -5
- package/dist/lint/post-synth/audit-entries.d.ts.map +1 -1
- package/dist/lint/post-synth/audit-entries.js +1 -0
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/post-synth/index.js +2 -0
- package/dist/lint/post-synth/sqlch128.d.ts +18 -0
- package/dist/lint/post-synth/sqlch128.d.ts.map +1 -0
- package/dist/lint/post-synth/sqlch128.js +222 -0
- package/dist/lint/rules/postgres-templates.js +2 -2
- package/dist/lsp/postgres-template.d.ts.map +1 -1
- package/dist/lsp/postgres-template.js +11 -0
- package/dist/manifest.json +1 -1
- package/dist/mcp/postgres.js +1 -1
- package/dist/meta.json +25 -0
- package/dist/migrate/from-postgres/detect.d.ts +4 -0
- package/dist/migrate/from-postgres/detect.d.ts.map +1 -0
- package/dist/migrate/from-postgres/detect.js +7 -0
- package/dist/migrate/from-postgres/emit.d.ts +26 -0
- package/dist/migrate/from-postgres/emit.d.ts.map +1 -0
- package/dist/migrate/from-postgres/emit.js +86 -0
- package/dist/migrate/from-postgres/index.d.ts +51 -0
- package/dist/migrate/from-postgres/index.d.ts.map +1 -0
- package/dist/migrate/from-postgres/index.js +115 -0
- package/dist/migrate/from-postgres/translate.d.ts +73 -0
- package/dist/migrate/from-postgres/translate.d.ts.map +1 -0
- package/dist/migrate/from-postgres/translate.js +261 -0
- package/dist/migrate/from-postgres/types.d.ts +51 -0
- package/dist/migrate/from-postgres/types.d.ts.map +1 -0
- package/dist/migrate/from-postgres/types.js +211 -0
- package/dist/migration-replay.d.ts +73 -0
- package/dist/migration-replay.d.ts.map +1 -0
- package/dist/migration-replay.js +294 -0
- package/dist/okf/index.md +6 -0
- package/dist/okf/rules/SQLCH128.md +11 -0
- package/dist/okf/types/PostgresForeignDataWrapper.md +9 -0
- package/dist/okf/types/PostgresForeignServer.md +9 -0
- package/dist/okf/types/PostgresOperator.md +9 -0
- package/dist/okf/types/PostgresPublication.md +9 -0
- package/dist/okf/types/PostgresStatistics.md +9 -0
- package/dist/op/activities/postgres-migration.d.ts.map +1 -1
- package/dist/op/activities/postgres-migration.js +4 -0
- package/dist/op/activity-contracts.d.ts +20 -0
- package/dist/op/activity-contracts.d.ts.map +1 -1
- package/dist/op/activity-contracts.js +2 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +36 -2
- package/dist/postgres/apply/apply.js +8 -1
- package/dist/postgres/apply/statements.d.ts.map +1 -1
- package/dist/postgres/apply/statements.js +87 -2
- package/dist/postgres/constraint-names.d.ts +30 -0
- package/dist/postgres/constraint-names.d.ts.map +1 -0
- package/dist/postgres/constraint-names.js +72 -0
- package/dist/postgres/entities.d.ts +110 -1
- package/dist/postgres/entities.d.ts.map +1 -1
- package/dist/postgres/entities.js +146 -0
- package/dist/postgres/entity-types.d.ts +5 -0
- package/dist/postgres/entity-types.d.ts.map +1 -1
- package/dist/postgres/entity-types.js +5 -0
- package/dist/postgres/import/generator.d.ts.map +1 -1
- package/dist/postgres/import/generator.js +50 -2
- package/dist/postgres/import/live-export.d.ts.map +1 -1
- package/dist/postgres/import/live-export.js +2 -0
- package/dist/postgres/live/catalog-kinds.d.ts +14 -0
- package/dist/postgres/live/catalog-kinds.d.ts.map +1 -1
- package/dist/postgres/live/catalog-kinds.js +206 -0
- package/dist/postgres/live/catalog.d.ts +2 -0
- package/dist/postgres/live/catalog.d.ts.map +1 -1
- package/dist/postgres/live/catalog.js +3 -1
- package/dist/postgres/live/describe-resources.d.ts.map +1 -1
- package/dist/postgres/live/describe-resources.js +10 -2
- package/dist/postgres/migrate/backfill.d.ts +2 -0
- package/dist/postgres/migrate/backfill.d.ts.map +1 -1
- package/dist/postgres/migrate/backfill.js +7 -1
- package/dist/postgres/migrate/carry.d.ts +45 -1
- package/dist/postgres/migrate/carry.d.ts.map +1 -1
- package/dist/postgres/migrate/carry.js +72 -8
- package/dist/postgres/migrate/observe.d.ts +28 -4
- package/dist/postgres/migrate/observe.d.ts.map +1 -1
- package/dist/postgres/migrate/observe.js +41 -12
- package/dist/postgres/migrate/op.d.ts +26 -1
- package/dist/postgres/migrate/op.d.ts.map +1 -1
- package/dist/postgres/migrate/op.js +3 -1
- package/dist/postgres/migrate/steps.d.ts +35 -2
- package/dist/postgres/migrate/steps.d.ts.map +1 -1
- package/dist/postgres/migrate/steps.js +158 -12
- package/dist/postgres/parser.d.ts +72 -1
- package/dist/postgres/parser.d.ts.map +1 -1
- package/dist/postgres/parser.js +194 -2
- package/dist/postgres/plan/deep.d.ts.map +1 -1
- package/dist/postgres/plan/deep.js +29 -1
- package/dist/postgres/plan/diff.d.ts.map +1 -1
- package/dist/postgres/plan/diff.js +39 -1
- package/dist/postgres/plan/normalize.d.ts +17 -2
- package/dist/postgres/plan/normalize.d.ts.map +1 -1
- package/dist/postgres/plan/normalize.js +109 -1
- package/dist/postgres/plan/rules.d.ts +1 -0
- package/dist/postgres/plan/rules.d.ts.map +1 -1
- package/dist/postgres/plan/rules.js +3 -2
- package/dist/postgres/plan/schema.js +1 -1
- package/dist/postgres/secret-options.d.ts +10 -0
- package/dist/postgres/secret-options.d.ts.map +1 -0
- package/dist/postgres/secret-options.js +9 -0
- package/dist/postgres/testing/roundtrip-schema.d.ts +3 -2
- package/dist/postgres/testing/roundtrip-schema.d.ts.map +1 -1
- package/dist/postgres/testing/roundtrip-schema.js +13 -1
- package/dist/postgres.d.ts +1 -1
- package/dist/postgres.d.ts.map +1 -1
- package/dist/postgres.js +1 -1
- package/dist/rules/audit-entries.ts +1 -0
- package/dist/rules/postgres-templates.ts +2 -2
- package/dist/rules/sqlch128.ts +230 -0
- package/dist/serializer.d.ts +5 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/serializer.js +13 -2
- package/dist/sql-types-command.d.ts +13 -0
- package/dist/sql-types-command.d.ts.map +1 -1
- package/dist/sql-types-command.js +31 -16
- package/dist/sql-types.d.ts +22 -0
- package/dist/sql-types.d.ts.map +1 -1
- package/dist/sql-types.js +10 -6
- package/dist/sqlserver/lexer.d.ts +37 -0
- package/dist/sqlserver/lexer.d.ts.map +1 -0
- package/dist/sqlserver/lexer.js +254 -0
- package/dist/sqlserver/parse.d.ts +185 -0
- package/dist/sqlserver/parse.d.ts.map +1 -0
- package/dist/sqlserver/parse.js +767 -0
- package/dist/sqlserver/translate.d.ts +34 -0
- package/dist/sqlserver/translate.d.ts.map +1 -0
- package/dist/sqlserver/translate.js +495 -0
- package/dist/sqlserver/types.d.ts +44 -0
- package/dist/sqlserver/types.d.ts.map +1 -0
- package/dist/sqlserver/types.js +133 -0
- package/dist/sqlserver/validate.d.ts +31 -0
- package/dist/sqlserver/validate.d.ts.map +1 -0
- package/dist/sqlserver/validate.js +67 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +5 -0
- package/package.json +2 -2
- package/src/change-set.ts +2 -1
- package/src/clickhouse/apply/statements.ts +25 -0
- package/src/clickhouse/import/live-export.ts +20 -5
- package/src/clickhouse/live/named-collections.e2e.test.ts +88 -0
- package/src/clickhouse/live/named-collections.ts +61 -0
- package/src/clickhouse/live/undeclarable.e2e.test.ts +5 -3
- package/src/clickhouse/live/undeclarable.ts +20 -4
- package/src/clickhouse/named-collections.test.ts +187 -0
- package/src/clickhouse/named-collections.ts +224 -0
- package/src/clickhouse/parser.ts +2 -1
- package/src/clickhouse/plan/commands.ts +17 -2
- package/src/clickhouse/plan/diff.ts +57 -1
- package/src/clickhouse/plan/disruption.ts +5 -0
- package/src/clickhouse/plan/rebuild-handoff.ts +3 -0
- package/src/clickhouse/plan/report.ts +6 -1
- package/src/clickhouse/plan/rules.ts +14 -4
- package/src/clickhouse/plan/stream-s3queue.e2e.test.ts +148 -0
- package/src/clickhouse/plan/stream.test.ts +237 -0
- package/src/clickhouse/plan/stream.ts +111 -0
- package/src/clickhouse/rebuild/observe.ts +15 -1
- package/src/clickhouse/review-catalog.test.ts +181 -0
- package/src/clickhouse/review-catalog.ts +489 -0
- package/src/codegen/classifier-page.ts +21 -4
- package/src/codegen/clickhouse-module.ts +3 -0
- package/src/codegen/coverage-page.ts +5 -3
- package/src/codegen/docs.ts +1 -1
- package/src/codegen/postgres-classifier-page.ts +1 -1
- package/src/codegen/registry.ts +5 -0
- package/src/composites/catalog.test.ts +6 -1
- package/src/composites/catalog.ts +100 -0
- package/src/composites/clickhouse.ts +2 -0
- package/src/composites/composites.test.ts +1 -1
- package/src/composites/otel-tables.test.ts +118 -0
- package/src/composites/otel-tables.ts +339 -0
- package/src/composites/stream-ingest.test.ts +114 -0
- package/src/composites/stream-ingest.ts +82 -0
- package/src/core/undeclarable.test.ts +6 -5
- package/src/coverage/clickhouse.json +1 -1
- package/src/coverage/postgres.json +7 -7
- package/src/coverage.test.ts +27 -2
- package/src/coverage.ts +18 -7
- package/src/drizzle-command.ts +94 -0
- package/src/drizzle-kit.e2e.test.ts +89 -0
- package/src/drizzle-types.e2e.test.ts +67 -0
- package/src/drizzle.test.ts +358 -0
- package/src/drizzle.ts +737 -0
- package/src/files/clickhouse.ts +8 -1
- package/src/files/common.ts +14 -1
- package/src/files/postgres.ts +12 -2
- package/src/generated/clickhouse.ts +3 -0
- package/src/generated/lexicon-sql.json +25 -0
- package/src/import-parser.sqlserver.test.ts +92 -0
- package/src/import-parser.ts +79 -5
- package/src/index.ts +1 -1
- package/src/lint/post-synth/audit-entries.ts +1 -0
- package/src/lint/post-synth/index.ts +2 -0
- package/src/lint/post-synth/sqlch128.test.ts +107 -0
- package/src/lint/post-synth/sqlch128.ts +230 -0
- package/src/lint/rules/postgres-templates.ts +2 -2
- package/src/lsp/postgres-template.ts +11 -0
- package/src/mcp/postgres.ts +1 -1
- package/src/migrate/from-postgres/cli.test.ts +165 -0
- package/src/migrate/from-postgres/detect.ts +7 -0
- package/src/migrate/from-postgres/emit.ts +96 -0
- package/src/migrate/from-postgres/example.test.ts +205 -0
- package/src/migrate/from-postgres/index.ts +122 -0
- package/src/migrate/from-postgres/translate.test.ts +242 -0
- package/src/migrate/from-postgres/translate.ts +359 -0
- package/src/migrate/from-postgres/types.test.ts +116 -0
- package/src/migrate/from-postgres/types.ts +236 -0
- package/src/migrate/from-postgres/validate.e2e.test.ts +44 -0
- package/src/migrate/from-postgres/workspace.e2e.test.ts +114 -0
- package/src/migration-replay.e2e.test.ts +155 -0
- package/src/migration-replay.test.ts +173 -0
- package/src/migration-replay.ts +289 -0
- package/src/op/__snapshots__/watch-pipeline.test.ts.snap +2 -2
- package/src/op/activities/postgres-migration.ts +4 -0
- package/src/op/activity-contracts.ts +2 -0
- package/src/plugin.ts +37 -2
- package/src/postgres/apply/apply.ts +9 -1
- package/src/postgres/apply/statements.ts +74 -1
- package/src/postgres/constraint-names.ts +71 -0
- package/src/postgres/database-kinds.test.ts +384 -0
- package/src/postgres/entities.ts +266 -1
- package/src/postgres/entity-types.ts +5 -0
- package/src/postgres/import/generator.ts +42 -2
- package/src/postgres/import/live-export.ts +1 -0
- package/src/postgres/live/catalog-kinds.ts +223 -0
- package/src/postgres/live/catalog.ts +6 -1
- package/src/postgres/live/database-kinds.e2e.test.ts +164 -0
- package/src/postgres/live/describe-resources.ts +11 -2
- package/src/postgres/live/undeclarable.e2e.test.ts +13 -6
- package/src/postgres/migrate/backfill.ts +13 -1
- package/src/postgres/migrate/carry.ts +103 -8
- package/src/postgres/migrate/followups.e2e.test.ts +218 -0
- package/src/postgres/migrate/followups.test.ts +199 -0
- package/src/postgres/migrate/kinds.e2e.test.ts +5 -5
- package/src/postgres/migrate/observe.ts +53 -14
- package/src/postgres/migrate/op.ts +28 -1
- package/src/postgres/migrate/partition.e2e.test.ts +129 -0
- package/src/postgres/migrate/partition.test.ts +206 -0
- package/src/postgres/migrate/steps.ts +157 -12
- package/src/postgres/parser.ts +234 -2
- package/src/postgres/plan/deep.ts +28 -1
- package/src/postgres/plan/diff.ts +36 -1
- package/src/postgres/plan/normalize.ts +122 -1
- package/src/postgres/plan/rules.ts +10 -3
- package/src/postgres/plan/schema.ts +1 -1
- package/src/postgres/secret-options.ts +10 -0
- package/src/postgres/testing/roundtrip-schema.ts +13 -1
- package/src/postgres.ts +16 -0
- package/src/serializer.ts +13 -2
- package/src/skills/chant-sql-postgres-migration.md +3 -3
- package/src/skills/chant-sql-postgres.md +6 -1
- package/src/skills/chant-sql.md +2 -1
- package/src/sql-types-command.ts +38 -16
- package/src/sql-types.ts +11 -6
- package/src/sqlserver/fixture.test.ts +186 -0
- package/src/sqlserver/import-cli.test.ts +61 -0
- package/src/sqlserver/lexer.ts +260 -0
- package/src/sqlserver/parse.ts +865 -0
- package/src/sqlserver/translate.test.ts +396 -0
- package/src/sqlserver/translate.ts +558 -0
- package/src/sqlserver/types.test.ts +102 -0
- package/src/sqlserver/types.ts +154 -0
- package/src/sqlserver/validate.e2e.test.ts +32 -0
- package/src/sqlserver/validate.test.ts +49 -0
- package/src/sqlserver/validate.ts +76 -0
- package/src/validate.ts +5 -0
- package/src/workspace-kinds.test.ts +11 -0
- package/workspace-kinds.json +100 -2
|
@@ -0,0 +1,767 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T-SQL DDL into a syntax tree: `CREATE TABLE`, `CREATE [UNIQUE] [CLUSTERED |
|
|
3
|
+
* NONCLUSTERED] INDEX`, `CREATE [CLUSTERED | NONCLUSTERED] COLUMNSTORE
|
|
4
|
+
* INDEX ... [ORDER (...)]`, `CREATE VIEW`, `CREATE SCHEMA`, `ALTER TABLE ...
|
|
5
|
+
* ADD CONSTRAINT`, and `sp_addextendedproperty` descriptions.
|
|
6
|
+
*
|
|
7
|
+
* Grammar sources (Microsoft Learn, Transact-SQL reference):
|
|
8
|
+
* - CREATE TABLE: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql
|
|
9
|
+
* - CREATE INDEX: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-index-transact-sql
|
|
10
|
+
* - CREATE COLUMNSTORE INDEX (the `ORDER (column [, ...n])` clause, which the
|
|
11
|
+
* SQL Server grammar places before or after `WITH`):
|
|
12
|
+
* https://learn.microsoft.com/en-us/sql/t-sql/statements/create-columnstore-index-transact-sql
|
|
13
|
+
*
|
|
14
|
+
* Every statement that is not one of these comes back as `refused` (a kind of
|
|
15
|
+
* object this source cannot read, named: `trigger [dbo].[trg_audit]`) or
|
|
16
|
+
* `ignored` (a session setting or a `DROP`, which declares nothing). Nothing
|
|
17
|
+
* is dropped without being returned.
|
|
18
|
+
*/
|
|
19
|
+
import { batches, tokenize, upper, sourceOf } from "./lexer.js";
|
|
20
|
+
export class TsqlParseError extends Error {
|
|
21
|
+
constructor(message, loc) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.loc = loc;
|
|
24
|
+
this.name = "TsqlParseError";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const loc = (t) => ({ line: t.line, column: t.column });
|
|
28
|
+
// --- statement boundaries --------------------------------------------------
|
|
29
|
+
const START_WORDS = new Set("CREATE ALTER DROP EXEC EXECUTE SET USE INSERT UPDATE DELETE DECLARE PRINT IF GRANT DENY REVOKE SELECT BEGIN TRUNCATE MERGE RETURN WHILE".split(" "));
|
|
30
|
+
/** Statements that own the rest of their batch: a body can hold any statement word. */
|
|
31
|
+
const BATCH_BODY = new Set(["VIEW", "PROC", "PROCEDURE", "FUNCTION", "TRIGGER"]);
|
|
32
|
+
/** Split one batch into statements: at `;`, and where a new statement word starts at depth 0. */
|
|
33
|
+
function statementsOf(batch) {
|
|
34
|
+
const out = [];
|
|
35
|
+
let cur = [];
|
|
36
|
+
let depth = 0;
|
|
37
|
+
let ownsBatch = false;
|
|
38
|
+
const flush = () => {
|
|
39
|
+
if (cur.length > 0)
|
|
40
|
+
out.push(cur);
|
|
41
|
+
cur = [];
|
|
42
|
+
depth = 0;
|
|
43
|
+
ownsBatch = false;
|
|
44
|
+
};
|
|
45
|
+
for (let k = 0; k < batch.length; k++) {
|
|
46
|
+
const t = batch[k];
|
|
47
|
+
if (t.kind === "punct" && t.value === ";" && depth === 0 && !ownsBatch) {
|
|
48
|
+
flush();
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (t.kind === "punct" && t.value === "(")
|
|
52
|
+
depth++;
|
|
53
|
+
if (t.kind === "punct" && t.value === ")")
|
|
54
|
+
depth = Math.max(0, depth - 1);
|
|
55
|
+
const w = upper(t);
|
|
56
|
+
const prev = upper(cur[cur.length - 1]);
|
|
57
|
+
if (cur.length > 0 && depth === 0 && !ownsBatch && START_WORDS.has(w)) {
|
|
58
|
+
const guarded = prev === "ON" || prev === "OR" || prev === "DELETE" || prev === "UPDATE" || (w === "SET" && batch[k + 1]?.value === "(") || (w === "IF" && upper(cur[0]) === "DROP");
|
|
59
|
+
// `DROP INDEX IF EXISTS`, `ON DELETE SET NULL` and `ALTER TABLE ... SET (...)` keep going.
|
|
60
|
+
if (!guarded)
|
|
61
|
+
flush();
|
|
62
|
+
}
|
|
63
|
+
cur.push(t);
|
|
64
|
+
if (cur.length === 2 || cur.length === 3 || cur.length === 4) {
|
|
65
|
+
// `CREATE [OR ALTER] VIEW`: the body runs to the end of the batch.
|
|
66
|
+
const words = cur.map(upper);
|
|
67
|
+
if (words[0] === "CREATE" || words[0] === "ALTER") {
|
|
68
|
+
const kind = words[1] === "OR" ? words[3] : words[1];
|
|
69
|
+
if (BATCH_BODY.has(kind ?? "") && (words[1] !== "OR" || cur.length === 4))
|
|
70
|
+
ownsBatch = true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
flush();
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
// --- cursor ------------------------------------------------------------------
|
|
78
|
+
class Cursor {
|
|
79
|
+
constructor(tokens) {
|
|
80
|
+
this.tokens = tokens;
|
|
81
|
+
this.i = 0;
|
|
82
|
+
}
|
|
83
|
+
get done() {
|
|
84
|
+
return this.i >= this.tokens.length;
|
|
85
|
+
}
|
|
86
|
+
peek(offset = 0) {
|
|
87
|
+
return this.tokens[this.i + offset];
|
|
88
|
+
}
|
|
89
|
+
next() {
|
|
90
|
+
const t = this.tokens[this.i++];
|
|
91
|
+
if (!t)
|
|
92
|
+
throw new TsqlParseError("the statement ends early", this.tokens.length > 0 ? loc(this.tokens[this.tokens.length - 1]) : { line: 1, column: 1 });
|
|
93
|
+
return t;
|
|
94
|
+
}
|
|
95
|
+
isWord(...words) {
|
|
96
|
+
return words.includes(upper(this.peek()));
|
|
97
|
+
}
|
|
98
|
+
/** Consume the next token when it is one of `words`. */
|
|
99
|
+
eat(...words) {
|
|
100
|
+
return this.isWord(...words) ? this.next() : undefined;
|
|
101
|
+
}
|
|
102
|
+
eatPunct(p) {
|
|
103
|
+
const t = this.peek();
|
|
104
|
+
if (t && t.kind === "punct" && t.value === p) {
|
|
105
|
+
this.i++;
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
expectWord(...words) {
|
|
111
|
+
const t = this.peek();
|
|
112
|
+
if (!this.isWord(...words))
|
|
113
|
+
throw new TsqlParseError(`expected ${words.join(" or ")}, found ${t ? `"${t.text}"` : "the end of the statement"}`, t ? loc(t) : { line: 1, column: 1 });
|
|
114
|
+
return this.next();
|
|
115
|
+
}
|
|
116
|
+
expectPunct(p) {
|
|
117
|
+
const t = this.peek();
|
|
118
|
+
if (!this.eatPunct(p))
|
|
119
|
+
throw new TsqlParseError(`expected "${p}", found ${t ? `"${t.text}"` : "the end of the statement"}`, t ? loc(t) : { line: 1, column: 1 });
|
|
120
|
+
}
|
|
121
|
+
/** Tokens inside a balanced `( ... )`, which the cursor is at. */
|
|
122
|
+
group() {
|
|
123
|
+
this.expectPunct("(");
|
|
124
|
+
const start = this.i;
|
|
125
|
+
let depth = 1;
|
|
126
|
+
while (!this.done) {
|
|
127
|
+
const t = this.next();
|
|
128
|
+
if (t.kind === "punct" && t.value === "(")
|
|
129
|
+
depth++;
|
|
130
|
+
if (t.kind === "punct" && t.value === ")" && --depth === 0)
|
|
131
|
+
return this.tokens.slice(start, this.i - 1);
|
|
132
|
+
}
|
|
133
|
+
throw new TsqlParseError('a "(" is never closed', loc(this.tokens[start - 1]));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** Split tokens at depth-0 commas. */
|
|
137
|
+
function splitCommas(tokens) {
|
|
138
|
+
const out = [];
|
|
139
|
+
let cur = [];
|
|
140
|
+
let depth = 0;
|
|
141
|
+
for (const t of tokens) {
|
|
142
|
+
if (t.kind === "punct" && t.value === "(")
|
|
143
|
+
depth++;
|
|
144
|
+
if (t.kind === "punct" && t.value === ")")
|
|
145
|
+
depth--;
|
|
146
|
+
if (depth === 0 && t.kind === "punct" && t.value === ",") {
|
|
147
|
+
out.push(cur);
|
|
148
|
+
cur = [];
|
|
149
|
+
}
|
|
150
|
+
else
|
|
151
|
+
cur.push(t);
|
|
152
|
+
}
|
|
153
|
+
if (cur.length > 0)
|
|
154
|
+
out.push(cur);
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
function qname(c) {
|
|
158
|
+
const first = c.peek();
|
|
159
|
+
if (!first || (first.kind !== "word" && first.kind !== "qident"))
|
|
160
|
+
throw new TsqlParseError(`expected a name, found ${first ? `"${first.text}"` : "the end of the statement"}`, first ? loc(first) : { line: 1, column: 1 });
|
|
161
|
+
const parts = [];
|
|
162
|
+
for (;;) {
|
|
163
|
+
const t = c.next();
|
|
164
|
+
parts.push(t.value);
|
|
165
|
+
const dot = c.peek();
|
|
166
|
+
if (dot && dot.kind === "punct" && dot.value === ".") {
|
|
167
|
+
c.i++;
|
|
168
|
+
// `db..table` has an empty schema part.
|
|
169
|
+
const after = c.peek();
|
|
170
|
+
if (after && after.kind === "punct" && after.value === ".") {
|
|
171
|
+
c.i++;
|
|
172
|
+
parts.push("dbo");
|
|
173
|
+
}
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
return { parts, loc: loc(first) };
|
|
179
|
+
}
|
|
180
|
+
function keyColumns(tokens) {
|
|
181
|
+
return splitCommas(tokens).map((part) => {
|
|
182
|
+
const c = new Cursor(part);
|
|
183
|
+
const name = c.next();
|
|
184
|
+
let descending = false;
|
|
185
|
+
if (c.eat("DESC"))
|
|
186
|
+
descending = true;
|
|
187
|
+
else
|
|
188
|
+
c.eat("ASC");
|
|
189
|
+
return { name: name.value, descending };
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
const nameList = (tokens) => splitCommas(tokens).map((p) => p[0].value);
|
|
193
|
+
// --- constraints ---------------------------------------------------------------
|
|
194
|
+
/** Text of a `WITH (...)` / `ON x` tail on a key, so the translation can name it. */
|
|
195
|
+
function keyOptions(c, text) {
|
|
196
|
+
const out = [];
|
|
197
|
+
for (;;) {
|
|
198
|
+
if (c.isWord("WITH") && c.peek(1)?.value === "(") {
|
|
199
|
+
const start = c.i;
|
|
200
|
+
c.next();
|
|
201
|
+
c.group();
|
|
202
|
+
out.push(sourceOf(text, c.tokens.slice(start, c.i)));
|
|
203
|
+
}
|
|
204
|
+
else if (c.isWord("ON") && c.peek(1) && !["DELETE", "UPDATE"].includes(upper(c.peek(1)))) {
|
|
205
|
+
const start = c.i;
|
|
206
|
+
c.next();
|
|
207
|
+
qname(c);
|
|
208
|
+
if (c.peek()?.value === "(")
|
|
209
|
+
c.group();
|
|
210
|
+
out.push(sourceOf(text, c.tokens.slice(start, c.i)));
|
|
211
|
+
}
|
|
212
|
+
else
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
return out;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* One constraint, with the cursor at `PRIMARY`, `UNIQUE`, `FOREIGN`, `CHECK`,
|
|
219
|
+
* `DEFAULT` or `REFERENCES` (a column-level foreign key). `onColumn` is the
|
|
220
|
+
* column a column-level constraint was written on.
|
|
221
|
+
*/
|
|
222
|
+
function constraint(c, text, name, onColumn, startTok) {
|
|
223
|
+
const base = { name, onColumn, options: [], loc: loc(startTok) };
|
|
224
|
+
if (c.eat("PRIMARY")) {
|
|
225
|
+
c.expectWord("KEY");
|
|
226
|
+
const clustered = c.eat("CLUSTERED", "NONCLUSTERED");
|
|
227
|
+
const columns = c.peek()?.value === "(" ? keyColumns(c.group()) : onColumn ? [{ name: onColumn, descending: false }] : [];
|
|
228
|
+
c.eat("HASH");
|
|
229
|
+
const options = keyOptions(c, text);
|
|
230
|
+
return { ...base, kind: "primary-key", clustered: clustered ? upper(clustered) === "CLUSTERED" : undefined, columns, options };
|
|
231
|
+
}
|
|
232
|
+
if (c.eat("UNIQUE")) {
|
|
233
|
+
const clustered = c.eat("CLUSTERED", "NONCLUSTERED");
|
|
234
|
+
const columns = c.peek()?.value === "(" ? keyColumns(c.group()) : onColumn ? [{ name: onColumn, descending: false }] : [];
|
|
235
|
+
const options = keyOptions(c, text);
|
|
236
|
+
return { ...base, kind: "unique", clustered: clustered ? upper(clustered) === "CLUSTERED" : undefined, columns, options };
|
|
237
|
+
}
|
|
238
|
+
if (c.eat("FOREIGN")) {
|
|
239
|
+
c.expectWord("KEY");
|
|
240
|
+
const columns = keyColumns(c.group());
|
|
241
|
+
c.expectWord("REFERENCES");
|
|
242
|
+
return { ...referenceTail(c, text, base), columns };
|
|
243
|
+
}
|
|
244
|
+
if (c.isWord("REFERENCES")) {
|
|
245
|
+
c.next();
|
|
246
|
+
return { ...referenceTail(c, text, base), columns: onColumn ? [{ name: onColumn, descending: false }] : [] };
|
|
247
|
+
}
|
|
248
|
+
if (c.eat("CHECK")) {
|
|
249
|
+
const notForReplication = c.isWord("NOT") && c.peek(1)?.value.toUpperCase() === "FOR";
|
|
250
|
+
if (notForReplication) {
|
|
251
|
+
c.next();
|
|
252
|
+
c.next();
|
|
253
|
+
c.next();
|
|
254
|
+
}
|
|
255
|
+
const startIdx = c.i;
|
|
256
|
+
const inner = c.group();
|
|
257
|
+
return { ...base, kind: "check", columns: onColumn ? [{ name: onColumn, descending: false }] : [], expression: sourceOf(text, c.tokens.slice(startIdx, c.i)), expressionTokens: inner };
|
|
258
|
+
}
|
|
259
|
+
if (c.eat("DEFAULT")) {
|
|
260
|
+
const { tokens, source } = defaultExpression(c, text);
|
|
261
|
+
return { ...base, kind: "default", columns: onColumn ? [{ name: onColumn, descending: false }] : [], expression: source, expressionTokens: tokens };
|
|
262
|
+
}
|
|
263
|
+
const t = c.peek();
|
|
264
|
+
throw new TsqlParseError(`a constraint this source does not read: "${t?.text ?? ""}"`, t ? loc(t) : base.loc);
|
|
265
|
+
}
|
|
266
|
+
function referenceTail(c, text, base) {
|
|
267
|
+
const table = qname(c);
|
|
268
|
+
const columns = c.peek()?.value === "(" ? nameList(c.group()) : [];
|
|
269
|
+
// ON DELETE | ON UPDATE actions, NOT FOR REPLICATION
|
|
270
|
+
const actions = [];
|
|
271
|
+
for (;;) {
|
|
272
|
+
if (c.isWord("ON") && ["DELETE", "UPDATE"].includes(c.peek(1)?.value.toUpperCase() ?? "")) {
|
|
273
|
+
const start = c.i;
|
|
274
|
+
c.next();
|
|
275
|
+
c.next();
|
|
276
|
+
if (c.eat("NO"))
|
|
277
|
+
c.expectWord("ACTION");
|
|
278
|
+
else if (c.eat("SET"))
|
|
279
|
+
c.expectWord("NULL", "DEFAULT");
|
|
280
|
+
else
|
|
281
|
+
c.expectWord("CASCADE");
|
|
282
|
+
actions.push(sourceOf(text, c.tokens.slice(start, c.i)));
|
|
283
|
+
}
|
|
284
|
+
else if (c.isWord("NOT") && c.peek(1)?.value.toUpperCase() === "FOR") {
|
|
285
|
+
c.next();
|
|
286
|
+
c.next();
|
|
287
|
+
c.next();
|
|
288
|
+
}
|
|
289
|
+
else
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
return { ...base, kind: "foreign-key", references: { table, columns }, options: actions };
|
|
293
|
+
}
|
|
294
|
+
/** A default's expression: a parenthesised group, or a signed literal / function call. */
|
|
295
|
+
function defaultExpression(c, text) {
|
|
296
|
+
const start = c.i;
|
|
297
|
+
if (c.peek()?.value === "(") {
|
|
298
|
+
const inner = c.group();
|
|
299
|
+
return { tokens: inner, source: sourceOf(text, c.tokens.slice(start, c.i)) };
|
|
300
|
+
}
|
|
301
|
+
const sign = c.peek();
|
|
302
|
+
if (sign && sign.kind === "punct" && (sign.value === "-" || sign.value === "+"))
|
|
303
|
+
c.next();
|
|
304
|
+
const t = c.next();
|
|
305
|
+
if (t.kind === "word" && c.peek()?.value === "(")
|
|
306
|
+
c.group();
|
|
307
|
+
const slice = c.tokens.slice(start, c.i);
|
|
308
|
+
return { tokens: slice, source: sourceOf(text, slice) };
|
|
309
|
+
}
|
|
310
|
+
// --- CREATE TABLE ----------------------------------------------------------------
|
|
311
|
+
const TABLE_LEVEL_START = new Set(["CONSTRAINT", "PRIMARY", "UNIQUE", "FOREIGN", "CHECK", "INDEX", "PERIOD", "CONNECTION"]);
|
|
312
|
+
function columnDef(tokens, text, constraints) {
|
|
313
|
+
const c = new Cursor(tokens);
|
|
314
|
+
const nameTok = c.next();
|
|
315
|
+
const col = { name: nameTok.value, loc: loc(nameTok), typeArgs: [], rowguid: false, sparse: false, extras: [] };
|
|
316
|
+
if (c.isWord("AS")) {
|
|
317
|
+
c.next();
|
|
318
|
+
const startIdx = c.i;
|
|
319
|
+
// `AS (expr)` or `AS expr` up to PERSISTED / NOT NULL / a constraint word
|
|
320
|
+
let expr;
|
|
321
|
+
if (c.peek()?.value === "(") {
|
|
322
|
+
c.group();
|
|
323
|
+
expr = c.tokens.slice(startIdx, c.i);
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
while (!c.done && !c.isWord("PERSISTED", "NOT", "NULL", "CONSTRAINT", "PRIMARY", "UNIQUE", "FOREIGN", "CHECK", "REFERENCES"))
|
|
327
|
+
c.next();
|
|
328
|
+
expr = c.tokens.slice(startIdx, c.i);
|
|
329
|
+
}
|
|
330
|
+
col.computed = { expression: sourceOf(text, expr), persisted: false };
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
// The type: `[schema].]name [(args)]`
|
|
334
|
+
const first = c.next();
|
|
335
|
+
let typeTok = first;
|
|
336
|
+
if (c.peek()?.value === "." && c.peek()?.kind === "punct") {
|
|
337
|
+
c.next();
|
|
338
|
+
col.typeSchema = first.value;
|
|
339
|
+
typeTok = c.next();
|
|
340
|
+
}
|
|
341
|
+
col.type = typeTok.value.toLowerCase();
|
|
342
|
+
if (c.peek()?.value === "(" && c.peek()?.kind === "punct") {
|
|
343
|
+
for (const arg of splitCommas(c.group())) {
|
|
344
|
+
const t = arg[0];
|
|
345
|
+
if (t && t.kind === "number")
|
|
346
|
+
col.typeArgs.push(Number(t.value));
|
|
347
|
+
else if (t && upper(t) === "MAX")
|
|
348
|
+
col.typeArgs.push("max");
|
|
349
|
+
else
|
|
350
|
+
throw new TsqlParseError(`a type argument this source does not read: "${arg.map((x) => x.text).join(" ")}"`, t ? loc(t) : col.loc);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
let constraintName;
|
|
355
|
+
while (!c.done) {
|
|
356
|
+
const t = c.peek();
|
|
357
|
+
const w = upper(t);
|
|
358
|
+
if (w === "CONSTRAINT") {
|
|
359
|
+
c.next();
|
|
360
|
+
constraintName = c.next().value;
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
if (w === "COLLATE") {
|
|
364
|
+
c.next();
|
|
365
|
+
col.collate = c.next().value;
|
|
366
|
+
}
|
|
367
|
+
else if (w === "NOT" && upper(c.peek(1)) === "NULL") {
|
|
368
|
+
c.next();
|
|
369
|
+
c.next();
|
|
370
|
+
col.nullable = false;
|
|
371
|
+
}
|
|
372
|
+
else if (w === "NULL") {
|
|
373
|
+
c.next();
|
|
374
|
+
col.nullable = true;
|
|
375
|
+
}
|
|
376
|
+
else if (w === "NOT" && upper(c.peek(1)) === "FOR") {
|
|
377
|
+
c.next();
|
|
378
|
+
c.next();
|
|
379
|
+
c.next();
|
|
380
|
+
col.extras.push("NOT FOR REPLICATION");
|
|
381
|
+
}
|
|
382
|
+
else if (w === "IDENTITY") {
|
|
383
|
+
c.next();
|
|
384
|
+
if (c.peek()?.value === "(") {
|
|
385
|
+
const args = splitCommas(c.group());
|
|
386
|
+
col.identity = { seed: args[0]?.map((x) => x.text).join("") ?? "1", increment: args[1]?.map((x) => x.text).join("") ?? "1" };
|
|
387
|
+
}
|
|
388
|
+
else
|
|
389
|
+
col.identity = { seed: "1", increment: "1" };
|
|
390
|
+
}
|
|
391
|
+
else if (w === "GENERATED") {
|
|
392
|
+
const start = c.i;
|
|
393
|
+
c.next();
|
|
394
|
+
c.expectWord("ALWAYS");
|
|
395
|
+
c.expectWord("AS");
|
|
396
|
+
while (!c.done && !c.isWord("NOT", "NULL", "HIDDEN", "CONSTRAINT"))
|
|
397
|
+
c.next();
|
|
398
|
+
col.generated = sourceOf(text, c.tokens.slice(start, c.i));
|
|
399
|
+
if (c.eat("HIDDEN"))
|
|
400
|
+
col.generated += " HIDDEN";
|
|
401
|
+
}
|
|
402
|
+
else if (w === "PERSISTED" && col.computed) {
|
|
403
|
+
c.next();
|
|
404
|
+
col.computed.persisted = true;
|
|
405
|
+
}
|
|
406
|
+
else if (w === "ROWGUIDCOL") {
|
|
407
|
+
c.next();
|
|
408
|
+
col.rowguid = true;
|
|
409
|
+
}
|
|
410
|
+
else if (w === "SPARSE") {
|
|
411
|
+
c.next();
|
|
412
|
+
col.sparse = true;
|
|
413
|
+
}
|
|
414
|
+
else if (w === "FILESTREAM") {
|
|
415
|
+
c.next();
|
|
416
|
+
col.extras.push("FILESTREAM");
|
|
417
|
+
}
|
|
418
|
+
else if (w === "MASKED" || w === "ENCRYPTED") {
|
|
419
|
+
const start = c.i;
|
|
420
|
+
c.next();
|
|
421
|
+
c.expectWord("WITH");
|
|
422
|
+
c.group();
|
|
423
|
+
col.extras.push(sourceOf(text, c.tokens.slice(start, c.i)));
|
|
424
|
+
}
|
|
425
|
+
else if (w === "DEFAULT" || w === "PRIMARY" || w === "UNIQUE" || w === "CHECK" || w === "REFERENCES" || w === "FOREIGN") {
|
|
426
|
+
constraints.push(constraint(c, text, constraintName, col.name, t));
|
|
427
|
+
constraintName = undefined;
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
throw new TsqlParseError(`a column option this source does not read: "${t.text}" on column ${col.name}`, loc(t));
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return col;
|
|
434
|
+
}
|
|
435
|
+
function tableBody(c, text, nameTok, name) {
|
|
436
|
+
const table = { kind: "table", name, columns: [], constraints: [], indexes: [], options: [], loc: loc(nameTok) };
|
|
437
|
+
if (c.isWord("AS")) {
|
|
438
|
+
// AS FileTable | AS NODE | AS EDGE
|
|
439
|
+
c.next();
|
|
440
|
+
table.asKind = c.next().value.toUpperCase();
|
|
441
|
+
}
|
|
442
|
+
const items = splitCommas(c.group());
|
|
443
|
+
for (const item of items) {
|
|
444
|
+
const ic = new Cursor(item);
|
|
445
|
+
const w = upper(ic.peek());
|
|
446
|
+
// A column may be named like a table-level keyword when bracketed; only a bare word starts one.
|
|
447
|
+
if (ic.peek()?.kind === "word" && TABLE_LEVEL_START.has(w)) {
|
|
448
|
+
if (w === "CONSTRAINT") {
|
|
449
|
+
const startTok = ic.next();
|
|
450
|
+
const cname = ic.next().value;
|
|
451
|
+
table.constraints.push(constraint(ic, text, cname, undefined, startTok));
|
|
452
|
+
}
|
|
453
|
+
else if (w === "INDEX") {
|
|
454
|
+
ic.next();
|
|
455
|
+
const nameTok2 = ic.next();
|
|
456
|
+
const idx = { kind: "index", name: nameTok2.value, unique: false, clustered: false, columnstore: false, columns: [], include: [], order: [], options: [], loc: loc(nameTok2) };
|
|
457
|
+
if (ic.eat("UNIQUE"))
|
|
458
|
+
idx.unique = true;
|
|
459
|
+
const cl = ic.eat("CLUSTERED", "NONCLUSTERED");
|
|
460
|
+
if (cl)
|
|
461
|
+
idx.clustered = upper(cl) === "CLUSTERED";
|
|
462
|
+
if (ic.eat("COLUMNSTORE"))
|
|
463
|
+
idx.columnstore = true;
|
|
464
|
+
if (ic.peek()?.value === "(")
|
|
465
|
+
idx.columns = keyColumns(ic.group());
|
|
466
|
+
if (ic.eat("INCLUDE"))
|
|
467
|
+
idx.include = nameList(ic.group());
|
|
468
|
+
if (ic.eat("WHERE")) {
|
|
469
|
+
const start = ic.i;
|
|
470
|
+
while (!ic.done && !ic.isWord("WITH", "ON"))
|
|
471
|
+
ic.next();
|
|
472
|
+
idx.where = sourceOf(text, ic.tokens.slice(start, ic.i));
|
|
473
|
+
}
|
|
474
|
+
idx.options = keyOptions(ic, text);
|
|
475
|
+
table.indexes.push(idx);
|
|
476
|
+
}
|
|
477
|
+
else if (w === "PERIOD") {
|
|
478
|
+
ic.next();
|
|
479
|
+
ic.expectWord("FOR");
|
|
480
|
+
ic.expectWord("SYSTEM_TIME");
|
|
481
|
+
const cols = nameList(ic.group());
|
|
482
|
+
table.period = { start: cols[0] ?? "", end: cols[1] ?? "" };
|
|
483
|
+
}
|
|
484
|
+
else if (w === "CONNECTION") {
|
|
485
|
+
throw new TsqlParseError("an edge-table CONNECTION constraint, which this source does not read", loc(ic.peek()));
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
table.constraints.push(constraint(ic, text, undefined, undefined, ic.peek()));
|
|
489
|
+
}
|
|
490
|
+
if (!ic.done)
|
|
491
|
+
throw new TsqlParseError(`text this source does not read after a table constraint: "${ic.peek().text}"`, loc(ic.peek()));
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
table.columns.push(columnDef(item, text, table.constraints));
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
// `WITH (...)`, `ON [PRIMARY]`, `TEXTIMAGE_ON x`, `FILESTREAM_ON x`, `LEDGER = ON`
|
|
498
|
+
while (!c.done) {
|
|
499
|
+
const start = c.i;
|
|
500
|
+
const t = c.next();
|
|
501
|
+
const w = upper(t);
|
|
502
|
+
if (w === "WITH" && c.peek()?.value === "(")
|
|
503
|
+
c.group();
|
|
504
|
+
else if (w === "ON") {
|
|
505
|
+
qname(c);
|
|
506
|
+
if (c.peek()?.value === "(")
|
|
507
|
+
c.group();
|
|
508
|
+
}
|
|
509
|
+
else if (w === "TEXTIMAGE_ON" || w === "FILESTREAM_ON")
|
|
510
|
+
qname(c);
|
|
511
|
+
else
|
|
512
|
+
throw new TsqlParseError(`a table clause this source does not read: "${t.text}"`, loc(t));
|
|
513
|
+
table.options.push(sourceOf(text, c.tokens.slice(start, c.i)));
|
|
514
|
+
}
|
|
515
|
+
return table;
|
|
516
|
+
}
|
|
517
|
+
// --- CREATE INDEX ----------------------------------------------------------------
|
|
518
|
+
function indexStatement(c, text, startTok) {
|
|
519
|
+
let unique = false;
|
|
520
|
+
let clustered = false;
|
|
521
|
+
let columnstore = false;
|
|
522
|
+
if (c.eat("UNIQUE"))
|
|
523
|
+
unique = true;
|
|
524
|
+
const cl = c.eat("CLUSTERED", "NONCLUSTERED");
|
|
525
|
+
if (cl)
|
|
526
|
+
clustered = upper(cl) === "CLUSTERED";
|
|
527
|
+
if (c.eat("COLUMNSTORE"))
|
|
528
|
+
columnstore = true;
|
|
529
|
+
c.expectWord("INDEX");
|
|
530
|
+
const nameTok = c.next();
|
|
531
|
+
c.expectWord("ON");
|
|
532
|
+
const table = qname(c);
|
|
533
|
+
const idx = { kind: "index", name: nameTok.value, table, unique, clustered, columnstore, columns: [], include: [], order: [], options: [], loc: loc(startTok) };
|
|
534
|
+
if (c.peek()?.value === "(")
|
|
535
|
+
idx.columns = keyColumns(c.group());
|
|
536
|
+
// Clauses may come in more than one order: ORDER before or after WITH.
|
|
537
|
+
while (!c.done) {
|
|
538
|
+
if (c.eat("INCLUDE"))
|
|
539
|
+
idx.include = nameList(c.group());
|
|
540
|
+
else if (c.eat("ORDER"))
|
|
541
|
+
idx.order = nameList(c.group());
|
|
542
|
+
else if (c.eat("WHERE")) {
|
|
543
|
+
const start = c.i;
|
|
544
|
+
while (!c.done && !c.isWord("WITH", "ON", "ORDER"))
|
|
545
|
+
c.next();
|
|
546
|
+
idx.where = sourceOf(text, c.tokens.slice(start, c.i));
|
|
547
|
+
}
|
|
548
|
+
else if (c.isWord("WITH") && c.peek(1)?.value === "(") {
|
|
549
|
+
const start = c.i;
|
|
550
|
+
c.next();
|
|
551
|
+
c.group();
|
|
552
|
+
idx.options.push(sourceOf(text, c.tokens.slice(start, c.i)));
|
|
553
|
+
}
|
|
554
|
+
else if (c.isWord("ON")) {
|
|
555
|
+
const start = c.i;
|
|
556
|
+
c.next();
|
|
557
|
+
qname(c);
|
|
558
|
+
if (c.peek()?.value === "(")
|
|
559
|
+
c.group();
|
|
560
|
+
idx.options.push(sourceOf(text, c.tokens.slice(start, c.i)));
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
const t = c.peek();
|
|
564
|
+
throw new TsqlParseError(`an index clause this source does not read: "${t.text}"`, loc(t));
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return idx;
|
|
568
|
+
}
|
|
569
|
+
// --- the rest ------------------------------------------------------------------------
|
|
570
|
+
const REFUSED_KINDS = {
|
|
571
|
+
TRIGGER: { kind: "trigger", reason: "a trigger runs T-SQL on writes; ClickHouse has no trigger, and its closest match is a materialized view" },
|
|
572
|
+
PROC: { kind: "procedure", reason: "a stored procedure is T-SQL code; ClickHouse has no stored procedures" },
|
|
573
|
+
PROCEDURE: { kind: "procedure", reason: "a stored procedure is T-SQL code; ClickHouse has no stored procedures" },
|
|
574
|
+
FUNCTION: { kind: "function", reason: "a T-SQL function is code this source does not translate; a ClickHouse user-defined function is a lambda expression" },
|
|
575
|
+
SEQUENCE: { kind: "sequence", reason: "ClickHouse has no sequence object" },
|
|
576
|
+
TYPE: { kind: "type", reason: "a user-defined type has no ClickHouse counterpart" },
|
|
577
|
+
SYNONYM: { kind: "synonym", reason: "ClickHouse has no synonym" },
|
|
578
|
+
ROLE: { kind: "role", reason: "access control is not read by this source" },
|
|
579
|
+
USER: { kind: "user", reason: "access control is not read by this source" },
|
|
580
|
+
LOGIN: { kind: "login", reason: "access control is not read by this source" },
|
|
581
|
+
DATABASE: { kind: "database", reason: "a database is created outside the schema file" },
|
|
582
|
+
STATISTICS: { kind: "statistics", reason: "ClickHouse keeps no user-created statistics" },
|
|
583
|
+
PARTITION: { kind: "partition function or scheme", reason: "ClickHouse partitions with PARTITION BY on the table" },
|
|
584
|
+
XML: { kind: "xml schema collection", reason: "ClickHouse has no XML schema collection" },
|
|
585
|
+
ASSEMBLY: { kind: "assembly", reason: "a CLR assembly is not portable" },
|
|
586
|
+
AGGREGATE: { kind: "aggregate", reason: "a CLR aggregate is not portable" },
|
|
587
|
+
RULE: { kind: "rule", reason: "ClickHouse has no rule object" },
|
|
588
|
+
DEFAULT: { kind: "default object", reason: "ClickHouse has no default object" },
|
|
589
|
+
EVENT: { kind: "event session or notification", reason: "not read by this source" },
|
|
590
|
+
FULLTEXT: { kind: "full-text object", reason: "ClickHouse has no full-text catalog" },
|
|
591
|
+
SEARCH: { kind: "search property list", reason: "not read by this source" },
|
|
592
|
+
MASTER: { kind: "master key", reason: "key management is not read by this source" },
|
|
593
|
+
CERTIFICATE: { kind: "certificate", reason: "key management is not read by this source" },
|
|
594
|
+
EXTERNAL: { kind: "external object", reason: "not read by this source" },
|
|
595
|
+
MATERIALIZED: { kind: "materialized view", reason: "not read by this source" },
|
|
596
|
+
SECURITY: { kind: "security policy", reason: "row-level security is not read by this source" },
|
|
597
|
+
QUEUE: { kind: "service broker object", reason: "ClickHouse has no service broker" },
|
|
598
|
+
};
|
|
599
|
+
function describeStatement(tokens, text) {
|
|
600
|
+
const shown = tokens.slice(0, 8);
|
|
601
|
+
return sourceOf(text, shown).replace(/\s+/g, " ");
|
|
602
|
+
}
|
|
603
|
+
function oneStatement(tokens, text, out) {
|
|
604
|
+
const c = new Cursor(tokens);
|
|
605
|
+
const first = c.peek();
|
|
606
|
+
const start = loc(first);
|
|
607
|
+
const w = upper(first);
|
|
608
|
+
try {
|
|
609
|
+
if (w === "CREATE" || w === "ALTER") {
|
|
610
|
+
c.next();
|
|
611
|
+
if (w === "CREATE" && c.isWord("OR")) {
|
|
612
|
+
c.next();
|
|
613
|
+
c.expectWord("ALTER");
|
|
614
|
+
}
|
|
615
|
+
const kind = upper(c.peek());
|
|
616
|
+
if (w === "CREATE" && kind === "TABLE") {
|
|
617
|
+
c.next();
|
|
618
|
+
const nameTok = c.peek();
|
|
619
|
+
const name = qname(c);
|
|
620
|
+
out.statements.push(tableBody(c, text, nameTok, name));
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
if (w === "CREATE" && (kind === "INDEX" || kind === "UNIQUE" || kind === "CLUSTERED" || kind === "NONCLUSTERED" || kind === "COLUMNSTORE")) {
|
|
624
|
+
out.statements.push(indexStatement(c, text, first));
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
if (w === "CREATE" && kind === "VIEW") {
|
|
628
|
+
c.next();
|
|
629
|
+
const name = qname(c);
|
|
630
|
+
const columns = c.peek()?.value === "(" ? nameList(c.group()) : [];
|
|
631
|
+
const options = [];
|
|
632
|
+
if (c.eat("WITH")) {
|
|
633
|
+
for (const part of splitCommas(tokens.slice(c.i)))
|
|
634
|
+
options.push(sourceOf(text, part));
|
|
635
|
+
while (!c.isWord("AS"))
|
|
636
|
+
c.next();
|
|
637
|
+
}
|
|
638
|
+
c.expectWord("AS");
|
|
639
|
+
let body = tokens.slice(c.i);
|
|
640
|
+
while (body.length > 0 && body[body.length - 1].kind === "punct" && body[body.length - 1].value === ";")
|
|
641
|
+
body = body.slice(0, -1);
|
|
642
|
+
out.statements.push({ kind: "view", name, columns, options, body: sourceOf(text, body), bodyTokens: [...body], loc: name.loc });
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
if (w === "CREATE" && kind === "SCHEMA") {
|
|
646
|
+
c.next();
|
|
647
|
+
out.statements.push({ kind: "schema", name: c.next().value, loc: start });
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
if (w === "CREATE" || (w === "ALTER" && kind !== "TABLE")) {
|
|
651
|
+
const refused = REFUSED_KINDS[kind];
|
|
652
|
+
const objectKind = refused?.kind ?? kind.toLowerCase();
|
|
653
|
+
c.next();
|
|
654
|
+
// `PARTITION FUNCTION f`, `XML SCHEMA COLLECTION c`, `EXTERNAL TABLE t`: skip the second word.
|
|
655
|
+
if (["PARTITION", "XML", "EXTERNAL", "EVENT", "FULLTEXT", "SEARCH", "MASTER", "SECURITY", "MATERIALIZED"].includes(kind)) {
|
|
656
|
+
const second = c.peek();
|
|
657
|
+
if (second && second.kind === "word")
|
|
658
|
+
c.next();
|
|
659
|
+
}
|
|
660
|
+
const nameTok = c.peek();
|
|
661
|
+
let name = "";
|
|
662
|
+
if (nameTok && (nameTok.kind === "word" || nameTok.kind === "qident"))
|
|
663
|
+
name = qname(c).parts.join(".");
|
|
664
|
+
out.statements.push({
|
|
665
|
+
kind: "refused",
|
|
666
|
+
objectKind: w === "ALTER" ? `alter ${objectKind}` : objectKind,
|
|
667
|
+
name,
|
|
668
|
+
reason: refused?.reason ?? "a kind of object this source does not read",
|
|
669
|
+
loc: start,
|
|
670
|
+
});
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
// ALTER TABLE t [WITH [NO]CHECK] ADD [CONSTRAINT n] ...
|
|
674
|
+
c.expectWord("TABLE");
|
|
675
|
+
const table = qname(c);
|
|
676
|
+
if (c.eat("WITH"))
|
|
677
|
+
c.expectWord("CHECK", "NOCHECK");
|
|
678
|
+
if (c.isWord("ADD")) {
|
|
679
|
+
c.next();
|
|
680
|
+
let cname;
|
|
681
|
+
const ctok = c.peek();
|
|
682
|
+
if (c.eat("CONSTRAINT"))
|
|
683
|
+
cname = c.next().value;
|
|
684
|
+
if (c.isWord("PRIMARY", "UNIQUE", "FOREIGN", "CHECK", "DEFAULT")) {
|
|
685
|
+
const k = constraint(c, text, cname, undefined, ctok);
|
|
686
|
+
// `ADD DEFAULT (x) FOR col`
|
|
687
|
+
if (k.kind === "default" && c.eat("FOR"))
|
|
688
|
+
k.columns = [{ name: c.next().value, descending: false }];
|
|
689
|
+
if (!c.done)
|
|
690
|
+
throw new TsqlParseError(`text this source does not read after the constraint: "${c.peek().text}"`, loc(c.peek()));
|
|
691
|
+
out.statements.push({ kind: "alter-constraint", table, constraint: k, loc: start });
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
out.unread.push({ text: describeStatement(tokens, text), reason: "an ALTER TABLE this source does not read; only ADD CONSTRAINT is read", loc: start });
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
if (w === "EXEC" || w === "EXECUTE") {
|
|
699
|
+
c.next();
|
|
700
|
+
const proc = c.peek();
|
|
701
|
+
if (proc && /^sp_addextendedproperty$/i.test(proc.value)) {
|
|
702
|
+
const ep = extendedProperty(c, start);
|
|
703
|
+
if (ep) {
|
|
704
|
+
out.statements.push(ep);
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
out.statements.push({ kind: "refused", objectKind: "statement", name: describeStatement(tokens, text), reason: "executing a procedure is code, not a declaration", loc: start });
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
if (w === "SET" || w === "USE" || w === "PRINT" || w === "DROP" || w === "DECLARE" || w === "RETURN" || w === "TRUNCATE") {
|
|
712
|
+
const reason = w === "DROP"
|
|
713
|
+
? "a DROP is not a declaration; the schema describes the end state"
|
|
714
|
+
: w === "SET" || w === "USE"
|
|
715
|
+
? "a session or connection setting, not a declaration"
|
|
716
|
+
: "not a declaration";
|
|
717
|
+
out.statements.push({ kind: "ignored", text: describeStatement(tokens, text), reason, loc: start });
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
out.statements.push({ kind: "refused", objectKind: "statement", name: describeStatement(tokens, text), reason: "this source reads CREATE TABLE, CREATE INDEX, CREATE VIEW, CREATE SCHEMA and ALTER TABLE ... ADD CONSTRAINT", loc: start });
|
|
721
|
+
}
|
|
722
|
+
catch (e) {
|
|
723
|
+
if (e instanceof TsqlParseError) {
|
|
724
|
+
out.unread.push({ text: describeStatement(tokens, text), reason: e.message, loc: e.loc });
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
throw e;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
/** `EXECUTE sp_addextendedproperty @name = N'...', @value = ..., @level0type = ..., ...`; undefined when it is not the shape SSDT writes. */
|
|
731
|
+
function extendedProperty(c, at) {
|
|
732
|
+
c.next();
|
|
733
|
+
const args = new Map();
|
|
734
|
+
const rest = c.tokens.slice(c.i);
|
|
735
|
+
for (const part of splitCommas(rest)) {
|
|
736
|
+
const t = part[0];
|
|
737
|
+
if (!t || t.kind !== "variable" || part[1]?.value !== "=" || !part[2])
|
|
738
|
+
return undefined;
|
|
739
|
+
const v = part[2];
|
|
740
|
+
args.set(t.value.toLowerCase(), v.kind === "string" || v.kind === "qident" ? v.value : v.text);
|
|
741
|
+
}
|
|
742
|
+
const name = args.get("@name");
|
|
743
|
+
const value = args.get("@value");
|
|
744
|
+
if (name === undefined || value === undefined)
|
|
745
|
+
return undefined;
|
|
746
|
+
const l2t = args.get("@level2type");
|
|
747
|
+
const l2n = args.get("@level2name");
|
|
748
|
+
return {
|
|
749
|
+
kind: "extended-property",
|
|
750
|
+
property: name,
|
|
751
|
+
value,
|
|
752
|
+
schema: args.get("@level0name"),
|
|
753
|
+
table: args.get("@level1name"),
|
|
754
|
+
level1Type: args.get("@level1type"),
|
|
755
|
+
...(l2t?.toUpperCase() === "COLUMN" ? { column: l2n } : l2t && l2n ? { level2: { type: l2t, name: l2n } } : {}),
|
|
756
|
+
loc: at,
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
/** Parse a file of T-SQL. Lexing errors throw `TsqlLexError`. */
|
|
760
|
+
export function parseTsql(text) {
|
|
761
|
+
const out = { statements: [], unread: [] };
|
|
762
|
+
for (const batch of batches(tokenize(text))) {
|
|
763
|
+
for (const stmt of statementsOf(batch))
|
|
764
|
+
oneStatement(stmt, text, out);
|
|
765
|
+
}
|
|
766
|
+
return out;
|
|
767
|
+
}
|