@intentius/chant-lexicon-sql 0.124.31 → 0.124.32

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,28 @@
1
+ /**
2
+ * sql lexicon packaging, delegating to core's packagePipeline.
3
+ */
4
+ import { readFileSync } from "fs";
5
+ import { join, dirname } from "path";
6
+ import { moduleSourceDir } from "@intentius/chant/module-source";
7
+ import { packagePipeline } from "@intentius/chant/codegen/package";
8
+ import { generate } from "./generate.js";
9
+ // src/codegen/package.ts -> src/
10
+ const srcDir = dirname(moduleSourceDir(import.meta.url));
11
+ export async function packageLexicon(opts = {}) {
12
+ const pkgJson = JSON.parse(readFileSync(join(srcDir, "..", "package.json"), "utf-8"));
13
+ return packagePipeline({
14
+ generate: (genOpts) => generate({ verbose: genOpts.verbose, force: genOpts.force }),
15
+ buildManifest: () => ({
16
+ name: "sql",
17
+ version: pkgJson.version ?? "0.0.1",
18
+ chantVersion: ">=0.1.0",
19
+ namespace: "SQL",
20
+ intrinsics: [],
21
+ pseudoParameters: {},
22
+ }),
23
+ srcDir,
24
+ // No skills yet; they arrive with chant #3211.
25
+ collectSkills: () => new Map(),
26
+ version: pkgJson.version ?? "0.0.1",
27
+ }, opts);
28
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * `docs/pages/postgres-change-classifier.mdx`, written from the Postgres
3
+ * classifier's own rule table by `npm run docs`, so the page cannot disagree
4
+ * with the rules.
5
+ */
6
+ import { PG_CHANGE_CLASSES, PG_CLASSIFIER_RULES } from "../postgres/plan/rules.js";
7
+ const cell = (s) => s.replace(/\|/g, "\\|");
8
+ export function renderPostgresClassifierPage() {
9
+ const rows = Object.values(PG_CLASSIFIER_RULES).map((r) => `| ${r.id} | ${PG_CHANGE_CLASSES.label[r.class]} | ${cell(r.title)} | ${cell(r.restriction)} [Postgres 18 docs](${r.cite}) |`);
10
+ return `---
11
+ title: "Postgres Locks and the Change Classifier"
12
+ label: "Locks and the Change Classifier"
13
+ description: "How a Postgres schema change is classified by the lock it takes and whether it reads or rewrites the table, each with the documentation behind it"
14
+ diataxis: reference
15
+ group: "Postgres"
16
+ order: 11
17
+ ---
18
+
19
+ {/* Written by src/codegen/postgres-classifier-page.ts from src/postgres/plan/rules.ts. Edit the rules, then run npm run docs. */}
20
+
21
+ Every change between two Postgres schemas is classified before anything runs, by the lock it takes and whether it reads or rewrites the table, as the Postgres 18 reference states them. Where 14 to 17 differ, the rule says so.
22
+
23
+ | Class | Means | Disruption |
24
+ |---|---|---|
25
+ | create | a new object; nothing that exists changes | \`in-place\` |
26
+ | metadata only | a catalog change under a brief lock; no row is read or written | \`in-place\` |
27
+ | validates under a weaker lock | every row is read to check a constraint, under a lock weaker than ACCESS EXCLUSIVE | \`rolling\` |
28
+ | needs CONCURRENTLY | an index build or drop that blocks writes unless it is CONCURRENTLY, which cannot run in a transaction block | \`rolling\` |
29
+ | ACCESS EXCLUSIVE rewrite or scan | the table is rewritten or read in full while reads and writes wait | \`rolling\` |
30
+ | expand and contract | no in-place change keeps old readers working; a plan refuses it | \`replace\` |
31
+ | drop | the object, and a table's or sequence's data, is gone | \`destroy\` |
32
+
33
+ The disruption column is what \`chant lifecycle plan\` reports for each update. A changed path alone cannot always say which rule applies (a column's type, a constraint, a view's query, an enum's labels), and those report \`unknown\` there; \`chant sql plan\` has both definitions and classifies them.
34
+
35
+ \`\`\`bash
36
+ chant sql diff base.json head.json # two chant build outputs, offline
37
+ chant sql plan prod schema.json # a build output against the prod server
38
+ \`\`\`
39
+
40
+ Both read the build's \`dialect\` and exit 2 when a change can only be made as expand and contract. \`--json\` prints the changes, the hints, the refused changes and the migration Op suggestions as one document. \`chant sql diff\` compares a pull request's base and head builds with no server. \`chant sql plan\` reads the server \`sql.profiles.<env>\` binds (else \`POSTGRES_URL\`), in the profile's \`schemas\` when it lists them, otherwise in the default schema and every schema the build declares an object in. Where the normalization rules leave an expression different, it asks that server: the declared view, or the table's column types, defaults, generated expressions and checks, are created as temporary objects in a transaction that is always rolled back, and read back with the same printers, so a view's \`SELECT *\` and the server's added parentheses are not reported as changes.
41
+
42
+ A clause chant's parser does not know ([Plain .sql Files](../sql-files/#clauses-chant-does-not-read)) is compared as text, and the rest of its object is planned as usual. A change to one is SQLPG299, refused for review with no migration Op suggested, since chant cannot tell which lock the change takes. A clause only the server prints is a hint, not a change.
43
+
44
+ ## Statements for a migration file
45
+
46
+ \`chant sql diff base.json head.json --statements\` prints the statements that take the base schema to the head one, offline: each one the applier would send for the same change, after a comment naming its object, rule and class, and whether it has to run outside a transaction block. \`--json\` prints them as one document, which \`diffStatements(before, after)\` from \`@intentius/chant-lexicon-sql\` also returns, each statement with \`transactional\` set:
47
+
48
+ \`\`\`sql
49
+ -- users (app.users): SQLPG201 metadata
50
+ ALTER TABLE app.users ADD COLUMN name text DEFAULT 'none' NOT NULL;
51
+
52
+ -- usersEmail (app.users_email_idx): SQLPG240 concurrently, outside a transaction
53
+ CREATE INDEX CONCURRENTLY users_email_idx ON app.users (email);
54
+
55
+ -- orders (app.orders): SQLPG205 made by PostgresMigrationOp, not a statement:
56
+ -- export const { op } = PostgresMigrationOp({ name: "migrate-app-orders-reference", env: "<env>", table: "app.orders", column: "reference" });
57
+ \`\`\`
58
+
59
+ A column rename or a type change across kinds is never DDL: it is a step naming \`PostgresMigrationOp\`, one per column. Any other expand-and-contract change, and the refused table's other changes, which the applier also holds back, are a manual step. A column or object drop is a statement marked destructive. Statements use the default schema for a bare name, as the applier sets \`search_path\`. The command exits 2 when a step is not a statement. What the applier asks the server is left out: its normalization of expressions the rules leave different, and an extension's own comment.
60
+
61
+ ## Which major
62
+
63
+ Two rules change class across the supported majors: a STORED generated column's expression (SQLPG212) is a rewrite from 17 and expand and contract before it, and a table's access method (SQLPG226) is a rewrite from 15 and expand and contract before it.
64
+
65
+ - \`chant sql diff\` classifies for the major the builds recorded (\`postgresMajor\` in the build output): the newer build's, else the older build's, else \`sql.postgresMajor\`, else 18. A diff between two revisions does not depend on today's config.
66
+ - \`chant sql plan\` classifies for the major the server runs (\`server_version_num\`), since the server is what takes the locks. When the build targets another major, the plan says so in a hint.
67
+
68
+ ## Identity and renames
69
+
70
+ An object is identified by its export name between two builds, and by its schema-qualified name against a server, which has no export names. Tables, views, materialized views, sequences and indexes share one namespace per schema, types and domains another, and schemas and extensions are matched by name. Functions, procedures and aggregates share a namespace of their own and are told apart by their input parameter types, so an overload is another object; composite and range types share the types' namespace, collations have one of their own, a cast is named by its two types and an event trigger by its name in the database; a trigger is matched by its name on its table.
71
+
72
+ A rename is declared where it happens. Before a CREATE, \`-- previously: <old name>\` says the object had another name; on a column's line, it says the column did:
73
+
74
+ \`\`\`sql
75
+ CREATE TABLE app.orders (
76
+ id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
77
+ memo text -- previously: note
78
+ )
79
+ \`\`\`
80
+
81
+ Without the hint, a column dropped and another added with the same type in the same place are reported as a drop and an add, with a hint asking whether it is a rename. A constraint left unnamed is matched by what it says, not by the name Postgres gave it, and an index on a table the same plan creates is part of the create.
82
+
83
+ An object another tool keeps is never proposed for a drop: an ORM's or migration runner's revision table with its sequence and indexes, and, with \`sql.provider\` set, the provider's own schemas and extensions. The plan names each in a hint and leaves it alone. Any other object in the schemas the plan reads that the build does not declare is a drop (SQLPG270); the applier drops it only when told to prune and only when it carries this project's marker.
84
+
85
+ ## After the plan
86
+
87
+ The plan only reports. [\`postgresApply\`](../postgres-applying/) makes every change that is not expand and contract, grouping the statements into transactions by class and running \`CONCURRENTLY\` builds outside them. A column rename (SQLPG205) or a type change across kinds (SQLPG208) runs as \`PostgresMigrationOp\`, and both commands print the declaration to start from for each such column (\`migrationOps\` in \`--json\`): see [Migrating a Column](../postgres-migration/). The other expand-and-contract changes (a NOT NULL column with no default, a view losing or reordering columns, partitioning, a rename of a table, an enum label removed) have no Op yet and are made by hand.
88
+
89
+ ## The rules
90
+
91
+ | Id | Class | Change | Restriction |
92
+ |---|---|---|---|
93
+ ${rows.join("\n")}
94
+ `;
95
+ }
@@ -0,0 +1,418 @@
1
+ /**
2
+ * Render the Postgres dialect's generated surface from the catalogs of every
3
+ * supported major:
4
+ *
5
+ * - `src/generated/postgres-types.ts`: string-literal unions of every named
6
+ * thing in any major's catalog, the `Settings` and `StorageParams_*`
7
+ * interfaces, and the `VERSION_RANGES` table saying which entries are not in
8
+ * all majors.
9
+ * - `src/generated/postgres.ts`: the tables lint and the LSP read (settings,
10
+ * storage parameters, column types), with the overlays merged in.
11
+ *
12
+ * The overlays are checked against the catalogs in both directions. An overlay
13
+ * key no major has, or a storage parameter the probe found with no overlay
14
+ * entry, is an error: a pin move that changes the grammar has to change the
15
+ * overlay in the same commit.
16
+ *
17
+ * Output depends only on the catalogs and the overlays: every map is walked
18
+ * in sorted order (by code unit, not locale), so two runs are byte-identical.
19
+ */
20
+ import { COLUMN_TYPE_PARAMETERS, SERIAL_TYPES, TYPE_SPELLINGS } from "../postgres/overlays/types.js";
21
+ import { STORAGE_PARAMETER_TYPES } from "../postgres/overlays/storage.js";
22
+ import { CONSTRAINTS } from "../postgres/overlays/constraints.js";
23
+ import { COLUMN_CLAUSES } from "../postgres/overlays/columns.js";
24
+ import { PARTITIONING } from "../postgres/overlays/partitioning.js";
25
+ import { INDEX_CLAUSES } from "../postgres/overlays/indexes.js";
26
+ import { RELATION_CLAUSES, SEQUENCE_CLAUSES, VIEW_CLAUSES } from "../postgres/overlays/relations.js";
27
+ import { TYPE_DDL } from "../postgres/overlays/type-ddl.js";
28
+ const compare = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
29
+ const sorted = (xs) => [...new Set(xs)].sort(compare);
30
+ const lit = (names) => names.length > 0 ? names.map((n) => JSON.stringify(n)).join(" | ") : "never";
31
+ const key = (name) => (/^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name));
32
+ const doc = (text) => text.replace(/\*\//g, "*\\/");
33
+ const identifier = (name) => name.replace(/[^A-Za-z0-9]+/g, "_");
34
+ /**
35
+ * Merge `pick(catalog)` across the majors by `keyOf`. `since` and `until` are
36
+ * set when the name is missing from the oldest or the newest major. A name
37
+ * with a gap (in 14, gone in 15, back in 16) is an error: a range cannot say it.
38
+ */
39
+ function union(what, catalogs, pick, keyOf, errors) {
40
+ const byName = new Map();
41
+ catalogs.forEach((c, index) => {
42
+ for (const row of pick(c)) {
43
+ const k = keyOf(row);
44
+ const list = byName.get(k);
45
+ if (list)
46
+ list.push({ index, row });
47
+ else
48
+ byName.set(k, [{ index, row }]);
49
+ }
50
+ });
51
+ const last = catalogs.length - 1;
52
+ const out = [];
53
+ for (const name of sorted(byName.keys())) {
54
+ const present = byName.get(name);
55
+ const first = present[0].index;
56
+ const end = present[present.length - 1].index;
57
+ if (end - first + 1 !== present.length) {
58
+ errors.push(`${what} ${name} is present in non-adjacent majors (${present.map((p) => majorOfIndex(catalogs, p.index)).join(", ")})`);
59
+ }
60
+ out.push({
61
+ name,
62
+ latest: present[present.length - 1].row,
63
+ rows: present.map((p) => p.row),
64
+ range: {
65
+ ...(first > 0 ? { since: majorOfIndex(catalogs, first) } : {}),
66
+ ...(end < last ? { until: majorOfIndex(catalogs, end) } : {}),
67
+ },
68
+ });
69
+ }
70
+ return out;
71
+ }
72
+ const majorOfIndex = (catalogs, i) => Number.parseInt(catalogs[i].version, 10);
73
+ const bounded = (r) => r.since !== undefined || r.until !== undefined;
74
+ /** ` | @since 15 | @until 17` */
75
+ function rangeTags(r) {
76
+ return [...(r.since !== undefined ? [`@since ${r.since}`] : []), ...(r.until !== undefined ? [`@until ${r.until}`] : [])];
77
+ }
78
+ function settingTsType(rows) {
79
+ const parts = new Set();
80
+ for (const s of rows) {
81
+ if (s.enumvals)
82
+ for (const v of s.enumvals)
83
+ parts.add(JSON.stringify(v));
84
+ else
85
+ parts.add({ bool: "boolean", integer: "number", real: "number", string: "string" }[s.type] ?? "string");
86
+ }
87
+ // `string` absorbs literals; a union of the two is the same type and noisier.
88
+ return parts.has("string") ? "string" : [...parts].sort(compare).join(" | ");
89
+ }
90
+ function storageTsType(t) {
91
+ if (t.kind === "boolean")
92
+ return "boolean";
93
+ if (t.kind === "enum")
94
+ return lit(t.values);
95
+ return "number";
96
+ }
97
+ function checkClauses(file, table, majors, errors) {
98
+ const lo = majors[0];
99
+ const hi = majors[majors.length - 1];
100
+ for (const [name, clause] of Object.entries(table)) {
101
+ for (const bound of [clause.since, clause.until]) {
102
+ if (bound !== undefined && (bound < lo || bound > hi)) {
103
+ errors.push(`overlays/${file}: ${name} has a bound ${bound} outside the supported majors ${lo}..${hi}`);
104
+ }
105
+ }
106
+ }
107
+ }
108
+ export function renderPostgresModule(catalogs) {
109
+ if (catalogs.length === 0)
110
+ throw new Error("no Postgres catalog was read");
111
+ const errors = [];
112
+ const notes = [];
113
+ const majors = catalogs.map((c) => Number.parseInt(c.version, 10));
114
+ // ── Union of every section ─────────────────────────────────────────
115
+ const types = union("type", catalogs, (c) => c.types.filter((t) => t.kind !== "p"), (t) => t.name, errors);
116
+ const accessMethods = union("access method", catalogs, (c) => c.accessMethods, (a) => a.name, errors);
117
+ const opclasses = union("operator class", catalogs, (c) => c.opclasses, (o) => `${o.am}.${o.name}`, errors);
118
+ const functions = union("function", catalogs, (c) => c.functions.filter((f) => !f.internal), (f) => f.name, errors);
119
+ const settings = union("setting", catalogs, (c) => c.settings, (s) => s.name, errors);
120
+ const keywords = union("keyword", catalogs, (c) => c.keywords, (k) => `${k.code}:${k.word}`, errors);
121
+ const extensions = union("extension", catalogs, (c) => c.extensions, (e) => e.name, errors);
122
+ const storage = union("storage parameter", catalogs, (c) => Object.entries(c.storageParameters).flatMap(([target, names]) => names.map((name) => ({ target, name }))), (p) => `${p.target}.${p.name}`, errors);
123
+ // ── Cross-checks of the overlays against the catalogs ──────────────
124
+ const sqlNames = new Set(types.map((t) => t.latest.sqlName));
125
+ for (const name of Object.keys(COLUMN_TYPE_PARAMETERS)) {
126
+ if (!sqlNames.has(name))
127
+ errors.push(`overlays/types.ts: type ${name} is not in any catalog`);
128
+ }
129
+ for (const [spelling, canonical] of [...Object.entries(TYPE_SPELLINGS), ...Object.entries(SERIAL_TYPES)]) {
130
+ if (!sqlNames.has(canonical))
131
+ errors.push(`overlays/types.ts: ${spelling} stands for ${canonical}, which is not in any catalog`);
132
+ }
133
+ const probed = new Set(storage.map((p) => p.latest.name));
134
+ for (const name of Object.keys(STORAGE_PARAMETER_TYPES)) {
135
+ if (!probed.has(name))
136
+ errors.push(`overlays/storage.ts: storage parameter ${name} was not accepted by any server`);
137
+ }
138
+ for (const name of probed) {
139
+ if (!STORAGE_PARAMETER_TYPES[name])
140
+ errors.push(`overlays/storage.ts: storage parameter ${name} has no entry`);
141
+ }
142
+ const indexMethods = new Set(accessMethods.filter((a) => a.latest.type === "i").map((a) => a.name));
143
+ for (const v of CONSTRAINTS.exclude.args.find((a) => a.name === "using")?.values ?? []) {
144
+ if (!indexMethods.has(v))
145
+ errors.push(`overlays/constraints.ts: exclusion method ${v} is not an index access method`);
146
+ }
147
+ for (const [file, table] of [
148
+ ["constraints.ts", CONSTRAINTS],
149
+ ["columns.ts", COLUMN_CLAUSES],
150
+ ["partitioning.ts", PARTITIONING],
151
+ ["indexes.ts", INDEX_CLAUSES],
152
+ ["relations.ts", { ...RELATION_CLAUSES, ...VIEW_CLAUSES, ...SEQUENCE_CLAUSES }],
153
+ ["type-ddl.ts", TYPE_DDL],
154
+ ]) {
155
+ checkClauses(file, table, majors, errors);
156
+ }
157
+ if (errors.length > 0) {
158
+ throw new Error(`The Postgres overlays disagree with the catalogs at ${catalogs.map((c) => c.version).join(", ")}:\n ${errors.join("\n ")}\n` +
159
+ "Update src/postgres/overlays/ for these pins.");
160
+ }
161
+ // ── Ranges ─────────────────────────────────────────────────────────
162
+ const ranges = {};
163
+ const addRanges = (section, entries) => {
164
+ const out = {};
165
+ for (const e of entries)
166
+ if (bounded(e.range))
167
+ out[e.name] = e.range;
168
+ ranges[section] = out;
169
+ };
170
+ addRanges("types", types);
171
+ addRanges("accessMethods", accessMethods);
172
+ addRanges("opclasses", opclasses);
173
+ addRanges("functions", functions);
174
+ addRanges("settings", settings);
175
+ addRanges("extensions", extensions);
176
+ addRanges("storageParameters", storage);
177
+ for (const code of ["R", "T", "C", "U"]) {
178
+ addRanges(`keywords${code}`, keywords.filter((k) => k.latest.code === code).map((k) => ({ name: k.latest.word, range: k.range })));
179
+ }
180
+ // Enum members that are not in every major a setting is in: `wal_compression` gains `lz4` in 15.
181
+ const settingValues = {};
182
+ for (const s of settings) {
183
+ const members = union(`setting ${s.name} value`, catalogs, (c) => c.settings.find((x) => x.name === s.name)?.enumvals ?? [], (v) => v, errors);
184
+ const own = {};
185
+ for (const m of members) {
186
+ // Relative to the majors that have the setting at all, not to all majors.
187
+ const since = m.range.since !== undefined && m.range.since > (s.range.since ?? majors[0]) ? m.range.since : undefined;
188
+ const lastOfSetting = s.range.until ?? majors[majors.length - 1];
189
+ const until = m.range.until !== undefined && m.range.until < lastOfSetting ? m.range.until : undefined;
190
+ if (since !== undefined || until !== undefined) {
191
+ own[m.name] = { ...(since !== undefined ? { since } : {}), ...(until !== undefined ? { until } : {}) };
192
+ }
193
+ }
194
+ if (Object.keys(own).length > 0)
195
+ settingValues[s.name] = own;
196
+ }
197
+ if (errors.length > 0)
198
+ throw new Error(`Postgres setting values: ${errors.join("; ")}`);
199
+ const rangeLiteral = (r) => `{ ${[...(r.since !== undefined ? [`since: ${r.since}`] : []), ...(r.until !== undefined ? [`until: ${r.until}`] : [])].join(", ")} }`;
200
+ const rangesTable = [
201
+ `export const VERSION_RANGES = {`,
202
+ ...Object.entries(ranges).flatMap(([section, entries]) => Object.keys(entries).length === 0
203
+ ? [` ${section}: {},`]
204
+ : [
205
+ ` ${section}: {`,
206
+ ...Object.entries(entries).map(([name, r]) => ` ${JSON.stringify(name)}: ${rangeLiteral(r)},`),
207
+ ` },`,
208
+ ]),
209
+ ` settingValues: {`,
210
+ ...Object.entries(settingValues).flatMap(([name, members]) => [
211
+ ` ${JSON.stringify(name)}: {`,
212
+ ...Object.entries(members).map(([m, r]) => ` ${JSON.stringify(m)}: ${rangeLiteral(r)},`),
213
+ ` },`,
214
+ ]),
215
+ ` },`,
216
+ `} as const satisfies Record<string, Record<string, VersionRange | Record<string, VersionRange>>>;`,
217
+ ];
218
+ // ── Declarations ───────────────────────────────────────────────────
219
+ const aliases = types.filter((t) => t.latest.sqlName !== t.latest.name).map((t) => [t.latest.name, t.latest.sqlName]);
220
+ const keywordNames = (code) => keywords.filter((k) => k.latest.code === code).map((k) => k.latest.word);
221
+ const byAm = new Map();
222
+ for (const o of opclasses) {
223
+ const list = byAm.get(o.latest.am);
224
+ if (list)
225
+ list.push(o.latest.name);
226
+ else
227
+ byAm.set(o.latest.am, [o.latest.name]);
228
+ }
229
+ const settingLines = [];
230
+ for (const s of settings) {
231
+ const tags = [doc(s.latest.category), `@context ${s.latest.context}`];
232
+ if (s.latest.unit)
233
+ tags.push(`@unit ${s.latest.unit}`);
234
+ if (s.latest.default !== null)
235
+ tags.push(`@default ${doc(JSON.stringify(s.latest.default))}`);
236
+ tags.push(...rangeTags(s.range));
237
+ settingLines.push(` /** ${tags.join(" | ")} */`, ` ${key(s.name)}?: ${settingTsType(s.rows)};`);
238
+ }
239
+ const storageTargets = sorted(storage.map((p) => p.latest.target));
240
+ const storageInterfaces = storageTargets.flatMap((target) => [
241
+ `export interface StorageParams_${identifier(target)} {`,
242
+ ...storage
243
+ .filter((p) => p.latest.target === target)
244
+ .flatMap((p) => {
245
+ const tags = rangeTags(p.range);
246
+ return [
247
+ ...(tags.length > 0 ? [` /** ${tags.join(" | ")} */`] : []),
248
+ ` ${key(p.latest.name)}?: ${storageTsType(STORAGE_PARAMETER_TYPES[p.latest.name])};`,
249
+ ];
250
+ }),
251
+ "}",
252
+ "",
253
+ ]);
254
+ const header = (what) => [
255
+ `// Generated by \`npm run generate\` from the Postgres catalogs at ${catalogs.map((c) => c.version).join(", ")}`,
256
+ `// (src/spec/postgres-catalog-<major>.snapshot.json) and the overlays in src/postgres/overlays/. Do not edit.`,
257
+ `// ${what}`,
258
+ "",
259
+ ];
260
+ const declarations = [
261
+ ...header("The type surface: the union of every supported major, with a range on each name that is not in all of them."),
262
+ `import type { VersionRange } from "../postgres/catalog-types.js";`,
263
+ "",
264
+ `/** The supported majors, oldest first. */`,
265
+ `export const POSTGRES_MAJORS = ${JSON.stringify(majors)} as const;`,
266
+ `export type PostgresMajor = (typeof POSTGRES_MAJORS)[number];`,
267
+ `/** The exact release each major's catalog was read from. */`,
268
+ `export const POSTGRES_VERSIONS = ${JSON.stringify(Object.fromEntries(catalogs.map((c, i) => [majors[i], c.version])))} as const;`,
269
+ "",
270
+ `export type TypeName = ${lit(types.map((t) => t.name))};`,
271
+ `export type TypeSqlName = ${lit(sorted(types.map((t) => t.latest.sqlName)))};`,
272
+ `export const TYPE_ALIASES = ${JSON.stringify(Object.fromEntries(aliases))} as const;`,
273
+ "",
274
+ `export type IndexAccessMethod = ${lit(accessMethods.filter((a) => a.latest.type === "i").map((a) => a.name))};`,
275
+ `export type TableAccessMethod = ${lit(accessMethods.filter((a) => a.latest.type === "t").map((a) => a.name))};`,
276
+ ...[...byAm].map(([am, names]) => `export type OpClass_${identifier(am)} = ${lit(names)};`),
277
+ "",
278
+ `export type ReservedKeyword = ${lit(keywordNames("R"))};`,
279
+ `export type ReservedAsFunctionKeyword = ${lit(keywordNames("T"))};`,
280
+ `export type ColumnNameKeyword = ${lit(keywordNames("C"))};`,
281
+ `export type UnreservedKeyword = ${lit(keywordNames("U"))};`,
282
+ "",
283
+ `export type AggregateFunctionName = ${lit(functions.filter((f) => f.latest.kinds.includes("a")).map((f) => f.name))};`,
284
+ `export type ScalarFunctionName = ${lit(functions.filter((f) => f.latest.kinds.includes("f")).map((f) => f.name))};`,
285
+ `export type ExtensionName = ${lit(extensions.map((e) => e.name))};`,
286
+ "",
287
+ `/** Server settings, \`SET\` and \`postgresql.conf\` names, from \`pg_settings\`. */`,
288
+ `export interface Settings {`,
289
+ ...settingLines,
290
+ `}`,
291
+ "",
292
+ ...storageInterfaces,
293
+ `/** The names that are not in every supported major, with the major they arrived in and the last that has them. */`,
294
+ ...rangesTable,
295
+ "",
296
+ ].join("\n");
297
+ const settingTable = [
298
+ `export const SETTINGS: Readonly<Record<keyof Settings, SettingSpec>> = {`,
299
+ ...settings.map((s) => {
300
+ const r = s.latest;
301
+ const spec = {
302
+ type: r.type,
303
+ context: r.context,
304
+ category: r.category,
305
+ ...(r.unit ? { unit: r.unit } : {}),
306
+ ...(r.default !== null ? { default: r.default } : {}),
307
+ ...(r.min !== null ? { min: r.min } : {}),
308
+ ...(r.max !== null ? { max: r.max } : {}),
309
+ };
310
+ return ` ${key(s.name)}: ${JSON.stringify(spec)},`;
311
+ }),
312
+ "};",
313
+ ];
314
+ const storageNames = sorted(storage.map((p) => p.latest.name));
315
+ const storageTable = [
316
+ `export const STORAGE_PARAMETERS: Readonly<Record<string, StorageParameterSpec>> = {`,
317
+ ...storageNames.map((name) => {
318
+ const targets = sorted(storage.filter((p) => p.latest.name === name).map((p) => p.latest.target));
319
+ return ` ${key(name)}: ${JSON.stringify({ type: STORAGE_PARAMETER_TYPES[name], targets })},`;
320
+ }),
321
+ "};",
322
+ ];
323
+ const spellingsOf = new Map();
324
+ const spell = (canonical, alias) => spellingsOf.set(canonical, [...(spellingsOf.get(canonical) ?? []), alias]);
325
+ for (const [a, canonical] of Object.entries(TYPE_SPELLINGS))
326
+ spell(canonical, a);
327
+ for (const t of types)
328
+ if (t.latest.sqlName !== t.latest.name)
329
+ spell(t.latest.sqlName, t.latest.name);
330
+ const canonicalTypes = new Map();
331
+ for (const t of types)
332
+ canonicalTypes.set(t.latest.sqlName, t.latest);
333
+ const typeTable = [
334
+ `export const COLUMN_TYPES: Readonly<Record<TypeSqlName, ColumnTypeSpec>> = {`,
335
+ ...[...canonicalTypes.keys()].sort(compare).map((name) => {
336
+ const t = canonicalTypes.get(name);
337
+ const spec = {
338
+ ...(t.name !== name ? { catalogName: t.name } : {}),
339
+ category: t.category,
340
+ aliases: sorted(spellingsOf.get(name) ?? []),
341
+ parameters: COLUMN_TYPE_PARAMETERS[name] ?? [],
342
+ };
343
+ return ` ${key(name)}: ${JSON.stringify(spec)},`;
344
+ }),
345
+ "};",
346
+ ];
347
+ // The parser's ColId rule and identifier quoting read the latest major's key words (#3279).
348
+ const keywordTable = [
349
+ `/** Every key word at the latest pinned major, by its \`pg_get_keywords()\` category: U unreserved, C column name, T type or function name, R reserved. */`,
350
+ `export const KEYWORDS: Readonly<Record<string, "U" | "C" | "T" | "R">> = {`,
351
+ ...keywords
352
+ .filter((k) => k.range.until === undefined)
353
+ .map((k) => k.latest)
354
+ .sort((a, b) => compare(a.word, b.word))
355
+ .map((k) => ` ${key(k.word)}: ${JSON.stringify(k.code)},`),
356
+ "};",
357
+ ];
358
+ // Names lint checks a declaration against, each with the majors that have it
359
+ // (`true`: every supported major). Read straight off the catalogs, internal
360
+ // functions included: a column default may call `pg_current_xact_id()`.
361
+ const presence = (lists) => {
362
+ const out = new Map();
363
+ lists.forEach((names, i) => {
364
+ for (const n of new Set(names))
365
+ out.set(n, [...(out.get(n) ?? []), majors[i]]);
366
+ });
367
+ return out;
368
+ };
369
+ const majorsLiteral = (ms) => (ms.length === majors.length ? "true" : JSON.stringify(ms));
370
+ const presenceTable = (m, indent) => sorted(m.keys()).map((n) => `${indent}${JSON.stringify(n)}: ${majorsLiteral(m.get(n))},`);
371
+ const functionTable = [
372
+ `/** Every function, aggregate and procedure name in \`pg_proc\`, to the majors that have it (\`true\`: all of them). */`,
373
+ `export const FUNCTIONS: Readonly<Record<string, true | readonly number[]>> = {`,
374
+ ...presenceTable(presence(catalogs.map((c) => c.functions.map((f) => f.name))), " "),
375
+ "};",
376
+ ];
377
+ const methodTable = [
378
+ `/** The index access methods, to the majors that have each (\`true\`: all of them). */`,
379
+ `export const INDEX_ACCESS_METHODS: Readonly<Record<string, true | readonly number[]>> = {`,
380
+ ...presenceTable(presence(catalogs.map((c) => c.accessMethods.filter((a) => a.type === "i").map((a) => a.name))), " "),
381
+ "};",
382
+ ];
383
+ const opclassAms = sorted(catalogs.flatMap((c) => c.opclasses.map((o) => o.am)));
384
+ const opclassTable = [
385
+ `/** The operator classes of each index access method, to the majors that have each (\`true\`: all of them). */`,
386
+ `export const OPERATOR_CLASSES: Readonly<Record<string, Readonly<Record<string, true | readonly number[]>>>> = {`,
387
+ ...opclassAms.flatMap((am) => [
388
+ ` ${key(am)}: {`,
389
+ ...presenceTable(presence(catalogs.map((c) => c.opclasses.filter((o) => o.am === am).map((o) => o.name))), " "),
390
+ " },",
391
+ ]),
392
+ "};",
393
+ ];
394
+ const tables = [
395
+ ...header("The tables lint and the LSP read, typed by ./postgres-types."),
396
+ `import type { ColumnTypeSpec, SettingSpec, StorageParameterSpec } from "../postgres/catalog-types.js";`,
397
+ `import type { Settings, TypeSqlName } from "./postgres-types";`,
398
+ "",
399
+ `export * from "./postgres-types";`,
400
+ "",
401
+ ...settingTable,
402
+ "",
403
+ ...storageTable,
404
+ "",
405
+ ...typeTable,
406
+ "",
407
+ ...keywordTable,
408
+ "",
409
+ ...functionTable,
410
+ "",
411
+ ...methodTable,
412
+ "",
413
+ ...opclassTable,
414
+ "",
415
+ ].join("\n");
416
+ notes.push(`postgres: ${types.length} types, ${functions.length} functions, ${settings.length} settings, ${storage.length} storage parameters across ${catalogs.length} majors`);
417
+ return { declarations, tables, notes };
418
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The entity registry, `lexicon-sql.json`: one entry per entity kind, which the
3
+ * LSP completes and hovers from and `dist/meta.json` packages.
4
+ *
5
+ * The entities are hand-written: their shape is the DDL a tag parses, not a
6
+ * schema document. Each is keyed by the kind's name and points at the entity
7
+ * type the tag gives it. ClickHouse's kinds came first and keep their bare
8
+ * names; Postgres's carry the dialect in the name, since both have a table
9
+ * and a view.
10
+ */
11
+ import { CLICKHOUSE_ENTITY_TYPES } from "../clickhouse/entities.js";
12
+ import { POSTGRES_ENTITY_TYPES } from "../postgres/entity-types.js";
13
+ const KINDS = {
14
+ Database: CLICKHOUSE_ENTITY_TYPES.database,
15
+ Table: CLICKHOUSE_ENTITY_TYPES.table,
16
+ View: CLICKHOUSE_ENTITY_TYPES.view,
17
+ MaterializedView: CLICKHOUSE_ENTITY_TYPES.materializedView,
18
+ Dictionary: CLICKHOUSE_ENTITY_TYPES.dictionary,
19
+ Function: CLICKHOUSE_ENTITY_TYPES.function,
20
+ User: CLICKHOUSE_ENTITY_TYPES.user,
21
+ Role: CLICKHOUSE_ENTITY_TYPES.role,
22
+ RowPolicy: CLICKHOUSE_ENTITY_TYPES.rowPolicy,
23
+ Grant: CLICKHOUSE_ENTITY_TYPES.grant,
24
+ Quota: CLICKHOUSE_ENTITY_TYPES.quota,
25
+ SettingsProfile: CLICKHOUSE_ENTITY_TYPES.settingsProfile,
26
+ PostgresSchema: POSTGRES_ENTITY_TYPES.schema,
27
+ PostgresTable: POSTGRES_ENTITY_TYPES.table,
28
+ PostgresIndex: POSTGRES_ENTITY_TYPES.index,
29
+ PostgresView: POSTGRES_ENTITY_TYPES.view,
30
+ PostgresMaterializedView: POSTGRES_ENTITY_TYPES.materializedView,
31
+ PostgresSequence: POSTGRES_ENTITY_TYPES.sequence,
32
+ PostgresEnum: POSTGRES_ENTITY_TYPES.enum,
33
+ PostgresCompositeType: POSTGRES_ENTITY_TYPES.composite,
34
+ PostgresRangeType: POSTGRES_ENTITY_TYPES.range,
35
+ PostgresDomain: POSTGRES_ENTITY_TYPES.domain,
36
+ PostgresAggregate: POSTGRES_ENTITY_TYPES.aggregate,
37
+ PostgresCollation: POSTGRES_ENTITY_TYPES.collation,
38
+ PostgresCast: POSTGRES_ENTITY_TYPES.cast,
39
+ PostgresEventTrigger: POSTGRES_ENTITY_TYPES.eventTrigger,
40
+ PostgresExtension: POSTGRES_ENTITY_TYPES.extension,
41
+ PostgresFunction: POSTGRES_ENTITY_TYPES.function,
42
+ PostgresProcedure: POSTGRES_ENTITY_TYPES.procedure,
43
+ PostgresTrigger: POSTGRES_ENTITY_TYPES.trigger,
44
+ PostgresPolicy: POSTGRES_ENTITY_TYPES.policy,
45
+ PostgresRole: POSTGRES_ENTITY_TYPES.role,
46
+ PostgresGrant: POSTGRES_ENTITY_TYPES.grant,
47
+ PostgresDefaultPrivileges: POSTGRES_ENTITY_TYPES.defaultPrivileges,
48
+ };
49
+ export function buildRegistry() {
50
+ const registry = Object.fromEntries(Object.entries(KINDS).map(([name, resourceType]) => [name, { resourceType, kind: "resource", lexicon: "sql" }]));
51
+ return `${JSON.stringify(registry, null, 2)}\n`;
52
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * `AuditLogTable`: an append-only audit log partitioned by month.
3
+ *
4
+ * The parent is `PARTITION BY RANGE (<timestamp>)`, and its primary key
5
+ * includes the timestamp, which Postgres requires of every unique key on a
6
+ * partitioned table. The composite declares a default partition so no insert
7
+ * is refused before a month's partition exists; a partition per month is
8
+ * created ahead of time by whatever the project uses for partition upkeep (a
9
+ * `FOR VALUES FROM ('2026-10-01') TO ('2026-11-01')` partition of the parent
10
+ * is one more `table` call). An index on `(<actor>, <timestamp>)` serves the
11
+ * question an audit log is asked most: what did this actor do, and when.
12
+ */
13
+ import { Composite } from "@intentius/chant/composite";
14
+ import { index, table } from "../postgres/entities.js";
15
+ /** An append-only audit log partitioned by month on its timestamp, with a default partition and an index by actor. */
16
+ export const AuditLogTable = Composite((props) => {
17
+ const schema = props.schema ?? "public";
18
+ const ts = props.timestamp ?? "occurred_at";
19
+ const actor = props.actor ?? "actor";
20
+ const log = table `
21
+ CREATE TABLE ${schema}.${props.name} (
22
+ id bigint GENERATED ALWAYS AS IDENTITY,
23
+ ${ts} timestamptz NOT NULL DEFAULT now(),
24
+ ${actor} text NOT NULL,
25
+ action text NOT NULL,
26
+ subject text,
27
+ details jsonb NOT NULL DEFAULT '{}',
28
+ PRIMARY KEY (id, ${ts})
29
+ ) PARTITION BY RANGE (${ts})`;
30
+ const fallback = table `
31
+ CREATE TABLE ${schema}.${`${props.name}_default`} PARTITION OF ${log} DEFAULT`;
32
+ const byActor = index `
33
+ CREATE INDEX ${`${props.name}_actor_idx`} ON ${log} (${actor}, ${ts} DESC)`;
34
+ return { table: log, defaultPartition: fallback, actorIndex: byActor };
35
+ }, "AuditLogTable");