@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.
Files changed (322) hide show
  1. package/dist/change-set.js +250 -0
  2. package/dist/clickhouse/access.js +722 -0
  3. package/dist/clickhouse/apply/apply.js +243 -0
  4. package/dist/clickhouse/apply/mutations.js +66 -0
  5. package/dist/clickhouse/apply/statements.js +545 -0
  6. package/dist/clickhouse/catalog-types.js +7 -0
  7. package/dist/clickhouse/container.js +152 -0
  8. package/dist/clickhouse/engine-syntax.js +159 -0
  9. package/dist/clickhouse/entities.js +593 -0
  10. package/dist/clickhouse/expressions.js +107 -0
  11. package/dist/clickhouse/http.js +55 -0
  12. package/dist/clickhouse/import/generator.js +192 -0
  13. package/dist/clickhouse/import/inlined.js +207 -0
  14. package/dist/clickhouse/import/ir.js +160 -0
  15. package/dist/clickhouse/import/live-export.js +131 -0
  16. package/dist/clickhouse/import/parser.js +17 -0
  17. package/dist/clickhouse/live/bind.js +116 -0
  18. package/dist/clickhouse/live/catalog.js +223 -0
  19. package/dist/clickhouse/live/describe-resources.js +91 -0
  20. package/dist/clickhouse/live/undeclarable.js +59 -0
  21. package/dist/clickhouse/overlays/codecs.js +57 -0
  22. package/dist/clickhouse/overlays/engines.js +101 -0
  23. package/dist/clickhouse/overlays/kinds.js +1 -0
  24. package/dist/clickhouse/overlays/projections.js +26 -0
  25. package/dist/clickhouse/overlays/skip-indexes.js +40 -0
  26. package/dist/clickhouse/overlays/ttl.js +34 -0
  27. package/dist/clickhouse/overlays/types.js +59 -0
  28. package/dist/clickhouse/ownership.js +83 -0
  29. package/dist/clickhouse/parser.js +674 -0
  30. package/dist/clickhouse/plan/commands.js +159 -0
  31. package/dist/clickhouse/plan/deep.js +345 -0
  32. package/dist/clickhouse/plan/diff.js +312 -0
  33. package/dist/clickhouse/plan/disruption.js +73 -0
  34. package/dist/clickhouse/plan/normalize.js +545 -0
  35. package/dist/clickhouse/plan/rebuild-handoff.js +57 -0
  36. package/dist/clickhouse/plan/report.js +26 -0
  37. package/dist/clickhouse/plan/rules.js +72 -0
  38. package/dist/clickhouse/plan/schema.js +67 -0
  39. package/dist/clickhouse/plan/server-format.js +35 -0
  40. package/dist/clickhouse/rebuild/backfill.js +265 -0
  41. package/dist/clickhouse/rebuild/engines.js +146 -0
  42. package/dist/clickhouse/rebuild/observe.js +260 -0
  43. package/dist/clickhouse/rebuild/op.js +152 -0
  44. package/dist/clickhouse/rebuild/partitions.js +65 -0
  45. package/dist/clickhouse/rebuild/receipts.js +120 -0
  46. package/dist/clickhouse/rebuild/replicas.js +121 -0
  47. package/dist/clickhouse/rebuild/shards.js +76 -0
  48. package/dist/clickhouse/rebuild/steps.js +383 -0
  49. package/dist/clickhouse/rebuild/verify.js +279 -0
  50. package/dist/clickhouse/row-types.js +151 -0
  51. package/dist/clickhouse/testing/fake-server.js +47 -0
  52. package/dist/clickhouse/testing/roundtrip-schema.js +25 -0
  53. package/dist/clickhouse/testing/writable-server.js +123 -0
  54. package/dist/clickhouse/tokens.js +23 -0
  55. package/dist/clickhouse/topology.js +463 -0
  56. package/dist/clickhouse/type-syntax.js +73 -0
  57. package/dist/clickhouse.js +25 -0
  58. package/dist/codegen/classifier-page.js +93 -0
  59. package/dist/codegen/clickhouse-module.js +249 -0
  60. package/dist/codegen/coverage-page.js +70 -0
  61. package/dist/codegen/docs-cli.js +3 -0
  62. package/dist/codegen/docs.js +222 -0
  63. package/dist/codegen/generate-cli.js +13 -0
  64. package/dist/codegen/generate.js +97 -0
  65. package/dist/codegen/package.js +28 -0
  66. package/dist/codegen/postgres-classifier-page.js +95 -0
  67. package/dist/codegen/postgres-module.js +418 -0
  68. package/dist/codegen/registry.js +52 -0
  69. package/dist/composites/audit-log-table.js +35 -0
  70. package/dist/composites/catalog.js +524 -0
  71. package/dist/composites/cdc-mirror.js +32 -0
  72. package/dist/composites/clickhouse.js +6 -0
  73. package/dist/composites/events-table.js +26 -0
  74. package/dist/composites/index.js +8 -0
  75. package/dist/composites/join-table.js +25 -0
  76. package/dist/composites/postgres.js +6 -0
  77. package/dist/composites/refreshed-view.js +24 -0
  78. package/dist/composites/replacing-table.js +28 -0
  79. package/dist/composites/rollup-view.js +29 -0
  80. package/dist/composites/sharded-table.js +27 -0
  81. package/dist/composites/soft-delete-table.js +34 -0
  82. package/dist/composites/tenant-table.js +26 -0
  83. package/dist/composites/typed.js +8 -0
  84. package/dist/config.js +206 -0
  85. package/dist/core/apply.js +93 -0
  86. package/dist/core/classifier.js +115 -0
  87. package/dist/core/container.js +69 -0
  88. package/dist/core/cursor.js +261 -0
  89. package/dist/core/diff.js +48 -0
  90. package/dist/core/entity.js +58 -0
  91. package/dist/core/find-templates.js +118 -0
  92. package/dist/core/foreign-tables.js +27 -0
  93. package/dist/core/handoff.js +32 -0
  94. package/dist/core/index.js +64 -0
  95. package/dist/core/interpolation.js +191 -0
  96. package/dist/core/normalize.js +43 -0
  97. package/dist/core/ownership.js +119 -0
  98. package/dist/core/receipts.js +22 -0
  99. package/dist/core/references.js +61 -0
  100. package/dist/core/statements.js +42 -0
  101. package/dist/core/template.js +79 -0
  102. package/dist/core/testing/http-stub.js +21 -0
  103. package/dist/core/tokens.js +202 -0
  104. package/dist/core/typed.js +40 -0
  105. package/dist/core/undeclarable.js +46 -0
  106. package/dist/coverage.js +161 -0
  107. package/dist/detect.js +32 -0
  108. package/dist/dialects.js +18 -0
  109. package/dist/files/build-root.js +72 -0
  110. package/dist/files/clickhouse.js +138 -0
  111. package/dist/files/common.js +58 -0
  112. package/dist/files/postgres.js +389 -0
  113. package/dist/generated/clickhouse-types.js +5 -0
  114. package/dist/generated/clickhouse.js +4361 -0
  115. package/dist/generated/postgres-types.js +227 -0
  116. package/dist/generated/postgres.js +3667 -0
  117. package/dist/import-generator.js +14 -0
  118. package/dist/import-parser.js +71 -0
  119. package/dist/index.js +24 -0
  120. package/dist/init-templates.js +290 -0
  121. package/dist/integrity.json +2 -2
  122. package/dist/lint/audit-catalog.js +31 -0
  123. package/dist/lint/audit-lineage.js +22 -0
  124. package/dist/lint/post-synth/audit-entries.js +34 -0
  125. package/dist/lint/post-synth/clickhouse-helpers.js +79 -0
  126. package/dist/lint/post-synth/clickhouse-type-check.js +162 -0
  127. package/dist/lint/post-synth/index.js +112 -0
  128. package/dist/lint/post-synth/postgres-audit-entries.js +35 -0
  129. package/dist/lint/post-synth/postgres-helpers.js +65 -0
  130. package/dist/lint/post-synth/postgres-names.js +507 -0
  131. package/dist/lint/post-synth/sql-helpers.js +23 -0
  132. package/dist/lint/post-synth/sql101.js +59 -0
  133. package/dist/lint/post-synth/sql102.js +34 -0
  134. package/dist/lint/post-synth/sqlch101.js +34 -0
  135. package/dist/lint/post-synth/sqlch102.js +23 -0
  136. package/dist/lint/post-synth/sqlch103.js +45 -0
  137. package/dist/lint/post-synth/sqlch104.js +41 -0
  138. package/dist/lint/post-synth/sqlch105.js +36 -0
  139. package/dist/lint/post-synth/sqlch106.js +27 -0
  140. package/dist/lint/post-synth/sqlch107.js +39 -0
  141. package/dist/lint/post-synth/sqlch108.js +28 -0
  142. package/dist/lint/post-synth/sqlch109.js +21 -0
  143. package/dist/lint/post-synth/sqlch110.js +31 -0
  144. package/dist/lint/post-synth/sqlch111.js +41 -0
  145. package/dist/lint/post-synth/sqlch112.js +28 -0
  146. package/dist/lint/post-synth/sqlch113.js +19 -0
  147. package/dist/lint/post-synth/sqlch114.js +26 -0
  148. package/dist/lint/post-synth/sqlch115.js +30 -0
  149. package/dist/lint/post-synth/sqlch116.js +22 -0
  150. package/dist/lint/post-synth/sqlch117.js +26 -0
  151. package/dist/lint/post-synth/sqlch118.js +27 -0
  152. package/dist/lint/post-synth/sqlch119.js +32 -0
  153. package/dist/lint/post-synth/sqlch120.js +20 -0
  154. package/dist/lint/post-synth/sqlch121.js +11 -0
  155. package/dist/lint/post-synth/sqlch122.js +11 -0
  156. package/dist/lint/post-synth/sqlch123.js +10 -0
  157. package/dist/lint/post-synth/sqlch124.js +21 -0
  158. package/dist/lint/post-synth/sqlch125.js +55 -0
  159. package/dist/lint/post-synth/sqlch126.js +62 -0
  160. package/dist/lint/post-synth/sqlch127.js +132 -0
  161. package/dist/lint/post-synth/sqlpg101.js +25 -0
  162. package/dist/lint/post-synth/sqlpg102.js +35 -0
  163. package/dist/lint/post-synth/sqlpg103.js +23 -0
  164. package/dist/lint/post-synth/sqlpg104.js +23 -0
  165. package/dist/lint/post-synth/sqlpg105.js +22 -0
  166. package/dist/lint/post-synth/sqlpg106.js +22 -0
  167. package/dist/lint/post-synth/sqlpg107.js +22 -0
  168. package/dist/lint/post-synth/sqlpg108.js +28 -0
  169. package/dist/lint/post-synth/sqlpg109.js +39 -0
  170. package/dist/lint/post-synth/sqlpg110.js +38 -0
  171. package/dist/lint/post-synth/sqlpg111.js +23 -0
  172. package/dist/lint/post-synth/sqlpg112.js +25 -0
  173. package/dist/lint/post-synth/sqlpg113.js +29 -0
  174. package/dist/lint/post-synth/sqlpg114.js +39 -0
  175. package/dist/lint/post-synth/sqlpg115.js +48 -0
  176. package/dist/lint/post-synth/sqlpg116.js +26 -0
  177. package/dist/lint/post-synth/sqlpg117.js +24 -0
  178. package/dist/lint/post-synth/sqlpg118.js +22 -0
  179. package/dist/lint/post-synth/sqlpg119.js +41 -0
  180. package/dist/lint/post-synth/sqlpg120.js +83 -0
  181. package/dist/lint/post-synth/sqlpg121.js +33 -0
  182. package/dist/lint/post-synth/sqlpg122.js +27 -0
  183. package/dist/lint/post-synth/sqlpg123.js +69 -0
  184. package/dist/lint/post-synth/sqlpg124.js +50 -0
  185. package/dist/lint/post-synth/sqlpg125.js +41 -0
  186. package/dist/lint/post-synth/sqlpg126.js +38 -0
  187. package/dist/lint/rules/index.js +12 -0
  188. package/dist/lint/rules/postgres-templates.js +27 -0
  189. package/dist/lint/rules/sqlch001.js +79 -0
  190. package/dist/lint/rules/sqlch002.js +124 -0
  191. package/dist/lint/rules/sqlch003.js +69 -0
  192. package/dist/lint/rules/sqlch004.js +83 -0
  193. package/dist/lint/rules/sqlpg001.js +68 -0
  194. package/dist/lint/rules/sqlpg002.js +69 -0
  195. package/dist/lint/rules/sqlpg003.js +58 -0
  196. package/dist/lint/rules/sqlpg004.js +77 -0
  197. package/dist/lint/rules/sqlpg005.js +86 -0
  198. package/dist/lint/rules/templates.js +25 -0
  199. package/dist/lint/rules/typed-names.js +132 -0
  200. package/dist/live-dialect.js +32 -0
  201. package/dist/lsp/catalog.js +44 -0
  202. package/dist/lsp/completions.js +136 -0
  203. package/dist/lsp/hover.js +115 -0
  204. package/dist/lsp/postgres-catalog.js +176 -0
  205. package/dist/lsp/postgres-template.js +171 -0
  206. package/dist/lsp/postgres.js +266 -0
  207. package/dist/lsp/refs.js +60 -0
  208. package/dist/lsp/registry.d.ts.map +1 -1
  209. package/dist/lsp/registry.js +19 -0
  210. package/dist/lsp/template.js +143 -0
  211. package/dist/manifest.json +1 -1
  212. package/dist/mcp/index.d.ts.map +1 -1
  213. package/dist/mcp/index.js +293 -0
  214. package/dist/mcp/postgres.js +117 -0
  215. package/dist/migration-statements.js +243 -0
  216. package/dist/op/activities/clickhouse-apply.js +83 -0
  217. package/dist/op/activities/clickhouse-emulator.js +53 -0
  218. package/dist/op/activities/clickhouse-rebuild.js +119 -0
  219. package/dist/op/activities/index.js +26 -0
  220. package/dist/op/activities/postgres-apply.js +126 -0
  221. package/dist/op/activities/postgres-emulator.js +70 -0
  222. package/dist/op/activities/postgres-migration.js +156 -0
  223. package/dist/op/activities/sql-exec.js +57 -0
  224. package/dist/op/activity-contracts.js +103 -0
  225. package/dist/package-cli.js +14 -0
  226. package/dist/plugin.js +259 -0
  227. package/dist/postgres/access/acl.js +344 -0
  228. package/dist/postgres/access/changes.js +30 -0
  229. package/dist/postgres/access/import.js +61 -0
  230. package/dist/postgres/access/live.js +96 -0
  231. package/dist/postgres/apply/apply.js +557 -0
  232. package/dist/postgres/apply/statements.js +882 -0
  233. package/dist/postgres/catalog-types.js +18 -0
  234. package/dist/postgres/container.js +58 -0
  235. package/dist/postgres/entities.js +1390 -0
  236. package/dist/postgres/entity-types.js +28 -0
  237. package/dist/postgres/import/generator.js +283 -0
  238. package/dist/postgres/import/ir.js +53 -0
  239. package/dist/postgres/import/live-export.js +83 -0
  240. package/dist/postgres/keywords.js +32 -0
  241. package/dist/postgres/live/bind.js +130 -0
  242. package/dist/postgres/live/catalog-kinds.js +301 -0
  243. package/dist/postgres/live/catalog.js +644 -0
  244. package/dist/postgres/live/client.js +151 -0
  245. package/dist/postgres/live/describe-resources.js +135 -0
  246. package/dist/postgres/live/undeclarable.js +111 -0
  247. package/dist/postgres/migrate/backfill.js +170 -0
  248. package/dist/postgres/migrate/batches.js +122 -0
  249. package/dist/postgres/migrate/carry.js +518 -0
  250. package/dist/postgres/migrate/handoff.js +59 -0
  251. package/dist/postgres/migrate/names.js +61 -0
  252. package/dist/postgres/migrate/observe.js +360 -0
  253. package/dist/postgres/migrate/op.js +178 -0
  254. package/dist/postgres/migrate/receipts.js +117 -0
  255. package/dist/postgres/migrate/replication.js +106 -0
  256. package/dist/postgres/migrate/steps.js +514 -0
  257. package/dist/postgres/overlays/columns.js +31 -0
  258. package/dist/postgres/overlays/constraints.js +52 -0
  259. package/dist/postgres/overlays/extension-objects.js +88 -0
  260. package/dist/postgres/overlays/indexes.js +25 -0
  261. package/dist/postgres/overlays/kinds.js +2 -0
  262. package/dist/postgres/overlays/partitioning.js +23 -0
  263. package/dist/postgres/overlays/relations.js +25 -0
  264. package/dist/postgres/overlays/storage.js +65 -0
  265. package/dist/postgres/overlays/type-ddl.js +36 -0
  266. package/dist/postgres/overlays/types.js +74 -0
  267. package/dist/postgres/parser.js +1824 -0
  268. package/dist/postgres/plan/commands.js +137 -0
  269. package/dist/postgres/plan/deep.js +324 -0
  270. package/dist/postgres/plan/diff.js +499 -0
  271. package/dist/postgres/plan/disruption.js +64 -0
  272. package/dist/postgres/plan/normalize.js +889 -0
  273. package/dist/postgres/plan/report.js +21 -0
  274. package/dist/postgres/plan/rules.js +115 -0
  275. package/dist/postgres/plan/schema.js +105 -0
  276. package/dist/postgres/plan/server-normalize.js +166 -0
  277. package/dist/postgres/providers/aurora.js +44 -0
  278. package/dist/postgres/providers/azure.js +53 -0
  279. package/dist/postgres/providers/cloud-sql.js +41 -0
  280. package/dist/postgres/providers/index.js +73 -0
  281. package/dist/postgres/providers/neon.js +44 -0
  282. package/dist/postgres/providers/rds.js +47 -0
  283. package/dist/postgres/providers/supabase.js +45 -0
  284. package/dist/postgres/providers/types.js +21 -0
  285. package/dist/postgres/row-types.js +107 -0
  286. package/dist/postgres/testing/fake-client.js +34 -0
  287. package/dist/postgres/testing/migration.js +48 -0
  288. package/dist/postgres/testing/roundtrip-schema.js +64 -0
  289. package/dist/postgres/testing/server.js +47 -0
  290. package/dist/postgres/testing/writable-server.js +160 -0
  291. package/dist/postgres/tokens.js +41 -0
  292. package/dist/postgres.js +15 -0
  293. package/dist/read/clickhouse.js +82 -0
  294. package/dist/read/objects.js +44 -0
  295. package/dist/read/postgres.js +104 -0
  296. package/dist/read.js +52 -0
  297. package/dist/receipts.js +207 -0
  298. package/dist/serializer.js +172 -0
  299. package/dist/skill-defs.js +93 -0
  300. package/dist/spec/catalog.js +191 -0
  301. package/dist/spec/fetch.d.ts.map +1 -1
  302. package/dist/spec/fetch.js +90 -0
  303. package/dist/spec/pin.js +42 -0
  304. package/dist/spec/postgres-catalog.js +171 -0
  305. package/dist/spec/postgres-fetch.js +91 -0
  306. package/dist/spec/postgres-pin.js +82 -0
  307. package/dist/sql-files.js +206 -0
  308. package/dist/sql-types-command.js +97 -0
  309. package/dist/sql-types.js +323 -0
  310. package/dist/token-source.js +156 -0
  311. package/dist/validate-cli.js +5 -0
  312. package/dist/validate.js +151 -0
  313. package/dist/workspace-outputs.js +191 -0
  314. package/package.json +7 -7
  315. package/src/codegen/package.ts +2 -2
  316. package/src/lint/rules/typescript-7.test.ts +2 -1
  317. package/src/lsp/catalog.ts +2 -2
  318. package/src/lsp/postgres-catalog.ts +2 -2
  319. package/src/lsp/registry.ts +2 -1
  320. package/src/mcp/index.ts +4 -2
  321. package/src/spec/fetch.ts +2 -2
  322. package/src/spec/postgres-fetch.ts +2 -2
@@ -0,0 +1,176 @@
1
+ /**
2
+ * The Postgres catalogs the editor and the MCP tools read: the committed
3
+ * snapshots of the pinned servers (one per supported major), parsed once.
4
+ * No server is needed; the snapshots are what generation reads too.
5
+ *
6
+ * A name's availability is read straight off the snapshots: it is in a major
7
+ * when that major's snapshot lists it, so `since` and `until` here agree with
8
+ * the generated types' `@since` / `@until`.
9
+ */
10
+ import { existsSync, readFileSync } from "fs";
11
+ import { dirname, join, resolve } from "path";
12
+ import { moduleSourceDir } from "@intentius/chant/module-source";
13
+ import { parseCatalog } from "../spec/postgres-catalog.js";
14
+ import { POSTGRES_LATEST_MAJOR, POSTGRES_MAJORS } from "../spec/postgres-pin.js";
15
+ import { STORAGE_PARAMETER_TYPES } from "../postgres/overlays/storage.js";
16
+ const SPEC = join(moduleSourceDir(import.meta.url), "..", "spec");
17
+ let cached = null;
18
+ /** Every supported major's catalog, or undefined when a snapshot cannot be read. */
19
+ export function postgresCatalogs() {
20
+ if (cached)
21
+ return cached;
22
+ try {
23
+ const map = new Map();
24
+ for (const major of POSTGRES_MAJORS)
25
+ map.set(major, parseCatalog(readFileSync(join(SPEC, `postgres-catalog-${major}.snapshot.json`), "utf-8")));
26
+ cached = map;
27
+ return map;
28
+ }
29
+ catch {
30
+ return undefined;
31
+ }
32
+ }
33
+ export const SECTIONS = ["types", "accessMethods", "functions", "keywords", "settings", "extensions", "storageParameters"];
34
+ /** SQL spellings with no `pg_type` row of their own. */
35
+ const SPELLINGS = {
36
+ int: "integer",
37
+ dec: "numeric",
38
+ decimal: "numeric",
39
+ float: "double precision",
40
+ serial: "integer",
41
+ bigserial: "bigint",
42
+ smallserial: "smallint",
43
+ serial4: "integer",
44
+ serial8: "bigint",
45
+ serial2: "smallint",
46
+ };
47
+ const unquote = (s) => (s.startsWith('"') ? s.slice(1, -1) : s);
48
+ function rowsOf(section, catalog) {
49
+ switch (section) {
50
+ case "types": {
51
+ const out = [];
52
+ const have = new Set();
53
+ for (const t of catalog.types) {
54
+ if (t.kind === "p" || t.name.startsWith("_"))
55
+ continue;
56
+ const sqlName = unquote(t.sqlName);
57
+ have.add(sqlName);
58
+ out.push({ key: sqlName, row: { sqlName, catalogName: t.name, kind: t.kind, category: t.category } });
59
+ if (t.name !== sqlName) {
60
+ have.add(t.name);
61
+ out.push({ key: t.name, row: { sqlName, catalogName: t.name, kind: t.kind, category: t.category, aliasOf: sqlName }, detail: `alias of ${sqlName}` });
62
+ }
63
+ }
64
+ for (const [alias, target] of Object.entries(SPELLINGS)) {
65
+ const base = out.find((o) => o.key === target)?.row;
66
+ if (base && !have.has(alias))
67
+ out.push({ key: alias, row: { ...base, aliasOf: target }, detail: `alias of ${target}` });
68
+ }
69
+ return out;
70
+ }
71
+ case "accessMethods":
72
+ return catalog.accessMethods.map((a) => ({
73
+ key: a.name,
74
+ row: { name: a.name, type: a.type === "i" ? "index" : "table", properties: a.properties ?? [] },
75
+ }));
76
+ case "functions":
77
+ return catalog.functions.filter((f) => !f.internal).map((f) => ({ key: f.name, row: { name: f.name, kinds: f.kinds, overloads: f.overloads } }));
78
+ case "keywords":
79
+ return catalog.keywords.map((k) => ({ key: k.word, row: k }));
80
+ case "settings":
81
+ return catalog.settings.map((s) => ({ key: s.name, row: s }));
82
+ case "extensions":
83
+ return catalog.extensions.map((e) => ({ key: e.name, row: e }));
84
+ case "storageParameters": {
85
+ const by = new Map();
86
+ for (const [target, names] of Object.entries(catalog.storageParameters))
87
+ for (const n of names)
88
+ by.set(n, [...(by.get(n) ?? []), target]);
89
+ return [...by].map(([name, targets]) => ({ key: name, row: { name, targets, type: STORAGE_PARAMETER_TYPES[name] } }));
90
+ }
91
+ }
92
+ }
93
+ const oldest = POSTGRES_MAJORS[0];
94
+ /** `since`/`until` for the majors a name is in. */
95
+ export function rangeOf(majors) {
96
+ const first = Math.min(...majors);
97
+ const last = Math.max(...majors);
98
+ return { ...(first > oldest ? { since: first } : {}), ...(last < POSTGRES_LATEST_MAJOR ? { until: last } : {}) };
99
+ }
100
+ /** "14 to 17", "16 and later" or "" when in every major. */
101
+ export function describeRange(r) {
102
+ if (r.since !== undefined && r.until !== undefined)
103
+ return `Postgres ${r.since} to ${r.until}`;
104
+ if (r.since !== undefined)
105
+ return `Postgres ${r.since} and later`;
106
+ if (r.until !== undefined)
107
+ return `up to Postgres ${r.until}`;
108
+ return "";
109
+ }
110
+ const sections = new Map();
111
+ /** A section across every major, one entry per name. */
112
+ export function section(name) {
113
+ const hit = sections.get(name);
114
+ if (hit)
115
+ return hit;
116
+ const catalogs = postgresCatalogs();
117
+ if (!catalogs)
118
+ return undefined;
119
+ const map = new Map();
120
+ for (const major of POSTGRES_MAJORS) {
121
+ for (const { key, row, detail } of rowsOf(name, catalogs.get(major))) {
122
+ const e = map.get(key);
123
+ if (e) {
124
+ e.row = row;
125
+ e.majors.push(major);
126
+ if (detail)
127
+ e.detail = detail;
128
+ }
129
+ else
130
+ map.set(key, { name: key, row, majors: [major], range: {}, ...(detail ? { detail } : {}) });
131
+ }
132
+ }
133
+ for (const e of map.values())
134
+ e.range = rangeOf(e.majors);
135
+ sections.set(name, map);
136
+ return map;
137
+ }
138
+ /** The entries of a section that exist at `major`. */
139
+ export function entriesAt(name, major) {
140
+ const s = section(name);
141
+ return s ? [...s.values()].filter((e) => e.majors.includes(major)) : [];
142
+ }
143
+ /** The entry for `name` at `major`, matching case-insensitively for names Postgres folds. */
144
+ export function entryAt(name, key, major) {
145
+ const s = section(name);
146
+ const e = s?.get(key) ?? s?.get(key.toLowerCase());
147
+ return e && e.majors.includes(major) ? e : undefined;
148
+ }
149
+ /** `postgresMajor` in the nearest `chant.config.*` above a file, when it names a supported one. */
150
+ export function configuredMajor(fileName) {
151
+ let dir = dirname(resolve(fileName));
152
+ for (let i = 0; i < 12; i++) {
153
+ for (const name of ["chant.config.ts", "chant.config.json", "chant.config.mts", "chant.config.js", "chant.config.mjs"]) {
154
+ const file = join(dir, name);
155
+ if (!existsSync(file))
156
+ continue;
157
+ try {
158
+ const m = /["']?postgresMajor["']?\s*:\s*(\d+)/.exec(readFileSync(file, "utf-8"));
159
+ const major = m ? Number(m[1]) : undefined;
160
+ return major !== undefined && POSTGRES_MAJORS.includes(major) ? major : undefined;
161
+ }
162
+ catch {
163
+ return undefined;
164
+ }
165
+ }
166
+ const up = dirname(dir);
167
+ if (up === dir)
168
+ break;
169
+ dir = up;
170
+ }
171
+ return undefined;
172
+ }
173
+ /** The major a file is written for: the project's configured one, else the newest. */
174
+ export function majorFor(fileName) {
175
+ return configuredMajor(fileName) ?? POSTGRES_LATEST_MAJOR;
176
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Finding where the cursor is inside a Postgres template, and what the SQL
3
+ * before it says should come next. Shares the template finder with the lint
4
+ * rules, so the editor and `chant lint` agree on which tags are Postgres's.
5
+ */
6
+ import { ts } from "@intentius/chant/lint/typescript";
7
+ import { isTrivia, tokenize } from "../postgres/tokens.js";
8
+ import { parseStatements } from "../postgres/parser.js";
9
+ import { POSTGRES_TEMPLATE_SOURCES, findPostgresTemplates } from "../lint/rules/postgres-templates.js";
10
+ import { locateIn } from "../core/find-templates.js";
11
+ /** The Postgres template the source offset `at` is inside, and where in it. */
12
+ export function locatePostgres(content, at, fileName = "file.ts") {
13
+ return locateIn(content, at, POSTGRES_TEMPLATE_SOURCES, fileName);
14
+ }
15
+ /** Significant tokens of the template up to `offset` in `part`; undefined when the text before it does not tokenize. */
16
+ export function postgresTokensBefore(found, part, offset) {
17
+ try {
18
+ const parts = [...found.parts.slice(0, part), found.parts[part].slice(0, offset)];
19
+ return tokenize(parts).filter((t) => !isTrivia(t));
20
+ }
21
+ catch {
22
+ return undefined;
23
+ }
24
+ }
25
+ const upper = (t) => (t?.kind === "ident" ? t.text.toUpperCase() : "");
26
+ const isPunct = (t, text) => t?.kind === "punct" && t.text === text;
27
+ const EXPRESSION_AFTER = new Set(["DEFAULT", "WHERE", "SELECT", "AND", "OR", "WHEN", "THEN", "ELSE", "HAVING", "BY", "ON", "NOT", "CHECK", "AS", "USING"]);
28
+ const EXPRESSION_CONTEXT = new Set(["SELECT", "DEFAULT", "CHECK", "WHERE", "GENERATED", "HAVING"]);
29
+ const NOT_A_COLUMN = new Set(["CONSTRAINT", "PRIMARY", "UNIQUE", "CHECK", "FOREIGN", "EXCLUDE", "LIKE"]);
30
+ /** What the SQL before the cursor expects; undefined where a name of the user's own goes. */
31
+ export function postgresExpectAfter(sig, tag) {
32
+ const last = sig[sig.length - 1];
33
+ if (!last)
34
+ return undefined;
35
+ const prev = sig[sig.length - 2];
36
+ if (tag === "extension" && (upper(last) === "EXTENSION" || upper(last) === "EXISTS"))
37
+ return "extension";
38
+ if (upper(last) === "USING") {
39
+ if (tag === "index" || upper(prev) === "EXCLUDE" || (prev === undefined ? false : sig.some((t) => upper(t) === "EXCLUDE")))
40
+ return "index-method";
41
+ return tag === "table" || tag === "view" ? "table-method" : undefined;
42
+ }
43
+ if (last.kind === "op" && last.text === "::")
44
+ return "type";
45
+ if ((tag === "domain" || tag === "sequence") && upper(last) === "AS")
46
+ return "type";
47
+ const open = [];
48
+ sig.forEach((t, i) => {
49
+ if (isPunct(t, "("))
50
+ open.push(i);
51
+ else if (isPunct(t, ")"))
52
+ open.pop();
53
+ });
54
+ const opener = open[open.length - 1];
55
+ const before = opener === undefined ? undefined : sig[opener - 1];
56
+ const afterOpenOrComma = isPunct(last, "(") || isPunct(last, ",");
57
+ if (afterOpenOrComma && opener !== undefined && upper(before) === "WITH")
58
+ return "storage-parameter";
59
+ // A table's column list: a name goes first, then a type.
60
+ const columnList = tag === "table" && open.length === 1 && opener === sig.findIndex((t) => isPunct(t, "("));
61
+ if (columnList) {
62
+ if (afterOpenOrComma)
63
+ return undefined;
64
+ if ((last.kind === "ident" || last.kind === "qident") && (isPunct(prev, "(") || isPunct(prev, ",")) && !NOT_A_COLUMN.has(upper(last)))
65
+ return "type";
66
+ }
67
+ if (EXPRESSION_AFTER.has(upper(last)))
68
+ return "function";
69
+ if ((afterOpenOrComma || last.kind === "op") && sig.some((t) => EXPRESSION_CONTEXT.has(upper(t))))
70
+ return "function";
71
+ if (afterOpenOrComma && !columnList)
72
+ return undefined;
73
+ if (last.kind === "ident" || last.kind === "qident" || isPunct(last, ")"))
74
+ return "keyword";
75
+ return undefined;
76
+ }
77
+ /** The storage-parameter target a template's `WITH (...)` is for. */
78
+ export function storageTarget(sig, tag) {
79
+ if (tag === "index") {
80
+ const i = sig.findIndex((t) => upper(t) === "USING");
81
+ const method = i >= 0 && sig[i + 1]?.kind === "ident" ? sig[i + 1].text.toLowerCase() : "btree";
82
+ return `index:${method}`;
83
+ }
84
+ if (tag === "view")
85
+ return sig.some((t) => upper(t) === "MATERIALIZED") ? "materialized view" : "view";
86
+ return "table";
87
+ }
88
+ function spanText(found, tokens, span, source) {
89
+ if (!span || span.to <= span.from)
90
+ return undefined;
91
+ return tokens
92
+ .slice(span.from, span.to)
93
+ .map((t) => (t.kind === "ref" ? `\${${found.expressions[t.part]?.getText(source) ?? "..."}}` : t.text))
94
+ .join("")
95
+ .trim();
96
+ }
97
+ /** Every `const x = table\`...\`` (and the other tags) in the content that parses, by const name. */
98
+ export function postgresDeclarations(content, fileName = "file.ts") {
99
+ const source = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true);
100
+ const out = [];
101
+ for (const found of findPostgresTemplates(source)) {
102
+ const decl = found.node.parent;
103
+ if (!decl || !ts.isVariableDeclaration(decl) || !ts.isIdentifier(decl.name))
104
+ continue;
105
+ try {
106
+ const tokens = tokenize(found.parts);
107
+ const node = parseStatements(tokens)[0];
108
+ if (!node)
109
+ continue;
110
+ const text = (span) => spanText(found, tokens, span, source);
111
+ let sqlName;
112
+ let kind = found.tag;
113
+ let columns = [];
114
+ switch (node.statement) {
115
+ case "table":
116
+ sqlName = text(node.name.span);
117
+ columns = node.columns.map((c) => ({ name: c.name || text(c.nameSpan) || "", ...(text(c.type) ? { type: text(c.type) } : {}) }));
118
+ break;
119
+ case "view":
120
+ sqlName = text(node.name.span);
121
+ kind = node.materialized ? "materialized view" : "view";
122
+ columns = node.columnNames.map((c) => ({ name: c.name || text(c.span) || "" }));
123
+ break;
124
+ case "enum":
125
+ sqlName = text(node.name.span);
126
+ kind = "enum type";
127
+ break;
128
+ case "composite":
129
+ case "range":
130
+ sqlName = text(node.name.span);
131
+ kind = `${node.statement} type`;
132
+ break;
133
+ case "aggregate":
134
+ case "collation":
135
+ sqlName = text(node.name.span);
136
+ kind = node.statement;
137
+ break;
138
+ case "eventTrigger":
139
+ sqlName = text(node.name.span);
140
+ kind = "event trigger";
141
+ break;
142
+ case "index":
143
+ sqlName = node.name ? text(node.name.span) : undefined;
144
+ break;
145
+ case "schema":
146
+ sqlName = node.name ? text(node.name.span) : undefined;
147
+ break;
148
+ case "sequence":
149
+ case "domain":
150
+ case "extension":
151
+ sqlName = text(node.name.span);
152
+ break;
153
+ case "function":
154
+ case "procedure":
155
+ case "trigger":
156
+ case "policy":
157
+ case "role":
158
+ sqlName = text(node.name.span);
159
+ kind = node.statement;
160
+ break;
161
+ default:
162
+ continue;
163
+ }
164
+ out.push({ name: decl.name.text, tag: found.tag, kind, sqlName: sqlName ?? decl.name.text, columns });
165
+ }
166
+ catch {
167
+ // A template that does not parse is SQLPG001's to report; it has nothing to offer.
168
+ }
169
+ }
170
+ return out;
171
+ }
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Completions and hover inside Postgres templates (chant #3283). Words come
3
+ * from the committed catalogs of the pinned servers, limited to what the
4
+ * project's major has (`sql.postgresMajor`, else the newest). `${` offers the
5
+ * objects declared in the file and `${t.columns.` their columns with types.
6
+ *
7
+ * Both entry points return undefined when the cursor is not in a Postgres
8
+ * template, so the ClickHouse provider (and the generic registry one) answer.
9
+ */
10
+ import { existsSync, readFileSync } from "fs";
11
+ import { dirname, resolve } from "path";
12
+ import { ts } from "@intentius/chant/lint/typescript";
13
+ import { describeRange, entriesAt, entryAt, majorFor, } from "./postgres-catalog.js";
14
+ import { locatePostgres, postgresDeclarations, postgresExpectAfter, postgresTokensBefore, storageTarget, } from "./postgres-template.js";
15
+ import { offsetAt, wordAround } from "../core/find-templates.js";
16
+ const LIMIT = 200;
17
+ const KEYWORD_CATEGORY = { U: "unreserved", C: "column name", T: "type or function name", R: "reserved" };
18
+ const fileName = (uri) => (uri.startsWith("file://") ? decodeURIComponent(uri.slice(7)) : uri) || "file.ts";
19
+ const code = (s) => `\`${s}\``;
20
+ const matches = (name, prefix) => prefix === "" || name.toLowerCase().startsWith(prefix.toLowerCase());
21
+ const note = (e) => {
22
+ const r = describeRange(e.range);
23
+ return r ? ` (${r})` : "";
24
+ };
25
+ /** Completions when the cursor is in a Postgres template; undefined when it is not. */
26
+ export function postgresCompletions(ctx) {
27
+ const file = fileName(ctx.uri);
28
+ const at = offsetAt(ctx.content, ctx.position);
29
+ const where = locatePostgres(ctx.content, at, file);
30
+ if (!where)
31
+ return undefined;
32
+ const refs = referenceCompletions(ctx, where, file);
33
+ if (refs)
34
+ return refs;
35
+ if (where.part < 0)
36
+ return [];
37
+ const text = where.found.parts[where.part];
38
+ const word = wordAround(text, where.offset);
39
+ const sig = postgresTokensBefore(where.found, where.part, word.start);
40
+ if (!sig)
41
+ return [];
42
+ const expect = postgresExpectAfter(sig, where.found.tag);
43
+ if (!expect)
44
+ return [];
45
+ const prefix = text.slice(word.start, where.offset);
46
+ return fromCatalog(expect, prefix, majorFor(file), storageTarget(sig, where.found.tag));
47
+ }
48
+ function fromCatalog(expect, prefix, major, target) {
49
+ const items = [];
50
+ const add = (item) => {
51
+ if (items.length < LIMIT)
52
+ items.push(item);
53
+ };
54
+ switch (expect) {
55
+ case "type":
56
+ for (const e of entriesAt("types", major))
57
+ if (matches(e.name, prefix) && e.row.kind !== "m")
58
+ add({ label: e.name, kind: "value", detail: (e.detail ?? `type, category ${e.row.category}`) + note(e) });
59
+ break;
60
+ case "index-method":
61
+ case "table-method": {
62
+ const want = expect === "index-method" ? "index" : "table";
63
+ for (const e of entriesAt("accessMethods", major))
64
+ if (e.row.type === want && matches(e.name, prefix))
65
+ add({ label: e.name, kind: "value", detail: `${want} access method${note(e)}` });
66
+ break;
67
+ }
68
+ case "storage-parameter":
69
+ for (const e of entriesAt("storageParameters", major)) {
70
+ if (!e.row.targets.includes(target) || !matches(e.name, prefix))
71
+ continue;
72
+ add({ label: e.name, insertText: `${e.name} = `, kind: "property", detail: storageType(e.row) + note(e) });
73
+ }
74
+ break;
75
+ case "extension":
76
+ for (const e of entriesAt("extensions", major))
77
+ if (matches(e.name, prefix))
78
+ add({ label: e.name, kind: "value", detail: `extension ${e.row.defaultVersion}`, documentation: e.row.description ?? undefined });
79
+ break;
80
+ case "function":
81
+ if (prefix === "")
82
+ break;
83
+ for (const e of entriesAt("functions", major))
84
+ if (matches(e.name, prefix))
85
+ add({ label: e.name, kind: "intrinsic", detail: functionKind(e.row) + note(e) });
86
+ break;
87
+ case "keyword":
88
+ if (prefix.length < 2)
89
+ break;
90
+ for (const e of entriesAt("keywords", major))
91
+ if (matches(e.name, prefix))
92
+ add({ label: e.name.toUpperCase(), kind: "value", detail: `${KEYWORD_CATEGORY[e.row.code] ?? "key word"} key word${note(e)}`, documentation: e.row.description });
93
+ break;
94
+ }
95
+ return items;
96
+ }
97
+ function storageType(row) {
98
+ const t = row.type;
99
+ if (!t)
100
+ return "storage parameter";
101
+ if (t.kind === "enum")
102
+ return `enum: ${t.values?.join(", ")}`;
103
+ return t.range ? `${t.kind}, range ${t.range[0]}..${t.range[1]}` : t.kind;
104
+ }
105
+ function functionKind(row) {
106
+ const k = row.kinds;
107
+ const what = k.includes("a") ? "aggregate function" : k.includes("w") ? "window function" : k.includes("p") ? "procedure" : "function";
108
+ return row.overloads > 1 ? `${what}, ${row.overloads} overloads` : what;
109
+ }
110
+ /** `${` offers the file's declared objects; `${t.` offers `columns`; `${t.columns.` offers its columns with their types. */
111
+ function referenceCompletions(ctx, where, file) {
112
+ const m = /\$\{\s*(?:([A-Za-z_$][\w$]*)\.(columns\.)?)?[\w$]*$/.exec(ctx.linePrefix);
113
+ if (!m)
114
+ return undefined;
115
+ const self = where.found.node.parent;
116
+ const selfName = self && ts.isVariableDeclaration(self) && ts.isIdentifier(self.name) ? self.name.text : undefined;
117
+ const declared = postgresDeclarations(ctx.content, file).filter((d) => d.name !== selfName);
118
+ const [, owner, columns] = m;
119
+ if (!owner) {
120
+ return declared.map((d) => ({
121
+ label: d.name,
122
+ kind: "resource",
123
+ detail: `${d.kind} ${d.sqlName}`,
124
+ documentation: d.columns.length ? `Columns: ${d.columns.map((c) => c.name).join(", ")}` : undefined,
125
+ }));
126
+ }
127
+ const entity = declared.find((d) => d.name === owner);
128
+ if (!entity)
129
+ return [];
130
+ if (!columns)
131
+ return entity.columns.length ? [{ label: "columns", kind: "property", detail: `columns of ${entity.sqlName}` }] : [];
132
+ return entity.columns.map((c) => ({ label: c.name, kind: "property", detail: c.type ?? "column" }));
133
+ }
134
+ // ── Hover ──────────────────────────────────────────────────────────────
135
+ /** Hover when the cursor is in a Postgres template; undefined when it is not (or there is nothing to say). */
136
+ export function postgresHover(ctx) {
137
+ const file = fileName(ctx.uri);
138
+ const where = locatePostgres(ctx.content, offsetAt(ctx.content, ctx.position), file);
139
+ if (!where)
140
+ return { handled: false };
141
+ if (where.part < 0) {
142
+ if (where.expression < 0)
143
+ return { handled: true };
144
+ const info = resolvePostgresReference(ctx.content, where.found.expressions[where.expression].getText(where.source), file);
145
+ return { handled: true, ...(info ? { info: { contents: info } } : {}) };
146
+ }
147
+ const contents = catalogHover(where, majorFor(file));
148
+ return { handled: true, ...(contents ? { info: { contents } } : {}) };
149
+ }
150
+ function catalogHover(where, major) {
151
+ const text = where.found.parts[where.part];
152
+ const word = wordAround(text, where.offset);
153
+ const name = text.slice(word.start, word.end);
154
+ if (!name)
155
+ return undefined;
156
+ const sig = postgresTokensBefore(where.found, where.part, word.start);
157
+ if (!sig)
158
+ return undefined;
159
+ const expect = postgresExpectAfter(sig, where.found.tag);
160
+ const after = text.slice(word.end);
161
+ const followedByParen = /^\s*\(/.test(after);
162
+ const type = () => {
163
+ const e = entryAt("types", name, major);
164
+ if (!e)
165
+ return undefined;
166
+ const alias = e.row.aliasOf ? `, alias of ${code(e.row.aliasOf)}` : "";
167
+ return `**${e.name}**: Postgres type${alias}${note(e)}\n\nCategory ${code(e.row.category)}, catalog name ${code(e.row.catalogName)}.`;
168
+ };
169
+ const method = () => {
170
+ const e = entryAt("accessMethods", name, major);
171
+ if (!e)
172
+ return undefined;
173
+ const props = e.row.properties.length ? `\n\nProperties: ${e.row.properties.join(", ")}.` : "";
174
+ return `**${e.name}**: ${e.row.type} access method${note(e)}${props}`;
175
+ };
176
+ const storage = () => {
177
+ const e = entryAt("storageParameters", name, major);
178
+ return e ? `**${e.name}**: storage parameter${note(e)}\n\n${storageType(e.row)}. Applies to: ${e.row.targets.join(", ")}.` : undefined;
179
+ };
180
+ const fn = () => {
181
+ const e = entryAt("functions", name, major);
182
+ return e && followedByParen ? `**${e.name}**: ${functionKind(e.row)}${note(e)}` : undefined;
183
+ };
184
+ const keyword = () => {
185
+ const e = entryAt("keywords", name, major);
186
+ return e ? `**${e.name.toUpperCase()}**: ${KEYWORD_CATEGORY[e.row.code] ?? "key word"} key word${note(e)}\n\n${e.row.description}` : undefined;
187
+ };
188
+ const extension = () => {
189
+ const e = entryAt("extensions", name, major);
190
+ return e ? `**${e.name}**: extension ${e.row.defaultVersion}${note(e)}${e.row.description ? `\n\n${e.row.description}` : ""}` : undefined;
191
+ };
192
+ const setting = () => {
193
+ const e = entryAt("settings", name, major);
194
+ if (!e)
195
+ return undefined;
196
+ const r = e.row;
197
+ const range = r.min !== null || r.max !== null ? `, range ${r.min ?? ""}..${r.max ?? ""}` : "";
198
+ return `**${e.name}**: server setting${note(e)}\n\nType ${code(r.type)}, default ${code(r.default ?? "")}${r.unit ? ` ${r.unit}` : ""}${range}, changed at ${r.context}.`;
199
+ };
200
+ switch (expect) {
201
+ case "type":
202
+ return type() ?? keyword();
203
+ case "index-method":
204
+ case "table-method":
205
+ return method();
206
+ case "storage-parameter":
207
+ return storage();
208
+ case "extension":
209
+ return extension();
210
+ }
211
+ if (/^\s*=(?!=)/.test(after)) {
212
+ const s = storage();
213
+ if (s)
214
+ return s;
215
+ }
216
+ return fn() ?? keyword() ?? setting();
217
+ }
218
+ // ── References ─────────────────────────────────────────────────────────
219
+ function importedFrom(content, local, file) {
220
+ const source = ts.createSourceFile(file, content, ts.ScriptTarget.Latest, true);
221
+ for (const stmt of source.statements) {
222
+ if (!ts.isImportDeclaration(stmt) || !ts.isStringLiteral(stmt.moduleSpecifier))
223
+ continue;
224
+ const spec = stmt.moduleSpecifier.text;
225
+ if (!spec.startsWith("."))
226
+ continue;
227
+ const named = stmt.importClause?.namedBindings;
228
+ if (!named || !ts.isNamedImports(named))
229
+ continue;
230
+ const el = named.elements.find((e) => e.name.text === local);
231
+ if (!el)
232
+ continue;
233
+ const base = resolve(dirname(file), spec);
234
+ for (const candidate of [`${base}.ts`, `${base}.tsx`, resolve(base, "index.ts"), base.replace(/\.js$/, ".ts")]) {
235
+ if (!existsSync(candidate))
236
+ continue;
237
+ const found = postgresDeclarations(readFileSync(candidate, "utf-8"), candidate).find((d) => d.name === (el.propertyName ?? el.name).text);
238
+ if (found)
239
+ return found;
240
+ }
241
+ }
242
+ return undefined;
243
+ }
244
+ const describe = (d) => {
245
+ const lines = [`**${d.name}**: Postgres ${d.kind} \`${d.sqlName}\``];
246
+ if (d.columns.length)
247
+ lines.push(`Columns: ${d.columns.map((c) => `\`${c.name}${c.type ? ` ${c.type}` : ""}\``).join(", ")}`);
248
+ return lines.join("\n\n");
249
+ };
250
+ /** Hover text for the expression written in a `${}`, or undefined when it is not a reference this can follow. */
251
+ export function resolvePostgresReference(content, expression, file) {
252
+ const m = /^([A-Za-z_$][\w$]*)(?:\.columns(?:\.([A-Za-z_$][\w$]*)|\[\s*["'`]([^"'`]+)["'`]\s*\]))?$/.exec(expression.trim());
253
+ if (!m)
254
+ return undefined;
255
+ const [, owner, dotted, bracketed] = m;
256
+ const column = dotted ?? bracketed;
257
+ const declared = postgresDeclarations(content, file).find((d) => d.name === owner) ?? importedFrom(content, owner, file);
258
+ if (!declared)
259
+ return undefined;
260
+ if (!column)
261
+ return describe(declared);
262
+ const c = declared.columns.find((x) => x.name === column);
263
+ if (!c)
264
+ return `**${owner}.columns.${column}**: no column \`${column}\` in ${declared.sqlName}. Columns: ${declared.columns.map((x) => x.name).join(", ")}`;
265
+ return `**${owner}.columns.${column}**: column \`${c.name}\` of ${declared.kind} \`${declared.sqlName}\`${c.type ? `, type \`${c.type}\`` : ""}`;
266
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * What a `${...}` in a template points at: a table or view declared in the same
3
+ * file or imported from a relative path, or one of its columns. The answer is
4
+ * the hover text, from the source of the declaration.
5
+ */
6
+ import { existsSync, readFileSync } from "fs";
7
+ import { dirname, resolve } from "path";
8
+ import { ts } from "@intentius/chant/lint/typescript";
9
+ import { declarations } from "./template.js";
10
+ function importedFrom(content, local, fileName) {
11
+ const source = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true);
12
+ for (const stmt of source.statements) {
13
+ if (!ts.isImportDeclaration(stmt) || !ts.isStringLiteral(stmt.moduleSpecifier))
14
+ continue;
15
+ const spec = stmt.moduleSpecifier.text;
16
+ if (!spec.startsWith("."))
17
+ continue;
18
+ const named = stmt.importClause?.namedBindings;
19
+ if (!named || !ts.isNamedImports(named))
20
+ continue;
21
+ const el = named.elements.find((e) => e.name.text === local);
22
+ if (!el)
23
+ continue;
24
+ const base = resolve(dirname(fileName), spec);
25
+ for (const candidate of [`${base}.ts`, `${base}.tsx`, resolve(base, "index.ts"), base.replace(/\.js$/, ".ts")]) {
26
+ if (!existsSync(candidate))
27
+ continue;
28
+ const found = declarations(readFileSync(candidate, "utf-8"), candidate).find((d) => d.name === (el.propertyName ?? el.name).text);
29
+ if (found)
30
+ return { declared: found, file: candidate };
31
+ }
32
+ }
33
+ return undefined;
34
+ }
35
+ const describe = (d) => {
36
+ const kind = d.materialized ? "materialized view" : d.tag;
37
+ const lines = [`**${d.name}**: ClickHouse ${kind} \`${d.sqlName}\``];
38
+ if (d.engine)
39
+ lines.push(`Engine: \`${d.engine}\``);
40
+ if (d.columns.length)
41
+ lines.push(`Columns: ${d.columns.map((c) => `\`${c.name}${c.type ? ` ${c.type}` : ""}\``).join(", ")}`);
42
+ return lines.join("\n\n");
43
+ };
44
+ /** Hover text for the expression written in a `${}`, or undefined when it is not a reference this can follow. */
45
+ export function resolveReference(content, expression, fileName) {
46
+ const m = /^([A-Za-z_$][\w$]*)(?:\.columns(?:\.([A-Za-z_$][\w$]*)|\[\s*["'`]([^"'`]+)["'`]\s*\]))?$/.exec(expression.trim());
47
+ if (!m)
48
+ return undefined;
49
+ const [, owner, dotted, bracketed] = m;
50
+ const column = dotted ?? bracketed;
51
+ const declared = declarations(content, fileName).find((d) => d.name === owner) ?? importedFrom(content, owner, fileName)?.declared;
52
+ if (!declared)
53
+ return undefined;
54
+ if (!column)
55
+ return describe(declared);
56
+ const c = declared.columns.find((x) => x.name === column);
57
+ if (!c)
58
+ return `**${owner}.columns.${column}**: no column \`${column}\` in ${declared.sqlName}. Columns: ${declared.columns.map((x) => x.name).join(", ")}`;
59
+ return `**${owner}.columns.${column}**: column \`${c.name}\` of ${declared.tag} \`${declared.sqlName}\`${c.type ? `, type \`${c.type}\`` : ""}`;
60
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/lsp/registry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAqB,MAAM,wCAAwC,CAAC;AAMzF,sFAAsF;AACtF,wBAAgB,aAAa,IAAI,YAAY,CAU5C"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/lsp/registry.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAqB,MAAM,wCAAwC,CAAC;AAMzF,sFAAsF;AACtF,wBAAgB,aAAa,IAAI,YAAY,CAU5C"}