@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,674 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A hand-written recursive-descent parser for the ClickHouse statements the
|
|
3
|
+
* dialect declares: `CREATE DATABASE`, `CREATE TABLE`, `CREATE VIEW`,
|
|
4
|
+
* `CREATE MATERIALIZED VIEW` (chant #3196), `CREATE DICTIONARY` and
|
|
5
|
+
* `CREATE FUNCTION` (#3682).
|
|
6
|
+
*
|
|
7
|
+
* Statement structure is parsed; expressions are not. An expression (a
|
|
8
|
+
* default, a codec, a sort key, a TTL, a view's SELECT) is kept as a span of
|
|
9
|
+
* tokens: its source text, verbatim, and the interpolations inside it. That is
|
|
10
|
+
* what entities, references, dependency order and lineage need. Comparing
|
|
11
|
+
* `INTERVAL 1 DAY` with the server's `toIntervalDay(1)` is normalization's job,
|
|
12
|
+
* which asks the server.
|
|
13
|
+
*
|
|
14
|
+
* An interpolation (`ref` token) is accepted wherever a name, a type or an
|
|
15
|
+
* expression goes. At build time the tag has already spliced plain strings into
|
|
16
|
+
* the token list as SQL text, so a `ref` left here is an entity or a column
|
|
17
|
+
* reference. Lint parses the source with every interpolation still a `ref`,
|
|
18
|
+
* because it cannot know the values; accepting them keeps it from reporting an
|
|
19
|
+
* error the build would not have.
|
|
20
|
+
*/
|
|
21
|
+
import { kw, SqlCursor, unquoteWith } from "../core/cursor.js";
|
|
22
|
+
/** A token that can stand where a name goes. */
|
|
23
|
+
const isNameToken = (t) => t !== undefined &&
|
|
24
|
+
(t.kind === "ident" || t.kind === "qident" || t.kind === "ref" || (t.kind === "number" && /[A-Za-z_]/.test(t.text)));
|
|
25
|
+
/** A name with its `` ` `` or `"` quotes taken off. */
|
|
26
|
+
export const unquote = (text) => unquoteWith(text, '`"');
|
|
27
|
+
const countOf = (text, c) => text.split(c).length - 1;
|
|
28
|
+
/**
|
|
29
|
+
* Words an expression in a DDL clause holds between two operands, or right
|
|
30
|
+
* after one: operators, `CASE`, `INTERVAL` and its units, a TTL's actions.
|
|
31
|
+
* Any other key word right after an operand starts a new clause (#3872).
|
|
32
|
+
*/
|
|
33
|
+
const EXPRESSION_WORDS = new Set(("AND OR NOT IN IS NULL LIKE ILIKE BETWEEN ASC DESC NULLS FIRST LAST COLLATE CASE WHEN THEN ELSE END AS INTERVAL GLOBAL ANY ALL " +
|
|
34
|
+
"DISTINCT FROM TO IF EXISTS DISK VOLUME DELETE RECOMPRESS CODEC WHERE GROUP BY SET WITH FILL STEP TRUE FALSE " +
|
|
35
|
+
"NANOSECOND MICROSECOND MILLISECOND SECOND MINUTE HOUR DAY WEEK MONTH QUARTER YEAR").split(" "));
|
|
36
|
+
/** The units an interval is counted in, singular and plural: what ends a refresh schedule before a column list. */
|
|
37
|
+
const INTERVAL_UNIT_WORDS = new Set(["NANOSECOND", "MICROSECOND", "MILLISECOND", "SECOND", "MINUTE", "HOUR", "DAY", "WEEK", "MONTH", "QUARTER", "YEAR"].flatMap((u) => [u, `${u}S`]));
|
|
38
|
+
/**
|
|
39
|
+
* The `CREATE` kinds the pinned server has (26.8: `system.*` and its parser's
|
|
40
|
+
* own list) that chant does not declare, each with why (#3911). A statement
|
|
41
|
+
* of one is refused with that reason, by name, never as a parse error.
|
|
42
|
+
*/
|
|
43
|
+
export const REFUSED_CREATE_KINDS = [
|
|
44
|
+
{ words: ["LIVE", "VIEW"], kind: "LIVE VIEW", reason: "the pinned server no longer has live views (removed in 25.x); a refreshable materialized view (REFRESH EVERY ...) is the replacement" },
|
|
45
|
+
{
|
|
46
|
+
words: ["WINDOW", "VIEW"],
|
|
47
|
+
kind: "WINDOW VIEW",
|
|
48
|
+
reason: "a window view is experimental and the pinned server refuses it under the default query analyzer (allow_experimental_analyzer), so it cannot be applied as declared",
|
|
49
|
+
},
|
|
50
|
+
{ words: ["TEMPORARY"], kind: "TEMPORARY TABLE", reason: "a temporary table lives for one session and is never in the catalog a plan reads" },
|
|
51
|
+
{
|
|
52
|
+
words: ["NAMED", "COLLECTION"],
|
|
53
|
+
kind: "NAMED COLLECTION",
|
|
54
|
+
reason: "a named collection holds credentials (a password, a secret key), which are the environment's and never in a build; the server hides its values, so a plan could not compare them either. Create it from the server's configuration or by hand",
|
|
55
|
+
},
|
|
56
|
+
{ words: ["MASKING", "POLICY"], kind: "MASKING POLICY", reason: "masking policies are available only in ClickHouse Cloud, not on the pinned server" },
|
|
57
|
+
{ words: ["WORKLOAD"], kind: "WORKLOAD", reason: "a workload is server-level scheduling, configured with the server, not a schema object" },
|
|
58
|
+
{ words: ["RESOURCE"], kind: "RESOURCE", reason: "a resource is a server-level scheduling resource (a disk's read or write), configured with the server, not a schema object" },
|
|
59
|
+
{ words: ["HANDLER"], kind: "HANDLER", reason: "an HTTP handler is server-level configuration, not a schema object" },
|
|
60
|
+
{
|
|
61
|
+
words: ["INDEX"],
|
|
62
|
+
kind: "INDEX",
|
|
63
|
+
reason: "a skip index belongs to its table: declare it in the table's column list (INDEX name expr TYPE type GRANULARITY n), and a plan adds or drops it with ALTER TABLE",
|
|
64
|
+
},
|
|
65
|
+
{ words: ["UNIQUE", "INDEX"], kind: "UNIQUE INDEX", reason: "the pinned server has no unique index on its table engines; declare a skip index in the table's column list" },
|
|
66
|
+
{ words: ["HYPOTHETICAL", "INDEX"], kind: "HYPOTHETICAL INDEX", reason: "a hypothetical index is a session's what-if for the query planner, never stored" },
|
|
67
|
+
];
|
|
68
|
+
const STORAGE_KEYWORDS = ["ENGINE", "ORDER", "PRIMARY", "PARTITION", "SAMPLE", "TTL", "SETTINGS", "COMMENT"];
|
|
69
|
+
/** A column's own clause words: an unknown one stops at the next of these (#3872). */
|
|
70
|
+
const COLUMN_WORDS = ["DEFAULT", "MATERIALIZED", "ALIAS", "EPHEMERAL", "NULL", "NOT", "COMMENT", "CODEC", "TTL", "STATISTICS", "SETTINGS", "PRIMARY"];
|
|
71
|
+
class Parser extends SqlCursor {
|
|
72
|
+
constructor() {
|
|
73
|
+
super(...arguments);
|
|
74
|
+
/** Reading a refreshable materialized view's schedule, which a column list ends. */
|
|
75
|
+
this.refreshing = false;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* `ORDER`, `PARTITION`, `SAMPLE` and `GROUP` stop only before `BY`, `PRIMARY`
|
|
79
|
+
* only before `KEY`. In a clause-aware expression, a clause the parser does
|
|
80
|
+
* not know stops it too.
|
|
81
|
+
*/
|
|
82
|
+
stopsAt(stop) {
|
|
83
|
+
// `REFRESH EVERY 1 HOUR (a UInt64) ENGINE ...`, as `SHOW CREATE` prints it: the column list follows the schedule.
|
|
84
|
+
if (this.refreshing && this.isPunct("(") && INTERVAL_UNIT_WORDS.has(this.peek(-1)?.text.toUpperCase() ?? ""))
|
|
85
|
+
return true;
|
|
86
|
+
if (!super.stopsAt(stop))
|
|
87
|
+
return this.startsUnknownClause();
|
|
88
|
+
const word = this.peek().text.toUpperCase();
|
|
89
|
+
const two = ["ORDER", "PARTITION", "SAMPLE", "GROUP"].includes(word) ? kw(this.peek(1), "BY") : true;
|
|
90
|
+
const key = word === "PRIMARY" ? kw(this.peek(1), "KEY") : true;
|
|
91
|
+
return two && key;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Whether a clause the parser does not know starts here, inside a
|
|
95
|
+
* clause-aware expression (#3872): a bare key word right after a complete
|
|
96
|
+
* operand (a name, a literal, a closing parenthesis), which no expression
|
|
97
|
+
* puts side by side unless the word is one of an expression's own.
|
|
98
|
+
*/
|
|
99
|
+
startsUnknownClause() {
|
|
100
|
+
if (this.clauseFrom === undefined || this.p <= this.clauseFrom)
|
|
101
|
+
return false;
|
|
102
|
+
const t = this.peek();
|
|
103
|
+
const prev = this.peek(-1);
|
|
104
|
+
if (t?.kind !== "ident" || !/^[A-Za-z_]{3,}$/.test(t.text) || EXPRESSION_WORDS.has(t.text.toUpperCase()))
|
|
105
|
+
return false;
|
|
106
|
+
if (prev === undefined)
|
|
107
|
+
return false;
|
|
108
|
+
// A query read into the expression (`ORDER BY id AS SELECT ...`, which the server takes as CREATE TABLE ... AS) is the query's, as it always was.
|
|
109
|
+
for (let n = this.clauseFrom; n < this.p; n++)
|
|
110
|
+
if (kw(this.tokens[this.sig[n]], "SELECT"))
|
|
111
|
+
return false;
|
|
112
|
+
return (prev.kind === "number" ||
|
|
113
|
+
prev.kind === "string" ||
|
|
114
|
+
prev.kind === "qident" ||
|
|
115
|
+
(prev.kind === "punct" && prev.text === ")") ||
|
|
116
|
+
(prev.kind === "ident" && !EXPRESSION_WORDS.has(prev.text.toUpperCase())));
|
|
117
|
+
}
|
|
118
|
+
/** An expression a clause the parser does not know can follow (#3872): it ends where that clause starts. */
|
|
119
|
+
clauseExpr(stop = [], stopAtComma = true) {
|
|
120
|
+
const saved = this.clauseFrom;
|
|
121
|
+
this.clauseFrom = this.p;
|
|
122
|
+
try {
|
|
123
|
+
return this.expr(stop, stopAtComma);
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
this.clauseFrom = saved;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** Array and map literals: `[` and `]` arrive inside operator runs. */
|
|
130
|
+
depthChange(t) {
|
|
131
|
+
if (t.kind === "op")
|
|
132
|
+
return countOf(t.text, "[") + countOf(t.text, "{") - countOf(t.text, "]") - countOf(t.text, "}");
|
|
133
|
+
return super.depthChange(t);
|
|
134
|
+
}
|
|
135
|
+
/** One name token. */
|
|
136
|
+
name(what = "a name") {
|
|
137
|
+
const t = this.next();
|
|
138
|
+
if (!isNameToken(t))
|
|
139
|
+
this.fail(`expected ${what}`, t);
|
|
140
|
+
const i = this.idx(-1);
|
|
141
|
+
return { text: t.kind === "ref" ? "" : unquote(t.text), span: { from: i, to: i + 1, refs: t.kind === "ref" ? [t.part] : [] } };
|
|
142
|
+
}
|
|
143
|
+
/** A name, optionally `db.`-qualified. */
|
|
144
|
+
qualifiedName() {
|
|
145
|
+
const from = this.idx();
|
|
146
|
+
const refs = [];
|
|
147
|
+
const one = () => {
|
|
148
|
+
const t = this.next();
|
|
149
|
+
if (!isNameToken(t))
|
|
150
|
+
this.fail("expected a name", t);
|
|
151
|
+
if (t.kind === "ref")
|
|
152
|
+
refs.push(t.part);
|
|
153
|
+
};
|
|
154
|
+
one();
|
|
155
|
+
while (this.isPunct(".")) {
|
|
156
|
+
this.p++;
|
|
157
|
+
one();
|
|
158
|
+
}
|
|
159
|
+
return this.span(from, refs);
|
|
160
|
+
}
|
|
161
|
+
/** `UInt64`, `Nullable(String)`, `DateTime64(3, 'UTC')`, `Tuple(a UInt8, b String)`. */
|
|
162
|
+
typeSpan() {
|
|
163
|
+
const from = this.idx();
|
|
164
|
+
const t = this.next();
|
|
165
|
+
if (t.kind !== "ident" && t.kind !== "ref")
|
|
166
|
+
this.fail("expected a type", t);
|
|
167
|
+
// `DOUBLE PRECISION`, `CHAR VARYING`, `CHAR LARGE OBJECT`: the SQL-compatible multi-word aliases.
|
|
168
|
+
while (kw(this.peek(), "PRECISION", "VARYING", "UNSIGNED", "SIGNED", "LARGE", "OBJECT"))
|
|
169
|
+
this.p++;
|
|
170
|
+
if (this.isPunct("("))
|
|
171
|
+
this.parenthesized();
|
|
172
|
+
// MySQL-compatible modifiers the server accepts and ignores, kept with the type as written.
|
|
173
|
+
for (;;) {
|
|
174
|
+
if (kw(this.peek(), "COLLATE") && isNameToken(this.peek(1)))
|
|
175
|
+
this.p += 2;
|
|
176
|
+
else if (kw(this.peek(), "AUTO_INCREMENT", "UNSIGNED", "SIGNED"))
|
|
177
|
+
this.p++;
|
|
178
|
+
else
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
return this.span(from, t.kind === "ref" ? [t.part] : []);
|
|
182
|
+
}
|
|
183
|
+
column(typed) {
|
|
184
|
+
const { text, span } = this.name("a column name");
|
|
185
|
+
const col = { name: text, nameSpan: span };
|
|
186
|
+
const STOP = ["COMMENT", "CODEC", "TTL", "STATISTICS", "SETTINGS", "PRIMARY"];
|
|
187
|
+
const at = this.peek();
|
|
188
|
+
const bare = at === undefined || (at.kind === "punct" && (at.text === "," || at.text === ")"));
|
|
189
|
+
if (!bare && !kw(at, "DEFAULT", "MATERIALIZED", "ALIAS", "EPHEMERAL", "NULL", "NOT", ...STOP)) {
|
|
190
|
+
col.type = this.typeSpan();
|
|
191
|
+
}
|
|
192
|
+
else if (typed && bare) {
|
|
193
|
+
this.fail("expected a column type");
|
|
194
|
+
}
|
|
195
|
+
for (;;) {
|
|
196
|
+
if (this.accept("NULL"))
|
|
197
|
+
col.nullable = true;
|
|
198
|
+
else if (kw(this.peek(), "NOT") && kw(this.peek(1), "NULL")) {
|
|
199
|
+
this.p += 2;
|
|
200
|
+
col.nullable = false;
|
|
201
|
+
}
|
|
202
|
+
else if (kw(this.peek(), "DEFAULT", "MATERIALIZED", "ALIAS")) {
|
|
203
|
+
const kind = this.next().text.toUpperCase();
|
|
204
|
+
col.default = { kind, expr: this.clauseExpr(STOP) };
|
|
205
|
+
}
|
|
206
|
+
else if (this.accept("EPHEMERAL")) {
|
|
207
|
+
const t = this.peek();
|
|
208
|
+
const hasExpr = t && !(t.kind === "punct" && (t.text === "," || t.text === ")")) && !kw(t, ...STOP);
|
|
209
|
+
col.default = { kind: "EPHEMERAL", expr: hasExpr ? this.clauseExpr(STOP) : undefined };
|
|
210
|
+
}
|
|
211
|
+
else if (this.accept("COMMENT"))
|
|
212
|
+
col.comment = this.clauseExpr(STOP);
|
|
213
|
+
else if (this.accept("CODEC"))
|
|
214
|
+
col.codec = this.parenthesized();
|
|
215
|
+
else if (this.accept("STATISTICS"))
|
|
216
|
+
col.statistics = this.parenthesized();
|
|
217
|
+
else if (this.accept("TTL"))
|
|
218
|
+
col.ttl = this.clauseExpr(STOP);
|
|
219
|
+
else if (this.accept("SETTINGS"))
|
|
220
|
+
col.settings = this.parenthesized();
|
|
221
|
+
else if (kw(this.peek(), "PRIMARY") && kw(this.peek(1), "KEY")) {
|
|
222
|
+
this.p += 2;
|
|
223
|
+
col.primaryKey = true;
|
|
224
|
+
}
|
|
225
|
+
else if (!this.unknownClause(COLUMN_WORDS, { inList: true, column: text || undefined }))
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
return col;
|
|
229
|
+
}
|
|
230
|
+
/** True when the element starting here is `INDEX name ... TYPE ...`, not a column named `index`. */
|
|
231
|
+
looksLikeIndex() {
|
|
232
|
+
if (!kw(this.peek(), "INDEX") || !isNameToken(this.peek(1)))
|
|
233
|
+
return false;
|
|
234
|
+
let depth = 0;
|
|
235
|
+
for (let n = 2;; n++) {
|
|
236
|
+
const t = this.peek(n);
|
|
237
|
+
if (t === undefined)
|
|
238
|
+
return false;
|
|
239
|
+
if (t.kind === "punct" && t.text === "(")
|
|
240
|
+
depth++;
|
|
241
|
+
else if (t.kind === "punct" && t.text === ")") {
|
|
242
|
+
if (depth === 0)
|
|
243
|
+
return false;
|
|
244
|
+
depth--;
|
|
245
|
+
}
|
|
246
|
+
else if (depth === 0 && t.kind === "punct" && t.text === ",")
|
|
247
|
+
return false;
|
|
248
|
+
else if (depth === 0 && kw(t, "TYPE"))
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
tableElements(node, typed) {
|
|
253
|
+
this.expectPunct("(");
|
|
254
|
+
for (;;) {
|
|
255
|
+
if (this.looksLikeIndex()) {
|
|
256
|
+
this.p++;
|
|
257
|
+
const name = this.name("an index name").text;
|
|
258
|
+
const expr = this.expr(["TYPE"]);
|
|
259
|
+
this.expect("TYPE");
|
|
260
|
+
const type = this.expr(["GRANULARITY"]);
|
|
261
|
+
const granularity = this.accept("GRANULARITY") ? this.expr() : undefined;
|
|
262
|
+
node.indexes.push({ name, expr, type, granularity });
|
|
263
|
+
}
|
|
264
|
+
else if (kw(this.peek(), "PROJECTION") && isNameToken(this.peek(1)) && this.isPunct("(", 2)) {
|
|
265
|
+
this.p++;
|
|
266
|
+
const name = this.name("a projection name").text;
|
|
267
|
+
node.projections.push({ name, body: this.parenthesized() });
|
|
268
|
+
}
|
|
269
|
+
else if (kw(this.peek(), "PROJECTION") && isNameToken(this.peek(1)) && kw(this.peek(2), "INDEX")) {
|
|
270
|
+
// `PROJECTION p INDEX expr TYPE basic`: a projection kept as an index.
|
|
271
|
+
this.p++;
|
|
272
|
+
const name = this.name("a projection name").text;
|
|
273
|
+
const from = this.idx();
|
|
274
|
+
this.p++;
|
|
275
|
+
const expr = this.expr(["TYPE"]);
|
|
276
|
+
this.expect("TYPE");
|
|
277
|
+
const type = this.expr();
|
|
278
|
+
node.projections.push({ name, body: { from, to: type.to, refs: [...expr.refs, ...type.refs] } });
|
|
279
|
+
}
|
|
280
|
+
else if (kw(this.peek(), "CONSTRAINT") && isNameToken(this.peek(1)) && kw(this.peek(2), "CHECK", "ASSUME")) {
|
|
281
|
+
this.p++;
|
|
282
|
+
const name = this.name("a constraint name").text;
|
|
283
|
+
const kind = this.next().text.toUpperCase();
|
|
284
|
+
node.constraints.push({ name, kind, expr: this.expr() });
|
|
285
|
+
}
|
|
286
|
+
else if (kw(this.peek(), "PRIMARY") && kw(this.peek(1), "KEY")) {
|
|
287
|
+
// `PRIMARY KEY (a, b)` inside the column list.
|
|
288
|
+
this.p += 2;
|
|
289
|
+
node.primaryKey = this.expr();
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
node.columns.push(this.column(typed));
|
|
293
|
+
}
|
|
294
|
+
// A trailing comma before the closing parenthesis is accepted, as the server accepts it.
|
|
295
|
+
if (this.acceptPunct(",")) {
|
|
296
|
+
if (this.acceptPunct(")"))
|
|
297
|
+
return;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
this.expectPunct(")");
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
engine() {
|
|
305
|
+
const from = this.idx(-1);
|
|
306
|
+
this.acceptPunct("=");
|
|
307
|
+
const { text, span } = this.name("an engine name");
|
|
308
|
+
let args;
|
|
309
|
+
if (this.acceptPunct("(")) {
|
|
310
|
+
args = [];
|
|
311
|
+
if (!this.acceptPunct(")")) {
|
|
312
|
+
for (;;) {
|
|
313
|
+
args.push(this.expr());
|
|
314
|
+
if (this.acceptPunct(","))
|
|
315
|
+
continue;
|
|
316
|
+
this.expectPunct(")");
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return { name: text, nameSpan: span, args, span: this.span(from, [...span.refs, ...(args?.flatMap((a) => a.refs) ?? [])]) };
|
|
322
|
+
}
|
|
323
|
+
settingsList(stop) {
|
|
324
|
+
const out = [];
|
|
325
|
+
for (;;) {
|
|
326
|
+
const key = this.name("a setting name").text;
|
|
327
|
+
this.expectPunct("=");
|
|
328
|
+
out.push({ key, value: this.clauseExpr(stop) });
|
|
329
|
+
if (!this.acceptPunct(","))
|
|
330
|
+
return out;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/** ENGINE and the storage clauses, in any order, as ClickHouse accepts them. */
|
|
334
|
+
storage(into, terminators) {
|
|
335
|
+
const STOP = [...STORAGE_KEYWORDS, ...terminators];
|
|
336
|
+
for (;;) {
|
|
337
|
+
if (this.accept("ENGINE"))
|
|
338
|
+
into.engine = this.engine();
|
|
339
|
+
else if (kw(this.peek(), "ORDER") && kw(this.peek(1), "BY")) {
|
|
340
|
+
this.p += 2;
|
|
341
|
+
into.orderBy = this.clauseExpr(STOP, false);
|
|
342
|
+
}
|
|
343
|
+
else if (kw(this.peek(), "PRIMARY") && kw(this.peek(1), "KEY")) {
|
|
344
|
+
this.p += 2;
|
|
345
|
+
into.primaryKey = this.clauseExpr(STOP, false);
|
|
346
|
+
}
|
|
347
|
+
else if (kw(this.peek(), "PARTITION") && kw(this.peek(1), "BY")) {
|
|
348
|
+
this.p += 2;
|
|
349
|
+
into.partitionBy = this.clauseExpr(STOP, false);
|
|
350
|
+
}
|
|
351
|
+
else if (kw(this.peek(), "SAMPLE") && kw(this.peek(1), "BY")) {
|
|
352
|
+
this.p += 2;
|
|
353
|
+
into.sampleBy = this.clauseExpr(STOP, false);
|
|
354
|
+
}
|
|
355
|
+
else if (this.accept("TTL"))
|
|
356
|
+
into.ttl = this.clauseExpr(STOP, false);
|
|
357
|
+
else if (kw(this.peek(), "SETTINGS")) {
|
|
358
|
+
const from = this.idx();
|
|
359
|
+
this.p++;
|
|
360
|
+
into.settings = this.settingsList(STOP);
|
|
361
|
+
into.settingsClause = this.span(from, into.settings.flatMap((s) => s.value.refs));
|
|
362
|
+
}
|
|
363
|
+
else if (!this.unknownClause(STOP))
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
parse() {
|
|
368
|
+
const node = this.statement();
|
|
369
|
+
if (this.unknownClauses.length > 0)
|
|
370
|
+
node.unknown = [...this.unknownClauses];
|
|
371
|
+
return node;
|
|
372
|
+
}
|
|
373
|
+
statement() {
|
|
374
|
+
this.expect("CREATE");
|
|
375
|
+
let orReplace = false;
|
|
376
|
+
if (this.accept("OR")) {
|
|
377
|
+
this.expect("REPLACE");
|
|
378
|
+
orReplace = true;
|
|
379
|
+
}
|
|
380
|
+
if (this.accept("DATABASE"))
|
|
381
|
+
return this.database();
|
|
382
|
+
if (this.accept("TABLE"))
|
|
383
|
+
return this.table(orReplace);
|
|
384
|
+
if (this.accept("DICTIONARY"))
|
|
385
|
+
return this.dictionary(orReplace);
|
|
386
|
+
if (this.accept("FUNCTION"))
|
|
387
|
+
return this.function(orReplace);
|
|
388
|
+
const materialized = this.accept("MATERIALIZED");
|
|
389
|
+
if (this.accept("VIEW"))
|
|
390
|
+
return this.view(orReplace, materialized);
|
|
391
|
+
if (!materialized) {
|
|
392
|
+
const refused = REFUSED_CREATE_KINDS.find((k) => k.words.every((w, i) => kw(this.peek(i), w)));
|
|
393
|
+
if (refused)
|
|
394
|
+
this.fail(`CREATE ${refused.kind} is not declared: ${refused.reason}`);
|
|
395
|
+
// Access control is read by its own tags (`./access.ts`).
|
|
396
|
+
const access = [
|
|
397
|
+
[["USER"], "USER", "user"],
|
|
398
|
+
[["ROLE"], "ROLE", "role"],
|
|
399
|
+
[["ROW", "POLICY"], "ROW POLICY", "policy"],
|
|
400
|
+
[["POLICY"], "ROW POLICY", "policy"],
|
|
401
|
+
[["QUOTA"], "QUOTA", "quota"],
|
|
402
|
+
[["SETTINGS", "PROFILE"], "SETTINGS PROFILE", "settingsProfile"],
|
|
403
|
+
[["PROFILE"], "SETTINGS PROFILE", "settingsProfile"],
|
|
404
|
+
];
|
|
405
|
+
const hit = access.find(([words]) => words.every((w, i) => kw(this.peek(i), w)));
|
|
406
|
+
if (hit)
|
|
407
|
+
this.fail(`CREATE ${hit[1]} is access control: declare it with the ${hit[2]} tag`);
|
|
408
|
+
}
|
|
409
|
+
return this.fail(materialized ? "expected VIEW" : "expected DATABASE, TABLE, VIEW, MATERIALIZED VIEW, DICTIONARY or FUNCTION");
|
|
410
|
+
}
|
|
411
|
+
ifNotExists() {
|
|
412
|
+
if (kw(this.peek(), "IF") && kw(this.peek(1), "NOT") && kw(this.peek(2), "EXISTS")) {
|
|
413
|
+
this.p += 3;
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
onCluster() {
|
|
419
|
+
if (kw(this.peek(), "ON") && kw(this.peek(1), "CLUSTER")) {
|
|
420
|
+
this.p += 2;
|
|
421
|
+
return this.qualifiedName();
|
|
422
|
+
}
|
|
423
|
+
return undefined;
|
|
424
|
+
}
|
|
425
|
+
/** A `UUID '...'` clause after the name, as `SHOW CREATE` can print it. Skipped. */
|
|
426
|
+
uuid() {
|
|
427
|
+
if (kw(this.peek(), "UUID") && this.peek(1)?.kind === "string")
|
|
428
|
+
this.p += 2;
|
|
429
|
+
}
|
|
430
|
+
/** The statement's end, after any trailing clauses the parser does not know (#3872); `known` are the statement's own clause words. */
|
|
431
|
+
finish(what, known = []) {
|
|
432
|
+
while (this.unknownClause(known))
|
|
433
|
+
;
|
|
434
|
+
this.acceptPunct(";");
|
|
435
|
+
if (this.peek() !== undefined)
|
|
436
|
+
this.fail(`unexpected token after the ${what}`);
|
|
437
|
+
}
|
|
438
|
+
database() {
|
|
439
|
+
const node = { statement: "database", ifNotExists: this.ifNotExists(), name: this.qualifiedName() };
|
|
440
|
+
this.uuid();
|
|
441
|
+
node.onCluster = this.onCluster();
|
|
442
|
+
const CLAUSES = ["ENGINE", "SETTINGS", "COMMENT"];
|
|
443
|
+
for (;;) {
|
|
444
|
+
// In the server's order: ENGINE, SETTINGS, COMMENT, with clauses chant does not know between them.
|
|
445
|
+
if (!node.engine && !node.settings && !node.comment && this.accept("ENGINE"))
|
|
446
|
+
node.engine = this.engine();
|
|
447
|
+
else if (!node.settings && !node.comment && this.accept("SETTINGS"))
|
|
448
|
+
node.settings = this.settingsList(CLAUSES);
|
|
449
|
+
else if (!node.comment && this.accept("COMMENT"))
|
|
450
|
+
node.comment = this.clauseExpr();
|
|
451
|
+
else if (!this.unknownClause(CLAUSES))
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
this.finish("database definition", CLAUSES);
|
|
455
|
+
return node;
|
|
456
|
+
}
|
|
457
|
+
table(orReplace) {
|
|
458
|
+
const ifNotExists = this.ifNotExists();
|
|
459
|
+
const name = this.qualifiedName();
|
|
460
|
+
this.uuid();
|
|
461
|
+
const node = {
|
|
462
|
+
statement: "table",
|
|
463
|
+
orReplace,
|
|
464
|
+
ifNotExists,
|
|
465
|
+
name,
|
|
466
|
+
onCluster: this.onCluster(),
|
|
467
|
+
columns: [],
|
|
468
|
+
indexes: [],
|
|
469
|
+
projections: [],
|
|
470
|
+
constraints: [],
|
|
471
|
+
};
|
|
472
|
+
if (this.isPunct("("))
|
|
473
|
+
this.tableElements(node, true);
|
|
474
|
+
else if (!kw(this.peek(), "AS"))
|
|
475
|
+
this.refuseCopy();
|
|
476
|
+
const asWord = this.peek();
|
|
477
|
+
if (this.accept("AS")) {
|
|
478
|
+
// `AS numbers(10)`: a table over a table function. `AS other` and `AS SELECT` copy, and are refused.
|
|
479
|
+
if (kw(this.peek(), "SELECT", "WITH") || this.isPunct("("))
|
|
480
|
+
this.refuseCopy("select", asWord);
|
|
481
|
+
const from = this.idx();
|
|
482
|
+
this.qualifiedName();
|
|
483
|
+
if (!this.isPunct("("))
|
|
484
|
+
this.refuseCopy("table", asWord);
|
|
485
|
+
this.parenthesized();
|
|
486
|
+
node.asFunction = this.span(from, this.tokens.slice(from, this.idx(-1) + 1).flatMap((t) => (t.kind === "ref" ? [t.part] : [])));
|
|
487
|
+
if (this.accept("COMMENT"))
|
|
488
|
+
node.comment = this.clauseExpr();
|
|
489
|
+
if (kw(this.peek(), ...STORAGE_KEYWORDS))
|
|
490
|
+
this.fail("a table over a table function has no engine or storage clauses: the function is its storage");
|
|
491
|
+
this.finish("table definition", ["COMMENT"]);
|
|
492
|
+
return node;
|
|
493
|
+
}
|
|
494
|
+
this.storage(node, []);
|
|
495
|
+
if (this.accept("COMMENT"))
|
|
496
|
+
node.comment = this.clauseExpr();
|
|
497
|
+
if (kw(this.peek(), "AS") && (kw(this.peek(1), "SELECT", "WITH") || this.peek(1)?.text === "("))
|
|
498
|
+
this.refuseCopy("select");
|
|
499
|
+
this.finish("table definition", STORAGE_KEYWORDS);
|
|
500
|
+
return node;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* The `CREATE TABLE` forms that copy another table or a query, refused
|
|
504
|
+
* with why (#3911). Each is a one-off the server keeps no trace of: it
|
|
505
|
+
* prints the copied columns and engine, or the query's result's, as if
|
|
506
|
+
* they were written out.
|
|
507
|
+
*/
|
|
508
|
+
refuseCopy(what, at = this.peek()) {
|
|
509
|
+
if (what === "select") {
|
|
510
|
+
return this.fail("CREATE TABLE ... AS SELECT is not declared: it creates the table and fills it once, and the server keeps the columns, not the query. Declare the table's columns and fill it with INSERT ... SELECT, or declare a materialized view", at);
|
|
511
|
+
}
|
|
512
|
+
if (what === "table") {
|
|
513
|
+
return this.fail("CREATE TABLE ... AS <table> is not declared: the server copies the other table's columns and engine and keeps no link to it. Declare them (a composite can share them), or write AS <table function>(...) for a table over a table function", at);
|
|
514
|
+
}
|
|
515
|
+
if (kw(this.peek(), "CLONE"))
|
|
516
|
+
return this.fail("CREATE TABLE ... CLONE AS is not declared: it copies another table's columns, engine and data once. Declare the columns and engine");
|
|
517
|
+
if (kw(this.peek(), "EMPTY"))
|
|
518
|
+
return this.fail("CREATE TABLE ... EMPTY AS SELECT is not declared: the server keeps the query's columns, not the query. Declare the columns");
|
|
519
|
+
return this.fail("expected the column list (a table declared without one, CREATE TABLE ... ENGINE = ... AS SELECT, is not declared: declare the columns)");
|
|
520
|
+
}
|
|
521
|
+
dictionaryAttribute() {
|
|
522
|
+
const { text, span } = this.name("an attribute name");
|
|
523
|
+
const STOP = ["DEFAULT", "EXPRESSION", "HIERARCHICAL", "BIDIRECTIONAL", "INJECTIVE", "IS_OBJECT_ID"];
|
|
524
|
+
const attr = { name: text, nameSpan: span, type: this.typeSpan(), flags: [] };
|
|
525
|
+
for (;;) {
|
|
526
|
+
if (this.accept("DEFAULT"))
|
|
527
|
+
attr.default = this.clauseExpr(STOP);
|
|
528
|
+
else if (this.accept("EXPRESSION"))
|
|
529
|
+
attr.expression = this.clauseExpr(STOP);
|
|
530
|
+
else if (kw(this.peek(), "HIERARCHICAL", "BIDIRECTIONAL", "INJECTIVE", "IS_OBJECT_ID"))
|
|
531
|
+
attr.flags.push(this.next().text.toUpperCase());
|
|
532
|
+
else if (!this.unknownClause(STOP, { inList: true, column: text || undefined }))
|
|
533
|
+
return attr;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
dictionary(orReplace) {
|
|
537
|
+
const ifNotExists = this.ifNotExists();
|
|
538
|
+
const name = this.qualifiedName();
|
|
539
|
+
this.uuid();
|
|
540
|
+
const node = { statement: "dictionary", orReplace, ifNotExists, name, onCluster: this.onCluster(), attributes: [] };
|
|
541
|
+
this.expectPunct("(");
|
|
542
|
+
for (;;) {
|
|
543
|
+
node.attributes.push(this.dictionaryAttribute());
|
|
544
|
+
if (this.acceptPunct(",")) {
|
|
545
|
+
if (this.acceptPunct(")"))
|
|
546
|
+
break;
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
this.expectPunct(")");
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
const CLAUSES = ["PRIMARY", "SOURCE", "LAYOUT", "LIFETIME", "RANGE", "SETTINGS", "COMMENT"];
|
|
553
|
+
for (;;) {
|
|
554
|
+
if (kw(this.peek(), "PRIMARY") && kw(this.peek(1), "KEY")) {
|
|
555
|
+
this.p += 2;
|
|
556
|
+
node.primaryKey = this.clauseExpr(CLAUSES, false);
|
|
557
|
+
}
|
|
558
|
+
else if (this.accept("SOURCE"))
|
|
559
|
+
node.source = this.parenthesized();
|
|
560
|
+
else if (this.accept("LAYOUT"))
|
|
561
|
+
node.layout = this.parenthesized();
|
|
562
|
+
else if (this.accept("LIFETIME"))
|
|
563
|
+
node.lifetime = this.parenthesized();
|
|
564
|
+
else if (this.accept("RANGE"))
|
|
565
|
+
node.range = this.parenthesized();
|
|
566
|
+
else if (this.accept("SETTINGS"))
|
|
567
|
+
node.settings = this.parenthesized();
|
|
568
|
+
else if (this.accept("COMMENT"))
|
|
569
|
+
node.comment = this.clauseExpr();
|
|
570
|
+
else if (!this.unknownClause(CLAUSES))
|
|
571
|
+
break;
|
|
572
|
+
}
|
|
573
|
+
if (!node.primaryKey)
|
|
574
|
+
this.fail("expected PRIMARY KEY: a dictionary needs its key columns");
|
|
575
|
+
if (!node.source)
|
|
576
|
+
this.fail("expected SOURCE(...): a dictionary needs a source");
|
|
577
|
+
if (!node.layout)
|
|
578
|
+
this.fail("expected LAYOUT(...): a dictionary needs a layout");
|
|
579
|
+
this.finish("dictionary definition", CLAUSES);
|
|
580
|
+
return node;
|
|
581
|
+
}
|
|
582
|
+
function(orReplace) {
|
|
583
|
+
const ifNotExists = this.ifNotExists();
|
|
584
|
+
const name = this.qualifiedName();
|
|
585
|
+
if (name.to - name.from > 1 && this.tokens.slice(name.from, name.to).some((t) => t.kind === "punct" && t.text === ".")) {
|
|
586
|
+
this.fail("a function belongs to no database: name it without one");
|
|
587
|
+
}
|
|
588
|
+
const onCluster = this.onCluster();
|
|
589
|
+
this.expect("AS");
|
|
590
|
+
const from = this.idx();
|
|
591
|
+
const params = [];
|
|
592
|
+
const param = () => {
|
|
593
|
+
const p = this.name("a parameter name");
|
|
594
|
+
params.push({ name: p.text, span: p.span });
|
|
595
|
+
};
|
|
596
|
+
if (this.acceptPunct("(")) {
|
|
597
|
+
if (!this.acceptPunct(")")) {
|
|
598
|
+
for (;;) {
|
|
599
|
+
param();
|
|
600
|
+
if (this.acceptPunct(","))
|
|
601
|
+
continue;
|
|
602
|
+
this.expectPunct(")");
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
else
|
|
608
|
+
param();
|
|
609
|
+
const arrow = this.next();
|
|
610
|
+
if (arrow?.kind !== "op" || arrow.text !== "->")
|
|
611
|
+
this.fail("expected -> and the function's expression", arrow);
|
|
612
|
+
const body = this.expr([], false);
|
|
613
|
+
const lambda = this.span(from, body.refs);
|
|
614
|
+
this.finish("function definition");
|
|
615
|
+
return { statement: "function", orReplace, ifNotExists, name, onCluster, params, lambda, body };
|
|
616
|
+
}
|
|
617
|
+
view(orReplace, materialized) {
|
|
618
|
+
const ifNotExists = this.ifNotExists();
|
|
619
|
+
const name = this.qualifiedName();
|
|
620
|
+
this.uuid();
|
|
621
|
+
const node = {
|
|
622
|
+
statement: "view",
|
|
623
|
+
materialized,
|
|
624
|
+
orReplace,
|
|
625
|
+
ifNotExists,
|
|
626
|
+
name,
|
|
627
|
+
onCluster: this.onCluster(),
|
|
628
|
+
append: false,
|
|
629
|
+
columns: [],
|
|
630
|
+
populate: false,
|
|
631
|
+
empty: false,
|
|
632
|
+
select: { from: 0, to: 0, refs: [] },
|
|
633
|
+
};
|
|
634
|
+
if (this.accept("REFRESH")) {
|
|
635
|
+
this.refreshing = true;
|
|
636
|
+
try {
|
|
637
|
+
node.refresh = this.expr(["TO", "ENGINE", "AS", "EMPTY", "APPEND", "DEPENDS", "SETTINGS"]);
|
|
638
|
+
}
|
|
639
|
+
finally {
|
|
640
|
+
this.refreshing = false;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
if (this.accept("APPEND"))
|
|
644
|
+
node.append = true;
|
|
645
|
+
if (this.accept("TO"))
|
|
646
|
+
node.to = this.qualifiedName();
|
|
647
|
+
if (this.isPunct("(")) {
|
|
648
|
+
const from = this.idx();
|
|
649
|
+
const holder = { columns: node.columns, indexes: [], projections: [], constraints: [] };
|
|
650
|
+
this.tableElements(holder, false);
|
|
651
|
+
node.columnsSpan = this.span(from, []);
|
|
652
|
+
}
|
|
653
|
+
const BEFORE_AS = ["AS", "POPULATE", "EMPTY", "DEFINER", "SQL"];
|
|
654
|
+
this.storage(node, BEFORE_AS);
|
|
655
|
+
if (this.accept("POPULATE"))
|
|
656
|
+
node.populate = true;
|
|
657
|
+
if (this.accept("EMPTY"))
|
|
658
|
+
node.empty = true;
|
|
659
|
+
if (kw(this.peek(), "DEFINER", "SQL"))
|
|
660
|
+
node.security = this.expr(["AS"], false);
|
|
661
|
+
while (!kw(this.peek(), "AS") && this.unknownClause([...STORAGE_KEYWORDS, ...BEFORE_AS]))
|
|
662
|
+
;
|
|
663
|
+
this.expect("AS");
|
|
664
|
+
node.select = this.expr(["COMMENT"], false);
|
|
665
|
+
if (this.accept("COMMENT"))
|
|
666
|
+
node.comment = this.expr();
|
|
667
|
+
this.finish("view's SELECT");
|
|
668
|
+
return node;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
/** Parse one CREATE statement. Throws {@link SqlSyntaxError} located in the template. */
|
|
672
|
+
export function parseCreate(tokens) {
|
|
673
|
+
return new Parser(tokens).parse();
|
|
674
|
+
}
|