@intentius/chant-lexicon-sql 0.124.30 → 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,137 @@
1
+ /**
2
+ * `chant sql diff` and `chant sql plan` for Postgres: the classified change
3
+ * between two builds, or between a build and a live server.
4
+ *
5
+ * `diff` is offline and needs no server. `plan` reads the server
6
+ * `sql.profiles.<env>` binds, and asks it about expressions the rules leave
7
+ * different (`./server-normalize.ts`). Both exit 2 when a change can only be
8
+ * made as expand and contract, which a plan refuses, and 0 otherwise.
9
+ */
10
+ import { bindPostgres, loadSqlConfig } from "../live/bind.js";
11
+ import { markProviderOwned, readLiveSchema } from "../live/catalog.js";
12
+ import { scopeFor } from "../live/describe-resources.js";
13
+ import { diffPgSchemas } from "./diff.js";
14
+ import { renderPgDiff } from "./report.js";
15
+ import { keyedByQualifiedName, pgBuildFileMajor, pgSchemaFromBuildFile, pgSchemaFromLive } from "./schema.js";
16
+ import { serverNormalized } from "./server-normalize.js";
17
+ import { POSTGRES_ENTITY_TYPES } from "../entity-types.js";
18
+ import { migrationOpSuggestions } from "../migrate/handoff.js";
19
+ import { POSTGRES_LATEST_MAJOR } from "../../spec/postgres-pin.js";
20
+ import { ACCESS_MANAGED_KINDS } from "./normalize.js";
21
+ import { accessChangesAgainst } from "../access/changes.js";
22
+ export function emitPg(diff, json, title) {
23
+ console.log(json ? JSON.stringify(diff, null, 2) : renderPgDiff(diff, { title }));
24
+ return diff.refused.length > 0 ? 2 : 0;
25
+ }
26
+ /** The major the project declares (`sql.postgresMajor`); undefined is the newest pinned one. */
27
+ export async function projectMajor(options = {}) {
28
+ const config = options.config ?? (await loadSqlConfig(options.cwd ?? process.cwd()));
29
+ return config?.sql?.postgresMajor;
30
+ }
31
+ /**
32
+ * Offline, the major is the one the builds recorded (`postgresMajor`): the
33
+ * newer build's, else the older's, so a diff between revisions does not
34
+ * depend on today's config. `configMajor` (`sql.postgresMajor`) answers for
35
+ * builds that recorded none.
36
+ */
37
+ export function diffPgBuildFiles(before, after, configMajor) {
38
+ const major = pgBuildFileMajor(after) ?? pgBuildFileMajor(before) ?? configMajor;
39
+ const head = pgSchemaFromBuildFile(after);
40
+ return withMigrationOps(diffPgSchemas(pgSchemaFromBuildFile(before), head, { major }), head, "<env>");
41
+ }
42
+ /** The diff with the migration Op to run for each refused column rename or type change (`../migrate/handoff.ts`). */
43
+ export function withMigrationOps(diff, declared, env, defaultSchema = "public") {
44
+ const migrationOps = migrationOpSuggestions(diff.refused, new Map(declared.map((o) => [o.key, o.canonical])), env, defaultSchema);
45
+ return migrationOps.length > 0 ? { ...diff, migrationOps } : diff;
46
+ }
47
+ /** The live server's major, from `server_version_num` (`180006` is 18). */
48
+ async function serverMajor(client) {
49
+ const rows = await client.query("select current_setting('server_version_num') as v");
50
+ const n = Number(rows[0]?.v);
51
+ return Number.isFinite(n) && n > 0 ? Math.floor(n / 10000) : undefined;
52
+ }
53
+ const differs = (a, b) => JSON.stringify([a.canonical.fields, a.canonical.columns, a.canonical.constraints]) !== JSON.stringify([b.canonical.fields, b.canonical.columns, b.canonical.constraints]);
54
+ /**
55
+ * The declared objects (a build's, keyed by export name) against what a
56
+ * connected server holds: read in the declarations' scope, the server asked
57
+ * about expressions the rules leave different, then diffed. The plan and the
58
+ * applier (`../apply/`) share it.
59
+ */
60
+ export async function planAgainstClient(client, target, build, options = {}) {
61
+ const managed = target.access === true;
62
+ const { kept, unmanaged } = accessScoped(keyedByQualifiedName(build), managed);
63
+ const declaredRaw = kept;
64
+ const scope = scopeFor(target, declaredRaw.filter((o) => o.canonical.kind !== "role" && o.canonical.kind !== "grant" && o.canonical.kind !== "defaultPrivileges").map((o) => ({ type: POSTGRES_ENTITY_TYPES[o.canonical.kind], props: { name: o.canonical.name, ...(o.canonical.schema ? { schema: o.canonical.schema } : {}) } })));
65
+ const roles = declaredRaw.filter((o) => o.canonical.kind === "role").map((o) => o.canonical.name);
66
+ const liveObjects = markProviderOwned(await (options.readLive ?? readLiveSchema)(client, { schemas: scope, access: managed, roles }), target.provider);
67
+ const live = pgSchemaFromLive(liveObjects, target.defaultSchema);
68
+ const liveByKey = new Map(live.map((o) => [o.key, o]));
69
+ const declared = [];
70
+ for (const o of declaredRaw) {
71
+ const l = liveByKey.get(o.key);
72
+ if (l && differs(o, l) && (o.canonical.kind === "table" || o.canonical.kind === "view" || o.canonical.kind === "materializedView" || o.canonical.kind === "trigger" || o.canonical.kind === "policy")) {
73
+ declared.push({ ...o, canonical: { ...o.canonical, ...(await (options.serverNormalize ?? serverNormalized)(client, o.canonical, target.defaultSchema)) } });
74
+ }
75
+ else
76
+ declared.push(o);
77
+ }
78
+ // The build's major, else the server's own when it differs, since the server is what takes the locks.
79
+ const running = await serverMajor(client).catch(() => undefined);
80
+ const major = running ?? options.major;
81
+ const diff = diffPgSchemas(live, declared, { major, access: false });
82
+ if (managed) {
83
+ const access = await accessChangesAgainst(client, declared, { major: major ?? POSTGRES_LATEST_MAJOR, ...(scope ? { scope } : {}) });
84
+ if (access.length > 0) {
85
+ diff.changes.push(...access.map((a) => a.change));
86
+ diff.access = access;
87
+ }
88
+ }
89
+ else if (unmanaged > 0) {
90
+ diff.hints.push(`${unmanaged} access declaration${unmanaged === 1 ? " is" : "s are"} not planned: ${options.environment ?? "this environment"}'s profile does not manage access (sql.profiles.<env>.access)`);
91
+ }
92
+ if (running !== undefined && options.major !== undefined && running !== options.major) {
93
+ diff.hints.push(`the build targets Postgres ${options.major} but ${options.environment ?? "the server"} runs Postgres ${running}; changes are classified for ${running}`);
94
+ }
95
+ return { declared, live, liveObjects, diff, ...(major !== undefined ? { major } : {}), ...(scope ? { scope } : {}) };
96
+ }
97
+ /**
98
+ * The declarations a plan compares: all of them where the profile manages
99
+ * access; otherwise none of the policies, roles, grants and default
100
+ * privileges, and no table's row-level security, so a project that manages
101
+ * access elsewhere is not affected. `unmanaged` counts what was left out.
102
+ */
103
+ export function accessScoped(objects, managed) {
104
+ if (managed)
105
+ return { kept: [...objects], unmanaged: 0 };
106
+ let unmanaged = 0;
107
+ const kept = [];
108
+ for (const o of objects) {
109
+ if (ACCESS_MANAGED_KINDS.has(o.canonical.kind)) {
110
+ unmanaged++;
111
+ continue;
112
+ }
113
+ if (o.canonical.kind === "table" && (o.canonical.fields.rowSecurity || o.canonical.fields.forceRowSecurity)) {
114
+ unmanaged++;
115
+ const { rowSecurity: _r, forceRowSecurity: _f, ...fields } = o.canonical.fields;
116
+ kept.push({ ...o, canonical: { ...o.canonical, fields } });
117
+ }
118
+ else
119
+ kept.push(o);
120
+ }
121
+ return { kept, unmanaged };
122
+ }
123
+ /** The declared objects against the server, keys by qualified name and labels with the export name. */
124
+ export async function planPgAgainstServer(environment, buildFile, options = {}) {
125
+ const { target, client } = await bindPostgres({ ...options, environment });
126
+ try {
127
+ const targeted = pgBuildFileMajor(buildFile) ?? (await projectMajor(options));
128
+ const { declared, diff } = await planAgainstClient(client, target, pgSchemaFromBuildFile(buildFile, target.defaultSchema), { ...(targeted !== undefined ? { major: targeted } : {}), environment });
129
+ const { migrationOps } = withMigrationOps(diff, declared, environment, target.defaultSchema);
130
+ const label = new Map(declared.map((o) => [o.key, `${o.canonical.exportName} (${o.key.slice(o.key.indexOf(" ") + 1)})`]));
131
+ const changes = diff.changes.map((c) => ({ ...c, object: label.get(c.object) ?? (c.object.startsWith("acl ") ? c.object.slice(4) : c.object) }));
132
+ return { changes, hints: diff.hints, refused: changes.filter((c) => c.class === "expand"), ...(migrationOps ? { migrationOps } : {}) };
133
+ }
134
+ finally {
135
+ await client.end();
136
+ }
137
+ }
@@ -0,0 +1,324 @@
1
+ /**
2
+ * `observeResourcesDeep()` for the Postgres dialect: each declared object's
3
+ * live definition as a property tree in the declaration's own shape, so
4
+ * `chant lifecycle diff --live` reports property drift with no false drift.
5
+ *
6
+ * The reader parses what the catalog prints for the object (`../live/catalog.ts`)
7
+ * with the same tag the declaration used, which gives the same props shape.
8
+ * A field whose canonical form (`./normalize.ts`) equals the declaration's is
9
+ * written with the declaration's own value, so `timestamptz` and
10
+ * `timestamp with time zone`, or a constraint the declaration left unnamed and
11
+ * the name Postgres gave it, do not read as drift; a field that differs keeps
12
+ * the server's value. `deepNormalizationHooks` prunes what is not a property
13
+ * of the object (the DDL text, the template source, lineage, reads).
14
+ */
15
+ import { deepObservation } from "@intentius/chant/deep-observation";
16
+ import { bindPostgres, classifyPostgresFailure } from "../live/bind.js";
17
+ import { liveKey, readLiveSchema } from "../live/catalog.js";
18
+ import { accessUnobserved, declaredAddress, scopeFor } from "../live/describe-resources.js";
19
+ import * as tags from "../entities.js";
20
+ import { POSTGRES_ENTITY_TYPES } from "../entity-types.js";
21
+ import { canonicalPgObject, columnShape, sameConstraint } from "./normalize.js";
22
+ import { serverNormalized } from "./server-normalize.js";
23
+ import { accessChangesAgainst } from "../access/changes.js";
24
+ import { POSTGRES_LATEST_MAJOR } from "../../spec/postgres-pin.js";
25
+ const TAG = {
26
+ [POSTGRES_ENTITY_TYPES.schema]: tags.schema,
27
+ [POSTGRES_ENTITY_TYPES.table]: tags.table,
28
+ [POSTGRES_ENTITY_TYPES.index]: tags.index,
29
+ [POSTGRES_ENTITY_TYPES.view]: tags.view,
30
+ [POSTGRES_ENTITY_TYPES.materializedView]: tags.view,
31
+ [POSTGRES_ENTITY_TYPES.sequence]: tags.sequence,
32
+ [POSTGRES_ENTITY_TYPES.enum]: tags.type,
33
+ [POSTGRES_ENTITY_TYPES.composite]: tags.type,
34
+ [POSTGRES_ENTITY_TYPES.range]: tags.type,
35
+ [POSTGRES_ENTITY_TYPES.domain]: tags.domain,
36
+ [POSTGRES_ENTITY_TYPES.aggregate]: tags.aggregate,
37
+ [POSTGRES_ENTITY_TYPES.collation]: tags.collation,
38
+ [POSTGRES_ENTITY_TYPES.cast]: tags.cast,
39
+ [POSTGRES_ENTITY_TYPES.eventTrigger]: tags.eventTrigger,
40
+ [POSTGRES_ENTITY_TYPES.extension]: tags.extension,
41
+ [POSTGRES_ENTITY_TYPES.function]: tags.func,
42
+ [POSTGRES_ENTITY_TYPES.procedure]: tags.procedure,
43
+ [POSTGRES_ENTITY_TYPES.trigger]: tags.trigger,
44
+ [POSTGRES_ENTITY_TYPES.policy]: tags.policy,
45
+ [POSTGRES_ENTITY_TYPES.role]: tags.role,
46
+ [POSTGRES_ENTITY_TYPES.grant]: tags.grant,
47
+ [POSTGRES_ENTITY_TYPES.defaultPrivileges]: tags.grant,
48
+ };
49
+ /** The props of what the server printed, parsed with the declaration's tag. */
50
+ export function liveProps(o) {
51
+ const strings = Object.assign([o.statement], { raw: [o.statement] });
52
+ return { ...TAG[o.type](strings).props };
53
+ }
54
+ const PROP_OF_FIELD = {
55
+ comment: ["comment"],
56
+ authorization: ["authorization"],
57
+ schema: ["schema"],
58
+ version: ["version"],
59
+ labels: ["labels"],
60
+ dataType: ["dataType"],
61
+ collate: ["collate"],
62
+ default: ["default"],
63
+ notNull: ["notNull"],
64
+ options: ["dataType", "increment", "minValue", "maxValue", "start", "cache", "cycle"],
65
+ unlogged: ["persistence"],
66
+ ownedBy: ["ownedBy"],
67
+ table: ["table", "tableName"],
68
+ unique: ["unique"],
69
+ method: ["method"],
70
+ elements: ["elements"],
71
+ include: ["include"],
72
+ nullsNotDistinct: ["nullsNotDistinct"],
73
+ with: ["with"],
74
+ where: ["where"],
75
+ tablespace: ["tablespace"],
76
+ query: ["query"],
77
+ columns: ["columns"],
78
+ checkOption: ["checkOption"],
79
+ withData: ["withData"],
80
+ columnComments: ["columnComments"],
81
+ partitionBy: ["partitionBy"],
82
+ partitionOf: ["partitionOf"],
83
+ partitionBound: ["partitionBound"],
84
+ inherits: ["inherits"],
85
+ using: ["using"],
86
+ args: ["args"],
87
+ returns: ["returns", "returnsTable"],
88
+ language: ["language"],
89
+ body: ["body"],
90
+ link: ["link"],
91
+ securityDefiner: ["securityDefiner"],
92
+ set: ["set"],
93
+ transform: ["transform"],
94
+ volatility: ["volatility"],
95
+ strict: ["strict"],
96
+ leakproof: ["leakproof"],
97
+ parallel: ["parallel"],
98
+ cost: ["cost"],
99
+ rows: ["rows"],
100
+ support: ["support"],
101
+ window: ["window"],
102
+ constraint: ["constraint"],
103
+ timing: ["timing"],
104
+ events: ["events"],
105
+ forEach: ["forEach"],
106
+ when: ["when"],
107
+ function: ["function", "functionName"],
108
+ from: ["from"],
109
+ deferrable: ["deferrable"],
110
+ initiallyDeferred: ["initiallyDeferred"],
111
+ referencing: ["referencing"],
112
+ restrictive: ["permissive"],
113
+ command: ["command"],
114
+ roles: ["roles"],
115
+ check: ["check"],
116
+ attributes: ["options"],
117
+ connectionLimit: ["connectionLimit"],
118
+ rowSecurity: ["rowSecurity"],
119
+ forceRowSecurity: ["forceRowSecurity"],
120
+ attributeDefs: ["attributes"],
121
+ subtype: ["subtype"],
122
+ subtypeOpclass: ["subtypeOpclass"],
123
+ collation: ["collation"],
124
+ canonical: ["canonical"],
125
+ subtypeDiff: ["subtypeDiff"],
126
+ multirange: ["multirangeTypeName"],
127
+ definition: ["options"],
128
+ castMethod: ["method", "functionArgs"],
129
+ context: ["context"],
130
+ event: ["event"],
131
+ tags: ["tags"],
132
+ };
133
+ /** Props keys that hold constraints, by constraint kind. */
134
+ const CONSTRAINT_PROPS = {
135
+ "PRIMARY KEY": "primaryKey",
136
+ UNIQUE: "uniques",
137
+ CHECK: "checks",
138
+ "FOREIGN KEY": "foreignKeys",
139
+ EXCLUDE: "exclusions",
140
+ };
141
+ /** The live props, with each field the declaration means the same as written the declaration's way. */
142
+ export function inDeclaredVocabulary(declared, live, d, l) {
143
+ const out = { ...live };
144
+ const adopt = (key) => {
145
+ if (declared[key] === undefined)
146
+ delete out[key];
147
+ else
148
+ out[key] = declared[key];
149
+ };
150
+ if (d.name === l.name)
151
+ adopt("name");
152
+ if (d.schema === l.schema)
153
+ adopt("schema");
154
+ // An aggregate's argument types and a cast's two types are its identity: the same, they are the declaration's.
155
+ if (d.kind === "aggregate" && d.signature === l.signature)
156
+ for (const key of ["args", "orderBy"])
157
+ adopt(key);
158
+ if (d.kind === "cast" && d.name === l.name)
159
+ for (const key of ["sourceType", "targetType"])
160
+ adopt(key);
161
+ const fieldKeys = new Set([...Object.keys(d.fields), ...Object.keys(l.fields)]);
162
+ for (const f of fieldKeys) {
163
+ if (f === "schema" && d.kind !== "extension")
164
+ continue;
165
+ if (JSON.stringify(d.fields[f]) !== JSON.stringify(l.fields[f]))
166
+ continue;
167
+ for (const key of PROP_OF_FIELD[f] ?? [])
168
+ adopt(key);
169
+ }
170
+ // Fields neither side's canonical form carries (left at their default) are the declaration's too.
171
+ for (const [f, keys] of Object.entries(PROP_OF_FIELD)) {
172
+ if (!fieldKeys.has(f))
173
+ for (const key of keys)
174
+ if (f !== "schema" || d.kind === "extension")
175
+ adopt(key);
176
+ }
177
+ if (d.kind === "table") {
178
+ const declaredColumns = declared.columns ?? [];
179
+ const liveColumns = live.columns ?? [];
180
+ // Columns are matched by name and listed in the declaration's order, the
181
+ // live columns the declaration does not name after them. Postgres keeps a
182
+ // table's columns in the order they were added, and no ALTER moves one: a
183
+ // column renamed by expand and contract is last on the server and where
184
+ // the old one was in the declaration (INTENTIUS/sql-yodeler#47). The plan
185
+ // compares columns by name too, so an order the server cannot be brought
186
+ // to is not drift.
187
+ const read = liveColumns.map((c, i) => {
188
+ const lc = l.columns[i];
189
+ const dc = lc ? d.columns.find((x) => x.name === lc.name) : undefined;
190
+ return { at: dc?.position, value: lc && dc && columnShape(lc) === columnShape(dc) ? declaredColumns[dc.position] : c };
191
+ });
192
+ out.columns = [
193
+ ...read.filter((c) => c.at !== undefined).sort((a, b) => a.at - b.at),
194
+ ...read.filter((c) => c.at === undefined),
195
+ ].map((c) => c.value);
196
+ }
197
+ // An aggregate's or a collation's `options` is the declaration's only when every field read from it is the same (a role's `options` shares the key).
198
+ const optionFields = d.kind === "aggregate" ? ["definition"] : d.kind === "collation" ? ["provider", "lcCollate", "lcCtype", "locale", "nondeterministic", "rules"] : undefined;
199
+ if (optionFields) {
200
+ if (optionFields.every((f) => JSON.stringify(d.fields[f]) === JSON.stringify(l.fields[f])))
201
+ adopt("options");
202
+ else
203
+ out.options = live.options;
204
+ }
205
+ // A cast's `method` shares its key with an index's.
206
+ if (d.kind === "cast" && JSON.stringify(d.fields.castMethod) !== JSON.stringify(l.fields.castMethod)) {
207
+ out.method = live.method;
208
+ out.functionArgs = live.functionArgs;
209
+ }
210
+ if (d.kind === "table" || d.kind === "domain") {
211
+ // A constraint the declaration says the same as the server is the declaration's (its name included or left out).
212
+ const kinds = d.kind === "domain" ? ["CHECK"] : Object.keys(CONSTRAINT_PROPS);
213
+ for (const kind of kinds) {
214
+ const key = CONSTRAINT_PROPS[kind];
215
+ const dl = d.constraints.filter((c) => c.kind === kind);
216
+ const ll = l.constraints.filter((c) => c.kind === kind);
217
+ const all = dl.length === ll.length && dl.every((c) => ll.some((x) => sameConstraint(c, x) && c.notValid === x.notValid && c.comment === x.comment));
218
+ if (all)
219
+ adopt(key);
220
+ }
221
+ }
222
+ return out;
223
+ }
224
+ export async function observeResourcesDeep(options) {
225
+ const resources = {};
226
+ const unobserved = {};
227
+ let target;
228
+ let live;
229
+ let client;
230
+ const declared = options.entityNames.map((name) => ({ name, entity: options.entities.get(name) }));
231
+ try {
232
+ const bound = await bindPostgres(options);
233
+ target = bound.target;
234
+ client = bound.client;
235
+ try {
236
+ live = await readLiveSchema(bound.client, {
237
+ schemas: scopeFor(target, declared.filter((x) => x.entity?.entityType.startsWith("Postgres::")).map((x) => ({ type: x.entity.entityType, props: x.entity.props }))),
238
+ access: target.access === true,
239
+ roles: declared.filter((x) => x.entity?.entityType === POSTGRES_ENTITY_TYPES.role).map((x) => String(x.entity.props.name)),
240
+ });
241
+ }
242
+ catch (err) {
243
+ await bound.client.end();
244
+ throw err;
245
+ }
246
+ }
247
+ catch (err) {
248
+ const why = classifyPostgresFailure(err);
249
+ for (const { name, entity } of declared)
250
+ unobserved[name] = { type: entity?.entityType ?? "", reason: why.reason, detail: why.detail };
251
+ return deepObservation({}, unobserved);
252
+ }
253
+ const pending = [];
254
+ try {
255
+ if (target.access === true) {
256
+ // #3706: the privilege changes an apply would make, which no one
257
+ // declaration's properties show, so the plan digest moves with them.
258
+ try {
259
+ pending.push(...(await pendingAccess(client, target.defaultSchema, declared, scopeFor(target, declared.filter((x) => x.entity?.entityType.startsWith("Postgres::")).map((x) => ({ type: x.entity.entityType, props: x.entity.props }))))));
260
+ }
261
+ catch (err) {
262
+ for (const { name, entity } of declared) {
263
+ if (entity?.entityType === POSTGRES_ENTITY_TYPES.grant || entity?.entityType === POSTGRES_ENTITY_TYPES.defaultPrivileges) {
264
+ unobserved[name] = { type: entity.entityType, reason: "read-failed", detail: `the server's privileges could not be compared: ${err.message}` };
265
+ }
266
+ }
267
+ }
268
+ }
269
+ const byKey = new Map(live.map((o) => [liveKey(o.type, o.schema, o.name, o.signature), o]));
270
+ for (const { name, entity } of declared) {
271
+ if (!entity || !entity.entityType.startsWith("Postgres::")) {
272
+ unobserved[name] = { type: entity?.entityType ?? "", reason: "unsupported-kind" };
273
+ continue;
274
+ }
275
+ const why = accessUnobserved(entity.entityType, target.access === true);
276
+ if (why) {
277
+ if (unobserved[name])
278
+ continue;
279
+ unobserved[name] = { type: entity.entityType, ...why };
280
+ continue;
281
+ }
282
+ const { schema, name: objectName, signature } = declaredAddress({ type: entity.entityType, props: entity.props }, target.defaultSchema);
283
+ const o = byKey.get(liveKey(entity.entityType, schema, objectName, signature));
284
+ if (!o)
285
+ continue;
286
+ try {
287
+ const lp = liveProps(o);
288
+ let d = canonicalPgObject(entity.entityType, entity.props, target.defaultSchema);
289
+ const l = canonicalPgObject(o.type, lp, target.defaultSchema);
290
+ // A trigger's WHEN and a policy's expressions as the server would print them, when the rules leave them different.
291
+ if ((d.kind === "trigger" && d.fields.when !== l.fields.when) || (d.kind === "policy" && (d.fields.using !== l.fields.using || d.fields.check !== l.fields.check))) {
292
+ d = await serverNormalized(client, d, target.defaultSchema);
293
+ }
294
+ resources[name] = { type: o.type, physicalId: o.oid, properties: inDeclaredVocabulary(entity.props, lp, d, l) };
295
+ }
296
+ catch (err) {
297
+ unobserved[name] = { type: entity.entityType, reason: "read-failed", detail: `the server's definition does not parse: ${err.message}` };
298
+ }
299
+ }
300
+ }
301
+ finally {
302
+ await client.end();
303
+ }
304
+ return deepObservation(resources, unobserved, pending);
305
+ }
306
+ /**
307
+ * The privilege changes an apply would make (#3706), as `chant sql plan`
308
+ * computes them, one per object and grantee: the statements, and the
309
+ * declarations behind them.
310
+ */
311
+ async function pendingAccess(client, defaultSchema, declared, scope) {
312
+ const objects = declared
313
+ .filter((x) => x.entity?.entityType.startsWith("Postgres::"))
314
+ .map((x) => ({ key: x.name, canonical: canonicalPgObject(x.entity.entityType, x.entity.props, defaultSchema) }));
315
+ const [row] = await client.query("SELECT current_setting('server_version_num') AS v");
316
+ const n = Number(row?.v);
317
+ const major = Number.isFinite(n) && n > 0 ? Math.floor(n / 10000) : POSTGRES_LATEST_MAJOR;
318
+ const changes = await accessChangesAgainst(client, objects, { major, ...(scope ? { scope } : {}) });
319
+ return changes.map((c) => ({
320
+ subject: c.change.object.startsWith("acl ") ? c.change.object.slice(4) : c.change.object,
321
+ change: c.sql.join("; "),
322
+ ...(c.exports.length > 0 ? { entities: [...c.exports].sort() } : {}),
323
+ }));
324
+ }