@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,28 @@
1
+ /**
2
+ * The Postgres entity types, apart from the tags so generation can name them
3
+ * before the generated tables the tags read exist.
4
+ */
5
+ export const POSTGRES_ENTITY_TYPES = {
6
+ schema: "Postgres::Schema",
7
+ table: "Postgres::Table",
8
+ index: "Postgres::Index",
9
+ view: "Postgres::View",
10
+ materializedView: "Postgres::MaterializedView",
11
+ sequence: "Postgres::Sequence",
12
+ enum: "Postgres::Enum",
13
+ composite: "Postgres::CompositeType",
14
+ range: "Postgres::RangeType",
15
+ domain: "Postgres::Domain",
16
+ aggregate: "Postgres::Aggregate",
17
+ collation: "Postgres::Collation",
18
+ cast: "Postgres::Cast",
19
+ eventTrigger: "Postgres::EventTrigger",
20
+ extension: "Postgres::Extension",
21
+ function: "Postgres::Function",
22
+ procedure: "Postgres::Procedure",
23
+ trigger: "Postgres::Trigger",
24
+ policy: "Postgres::Policy",
25
+ role: "Postgres::Role",
26
+ grant: "Postgres::Grant",
27
+ defaultPrivileges: "Postgres::DefaultPrivileges",
28
+ };
@@ -0,0 +1,283 @@
1
+ /**
2
+ * The import IR as TypeScript: one `schema.ts` holding every imported object
3
+ * as a `schema`, `table`, `index`, `view`, `sequence`, `type`, `domain`,
4
+ * `extension`, `func`, `aggregate`, `collation`, `cast`, `eventTrigger` (and
5
+ * the rest of the tags) declaration, in dependency order, with the references between
6
+ * them interpolated, so the build orders them as the server did:
7
+ *
8
+ * - a qualified name of another imported object becomes `${export}`
9
+ * (`REFERENCES app.users(id)` is `REFERENCES ${users}(id)`, a type
10
+ * `app.order_status` is `${orderStatus}`);
11
+ * - a sequence named in a `regclass` literal becomes the reference
12
+ * (`nextval('app.invoice_seq'::regclass)` is `nextval(${invoiceSeq})`);
13
+ * - the schema part of the object's own name, wherever the name appears,
14
+ * becomes the schema's export (`CREATE TABLE ${appSchema}.users`);
15
+ * - an extension's `WITH SCHEMA`, a grant's `ON SCHEMA` and default
16
+ * privileges' `IN SCHEMA` name the schema's export;
17
+ * - a grant's object becomes the object's export (`GRANT SELECT ON TABLE
18
+ * ${orders} TO reader`), a policy's table its table's.
19
+ *
20
+ * Columns stay text, as they do for ClickHouse (#3236): which relation a bare
21
+ * column belongs to is the server's to resolve. One file, because the
22
+ * references cross schemas freely.
23
+ */
24
+ import { relationKey } from "../../core/typed.js";
25
+ import { isTrivia, tokenizeText } from "../tokens.js";
26
+ import { identValue, parseStatements } from "../parser.js";
27
+ import { POSTGRES_ENTITY_TYPES } from "../entity-types.js";
28
+ const TAG = {
29
+ [POSTGRES_ENTITY_TYPES.schema]: "schema",
30
+ [POSTGRES_ENTITY_TYPES.table]: "table",
31
+ [POSTGRES_ENTITY_TYPES.index]: "index",
32
+ [POSTGRES_ENTITY_TYPES.view]: "view",
33
+ [POSTGRES_ENTITY_TYPES.materializedView]: "view",
34
+ [POSTGRES_ENTITY_TYPES.sequence]: "sequence",
35
+ [POSTGRES_ENTITY_TYPES.enum]: "type",
36
+ [POSTGRES_ENTITY_TYPES.composite]: "type",
37
+ [POSTGRES_ENTITY_TYPES.range]: "type",
38
+ [POSTGRES_ENTITY_TYPES.domain]: "domain",
39
+ [POSTGRES_ENTITY_TYPES.aggregate]: "aggregate",
40
+ [POSTGRES_ENTITY_TYPES.collation]: "collation",
41
+ [POSTGRES_ENTITY_TYPES.cast]: "cast",
42
+ [POSTGRES_ENTITY_TYPES.eventTrigger]: "eventTrigger",
43
+ [POSTGRES_ENTITY_TYPES.extension]: "extension",
44
+ [POSTGRES_ENTITY_TYPES.function]: "func",
45
+ [POSTGRES_ENTITY_TYPES.procedure]: "procedure",
46
+ [POSTGRES_ENTITY_TYPES.trigger]: "trigger",
47
+ [POSTGRES_ENTITY_TYPES.policy]: "policy",
48
+ [POSTGRES_ENTITY_TYPES.role]: "role",
49
+ [POSTGRES_ENTITY_TYPES.grant]: "grant",
50
+ [POSTGRES_ENTITY_TYPES.defaultPrivileges]: "grant",
51
+ };
52
+ /** The kinds no other object's DDL names. */
53
+ const UNREFERENCED = new Set([
54
+ POSTGRES_ENTITY_TYPES.index,
55
+ POSTGRES_ENTITY_TYPES.trigger,
56
+ POSTGRES_ENTITY_TYPES.policy,
57
+ POSTGRES_ENTITY_TYPES.grant,
58
+ POSTGRES_ENTITY_TYPES.defaultPrivileges,
59
+ POSTGRES_ENTITY_TYPES.role,
60
+ POSTGRES_ENTITY_TYPES.extension,
61
+ POSTGRES_ENTITY_TYPES.cast,
62
+ POSTGRES_ENTITY_TYPES.eventTrigger,
63
+ ]);
64
+ /** The tag each entity type is declared with (`table`, `func`, ...). */
65
+ export const POSTGRES_TAG_OF = TAG;
66
+ /** Text that sits inside a template literal: a backquote and `${` escaped. */
67
+ const templateSafe = (s) => s.replace(/\\(?=`|\$\{)/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
68
+ const isName = (t) => t !== undefined && (t.kind === "ident" || t.kind === "qident");
69
+ /** `'app.invoice_seq'` as the qualified name it holds, or the bare name when unqualified. */
70
+ function regclassName(t) {
71
+ if (t.kind !== "string" || !/^'.*'$/s.test(t.text))
72
+ return undefined;
73
+ const inner = t.text.slice(1, -1).replace(/''/g, "'");
74
+ const pieces = inner.match(/"(?:[^"]|"")*"|[^.]+/g)?.map((p) => identValue(p)) ?? [];
75
+ return pieces.length === 2 ? `${pieces[0]}.${pieces[1]}` : pieces.length === 1 ? pieces[0] : undefined;
76
+ }
77
+ /** Words after which a bare name is a relation: `FROM t`, `JOIN t`, `REFERENCES t`. */
78
+ const RELATION_WORDS = new Set(["FROM", "JOIN", "REFERENCES"]);
79
+ /**
80
+ * The tokens where a bare (unqualified) name is another object's: a column's
81
+ * or a domain's type, the table of an index, trigger or policy, a trigger's
82
+ * function, a grant's objects. A `FROM`, `JOIN` or `REFERENCES` before a
83
+ * name is read off the tokens directly.
84
+ */
85
+ function bareSpots(tokens) {
86
+ const out = new Set();
87
+ let nodes;
88
+ try {
89
+ nodes = parseStatements(tokens);
90
+ }
91
+ catch {
92
+ return out;
93
+ }
94
+ const first = (from, to) => {
95
+ for (let i = from; i < to; i++)
96
+ if (!isTrivia(tokens[i]))
97
+ return out.add(i);
98
+ return out;
99
+ };
100
+ const name = (n) => n && n.span.to - n.span.from > 0 && first(n.span.from, n.span.to);
101
+ for (const node of nodes) {
102
+ if (node.statement === "table")
103
+ for (const c of node.columns)
104
+ if (c.type)
105
+ first(c.type.from, c.type.to);
106
+ if (node.statement === "domain")
107
+ first(node.type.from, node.type.to);
108
+ if (node.statement === "composite")
109
+ for (const a of node.attributes)
110
+ first(a.type.from, a.type.to);
111
+ if (node.statement === "cast") {
112
+ first(node.source.from, node.source.to);
113
+ first(node.target.from, node.target.to);
114
+ name(node.function);
115
+ }
116
+ if (node.statement === "eventTrigger")
117
+ name(node.function);
118
+ if (node.statement === "index" || node.statement === "policy")
119
+ name(node.table);
120
+ if (node.statement === "trigger") {
121
+ name(node.table);
122
+ name(node.function);
123
+ }
124
+ if (node.statement === "grant")
125
+ for (const o of node.objects)
126
+ name(o.name);
127
+ }
128
+ return out;
129
+ }
130
+ function templateBody(item, byQualified, schemaExport) {
131
+ const tokens = tokenizeText(item.ddl, 0);
132
+ const sig = [];
133
+ tokens.forEach((t, i) => {
134
+ if (!isTrivia(t))
135
+ sig.push(i);
136
+ });
137
+ const spots = bareSpots(tokens);
138
+ const replace = new Map();
139
+ const isSelf = (target) => target === item || (target.exportName === item.exportName && target.type === item.type);
140
+ for (let k = 0; k < sig.length; k++) {
141
+ const a = tokens[sig[k]];
142
+ const prev = tokens[sig[k - 1] ?? -1];
143
+ if (prev?.kind === "punct" && prev.text === ".")
144
+ continue;
145
+ // A regclass literal: nextval('app.seq'::regclass) or 'app.t'::regclass.
146
+ if (a.kind === "string" && tokens[sig[k + 1] ?? -1]?.text === "::" && tokens[sig[k + 2] ?? -1]?.text.toLowerCase() === "regclass") {
147
+ const held = regclassName(a) ?? "";
148
+ // An unqualified name is the object in `public`, as the server's own printing has it, else the unqualified object of that name.
149
+ const target = (!held.includes(".") ? byQualified.get(`public.${held}`) : undefined) ?? byQualified.get(held);
150
+ if (target && !isSelf(target)) {
151
+ const fn = tokens[sig[k - 2] ?? -1];
152
+ const inCall = prev?.text === "(" && fn?.kind === "ident" && /^(nextval|currval|setval)$/i.test(fn.text);
153
+ replace.set(sig[k], { to: inCall ? sig[k + 2] + 1 : sig[k] + 1, ref: target.exportName });
154
+ k += inCall ? 2 : 0;
155
+ continue;
156
+ }
157
+ }
158
+ const dot = tokens[sig[k + 1] ?? -1];
159
+ const b = tokens[sig[k + 2] ?? -1];
160
+ if (!isName(a) || dot?.kind !== "punct" || dot.text !== "." || !isName(b)) {
161
+ // An extension's `WITH SCHEMA app`, a grant's `ON SCHEMA app`, default privileges' `IN SCHEMA app`.
162
+ const bySchemaWord = item.type === POSTGRES_ENTITY_TYPES.extension || item.type === POSTGRES_ENTITY_TYPES.grant || item.type === POSTGRES_ENTITY_TYPES.defaultPrivileges;
163
+ if (bySchemaWord && isName(a) && prev?.kind === "ident" && prev.text.toUpperCase() === "SCHEMA") {
164
+ const s = schemaExport.get(identValue(a));
165
+ if (s)
166
+ replace.set(sig[k], { to: sig[k] + 1, ref: s });
167
+ continue;
168
+ }
169
+ // A bare name where an object is named: `REFERENCES users`, `ON users`, a column of type `status`.
170
+ const bare = isName(a) && (spots.has(sig[k]) || (prev?.kind === "ident" && RELATION_WORDS.has(prev.text.toUpperCase())));
171
+ const target = bare ? byQualified.get(identValue(a)) : undefined;
172
+ if (target && !isSelf(target))
173
+ replace.set(sig[k], { to: sig[k] + 1, ref: target.exportName });
174
+ continue;
175
+ }
176
+ const schema = identValue(a);
177
+ const name = identValue(b);
178
+ const self = schema === item.schema && name === item.name;
179
+ const target = byQualified.get(`${schema}.${name}`);
180
+ if (!self && target && !isSelf(target)) {
181
+ replace.set(sig[k], { to: sig[k + 2] + 1, ref: target.exportName });
182
+ k += 2;
183
+ }
184
+ else if (self && schemaExport.has(schema)) {
185
+ replace.set(sig[k], { to: sig[k] + 1, ref: schemaExport.get(schema) });
186
+ k += 2;
187
+ }
188
+ }
189
+ const parts = [""];
190
+ const refs = [];
191
+ for (let i = 0; i < tokens.length;) {
192
+ const r = replace.get(i);
193
+ if (r) {
194
+ refs.push(r.ref);
195
+ parts.push("");
196
+ i = r.to;
197
+ continue;
198
+ }
199
+ parts[parts.length - 1] += templateSafe(tokens[i].text);
200
+ i++;
201
+ }
202
+ return { parts, refs };
203
+ }
204
+ /** A template body as the text between the backquotes. */
205
+ export const bodyText = (body) => body.parts.map((p, i) => (i === 0 ? p : `\${${body.refs[i - 1]}}${p}`)).join("");
206
+ /**
207
+ * The template of each object in `items`, with the references to `targets`
208
+ * (the items themselves, and any object declared elsewhere that they may
209
+ * name) interpolated, and the items in the order a file declares them: each
210
+ * after what it references.
211
+ *
212
+ * A name is a reference where it is qualified (`app.users`), or bare where an
213
+ * object is named (`REFERENCES users`, `FROM users`, an index's table, a
214
+ * column's type) and an unqualified object has that name.
215
+ */
216
+ export function postgresTemplates(items, targets = items) {
217
+ const byQualified = new Map();
218
+ const schemaExport = new Map();
219
+ for (const it of targets) {
220
+ if (it.type === POSTGRES_ENTITY_TYPES.schema)
221
+ schemaExport.set(it.name, it.exportName);
222
+ // An index, a trigger, a cast or an event trigger is never referenced by name in another object's DDL.
223
+ else if (!UNREFERENCED.has(it.type)) {
224
+ byQualified.set(it.schema ? `${it.schema}.${it.name}` : it.name, it);
225
+ }
226
+ }
227
+ const bodies = new Map(items.map((it) => [it.exportName, templateBody(it, byQualified, schemaExport)]));
228
+ const order = [];
229
+ const done = new Set();
230
+ const visiting = new Set();
231
+ const byExport = new Map(items.map((it) => [it.exportName, it]));
232
+ const visit = (it) => {
233
+ if (done.has(it.exportName) || visiting.has(it.exportName))
234
+ return;
235
+ visiting.add(it.exportName);
236
+ for (const ref of bodies.get(it.exportName).refs) {
237
+ const dep = byExport.get(ref);
238
+ if (dep)
239
+ visit(dep);
240
+ }
241
+ visiting.delete(it.exportName);
242
+ done.add(it.exportName);
243
+ order.push(it);
244
+ };
245
+ for (const it of items)
246
+ visit(it);
247
+ return { order, bodies };
248
+ }
249
+ const indent = (body) => body
250
+ .split("\n")
251
+ .map((l, i) => (i === 0 || l.length === 0 ? l : ` ${l}`))
252
+ .join("\n");
253
+ const RELATIONS = new Set([POSTGRES_ENTITY_TYPES.table, POSTGRES_ENTITY_TYPES.view, POSTGRES_ENTITY_TYPES.materializedView]);
254
+ /** A table's or view's qualified name as its tag's type argument (#3751), so its columns are typed once `chant sql types` has run. */
255
+ const typeArgument = (it) => (RELATIONS.has(it.type) ? `<${JSON.stringify(relationKey(it))}>` : "");
256
+ export class PostgresGenerator {
257
+ constructor(header) {
258
+ this.header = header;
259
+ this.ownsLayout = true;
260
+ }
261
+ generate(ir) {
262
+ const items = ir.resources
263
+ .filter((r) => TAG[r.type])
264
+ .map((r) => ({
265
+ exportName: r.logicalId,
266
+ type: r.type,
267
+ schema: r.properties.schema,
268
+ name: String(r.properties.name),
269
+ ddl: String(r.properties.ddl),
270
+ }));
271
+ if (items.length === 0)
272
+ return [];
273
+ const { order, bodies } = postgresTemplates(items);
274
+ const tags = [...new Set(order.map((it) => TAG[it.type]))].sort();
275
+ const lines = [];
276
+ if (this.header)
277
+ lines.push(...this.header.split("\n").map((l) => `// ${l}`), "");
278
+ lines.push(`import { ${tags.join(", ")} } from "@intentius/chant-lexicon-sql/postgres";`, "");
279
+ for (const it of order)
280
+ lines.push(`export const ${it.exportName} = ${TAG[it.type]}${typeArgument(it)}\`\n ${indent(bodyText(bodies.get(it.exportName)))}\`;`, "");
281
+ return [{ path: "schema.ts", content: `${lines.join("\n").trimEnd()}\n` }];
282
+ }
283
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Postgres objects into the import IR: one resource per object, the
3
+ * statements that create it as the server prints them (`../live/catalog.ts`),
4
+ * and an export name.
5
+ */
6
+ import { POSTGRES_ENTITY_TYPES } from "../entity-types.js";
7
+ const JS_RESERVED = new Set("break case catch class const continue debugger default delete do else enum export extends false finally for function if import in instanceof new null return super switch this throw true try typeof var void while with yield let static implements interface package private protected public await".split(" "));
8
+ function camel(name) {
9
+ const words = name.split(/[^A-Za-z0-9]+/).filter(Boolean);
10
+ if (words.length === 0)
11
+ return "object";
12
+ const s = words.map((w, i) => (i === 0 ? w[0].toLowerCase() + w.slice(1) : w[0].toUpperCase() + w.slice(1))).join("");
13
+ return /^[0-9]/.test(s) ? `o${s}` : s;
14
+ }
15
+ /**
16
+ * Export names: an object's name in camel case, prefixed with its schema when
17
+ * two schemas hold the same name; a schema's name with `Schema` after it and
18
+ * an extension's with `Extension`, so `app` the schema and `app` a table do
19
+ * not collide.
20
+ */
21
+ export function exportNames(objects) {
22
+ const count = new Map();
23
+ for (const o of objects)
24
+ if (o.schema)
25
+ count.set(o.name, (count.get(o.name) ?? 0) + 1);
26
+ const used = new Set();
27
+ return objects.map((o) => {
28
+ let base = o.type === POSTGRES_ENTITY_TYPES.schema
29
+ ? `${camel(o.name)}Schema`
30
+ : o.type === POSTGRES_ENTITY_TYPES.extension
31
+ ? `${camel(o.name)}Extension`
32
+ : (count.get(o.name) ?? 0) > 1 && o.schema
33
+ ? camel(`${o.schema}_${o.name}`)
34
+ : camel(o.name);
35
+ if (JS_RESERVED.has(base))
36
+ base = `${base}Object`;
37
+ let name = base;
38
+ for (let i = 2; used.has(name); i++)
39
+ name = `${base}${i}`;
40
+ used.add(name);
41
+ return name;
42
+ });
43
+ }
44
+ /** The IR for a set of objects. */
45
+ export function objectsToIR(objects, warnings = []) {
46
+ const names = exportNames(objects);
47
+ const resources = objects.map((o, i) => ({
48
+ logicalId: names[i],
49
+ type: o.type,
50
+ properties: { ...(o.schema ? { schema: o.schema } : {}), name: o.name, ddl: o.ddl },
51
+ }));
52
+ return { resources, parameters: [], ...(warnings.length ? { warnings } : {}) };
53
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Live export: `chant import --from <env>` writes a Postgres server's schema as
3
+ * declarations, each object as the statements the catalog's own printers
4
+ * give for it (`../live/catalog.ts`), the canonical form.
5
+ *
6
+ * - `selector.type` is an entity type (`Postgres::Table`); `selector.name` is
7
+ * `name` or `schema.name`.
8
+ * - `owned` keeps the objects whose comment carries chant's ownership marker.
9
+ * - Another tool's objects (an ORM's revision table, and its sequence and
10
+ * indexes) are left out with a warning naming the tool: they are its, and
11
+ * a declaration of them would have chant fight it.
12
+ * - `verbatim` changes nothing yet: the printers already leave out what the
13
+ * server would add by itself.
14
+ * - Where the profile manages access (`sql.profiles.<env>.access`), the
15
+ * policies and each table's row-level security come with the objects, and
16
+ * the privileges on them and the default privileges are written as `grant`
17
+ * declarations: the GRANT and REVOKE statements that take a new object's
18
+ * privileges to what the server holds. Roles are the environment's and are
19
+ * not imported.
20
+ * - An object of a kind no declaration holds (an aggregate, a cast, a
21
+ * composite type, an event trigger, ...; `../live/undeclarable.ts`) fails
22
+ * the import, which names every one of them (#3911). `skipUndeclarable`
23
+ * (`--skip-undeclarable`) imports the rest and lists them as warnings. A
24
+ * selector or `owned` import asks for objects chant can hold, so it does
25
+ * not look.
26
+ */
27
+ import { bindPostgres } from "../live/bind.js";
28
+ import { markProviderOwned, readLiveSchema } from "../live/catalog.js";
29
+ import { objectsToIR } from "./ir.js";
30
+ import { isChantManaged } from "../../core/ownership.js";
31
+ import { importedAccess } from "../access/import.js";
32
+ import { readUndeclarable } from "../live/undeclarable.js";
33
+ import { describeUndeclarable, refuseUndeclarable } from "../../core/undeclarable.js";
34
+ import { isProviderOwned, providerData } from "../providers/index.js";
35
+ export async function exportResources(options) {
36
+ const { target, client } = await bindPostgres(options);
37
+ let live;
38
+ let access = [];
39
+ // A selector or an owned import asks only for what chant can hold.
40
+ const checked = !options.selector && !options.owned;
41
+ let undeclarable = { objects: [], unread: [] };
42
+ try {
43
+ if (checked)
44
+ undeclarable = await readUndeclarable(client, { schemas: target.schemas });
45
+ live = markProviderOwned(await readLiveSchema(client, { schemas: target.schemas, access: target.access === true }), target.provider);
46
+ // Where the profile manages access, the privileges on what is imported, and the default privileges, as grant declarations.
47
+ if (target.access === true && !options.selector)
48
+ access = await importedAccess(client, live.filter((o) => !o.foreign && !o.unsupported && (!options.owned || isChantManaged(o.comment))), target.schemas);
49
+ }
50
+ finally {
51
+ await client.end();
52
+ }
53
+ const warnings = undeclarable.unread.map((u) => `the server was not checked for objects of one kind chant cannot declare, so some may be left out: ${u}`);
54
+ // A managed provider's own (in its schemas, or owned by its roles) is the provider's, as for the objects read below.
55
+ const provider = target.provider;
56
+ const providers = (o) => provider !== undefined &&
57
+ ((o.schema !== undefined && isProviderOwned(provider, { kind: "schema", name: o.schema })) || (o.owner !== undefined && isProviderOwned(provider, { kind: "role", name: o.owner })));
58
+ for (const o of undeclarable.objects.filter(providers))
59
+ warnings.push(`${describeUndeclarable(o)} is kept by ${providerData(provider).label}; left out`);
60
+ refuseUndeclarable("postgres", undeclarable.objects.filter((o) => !providers(o)), options.skipUndeclarable, warnings);
61
+ const objects = [];
62
+ for (const o of live) {
63
+ if (options.owned && !isChantManaged(o.comment))
64
+ continue;
65
+ if (options.selector?.type && o.type !== options.selector.type)
66
+ continue;
67
+ const name = options.selector?.name;
68
+ if (name !== undefined && name !== o.name && name !== `${o.schema}.${o.name}`)
69
+ continue;
70
+ if (o.unsupported) {
71
+ // When checked, it is among the undeclarable objects above.
72
+ if (!checked)
73
+ warnings.push(`${o.schema ? `${o.schema}.` : ""}${o.name}${o.signature ?? ""} is left out: ${o.unsupported}`);
74
+ continue;
75
+ }
76
+ if (o.foreign) {
77
+ warnings.push(`${o.schema ? `${o.schema}.` : ""}${o.name} is kept by ${o.foreign}; left out, since declaring it would have chant change what that tool owns`);
78
+ continue;
79
+ }
80
+ objects.push({ type: o.type, ...(o.schema ? { schema: o.schema } : {}), name: o.name, ddl: o.statement });
81
+ }
82
+ return objectsToIR([...objects, ...access], warnings);
83
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Postgres key words and their categories at the pinned server, as
3
+ * `pg_get_keywords()` reports them: what decides whether a bare word can be a
4
+ * name. The parser refuses an unquoted reserved word (`R`) or type/function
5
+ * name word (`T`) where a column, table or constraint name goes, as Postgres's
6
+ * `ColId` does, and quoting reads the same table.
7
+ */
8
+ import { KEYWORDS as GENERATED_KEYWORDS } from "../generated/postgres.js";
9
+ /** Generated from `pg_get_keywords()` at the latest pinned major (`src/spec/postgres-catalog-18.snapshot.json`). */
10
+ const KEYWORDS = GENERATED_KEYWORDS;
11
+ /** A word's key word category, case-insensitively; undefined when it is not a key word. */
12
+ export function keywordCategory(word) {
13
+ const w = word.toLowerCase();
14
+ return Object.prototype.hasOwnProperty.call(KEYWORDS, w) ? KEYWORDS[w] : undefined;
15
+ }
16
+ /** Whether `word` is a key word of any category. */
17
+ export const isKeyword = (word) => keywordCategory(word) !== undefined;
18
+ /**
19
+ * Whether `name` must be double-quoted to read back as itself: anything but a
20
+ * lower-case identifier, and every key word that is not unreserved (a
21
+ * `C` word such as `bigint` is a name only where a column name goes).
22
+ */
23
+ export function needsQuotes(name) {
24
+ if (!/^[a-z_][a-z0-9_$]*$/.test(name))
25
+ return true;
26
+ const k = keywordCategory(name);
27
+ return k !== undefined && k !== "U";
28
+ }
29
+ /** A name as Postgres reads it back: bare when it can be, double-quoted otherwise. */
30
+ export function quoteIdent(name) {
31
+ return needsQuotes(name) ? `"${name.replace(/"/g, '""')}"` : name;
32
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Which Postgres server a chant environment reads: the config lookup and the
3
+ * binding, shared by observation, live export and the plan, so all of them
4
+ * read the same server.
5
+ */
6
+ import { connectPostgres, DEFAULT_LOCK_TIMEOUT_MS, PostgresQueryError, withCatalogLockTimeout } from "./client.js";
7
+ import { credentialSource, TokenSourceError } from "../../token-source.js";
8
+ export const isUnresolvedTarget = (v) => "reason" in v;
9
+ /** Whether a URL names a Postgres server. */
10
+ export const isPostgresUrl = (url) => /^postgres(ql)?:\/\//i.test(url);
11
+ /** A URL with any password in it taken out, for a message. */
12
+ export const redactUrl = (url) => url.replace(/^(postgres(?:ql)?:\/\/[^:/@]+):[^@]*@/i, "$1:***@");
13
+ /**
14
+ * The server an environment reads. Pure: `config` and `env` are passed in.
15
+ *
16
+ * 1. `sql.profiles.<environment>` with a `postgres://` URL. A profile naming a
17
+ * credential variable that is not set is `no-credentials`.
18
+ * 2. `POSTGRES_URL`, with `POSTGRES_USER` and `POSTGRES_PASSWORD` when set.
19
+ * 3. Otherwise `no-binding`.
20
+ */
21
+ export function resolvePostgresTarget(input) {
22
+ const env = input.env ?? process.env;
23
+ const profile = input.environment !== undefined ? input.config?.sql?.profiles?.[input.environment] : undefined;
24
+ const provider = (profile && isPostgresUrl(profile.url) ? profile.provider : undefined) ?? input.config?.sql?.provider;
25
+ const withProvider = (t) => (provider ? { ...t, provider } : t);
26
+ if (profile && isPostgresUrl(profile.url)) {
27
+ const source = `sql.profiles.${input.environment}`;
28
+ const endpoint = { url: profile.url };
29
+ if (profile.user) {
30
+ const value = env[profile.user.env];
31
+ if (value === undefined)
32
+ return { reason: "no-credentials", detail: `${source}.user names ${profile.user.env}, which is not set` };
33
+ endpoint.user = value;
34
+ }
35
+ const password = profile.password;
36
+ if (password && "env" in password) {
37
+ const value = env[password.env];
38
+ if (value === undefined)
39
+ return { reason: "no-credentials", detail: `${source}.password names ${password.env}, which is not set` };
40
+ endpoint.password = value;
41
+ }
42
+ else if (password) {
43
+ if (password.token === "rds-iam" && endpoint.user === undefined) {
44
+ return { reason: "no-credentials", detail: `${source}.password is an rds-iam token, which is minted for one user, and ${source}.user is not set` };
45
+ }
46
+ endpoint.token = credentialSource(password, { url: profile.url, ...(endpoint.user !== undefined ? { user: endpoint.user } : {}), env, source: `${source}.password` });
47
+ }
48
+ const timeouts = Object.fromEntries(["lockTimeoutMs", "statementTimeoutMs", "scanTimeoutMs"].filter((k) => profile[k] !== undefined).map((k) => [k, profile[k]]));
49
+ return withProvider({
50
+ endpoint,
51
+ source,
52
+ ...(profile.schemas ? { schemas: profile.schemas } : {}),
53
+ defaultSchema: profile.defaultSchema ?? "public",
54
+ ...(Object.keys(timeouts).length > 0 ? { timeouts } : {}),
55
+ ...(profile.access === true ? { access: true } : {}),
56
+ });
57
+ }
58
+ const url = env.POSTGRES_URL;
59
+ if (!url) {
60
+ const where = input.environment !== undefined ? `sql.profiles.${input.environment} names no postgres:// server and ` : "";
61
+ return { reason: "no-binding", detail: `${where}POSTGRES_URL is not set, so there is no Postgres server to read` };
62
+ }
63
+ return withProvider({
64
+ endpoint: {
65
+ url,
66
+ ...(env.POSTGRES_USER !== undefined ? { user: env.POSTGRES_USER } : {}),
67
+ ...(env.POSTGRES_PASSWORD !== undefined ? { password: env.POSTGRES_PASSWORD } : {}),
68
+ },
69
+ source: "env POSTGRES_URL",
70
+ defaultSchema: "public",
71
+ });
72
+ }
73
+ /** A target that could not be resolved. */
74
+ export class PostgresBindingError extends Error {
75
+ constructor(unresolved) {
76
+ super(unresolved.detail);
77
+ this.unresolved = unresolved;
78
+ this.name = "PostgresBindingError";
79
+ }
80
+ }
81
+ export async function loadSqlConfig(cwd) {
82
+ try {
83
+ const { loadChantConfig } = await import("@intentius/chant/config");
84
+ return (await loadChantConfig(cwd)).config;
85
+ }
86
+ catch {
87
+ // No project config: the environment variables still bind.
88
+ return undefined;
89
+ }
90
+ }
91
+ /** Resolve the environment's server. Throws {@link PostgresBindingError} when there is none. */
92
+ export async function resolveBoundTarget(options = {}) {
93
+ const config = options.config ?? (await loadSqlConfig(options.cwd ?? process.cwd()));
94
+ const target = resolvePostgresTarget({ environment: options.environment, config, env: options.env });
95
+ if (isUnresolvedTarget(target))
96
+ throw new PostgresBindingError(target);
97
+ return target;
98
+ }
99
+ /**
100
+ * Resolve the environment's server and connect to it. The caller ends the client.
101
+ *
102
+ * The session reads under the profile's `lockTimeoutMs` (the applier's 5000 ms
103
+ * otherwise): a catalog read blocked behind ACCESS EXCLUSIVE fails with the
104
+ * relation and the pid that holds it, instead of waiting for as long as the
105
+ * lock is held.
106
+ */
107
+ export async function bindPostgres(options = {}) {
108
+ const target = await resolveBoundTarget(options);
109
+ const raw = await (options.connect ?? connectPostgres)(target.endpoint);
110
+ try {
111
+ return { target, client: await withCatalogLockTimeout(raw, target.timeouts?.lockTimeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS) };
112
+ }
113
+ catch (err) {
114
+ await raw.end();
115
+ throw err;
116
+ }
117
+ }
118
+ /** SQLSTATEs that mean the server refused who we are: invalid_authorization_specification, invalid_password, insufficient_privilege. */
119
+ const CREDENTIAL_STATES = new Set(["28000", "28P01", "42501"]);
120
+ /** What a failed bind or read means, in the observation vocabulary. */
121
+ export function classifyPostgresFailure(err) {
122
+ if (err instanceof PostgresBindingError)
123
+ return { reason: err.unresolved.reason, detail: err.unresolved.detail };
124
+ if (err instanceof TokenSourceError)
125
+ return { reason: "no-credentials", detail: err.message };
126
+ if (err instanceof PostgresQueryError && err.code && CREDENTIAL_STATES.has(err.code)) {
127
+ return { reason: "no-credentials", detail: `${err.message} (the server refused the credentials, SQLSTATE ${err.code})` };
128
+ }
129
+ return { reason: "read-failed", detail: err instanceof Error ? err.message.split("\n")[0] : String(err) };
130
+ }