@intentius/chant-lexicon-sql 0.124.31 → 0.124.33
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.js +250 -0
- package/dist/clickhouse/access.js +722 -0
- package/dist/clickhouse/apply/apply.js +243 -0
- package/dist/clickhouse/apply/mutations.js +66 -0
- package/dist/clickhouse/apply/statements.js +545 -0
- package/dist/clickhouse/catalog-types.js +7 -0
- package/dist/clickhouse/container.js +152 -0
- package/dist/clickhouse/engine-syntax.js +159 -0
- package/dist/clickhouse/entities.js +593 -0
- package/dist/clickhouse/expressions.js +107 -0
- package/dist/clickhouse/http.js +55 -0
- package/dist/clickhouse/import/generator.js +192 -0
- package/dist/clickhouse/import/inlined.js +207 -0
- package/dist/clickhouse/import/ir.js +160 -0
- package/dist/clickhouse/import/live-export.js +131 -0
- package/dist/clickhouse/import/parser.js +17 -0
- package/dist/clickhouse/live/bind.js +116 -0
- package/dist/clickhouse/live/catalog.js +223 -0
- package/dist/clickhouse/live/describe-resources.js +91 -0
- package/dist/clickhouse/live/undeclarable.js +59 -0
- package/dist/clickhouse/overlays/codecs.js +57 -0
- package/dist/clickhouse/overlays/engines.js +101 -0
- package/dist/clickhouse/overlays/kinds.js +1 -0
- package/dist/clickhouse/overlays/projections.js +26 -0
- package/dist/clickhouse/overlays/skip-indexes.js +40 -0
- package/dist/clickhouse/overlays/ttl.js +34 -0
- package/dist/clickhouse/overlays/types.js +59 -0
- package/dist/clickhouse/ownership.js +83 -0
- package/dist/clickhouse/parser.js +674 -0
- package/dist/clickhouse/plan/commands.js +159 -0
- package/dist/clickhouse/plan/deep.js +345 -0
- package/dist/clickhouse/plan/diff.js +312 -0
- package/dist/clickhouse/plan/disruption.js +73 -0
- package/dist/clickhouse/plan/normalize.js +545 -0
- package/dist/clickhouse/plan/rebuild-handoff.js +57 -0
- package/dist/clickhouse/plan/report.js +26 -0
- package/dist/clickhouse/plan/rules.js +72 -0
- package/dist/clickhouse/plan/schema.js +67 -0
- package/dist/clickhouse/plan/server-format.js +35 -0
- package/dist/clickhouse/rebuild/backfill.js +265 -0
- package/dist/clickhouse/rebuild/engines.js +146 -0
- package/dist/clickhouse/rebuild/observe.js +260 -0
- package/dist/clickhouse/rebuild/op.js +152 -0
- package/dist/clickhouse/rebuild/partitions.js +65 -0
- package/dist/clickhouse/rebuild/receipts.js +120 -0
- package/dist/clickhouse/rebuild/replicas.js +121 -0
- package/dist/clickhouse/rebuild/shards.js +76 -0
- package/dist/clickhouse/rebuild/steps.js +383 -0
- package/dist/clickhouse/rebuild/verify.js +279 -0
- package/dist/clickhouse/row-types.js +151 -0
- package/dist/clickhouse/testing/fake-server.js +47 -0
- package/dist/clickhouse/testing/roundtrip-schema.js +25 -0
- package/dist/clickhouse/testing/writable-server.js +123 -0
- package/dist/clickhouse/tokens.js +23 -0
- package/dist/clickhouse/topology.js +463 -0
- package/dist/clickhouse/type-syntax.js +73 -0
- package/dist/clickhouse.js +25 -0
- package/dist/codegen/classifier-page.js +93 -0
- package/dist/codegen/clickhouse-module.js +249 -0
- package/dist/codegen/coverage-page.js +70 -0
- package/dist/codegen/docs-cli.js +3 -0
- package/dist/codegen/docs.js +222 -0
- package/dist/codegen/generate-cli.js +13 -0
- package/dist/codegen/generate.js +97 -0
- package/dist/codegen/package.js +28 -0
- package/dist/codegen/postgres-classifier-page.js +95 -0
- package/dist/codegen/postgres-module.js +418 -0
- package/dist/codegen/registry.js +52 -0
- package/dist/composites/audit-log-table.js +35 -0
- package/dist/composites/catalog.js +524 -0
- package/dist/composites/cdc-mirror.js +32 -0
- package/dist/composites/clickhouse.js +6 -0
- package/dist/composites/events-table.js +26 -0
- package/dist/composites/index.js +8 -0
- package/dist/composites/join-table.js +25 -0
- package/dist/composites/postgres.js +6 -0
- package/dist/composites/refreshed-view.js +24 -0
- package/dist/composites/replacing-table.js +28 -0
- package/dist/composites/rollup-view.js +29 -0
- package/dist/composites/sharded-table.js +27 -0
- package/dist/composites/soft-delete-table.js +34 -0
- package/dist/composites/tenant-table.js +26 -0
- package/dist/composites/typed.js +8 -0
- package/dist/config.js +206 -0
- package/dist/core/apply.js +93 -0
- package/dist/core/classifier.js +115 -0
- package/dist/core/container.js +69 -0
- package/dist/core/cursor.js +261 -0
- package/dist/core/diff.js +48 -0
- package/dist/core/entity.js +58 -0
- package/dist/core/find-templates.js +118 -0
- package/dist/core/foreign-tables.js +27 -0
- package/dist/core/handoff.js +32 -0
- package/dist/core/index.js +64 -0
- package/dist/core/interpolation.js +191 -0
- package/dist/core/normalize.js +43 -0
- package/dist/core/ownership.js +119 -0
- package/dist/core/receipts.js +22 -0
- package/dist/core/references.js +61 -0
- package/dist/core/statements.js +42 -0
- package/dist/core/template.js +79 -0
- package/dist/core/testing/http-stub.js +21 -0
- package/dist/core/tokens.js +202 -0
- package/dist/core/typed.js +40 -0
- package/dist/core/undeclarable.js +46 -0
- package/dist/coverage.js +161 -0
- package/dist/detect.js +32 -0
- package/dist/dialects.js +18 -0
- package/dist/files/build-root.js +72 -0
- package/dist/files/clickhouse.js +138 -0
- package/dist/files/common.js +58 -0
- package/dist/files/postgres.js +389 -0
- package/dist/generated/clickhouse-types.js +5 -0
- package/dist/generated/clickhouse.js +4361 -0
- package/dist/generated/postgres-types.js +227 -0
- package/dist/generated/postgres.js +3667 -0
- package/dist/import-generator.js +14 -0
- package/dist/import-parser.js +71 -0
- package/dist/index.js +24 -0
- package/dist/init-templates.js +290 -0
- package/dist/integrity.json +2 -2
- package/dist/lint/audit-catalog.js +31 -0
- package/dist/lint/audit-lineage.js +22 -0
- package/dist/lint/post-synth/audit-entries.js +34 -0
- package/dist/lint/post-synth/clickhouse-helpers.js +79 -0
- package/dist/lint/post-synth/clickhouse-type-check.js +162 -0
- package/dist/lint/post-synth/index.js +112 -0
- package/dist/lint/post-synth/postgres-audit-entries.js +35 -0
- package/dist/lint/post-synth/postgres-helpers.js +65 -0
- package/dist/lint/post-synth/postgres-names.js +507 -0
- package/dist/lint/post-synth/sql-helpers.js +23 -0
- package/dist/lint/post-synth/sql101.js +59 -0
- package/dist/lint/post-synth/sql102.js +34 -0
- package/dist/lint/post-synth/sqlch101.js +34 -0
- package/dist/lint/post-synth/sqlch102.js +23 -0
- package/dist/lint/post-synth/sqlch103.js +45 -0
- package/dist/lint/post-synth/sqlch104.js +41 -0
- package/dist/lint/post-synth/sqlch105.js +36 -0
- package/dist/lint/post-synth/sqlch106.js +27 -0
- package/dist/lint/post-synth/sqlch107.js +39 -0
- package/dist/lint/post-synth/sqlch108.js +28 -0
- package/dist/lint/post-synth/sqlch109.js +21 -0
- package/dist/lint/post-synth/sqlch110.js +31 -0
- package/dist/lint/post-synth/sqlch111.js +41 -0
- package/dist/lint/post-synth/sqlch112.js +28 -0
- package/dist/lint/post-synth/sqlch113.js +19 -0
- package/dist/lint/post-synth/sqlch114.js +26 -0
- package/dist/lint/post-synth/sqlch115.js +30 -0
- package/dist/lint/post-synth/sqlch116.js +22 -0
- package/dist/lint/post-synth/sqlch117.js +26 -0
- package/dist/lint/post-synth/sqlch118.js +27 -0
- package/dist/lint/post-synth/sqlch119.js +32 -0
- package/dist/lint/post-synth/sqlch120.js +20 -0
- package/dist/lint/post-synth/sqlch121.js +11 -0
- package/dist/lint/post-synth/sqlch122.js +11 -0
- package/dist/lint/post-synth/sqlch123.js +10 -0
- package/dist/lint/post-synth/sqlch124.js +21 -0
- package/dist/lint/post-synth/sqlch125.js +55 -0
- package/dist/lint/post-synth/sqlch126.js +62 -0
- package/dist/lint/post-synth/sqlch127.js +132 -0
- package/dist/lint/post-synth/sqlpg101.js +25 -0
- package/dist/lint/post-synth/sqlpg102.js +35 -0
- package/dist/lint/post-synth/sqlpg103.js +23 -0
- package/dist/lint/post-synth/sqlpg104.js +23 -0
- package/dist/lint/post-synth/sqlpg105.js +22 -0
- package/dist/lint/post-synth/sqlpg106.js +22 -0
- package/dist/lint/post-synth/sqlpg107.js +22 -0
- package/dist/lint/post-synth/sqlpg108.js +28 -0
- package/dist/lint/post-synth/sqlpg109.js +39 -0
- package/dist/lint/post-synth/sqlpg110.js +38 -0
- package/dist/lint/post-synth/sqlpg111.js +23 -0
- package/dist/lint/post-synth/sqlpg112.js +25 -0
- package/dist/lint/post-synth/sqlpg113.js +29 -0
- package/dist/lint/post-synth/sqlpg114.js +39 -0
- package/dist/lint/post-synth/sqlpg115.js +48 -0
- package/dist/lint/post-synth/sqlpg116.js +26 -0
- package/dist/lint/post-synth/sqlpg117.js +24 -0
- package/dist/lint/post-synth/sqlpg118.js +22 -0
- package/dist/lint/post-synth/sqlpg119.js +41 -0
- package/dist/lint/post-synth/sqlpg120.js +83 -0
- package/dist/lint/post-synth/sqlpg121.js +33 -0
- package/dist/lint/post-synth/sqlpg122.js +27 -0
- package/dist/lint/post-synth/sqlpg123.js +69 -0
- package/dist/lint/post-synth/sqlpg124.js +50 -0
- package/dist/lint/post-synth/sqlpg125.js +41 -0
- package/dist/lint/post-synth/sqlpg126.js +38 -0
- package/dist/lint/rules/index.js +12 -0
- package/dist/lint/rules/postgres-templates.js +27 -0
- package/dist/lint/rules/sqlch001.js +79 -0
- package/dist/lint/rules/sqlch002.js +124 -0
- package/dist/lint/rules/sqlch003.js +69 -0
- package/dist/lint/rules/sqlch004.js +83 -0
- package/dist/lint/rules/sqlpg001.js +68 -0
- package/dist/lint/rules/sqlpg002.js +69 -0
- package/dist/lint/rules/sqlpg003.js +58 -0
- package/dist/lint/rules/sqlpg004.js +77 -0
- package/dist/lint/rules/sqlpg005.js +86 -0
- package/dist/lint/rules/templates.js +25 -0
- package/dist/lint/rules/typed-names.js +132 -0
- package/dist/live-dialect.js +32 -0
- package/dist/lsp/catalog.js +44 -0
- package/dist/lsp/completions.js +136 -0
- package/dist/lsp/hover.js +115 -0
- package/dist/lsp/postgres-catalog.js +176 -0
- package/dist/lsp/postgres-template.js +171 -0
- package/dist/lsp/postgres.js +266 -0
- package/dist/lsp/refs.js +60 -0
- package/dist/lsp/registry.d.ts.map +1 -1
- package/dist/lsp/registry.js +19 -0
- package/dist/lsp/template.js +143 -0
- package/dist/manifest.json +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +293 -0
- package/dist/mcp/postgres.js +117 -0
- package/dist/migration-statements.js +243 -0
- package/dist/op/activities/clickhouse-apply.js +83 -0
- package/dist/op/activities/clickhouse-emulator.js +53 -0
- package/dist/op/activities/clickhouse-rebuild.js +119 -0
- package/dist/op/activities/index.js +26 -0
- package/dist/op/activities/postgres-apply.js +126 -0
- package/dist/op/activities/postgres-emulator.js +70 -0
- package/dist/op/activities/postgres-migration.js +156 -0
- package/dist/op/activities/sql-exec.js +57 -0
- package/dist/op/activity-contracts.js +103 -0
- package/dist/package-cli.js +14 -0
- package/dist/plugin.js +259 -0
- package/dist/postgres/access/acl.js +344 -0
- package/dist/postgres/access/changes.js +30 -0
- package/dist/postgres/access/import.js +61 -0
- package/dist/postgres/access/live.js +96 -0
- package/dist/postgres/apply/apply.js +557 -0
- package/dist/postgres/apply/statements.js +882 -0
- package/dist/postgres/catalog-types.js +18 -0
- package/dist/postgres/container.js +58 -0
- package/dist/postgres/entities.js +1390 -0
- package/dist/postgres/entity-types.js +28 -0
- package/dist/postgres/import/generator.js +283 -0
- package/dist/postgres/import/ir.js +53 -0
- package/dist/postgres/import/live-export.js +83 -0
- package/dist/postgres/keywords.js +32 -0
- package/dist/postgres/live/bind.js +130 -0
- package/dist/postgres/live/catalog-kinds.js +301 -0
- package/dist/postgres/live/catalog.js +644 -0
- package/dist/postgres/live/client.js +151 -0
- package/dist/postgres/live/describe-resources.js +135 -0
- package/dist/postgres/live/undeclarable.js +111 -0
- package/dist/postgres/migrate/backfill.js +170 -0
- package/dist/postgres/migrate/batches.js +122 -0
- package/dist/postgres/migrate/carry.js +518 -0
- package/dist/postgres/migrate/handoff.js +59 -0
- package/dist/postgres/migrate/names.js +61 -0
- package/dist/postgres/migrate/observe.js +360 -0
- package/dist/postgres/migrate/op.js +178 -0
- package/dist/postgres/migrate/receipts.js +117 -0
- package/dist/postgres/migrate/replication.js +106 -0
- package/dist/postgres/migrate/steps.js +514 -0
- package/dist/postgres/overlays/columns.js +31 -0
- package/dist/postgres/overlays/constraints.js +52 -0
- package/dist/postgres/overlays/extension-objects.js +88 -0
- package/dist/postgres/overlays/indexes.js +25 -0
- package/dist/postgres/overlays/kinds.js +2 -0
- package/dist/postgres/overlays/partitioning.js +23 -0
- package/dist/postgres/overlays/relations.js +25 -0
- package/dist/postgres/overlays/storage.js +65 -0
- package/dist/postgres/overlays/type-ddl.js +36 -0
- package/dist/postgres/overlays/types.js +74 -0
- package/dist/postgres/parser.js +1824 -0
- package/dist/postgres/plan/commands.js +137 -0
- package/dist/postgres/plan/deep.js +324 -0
- package/dist/postgres/plan/diff.js +499 -0
- package/dist/postgres/plan/disruption.js +64 -0
- package/dist/postgres/plan/normalize.js +889 -0
- package/dist/postgres/plan/report.js +21 -0
- package/dist/postgres/plan/rules.js +115 -0
- package/dist/postgres/plan/schema.js +105 -0
- package/dist/postgres/plan/server-normalize.js +166 -0
- package/dist/postgres/providers/aurora.js +44 -0
- package/dist/postgres/providers/azure.js +53 -0
- package/dist/postgres/providers/cloud-sql.js +41 -0
- package/dist/postgres/providers/index.js +73 -0
- package/dist/postgres/providers/neon.js +44 -0
- package/dist/postgres/providers/rds.js +47 -0
- package/dist/postgres/providers/supabase.js +45 -0
- package/dist/postgres/providers/types.js +21 -0
- package/dist/postgres/row-types.js +107 -0
- package/dist/postgres/testing/fake-client.js +34 -0
- package/dist/postgres/testing/migration.js +48 -0
- package/dist/postgres/testing/roundtrip-schema.js +64 -0
- package/dist/postgres/testing/server.js +47 -0
- package/dist/postgres/testing/writable-server.js +160 -0
- package/dist/postgres/tokens.js +41 -0
- package/dist/postgres.js +15 -0
- package/dist/read/clickhouse.js +82 -0
- package/dist/read/objects.js +44 -0
- package/dist/read/postgres.js +104 -0
- package/dist/read.js +52 -0
- package/dist/receipts.js +207 -0
- package/dist/serializer.js +172 -0
- package/dist/skill-defs.js +93 -0
- package/dist/spec/catalog.js +191 -0
- package/dist/spec/fetch.d.ts.map +1 -1
- package/dist/spec/fetch.js +90 -0
- package/dist/spec/pin.js +42 -0
- package/dist/spec/postgres-catalog.js +171 -0
- package/dist/spec/postgres-fetch.js +91 -0
- package/dist/spec/postgres-pin.js +82 -0
- package/dist/sql-files.js +206 -0
- package/dist/sql-types-command.js +97 -0
- package/dist/sql-types.js +323 -0
- package/dist/token-source.js +156 -0
- package/dist/validate-cli.js +5 -0
- package/dist/validate.js +151 -0
- package/dist/workspace-outputs.js +191 -0
- package/package.json +7 -7
- package/src/codegen/package.ts +2 -2
- package/src/lint/rules/typescript-7.test.ts +2 -1
- package/src/lsp/catalog.ts +2 -2
- package/src/lsp/postgres-catalog.ts +2 -2
- package/src/lsp/registry.ts +2 -1
- package/src/mcp/index.ts +4 -2
- package/src/spec/fetch.ts +2 -2
- package/src/spec/postgres-fetch.ts +2 -2
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A lossless tokenizer over a tagged template's parts (chant #3196).
|
|
3
|
+
*
|
|
4
|
+
* The template is never joined into one string with placeholders. Each literal
|
|
5
|
+
* part is tokenized on its own and an interpolation is a `ref` token between
|
|
6
|
+
* two parts, so a token's offset is an offset into the part the author wrote:
|
|
7
|
+
* what a lint rule and the editor point at.
|
|
8
|
+
*
|
|
9
|
+
* Lossless: concatenating every token's `text`, with each `ref` written back as
|
|
10
|
+
* `${...}`, gives the raw template text back byte for byte. Whitespace and
|
|
11
|
+
* comments are tokens (`ws`, `comment`), not discarded.
|
|
12
|
+
*
|
|
13
|
+
* Shared by every dialect. What differs at the character level (which quotes
|
|
14
|
+
* make an identifier, whether a backslash escapes, the punctuation and
|
|
15
|
+
* operator characters) is a dialect's {@link LexicalRules}; ClickHouse's are
|
|
16
|
+
* `../clickhouse/tokens.ts`, Postgres's `../postgres/tokens.ts`. The rules
|
|
17
|
+
* Postgres adds (#3278: dollar quotes, `E'...'` strings, nested comments,
|
|
18
|
+
* `$1` parameters, its number syntax) are optional fields, off unless a
|
|
19
|
+
* dialect sets them, so ClickHouse tokenizes exactly as before.
|
|
20
|
+
*/
|
|
21
|
+
/** A syntax error, located in the template: the part and the offset in it. */
|
|
22
|
+
export class SqlSyntaxError extends Error {
|
|
23
|
+
constructor(message, part, offset,
|
|
24
|
+
/** The token the error is at, when there is one. A spliced token's position is the interpolation's. */
|
|
25
|
+
token) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.part = part;
|
|
28
|
+
this.offset = offset;
|
|
29
|
+
this.token = token;
|
|
30
|
+
this.name = "SqlSyntaxError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const IDENT_START_ASCII = /[A-Za-z_$]/;
|
|
34
|
+
const IDENT_PART_ASCII = /[A-Za-z0-9_$]/;
|
|
35
|
+
const IDENT_START_UNICODE = /[A-Za-z_\u0080-\uffff]/;
|
|
36
|
+
const IDENT_PART_UNICODE = /[A-Za-z0-9_$\u0080-\uffff]/;
|
|
37
|
+
/** Tokenize one string. `part` is the part index recorded on each token and on an error. */
|
|
38
|
+
export function tokenizeText(src, part, rules) {
|
|
39
|
+
const tokens = [];
|
|
40
|
+
let i = 0;
|
|
41
|
+
const push = (kind, start, end) => tokens.push({ kind, text: src.slice(start, end), part, start, end });
|
|
42
|
+
const identStart = rules.unicodeIdentifiers ? IDENT_START_UNICODE : IDENT_START_ASCII;
|
|
43
|
+
const identPart = rules.unicodeIdentifiers ? IDENT_PART_UNICODE : IDENT_PART_ASCII;
|
|
44
|
+
/** The index after the quote that closes the one at `at`. A doubled quote is an escaped quote. */
|
|
45
|
+
const quoted = (at, q, backslash) => {
|
|
46
|
+
let j = at + 1;
|
|
47
|
+
for (;;) {
|
|
48
|
+
if (j >= src.length)
|
|
49
|
+
throw new SqlSyntaxError(`unterminated ${q} quote`, part, at);
|
|
50
|
+
if (backslash && src[j] === "\\") {
|
|
51
|
+
j += 2;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (src[j] === q) {
|
|
55
|
+
if (src[j + 1] === q) {
|
|
56
|
+
j += 2;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
return j + 1;
|
|
60
|
+
}
|
|
61
|
+
j++;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
while (i < src.length) {
|
|
65
|
+
const c = src[i];
|
|
66
|
+
const start = i;
|
|
67
|
+
if (/\s/.test(c)) {
|
|
68
|
+
while (i < src.length && /\s/.test(src[i]))
|
|
69
|
+
i++;
|
|
70
|
+
push("ws", start, i);
|
|
71
|
+
}
|
|
72
|
+
else if (c === "-" && src[i + 1] === "-") {
|
|
73
|
+
while (i < src.length && src[i] !== "\n")
|
|
74
|
+
i++;
|
|
75
|
+
push("comment", start, i);
|
|
76
|
+
}
|
|
77
|
+
else if (c === "/" && src[i + 1] === "*") {
|
|
78
|
+
if (rules.nestedComments) {
|
|
79
|
+
let depth = 1;
|
|
80
|
+
i += 2;
|
|
81
|
+
while (depth > 0) {
|
|
82
|
+
if (i >= src.length)
|
|
83
|
+
throw new SqlSyntaxError("unterminated /* comment", part, start);
|
|
84
|
+
if (src[i] === "*" && src[i + 1] === "/") {
|
|
85
|
+
depth--;
|
|
86
|
+
i += 2;
|
|
87
|
+
}
|
|
88
|
+
else if (src[i] === "/" && src[i + 1] === "*") {
|
|
89
|
+
depth++;
|
|
90
|
+
i += 2;
|
|
91
|
+
}
|
|
92
|
+
else
|
|
93
|
+
i++;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
const close = src.indexOf("*/", i + 2);
|
|
98
|
+
if (close < 0)
|
|
99
|
+
throw new SqlSyntaxError("unterminated /* comment", part, start);
|
|
100
|
+
i = close + 2;
|
|
101
|
+
}
|
|
102
|
+
push("comment", start, i);
|
|
103
|
+
}
|
|
104
|
+
else if (rules.prefixedStrings && /[EeBbXxNn]/.test(c) && src[i + 1] === "'" && !(start > 0 && identPart.test(src[start - 1]))) {
|
|
105
|
+
i = quoted(i + 1, "'", c === "E" || c === "e");
|
|
106
|
+
push("string", start, i);
|
|
107
|
+
}
|
|
108
|
+
else if (rules.prefixedStrings && /[Uu]/.test(c) && src[i + 1] === "&" && (src[i + 2] === "'" || src[i + 2] === '"') && !(start > 0 && identPart.test(src[start - 1]))) {
|
|
109
|
+
const q = src[i + 2];
|
|
110
|
+
i = quoted(i + 2, q, false);
|
|
111
|
+
push(q === "'" ? "string" : "qident", start, i);
|
|
112
|
+
}
|
|
113
|
+
else if (c === "'" || rules.identQuotes.includes(c)) {
|
|
114
|
+
i = quoted(i, c, rules.backslashEscapes);
|
|
115
|
+
push(c === "'" ? "string" : "qident", start, i);
|
|
116
|
+
}
|
|
117
|
+
else if (rules.dollarQuotes && c === "$" && /[0-9]/.test(src[i + 1] ?? "")) {
|
|
118
|
+
i++;
|
|
119
|
+
while (i < src.length && /[0-9]/.test(src[i]))
|
|
120
|
+
i++;
|
|
121
|
+
push("param", start, i);
|
|
122
|
+
}
|
|
123
|
+
else if (rules.dollarQuotes && c === "$" && /^\$([A-Za-z_\u0080-\uffff][A-Za-z0-9_\u0080-\uffff]*)?\$/.test(src.slice(i))) {
|
|
124
|
+
const delimiter = /^\$([A-Za-z_\u0080-\uffff][A-Za-z0-9_\u0080-\uffff]*)?\$/.exec(src.slice(i))[0];
|
|
125
|
+
const close = src.indexOf(delimiter, i + delimiter.length);
|
|
126
|
+
if (close < 0)
|
|
127
|
+
throw new SqlSyntaxError(`unterminated ${delimiter} string`, part, start);
|
|
128
|
+
i = close + delimiter.length;
|
|
129
|
+
push("string", start, i);
|
|
130
|
+
}
|
|
131
|
+
else if (rules.numbers === "sql" && (/[0-9]/.test(c) || (c === "." && /[0-9]/.test(src[i + 1] ?? "")))) {
|
|
132
|
+
if (/^0[xXoObB]/.test(src.slice(i, i + 2))) {
|
|
133
|
+
i += 2;
|
|
134
|
+
while (i < src.length && /[0-9A-Fa-f_]/.test(src[i]))
|
|
135
|
+
i++;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
while (i < src.length && /[0-9_]/.test(src[i]))
|
|
139
|
+
i++;
|
|
140
|
+
if (src[i] === "." && src[i + 1] !== ".") {
|
|
141
|
+
i++;
|
|
142
|
+
while (i < src.length && /[0-9_]/.test(src[i]))
|
|
143
|
+
i++;
|
|
144
|
+
}
|
|
145
|
+
if (/[eE]/.test(src[i] ?? "") && /^([0-9]|[+-][0-9])/.test(src.slice(i + 1, i + 3))) {
|
|
146
|
+
i += 2;
|
|
147
|
+
while (i < src.length && /[0-9]/.test(src[i]))
|
|
148
|
+
i++;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
push("number", start, i);
|
|
152
|
+
}
|
|
153
|
+
else if (rules.numbers !== "sql" && /[0-9]/.test(c)) {
|
|
154
|
+
// `1.5`, `1e3`, `0x1F`; and a name that starts with a digit (`00662_events`), which the parser accepts as a name.
|
|
155
|
+
while (i < src.length && /[0-9a-zA-Z_.]/.test(src[i])) {
|
|
156
|
+
if (src[i] === "." && !/[0-9]/.test(src[i + 1] ?? ""))
|
|
157
|
+
break;
|
|
158
|
+
i++;
|
|
159
|
+
}
|
|
160
|
+
push("number", start, i);
|
|
161
|
+
}
|
|
162
|
+
else if (identStart.test(c)) {
|
|
163
|
+
while (i < src.length && identPart.test(src[i]))
|
|
164
|
+
i++;
|
|
165
|
+
push("ident", start, i);
|
|
166
|
+
}
|
|
167
|
+
else if (rules.punct.includes(c)) {
|
|
168
|
+
i++;
|
|
169
|
+
push("punct", start, i);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
while (i < src.length &&
|
|
173
|
+
rules.opChars.test(src[i]) &&
|
|
174
|
+
!(src[i] === "-" && src[i + 1] === "-") &&
|
|
175
|
+
!(rules.nestedComments && src[i] === "/" && src[i + 1] === "*"))
|
|
176
|
+
i++;
|
|
177
|
+
if (i === start)
|
|
178
|
+
i++;
|
|
179
|
+
if (rules.operatorsEndWithoutSign && i - start > 1 && !/[~!@#%^&|`?]/.test(src.slice(start, i))) {
|
|
180
|
+
while (i - start > 1 && /[+-]/.test(src[i - 1]))
|
|
181
|
+
i--;
|
|
182
|
+
}
|
|
183
|
+
push("op", start, i);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return tokens;
|
|
187
|
+
}
|
|
188
|
+
/** Tokenize a template's parts, with a `ref` token for each interpolation between them. */
|
|
189
|
+
export function tokenize(parts, rules) {
|
|
190
|
+
const tokens = [];
|
|
191
|
+
parts.forEach((src, part) => {
|
|
192
|
+
tokens.push(...tokenizeText(src, part, rules));
|
|
193
|
+
if (part < parts.length - 1)
|
|
194
|
+
tokens.push({ kind: "ref", text: "", part, start: 0, end: 0 });
|
|
195
|
+
});
|
|
196
|
+
return tokens;
|
|
197
|
+
}
|
|
198
|
+
export const isTrivia = (t) => t.kind === "ws" || t.kind === "comment";
|
|
199
|
+
/** Every token's text back in order, each `ref` written by `ref`. */
|
|
200
|
+
export function untokenize(tokens, ref) {
|
|
201
|
+
return tokens.map((t) => (t.kind === "ref" ? ref(t.part) : t.text)).join("");
|
|
202
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed columns (#3751): the types that carry a relation's columns from the
|
|
3
|
+
* declarations `chant sql types` generates to `tsc`, in every dialect.
|
|
4
|
+
*
|
|
5
|
+
* Each dialect declares an empty `SqlSchema` interface keyed by qualified
|
|
6
|
+
* name, and the generated `sql-types.d.ts` augments it. A tag given the name
|
|
7
|
+
* as its type argument (`` table<"shop.orders">`CREATE TABLE ...` ``) returns
|
|
8
|
+
* a relation whose `columns` are that entry's, each a {@link Column} branded
|
|
9
|
+
* with its SQL type. The tag's interpolations are typed by the entry's
|
|
10
|
+
* `slots`: the generator writes, per `${}` position, the column a foreign key
|
|
11
|
+
* or a sort key there must be ({@link KeyColumn}, {@link NotNullColumn}).
|
|
12
|
+
*
|
|
13
|
+
* A relation with no type argument, or one the generated file does not list
|
|
14
|
+
* yet, keeps unchecked columns: any name type-checks and every slot takes
|
|
15
|
+
* them, so a project adopts the type argument one declaration at a time.
|
|
16
|
+
*
|
|
17
|
+
* The runtime half is the record of those slots: a tag notes which `${}`
|
|
18
|
+
* feeds a foreign key's column list or a sort key, off the serialized output
|
|
19
|
+
* as `setInterpolationFields` keeps its record, for the generator to read.
|
|
20
|
+
*/
|
|
21
|
+
const TYPED_SLOTS = Symbol.for("chant.sql.typedSlots");
|
|
22
|
+
/** Record the typed slots on an entity: non-enumerable and symbol-keyed, so no serializer sees it. */
|
|
23
|
+
export function setTypedSlots(entity, slots) {
|
|
24
|
+
if (slots.length === 0 || !Object.isExtensible(entity))
|
|
25
|
+
return;
|
|
26
|
+
Object.defineProperty(entity, TYPED_SLOTS, { value: Object.freeze([...slots]), enumerable: false, writable: false, configurable: true });
|
|
27
|
+
}
|
|
28
|
+
/** What {@link setTypedSlots} recorded on an entity; empty when nothing was. */
|
|
29
|
+
export function getTypedSlots(entity) {
|
|
30
|
+
return entity[TYPED_SLOTS] ?? [];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A relation's key in `SqlSchema`: `schema.name` (Postgres) or
|
|
34
|
+
* `database.name` (ClickHouse), unquoted and with Postgres's folding
|
|
35
|
+
* applied, or the name alone when the DDL does not qualify it.
|
|
36
|
+
*/
|
|
37
|
+
export function relationKey(props) {
|
|
38
|
+
const qualifier = props.schema ?? props.database;
|
|
39
|
+
return qualifier ? `${qualifier}.${props.name}` : props.name;
|
|
40
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Objects an import finds that no declaration can hold (#3911).
|
|
3
|
+
*
|
|
4
|
+
* Import reads every kind of object the server has, not only the kinds chant
|
|
5
|
+
* declares, so none is left out without a word. An object of a kind the
|
|
6
|
+
* coverage table marks refused (`../coverage.ts`) is collected here, and the
|
|
7
|
+
* import fails naming every one of them at once: kind, schema and name.
|
|
8
|
+
* `--skip-undeclarable` imports the rest and lists them as warnings instead,
|
|
9
|
+
* so an adopter is not stuck behind an aggregate they mean to keep by hand.
|
|
10
|
+
*/
|
|
11
|
+
import { kindRow } from "../coverage.js";
|
|
12
|
+
/** `aggregate app.total(integer)`. */
|
|
13
|
+
export const describeUndeclarable = (o) => `${o.kind} ${o.schema ? `${o.schema}.` : ""}${o.name}`;
|
|
14
|
+
const DOCS = "https://intentius.io/chant/lexicons/sql/object-coverage/";
|
|
15
|
+
/** The import's refusal: every undeclarable object, then why for each kind among them. */
|
|
16
|
+
export class UndeclarableObjectsError extends Error {
|
|
17
|
+
constructor(dialect, objects) {
|
|
18
|
+
const kinds = [...new Set(objects.map((o) => o.kind))];
|
|
19
|
+
const n = objects.length;
|
|
20
|
+
super([
|
|
21
|
+
`the ${dialect === "postgres" ? "Postgres" : "ClickHouse"} server has ${n === 1 ? "an object" : `${n} objects`} of a kind chant cannot declare, so the import would leave ${n === 1 ? "it" : "them"} out:`,
|
|
22
|
+
...objects.map((o) => ` - ${describeUndeclarable(o)}`),
|
|
23
|
+
"Why, by kind:",
|
|
24
|
+
...kinds.map((k) => ` - ${k}: ${kindRow(dialect, k).reason ?? "not declared"}`),
|
|
25
|
+
`To import everything else and list ${n === 1 ? "it" : "these"} as warnings, run the import again with --skip-undeclarable. Which kinds chant declares: ${DOCS}`,
|
|
26
|
+
].join("\n"));
|
|
27
|
+
this.dialect = dialect;
|
|
28
|
+
this.objects = objects;
|
|
29
|
+
this.name = "UndeclarableObjectsError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** One warning per object, for `--skip-undeclarable`. */
|
|
33
|
+
export function undeclarableWarnings(dialect, objects) {
|
|
34
|
+
return objects.map((o) => `${describeUndeclarable(o)} is left out (--skip-undeclarable): ${kindRow(dialect, o.kind).reason ?? "chant cannot declare it"}`);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Refuse the import when it found undeclarable objects, or, with `skip`,
|
|
38
|
+
* add a warning per object to `warnings`. Nothing found is nothing to do.
|
|
39
|
+
*/
|
|
40
|
+
export function refuseUndeclarable(dialect, objects, skip, warnings) {
|
|
41
|
+
if (objects.length === 0)
|
|
42
|
+
return;
|
|
43
|
+
if (!skip)
|
|
44
|
+
throw new UndeclarableObjectsError(dialect, objects);
|
|
45
|
+
warnings.push(...undeclarableWarnings(dialect, objects));
|
|
46
|
+
}
|
package/dist/coverage.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coverage, two ways.
|
|
3
|
+
*
|
|
4
|
+
* Which kinds of object each dialect declares (#3911): every `CREATE` command
|
|
5
|
+
* a pinned server has (`./coverage/<dialect>-commands.json`, written by
|
|
6
|
+
* `scripts/sql-create-commands.ts` from the server's own sources) has a row
|
|
7
|
+
* in `./coverage/<dialect>.json` marking it declared, refused with a reason,
|
|
8
|
+
* or out of scope. A command with several forms that differ in what chant can
|
|
9
|
+
* hold (`CREATE TYPE ... AS ENUM` and `CREATE TYPE ... AS RANGE`) has a row
|
|
10
|
+
* per form. Import refuses an object of a refused kind by that row's `kind`
|
|
11
|
+
* (`./core/undeclarable.ts`), and the docs page is rendered from the rows
|
|
12
|
+
* (`./codegen/coverage-page.ts`). To declare a kind, flip its row to
|
|
13
|
+
* `declared` with the `entity` that holds it, and drop the reason.
|
|
14
|
+
*
|
|
15
|
+
* How much of the pinned ClickHouse catalog the dialect types beyond its name.
|
|
16
|
+
*
|
|
17
|
+
* Every engine, type family, codec, skip index type and setting at the pin is
|
|
18
|
+
* generated as a name, so name coverage is total by construction. What varies
|
|
19
|
+
* is how far the overlays go: an engine whose arguments have kinds can be
|
|
20
|
+
* checked, one whose arguments are `unknown` can only be named. This reports
|
|
21
|
+
* that, per section, from the generated tables.
|
|
22
|
+
*/
|
|
23
|
+
import { CODECS, SKIP_INDEX_TYPES, TABLE_ENGINES, CLICKHOUSE_VERSION } from "./generated/clickhouse.js";
|
|
24
|
+
import { POSTGRES_PINS } from "./spec/postgres-pin.js";
|
|
25
|
+
import postgresKinds from "./coverage/postgres.json" with { type: "json" };
|
|
26
|
+
import clickhouseKinds from "./coverage/clickhouse.json" with { type: "json" };
|
|
27
|
+
import postgresCommands from "./coverage/postgres-commands.json" with { type: "json" };
|
|
28
|
+
import clickhouseCommands from "./coverage/clickhouse-commands.json" with { type: "json" };
|
|
29
|
+
/** Each dialect's coverage table. */
|
|
30
|
+
export const KIND_COVERAGE = {
|
|
31
|
+
postgres: postgresKinds.kinds,
|
|
32
|
+
clickhouse: clickhouseKinds.kinds,
|
|
33
|
+
};
|
|
34
|
+
/** The `CREATE` commands each pinned server has: per Postgres major, and the pinned ClickHouse release. */
|
|
35
|
+
export const PINNED_COMMANDS = {
|
|
36
|
+
postgres: Object.values(postgresCommands.majors),
|
|
37
|
+
clickhouse: [{ version: clickhouseCommands.version, commands: clickhouseCommands.commands }],
|
|
38
|
+
};
|
|
39
|
+
/** A dialect's row for `kind`; throws for a kind the table does not have, so a probe cannot name one that is not there. */
|
|
40
|
+
export function kindRow(dialect, kind) {
|
|
41
|
+
const row = KIND_COVERAGE[dialect].find((r) => r.kind === kind);
|
|
42
|
+
if (!row)
|
|
43
|
+
throw new Error(`no ${dialect} coverage row for the kind "${kind}" (src/coverage/${dialect}.json)`);
|
|
44
|
+
return row;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* What is wrong with a dialect's coverage table, against its pinned servers'
|
|
48
|
+
* commands: a command a pinned server has and no row covers, a row for a
|
|
49
|
+
* command no pinned server has, a kind named twice, a declared row with no
|
|
50
|
+
* entity, a refused or out-of-scope row with no reason. Empty when complete.
|
|
51
|
+
*/
|
|
52
|
+
export function kindCoverageProblems(dialect, opts = {}) {
|
|
53
|
+
const rows = opts.rows ?? KIND_COVERAGE[dialect];
|
|
54
|
+
const pins = opts.pins ?? PINNED_COMMANDS[dialect];
|
|
55
|
+
const problems = [];
|
|
56
|
+
const covered = new Set(rows.map((r) => r.command));
|
|
57
|
+
const listed = new Set(pins.flatMap((p) => p.commands));
|
|
58
|
+
for (const p of pins) {
|
|
59
|
+
for (const c of p.commands)
|
|
60
|
+
if (!covered.has(c))
|
|
61
|
+
problems.push(`${dialect} ${p.version} has ${c}, and no row in src/coverage/${dialect}.json covers it`);
|
|
62
|
+
}
|
|
63
|
+
for (const c of covered)
|
|
64
|
+
if (!listed.has(c))
|
|
65
|
+
problems.push(`src/coverage/${dialect}.json has a row for ${c}, which no pinned ${dialect} server has`);
|
|
66
|
+
const seen = new Set();
|
|
67
|
+
for (const r of rows) {
|
|
68
|
+
if (seen.has(r.kind))
|
|
69
|
+
problems.push(`the kind "${r.kind}" has two rows`);
|
|
70
|
+
seen.add(r.kind);
|
|
71
|
+
if (!["declared", "refused", "out-of-scope"].includes(r.status))
|
|
72
|
+
problems.push(`${r.kind}: status "${r.status}" is not declared, refused or out-of-scope`);
|
|
73
|
+
if (r.status === "declared" && !r.entity)
|
|
74
|
+
problems.push(`${r.kind} is declared, and names no entity type`);
|
|
75
|
+
if (r.status !== "declared" && !r.reason)
|
|
76
|
+
problems.push(`${r.kind} is ${r.status}, and gives no reason`);
|
|
77
|
+
}
|
|
78
|
+
return problems;
|
|
79
|
+
}
|
|
80
|
+
/** A statement's words, upper case, with comments and string literals (quoted and dollar-quoted) taken out. */
|
|
81
|
+
function bareStatement(sql) {
|
|
82
|
+
return sql
|
|
83
|
+
.replace(/--[^\n]*/g, " ")
|
|
84
|
+
.replace(/\/\*[\s\S]*?\*\//g, " ")
|
|
85
|
+
.replace(/\$([A-Za-z_][A-Za-z0-9_]*)?\$[\s\S]*?\$\1\$/g, " '' ")
|
|
86
|
+
.replace(/'(?:[^'\\]|''|\\.)*'/g, " '' ")
|
|
87
|
+
.replace(/\s+/g, " ")
|
|
88
|
+
.trim()
|
|
89
|
+
.toUpperCase();
|
|
90
|
+
}
|
|
91
|
+
/** Words a `CREATE` can carry before the command's own: `CREATE OR REPLACE TRUSTED PROCEDURAL LANGUAGE`, `CREATE DEFAULT CONVERSION`. */
|
|
92
|
+
const MODIFIERS = new Set(["OR", "REPLACE", "TRUSTED", "PROCEDURAL", "DEFAULT", "UNIQUE", "UNLOGGED", "RECURSIVE", "CONSTRAINT", "GLOBAL", "LOCAL", "TEMP", "TEMPORARY"]);
|
|
93
|
+
/**
|
|
94
|
+
* The coverage row a `CREATE` statement is of, by its command (the longest
|
|
95
|
+
* that its words start with) and, among the command's forms, the first whose
|
|
96
|
+
* `match` it matches; undefined for a statement no row covers.
|
|
97
|
+
*/
|
|
98
|
+
export function statementKind(dialect, sql) {
|
|
99
|
+
const bare = bareStatement(sql);
|
|
100
|
+
const words = bare.split(/[\s(]+/);
|
|
101
|
+
if (words[0] !== "CREATE")
|
|
102
|
+
return undefined;
|
|
103
|
+
const rest = words.slice(1).filter((w, i, all) => !MODIFIERS.has(w) || all.slice(0, i).some((x) => !MODIFIERS.has(x)));
|
|
104
|
+
const rows = KIND_COVERAGE[dialect];
|
|
105
|
+
let command;
|
|
106
|
+
for (const r of rows) {
|
|
107
|
+
const cmd = r.command.split(" ").slice(1);
|
|
108
|
+
if (cmd.every((w, i) => rest[i] === w) && (!command || cmd.length > command.split(" ").length - 1))
|
|
109
|
+
command = r.command;
|
|
110
|
+
}
|
|
111
|
+
if (!command)
|
|
112
|
+
return undefined;
|
|
113
|
+
const forms = rows.filter((r) => r.command === command);
|
|
114
|
+
return forms.find((r) => r.match !== undefined && new RegExp(r.match, "i").test(bare)) ?? forms.find((r) => r.match === undefined);
|
|
115
|
+
}
|
|
116
|
+
/** Whether the commands file was written for the pinned servers: a list for each pinned version, and for no other. */
|
|
117
|
+
export function pinnedCommandsCurrent(dialect) {
|
|
118
|
+
const have = PINNED_COMMANDS[dialect].map((p) => p.version);
|
|
119
|
+
const want = dialect === "postgres" ? POSTGRES_PINS.map((p) => p.version) : [CLICKHOUSE_VERSION];
|
|
120
|
+
const run = `run npx tsx scripts/sql-create-commands.ts ${dialect}`;
|
|
121
|
+
return [
|
|
122
|
+
...want.filter((v) => !have.includes(v)).map((v) => `${dialect} ${v} is pinned, and src/coverage/${dialect}-commands.json has no command list for it: ${run}`),
|
|
123
|
+
...have.filter((v) => !want.includes(v)).map((v) => `src/coverage/${dialect}-commands.json lists ${dialect} ${v}, which is no longer pinned: ${run}`),
|
|
124
|
+
];
|
|
125
|
+
}
|
|
126
|
+
export function analyze() {
|
|
127
|
+
const engines = Object.entries(TABLE_ENGINES).filter(([, e]) => e.args !== undefined);
|
|
128
|
+
const mergeTree = engines.filter(([, e]) => e.mergeTree);
|
|
129
|
+
const codecs = Object.entries(CODECS).filter(([, c]) => c.role !== "internal");
|
|
130
|
+
const indexes = Object.entries(SKIP_INDEX_TYPES);
|
|
131
|
+
const section = (name, rows) => ({
|
|
132
|
+
section: name,
|
|
133
|
+
total: rows.length,
|
|
134
|
+
typed: rows.filter(([, ok]) => ok).length,
|
|
135
|
+
untyped: rows.filter(([, ok]) => !ok).map(([n]) => n),
|
|
136
|
+
});
|
|
137
|
+
return {
|
|
138
|
+
version: CLICKHOUSE_VERSION,
|
|
139
|
+
sections: [
|
|
140
|
+
section("MergeTree engine arguments", mergeTree.map(([n, e]) => [n, e.typed])),
|
|
141
|
+
section("all engine arguments", engines.map(([n, e]) => [n, e.typed])),
|
|
142
|
+
section("codec parameters", codecs.map(([n, c]) => [n, c.parameters !== "unknown"])),
|
|
143
|
+
section("skip index parameters", indexes.map(([n, i]) => [n, (i.args ?? []).every((a) => a.kind !== "unknown")])),
|
|
144
|
+
],
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
export function printCoverage(report, opts = {}) {
|
|
148
|
+
console.error(`ClickHouse ${report.version}`);
|
|
149
|
+
for (const s of report.sections) {
|
|
150
|
+
const pct = s.total === 0 ? 100 : Math.round((s.typed / s.total) * 100);
|
|
151
|
+
console.error(` ${s.section}: ${s.typed}/${s.total} typed (${pct}%)`);
|
|
152
|
+
if (opts.verbose && s.untyped.length > 0)
|
|
153
|
+
console.error(` untyped: ${s.untyped.join(", ")}`);
|
|
154
|
+
}
|
|
155
|
+
if (opts.minOverall !== undefined) {
|
|
156
|
+
const mergeTree = report.sections[0];
|
|
157
|
+
const pct = mergeTree.total === 0 ? 100 : (mergeTree.typed / mergeTree.total) * 100;
|
|
158
|
+
if (pct < opts.minOverall)
|
|
159
|
+
throw new Error(`MergeTree argument coverage ${pct.toFixed(0)}% is below ${opts.minOverall}%`);
|
|
160
|
+
}
|
|
161
|
+
}
|
package/dist/detect.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a parsed sql document looks like. Kept free of the plugin and the
|
|
3
|
+
* TypeScript compiler so it bundles for edge runtimes, like the other
|
|
4
|
+
* lexicons' `detect` modules.
|
|
5
|
+
*/
|
|
6
|
+
function isObject(v) {
|
|
7
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
8
|
+
}
|
|
9
|
+
/** The entity type prefix each dialect's build output carries. */
|
|
10
|
+
const TYPE_PREFIX = { clickhouse: "ClickHouse::", postgres: "Postgres::" };
|
|
11
|
+
/**
|
|
12
|
+
* The sql lexicon's build output (`chant build --lexicon sql`): a JSON
|
|
13
|
+
* document with a `dialect` (`clickhouse` or `postgres`), an `applyOrder` list
|
|
14
|
+
* and an `objects` list whose entries carry a type of that dialect
|
|
15
|
+
* (`ClickHouse::Table`, `Postgres::Table`). This is how a project that already
|
|
16
|
+
* uses the lexicon is recognized from its built output.
|
|
17
|
+
*/
|
|
18
|
+
export function looksLikeSqlBuild(data) {
|
|
19
|
+
if (!isObject(data))
|
|
20
|
+
return false;
|
|
21
|
+
if (typeof data.dialect !== "string")
|
|
22
|
+
return false;
|
|
23
|
+
const prefix = TYPE_PREFIX[data.dialect];
|
|
24
|
+
if (!prefix)
|
|
25
|
+
return false;
|
|
26
|
+
if (!Array.isArray(data.applyOrder) || !Array.isArray(data.objects))
|
|
27
|
+
return false;
|
|
28
|
+
return data.objects.length > 0 && data.objects.every((o) => isObject(o) && typeof o.type === "string" && o.type.startsWith(prefix));
|
|
29
|
+
}
|
|
30
|
+
export function detectTemplate(data) {
|
|
31
|
+
return looksLikeSqlBuild(data);
|
|
32
|
+
}
|
package/dist/dialects.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The database dialects this lexicon declares, each at its own subpath
|
|
3
|
+
* (`@intentius/chant-lexicon-sql/<dialect>`).
|
|
4
|
+
*
|
|
5
|
+
* A dialect is a database's own DDL, types and change rules, generated from
|
|
6
|
+
* that database's catalog at a pinned version. There is no neutral schema
|
|
7
|
+
* model between them: a Postgres table and a ClickHouse table are different
|
|
8
|
+
* declarations, because the databases differ in ways a schema tool has to
|
|
9
|
+
* respect (ClickHouse has no foreign keys and rebuilds a table to change its
|
|
10
|
+
* sort key; Postgres has transactional DDL).
|
|
11
|
+
*/
|
|
12
|
+
export const SQL_DIALECTS = ["clickhouse", "postgres"];
|
|
13
|
+
/**
|
|
14
|
+
* Dialects being built, not yet declarable. `sql.dialect` refuses them with a
|
|
15
|
+
* message saying so, rather than as an unknown name. None today: Postgres
|
|
16
|
+
* became declarable with #3279.
|
|
17
|
+
*/
|
|
18
|
+
export const PLANNED_SQL_DIALECTS = [];
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `.sql` files in a project's source directory, read into declarations
|
|
3
|
+
* at build time (the plugin's `buildRoots` hook): each file's objects join
|
|
4
|
+
* the tagged templates' as if an import had written them, with the
|
|
5
|
+
* references between the two as interpolations (`../sql-files.ts`).
|
|
6
|
+
*
|
|
7
|
+
* The files are found by the walk source discovery uses, so `dist`,
|
|
8
|
+
* git-ignored paths, child projects and the project's `exclude` globs are
|
|
9
|
+
* left out. A file whose first lines carry `-- chant-discovery-skip` (or the
|
|
10
|
+
* `/* chant-discovery-skip *\/` form) is not read, for SQL that is not schema:
|
|
11
|
+
* seed data, a migration, a query. Nor is a file under a `migrations`
|
|
12
|
+
* directory (#3713): versioned migrations and an ORM's generated migrations
|
|
13
|
+
* are `ALTER`s and repeated `CREATE`s of objects declared elsewhere, and a
|
|
14
|
+
* build of the project root, as `chant lifecycle diff` and an ApplyOp's plan
|
|
15
|
+
* run with no `sourceDir`, walks into them.
|
|
16
|
+
*
|
|
17
|
+
* The dialect is the declarations': a project whose templates are Postgres
|
|
18
|
+
* reads its files as Postgres. With no templates it is `sql.dialect`, and
|
|
19
|
+
* ClickHouse when that is unset, as for the rest of the lexicon.
|
|
20
|
+
*/
|
|
21
|
+
import { readFileSync } from "node:fs";
|
|
22
|
+
import { relative, resolve, sep } from "node:path";
|
|
23
|
+
import { findSourceFiles, hasDiscoveryMarkerSync } from "@intentius/chant/discovery/files";
|
|
24
|
+
import { readHead } from "@intentius/chant/discovery/walk";
|
|
25
|
+
import { SQL_DIALECTS } from "../dialects.js";
|
|
26
|
+
import { isPostgresObject } from "../postgres/entities.js";
|
|
27
|
+
import { isClickHouseObject } from "../clickhouse/entities.js";
|
|
28
|
+
import { sqlFileEntities } from "../sql-files.js";
|
|
29
|
+
/** `-- chant-discovery-skip` on a line of its own, near the top. */
|
|
30
|
+
const SQL_SKIP_MARKER = /^[ \t]*--[ \t]*chant-discovery-skip(?![\w-])/m;
|
|
31
|
+
/** Whether a file is a `.sql` file to read: the name, and no skip or generated marker. */
|
|
32
|
+
export function isSqlSourceFile(name, full) {
|
|
33
|
+
if (!name.endsWith(".sql"))
|
|
34
|
+
return false;
|
|
35
|
+
if (hasDiscoveryMarkerSync(full))
|
|
36
|
+
return false;
|
|
37
|
+
return !SQL_SKIP_MARKER.test(readHead(full, 1024));
|
|
38
|
+
}
|
|
39
|
+
/** A directory whose `.sql` files are migrations, never declarations. */
|
|
40
|
+
export const MIGRATIONS_DIR = "migrations";
|
|
41
|
+
/** Whether `file` sits under a {@link MIGRATIONS_DIR} directory below `sourceDir`. */
|
|
42
|
+
export function inMigrationsDir(sourceDir, file) {
|
|
43
|
+
return relative(sourceDir, file).split(sep).slice(0, -1).includes(MIGRATIONS_DIR);
|
|
44
|
+
}
|
|
45
|
+
/** The dialect the project's `.sql` files are read as. */
|
|
46
|
+
export function filesDialect(entities, config) {
|
|
47
|
+
for (const e of entities?.values() ?? []) {
|
|
48
|
+
if (isPostgresObject(e))
|
|
49
|
+
return "postgres";
|
|
50
|
+
if (isClickHouseObject(e))
|
|
51
|
+
return "clickhouse";
|
|
52
|
+
}
|
|
53
|
+
const dialect = config.sql?.dialect;
|
|
54
|
+
if (typeof dialect === "string" && SQL_DIALECTS.includes(dialect))
|
|
55
|
+
return dialect;
|
|
56
|
+
if (Array.isArray(dialect)) {
|
|
57
|
+
if (dialect.length === 1 && SQL_DIALECTS.includes(String(dialect[0])))
|
|
58
|
+
return dialect[0];
|
|
59
|
+
throw new Error(`sql: the project's .sql files hold no declaration that says their dialect, and sql.dialect lists ${dialect.join(", ")}; set sql.dialect to one, or declare one object with a tagged template`);
|
|
60
|
+
}
|
|
61
|
+
return "clickhouse";
|
|
62
|
+
}
|
|
63
|
+
/** The `buildRoots` contribution: every `.sql` file's declarations, or nothing when the source directory holds none. */
|
|
64
|
+
export async function sqlFilesBuildRoot(ctx) {
|
|
65
|
+
const sourceDir = ctx.sourceDir ?? resolve(ctx.projectRoot, typeof ctx.config.sourceDir === "string" ? ctx.config.sourceDir : ".");
|
|
66
|
+
const files = (await findSourceFiles(sourceDir, isSqlSourceFile)).filter((f) => !inMigrationsDir(sourceDir, f)).sort();
|
|
67
|
+
if (files.length === 0)
|
|
68
|
+
return { entities: new Map() };
|
|
69
|
+
const dialect = filesDialect(ctx.entities, ctx.config);
|
|
70
|
+
const sources = files.map((f) => ({ origin: relative(ctx.projectRoot, f) || f, ddl: readFileSync(f, "utf8") }));
|
|
71
|
+
return { entities: sqlFileEntities(dialect, sources, { known: ctx.entities ?? new Map() }) };
|
|
72
|
+
}
|