@quereus/quereus 4.8.0 → 4.10.0

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 (280) hide show
  1. package/dist/src/core/database.d.ts +6 -0
  2. package/dist/src/core/database.d.ts.map +1 -1
  3. package/dist/src/core/database.js +45 -5
  4. package/dist/src/core/database.js.map +1 -1
  5. package/dist/src/core/statement.d.ts.map +1 -1
  6. package/dist/src/core/statement.js +74 -15
  7. package/dist/src/core/statement.js.map +1 -1
  8. package/dist/src/func/builtins/aggregate.d.ts.map +1 -1
  9. package/dist/src/func/builtins/aggregate.js +88 -35
  10. package/dist/src/func/builtins/aggregate.js.map +1 -1
  11. package/dist/src/func/builtins/conversion.d.ts +5 -0
  12. package/dist/src/func/builtins/conversion.d.ts.map +1 -1
  13. package/dist/src/func/builtins/conversion.js +16 -2
  14. package/dist/src/func/builtins/conversion.js.map +1 -1
  15. package/dist/src/func/builtins/explain.d.ts.map +1 -1
  16. package/dist/src/func/builtins/explain.js +9 -2
  17. package/dist/src/func/builtins/explain.js.map +1 -1
  18. package/dist/src/func/builtins/index.d.ts.map +1 -1
  19. package/dist/src/func/builtins/index.js +5 -2
  20. package/dist/src/func/builtins/index.js.map +1 -1
  21. package/dist/src/func/builtins/scalar.d.ts.map +1 -1
  22. package/dist/src/func/builtins/scalar.js +4 -2
  23. package/dist/src/func/builtins/scalar.js.map +1 -1
  24. package/dist/src/func/builtins/string.d.ts +2 -0
  25. package/dist/src/func/builtins/string.d.ts.map +1 -1
  26. package/dist/src/func/builtins/string.js +60 -4
  27. package/dist/src/func/builtins/string.js.map +1 -1
  28. package/dist/src/func/registration.d.ts +6 -0
  29. package/dist/src/func/registration.d.ts.map +1 -1
  30. package/dist/src/func/registration.js +1 -0
  31. package/dist/src/func/registration.js.map +1 -1
  32. package/dist/src/index.d.ts +7 -3
  33. package/dist/src/index.d.ts.map +1 -1
  34. package/dist/src/index.js +21 -2
  35. package/dist/src/index.js.map +1 -1
  36. package/dist/src/planner/building/constraint-builder.js +1 -1
  37. package/dist/src/planner/building/constraint-builder.js.map +1 -1
  38. package/dist/src/planner/nodes/scalar.d.ts.map +1 -1
  39. package/dist/src/planner/nodes/scalar.js +13 -1
  40. package/dist/src/planner/nodes/scalar.js.map +1 -1
  41. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.d.ts.map +1 -1
  42. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js +121 -51
  43. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js.map +1 -1
  44. package/dist/src/planner/rules/shared/index-style-context.d.ts +14 -4
  45. package/dist/src/planner/rules/shared/index-style-context.d.ts.map +1 -1
  46. package/dist/src/planner/rules/shared/index-style-context.js +14 -4
  47. package/dist/src/planner/rules/shared/index-style-context.js.map +1 -1
  48. package/dist/src/runtime/emission-context.d.ts +14 -1
  49. package/dist/src/runtime/emission-context.d.ts.map +1 -1
  50. package/dist/src/runtime/emission-context.js +17 -5
  51. package/dist/src/runtime/emission-context.js.map +1 -1
  52. package/dist/src/runtime/emit/aggregate-setup.d.ts +44 -8
  53. package/dist/src/runtime/emit/aggregate-setup.d.ts.map +1 -1
  54. package/dist/src/runtime/emit/aggregate-setup.js +79 -15
  55. package/dist/src/runtime/emit/aggregate-setup.js.map +1 -1
  56. package/dist/src/runtime/emit/aggregate.d.ts.map +1 -1
  57. package/dist/src/runtime/emit/aggregate.js +21 -106
  58. package/dist/src/runtime/emit/aggregate.js.map +1 -1
  59. package/dist/src/runtime/emit/alter-table.d.ts.map +1 -1
  60. package/dist/src/runtime/emit/alter-table.js +212 -155
  61. package/dist/src/runtime/emit/alter-table.js.map +1 -1
  62. package/dist/src/runtime/emit/asof-scan.js +1 -1
  63. package/dist/src/runtime/emit/asof-scan.js.map +1 -1
  64. package/dist/src/runtime/emit/assertion-drop-guard.d.ts +26 -12
  65. package/dist/src/runtime/emit/assertion-drop-guard.d.ts.map +1 -1
  66. package/dist/src/runtime/emit/assertion-drop-guard.js +45 -26
  67. package/dist/src/runtime/emit/assertion-drop-guard.js.map +1 -1
  68. package/dist/src/runtime/emit/assertion-rename-helpers.d.ts +6 -6
  69. package/dist/src/runtime/emit/assertion-rename-helpers.d.ts.map +1 -1
  70. package/dist/src/runtime/emit/assertion-rename-helpers.js +18 -20
  71. package/dist/src/runtime/emit/assertion-rename-helpers.js.map +1 -1
  72. package/dist/src/runtime/emit/between.d.ts +2 -0
  73. package/dist/src/runtime/emit/between.d.ts.map +1 -1
  74. package/dist/src/runtime/emit/between.js +7 -8
  75. package/dist/src/runtime/emit/between.js.map +1 -1
  76. package/dist/src/runtime/emit/binary.d.ts +27 -5
  77. package/dist/src/runtime/emit/binary.d.ts.map +1 -1
  78. package/dist/src/runtime/emit/binary.js +278 -187
  79. package/dist/src/runtime/emit/binary.js.map +1 -1
  80. package/dist/src/runtime/emit/case.d.ts +34 -0
  81. package/dist/src/runtime/emit/case.d.ts.map +1 -1
  82. package/dist/src/runtime/emit/case.js +18 -11
  83. package/dist/src/runtime/emit/case.js.map +1 -1
  84. package/dist/src/runtime/emit/cast.d.ts +2 -0
  85. package/dist/src/runtime/emit/cast.d.ts.map +1 -1
  86. package/dist/src/runtime/emit/cast.js +7 -5
  87. package/dist/src/runtime/emit/cast.js.map +1 -1
  88. package/dist/src/runtime/emit/column-reference.d.ts +3 -1
  89. package/dist/src/runtime/emit/column-reference.d.ts.map +1 -1
  90. package/dist/src/runtime/emit/column-reference.js +6 -2
  91. package/dist/src/runtime/emit/column-reference.js.map +1 -1
  92. package/dist/src/runtime/emit/column-rename-cascade.d.ts +84 -0
  93. package/dist/src/runtime/emit/column-rename-cascade.d.ts.map +1 -0
  94. package/dist/src/runtime/emit/column-rename-cascade.js +134 -0
  95. package/dist/src/runtime/emit/column-rename-cascade.js.map +1 -0
  96. package/dist/src/runtime/emit/dml-executor.d.ts.map +1 -1
  97. package/dist/src/runtime/emit/dml-executor.js +21 -0
  98. package/dist/src/runtime/emit/dml-executor.js.map +1 -1
  99. package/dist/src/runtime/emit/drop-column-guards.d.ts +82 -53
  100. package/dist/src/runtime/emit/drop-column-guards.d.ts.map +1 -1
  101. package/dist/src/runtime/emit/drop-column-guards.js +155 -90
  102. package/dist/src/runtime/emit/drop-column-guards.js.map +1 -1
  103. package/dist/src/runtime/emit/drop-table.d.ts.map +1 -1
  104. package/dist/src/runtime/emit/drop-table.js +5 -0
  105. package/dist/src/runtime/emit/drop-table.js.map +1 -1
  106. package/dist/src/runtime/emit/drop-view.d.ts.map +1 -1
  107. package/dist/src/runtime/emit/drop-view.js +5 -0
  108. package/dist/src/runtime/emit/drop-view.js.map +1 -1
  109. package/dist/src/runtime/emit/expression-drop-guard.d.ts +57 -0
  110. package/dist/src/runtime/emit/expression-drop-guard.d.ts.map +1 -0
  111. package/dist/src/runtime/emit/expression-drop-guard.js +82 -0
  112. package/dist/src/runtime/emit/expression-drop-guard.js.map +1 -0
  113. package/dist/src/runtime/emit/filter.d.ts +1 -1
  114. package/dist/src/runtime/emit/filter.js +1 -1
  115. package/dist/src/runtime/emit/hash-aggregate.d.ts.map +1 -1
  116. package/dist/src/runtime/emit/hash-aggregate.js +18 -88
  117. package/dist/src/runtime/emit/hash-aggregate.js.map +1 -1
  118. package/dist/src/runtime/emit/literal.d.ts +14 -1
  119. package/dist/src/runtime/emit/literal.d.ts.map +1 -1
  120. package/dist/src/runtime/emit/literal.js +33 -7
  121. package/dist/src/runtime/emit/literal.js.map +1 -1
  122. package/dist/src/runtime/emit/materialized-view-helpers.d.ts +33 -10
  123. package/dist/src/runtime/emit/materialized-view-helpers.d.ts.map +1 -1
  124. package/dist/src/runtime/emit/materialized-view-helpers.js +158 -49
  125. package/dist/src/runtime/emit/materialized-view-helpers.js.map +1 -1
  126. package/dist/src/runtime/emit/materialized-view.d.ts.map +1 -1
  127. package/dist/src/runtime/emit/materialized-view.js +3 -0
  128. package/dist/src/runtime/emit/materialized-view.js.map +1 -1
  129. package/dist/src/runtime/emit/parameter.d.ts +3 -1
  130. package/dist/src/runtime/emit/parameter.d.ts.map +1 -1
  131. package/dist/src/runtime/emit/parameter.js +7 -2
  132. package/dist/src/runtime/emit/parameter.js.map +1 -1
  133. package/dist/src/runtime/emit/scalar-function.d.ts +29 -1
  134. package/dist/src/runtime/emit/scalar-function.d.ts.map +1 -1
  135. package/dist/src/runtime/emit/scalar-function.js +66 -16
  136. package/dist/src/runtime/emit/scalar-function.js.map +1 -1
  137. package/dist/src/runtime/emit/scalar-op.d.ts +63 -0
  138. package/dist/src/runtime/emit/scalar-op.d.ts.map +1 -0
  139. package/dist/src/runtime/emit/scalar-op.js +37 -0
  140. package/dist/src/runtime/emit/scalar-op.js.map +1 -0
  141. package/dist/src/runtime/emit/scan.d.ts.map +1 -1
  142. package/dist/src/runtime/emit/scan.js +19 -1
  143. package/dist/src/runtime/emit/scan.js.map +1 -1
  144. package/dist/src/runtime/emit/temporal-arithmetic.d.ts +18 -8
  145. package/dist/src/runtime/emit/temporal-arithmetic.d.ts.map +1 -1
  146. package/dist/src/runtime/emit/temporal-arithmetic.js +29 -311
  147. package/dist/src/runtime/emit/temporal-arithmetic.js.map +1 -1
  148. package/dist/src/runtime/emit/unary.d.ts +2 -0
  149. package/dist/src/runtime/emit/unary.d.ts.map +1 -1
  150. package/dist/src/runtime/emit/unary.js +125 -53
  151. package/dist/src/runtime/emit/unary.js.map +1 -1
  152. package/dist/src/runtime/emitters.d.ts +22 -0
  153. package/dist/src/runtime/emitters.d.ts.map +1 -1
  154. package/dist/src/runtime/emitters.js +29 -0
  155. package/dist/src/runtime/emitters.js.map +1 -1
  156. package/dist/src/runtime/scalar-fusion.d.ts +47 -0
  157. package/dist/src/runtime/scalar-fusion.d.ts.map +1 -0
  158. package/dist/src/runtime/scalar-fusion.js +275 -0
  159. package/dist/src/runtime/scalar-fusion.js.map +1 -0
  160. package/dist/src/runtime/strict-flags.d.ts +9 -0
  161. package/dist/src/runtime/strict-flags.d.ts.map +1 -1
  162. package/dist/src/runtime/strict-flags.js +9 -0
  163. package/dist/src/runtime/strict-flags.js.map +1 -1
  164. package/dist/src/runtime/strict-fork.js +2 -2
  165. package/dist/src/runtime/strict-fork.js.map +1 -1
  166. package/dist/src/runtime/strict-representation.d.ts +78 -0
  167. package/dist/src/runtime/strict-representation.d.ts.map +1 -0
  168. package/dist/src/runtime/strict-representation.js +159 -0
  169. package/dist/src/runtime/strict-representation.js.map +1 -0
  170. package/dist/src/schema/assertion.d.ts +58 -0
  171. package/dist/src/schema/assertion.d.ts.map +1 -1
  172. package/dist/src/schema/assertion.js +78 -0
  173. package/dist/src/schema/assertion.js.map +1 -1
  174. package/dist/src/schema/catalog-persistability.d.ts +11 -3
  175. package/dist/src/schema/catalog-persistability.d.ts.map +1 -1
  176. package/dist/src/schema/catalog-persistability.js +48 -29
  177. package/dist/src/schema/catalog-persistability.js.map +1 -1
  178. package/dist/src/schema/catalog.d.ts +22 -0
  179. package/dist/src/schema/catalog.d.ts.map +1 -1
  180. package/dist/src/schema/catalog.js +4 -1
  181. package/dist/src/schema/catalog.js.map +1 -1
  182. package/dist/src/schema/column-republication.d.ts +126 -0
  183. package/dist/src/schema/column-republication.d.ts.map +1 -0
  184. package/dist/src/schema/column-republication.js +121 -0
  185. package/dist/src/schema/column-republication.js.map +1 -0
  186. package/dist/src/schema/column-source-resolver.d.ts +29 -6
  187. package/dist/src/schema/column-source-resolver.d.ts.map +1 -1
  188. package/dist/src/schema/column-source-resolver.js +78 -9
  189. package/dist/src/schema/column-source-resolver.js.map +1 -1
  190. package/dist/src/schema/expression-dependents.d.ts +160 -0
  191. package/dist/src/schema/expression-dependents.d.ts.map +1 -0
  192. package/dist/src/schema/expression-dependents.js +228 -0
  193. package/dist/src/schema/expression-dependents.js.map +1 -0
  194. package/dist/src/schema/function.d.ts +15 -0
  195. package/dist/src/schema/function.d.ts.map +1 -1
  196. package/dist/src/schema/function.js.map +1 -1
  197. package/dist/src/schema/object-dependency-closure.d.ts +127 -0
  198. package/dist/src/schema/object-dependency-closure.d.ts.map +1 -0
  199. package/dist/src/schema/object-dependency-closure.js +117 -0
  200. package/dist/src/schema/object-dependency-closure.js.map +1 -0
  201. package/dist/src/schema/object-ref-resolver.d.ts +83 -0
  202. package/dist/src/schema/object-ref-resolver.d.ts.map +1 -0
  203. package/dist/src/schema/object-ref-resolver.js +135 -0
  204. package/dist/src/schema/object-ref-resolver.js.map +1 -0
  205. package/dist/src/schema/rename/column-rename.d.ts +228 -0
  206. package/dist/src/schema/rename/column-rename.d.ts.map +1 -0
  207. package/dist/src/schema/rename/column-rename.js +1225 -0
  208. package/dist/src/schema/rename/column-rename.js.map +1 -0
  209. package/dist/src/schema/rename/self-qualifier-strip.d.ts +29 -0
  210. package/dist/src/schema/rename/self-qualifier-strip.d.ts.map +1 -0
  211. package/dist/src/schema/rename/self-qualifier-strip.js +267 -0
  212. package/dist/src/schema/rename/self-qualifier-strip.js.map +1 -0
  213. package/dist/src/schema/rename/shared.d.ts +108 -0
  214. package/dist/src/schema/rename/shared.d.ts.map +1 -0
  215. package/dist/src/schema/rename/shared.js +49 -0
  216. package/dist/src/schema/rename/shared.js.map +1 -0
  217. package/dist/src/schema/rename/table-rename.d.ts +263 -0
  218. package/dist/src/schema/rename/table-rename.d.ts.map +1 -0
  219. package/dist/src/schema/rename/table-rename.js +860 -0
  220. package/dist/src/schema/rename/table-rename.js.map +1 -0
  221. package/dist/src/schema/rename-rewriter.d.ts +18 -278
  222. package/dist/src/schema/rename-rewriter.d.ts.map +1 -1
  223. package/dist/src/schema/rename-rewriter.js +17 -1506
  224. package/dist/src/schema/rename-rewriter.js.map +1 -1
  225. package/dist/src/schema/schema-differ.d.ts.map +1 -1
  226. package/dist/src/schema/schema-differ.js +228 -21
  227. package/dist/src/schema/schema-differ.js.map +1 -1
  228. package/dist/src/types/builtin-types.d.ts.map +1 -1
  229. package/dist/src/types/builtin-types.js +29 -36
  230. package/dist/src/types/builtin-types.js.map +1 -1
  231. package/dist/src/types/cast-semantics.d.ts +7 -5
  232. package/dist/src/types/cast-semantics.d.ts.map +1 -1
  233. package/dist/src/types/cast-semantics.js +20 -9
  234. package/dist/src/types/cast-semantics.js.map +1 -1
  235. package/dist/src/types/index.d.ts +1 -1
  236. package/dist/src/types/index.d.ts.map +1 -1
  237. package/dist/src/types/index.js +1 -1
  238. package/dist/src/types/index.js.map +1 -1
  239. package/dist/src/types/registry.d.ts.map +1 -1
  240. package/dist/src/types/registry.js +2 -1
  241. package/dist/src/types/registry.js.map +1 -1
  242. package/dist/src/types/temporal-ops.d.ts +119 -0
  243. package/dist/src/types/temporal-ops.d.ts.map +1 -0
  244. package/dist/src/types/temporal-ops.js +348 -0
  245. package/dist/src/types/temporal-ops.js.map +1 -0
  246. package/dist/src/types/temporal-types.d.ts +11 -0
  247. package/dist/src/types/temporal-types.d.ts.map +1 -1
  248. package/dist/src/types/temporal-types.js +87 -0
  249. package/dist/src/types/temporal-types.js.map +1 -1
  250. package/dist/src/util/affinity.d.ts +2 -1
  251. package/dist/src/util/affinity.d.ts.map +1 -1
  252. package/dist/src/util/affinity.js +16 -5
  253. package/dist/src/util/affinity.js.map +1 -1
  254. package/dist/src/util/ast-spine-clone.d.ts +2 -2
  255. package/dist/src/util/ast-spine-clone.js +2 -2
  256. package/dist/src/util/coercion.d.ts +22 -7
  257. package/dist/src/util/coercion.d.ts.map +1 -1
  258. package/dist/src/util/coercion.js +28 -15
  259. package/dist/src/util/coercion.js.map +1 -1
  260. package/dist/src/util/comparison.d.ts +25 -0
  261. package/dist/src/util/comparison.d.ts.map +1 -1
  262. package/dist/src/util/comparison.js +25 -0
  263. package/dist/src/util/comparison.js.map +1 -1
  264. package/dist/src/util/key-tuple-codec.d.ts +12 -3
  265. package/dist/src/util/key-tuple-codec.d.ts.map +1 -1
  266. package/dist/src/util/key-tuple-codec.js.map +1 -1
  267. package/dist/src/util/numeric-canonical.d.ts +26 -0
  268. package/dist/src/util/numeric-canonical.d.ts.map +1 -0
  269. package/dist/src/util/numeric-canonical.js +66 -0
  270. package/dist/src/util/numeric-canonical.js.map +1 -0
  271. package/dist/src/util/value-text.d.ts +48 -0
  272. package/dist/src/util/value-text.d.ts.map +1 -0
  273. package/dist/src/util/value-text.js +35 -0
  274. package/dist/src/util/value-text.js.map +1 -0
  275. package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
  276. package/dist/src/vtab/memory/layer/manager.js +14 -5
  277. package/dist/src/vtab/memory/layer/manager.js.map +1 -1
  278. package/dist/src/vtab/module.d.ts +25 -0
  279. package/dist/src/vtab/module.d.ts.map +1 -1
  280. package/package.json +4 -3
@@ -1,1508 +1,19 @@
1
- import { spineCloneAst } from '../util/ast-spine-clone.js';
2
- const eq = (a, b) => (a ?? '').toLowerCase() === (b ?? '').toLowerCase();
3
- const schemaMatches = (nodeSchema, defaultSchema) => nodeSchema === undefined || eq(nodeSchema, defaultSchema);
4
- export function renameTableInAst(node, oldName, newName, defaultSchemaName) {
5
- if (!node)
6
- return false;
7
- const ctx = { changed: false };
8
- visitTableRename(node, oldName, newName, defaultSchemaName, ctx);
9
- return ctx.changed;
10
- }
11
1
  /**
12
- * Whether `node` refers to the table/view `name` in schema `defaultSchemaName` —
13
- * read-only, nothing is mutated.
14
- *
15
- * Deliberately the SAME traversal {@link renameTableInAst} uses, run with
16
- * `dryRun`, so "refers to" can never drift from "would have been rewritten by a
17
- * rename". That equivalence is the whole point: the DROP guard refuses exactly
18
- * the cases `ALTER TABLE … RENAME` would have followed. A second traversal — or
19
- * a `renameTableInAst(clone, name, name, …)` trick, which would silently
20
- * re-case identifiers — would both break it.
21
- *
22
- * `name` is passed as both old and new name; under `dryRun` the new name is
23
- * never read.
24
- *
25
- * The column-level probes ({@link columnReferencedInAst},
26
- * {@link columnReferencedInCheckExpression}) reach the same equivalence a
27
- * different way — a real rename, to a sentinel name, over a throwaway
28
- * {@link spineCloneAst} copy — because the column walker has ~8 mutation points
29
- * and a CTE-re-exposure branch that reads the new name, so threading `dryRun`
30
- * through it is the riskier of the two. Neither flaw of the identity-rename
31
- * trick warned against above applies there: the mutated node is a discarded
32
- * clone (nothing user-visible is re-cased), and the target is a fresh name no
33
- * column can hold, so `newCol !== oldCol` and the re-exposure branch answers
34
- * exactly as it would for a real rename to an unused name.
35
- */
36
- export function tableReferencedInAst(node, name, defaultSchemaName) {
37
- if (!node)
38
- return false;
39
- const ctx = { changed: false, dryRun: true };
40
- visitTableRename(node, name, name, defaultSchemaName, ctx);
41
- return ctx.changed;
42
- }
43
- function visitTableRename(node, oldName, newName, defaultSchemaName, ctx) {
44
- if (!node)
45
- return;
46
- // A dry run only answers yes/no — stop as soon as the answer is known.
47
- if (ctx.dryRun && ctx.changed)
48
- return;
49
- switch (node.type) {
50
- case 'select': {
51
- const stmt = node;
52
- stmt.withClause?.ctes.forEach(cte => visitTableRename(cte.query, oldName, newName, defaultSchemaName, ctx));
53
- (stmt.columns ?? []).forEach(c => {
54
- if (c.type === 'column') {
55
- visitTableRename(c.expr, oldName, newName, defaultSchemaName, ctx);
56
- // A `with inverse` assignment expr can embed a subquery naming any
57
- // table; the assignment's target names a base COLUMN, untouched by a
58
- // table rename (same as the `with defaults` clause below).
59
- (c.inverse ?? []).forEach(a => visitTableRename(a.expr, oldName, newName, defaultSchemaName, ctx));
60
- }
61
- });
62
- // `with defaults` clause: each entry's `expr` (an inserted-row default) can
63
- // embed a subquery naming any table; the entry's `column` names a base
64
- // COLUMN, untouched by a table rename.
65
- (stmt.defaults ?? []).forEach(d => visitTableRename(d.expr, oldName, newName, defaultSchemaName, ctx));
66
- (stmt.from ?? []).forEach(f => visitTableRename(f, oldName, newName, defaultSchemaName, ctx));
67
- visitTableRename(stmt.where, oldName, newName, defaultSchemaName, ctx);
68
- (stmt.groupBy ?? []).forEach(g => visitTableRename(g, oldName, newName, defaultSchemaName, ctx));
69
- visitTableRename(stmt.having, oldName, newName, defaultSchemaName, ctx);
70
- (stmt.orderBy ?? []).forEach(o => visitTableRename(o.expr, oldName, newName, defaultSchemaName, ctx));
71
- visitTableRename(stmt.limit, oldName, newName, defaultSchemaName, ctx);
72
- visitTableRename(stmt.offset, oldName, newName, defaultSchemaName, ctx);
73
- visitTableRename(stmt.union, oldName, newName, defaultSchemaName, ctx);
74
- if (stmt.compound)
75
- visitTableRename(stmt.compound.select, oldName, newName, defaultSchemaName, ctx);
76
- break;
77
- }
78
- case 'insert': {
79
- const stmt = node;
80
- stmt.withClause?.ctes.forEach(cte => visitTableRename(cte.query, oldName, newName, defaultSchemaName, ctx));
81
- rewriteIdentifierIfTable(stmt.table, oldName, newName, defaultSchemaName, ctx);
82
- visitTableRename(stmt.source, oldName, newName, defaultSchemaName, ctx);
83
- (stmt.upsertClauses ?? []).forEach(uc => {
84
- (uc.assignments ?? []).forEach(a => visitTableRename(a.value, oldName, newName, defaultSchemaName, ctx));
85
- visitTableRename(uc.where, oldName, newName, defaultSchemaName, ctx);
86
- });
87
- (stmt.returning ?? []).forEach(r => {
88
- if (r.type === 'column')
89
- visitTableRename(r.expr, oldName, newName, defaultSchemaName, ctx);
90
- });
91
- (stmt.contextValues ?? []).forEach(cv => visitTableRename(cv.value, oldName, newName, defaultSchemaName, ctx));
92
- break;
93
- }
94
- case 'update': {
95
- const stmt = node;
96
- stmt.withClause?.ctes.forEach(cte => visitTableRename(cte.query, oldName, newName, defaultSchemaName, ctx));
97
- rewriteIdentifierIfTable(stmt.table, oldName, newName, defaultSchemaName, ctx);
98
- stmt.assignments.forEach(a => visitTableRename(a.value, oldName, newName, defaultSchemaName, ctx));
99
- visitTableRename(stmt.where, oldName, newName, defaultSchemaName, ctx);
100
- (stmt.returning ?? []).forEach(r => {
101
- if (r.type === 'column')
102
- visitTableRename(r.expr, oldName, newName, defaultSchemaName, ctx);
103
- });
104
- (stmt.contextValues ?? []).forEach(cv => visitTableRename(cv.value, oldName, newName, defaultSchemaName, ctx));
105
- break;
106
- }
107
- case 'delete': {
108
- const stmt = node;
109
- stmt.withClause?.ctes.forEach(cte => visitTableRename(cte.query, oldName, newName, defaultSchemaName, ctx));
110
- rewriteIdentifierIfTable(stmt.table, oldName, newName, defaultSchemaName, ctx);
111
- visitTableRename(stmt.where, oldName, newName, defaultSchemaName, ctx);
112
- (stmt.returning ?? []).forEach(r => {
113
- if (r.type === 'column')
114
- visitTableRename(r.expr, oldName, newName, defaultSchemaName, ctx);
115
- });
116
- (stmt.contextValues ?? []).forEach(cv => visitTableRename(cv.value, oldName, newName, defaultSchemaName, ctx));
117
- break;
118
- }
119
- case 'values': {
120
- const stmt = node;
121
- stmt.values.forEach(row => row.forEach(v => visitTableRename(v, oldName, newName, defaultSchemaName, ctx)));
122
- break;
123
- }
124
- case 'table': {
125
- const ts = node;
126
- if (eq(ts.table.name, oldName) && schemaMatches(ts.table.schema, defaultSchemaName)) {
127
- if (!ctx.dryRun)
128
- ts.table.name = newName;
129
- ctx.changed = true;
130
- }
131
- break;
132
- }
133
- case 'join': {
134
- const join = node;
135
- visitTableRename(join.left, oldName, newName, defaultSchemaName, ctx);
136
- visitTableRename(join.right, oldName, newName, defaultSchemaName, ctx);
137
- visitTableRename(join.condition, oldName, newName, defaultSchemaName, ctx);
138
- break;
139
- }
140
- case 'functionSource': {
141
- const fs = node;
142
- fs.args.forEach(a => visitTableRename(a, oldName, newName, defaultSchemaName, ctx));
143
- break;
144
- }
145
- case 'subquerySource': {
146
- const ss = node;
147
- visitTableRename(ss.subquery, oldName, newName, defaultSchemaName, ctx);
148
- break;
149
- }
150
- case 'binary': {
151
- const e = node;
152
- visitTableRename(e.left, oldName, newName, defaultSchemaName, ctx);
153
- visitTableRename(e.right, oldName, newName, defaultSchemaName, ctx);
154
- break;
155
- }
156
- case 'unary':
157
- case 'cast':
158
- case 'collate': {
159
- visitTableRename(node.expr, oldName, newName, defaultSchemaName, ctx);
160
- break;
161
- }
162
- case 'function': {
163
- node.args.forEach(a => visitTableRename(a, oldName, newName, defaultSchemaName, ctx));
164
- break;
165
- }
166
- case 'subquery': {
167
- visitTableRename(node.query, oldName, newName, defaultSchemaName, ctx);
168
- break;
169
- }
170
- case 'windowFunction': {
171
- const wf = node;
172
- visitTableRename(wf.function, oldName, newName, defaultSchemaName, ctx);
173
- visitTableRename(wf.window, oldName, newName, defaultSchemaName, ctx);
174
- break;
175
- }
176
- case 'windowDefinition': {
177
- const wd = node;
178
- (wd.partitionBy ?? []).forEach(p => visitTableRename(p, oldName, newName, defaultSchemaName, ctx));
179
- (wd.orderBy ?? []).forEach(o => visitTableRename(o.expr, oldName, newName, defaultSchemaName, ctx));
180
- break;
181
- }
182
- case 'case': {
183
- const ce = node;
184
- visitTableRename(ce.baseExpr, oldName, newName, defaultSchemaName, ctx);
185
- ce.whenThenClauses.forEach(wt => {
186
- visitTableRename(wt.when, oldName, newName, defaultSchemaName, ctx);
187
- visitTableRename(wt.then, oldName, newName, defaultSchemaName, ctx);
188
- });
189
- visitTableRename(ce.elseExpr, oldName, newName, defaultSchemaName, ctx);
190
- break;
191
- }
192
- case 'in': {
193
- const ie = node;
194
- visitTableRename(ie.expr, oldName, newName, defaultSchemaName, ctx);
195
- (ie.values ?? []).forEach(v => visitTableRename(v, oldName, newName, defaultSchemaName, ctx));
196
- visitTableRename(ie.subquery, oldName, newName, defaultSchemaName, ctx);
197
- break;
198
- }
199
- case 'exists': {
200
- visitTableRename(node.subquery, oldName, newName, defaultSchemaName, ctx);
201
- break;
202
- }
203
- case 'between': {
204
- const be = node;
205
- visitTableRename(be.expr, oldName, newName, defaultSchemaName, ctx);
206
- visitTableRename(be.lower, oldName, newName, defaultSchemaName, ctx);
207
- visitTableRename(be.upper, oldName, newName, defaultSchemaName, ctx);
208
- break;
209
- }
210
- case 'column': {
211
- const col = node;
212
- if (col.table && eq(col.table, oldName) && schemaMatches(col.schema, defaultSchemaName)) {
213
- if (!ctx.dryRun)
214
- col.table = newName;
215
- ctx.changed = true;
216
- }
217
- break;
218
- }
219
- // Leaf nodes / DDL — nothing to recurse into for our purposes.
220
- default:
221
- break;
222
- }
223
- }
224
- /**
225
- * Apply an in-place expression rewrite across a schema-object collection,
226
- * skipping items whose expression is absent. Returns whether any item changed.
227
- *
228
- * Backs the `rename{Column,Table}In{IndexPredicates,CheckConstraints,ColumnExpressions}`
229
- * entry points below, which differ only in which field they pluck and which walker they
230
- * run.
231
- */
232
- function rewriteEach(items, pick, rewrite) {
233
- let changed = false;
234
- for (const item of items ?? []) {
235
- const expr = pick(item);
236
- if (expr && rewrite(expr))
237
- changed = true;
238
- }
239
- return changed;
240
- }
241
- /**
242
- * Rewrite the renamed table inside every partial-index predicate of `indexes`,
243
- * in place. A predicate may carry a table-qualified self-reference
244
- * (`create index ix on t (b) where t.b > 0`), which a table rename must follow.
245
- *
246
- * Sharing and idempotence work exactly as in {@link renameColumnInIndexPredicates}:
247
- * the predicate `Expression` is shared by reference with the catalog's
248
- * `TableSchema` and with a unique partial index's `derivedFromIndex` UNIQUE
249
- * constraint, so one in-place rewrite covers all of them, and a second call with
250
- * the same pair finds nothing naming `oldName` and returns false.
251
- */
252
- export function renameTableInIndexPredicates(indexes, oldName, newName, defaultSchemaName) {
253
- return rewriteEach(indexes, idx => idx.predicate, expr => renameTableInAst(expr, oldName, newName, defaultSchemaName));
254
- }
255
- /**
256
- * Rewrite the renamed table inside every CHECK constraint expression of `checks`,
257
- * in place. A CHECK may carry a table-qualified self-reference (`check (t.b > 0)`)
258
- * exactly as a partial-index predicate may, and a table rename must follow it.
259
- *
260
- * Same sharing and idempotence story as {@link renameTableInIndexPredicates}: the
261
- * `expr` is the very AST the catalog's `TableSchema` holds, so one rewrite covers
262
- * every holder and a second call with the same pair is a no-op.
263
- */
264
- export function renameTableInCheckConstraints(checks, oldName, newName, defaultSchemaName) {
265
- return rewriteEach(checks, cc => cc.expr, expr => renameTableInAst(expr, oldName, newName, defaultSchemaName));
266
- }
267
- /**
268
- * Rewrite the renamed table inside the two expressions a `ColumnSchema` can carry —
269
- * a column DEFAULT (`w integer default ((select min(v) from u))`, however authored:
270
- * inline, or via `ALTER TABLE … ALTER COLUMN … SET DEFAULT`, which writes the same
271
- * field) and a generated column's body (`g integer generated always as ((select
272
- * min(u.v) from u) + id)`) — in place.
273
- *
274
- * Unlike the column-rename counterpart ({@link renameColumnInColumnExpressions}) there
275
- * is no seeded/unseeded split: {@link renameTableInAst} resolves nothing against an
276
- * implicit owning table, so the SAME entry point is correct for the renamed table's own
277
- * columns (a self-referencing default, `default ((select count(*) from t))`) and for
278
- * every other table's.
279
- *
280
- * Same sharing and idempotence story as {@link renameTableInCheckConstraints}: the
281
- * `Expression` is the very AST the catalog's `TableSchema` holds, so one in-place
282
- * rewrite reaches every holder and a second call with the same pair finds nothing
283
- * naming `oldName` and returns false.
284
- *
285
- * The parameter is structurally typed rather than `ColumnSchema[]` so this module stays
286
- * free of catalog imports; `ColumnSchema` satisfies it.
287
- */
288
- export function renameTableInColumnExpressions(columns, oldName, newName, defaultSchemaName) {
289
- const rewrite = (expr) => renameTableInAst(expr, oldName, newName, defaultSchemaName);
290
- // Both walks always run — `||` on the results, not short-circuited between them.
291
- const defaultsChanged = rewriteEach(columns, c => c.defaultValue ?? undefined, rewrite);
292
- const generatedChanged = rewriteEach(columns, c => c.generatedExpr, rewrite);
293
- return defaultsChanged || generatedChanged;
294
- }
295
- function rewriteIdentifierIfTable(id, oldName, newName, defaultSchemaName, ctx) {
296
- if (!id)
297
- return;
298
- if (eq(id.name, oldName) && schemaMatches(id.schema, defaultSchemaName)) {
299
- if (!ctx.dryRun)
300
- id.name = newName;
301
- ctx.changed = true;
302
- }
303
- }
304
- // ──────────────────────────────────────────────────────────────────────
305
- // Column rename
306
- // ──────────────────────────────────────────────────────────────────────
307
- /**
308
- * Rewrite column references inside a full statement/expression AST, resolving
309
- * unqualified refs against the FROM scopes the walk descends (no implicit seed,
310
- * unlike {@link renameColumnInCheckExpression}). The walk descends a select
311
- * body's trailing `with defaults (…)` clause ({@link AST.SelectStmt.defaults}),
312
- * whose entry exprs evaluate in the body's FROM scope.
313
- *
314
- * When `resolveColumnInSource` is supplied, the scope walk consults it at each
315
- * inner FROM frame so an unqualified ref that legitimately binds to a like-named
316
- * column on a subquery's own FROM source is NOT false-captured by an enclosing
317
- * binding (e.g. a `with defaults` expr `cap + (select max(cap) from lim)` under
318
- * a `t.cap` rename must leave the inner `lim.cap` untouched). The same callback
319
- * infrastructure backs {@link renameColumnInCheckExpression}; both the forward
320
- * propagation (live schema lookup) and the differ's inverse reconcile
321
- * (declared-side resolver) pass it so the two stay in parity.
322
- */
323
- export function renameColumnInAst(node, tableName, oldColName, newColName, defaultSchemaName, resolveColumnInSource) {
324
- if (!node)
325
- return false;
326
- const state = {
327
- tableName: tableName.toLowerCase(),
328
- oldCol: oldColName.toLowerCase(),
329
- newCol: newColName,
330
- defaultSchema: defaultSchemaName.toLowerCase(),
331
- scopeStack: [],
332
- changed: false,
333
- resolveColumnInSource,
334
- matchRowImageQualifier: false,
335
- };
336
- visitColumnRename(node, state);
337
- return state.changed;
338
- }
339
- /**
340
- * Rewrite a column reference inside a CHECK expression. Unlike
341
- * `renameColumnInAst`, this entry point seeds the scope stack with an
342
- * implicit unaliased binding to `tableName` so top-level unqualified
343
- * `ColumnExpr` nodes resolve to the owning table. CHECK expressions
344
- * cannot reference other tables at top level, so the implicit binding
345
- * is safe there.
346
- *
347
- * When `resolveColumnInSource` is supplied, the scope walk consults it at
348
- * each inner FROM frame: if any real-table source in that frame exposes
349
- * `oldColName`, the unqualified ref binds inside the subquery and the
350
- * walk stops before reaching the seed. This stops the rewriter from
351
- * false-positively rewriting an inner unqualified ref that legitimately
352
- * binds to a like-named column on the subquery's FROM (e.g.
353
- * `check ((select min(v) from u) > 0)` when `u` also has a `v` column).
354
- *
355
- * Limitation: aliased subquery / function-source / CTE-projection inner
356
- * sources are not asked (the rewriter would need recursive column-set
357
- * inference on their bodies). `renameColumnInAst` shares the same callback
358
- * (passed by the view-body callers) and the same limitation.
359
- *
360
- * The seed also makes this walk the owner of the `new.` / `old.` **row-image**
361
- * namespace: a CHECK may name the row being written explicitly
362
- * (`check (new.a > 0)`, `check on delete (old.a > 0)` — `docs/sql-ddl.md`
363
- * § CHECK Constraints), and those qualifiers name the owning table's row, not
364
- * anything a FROM clause binds. So the qualified-ref case matches them too —
365
- * but only here, never through `renameColumnInAst`, where a `new.` ref belongs
366
- * to some other table's row image.
367
- *
368
- * That match is scope-**aware**, not depth-blind, because `new` and `old` are
369
- * not reserved words in this parser: `create table "new" (…)` is legal, so a
370
- * CHECK may legitimately contain `(select max("new".a) from "new")`. The match
371
- * therefore requires that no FROM/WITH frame above the seed rebind the
372
- * qualifier — see `matchesRowImage`.
373
- */
374
- export function renameColumnInCheckExpression(expr, tableName, oldColName, newColName, defaultSchemaName, resolveColumnInSource) {
375
- if (!expr)
376
- return false;
377
- const state = {
378
- tableName: tableName.toLowerCase(),
379
- oldCol: oldColName.toLowerCase(),
380
- newCol: newColName,
381
- defaultSchema: defaultSchemaName.toLowerCase(),
382
- scopeStack: [],
383
- changed: false,
384
- resolveColumnInSource,
385
- matchRowImageQualifier: true,
386
- };
387
- const frame = emptyFrame();
388
- frame.unaliased.add(state.tableName);
389
- state.scopeStack.push(frame);
390
- try {
391
- visitColumnRename(expr, state);
392
- }
393
- finally {
394
- state.scopeStack.pop();
395
- }
396
- return state.changed;
397
- }
398
- /**
399
- * Sentinel rename target for the two read-only column probes below. No user
400
- * column can hold this name, and the probe rewrites *to* it, so it can never
401
- * collide with anything the walk is looking for.
402
- */
403
- const PROBE_COLUMN_NAME = '__quereus_column_probe__';
404
- /**
405
- * Whether `node` refers to `tableName`.`columnName` — read-only with respect to
406
- * the caller's AST; a throwaway {@link spineCloneAst} copy is what gets
407
- * rewritten.
408
- *
409
- * Same equivalence {@link tableReferencedInAst} establishes for the table verb,
410
- * and for the same reason: "refers to" must mean "would have been rewritten by
411
- * `ALTER TABLE … RENAME COLUMN`", or a DROP guard built on it refuses a
412
- * different set of cases than the rename follows. See that function's comment
413
- * for why the column verb reaches it by clone+sentinel rather than by `dryRun`.
414
- *
415
- * `resolveColumnInSource` is **not optional in practice**: without it the walk
416
- * has no way to tell that an unqualified ref inside a subquery binds to a
417
- * like-named column on the subquery's own FROM source, and a caller asking
418
- * "does this body name `t.v`?" gets `true` for a body whose only `v` is
419
- * another table's. Every caller should pass the catalog-backed resolver
420
- * (`buildColumnSourceResolver`).
421
- *
422
- * NOTE: one spine clone plus one walk per probe, and the DROP COLUMN guard probes every
423
- * live assertion. Trivial at the handful-of-assertions scale schemas have today, and it
424
- * only runs on DDL; if a schema ever carries assertions by the hundred, filter by
425
- * `tableReferencedInAst` (no clone) before paying for the column probe.
426
- */
427
- export function columnReferencedInAst(node, tableName, columnName, defaultSchemaName, resolveColumnInSource) {
428
- if (!node)
429
- return false;
430
- return renameColumnInAst(spineCloneAst(node), tableName, columnName, PROBE_COLUMN_NAME, defaultSchemaName, resolveColumnInSource);
431
- }
432
- /**
433
- * {@link columnReferencedInAst} for an expression that resolves unqualified refs
434
- * against an implicit binding to `tableName` — a CHECK expression or a
435
- * partial-index predicate. Uses {@link renameColumnInCheckExpression}'s seeded
436
- * entry point so the probe answers for exactly the scope that expression is
437
- * planned in.
438
- */
439
- export function columnReferencedInCheckExpression(expr, tableName, columnName, defaultSchemaName, resolveColumnInSource) {
440
- if (!expr)
441
- return false;
442
- return renameColumnInCheckExpression(spineCloneAst(expr), tableName, columnName, PROBE_COLUMN_NAME, defaultSchemaName, resolveColumnInSource);
443
- }
444
- /**
445
- * Rewrite the renamed column inside every partial-index predicate of `indexes`,
446
- * in place. A predicate resolves unqualified refs against the indexed table
447
- * itself — the same implicit seed a CHECK expression uses — so
448
- * {@link renameColumnInCheckExpression} is the correct entry point here, not
449
- * {@link renameColumnInAst}.
450
- *
451
- * The predicate `Expression` is shared by reference between the catalog's
452
- * `TableSchema`, any module-local copy of it, and — for a unique partial index —
453
- * the `derivedFromIndex` UNIQUE constraint that carries the same predicate.
454
- * Rewriting in place keeps all of them in step; cloning would strand the derived
455
- * constraint on the old AST.
456
- *
457
- * Idempotent: once rewritten, nothing names `oldColName` any more, so a second
458
- * call with the same pair returns false without touching the AST.
459
- *
460
- * The parameter is structurally typed rather than `IndexSchema[]` so this module
461
- * stays free of catalog imports; `IndexSchema` satisfies it.
462
- *
463
- * Sharing the seeded entry point also brings its `new.` / `old.` row-image match
464
- * along, which is inert here: a partial-index predicate describes rows already
465
- * stored, has no written-row context, and a predicate naming one would not plan
466
- * in the first place. Nothing to suppress — noted so the shared entry point does
467
- * not read as an oversight.
468
- */
469
- export function renameColumnInIndexPredicates(indexes, tableName, oldColName, newColName, defaultSchemaName, resolveColumnInSource) {
470
- return rewriteEach(indexes, idx => idx.predicate, expr => renameColumnInCheckExpression(expr, tableName, oldColName, newColName, defaultSchemaName, resolveColumnInSource));
471
- }
472
- /**
473
- * Rewrite the renamed column inside every CHECK constraint expression of `checks`
474
- * belonging to the renamed table itself, in place. The seeded-scope entry point
475
- * applies for the same reason it does for a partial-index predicate: a CHECK
476
- * resolves unqualified refs against its owning table.
477
- *
478
- * Only pass the renamed table's OWN checks — a CHECK on a *different* table that
479
- * happens to reference the renamed table needs {@link renameColumnInAst}, whose
480
- * walk has no implicit seed.
481
- */
482
- export function renameColumnInCheckConstraints(checks, tableName, oldColName, newColName, defaultSchemaName, resolveColumnInSource) {
483
- return rewriteEach(checks, cc => cc.expr, expr => renameColumnInCheckExpression(expr, tableName, oldColName, newColName, defaultSchemaName, resolveColumnInSource));
484
- }
485
- /**
486
- * Rewrite the renamed column inside the two expressions a `ColumnSchema` can carry —
487
- * a column DEFAULT (`b integer default (new.a + 1)`, however authored: inline, or via
488
- * `ALTER TABLE … ALTER COLUMN … SET DEFAULT`, which writes the same field) and a
489
- * generated column's body (`g integer generated always as (a + 1)`) — in place, for
490
- * every column of the renamed table itself.
491
- *
492
- * The seeded {@link renameColumnInCheckExpression} entry point is correct for both, for
493
- * the two halves of what the seed provides:
494
- *
495
- * - The implicit unaliased binding to the owning table is what makes a generated
496
- * column's **bare** `a` resolve — it has no FROM clause to bind against, exactly like
497
- * a CHECK.
498
- * - The seed also owns the `new.` / `old.` **row-image** namespace, which is what makes a
499
- * default's `new.a` resolve. A default is evaluated against the row being written, so
500
- * that qualifier names this table's row image, not anything a FROM binds.
501
- *
502
- * Case folding (`NEW.A`) and the shadowing edge (a real table literally named `new`,
503
- * reached from a subquery inside the expression) therefore behave exactly as they do for
504
- * a CHECK, because the same walk decides all three.
505
- *
506
- * Only pass the renamed table's OWN columns. A default on a *different* table can reach
507
- * this table only through a subquery, so an unqualified ref there must bind inside that
508
- * subquery's FROM — that caller wants {@link renameColumnInAst}, whose walk has no seed.
509
- *
510
- * Same sharing and idempotence story as {@link renameColumnInCheckConstraints}: a
511
- * module's rename hook rebuilds only the renamed column's `ColumnSchema` and keeps every
512
- * other one by reference, and even the rebuilt one carries the SAME expression nodes
513
- * (`buildConstraintsFromColumn` passes them into the reconstructed `ColumnDef` by
514
- * reference and `columnDefToSchema` assigns them straight back), so one in-place rewrite
515
- * reaches every holder and a second call with the same pair is a no-op.
516
- *
517
- * The parameter is structurally typed rather than `ColumnSchema[]` so this module stays
518
- * free of catalog imports; `ColumnSchema` satisfies it.
519
- */
520
- export function renameColumnInColumnExpressions(columns, tableName, oldColName, newColName, defaultSchemaName, resolveColumnInSource) {
521
- const rewrite = (expr) => renameColumnInCheckExpression(expr, tableName, oldColName, newColName, defaultSchemaName, resolveColumnInSource);
522
- // Both walks always run — `||` on the results, not short-circuited between them.
523
- const defaultsChanged = rewriteEach(columns, c => c.defaultValue ?? undefined, rewrite);
524
- const generatedChanged = rewriteEach(columns, c => c.generatedExpr, rewrite);
525
- return defaultsChanged || generatedChanged;
526
- }
527
- function emptyFrame() {
528
- return {
529
- unaliased: new Set(),
530
- aliasMap: new Map(),
531
- ctesExposingRenamed: new Set(),
532
- ctesInScope: new Set(),
533
- ctesShadowingSource: new Set(),
534
- realSources: [],
535
- };
536
- }
537
- function buildScopeFrame(from, state) {
538
- const frame = emptyFrame();
539
- if (!from)
540
- return frame;
541
- for (const item of from) {
542
- collectFromBindings(item, state, frame);
543
- }
544
- return frame;
545
- }
546
- function collectFromBindings(item, state, frame) {
547
- switch (item.type) {
548
- case 'table': {
549
- const ts = item;
550
- const name = ts.table.name.toLowerCase();
551
- // Unqualified reference to a CTE in scope — the CTE shadows any
552
- // same-named real table. Whether it re-exposes the renamed column
553
- // determines whether unqualified refs against this source rewrite.
554
- if (ts.table.schema === undefined && isCteInScope(state, name)) {
555
- if (isCteExposingInScope(state, name)) {
556
- if (ts.alias) {
557
- frame.aliasMap.set(ts.alias.toLowerCase(), state.tableName);
558
- }
559
- else {
560
- frame.unaliased.add(state.tableName);
561
- // The CTE name acts as an implicit qualifier for refs like "a.k".
562
- frame.aliasMap.set(name, state.tableName);
563
- }
564
- }
565
- else {
566
- // Shadowing-but-not-exposing: the source binds to the CTE
567
- // row source, not the renamed real table. Record the
568
- // qualifier (alias if present, otherwise the source name)
569
- // so qualified column refs against it don't short-circuit
570
- // to the renamed table.
571
- frame.ctesShadowingSource.add(ts.alias ? ts.alias.toLowerCase() : name);
572
- }
573
- // Shadowing-but-not-exposing: do not bind as the renamed table.
574
- break;
575
- }
576
- const schemaLower = (ts.table.schema ?? state.defaultSchema).toLowerCase();
577
- if (ts.alias) {
578
- frame.aliasMap.set(ts.alias.toLowerCase(), name);
579
- }
580
- else if (schemaLower === state.defaultSchema || ts.table.schema === undefined) {
581
- frame.unaliased.add(name);
582
- }
583
- // Record as a real-table source so the unqualified-scope walk can
584
- // ask whether this source exposes the renamed column. Both aliased
585
- // and unaliased real sources are recorded — asking "does u expose
586
- // col v" is the same question regardless of any alias.
587
- frame.realSources.push({ schema: schemaLower, name });
588
- break;
589
- }
590
- case 'join': {
591
- const join = item;
592
- collectFromBindings(join.left, state, frame);
593
- collectFromBindings(join.right, state, frame);
594
- break;
595
- }
596
- case 'subquerySource':
597
- case 'functionSource':
598
- // Aliased; these don't contribute the renamed underlying table for
599
- // unqualified resolution purposes.
600
- break;
601
- }
602
- }
603
- /**
604
- * Innermost-first walk: an inner non-exposing same-name CTE shadows an
605
- * outer exposing one, so a `ctesInScope` hit without a matching
606
- * `ctesExposingRenamed` entry wins. `isCteInScope` (below) intentionally
607
- * stays OR-shaped — it only gates "is this source a CTE rather than a
608
- * real table?", a question for which any enclosing CTE suffices.
609
- */
610
- function isCteExposingInScope(state, name) {
611
- for (let i = state.scopeStack.length - 1; i >= 0; i--) {
612
- const frame = state.scopeStack[i];
613
- if (frame.ctesExposingRenamed.has(name))
614
- return true;
615
- if (frame.ctesInScope.has(name))
616
- return false;
617
- }
618
- return false;
619
- }
620
- function isCteInScope(state, name) {
621
- for (const frame of state.scopeStack) {
622
- if (frame.ctesInScope.has(name))
623
- return true;
624
- }
625
- return false;
626
- }
627
- /**
628
- * Innermost-first walk: a closer same-name CTE shadows an outer unaliased
629
- * binding to the renamed real table. When a `resolveColumnInSource`
630
- * callback is configured, also stop at any inner FROM frame whose real
631
- * sources expose `oldCol` — the unqualified ref binds inside that frame
632
- * and an outer seed binding must not capture it.
633
- */
634
- function isTableInUnaliasedScope(state) {
635
- for (let i = state.scopeStack.length - 1; i >= 0; i--) {
636
- const frame = state.scopeStack[i];
637
- if (frame.ctesInScope.has(state.tableName))
638
- return false;
639
- if (state.resolveColumnInSource && frame.realSources.length > 0) {
640
- for (const src of frame.realSources) {
641
- // The renamed table itself trivially exposes oldCol; defer to
642
- // the existing `unaliased` check below so we don't
643
- // double-capture.
644
- if (src.name === state.tableName && src.schema === state.defaultSchema)
645
- continue;
646
- if (state.resolveColumnInSource(src.schema, src.name, state.oldCol))
647
- return false;
648
- }
649
- }
650
- if (frame.unaliased.has(state.tableName))
651
- return true;
652
- }
653
- return false;
654
- }
655
- /**
656
- * Innermost-first walk: a closer alias binding wins over an outer one
657
- * (standard SQL alias shadowing).
658
- */
659
- function aliasResolvesToTable(state, alias) {
660
- const aliasLower = alias.toLowerCase();
661
- for (let i = state.scopeStack.length - 1; i >= 0; i--) {
662
- const target = state.scopeStack[i].aliasMap.get(aliasLower);
663
- if (target !== undefined)
664
- return target === state.tableName;
665
- }
666
- return false;
667
- }
668
- /**
669
- * Walk the scope stack innermost-first to decide whether a qualifier
670
- * resolves to a non-exposing shadowing CTE rather than the renamed real
671
- * table. A closer rebind to the real table (via unaliased binding or alias)
672
- * wins over an outer shadowing entry.
673
- */
674
- function isQualifierShadowedInScope(state, qualifier) {
675
- for (let i = state.scopeStack.length - 1; i >= 0; i--) {
676
- const frame = state.scopeStack[i];
677
- if (frame.ctesShadowingSource.has(qualifier))
678
- return true;
679
- // Closer rebind to the real table wins → not shadowed at this point.
680
- if (frame.aliasMap.get(qualifier) === state.tableName)
681
- return false;
682
- if (qualifier === state.tableName && frame.unaliased.has(state.tableName))
683
- return false;
684
- }
685
- return false;
686
- }
687
- /**
688
- * Whether a qualified column ref names the **row image** of the table this
689
- * seeded walk was entered for — `new.<col>` / `old.<col>` inside one of its own
690
- * CHECK expressions. Only the seeded entry point sets
691
- * `matchRowImageQualifier`; see {@link renameColumnInCheckExpression} for why
692
- * that scoping is what keeps another table's `new.` ref out of this match.
693
- *
694
- * A schema-qualified `main.new.a` is a real three-part table reference, never a
695
- * row image, so it is excluded outright.
696
- *
697
- * `qualifierLower` is `col.table` already lowercased by the caller.
698
- */
699
- function matchesRowImage(state, col, qualifierLower) {
700
- if (!state.matchRowImageQualifier)
701
- return false;
702
- if (col.schema !== undefined)
703
- return false;
704
- if (qualifierLower !== 'new' && qualifierLower !== 'old')
705
- return false;
706
- return !isQualifierReboundAboveSeed(state, qualifierLower);
707
- }
708
- /**
709
- * Whether any real FROM / WITH frame rebinds `qualifier` to a row source of its
710
- * own, which would make a `new.` / `old.` ref name that source rather than the
711
- * row image. `new` and `old` are not reserved words here — `create table "new"`
712
- * is legal — so this scan is what stops the row-image match from false-firing
713
- * inside `check ((select max("new".a) from "new") >= 0)`.
714
- *
715
- * The scan starts at index 1: frame 0 is the implicit seed
716
- * {@link renameColumnInCheckExpression} pushes for the owning table, which is
717
- * not a FROM binding and must not count as a rebind. All four ways a frame can
718
- * bind a qualifier are checked — an unaliased source, an alias, a CTE name in
719
- * scope, and a shadowing-but-not-exposing CTE source.
720
- *
721
- * Deliberately "any enclosing frame wins" rather than innermost-first: a frame
722
- * only sits above index 0 while the walk is *inside* the subquery that pushed
723
- * it, so every such frame really does enclose the reference. A top-level
724
- * `new.a` in `check (new.a > 0 and (select count(*) from "new") >= 0)` is
725
- * visited with only the seed on the stack and still matches.
726
- *
727
- * NOTE: the residual is a *correlated* `new.<col>` written inside a subquery
728
- * that selects from a real table named `new` — left alone by both the rewrite
729
- * and the drop refusal, since the qualifier is ambiguous there anyway. If that
730
- * ever needs to resolve to the row image, it needs a spelling that distinguishes
731
- * the two (the SQL standard has none), not a change to this scan.
732
- */
733
- function isQualifierReboundAboveSeed(state, qualifier) {
734
- for (let i = 1; i < state.scopeStack.length; i++) {
735
- const frame = state.scopeStack[i];
736
- if (frame.unaliased.has(qualifier))
737
- return true;
738
- if (frame.aliasMap.has(qualifier))
739
- return true;
740
- if (frame.ctesInScope.has(qualifier))
741
- return true;
742
- if (frame.ctesShadowingSource.has(qualifier))
743
- return true;
744
- }
745
- return false;
746
- }
747
- function visitColumnRename(node, state) {
748
- if (!node)
749
- return;
750
- switch (node.type) {
751
- case 'select': {
752
- const stmt = node;
753
- pushWithFrame(stmt.withClause, state);
754
- try {
755
- const frame = buildScopeFrame(stmt.from, state);
756
- state.scopeStack.push(frame);
757
- try {
758
- // Capture pre-rewrite output names of UNALIASED bare projections: a
759
- // rename that rewrites one shifts the select's OUTPUT name with it, so
760
- // any `new.<old>` refs in sibling `with inverse` exprs must follow
761
- // (a `new.` ref is by view-output name; aliased / computed projections
762
- // keep their output name, so the body rewrite alone covers them).
763
- const preOutputNames = (stmt.columns ?? []).map(c => c.type === 'column' && !c.alias && c.expr.type === 'column' ? c.expr.name : undefined);
764
- (stmt.columns ?? []).forEach(c => {
765
- if (c.type === 'column')
766
- visitColumnRename(c.expr, state);
767
- });
768
- // `with inverse` clauses: the assignment target is a bare base-column
769
- // name resolving against this select's FROM — exactly an unqualified
770
- // body ref, so it rides the same scope-aware walk via a synthetic
771
- // probe (the `with defaults` clause below uses the same pattern); the
772
- // assignment expr rewrites like any body expression.
773
- (stmt.columns ?? []).forEach(c => {
774
- if (c.type !== 'column' || !c.inverse?.length)
775
- return;
776
- c.inverse.forEach(a => {
777
- const probe = { type: 'column', name: a.column };
778
- visitColumnRename(probe, state);
779
- if (probe.name !== a.column) {
780
- a.column = probe.name;
781
- state.changed = true;
782
- }
783
- visitColumnRename(a.expr, state);
784
- });
785
- });
786
- // `with defaults` clause: each entry's `column` is a bare base-column
787
- // name of this select's FROM (a projected-away base column), so it
788
- // rides the same scope-aware synthetic probe as a `with inverse`
789
- // target; the entry's `expr` evaluates in the inserted-row context of
790
- // the FROM table — exactly the FROM frame already on the scope stack —
791
- // so it rewrites like any body expression (an inner subquery in the
792
- // expr pushes its own frame and disambiguates a like-named column).
793
- (stmt.defaults ?? []).forEach(d => {
794
- const probe = { type: 'column', name: d.column };
795
- visitColumnRename(probe, state);
796
- if (probe.name !== d.column) {
797
- d.column = probe.name;
798
- state.changed = true;
799
- }
800
- visitColumnRename(d.expr, state);
801
- });
802
- const outputRenames = new Map();
803
- (stmt.columns ?? []).forEach((c, i) => {
804
- const before = preOutputNames[i];
805
- if (before !== undefined && c.type === 'column' && c.expr.type === 'column' && c.expr.name !== before) {
806
- outputRenames.set(before.toLowerCase(), c.expr.name);
807
- }
808
- });
809
- // A star projection covering the renamed table exposes the old column
810
- // name as an OUTPUT name too — the rename shifts it exactly like an
811
- // unaliased bare projection, so sibling `new.<old>` refs must follow.
812
- // Skipped when an explicit projection still exposes the old name
813
- // (first-occurrence resolution keeps `new.<old>` bound to it).
814
- const hasInverseClauses = (stmt.columns ?? []).some(c => c.type === 'column' && !!c.inverse?.length);
815
- if (hasInverseClauses && !outputRenames.has(state.oldCol)) {
816
- const starCoversRenamed = (stmt.columns ?? []).some(c => {
817
- if (c.type !== 'all')
818
- return false;
819
- const boundToRenamed = frame.unaliased.has(state.tableName)
820
- || [...frame.aliasMap.values()].includes(state.tableName);
821
- if (c.table === undefined)
822
- return boundToRenamed;
823
- const q = c.table.toLowerCase();
824
- return frame.aliasMap.get(q) === state.tableName
825
- || (q === state.tableName && frame.unaliased.has(state.tableName));
826
- });
827
- const oldStillExposed = (stmt.columns ?? []).some(c => c.type === 'column'
828
- && (c.alias
829
- ? c.alias.toLowerCase() === state.oldCol
830
- : c.expr.type === 'column' && c.expr.name.toLowerCase() === state.oldCol));
831
- if (starCoversRenamed && !oldStillExposed) {
832
- outputRenames.set(state.oldCol, state.newCol);
833
- }
834
- }
835
- if (outputRenames.size > 0) {
836
- (stmt.columns ?? []).forEach(c => {
837
- if (c.type !== 'column' || !c.inverse?.length)
838
- return;
839
- c.inverse.forEach(a => {
840
- if (renameNewQualifiedRefs(a.expr, outputRenames))
841
- state.changed = true;
842
- });
843
- });
844
- }
845
- (stmt.from ?? []).forEach(f => visitColumnRename(f, state));
846
- visitColumnRename(stmt.where, state);
847
- (stmt.groupBy ?? []).forEach(g => visitColumnRename(g, state));
848
- visitColumnRename(stmt.having, state);
849
- (stmt.orderBy ?? []).forEach(o => visitColumnRename(o.expr, state));
850
- visitColumnRename(stmt.limit, state);
851
- visitColumnRename(stmt.offset, state);
852
- visitColumnRename(stmt.union, state);
853
- if (stmt.compound)
854
- visitColumnRename(stmt.compound.select, state);
855
- }
856
- finally {
857
- state.scopeStack.pop();
858
- }
859
- }
860
- finally {
861
- state.scopeStack.pop();
862
- }
863
- break;
864
- }
865
- case 'insert': {
866
- const stmt = node;
867
- pushWithFrame(stmt.withClause, state);
868
- try {
869
- const targetIsRenamed = eq(stmt.table.name, state.tableName) &&
870
- (stmt.table.schema === undefined || eq(stmt.table.schema, state.defaultSchema));
871
- if (targetIsRenamed && stmt.columns) {
872
- stmt.columns = stmt.columns.map(c => {
873
- if (c.toLowerCase() === state.oldCol) {
874
- state.changed = true;
875
- return state.newCol;
876
- }
877
- return c;
878
- });
879
- }
880
- if (targetIsRenamed) {
881
- (stmt.upsertClauses ?? []).forEach(uc => {
882
- if (uc.conflictTarget) {
883
- uc.conflictTarget = uc.conflictTarget.map(c => {
884
- if (c.toLowerCase() === state.oldCol) {
885
- state.changed = true;
886
- return state.newCol;
887
- }
888
- return c;
889
- });
890
- }
891
- if (uc.assignments) {
892
- for (const a of uc.assignments) {
893
- if (a.column.toLowerCase() === state.oldCol) {
894
- a.column = state.newCol;
895
- state.changed = true;
896
- }
897
- }
898
- }
899
- });
900
- }
901
- visitColumnRename(stmt.source, state);
902
- (stmt.upsertClauses ?? []).forEach(uc => {
903
- (uc.assignments ?? []).forEach(a => visitColumnRename(a.value, state));
904
- visitColumnRename(uc.where, state);
905
- });
906
- (stmt.returning ?? []).forEach(r => {
907
- if (r.type === 'column')
908
- visitColumnRename(r.expr, state);
909
- });
910
- (stmt.contextValues ?? []).forEach(cv => visitColumnRename(cv.value, state));
911
- }
912
- finally {
913
- state.scopeStack.pop();
914
- }
915
- break;
916
- }
917
- case 'update': {
918
- const stmt = node;
919
- pushWithFrame(stmt.withClause, state);
920
- try {
921
- const targetIsRenamed = eq(stmt.table.name, state.tableName) &&
922
- (stmt.table.schema === undefined || eq(stmt.table.schema, state.defaultSchema));
923
- if (targetIsRenamed) {
924
- for (const a of stmt.assignments) {
925
- if (a.column.toLowerCase() === state.oldCol) {
926
- a.column = state.newCol;
927
- state.changed = true;
928
- }
929
- }
930
- }
931
- // Push a scope frame so unqualified column refs in WHERE/RETURNING
932
- // resolve against the update target.
933
- const frame = emptyFrame();
934
- if (stmt.table.schema === undefined || eq(stmt.table.schema, state.defaultSchema)) {
935
- frame.unaliased.add(stmt.table.name.toLowerCase());
936
- }
937
- state.scopeStack.push(frame);
938
- try {
939
- stmt.assignments.forEach(a => visitColumnRename(a.value, state));
940
- visitColumnRename(stmt.where, state);
941
- (stmt.returning ?? []).forEach(r => {
942
- if (r.type === 'column')
943
- visitColumnRename(r.expr, state);
944
- });
945
- (stmt.contextValues ?? []).forEach(cv => visitColumnRename(cv.value, state));
946
- }
947
- finally {
948
- state.scopeStack.pop();
949
- }
950
- }
951
- finally {
952
- state.scopeStack.pop();
953
- }
954
- break;
955
- }
956
- case 'delete': {
957
- const stmt = node;
958
- pushWithFrame(stmt.withClause, state);
959
- try {
960
- const frame = emptyFrame();
961
- if (stmt.table.schema === undefined || eq(stmt.table.schema, state.defaultSchema)) {
962
- frame.unaliased.add(stmt.table.name.toLowerCase());
963
- }
964
- state.scopeStack.push(frame);
965
- try {
966
- visitColumnRename(stmt.where, state);
967
- (stmt.returning ?? []).forEach(r => {
968
- if (r.type === 'column')
969
- visitColumnRename(r.expr, state);
970
- });
971
- (stmt.contextValues ?? []).forEach(cv => visitColumnRename(cv.value, state));
972
- }
973
- finally {
974
- state.scopeStack.pop();
975
- }
976
- }
977
- finally {
978
- state.scopeStack.pop();
979
- }
980
- break;
981
- }
982
- case 'values': {
983
- const stmt = node;
984
- stmt.values.forEach(row => row.forEach(v => visitColumnRename(v, state)));
985
- break;
986
- }
987
- case 'join': {
988
- const join = node;
989
- visitColumnRename(join.left, state);
990
- visitColumnRename(join.right, state);
991
- visitColumnRename(join.condition, state);
992
- break;
993
- }
994
- case 'functionSource': {
995
- node.args.forEach(a => visitColumnRename(a, state));
996
- break;
997
- }
998
- case 'subquerySource': {
999
- visitColumnRename(node.subquery, state);
1000
- break;
1001
- }
1002
- case 'binary': {
1003
- const e = node;
1004
- visitColumnRename(e.left, state);
1005
- visitColumnRename(e.right, state);
1006
- break;
1007
- }
1008
- case 'unary':
1009
- case 'cast':
1010
- case 'collate':
1011
- visitColumnRename(node.expr, state);
1012
- break;
1013
- case 'function':
1014
- node.args.forEach(a => visitColumnRename(a, state));
1015
- break;
1016
- case 'subquery':
1017
- visitColumnRename(node.query, state);
1018
- break;
1019
- case 'windowFunction': {
1020
- const wf = node;
1021
- visitColumnRename(wf.function, state);
1022
- visitColumnRename(wf.window, state);
1023
- break;
1024
- }
1025
- case 'windowDefinition': {
1026
- const wd = node;
1027
- (wd.partitionBy ?? []).forEach(p => visitColumnRename(p, state));
1028
- (wd.orderBy ?? []).forEach(o => visitColumnRename(o.expr, state));
1029
- break;
1030
- }
1031
- case 'case': {
1032
- const ce = node;
1033
- visitColumnRename(ce.baseExpr, state);
1034
- ce.whenThenClauses.forEach(wt => {
1035
- visitColumnRename(wt.when, state);
1036
- visitColumnRename(wt.then, state);
1037
- });
1038
- visitColumnRename(ce.elseExpr, state);
1039
- break;
1040
- }
1041
- case 'in': {
1042
- const ie = node;
1043
- visitColumnRename(ie.expr, state);
1044
- (ie.values ?? []).forEach(v => visitColumnRename(v, state));
1045
- visitColumnRename(ie.subquery, state);
1046
- break;
1047
- }
1048
- case 'exists':
1049
- visitColumnRename(node.subquery, state);
1050
- break;
1051
- case 'between': {
1052
- const be = node;
1053
- visitColumnRename(be.expr, state);
1054
- visitColumnRename(be.lower, state);
1055
- visitColumnRename(be.upper, state);
1056
- break;
1057
- }
1058
- case 'column': {
1059
- const col = node;
1060
- if (col.name.toLowerCase() !== state.oldCol)
1061
- break;
1062
- if (col.table) {
1063
- const qualifierLower = col.table.toLowerCase();
1064
- const directHit = qualifierLower === state.tableName &&
1065
- (col.schema === undefined || eq(col.schema, state.defaultSchema)) &&
1066
- !isQualifierShadowedInScope(state, qualifierLower);
1067
- const viaAlias = aliasResolvesToTable(state, col.table);
1068
- // Row-image match is tried LAST so a table genuinely named `new` or
1069
- // `old` that is itself the renamed table keeps resolving through
1070
- // `directHit` exactly as it did before this path existed.
1071
- const rowImageHit = !directHit && !viaAlias && matchesRowImage(state, col, qualifierLower);
1072
- if (directHit || viaAlias || rowImageHit) {
1073
- col.name = state.newCol;
1074
- state.changed = true;
1075
- }
1076
- }
1077
- else {
1078
- if (isTableInUnaliasedScope(state)) {
1079
- col.name = state.newCol;
1080
- state.changed = true;
1081
- }
1082
- }
1083
- break;
1084
- }
1085
- case 'table':
1086
- // Table sources don't contain column names.
1087
- break;
1088
- default:
1089
- break;
1090
- }
1091
- }
1092
- /**
1093
- * Push a with-frame that registers any CTEs in the given WITH clause that
1094
- * re-expose the renamed column. CTEs are visited in declaration order so
1095
- * later CTEs see earlier ones in the same WITH.
1096
- *
1097
- * For `with recursive`, each CTE's name is registered in `ctesInScope`
1098
- * *before* its body is visited so self-references inside the recursive step
1099
- * resolve to the CTE (not to a same-named renamed table). For non-recursive
1100
- * WITH, the name is registered only after the body — a non-recursive body
1101
- * must not see itself.
1102
- *
1103
- * Caller is responsible for popping the frame via `state.scopeStack.pop()`.
1104
- */
1105
- function pushWithFrame(withClause, state) {
1106
- const frame = emptyFrame();
1107
- state.scopeStack.push(frame);
1108
- if (withClause) {
1109
- for (const cte of withClause.ctes) {
1110
- const nameLower = cte.name.toLowerCase();
1111
- if (withClause.recursive) {
1112
- frame.ctesInScope.add(nameLower);
1113
- }
1114
- visitColumnRename(cte.query, state);
1115
- if (cteExposesRenamedColumn(cte, state)) {
1116
- frame.ctesExposingRenamed.add(nameLower);
1117
- }
1118
- frame.ctesInScope.add(nameLower);
1119
- }
1120
- }
1121
- return frame;
1122
- }
1123
- /**
1124
- * Rebuild a with-frame's `ctesExposingRenamed` set for exposure analysis
1125
- * without re-visiting CTE bodies (they were already visited).
1126
- */
1127
- function analyzeWithFrame(withClause, state) {
1128
- const frame = emptyFrame();
1129
- if (!withClause)
1130
- return frame;
1131
- state.scopeStack.push(frame);
1132
- try {
1133
- for (const cte of withClause.ctes) {
1134
- if (cteExposesRenamedColumn(cte, state)) {
1135
- frame.ctesExposingRenamed.add(cte.name.toLowerCase());
1136
- }
1137
- frame.ctesInScope.add(cte.name.toLowerCase());
1138
- }
1139
- }
1140
- finally {
1141
- state.scopeStack.pop();
1142
- }
1143
- return frame;
1144
- }
1145
- /**
1146
- * Determine whether a CTE re-exposes the renamed column under name `state.newCol`
1147
- * (the column has already been rewritten inside its body if the body referenced it).
1148
- *
1149
- * Returns false when:
1150
- * - The CTE has an explicit column list (renaming the input to fixed names).
1151
- * - The body is not a SELECT (INSERT/UPDATE/DELETE WITH RETURNING — out of scope).
1152
- * - No passthrough result column references the renamed table's column.
1153
- */
1154
- function cteExposesRenamedColumn(cte, state) {
1155
- if (cte.columns)
1156
- return false;
1157
- const query = cte.query;
1158
- if (query.type !== 'select')
1159
- return false;
1160
- const select = query;
1161
- // Recreate the body's own with-frame so nested CTE refs in `select.from`
1162
- // resolve correctly during exposure analysis.
1163
- const bodyWithFrame = analyzeWithFrame(select.withClause, state);
1164
- state.scopeStack.push(bodyWithFrame);
1165
- try {
1166
- const bodyFrame = buildScopeFrame(select.from, state);
1167
- for (const col of select.columns ?? []) {
1168
- if (isResultColumnExposure(col, bodyFrame, state))
1169
- return true;
1170
- }
1171
- return false;
1172
- }
1173
- finally {
1174
- state.scopeStack.pop();
1175
- }
1176
- }
1177
- function isResultColumnExposure(col, bodyFrame, state) {
1178
- if (col.type === 'all') {
1179
- if (col.table === undefined) {
1180
- return bodyFrame.unaliased.has(state.tableName);
1181
- }
1182
- const qualLower = col.table.toLowerCase();
1183
- if (qualLower === state.tableName && bodyFrame.unaliased.has(state.tableName))
1184
- return true;
1185
- return bodyFrame.aliasMap.get(qualLower) === state.tableName;
1186
- }
1187
- if (col.alias !== undefined)
1188
- return false;
1189
- const expr = col.expr;
1190
- if (expr.type !== 'column')
1191
- return false;
1192
- const colExpr = expr;
1193
- if (colExpr.name.toLowerCase() !== state.newCol.toLowerCase())
1194
- return false;
1195
- if (colExpr.table === undefined) {
1196
- return bodyFrame.unaliased.has(state.tableName);
1197
- }
1198
- const qualLower = colExpr.table.toLowerCase();
1199
- if (qualLower === state.tableName &&
1200
- (colExpr.schema === undefined || eq(colExpr.schema, state.defaultSchema))) {
1201
- return true;
1202
- }
1203
- return bodyFrame.aliasMap.get(qualLower) === state.tableName;
1204
- }
1205
- /**
1206
- * Rename `new.<old>` → `new.<new>` references inside a `with inverse` assignment
1207
- * expression, for output columns whose name shifted under a column rename (an
1208
- * unaliased bare projection of the renamed column). Uniform, depth-blind in-place
1209
- * walk over the expression's object graph: the `new.` qualifier alone decides (it
1210
- * is the reserved written-row namespace no FROM source legitimately shadows), so
1211
- * no scope tracking applies — narrower and simpler than the scope-aware walkers
1212
- * above. Returns whether any reference was rewritten.
1213
- *
1214
- * Depth-blind **on purpose**, unlike the row-image match in `matchesRowImage`.
1215
- * The two look alike but answer different questions: this one rewrites by view
1216
- * *output* name inside a `with inverse` expr, whose grammar admits no FROM
1217
- * clause that could rebind `new`, whereas a CHECK body may contain a subquery
1218
- * selecting from a real table named `"new"` and so must consult the scope stack.
1219
- */
1220
- function renameNewQualifiedRefs(expr, renames) {
1221
- let changed = false;
1222
- const visit = (v) => {
1223
- if (Array.isArray(v)) {
1224
- v.forEach(visit);
1225
- return;
1226
- }
1227
- if (v === null || typeof v !== 'object')
1228
- return;
1229
- const n = v;
1230
- if (n.type === 'column' && typeof n.table === 'string' && n.table.toLowerCase() === 'new'
1231
- && n.schema === undefined && typeof n.name === 'string') {
1232
- const to = renames.get(n.name.toLowerCase());
1233
- if (to !== undefined && to !== n.name) {
1234
- n.name = to;
1235
- changed = true;
1236
- }
1237
- }
1238
- for (const key of Object.keys(n)) {
1239
- if (key === 'loc')
1240
- continue;
1241
- visit(n[key]);
1242
- }
1243
- };
1244
- visit(expr);
1245
- return changed;
1246
- }
1247
- /**
1248
- * Strip table-qualified self-references in a CHECK expression down to the
1249
- * unqualified form: `check (t.qty > 0)` (or `main.t.qty`) becomes
1250
- * `check (qty > 0)` so the constraint planner's row-context scope — which
1251
- * registers bare / `NEW.` / `OLD.` column names only — can resolve it.
1252
- *
1253
- * Deliberately NOT done by seeding `<table>.<col>` keys into the constraint
1254
- * scope: that scope is an ancestor of every subquery planned inside the
1255
- * CHECK, and a join peer's parent-chain fallback (`MultiScope` first-match
1256
- * on qualified names) would resolve an inner relation's qualified columns
1257
- * against the outer row context (observed with lens view expansions).
1258
- *
1259
- * The walk mirrors SQL shadowing rules: a qualifier rebound by an inner
1260
- * FROM (same table re-selected, an alias, or a CTE) is left untouched. A
1261
- * self-qualified ref inside a subquery is stripped only when no
1262
- * intervening FROM frame could capture the resulting unqualified name —
1263
- * real-table sources are asked via `resolveColumnInSource`; subquery /
1264
- * function / CTE sources are unanalyzable and conservatively block the
1265
- * strip (the ref then stays qualified and fails to resolve exactly as it
1266
- * did before this rewrite existed). CTE and derived-table bodies cannot
1267
- * correlate to the constraint row, so stripping is suppressed inside them.
1268
- *
1269
- * Mutates `expr` in place (callers pass a clone of the stored constraint
1270
- * AST) and returns whether anything was rewritten.
1271
- */
1272
- export function stripSelfQualifierInCheckExpression(expr, tableName, defaultSchemaName, resolveColumnInSource) {
1273
- if (!expr)
1274
- return false;
1275
- const state = {
1276
- tableName: tableName.toLowerCase(),
1277
- defaultSchema: defaultSchemaName.toLowerCase(),
1278
- resolve: resolveColumnInSource,
1279
- stack: [],
1280
- changed: false,
1281
- };
1282
- const seed = emptyStripFrame();
1283
- seed.bound.add(state.tableName);
1284
- state.stack.push(seed);
1285
- try {
1286
- visitStrip(expr, state);
1287
- }
1288
- finally {
1289
- state.stack.pop();
1290
- }
1291
- return state.changed;
1292
- }
1293
- function emptyStripFrame() {
1294
- return { bound: new Set(), realSources: [], hasOpaque: false, cteNames: new Set() };
1295
- }
1296
- function isStripCteName(state, name) {
1297
- for (const frame of state.stack) {
1298
- if (frame.cteNames.has(name))
1299
- return true;
1300
- }
1301
- return false;
1302
- }
1303
- function collectStripBindings(item, state, frame) {
1304
- switch (item.type) {
1305
- case 'table': {
1306
- const ts = item;
1307
- const name = ts.table.name.toLowerCase();
1308
- frame.bound.add(ts.alias ? ts.alias.toLowerCase() : name);
1309
- if (ts.table.schema === undefined && isStripCteName(state, name)) {
1310
- // CTE source — column set not analyzed.
1311
- frame.hasOpaque = true;
1312
- }
1313
- else {
1314
- frame.realSources.push({ schema: (ts.table.schema ?? state.defaultSchema).toLowerCase(), name });
1315
- }
1316
- break;
1317
- }
1318
- case 'join': {
1319
- const join = item;
1320
- collectStripBindings(join.left, state, frame);
1321
- collectStripBindings(join.right, state, frame);
1322
- break;
1323
- }
1324
- case 'subquerySource': {
1325
- frame.bound.add(item.alias.toLowerCase());
1326
- frame.hasOpaque = true;
1327
- break;
1328
- }
1329
- case 'functionSource': {
1330
- const fs = item;
1331
- if (fs.alias)
1332
- frame.bound.add(fs.alias.toLowerCase());
1333
- frame.hasOpaque = true;
1334
- break;
1335
- }
1336
- }
1337
- }
1338
- /** Visit a node in a context where stripping must not occur (CTE / derived-table bodies). */
1339
- function visitStripBarrier(node, state) {
1340
- const barrier = emptyStripFrame();
1341
- barrier.hasOpaque = true;
1342
- state.stack.push(barrier);
1343
- try {
1344
- visitStrip(node, state);
1345
- }
1346
- finally {
1347
- state.stack.pop();
1348
- }
1349
- }
1350
- function visitStrip(node, state) {
1351
- if (!node)
1352
- return;
1353
- switch (node.type) {
1354
- case 'select': {
1355
- const stmt = node;
1356
- const withFrame = emptyStripFrame();
1357
- state.stack.push(withFrame);
1358
- try {
1359
- for (const cte of stmt.withClause?.ctes ?? []) {
1360
- // A CTE body cannot correlate to the constraint row — no stripping inside.
1361
- visitStripBarrier(cte.query, state);
1362
- withFrame.cteNames.add(cte.name.toLowerCase());
1363
- }
1364
- const frame = buildStripFrame(stmt.from, state);
1365
- state.stack.push(frame);
1366
- try {
1367
- (stmt.columns ?? []).forEach(c => {
1368
- if (c.type === 'column')
1369
- visitStrip(c.expr, state);
1370
- });
1371
- (stmt.from ?? []).forEach(f => visitStrip(f, state));
1372
- visitStrip(stmt.where, state);
1373
- (stmt.groupBy ?? []).forEach(g => visitStrip(g, state));
1374
- visitStrip(stmt.having, state);
1375
- (stmt.orderBy ?? []).forEach(o => visitStrip(o.expr, state));
1376
- visitStrip(stmt.limit, state);
1377
- visitStrip(stmt.offset, state);
1378
- visitStrip(stmt.union, state);
1379
- if (stmt.compound)
1380
- visitStrip(stmt.compound.select, state);
1381
- }
1382
- finally {
1383
- state.stack.pop();
1384
- }
1385
- }
1386
- finally {
1387
- state.stack.pop();
1388
- }
1389
- break;
1390
- }
1391
- case 'values': {
1392
- node.values.forEach(row => row.forEach(v => visitStrip(v, state)));
1393
- break;
1394
- }
1395
- case 'join': {
1396
- const join = node;
1397
- visitStrip(join.left, state);
1398
- visitStrip(join.right, state);
1399
- visitStrip(join.condition, state);
1400
- break;
1401
- }
1402
- case 'functionSource': {
1403
- node.args.forEach(a => visitStrip(a, state));
1404
- break;
1405
- }
1406
- case 'subquerySource': {
1407
- // A derived table cannot correlate to the constraint row — no stripping inside.
1408
- visitStripBarrier(node.subquery, state);
1409
- break;
1410
- }
1411
- case 'binary': {
1412
- const e = node;
1413
- visitStrip(e.left, state);
1414
- visitStrip(e.right, state);
1415
- break;
1416
- }
1417
- case 'unary':
1418
- case 'cast':
1419
- case 'collate':
1420
- visitStrip(node.expr, state);
1421
- break;
1422
- case 'function':
1423
- node.args.forEach(a => visitStrip(a, state));
1424
- break;
1425
- case 'subquery':
1426
- visitStrip(node.query, state);
1427
- break;
1428
- case 'windowFunction': {
1429
- const wf = node;
1430
- visitStrip(wf.function, state);
1431
- visitStrip(wf.window, state);
1432
- break;
1433
- }
1434
- case 'windowDefinition': {
1435
- const wd = node;
1436
- (wd.partitionBy ?? []).forEach(p => visitStrip(p, state));
1437
- (wd.orderBy ?? []).forEach(o => visitStrip(o.expr, state));
1438
- break;
1439
- }
1440
- case 'case': {
1441
- const ce = node;
1442
- visitStrip(ce.baseExpr, state);
1443
- ce.whenThenClauses.forEach(wt => {
1444
- visitStrip(wt.when, state);
1445
- visitStrip(wt.then, state);
1446
- });
1447
- visitStrip(ce.elseExpr, state);
1448
- break;
1449
- }
1450
- case 'in': {
1451
- const ie = node;
1452
- visitStrip(ie.expr, state);
1453
- (ie.values ?? []).forEach(v => visitStrip(v, state));
1454
- visitStrip(ie.subquery, state);
1455
- break;
1456
- }
1457
- case 'exists':
1458
- visitStrip(node.subquery, state);
1459
- break;
1460
- case 'between': {
1461
- const be = node;
1462
- visitStrip(be.expr, state);
1463
- visitStrip(be.lower, state);
1464
- visitStrip(be.upper, state);
1465
- break;
1466
- }
1467
- case 'column': {
1468
- stripColumnQualifier(node, state);
1469
- break;
1470
- }
1471
- default:
1472
- break;
1473
- }
1474
- }
1475
- function buildStripFrame(from, state) {
1476
- const frame = emptyStripFrame();
1477
- (from ?? []).forEach(item => collectStripBindings(item, state, frame));
1478
- return frame;
1479
- }
1480
- function stripColumnQualifier(col, state) {
1481
- if (!col.table)
1482
- return;
1483
- const qualifier = col.table.toLowerCase();
1484
- // Innermost-first: a qualifier rebound by any inner FROM resolves there.
1485
- for (let i = state.stack.length - 1; i >= 1; i--) {
1486
- if (state.stack[i].bound.has(qualifier))
1487
- return;
1488
- }
1489
- if (qualifier !== state.tableName)
1490
- return;
1491
- if (!schemaMatches(col.schema, state.defaultSchema))
1492
- return;
1493
- // Strip only when no intervening frame could capture the unqualified name.
1494
- const colLower = col.name.toLowerCase();
1495
- for (let i = 1; i < state.stack.length; i++) {
1496
- const frame = state.stack[i];
1497
- if (frame.hasOpaque)
1498
- return;
1499
- for (const src of frame.realSources) {
1500
- if (state.resolve(src.schema, src.name, colLower))
1501
- return;
1502
- }
1503
- }
1504
- col.table = undefined;
1505
- col.schema = undefined;
1506
- state.changed = true;
1507
- }
2
+ * In-place, scope-aware AST rewriters over schema-object expressions: the
3
+ * rename walkers propagate ALTER TABLE RENAME operations into dependent
4
+ * objects (CHECK expressions, view SELECT bodies, etc.), and the
5
+ * self-qualifier strip folds a CHECK's table-qualified self-references so
6
+ * the constraint planner's row-context scope can resolve them.
7
+ *
8
+ * The three walkers live in `rename/`, split along their seams — table
9
+ * rename (`rename/table-rename.ts`), column rename
10
+ * (`rename/column-rename.ts`), self-qualifier strip
11
+ * (`rename/self-qualifier-strip.ts`) — over the shared vocabulary in
12
+ * `rename/shared.ts`. This module re-exports the public surface so callers
13
+ * keep one import path.
14
+ */
15
+ export { objectRefKey, objectRefKeySchema, singleSchemaObjectRefResolver } from './rename/shared.js';
16
+ export { renameTableInAst, tableReferencedInAst, collectTableRefsInAst, renameTableInIndexPredicates, renameTableInCheckConstraints, renameTableInColumnExpressions, } from './rename/table-rename.js';
17
+ export { PROBE_COLUMN_NAME, renameColumnInAst, renameColumnInCheckExpression, columnReferencedInAst, columnReferencedInCheckExpression, renameColumnInIndexPredicates, renameColumnInCheckConstraints, renameColumnInColumnExpressions, bodyExposesRenamedColumn, bodyPublishesColumnNamed, } from './rename/column-rename.js';
18
+ export { stripSelfQualifierInCheckExpression } from './rename/self-qualifier-strip.js';
1508
19
  //# sourceMappingURL=rename-rewriter.js.map