@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,86 @@
|
|
|
1
|
+
import { parseStatements, SqlSyntaxError } from "../../postgres/parser.js";
|
|
2
|
+
import { findPostgresTemplates, postgresTokensOf } from "./postgres-templates.js";
|
|
3
|
+
import { declaredText, generatedNamesFor, nameAgrees, normalizedName, splitQualified, SQL_TYPES_FILE, typeArgumentName } from "./typed-names.js";
|
|
4
|
+
const MODULE = "@intentius/chant-lexicon-sql/postgres";
|
|
5
|
+
/**
|
|
6
|
+
* SQLPG005: a typed Postgres tag whose type argument does not name the object
|
|
7
|
+
* its template declares, or is not in the generated `sql-types.d.ts`.
|
|
8
|
+
*
|
|
9
|
+
* `table<"shop.orders">\`CREATE TABLE shop.orders ...\`` types `.columns` from
|
|
10
|
+
* the generated file's `SqlSchema["shop.orders"]`. tsc does not compare the
|
|
11
|
+
* argument with the template's text, and an argument the file does not list
|
|
12
|
+
* falls back to unchecked columns, so a typo checks nothing and says nothing.
|
|
13
|
+
* The rule reads the declared name from the template (a schema supplied by an
|
|
14
|
+
* interpolation is not known to lint, and is left out of the comparison) and
|
|
15
|
+
* the listed names from the nearest `sql-types.d.ts` above the file. With no
|
|
16
|
+
* generated file, only the first check runs. A name missing from the file is
|
|
17
|
+
* a warning, since a table added since the last `chant sql types` is stale
|
|
18
|
+
* and `chant sql types --check` is what fails on it.
|
|
19
|
+
*
|
|
20
|
+
* chant#3787: `schema<"app">\`CREATE SCHEMA app\`` is read too, since a
|
|
21
|
+
* composite placed in the schema builds its members' qualified names from
|
|
22
|
+
* that argument. A schema is not in the generated file, so only the name
|
|
23
|
+
* comparison runs, and the argument is the schema's own name, one piece.
|
|
24
|
+
*/
|
|
25
|
+
export const sqlpg005 = {
|
|
26
|
+
id: "SQLPG005",
|
|
27
|
+
severity: "error",
|
|
28
|
+
category: "correctness",
|
|
29
|
+
description: "A Postgres tag's type argument does not name the object its template declares, or is not in sql-types.d.ts",
|
|
30
|
+
check(context) {
|
|
31
|
+
const source = context.sourceFile;
|
|
32
|
+
const out = [];
|
|
33
|
+
let listed = null;
|
|
34
|
+
for (const found of findPostgresTemplates(source)) {
|
|
35
|
+
if (found.tag !== "table" && found.tag !== "view" && found.tag !== "schema")
|
|
36
|
+
continue;
|
|
37
|
+
const arg = typeArgumentName(found.node);
|
|
38
|
+
if (!arg)
|
|
39
|
+
continue;
|
|
40
|
+
const at = (() => {
|
|
41
|
+
const { line, character } = source.getLineAndCharacterOfPosition(arg.node.getStart(source));
|
|
42
|
+
return { line: line + 1, column: character + 1 };
|
|
43
|
+
})();
|
|
44
|
+
let declared;
|
|
45
|
+
try {
|
|
46
|
+
const node = parseStatements(postgresTokensOf(found))[0];
|
|
47
|
+
if (node && (node.statement === "table" || node.statement === "view"))
|
|
48
|
+
declared = node.name.pieces;
|
|
49
|
+
else if (node && node.statement === "schema" && found.tag === "schema")
|
|
50
|
+
declared = node.name?.pieces;
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
if (err instanceof SqlSyntaxError)
|
|
54
|
+
continue; // SQLPG001 reports it
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
if (!declared)
|
|
58
|
+
continue; // SQLPG001: the tag holds another statement
|
|
59
|
+
const agrees = found.tag === "schema" ? splitQualified(arg.name).length === 1 && nameAgrees(declared, arg.name) : nameAgrees(declared, arg.name);
|
|
60
|
+
if (!agrees) {
|
|
61
|
+
out.push({
|
|
62
|
+
ruleId: "SQLPG005",
|
|
63
|
+
severity: "error",
|
|
64
|
+
message: `${found.tag}<"${arg.name}"> names another object than the template declares (${declaredText(declared)}); the type argument is the qualified name the build renders`,
|
|
65
|
+
file: context.filePath,
|
|
66
|
+
...at,
|
|
67
|
+
});
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (found.tag === "schema")
|
|
71
|
+
continue;
|
|
72
|
+
if (listed === null)
|
|
73
|
+
listed = generatedNamesFor(context.filePath, MODULE);
|
|
74
|
+
if (listed && !listed.has(normalizedName(arg.name))) {
|
|
75
|
+
out.push({
|
|
76
|
+
ruleId: "SQLPG005",
|
|
77
|
+
severity: "warning",
|
|
78
|
+
message: `"${arg.name}" is not in ${SQL_TYPES_FILE}, so its columns are unchecked; run \`chant sql types\` to add it`,
|
|
79
|
+
file: context.filePath,
|
|
80
|
+
...at,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
},
|
|
86
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finding the ClickHouse tags in a source file, for the source-level rules
|
|
3
|
+
* (and the editor). A rule reads the template's raw parts straight from the
|
|
4
|
+
* TypeScript AST and parses them the way the tag does, with every
|
|
5
|
+
* interpolation left a reference: lint cannot know the values. The finder is
|
|
6
|
+
* the shared core's (`../../core/find-templates.ts`); a tag is ClickHouse's
|
|
7
|
+
* because of the module it is imported from.
|
|
8
|
+
*/
|
|
9
|
+
import { tokenize } from "../../clickhouse/tokens.js";
|
|
10
|
+
import { findSqlTemplates } from "../../core/find-templates.js";
|
|
11
|
+
export { templatePosition, tokenPosition } from "../../core/find-templates.js";
|
|
12
|
+
/**
|
|
13
|
+
* The ClickHouse tags, by the modules they are imported from. The package
|
|
14
|
+
* root still exports them, so it counts as ClickHouse too.
|
|
15
|
+
*/
|
|
16
|
+
export const CLICKHOUSE_TEMPLATE_SOURCE = {
|
|
17
|
+
dialect: "clickhouse",
|
|
18
|
+
modules: ["@intentius/chant-lexicon-sql", "@intentius/chant-lexicon-sql/clickhouse"],
|
|
19
|
+
tags: ["database", "table", "view", "dictionary", "func", "user", "role", "policy", "quota", "settingsProfile", "grant"],
|
|
20
|
+
};
|
|
21
|
+
/** Every ClickHouse template in the file (the shared core's finder). */
|
|
22
|
+
export function findTemplates(source) {
|
|
23
|
+
return findSqlTemplates(source, [CLICKHOUSE_TEMPLATE_SOURCE]);
|
|
24
|
+
}
|
|
25
|
+
export const tokensOf = (found) => tokenize(found.parts);
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the typed-column rules (SQLPG005, SQLCH004; chant #3751) share: reading
|
|
3
|
+
* a tag's type argument, comparing it with the name the template declares, and
|
|
4
|
+
* reading the names the generated `sql-types.d.ts` lists.
|
|
5
|
+
*
|
|
6
|
+
* The type argument is `table<"shop.orders">\`CREATE TABLE ...\``. tsc checks
|
|
7
|
+
* that it names an entry of the generated file's `SqlSchema` only when the
|
|
8
|
+
* argument is a constraint, and an unlisted name falls back to unchecked
|
|
9
|
+
* columns, so a mistyped argument is silent without these rules.
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
12
|
+
import { dirname, join, resolve } from "node:path";
|
|
13
|
+
import { ts } from "@intentius/chant/lint/typescript";
|
|
14
|
+
/** The generated file's name, beside the source (`chant sql types`). */
|
|
15
|
+
export const SQL_TYPES_FILE = "sql-types.d.ts";
|
|
16
|
+
/** A tag's type argument, when it is a string literal: `table<"shop.orders">`. */
|
|
17
|
+
export function typeArgumentName(node) {
|
|
18
|
+
const arg = node.typeArguments?.[0];
|
|
19
|
+
if (!arg || node.typeArguments.length !== 1)
|
|
20
|
+
return undefined;
|
|
21
|
+
if (!ts.isLiteralTypeNode(arg) || !ts.isStringLiteral(arg.literal))
|
|
22
|
+
return undefined;
|
|
23
|
+
return { name: arg.literal.text, node: arg };
|
|
24
|
+
}
|
|
25
|
+
/** Split a dotted name outside quotes and take the quotes off: `shop."Order Items"` is `shop`, `Order Items`. */
|
|
26
|
+
export function splitQualified(name) {
|
|
27
|
+
const pieces = [];
|
|
28
|
+
let cur = "";
|
|
29
|
+
let quote;
|
|
30
|
+
for (let i = 0; i < name.length; i++) {
|
|
31
|
+
const c = name[i];
|
|
32
|
+
if (quote) {
|
|
33
|
+
if (c === quote && name[i + 1] === quote) {
|
|
34
|
+
cur += c;
|
|
35
|
+
i++;
|
|
36
|
+
}
|
|
37
|
+
else if (c === quote)
|
|
38
|
+
quote = undefined;
|
|
39
|
+
else
|
|
40
|
+
cur += c;
|
|
41
|
+
}
|
|
42
|
+
else if (c === '"' || c === "`")
|
|
43
|
+
quote = c;
|
|
44
|
+
else if (c === ".") {
|
|
45
|
+
pieces.push(cur);
|
|
46
|
+
cur = "";
|
|
47
|
+
}
|
|
48
|
+
else
|
|
49
|
+
cur += c;
|
|
50
|
+
}
|
|
51
|
+
pieces.push(cur);
|
|
52
|
+
return pieces;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Whether the declared name (an empty piece is an interpolation, which lint
|
|
56
|
+
* cannot read) can be the type argument's. Compared from the last piece back.
|
|
57
|
+
* A template that does not qualify its name takes the argument's schema, since
|
|
58
|
+
* the build qualifies it with the schema or database the project sets; one
|
|
59
|
+
* that does qualify it must agree with the argument.
|
|
60
|
+
*/
|
|
61
|
+
export function nameAgrees(declared, argument) {
|
|
62
|
+
const given = splitQualified(argument);
|
|
63
|
+
if (declared.length === 0)
|
|
64
|
+
return true;
|
|
65
|
+
if (declared.length > given.length)
|
|
66
|
+
return false;
|
|
67
|
+
for (let i = 1; i <= declared.length; i++) {
|
|
68
|
+
const d = declared[declared.length - i];
|
|
69
|
+
if (d === "")
|
|
70
|
+
continue;
|
|
71
|
+
if (d !== given[given.length - i])
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
/** The text of a declared name for a message; an interpolation is `${...}`. */
|
|
77
|
+
export const declaredText = (declared) => declared.map((p) => (p === "" ? "${...}" : p)).join(".");
|
|
78
|
+
/** A qualified name with quoting taken off, so `"shop"."orders"` and `shop.orders` compare equal. */
|
|
79
|
+
export const normalizedName = (name) => splitQualified(name).join("\0");
|
|
80
|
+
/** The names (normalized, see {@link normalizedName}) a `declare module "<module>"` block in a generated file augments `SqlSchema` with. */
|
|
81
|
+
export function namesInGenerated(content, module) {
|
|
82
|
+
const source = ts.createSourceFile(SQL_TYPES_FILE, content, ts.ScriptTarget.Latest, true);
|
|
83
|
+
let names;
|
|
84
|
+
for (const stmt of source.statements) {
|
|
85
|
+
if (!ts.isModuleDeclaration(stmt) || !ts.isStringLiteral(stmt.name) || stmt.name.text !== module)
|
|
86
|
+
continue;
|
|
87
|
+
if (!stmt.body || !ts.isModuleBlock(stmt.body))
|
|
88
|
+
continue;
|
|
89
|
+
for (const inner of stmt.body.statements) {
|
|
90
|
+
if (!ts.isInterfaceDeclaration(inner) || inner.name.text !== "SqlSchema")
|
|
91
|
+
continue;
|
|
92
|
+
names ?? (names = new Set());
|
|
93
|
+
for (const member of inner.members) {
|
|
94
|
+
const n = member.name;
|
|
95
|
+
if (n && (ts.isStringLiteral(n) || ts.isIdentifier(n)))
|
|
96
|
+
names.add(normalizedName(n.text));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return names;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The generated file nearest above a source file, up to the directory that
|
|
104
|
+
* holds the project's config. `chant sql types` writes it in `sourceDir` (or
|
|
105
|
+
* the project root), so the walk finds it from any file below.
|
|
106
|
+
*/
|
|
107
|
+
export function findGeneratedFile(filePath) {
|
|
108
|
+
let dir = dirname(resolve(filePath));
|
|
109
|
+
for (;;) {
|
|
110
|
+
const candidate = join(dir, SQL_TYPES_FILE);
|
|
111
|
+
if (existsSync(candidate))
|
|
112
|
+
return candidate;
|
|
113
|
+
if (existsSync(join(dir, "chant.config.ts")) || existsSync(join(dir, "chant.config.json")))
|
|
114
|
+
return undefined;
|
|
115
|
+
const up = dirname(dir);
|
|
116
|
+
if (up === dir)
|
|
117
|
+
return undefined;
|
|
118
|
+
dir = up;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** The names the generated file lists for a dialect's module, or undefined when there is no file or it has no such block. */
|
|
122
|
+
export function generatedNamesFor(filePath, module) {
|
|
123
|
+
const found = findGeneratedFile(filePath);
|
|
124
|
+
if (!found)
|
|
125
|
+
return undefined;
|
|
126
|
+
try {
|
|
127
|
+
return namesInGenerated(readFileSync(found, "utf8"), module);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which dialect a live read is for. A build holds one dialect, so a read of
|
|
3
|
+
* declared entities follows their types; a read with nothing declared (import)
|
|
4
|
+
* follows the binding: the environment's profile URL (`postgres://` is
|
|
5
|
+
* Postgres), else `sql.dialect`, else whichever of `POSTGRES_URL` and
|
|
6
|
+
* `CLICKHOUSE_URL` is set, else ClickHouse, the first dialect.
|
|
7
|
+
*/
|
|
8
|
+
import { isPostgresUrl, loadSqlConfig } from "./postgres/live/bind.js";
|
|
9
|
+
export function dialectOfEntities(entities) {
|
|
10
|
+
for (const e of entities?.values() ?? [])
|
|
11
|
+
if (e.entityType.startsWith("Postgres::"))
|
|
12
|
+
return "postgres";
|
|
13
|
+
return "clickhouse";
|
|
14
|
+
}
|
|
15
|
+
export function dialectOfBinding(input) {
|
|
16
|
+
const env = input.env ?? process.env;
|
|
17
|
+
const profile = input.environment !== undefined ? input.config?.sql?.profiles?.[input.environment] : undefined;
|
|
18
|
+
if (profile)
|
|
19
|
+
return isPostgresUrl(profile.url) ? "postgres" : "clickhouse";
|
|
20
|
+
const configured = input.config?.sql?.dialect;
|
|
21
|
+
const named = Array.isArray(configured) ? (configured.length === 1 ? configured[0] : undefined) : configured;
|
|
22
|
+
if (named)
|
|
23
|
+
return named;
|
|
24
|
+
if (env.POSTGRES_URL && !env.CLICKHOUSE_URL)
|
|
25
|
+
return "postgres";
|
|
26
|
+
return "clickhouse";
|
|
27
|
+
}
|
|
28
|
+
/** The binding's dialect, loading the project config from `cwd` when none is passed. */
|
|
29
|
+
export async function resolveBindingDialect(options) {
|
|
30
|
+
const config = options.config ?? (await loadSqlConfig(options.cwd ?? process.cwd()));
|
|
31
|
+
return dialectOfBinding({ environment: options.environment, config, env: options.env });
|
|
32
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ClickHouse catalog the editor and the MCP tools read: the committed
|
|
3
|
+
* snapshot of the pinned server's `system.*` tables, parsed once and indexed
|
|
4
|
+
* by name. No server is needed; the snapshot is what generation reads too.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync } from "fs";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
import { moduleSourceDir } from "@intentius/chant/module-source";
|
|
9
|
+
import { parseCatalog } from "../spec/catalog.js";
|
|
10
|
+
const SNAPSHOT = join(moduleSourceDir(import.meta.url), "..", "spec", "clickhouse-catalog.snapshot.json");
|
|
11
|
+
let cached = null;
|
|
12
|
+
const byName = (rows, foldCase) => {
|
|
13
|
+
const map = new Map();
|
|
14
|
+
for (const row of rows) {
|
|
15
|
+
map.set(row.name, row);
|
|
16
|
+
if (foldCase?.(row))
|
|
17
|
+
map.set(row.name.toLowerCase(), row);
|
|
18
|
+
}
|
|
19
|
+
return map;
|
|
20
|
+
};
|
|
21
|
+
/** The catalog, or undefined when the snapshot cannot be read. */
|
|
22
|
+
export function catalogIndex() {
|
|
23
|
+
if (cached)
|
|
24
|
+
return cached;
|
|
25
|
+
try {
|
|
26
|
+
const catalog = parseCatalog(readFileSync(SNAPSHOT, "utf-8"));
|
|
27
|
+
cached = {
|
|
28
|
+
catalog,
|
|
29
|
+
engines: byName(catalog.tableEngines),
|
|
30
|
+
databaseEngines: byName(catalog.databaseEngines),
|
|
31
|
+
types: byName(catalog.typeFamilies, (t) => t.caseInsensitive),
|
|
32
|
+
codecs: byName(catalog.codecs),
|
|
33
|
+
indexTypes: byName(catalog.skipIndexTypes),
|
|
34
|
+
mergeTreeSettings: byName(catalog.mergeTreeSettings),
|
|
35
|
+
querySettings: byName(catalog.querySettings),
|
|
36
|
+
functions: byName(catalog.functions, (f) => f.caseInsensitive),
|
|
37
|
+
formats: byName(catalog.formats),
|
|
38
|
+
};
|
|
39
|
+
return cached;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { ts } from "@intentius/chant/lint/typescript";
|
|
2
|
+
import { lexiconCompletions } from "@intentius/chant/lsp/lexicon-providers";
|
|
3
|
+
import { catalogIndex } from "./catalog.js";
|
|
4
|
+
import { postgresCompletions } from "./postgres.js";
|
|
5
|
+
import { registryIndex } from "./registry.js";
|
|
6
|
+
import { declarations, expectAfter, locate, offsetAt, tokensBefore, wordAround } from "./template.js";
|
|
7
|
+
const LIMIT = 200;
|
|
8
|
+
/**
|
|
9
|
+
* Completions for the sql lexicon.
|
|
10
|
+
*
|
|
11
|
+
* Inside a `table`, `view` or `database` template they come from the catalog of
|
|
12
|
+
* the pinned ClickHouse server: engines after `ENGINE =`, type families where a
|
|
13
|
+
* column's type goes, codecs inside `CODEC(...)`, skip index types after `TYPE`,
|
|
14
|
+
* settings after `SETTINGS`, functions in expressions. Inside `${` they are the
|
|
15
|
+
* tables and views declared in the file and their columns. Anywhere else, the
|
|
16
|
+
* entity classes from the generated registry.
|
|
17
|
+
*/
|
|
18
|
+
export function completions(ctx) {
|
|
19
|
+
const pg = postgresCompletions(ctx);
|
|
20
|
+
if (pg)
|
|
21
|
+
return pg;
|
|
22
|
+
const refs = interpolationCompletions(ctx);
|
|
23
|
+
if (refs)
|
|
24
|
+
return refs;
|
|
25
|
+
const at = offsetAt(ctx.content, ctx.position);
|
|
26
|
+
const where = locate(ctx.content, at, fileName(ctx.uri));
|
|
27
|
+
if (where && where.part >= 0) {
|
|
28
|
+
const index = catalogIndex();
|
|
29
|
+
if (!index)
|
|
30
|
+
return [];
|
|
31
|
+
const text = where.found.parts[where.part];
|
|
32
|
+
const word = wordAround(text, where.offset);
|
|
33
|
+
const sig = tokensBefore(where.found, where.part, word.start);
|
|
34
|
+
if (!sig)
|
|
35
|
+
return [];
|
|
36
|
+
const expect = expectAfter(sig, where.found.tag);
|
|
37
|
+
if (!expect)
|
|
38
|
+
return [];
|
|
39
|
+
const prefix = text.slice(word.start, where.offset);
|
|
40
|
+
return fromCatalog(index, expect, where.found.tag, prefix);
|
|
41
|
+
}
|
|
42
|
+
if (where)
|
|
43
|
+
return [];
|
|
44
|
+
return lexiconCompletions(ctx, registryIndex(), "sql entity");
|
|
45
|
+
}
|
|
46
|
+
const fileName = (uri) => (uri.startsWith("file://") ? decodeURIComponent(uri.slice(7)) : uri) || "file.ts";
|
|
47
|
+
const matches = (name, prefix, fold) => prefix === "" || (fold ? name.toLowerCase().startsWith(prefix.toLowerCase()) : name.startsWith(prefix));
|
|
48
|
+
function fromCatalog(index, expect, tag, prefix) {
|
|
49
|
+
const items = [];
|
|
50
|
+
const add = (item) => {
|
|
51
|
+
if (items.length < LIMIT)
|
|
52
|
+
items.push(item);
|
|
53
|
+
};
|
|
54
|
+
const { catalog } = index;
|
|
55
|
+
switch (expect) {
|
|
56
|
+
case "engine":
|
|
57
|
+
if (tag === "database") {
|
|
58
|
+
for (const e of catalog.databaseEngines)
|
|
59
|
+
if (matches(e.name, prefix, true))
|
|
60
|
+
add({ label: e.name, kind: "value", detail: e.syntax, documentation: e.summary });
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
for (const e of catalog.tableEngines)
|
|
64
|
+
if (matches(e.name, prefix, true))
|
|
65
|
+
add({ label: e.name, kind: "value", detail: e.syntax, documentation: e.summary });
|
|
66
|
+
}
|
|
67
|
+
break;
|
|
68
|
+
case "type":
|
|
69
|
+
for (const t of catalog.typeFamilies)
|
|
70
|
+
if (matches(t.name, prefix, t.caseInsensitive))
|
|
71
|
+
add({ label: t.name, kind: "value", detail: t.aliasOf ? `alias of ${t.aliasOf}` : "type family" });
|
|
72
|
+
break;
|
|
73
|
+
case "codec":
|
|
74
|
+
for (const c of catalog.codecs)
|
|
75
|
+
if (matches(c.name, prefix, true))
|
|
76
|
+
add({ label: c.name, kind: "value", detail: codecKind(c), documentation: c.summary });
|
|
77
|
+
break;
|
|
78
|
+
case "index-type":
|
|
79
|
+
for (const i of catalog.skipIndexTypes)
|
|
80
|
+
if (matches(i.name, prefix, false))
|
|
81
|
+
add({ label: i.name, kind: "value", detail: i.syntax, documentation: i.summary });
|
|
82
|
+
break;
|
|
83
|
+
case "merge-tree-setting":
|
|
84
|
+
case "query-setting": {
|
|
85
|
+
const rows = expect === "merge-tree-setting" ? catalog.mergeTreeSettings : catalog.querySettings;
|
|
86
|
+
for (const s of rows) {
|
|
87
|
+
if (s.obsolete || !matches(s.name, prefix, false))
|
|
88
|
+
continue;
|
|
89
|
+
add({ label: s.name, insertText: `${s.name} = `, kind: "property", detail: `${s.type}, default ${s.default || "''"}`, documentation: s.summary });
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
case "function":
|
|
94
|
+
if (prefix === "")
|
|
95
|
+
break;
|
|
96
|
+
for (const f of catalog.functions)
|
|
97
|
+
if (!f.aliasOf && matches(f.name, prefix, f.caseInsensitive))
|
|
98
|
+
add({ label: f.name, kind: "intrinsic", detail: f.aggregate ? "aggregate function" : "function" });
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
return items;
|
|
102
|
+
}
|
|
103
|
+
function codecKind(c) {
|
|
104
|
+
return c.encryption ? "encryption codec" : c.timeseries ? "time series codec" : c.compression ? "compression codec" : "codec";
|
|
105
|
+
}
|
|
106
|
+
/** `${` offers the file's tables and views; `${t.` offers `columns`; `${t.columns.` offers its columns. */
|
|
107
|
+
function interpolationCompletions(ctx) {
|
|
108
|
+
const before = ctx.content.slice(0, offsetAt(ctx.content, ctx.position));
|
|
109
|
+
if (!before.includes("chant-lexicon-sql") && !ctx.content.includes("chant-lexicon-sql"))
|
|
110
|
+
return undefined;
|
|
111
|
+
const m = /\$\{\s*(?:([A-Za-z_$][\w$]*)\.(columns\.)?)?[\w$]*$/.exec(ctx.linePrefix);
|
|
112
|
+
if (!m)
|
|
113
|
+
return undefined;
|
|
114
|
+
// Inside a template: an odd number of backticks before the cursor.
|
|
115
|
+
if ((before.match(/`/g)?.length ?? 0) % 2 === 0)
|
|
116
|
+
return undefined;
|
|
117
|
+
// The template being written is not something it can refer to.
|
|
118
|
+
const self = locate(ctx.content, offsetAt(ctx.content, ctx.position), fileName(ctx.uri))?.found.node.parent;
|
|
119
|
+
const selfName = self && ts.isVariableDeclaration(self) && ts.isIdentifier(self.name) ? self.name.text : undefined;
|
|
120
|
+
const declared = declarations(ctx.content, fileName(ctx.uri)).filter((d) => d.name !== selfName);
|
|
121
|
+
const [, owner, columns] = m;
|
|
122
|
+
if (!owner) {
|
|
123
|
+
return declared.map((d) => ({
|
|
124
|
+
label: d.name,
|
|
125
|
+
kind: "resource",
|
|
126
|
+
detail: `${d.materialized ? "materialized view" : d.tag} ${d.sqlName}`,
|
|
127
|
+
documentation: d.columns.length ? `Columns: ${d.columns.map((c) => c.name).join(", ")}` : undefined,
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
|
+
const entity = declared.find((d) => d.name === owner);
|
|
131
|
+
if (!entity)
|
|
132
|
+
return [];
|
|
133
|
+
if (!columns)
|
|
134
|
+
return entity.columns.length ? [{ label: "columns", kind: "property", detail: `columns of ${entity.sqlName}` }] : [];
|
|
135
|
+
return entity.columns.map((c) => ({ label: c.name, kind: "property", detail: c.type ?? "column" }));
|
|
136
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { lexiconHover } from "@intentius/chant/lsp/lexicon-providers";
|
|
2
|
+
import { catalogIndex } from "./catalog.js";
|
|
3
|
+
import { postgresHover } from "./postgres.js";
|
|
4
|
+
import { registryIndex } from "./registry.js";
|
|
5
|
+
import { resolveReference } from "./refs.js";
|
|
6
|
+
import { expectAfter, locate, offsetAt, tokensBefore, wordAround } from "./template.js";
|
|
7
|
+
/**
|
|
8
|
+
* Hover for the sql lexicon.
|
|
9
|
+
*
|
|
10
|
+
* Inside a template, a word is looked up in the catalog where the SQL before it
|
|
11
|
+
* says what kind of word it is (an engine after `ENGINE =`, a type where a type
|
|
12
|
+
* goes, a setting after `SETTINGS`), and a function when it is followed by `(`.
|
|
13
|
+
* A `${ref}` shows the table, view or column it points at, with its type.
|
|
14
|
+
* Anywhere else, the entity classes from the generated registry.
|
|
15
|
+
*/
|
|
16
|
+
export function hover(ctx) {
|
|
17
|
+
const pg = postgresHover(ctx);
|
|
18
|
+
if (pg.handled)
|
|
19
|
+
return pg.info;
|
|
20
|
+
const at = offsetAt(ctx.content, ctx.position);
|
|
21
|
+
const where = locate(ctx.content, at, fileName(ctx.uri));
|
|
22
|
+
if (where) {
|
|
23
|
+
if (where.part < 0)
|
|
24
|
+
return where.expression < 0 ? undefined : reference(ctx, where.expression, where);
|
|
25
|
+
return catalogHover(where);
|
|
26
|
+
}
|
|
27
|
+
return lexiconHover(ctx, registryIndex(), entityHover);
|
|
28
|
+
}
|
|
29
|
+
const fileName = (uri) => (uri.startsWith("file://") ? decodeURIComponent(uri.slice(7)) : uri) || "file.ts";
|
|
30
|
+
function entityHover(className, entry) {
|
|
31
|
+
const [dialect] = entry.resourceType.split("::");
|
|
32
|
+
return { contents: `**${className}**\n\n${dialect} type: \`${entry.resourceType}\`` };
|
|
33
|
+
}
|
|
34
|
+
function reference(ctx, expression, where) {
|
|
35
|
+
const expr = where.found.expressions[expression];
|
|
36
|
+
const info = resolveReference(ctx.content, expr.getText(where.source), fileName(ctx.uri));
|
|
37
|
+
return info ? { contents: info } : undefined;
|
|
38
|
+
}
|
|
39
|
+
function catalogHover(where) {
|
|
40
|
+
const index = catalogIndex();
|
|
41
|
+
if (!index)
|
|
42
|
+
return undefined;
|
|
43
|
+
const text = where.found.parts[where.part];
|
|
44
|
+
const word = wordAround(text, where.offset);
|
|
45
|
+
const name = text.slice(word.start, word.end);
|
|
46
|
+
if (!name)
|
|
47
|
+
return undefined;
|
|
48
|
+
const sig = tokensBefore(where.found, where.part, word.start);
|
|
49
|
+
if (!sig)
|
|
50
|
+
return undefined;
|
|
51
|
+
const expect = expectAfter(sig, where.found.tag);
|
|
52
|
+
const followedByParen = /^\s*\(/.test(text.slice(word.end));
|
|
53
|
+
const code = (s) => `\`${s}\``;
|
|
54
|
+
let contents;
|
|
55
|
+
const function_ = () => {
|
|
56
|
+
const f = index.functions.get(name) ?? index.functions.get(name.toLowerCase());
|
|
57
|
+
if (!f || !followedByParen)
|
|
58
|
+
return undefined;
|
|
59
|
+
const alias = f.aliasOf ? `\n\nAlias of ${code(f.aliasOf)}.` : "";
|
|
60
|
+
return `**${f.name}**: ${f.aggregate ? "aggregate function" : "function"}${f.caseInsensitive ? ", case-insensitive" : ""}${alias}`;
|
|
61
|
+
};
|
|
62
|
+
switch (expect) {
|
|
63
|
+
case "engine": {
|
|
64
|
+
const e = where.found.tag === "database" ? index.databaseEngines.get(name) : index.engines.get(name);
|
|
65
|
+
if (e) {
|
|
66
|
+
const caps = where.found.tag === "database" ? [] : Object.entries(index.engines.get(name).capabilities).filter(([, on]) => on).map(([k]) => k);
|
|
67
|
+
contents = `**${e.name}** engine\n\n${code(e.syntax)}\n\n${e.summary}${caps.length ? `\n\nSupports: ${caps.join(", ")}.` : ""}`;
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case "type": {
|
|
72
|
+
const t = index.types.get(name) ?? index.types.get(name.toLowerCase());
|
|
73
|
+
if (t) {
|
|
74
|
+
contents = `**${t.name}**: ClickHouse type family${t.aliasOf ? `, alias of ${code(t.aliasOf)}` : ""}${t.caseInsensitive ? ", case-insensitive" : ""}`;
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case "codec": {
|
|
79
|
+
const c = index.codecs.get(name);
|
|
80
|
+
if (c)
|
|
81
|
+
contents = `**${c.name}** codec${c.experimental ? " (experimental)" : ""}\n\n${c.summary}`;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case "index-type": {
|
|
85
|
+
const i = index.indexTypes.get(name);
|
|
86
|
+
if (i)
|
|
87
|
+
contents = `**${i.name}** skip index\n\n${code(i.syntax)}\n\n${i.summary}`;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case "merge-tree-setting":
|
|
91
|
+
case "query-setting":
|
|
92
|
+
contents = settingHover(index, name, expect === "merge-tree-setting");
|
|
93
|
+
break;
|
|
94
|
+
case "function":
|
|
95
|
+
contents = function_();
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
// A later setting in a list follows its value, not a comma-or-SETTINGS: look it up by name when it is followed by `=`.
|
|
99
|
+
if (!contents && /^\s*=(?!=)/.test(text.slice(word.end)))
|
|
100
|
+
contents = settingHover(index, name, where.found.tag !== "view");
|
|
101
|
+
if (!contents)
|
|
102
|
+
contents = function_();
|
|
103
|
+
return contents ? { contents } : undefined;
|
|
104
|
+
}
|
|
105
|
+
function settingHover(index, name, mergeTreeFirst) {
|
|
106
|
+
const row = mergeTreeFirst
|
|
107
|
+
? (index.mergeTreeSettings.get(name) ?? index.querySettings.get(name))
|
|
108
|
+
: (index.querySettings.get(name) ?? index.mergeTreeSettings.get(name));
|
|
109
|
+
if (!row)
|
|
110
|
+
return undefined;
|
|
111
|
+
const scope = index.mergeTreeSettings.get(name) === row ? "MergeTree setting" : "query setting";
|
|
112
|
+
const range = row.min !== undefined || row.max !== undefined ? `, range ${row.min ?? ""}..${row.max ?? ""}` : "";
|
|
113
|
+
const flags = [row.obsolete ? "obsolete" : "", row.readonly ? "read-only" : "", row.tier !== "Production" ? row.tier.toLowerCase() : ""].filter(Boolean);
|
|
114
|
+
return `**${row.name}**: ${scope}\n\nType \`${row.type}\`, default \`${row.default || "''"}\`${range}${flags.length ? ` (${flags.join(", ")})` : ""}\n\n${row.summary}`;
|
|
115
|
+
}
|