@quereus/quereus 4.4.0 → 4.5.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 (491) hide show
  1. package/README.md +6 -1
  2. package/dist/src/common/types.d.ts +9 -0
  3. package/dist/src/common/types.d.ts.map +1 -1
  4. package/dist/src/common/types.js.map +1 -1
  5. package/dist/src/core/database-events.d.ts +49 -0
  6. package/dist/src/core/database-events.d.ts.map +1 -1
  7. package/dist/src/core/database-events.js +148 -0
  8. package/dist/src/core/database-events.js.map +1 -1
  9. package/dist/src/core/database-materialized-views-analysis.d.ts.map +1 -1
  10. package/dist/src/core/database-materialized-views-analysis.js +1 -0
  11. package/dist/src/core/database-materialized-views-analysis.js.map +1 -1
  12. package/dist/src/core/database-materialized-views-plan-builders.d.ts +2 -1
  13. package/dist/src/core/database-materialized-views-plan-builders.d.ts.map +1 -1
  14. package/dist/src/core/database-materialized-views-plan-builders.js +26 -5
  15. package/dist/src/core/database-materialized-views-plan-builders.js.map +1 -1
  16. package/dist/src/core/database-materialized-views-plans.d.ts +6 -3
  17. package/dist/src/core/database-materialized-views-plans.d.ts.map +1 -1
  18. package/dist/src/core/database-materialized-views.d.ts +3 -2
  19. package/dist/src/core/database-materialized-views.d.ts.map +1 -1
  20. package/dist/src/core/database-materialized-views.js +26 -5
  21. package/dist/src/core/database-materialized-views.js.map +1 -1
  22. package/dist/src/core/database.d.ts +5 -0
  23. package/dist/src/core/database.d.ts.map +1 -1
  24. package/dist/src/core/database.js +21 -1
  25. package/dist/src/core/database.js.map +1 -1
  26. package/dist/src/core/internal-statement-cache.d.ts +18 -0
  27. package/dist/src/core/internal-statement-cache.d.ts.map +1 -0
  28. package/dist/src/core/internal-statement-cache.js +211 -0
  29. package/dist/src/core/internal-statement-cache.js.map +1 -0
  30. package/dist/src/func/builtins/aggregate.d.ts +13 -12
  31. package/dist/src/func/builtins/aggregate.d.ts.map +1 -1
  32. package/dist/src/func/builtins/aggregate.js +45 -67
  33. package/dist/src/func/builtins/aggregate.js.map +1 -1
  34. package/dist/src/func/builtins/builtin-window-functions.d.ts.map +1 -1
  35. package/dist/src/func/builtins/builtin-window-functions.js +63 -48
  36. package/dist/src/func/builtins/builtin-window-functions.js.map +1 -1
  37. package/dist/src/func/builtins/scalar.d.ts.map +1 -1
  38. package/dist/src/func/builtins/scalar.js +92 -3
  39. package/dist/src/func/builtins/scalar.js.map +1 -1
  40. package/dist/src/func/registration.d.ts +18 -1
  41. package/dist/src/func/registration.d.ts.map +1 -1
  42. package/dist/src/func/registration.js +26 -0
  43. package/dist/src/func/registration.js.map +1 -1
  44. package/dist/src/index.d.ts +10 -10
  45. package/dist/src/index.d.ts.map +1 -1
  46. package/dist/src/index.js +13 -12
  47. package/dist/src/index.js.map +1 -1
  48. package/dist/src/parser/parser.d.ts +6 -0
  49. package/dist/src/parser/parser.d.ts.map +1 -1
  50. package/dist/src/parser/parser.js +45 -34
  51. package/dist/src/parser/parser.js.map +1 -1
  52. package/dist/src/planner/analysis/coarsened-key.d.ts +4 -3
  53. package/dist/src/planner/analysis/coarsened-key.d.ts.map +1 -1
  54. package/dist/src/planner/analysis/coarsened-key.js +37 -3
  55. package/dist/src/planner/analysis/coarsened-key.js.map +1 -1
  56. package/dist/src/planner/analysis/comparison-collation.d.ts +35 -3
  57. package/dist/src/planner/analysis/comparison-collation.d.ts.map +1 -1
  58. package/dist/src/planner/analysis/comparison-collation.js +37 -3
  59. package/dist/src/planner/analysis/comparison-collation.js.map +1 -1
  60. package/dist/src/planner/analysis/coverage-prover.d.ts +13 -2
  61. package/dist/src/planner/analysis/coverage-prover.d.ts.map +1 -1
  62. package/dist/src/planner/analysis/coverage-prover.js +10 -14
  63. package/dist/src/planner/analysis/coverage-prover.js.map +1 -1
  64. package/dist/src/planner/analysis/expression-fingerprint.d.ts.map +1 -1
  65. package/dist/src/planner/analysis/expression-fingerprint.js +29 -0
  66. package/dist/src/planner/analysis/expression-fingerprint.js.map +1 -1
  67. package/dist/src/planner/analysis/predicate-conjuncts.d.ts +10 -4
  68. package/dist/src/planner/analysis/predicate-conjuncts.d.ts.map +1 -1
  69. package/dist/src/planner/analysis/predicate-conjuncts.js +28 -7
  70. package/dist/src/planner/analysis/predicate-conjuncts.js.map +1 -1
  71. package/dist/src/planner/analysis/query-rewrite-matcher.d.ts +6 -0
  72. package/dist/src/planner/analysis/query-rewrite-matcher.d.ts.map +1 -1
  73. package/dist/src/planner/analysis/query-rewrite-matcher.js +19 -29
  74. package/dist/src/planner/analysis/query-rewrite-matcher.js.map +1 -1
  75. package/dist/src/planner/analysis/sat-checker.d.ts.map +1 -1
  76. package/dist/src/planner/analysis/sat-checker.js +43 -4
  77. package/dist/src/planner/analysis/sat-checker.js.map +1 -1
  78. package/dist/src/planner/analysis/scalar-subqueries.d.ts +22 -3
  79. package/dist/src/planner/analysis/scalar-subqueries.d.ts.map +1 -1
  80. package/dist/src/planner/analysis/scalar-subqueries.js +32 -3
  81. package/dist/src/planner/analysis/scalar-subqueries.js.map +1 -1
  82. package/dist/src/planner/analysis/set-op-type-merge.d.ts +64 -0
  83. package/dist/src/planner/analysis/set-op-type-merge.d.ts.map +1 -0
  84. package/dist/src/planner/analysis/set-op-type-merge.js +97 -0
  85. package/dist/src/planner/analysis/set-op-type-merge.js.map +1 -0
  86. package/dist/src/planner/building/coercion.d.ts +78 -0
  87. package/dist/src/planner/building/coercion.d.ts.map +1 -0
  88. package/dist/src/planner/building/coercion.js +137 -0
  89. package/dist/src/planner/building/coercion.js.map +1 -0
  90. package/dist/src/planner/building/constraint-builder.d.ts.map +1 -1
  91. package/dist/src/planner/building/constraint-builder.js +5 -18
  92. package/dist/src/planner/building/constraint-builder.js.map +1 -1
  93. package/dist/src/planner/building/delete.d.ts.map +1 -1
  94. package/dist/src/planner/building/delete.js +10 -3
  95. package/dist/src/planner/building/delete.js.map +1 -1
  96. package/dist/src/planner/building/expression.d.ts.map +1 -1
  97. package/dist/src/planner/building/expression.js +13 -35
  98. package/dist/src/planner/building/expression.js.map +1 -1
  99. package/dist/src/planner/building/foreign-key-builder.d.ts +44 -1
  100. package/dist/src/planner/building/foreign-key-builder.d.ts.map +1 -1
  101. package/dist/src/planner/building/foreign-key-builder.js +54 -0
  102. package/dist/src/planner/building/foreign-key-builder.js.map +1 -1
  103. package/dist/src/planner/building/function-call.d.ts.map +1 -1
  104. package/dist/src/planner/building/function-call.js +8 -0
  105. package/dist/src/planner/building/function-call.js.map +1 -1
  106. package/dist/src/planner/building/insert.d.ts.map +1 -1
  107. package/dist/src/planner/building/insert.js +9 -12
  108. package/dist/src/planner/building/insert.js.map +1 -1
  109. package/dist/src/planner/building/select-compound.d.ts.map +1 -1
  110. package/dist/src/planner/building/select-compound.js +10 -6
  111. package/dist/src/planner/building/select-compound.js.map +1 -1
  112. package/dist/src/planner/building/update.d.ts.map +1 -1
  113. package/dist/src/planner/building/update.js +13 -4
  114. package/dist/src/planner/building/update.js.map +1 -1
  115. package/dist/src/planner/cost/conjunct-cost.d.ts +42 -0
  116. package/dist/src/planner/cost/conjunct-cost.d.ts.map +1 -0
  117. package/dist/src/planner/cost/conjunct-cost.js +62 -0
  118. package/dist/src/planner/cost/conjunct-cost.js.map +1 -0
  119. package/dist/src/planner/framework/pass.js +3 -3
  120. package/dist/src/planner/mutation/propagate.d.ts.map +1 -1
  121. package/dist/src/planner/mutation/propagate.js +1 -0
  122. package/dist/src/planner/mutation/propagate.js.map +1 -1
  123. package/dist/src/planner/nodes/async-gather-node.d.ts.map +1 -1
  124. package/dist/src/planner/nodes/async-gather-node.js +19 -8
  125. package/dist/src/planner/nodes/async-gather-node.js.map +1 -1
  126. package/dist/src/planner/nodes/bloom-join-node.d.ts +2 -0
  127. package/dist/src/planner/nodes/bloom-join-node.d.ts.map +1 -1
  128. package/dist/src/planner/nodes/bloom-join-node.js +10 -5
  129. package/dist/src/planner/nodes/bloom-join-node.js.map +1 -1
  130. package/dist/src/planner/nodes/dml-executor-node.d.ts +0 -9
  131. package/dist/src/planner/nodes/dml-executor-node.d.ts.map +1 -1
  132. package/dist/src/planner/nodes/dml-executor-node.js.map +1 -1
  133. package/dist/src/planner/nodes/join-node.d.ts +5 -0
  134. package/dist/src/planner/nodes/join-node.d.ts.map +1 -1
  135. package/dist/src/planner/nodes/join-node.js +8 -1
  136. package/dist/src/planner/nodes/join-node.js.map +1 -1
  137. package/dist/src/planner/nodes/join-utils.d.ts +57 -0
  138. package/dist/src/planner/nodes/join-utils.d.ts.map +1 -1
  139. package/dist/src/planner/nodes/join-utils.js +47 -0
  140. package/dist/src/planner/nodes/join-utils.js.map +1 -1
  141. package/dist/src/planner/nodes/key-set-semi-join-node.d.ts +87 -0
  142. package/dist/src/planner/nodes/key-set-semi-join-node.d.ts.map +1 -0
  143. package/dist/src/planner/nodes/key-set-semi-join-node.js +137 -0
  144. package/dist/src/planner/nodes/key-set-semi-join-node.js.map +1 -0
  145. package/dist/src/planner/nodes/merge-join-node.d.ts +2 -0
  146. package/dist/src/planner/nodes/merge-join-node.d.ts.map +1 -1
  147. package/dist/src/planner/nodes/merge-join-node.js +14 -6
  148. package/dist/src/planner/nodes/merge-join-node.js.map +1 -1
  149. package/dist/src/planner/nodes/plan-node-type.d.ts +1 -0
  150. package/dist/src/planner/nodes/plan-node-type.d.ts.map +1 -1
  151. package/dist/src/planner/nodes/plan-node-type.js +1 -0
  152. package/dist/src/planner/nodes/plan-node-type.js.map +1 -1
  153. package/dist/src/planner/nodes/scalar.d.ts.map +1 -1
  154. package/dist/src/planner/nodes/scalar.js +26 -3
  155. package/dist/src/planner/nodes/scalar.js.map +1 -1
  156. package/dist/src/planner/nodes/set-operation-node.d.ts +68 -16
  157. package/dist/src/planner/nodes/set-operation-node.d.ts.map +1 -1
  158. package/dist/src/planner/nodes/set-operation-node.js +147 -26
  159. package/dist/src/planner/nodes/set-operation-node.js.map +1 -1
  160. package/dist/src/planner/nodes/table-access-nodes.d.ts +19 -1
  161. package/dist/src/planner/nodes/table-access-nodes.d.ts.map +1 -1
  162. package/dist/src/planner/nodes/table-access-nodes.js +13 -2
  163. package/dist/src/planner/nodes/table-access-nodes.js.map +1 -1
  164. package/dist/src/planner/optimizer.d.ts.map +1 -1
  165. package/dist/src/planner/optimizer.js +54 -12
  166. package/dist/src/planner/optimizer.js.map +1 -1
  167. package/dist/src/planner/rules/access/rule-key-set-seek.d.ts +68 -0
  168. package/dist/src/planner/rules/access/rule-key-set-seek.d.ts.map +1 -0
  169. package/dist/src/planner/rules/access/rule-key-set-seek.js +408 -0
  170. package/dist/src/planner/rules/access/rule-key-set-seek.js.map +1 -0
  171. package/dist/src/planner/rules/access/rule-monotonic-range-access.d.ts.map +1 -1
  172. package/dist/src/planner/rules/access/rule-monotonic-range-access.js +2 -2
  173. package/dist/src/planner/rules/access/rule-monotonic-range-access.js.map +1 -1
  174. package/dist/src/planner/rules/access/rule-select-access-path.d.ts +27 -0
  175. package/dist/src/planner/rules/access/rule-select-access-path.d.ts.map +1 -1
  176. package/dist/src/planner/rules/access/rule-select-access-path.js +74 -29
  177. package/dist/src/planner/rules/access/rule-select-access-path.js.map +1 -1
  178. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.d.ts.map +1 -1
  179. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js +23 -4
  180. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js.map +1 -1
  181. package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.d.ts +10 -8
  182. package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.d.ts.map +1 -1
  183. package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.js +10 -8
  184. package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.js.map +1 -1
  185. package/dist/src/planner/rules/join/equi-pair-extractor.d.ts +67 -41
  186. package/dist/src/planner/rules/join/equi-pair-extractor.d.ts.map +1 -1
  187. package/dist/src/planner/rules/join/equi-pair-extractor.js +72 -36
  188. package/dist/src/planner/rules/join/equi-pair-extractor.js.map +1 -1
  189. package/dist/src/planner/rules/join/rule-join-physical-selection.d.ts.map +1 -1
  190. package/dist/src/planner/rules/join/rule-join-physical-selection.js +14 -2
  191. package/dist/src/planner/rules/join/rule-join-physical-selection.js.map +1 -1
  192. package/dist/src/planner/rules/join/rule-monotonic-merge-join.d.ts.map +1 -1
  193. package/dist/src/planner/rules/join/rule-monotonic-merge-join.js +11 -1
  194. package/dist/src/planner/rules/join/rule-monotonic-merge-join.js.map +1 -1
  195. package/dist/src/planner/rules/predicate/rule-empty-relation-folding.d.ts +1 -1
  196. package/dist/src/planner/rules/predicate/rule-empty-relation-folding.js +1 -1
  197. package/dist/src/planner/rules/predicate/rule-filter-conjunct-ordering.d.ts +30 -0
  198. package/dist/src/planner/rules/predicate/rule-filter-conjunct-ordering.d.ts.map +1 -0
  199. package/dist/src/planner/rules/predicate/rule-filter-conjunct-ordering.js +63 -0
  200. package/dist/src/planner/rules/predicate/rule-filter-conjunct-ordering.js.map +1 -0
  201. package/dist/src/planner/rules/predicate/rule-filter-selectivity.d.ts +17 -1
  202. package/dist/src/planner/rules/predicate/rule-filter-selectivity.d.ts.map +1 -1
  203. package/dist/src/planner/rules/predicate/rule-filter-selectivity.js +206 -12
  204. package/dist/src/planner/rules/predicate/rule-filter-selectivity.js.map +1 -1
  205. package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.js +8 -1
  206. package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.js.map +1 -1
  207. package/dist/src/planner/rules/predicate/rule-predicate-pushdown.d.ts +3 -1
  208. package/dist/src/planner/rules/predicate/rule-predicate-pushdown.d.ts.map +1 -1
  209. package/dist/src/planner/rules/predicate/rule-predicate-pushdown.js +13 -1
  210. package/dist/src/planner/rules/predicate/rule-predicate-pushdown.js.map +1 -1
  211. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.d.ts.map +1 -1
  212. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js +18 -3
  213. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js.map +1 -1
  214. package/dist/src/planner/rules/shared/index-style-context.d.ts +17 -0
  215. package/dist/src/planner/rules/shared/index-style-context.d.ts.map +1 -1
  216. package/dist/src/planner/rules/shared/index-style-context.js +7 -0
  217. package/dist/src/planner/rules/shared/index-style-context.js.map +1 -1
  218. package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.js +4 -0
  219. package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.js.map +1 -1
  220. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.d.ts +24 -6
  221. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.d.ts.map +1 -1
  222. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js +175 -60
  223. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js.map +1 -1
  224. package/dist/src/planner/stats/catalog-stats.d.ts +52 -0
  225. package/dist/src/planner/stats/catalog-stats.d.ts.map +1 -1
  226. package/dist/src/planner/stats/catalog-stats.js +151 -5
  227. package/dist/src/planner/stats/catalog-stats.js.map +1 -1
  228. package/dist/src/planner/stats/index.d.ts +15 -0
  229. package/dist/src/planner/stats/index.d.ts.map +1 -1
  230. package/dist/src/planner/stats/index.js.map +1 -1
  231. package/dist/src/planner/stats/selectivity-combine.d.ts +53 -0
  232. package/dist/src/planner/stats/selectivity-combine.d.ts.map +1 -0
  233. package/dist/src/planner/stats/selectivity-combine.js +75 -0
  234. package/dist/src/planner/stats/selectivity-combine.js.map +1 -0
  235. package/dist/src/planner/util/column-origins.d.ts +58 -0
  236. package/dist/src/planner/util/column-origins.d.ts.map +1 -0
  237. package/dist/src/planner/util/column-origins.js +90 -0
  238. package/dist/src/planner/util/column-origins.js.map +1 -0
  239. package/dist/src/runtime/deferred-constraint-queue.d.ts +40 -0
  240. package/dist/src/runtime/deferred-constraint-queue.d.ts.map +1 -1
  241. package/dist/src/runtime/deferred-constraint-queue.js +91 -0
  242. package/dist/src/runtime/deferred-constraint-queue.js.map +1 -1
  243. package/dist/src/runtime/emit/aggregate-setup.d.ts +49 -0
  244. package/dist/src/runtime/emit/aggregate-setup.d.ts.map +1 -0
  245. package/dist/src/runtime/emit/aggregate-setup.js +99 -0
  246. package/dist/src/runtime/emit/aggregate-setup.js.map +1 -0
  247. package/dist/src/runtime/emit/aggregate.d.ts.map +1 -1
  248. package/dist/src/runtime/emit/aggregate.js +4 -69
  249. package/dist/src/runtime/emit/aggregate.js.map +1 -1
  250. package/dist/src/runtime/emit/alter-table.d.ts.map +1 -1
  251. package/dist/src/runtime/emit/alter-table.js +354 -165
  252. package/dist/src/runtime/emit/alter-table.js.map +1 -1
  253. package/dist/src/runtime/emit/asof-scan.d.ts.map +1 -1
  254. package/dist/src/runtime/emit/asof-scan.js +11 -0
  255. package/dist/src/runtime/emit/asof-scan.js.map +1 -1
  256. package/dist/src/runtime/emit/between.d.ts.map +1 -1
  257. package/dist/src/runtime/emit/between.js +7 -12
  258. package/dist/src/runtime/emit/between.js.map +1 -1
  259. package/dist/src/runtime/emit/binary.d.ts.map +1 -1
  260. package/dist/src/runtime/emit/binary.js +24 -25
  261. package/dist/src/runtime/emit/binary.js.map +1 -1
  262. package/dist/src/runtime/emit/bloom-join.d.ts.map +1 -1
  263. package/dist/src/runtime/emit/bloom-join.js +8 -16
  264. package/dist/src/runtime/emit/bloom-join.js.map +1 -1
  265. package/dist/src/runtime/emit/case.d.ts.map +1 -1
  266. package/dist/src/runtime/emit/case.js +40 -6
  267. package/dist/src/runtime/emit/case.js.map +1 -1
  268. package/dist/src/runtime/emit/cast.d.ts.map +1 -1
  269. package/dist/src/runtime/emit/cast.js +10 -23
  270. package/dist/src/runtime/emit/cast.js.map +1 -1
  271. package/dist/src/runtime/emit/constraint-check.d.ts.map +1 -1
  272. package/dist/src/runtime/emit/constraint-check.js +72 -51
  273. package/dist/src/runtime/emit/constraint-check.js.map +1 -1
  274. package/dist/src/runtime/emit/create-view.d.ts.map +1 -1
  275. package/dist/src/runtime/emit/create-view.js +6 -0
  276. package/dist/src/runtime/emit/create-view.js.map +1 -1
  277. package/dist/src/runtime/emit/distinct.d.ts.map +1 -1
  278. package/dist/src/runtime/emit/distinct.js +6 -3
  279. package/dist/src/runtime/emit/distinct.js.map +1 -1
  280. package/dist/src/runtime/emit/dml-executor.d.ts.map +1 -1
  281. package/dist/src/runtime/emit/dml-executor.js +188 -84
  282. package/dist/src/runtime/emit/dml-executor.js.map +1 -1
  283. package/dist/src/runtime/emit/filter.d.ts +18 -0
  284. package/dist/src/runtime/emit/filter.d.ts.map +1 -1
  285. package/dist/src/runtime/emit/filter.js +52 -10
  286. package/dist/src/runtime/emit/filter.js.map +1 -1
  287. package/dist/src/runtime/emit/hash-aggregate.d.ts.map +1 -1
  288. package/dist/src/runtime/emit/hash-aggregate.js +16 -65
  289. package/dist/src/runtime/emit/hash-aggregate.js.map +1 -1
  290. package/dist/src/runtime/emit/insert.d.ts.map +1 -1
  291. package/dist/src/runtime/emit/insert.js +18 -7
  292. package/dist/src/runtime/emit/insert.js.map +1 -1
  293. package/dist/src/runtime/emit/join-key-extractor.d.ts +46 -0
  294. package/dist/src/runtime/emit/join-key-extractor.d.ts.map +1 -0
  295. package/dist/src/runtime/emit/join-key-extractor.js +60 -0
  296. package/dist/src/runtime/emit/join-key-extractor.js.map +1 -0
  297. package/dist/src/runtime/emit/join.d.ts.map +1 -1
  298. package/dist/src/runtime/emit/join.js +37 -11
  299. package/dist/src/runtime/emit/join.js.map +1 -1
  300. package/dist/src/runtime/emit/key-set-semi-join.d.ts +29 -0
  301. package/dist/src/runtime/emit/key-set-semi-join.d.ts.map +1 -0
  302. package/dist/src/runtime/emit/key-set-semi-join.js +143 -0
  303. package/dist/src/runtime/emit/key-set-semi-join.js.map +1 -0
  304. package/dist/src/runtime/emit/materialized-view-helpers.d.ts +14 -1
  305. package/dist/src/runtime/emit/materialized-view-helpers.d.ts.map +1 -1
  306. package/dist/src/runtime/emit/materialized-view-helpers.js +33 -3
  307. package/dist/src/runtime/emit/materialized-view-helpers.js.map +1 -1
  308. package/dist/src/runtime/emit/materialized-view.d.ts.map +1 -1
  309. package/dist/src/runtime/emit/materialized-view.js +47 -6
  310. package/dist/src/runtime/emit/materialized-view.js.map +1 -1
  311. package/dist/src/runtime/emit/merge-join.d.ts.map +1 -1
  312. package/dist/src/runtime/emit/merge-join.js +32 -15
  313. package/dist/src/runtime/emit/merge-join.js.map +1 -1
  314. package/dist/src/runtime/emit/operand-comparator.d.ts +40 -0
  315. package/dist/src/runtime/emit/operand-comparator.d.ts.map +1 -0
  316. package/dist/src/runtime/emit/operand-comparator.js +59 -0
  317. package/dist/src/runtime/emit/operand-comparator.js.map +1 -0
  318. package/dist/src/runtime/emit/scan.d.ts.map +1 -1
  319. package/dist/src/runtime/emit/scan.js +11 -2
  320. package/dist/src/runtime/emit/scan.js.map +1 -1
  321. package/dist/src/runtime/emit/set-object-tags.d.ts +8 -0
  322. package/dist/src/runtime/emit/set-object-tags.d.ts.map +1 -1
  323. package/dist/src/runtime/emit/set-object-tags.js +8 -0
  324. package/dist/src/runtime/emit/set-object-tags.js.map +1 -1
  325. package/dist/src/runtime/emit/set-operation.d.ts.map +1 -1
  326. package/dist/src/runtime/emit/set-operation.js +7 -4
  327. package/dist/src/runtime/emit/set-operation.js.map +1 -1
  328. package/dist/src/runtime/emit/sort.d.ts.map +1 -1
  329. package/dist/src/runtime/emit/sort.js +5 -2
  330. package/dist/src/runtime/emit/sort.js.map +1 -1
  331. package/dist/src/runtime/emit/subquery.d.ts.map +1 -1
  332. package/dist/src/runtime/emit/subquery.js +113 -13
  333. package/dist/src/runtime/emit/subquery.js.map +1 -1
  334. package/dist/src/runtime/emit/temporal-arithmetic.d.ts +7 -0
  335. package/dist/src/runtime/emit/temporal-arithmetic.d.ts.map +1 -1
  336. package/dist/src/runtime/emit/temporal-arithmetic.js +15 -7
  337. package/dist/src/runtime/emit/temporal-arithmetic.js.map +1 -1
  338. package/dist/src/runtime/emit/update.d.ts.map +1 -1
  339. package/dist/src/runtime/emit/update.js +44 -3
  340. package/dist/src/runtime/emit/update.js.map +1 -1
  341. package/dist/src/runtime/emit/window.d.ts.map +1 -1
  342. package/dist/src/runtime/emit/window.js +72 -55
  343. package/dist/src/runtime/emit/window.js.map +1 -1
  344. package/dist/src/runtime/foreign-key-actions.d.ts +36 -1
  345. package/dist/src/runtime/foreign-key-actions.d.ts.map +1 -1
  346. package/dist/src/runtime/foreign-key-actions.js +238 -116
  347. package/dist/src/runtime/foreign-key-actions.js.map +1 -1
  348. package/dist/src/runtime/parallel-driver.d.ts +10 -2
  349. package/dist/src/runtime/parallel-driver.d.ts.map +1 -1
  350. package/dist/src/runtime/parallel-driver.js +12 -2
  351. package/dist/src/runtime/parallel-driver.js.map +1 -1
  352. package/dist/src/runtime/register.d.ts.map +1 -1
  353. package/dist/src/runtime/register.js +2 -0
  354. package/dist/src/runtime/register.js.map +1 -1
  355. package/dist/src/runtime/types.d.ts +25 -0
  356. package/dist/src/runtime/types.d.ts.map +1 -1
  357. package/dist/src/runtime/types.js.map +1 -1
  358. package/dist/src/schema/catalog-persistability.d.ts +58 -0
  359. package/dist/src/schema/catalog-persistability.d.ts.map +1 -0
  360. package/dist/src/schema/catalog-persistability.js +87 -0
  361. package/dist/src/schema/catalog-persistability.js.map +1 -0
  362. package/dist/src/schema/catalog.d.ts +17 -1
  363. package/dist/src/schema/catalog.d.ts.map +1 -1
  364. package/dist/src/schema/catalog.js +25 -3
  365. package/dist/src/schema/catalog.js.map +1 -1
  366. package/dist/src/schema/constraint-builder.d.ts +20 -22
  367. package/dist/src/schema/constraint-builder.d.ts.map +1 -1
  368. package/dist/src/schema/constraint-builder.js +43 -36
  369. package/dist/src/schema/constraint-builder.js.map +1 -1
  370. package/dist/src/schema/ddl-generator.d.ts +23 -0
  371. package/dist/src/schema/ddl-generator.d.ts.map +1 -1
  372. package/dist/src/schema/ddl-generator.js +27 -0
  373. package/dist/src/schema/ddl-generator.js.map +1 -1
  374. package/dist/src/schema/function.d.ts +39 -1
  375. package/dist/src/schema/function.d.ts.map +1 -1
  376. package/dist/src/schema/function.js.map +1 -1
  377. package/dist/src/schema/manager.d.ts +28 -0
  378. package/dist/src/schema/manager.d.ts.map +1 -1
  379. package/dist/src/schema/manager.js +82 -14
  380. package/dist/src/schema/manager.js.map +1 -1
  381. package/dist/src/schema/schema-differ.d.ts.map +1 -1
  382. package/dist/src/schema/schema-differ.js +51 -12
  383. package/dist/src/schema/schema-differ.js.map +1 -1
  384. package/dist/src/schema/unique-enforcement.d.ts +31 -0
  385. package/dist/src/schema/unique-enforcement.d.ts.map +1 -1
  386. package/dist/src/schema/unique-enforcement.js +37 -1
  387. package/dist/src/schema/unique-enforcement.js.map +1 -1
  388. package/dist/src/schema/window-function.d.ts +30 -10
  389. package/dist/src/schema/window-function.d.ts.map +1 -1
  390. package/dist/src/schema/window-function.js +20 -16
  391. package/dist/src/schema/window-function.js.map +1 -1
  392. package/dist/src/types/builtin-types.d.ts.map +1 -1
  393. package/dist/src/types/builtin-types.js +30 -4
  394. package/dist/src/types/builtin-types.js.map +1 -1
  395. package/dist/src/types/cast-semantics.d.ts +43 -0
  396. package/dist/src/types/cast-semantics.d.ts.map +1 -0
  397. package/dist/src/types/cast-semantics.js +59 -0
  398. package/dist/src/types/cast-semantics.js.map +1 -0
  399. package/dist/src/types/index.d.ts +1 -1
  400. package/dist/src/types/index.d.ts.map +1 -1
  401. package/dist/src/types/index.js +1 -1
  402. package/dist/src/types/index.js.map +1 -1
  403. package/dist/src/types/json-type.d.ts.map +1 -1
  404. package/dist/src/types/json-type.js +31 -10
  405. package/dist/src/types/json-type.js.map +1 -1
  406. package/dist/src/types/logical-type.d.ts +22 -0
  407. package/dist/src/types/logical-type.d.ts.map +1 -1
  408. package/dist/src/types/logical-type.js.map +1 -1
  409. package/dist/src/types/temporal-types.d.ts.map +1 -1
  410. package/dist/src/types/temporal-types.js +33 -11
  411. package/dist/src/types/temporal-types.js.map +1 -1
  412. package/dist/src/types/validation.d.ts +39 -1
  413. package/dist/src/types/validation.d.ts.map +1 -1
  414. package/dist/src/types/validation.js +60 -0
  415. package/dist/src/types/validation.js.map +1 -1
  416. package/dist/src/util/ast-spine-clone.d.ts +23 -0
  417. package/dist/src/util/ast-spine-clone.d.ts.map +1 -0
  418. package/dist/src/util/ast-spine-clone.js +41 -0
  419. package/dist/src/util/ast-spine-clone.js.map +1 -0
  420. package/dist/src/util/coercion.d.ts +13 -0
  421. package/dist/src/util/coercion.d.ts.map +1 -1
  422. package/dist/src/util/coercion.js +10 -1
  423. package/dist/src/util/coercion.js.map +1 -1
  424. package/dist/src/util/comparison.d.ts +114 -0
  425. package/dist/src/util/comparison.d.ts.map +1 -1
  426. package/dist/src/util/comparison.js +187 -27
  427. package/dist/src/util/comparison.js.map +1 -1
  428. package/dist/src/vtab/best-access-plan.d.ts +87 -0
  429. package/dist/src/vtab/best-access-plan.d.ts.map +1 -1
  430. package/dist/src/vtab/best-access-plan.js +87 -0
  431. package/dist/src/vtab/best-access-plan.js.map +1 -1
  432. package/dist/src/vtab/manifest.d.ts +7 -0
  433. package/dist/src/vtab/manifest.d.ts.map +1 -1
  434. package/dist/src/vtab/memory/connection.d.ts +14 -1
  435. package/dist/src/vtab/memory/connection.d.ts.map +1 -1
  436. package/dist/src/vtab/memory/connection.js +20 -2
  437. package/dist/src/vtab/memory/connection.js.map +1 -1
  438. package/dist/src/vtab/memory/layer/base.d.ts +12 -5
  439. package/dist/src/vtab/memory/layer/base.d.ts.map +1 -1
  440. package/dist/src/vtab/memory/layer/base.js +20 -9
  441. package/dist/src/vtab/memory/layer/base.js.map +1 -1
  442. package/dist/src/vtab/memory/layer/connection.d.ts +17 -0
  443. package/dist/src/vtab/memory/layer/connection.d.ts.map +1 -1
  444. package/dist/src/vtab/memory/layer/connection.js +26 -0
  445. package/dist/src/vtab/memory/layer/connection.js.map +1 -1
  446. package/dist/src/vtab/memory/layer/manager.d.ts +156 -31
  447. package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
  448. package/dist/src/vtab/memory/layer/manager.js +695 -158
  449. package/dist/src/vtab/memory/layer/manager.js.map +1 -1
  450. package/dist/src/vtab/memory/layer/plan-filter.d.ts +41 -20
  451. package/dist/src/vtab/memory/layer/plan-filter.d.ts.map +1 -1
  452. package/dist/src/vtab/memory/layer/plan-filter.js +57 -27
  453. package/dist/src/vtab/memory/layer/plan-filter.js.map +1 -1
  454. package/dist/src/vtab/memory/layer/row-convert.d.ts +21 -0
  455. package/dist/src/vtab/memory/layer/row-convert.d.ts.map +1 -0
  456. package/dist/src/vtab/memory/layer/row-convert.js +30 -0
  457. package/dist/src/vtab/memory/layer/row-convert.js.map +1 -0
  458. package/dist/src/vtab/memory/layer/scan-layer.d.ts +6 -3
  459. package/dist/src/vtab/memory/layer/scan-layer.d.ts.map +1 -1
  460. package/dist/src/vtab/memory/layer/scan-layer.js +48 -34
  461. package/dist/src/vtab/memory/layer/scan-layer.js.map +1 -1
  462. package/dist/src/vtab/memory/layer/transaction.d.ts +142 -19
  463. package/dist/src/vtab/memory/layer/transaction.d.ts.map +1 -1
  464. package/dist/src/vtab/memory/layer/transaction.js +237 -73
  465. package/dist/src/vtab/memory/layer/transaction.js.map +1 -1
  466. package/dist/src/vtab/memory/module.d.ts +14 -2
  467. package/dist/src/vtab/memory/module.d.ts.map +1 -1
  468. package/dist/src/vtab/memory/module.js +60 -41
  469. package/dist/src/vtab/memory/module.js.map +1 -1
  470. package/dist/src/vtab/memory/table.d.ts +1 -1
  471. package/dist/src/vtab/memory/table.d.ts.map +1 -1
  472. package/dist/src/vtab/memory/table.js +12 -4
  473. package/dist/src/vtab/memory/table.js.map +1 -1
  474. package/dist/src/vtab/memory/types.d.ts +27 -1
  475. package/dist/src/vtab/memory/types.d.ts.map +1 -1
  476. package/dist/src/vtab/memory/types.js +16 -1
  477. package/dist/src/vtab/memory/types.js.map +1 -1
  478. package/dist/src/vtab/memory/utils/primary-key.d.ts +9 -0
  479. package/dist/src/vtab/memory/utils/primary-key.d.ts.map +1 -1
  480. package/dist/src/vtab/memory/utils/primary-key.js +11 -0
  481. package/dist/src/vtab/memory/utils/primary-key.js.map +1 -1
  482. package/dist/src/vtab/module.d.ts +63 -0
  483. package/dist/src/vtab/module.d.ts.map +1 -1
  484. package/dist/src/vtab/table.d.ts +33 -7
  485. package/dist/src/vtab/table.d.ts.map +1 -1
  486. package/dist/src/vtab/table.js.map +1 -1
  487. package/package.json +3 -3
  488. package/dist/src/planner/rules/cache/rule-in-subquery-cache.d.ts +0 -26
  489. package/dist/src/planner/rules/cache/rule-in-subquery-cache.d.ts.map +0 -1
  490. package/dist/src/planner/rules/cache/rule-in-subquery-cache.js +0 -66
  491. package/dist/src/planner/rules/cache/rule-in-subquery-cache.js.map +0 -1
@@ -1,4 +1,5 @@
1
1
  import { buildColumnIndexMap, columnDefToSchema, resolvePkDefaultConflict, resolveNamedConstraintClass, validateCollationForType } from '../../../schema/table.js';
2
+ import { keyParts } from '../types.js';
2
3
  import { BTree } from 'inheritree';
3
4
  import { StatusCode } from '../../../common/types.js';
4
5
  import { BaseLayer, iteratePrimaryRows, populateIndexFromRows, populateIndexFromRowsAsync } from './base.js';
@@ -7,20 +8,84 @@ import { MemoryTableConnection } from './connection.js';
7
8
  import { MemoryVirtualTableConnection } from '../connection.js';
8
9
  import { QuereusError } from '../../../common/errors.js';
9
10
  import { ConflictResolution } from '../../../common/constants.js';
10
- import { buildUniqueConstraintSchema, buildForeignKeyConstraintSchema, buildCheckConstraintSchema, validateForeignKeyOverExistingRows, maintainedTableUniqueViolationError } from '../../../schema/constraint-builder.js';
11
- import { indexEnforcesUnique, uniqueEnforcementCollations } from '../../../schema/unique-enforcement.js';
12
- import { compareSqlValues, compareSqlValuesFast, rowsValueIdentical, normalizeCollationName } from '../../../util/comparison.js';
11
+ import { buildUniqueConstraintSchema, buildForeignKeyConstraintSchema, buildCheckConstraintSchema, validateForeignKeyOverExistingRows, maintainedTableUniqueViolationError, formatKeyValue } from '../../../schema/constraint-builder.js';
12
+ import { indexEnforcesUnique, uniqueEnforcementCollations, uniqueEnforcementComparators } from '../../../schema/unique-enforcement.js';
13
+ import { generateIndexDDL, generateDropIndexDDL } from '../../../schema/ddl-generator.js';
14
+ import { compareSqlValues, rowsValueIdentical, normalizeCollationName, comparisonSemanticsDiffer } from '../../../util/comparison.js';
13
15
  import { scanLayer as scanLayerImpl } from './scan-layer.js';
14
- import { createPrimaryKeyFunctions, buildPrimaryKeyFromValues } from '../utils/primary-key.js';
16
+ import { createPrimaryKeyFunctions, buildPrimaryKeyFromValues, primaryKeyArity } from '../utils/primary-key.js';
15
17
  import { createMemoryTableLoggers } from '../utils/logging.js';
16
18
  import { tryFoldLiteral } from '../../../parser/utils.js';
17
- import { validateAndParse } from '../../../types/validation.js';
19
+ import { validateAndParse, coerceRowToSchema } from '../../../types/validation.js';
18
20
  import { inferType } from '../../../types/registry.js';
19
21
  import { compilePredicate } from '../utils/predicate.js';
20
22
  import { renameColumnInIndexPredicates } from '../../../schema/rename-rewriter.js';
21
23
  import { MemoryIndex } from '../index.js';
24
+ import { convertRowAtIndex, mapRows, mapRowsAsync } from './row-convert.js';
22
25
  let tableManagerCounter = 0;
23
26
  const logger = createMemoryTableLoggers('layer:manager');
27
+ /** A copy of `items` with `value` inserted at `at` (`at === items.length` ⇒ append). */
28
+ function insertValueAt(items, at, value) {
29
+ const out = items.slice();
30
+ out.splice(at, 0, value);
31
+ return out;
32
+ }
33
+ /** Index of a column after a new one is inserted at `at`. */
34
+ const shiftForInsert = (index, at) => index >= at ? index + 1 : index;
35
+ /**
36
+ * The index-bearing fields of `schema` renumbered for a column inserted at `at`, to be
37
+ * spread over the schema alongside the new `columns` array. Only reached when `at` is a
38
+ * real insert point (an append leaves every existing index valid), and the mirror image
39
+ * of the shift `dropColumn` performs — except that an insert can never empty an index or
40
+ * a constraint, so none of `dropColumn`'s prune/filter logic has a counterpart here.
41
+ *
42
+ * `checkConstraints` and the manager's implicit covering structures are name/AST-keyed
43
+ * and need nothing. `statistics.columnStats` is keyed by column name. `columnIndexMap` is
44
+ * rebuilt from the new column list by the caller.
45
+ *
46
+ * A foreign key's `columns` are indices into THIS table and always shift. Its
47
+ * `referencedColumns` are NOT touched: every constructor of a foreign key leaves that
48
+ * array empty (`constraint-builder.ts`, `schema/manager.ts`) and enforcement resolves the
49
+ * parent indices on demand from `referencedColumnNames` against the parent's CURRENT
50
+ * schema (`resolveReferencedColumns`) — so even a self-referential key needs no shift here.
51
+ *
52
+ * `derivation.logicalKey` / `derivation.ordering` also hold indices into this table and are
53
+ * NOT shifted, in either direction: they exist only on a maintained table (a materialized
54
+ * view's backing table), whose shape is defined by its body, and the emitter rejects every
55
+ * structural ALTER on one before dispatch.
56
+ */
57
+ function shiftSchemaIndicesForInsert(schema, at) {
58
+ const shift = (index) => shiftForInsert(index, at);
59
+ const primaryKeyDefinition = schema.primaryKeyDefinition.map(def => ({ ...def, index: shift(def.index) }));
60
+ const indexes = schema.indexes?.map(idx => ({
61
+ ...idx,
62
+ columns: idx.columns.map(ic => ({ ...ic, index: shift(ic.index) })),
63
+ }));
64
+ const uniqueConstraints = schema.uniqueConstraints?.map(uc => ({
65
+ ...uc,
66
+ columns: Object.freeze(uc.columns.map(shift)),
67
+ }));
68
+ const foreignKeys = schema.foreignKeys?.map(fk => ({
69
+ ...fk,
70
+ columns: Object.freeze(fk.columns.map(shift)),
71
+ }));
72
+ // Generated-column bookkeeping is keyed BY column index and holds column indices.
73
+ // Nothing inside the memory module reads it — the engine recomputes the graph after
74
+ // its own ADD COLUMN — but a wrapper driving this API directly gets the schema back
75
+ // verbatim, so leaving it unshifted would hand out a schema whose generated-column
76
+ // edges point at the wrong columns.
77
+ const generatedColumnDependencies = schema.generatedColumnDependencies
78
+ && new Map(Array.from(schema.generatedColumnDependencies, ([col, deps]) => [shift(col), deps.map(shift)]));
79
+ const generatedColumnTopoOrder = schema.generatedColumnTopoOrder?.map(shift);
80
+ return {
81
+ primaryKeyDefinition: Object.freeze(primaryKeyDefinition),
82
+ ...(indexes && { indexes: Object.freeze(indexes) }),
83
+ ...(uniqueConstraints && { uniqueConstraints: Object.freeze(uniqueConstraints) }),
84
+ ...(foreignKeys && { foreignKeys: Object.freeze(foreignKeys) }),
85
+ ...(generatedColumnDependencies && { generatedColumnDependencies }),
86
+ ...(generatedColumnTopoOrder && { generatedColumnTopoOrder: Object.freeze(generatedColumnTopoOrder) }),
87
+ };
88
+ }
24
89
  export class MemoryTableManager {
25
90
  managerId;
26
91
  db;
@@ -305,10 +370,10 @@ export class MemoryTableManager {
305
370
  // `baseLayer` by ALTER TABLE) or carries an out-of-date schema, leave
306
371
  // it alone — committing such a layer would supplant the schema-aware
307
372
  // committed head with stale data.
373
+ let refusedSnapshotWrap = false;
308
374
  if (!connection.pendingTransactionLayer
309
375
  && connection.readLayer !== this._currentCommittedLayer
310
- && connection.readLayer instanceof TransactionLayer
311
- && connection.readLayer.getSchema() === this.tableSchema) {
376
+ && connection.readLayer instanceof TransactionLayer) {
312
377
  let walker = connection.readLayer.getParent();
313
378
  let isAhead = false;
314
379
  while (walker) {
@@ -319,17 +384,32 @@ export class MemoryTableManager {
319
384
  walker = walker.getParent();
320
385
  }
321
386
  if (isAhead) {
322
- connection.pendingTransactionLayer = new TransactionLayer(connection.readLayer);
323
- if (this.eventEmitter?.hasDataListeners?.()) {
324
- connection.pendingTransactionLayer.enableChangeTracking();
387
+ if (connection.readLayer.getSchema() === this.tableSchema) {
388
+ connection.pendingTransactionLayer = new TransactionLayer(connection.readLayer);
389
+ if (this.eventEmitter?.hasDataListeners?.()) {
390
+ connection.pendingTransactionLayer.enableChangeTracking();
391
+ }
392
+ }
393
+ else {
394
+ // A snapshot AHEAD of the head whose schema object nonetheless predates the
395
+ // manager's: some schema-mutating arm failed to hand its new TableSchema to
396
+ // the open layers (`adoptSchemaOnOpenLayers` and friends). Refusing the wrap
397
+ // is still the safe call, but it discards every row the transaction staged,
398
+ // so flag it for the warning below rather than let COMMIT report success over
399
+ // a silent data loss.
400
+ refusedSnapshotWrap = true;
325
401
  }
326
402
  }
327
403
  }
328
404
  const pendingLayer = connection.pendingTransactionLayer;
329
405
  if (!pendingLayer) {
406
+ if (refusedSnapshotWrap) {
407
+ logger.warn('Commit Transaction', this._tableName, 'Discarding staged rows: savepoint snapshot carries a schema object older than the table\'s. A schema-mutating arm did not adopt its new schema on the open transaction layers.', { schemaName: this.schemaName, connectionId: connection.connectionId });
408
+ }
330
409
  // No pending — refresh readLayer to the current committed head so a
331
410
  // stale ancestor (post-schema-change) doesn't leak into the next
332
- // statement's view.
411
+ // statement's view. (The ordinary read-only / no-writes commit lands
412
+ // here too, which is why the warning above is gated.)
333
413
  connection.readLayer = this._currentCommittedLayer;
334
414
  return;
335
415
  }
@@ -382,6 +462,15 @@ export class MemoryTableManager {
382
462
  // since the pending layer forked. Replaying stale-schema rows onto
383
463
  // the new-schema head is unsafe — abort with BUSY rather than corrupt
384
464
  // the committed head (mirrors the stale-ancestor caution above).
465
+ //
466
+ // NOTE: `adoptSchemaOnOpenLayers` only walks the DDL connection's own chain,
467
+ // so a SIBLING connection's pending layer keeps its creation-time schema
468
+ // object even after a metadata-only change like `RENAME TO` that moves no
469
+ // rows — which would make this arm fire spuriously. Not reachable from SQL
470
+ // today: `renameTable` never calls `ensureSchemaChangeSafety`, and a single
471
+ // Database runs one transaction at a time. If sibling connections ever hold
472
+ // concurrent pending layers over one manager, compare column shape here
473
+ // rather than schema-object identity.
385
474
  if (pendingLayer.getSchema() !== this.tableSchema) {
386
475
  connection.pendingTransactionLayer = null;
387
476
  connection.clearSavepoints();
@@ -415,12 +504,21 @@ export class MemoryTableManager {
415
504
  connection.clearSavepoints();
416
505
  // Emit data change events after successful commit
417
506
  if (changes.length > 0 && this.eventEmitter?.emitDataChange) {
507
+ // The event contract carries the PK as a component array. Derive the stored
508
+ // key's shape from the PK arity, never from `Array.isArray` — a JSON PK whose
509
+ // value is a document like `[1]` is a SCALAR key that happens to be a JS
510
+ // array, and sniffing it would emit `[1]` where the contract wants `[[1]]`.
511
+ // NOTE: on the tuple path this hands listeners the STORED key array itself
512
+ // (`VTableDataChangeEvent.key` is mutable `SqlValue[]`, as it always was).
513
+ // If a listener is ever seen mutating `event.key`, copy here — an in-place
514
+ // edit would re-order the primary tree under the comparator.
515
+ const eventKeyIsTuple = primaryKeyArity(this.tableSchema) !== 1;
418
516
  for (const change of changes) {
419
517
  const event = {
420
518
  type: change.type,
421
519
  schemaName: this.schemaName,
422
520
  tableName: this._tableName,
423
- key: Array.isArray(change.pk) ? change.pk : [change.pk],
521
+ key: keyParts(change.pk, eventKeyIsTuple),
424
522
  oldRow: change.oldRow,
425
523
  newRow: change.newRow,
426
524
  };
@@ -640,7 +738,7 @@ export class MemoryTableManager {
640
738
  const result = primaryTree.get(key);
641
739
  return result === undefined ? null : result;
642
740
  }
643
- async performMutation(connection, operation, values, oldKeyValues, onConflict) {
741
+ async performMutation(connection, operation, values, oldKeyValues, onConflict, preCoerced) {
644
742
  this.validateMutationPermissions(operation);
645
743
  const wasExplicitTransaction = connection.explicitTransaction;
646
744
  this.ensureTransactionLayer(connection);
@@ -648,10 +746,10 @@ export class MemoryTableManager {
648
746
  let result;
649
747
  switch (operation) {
650
748
  case 'insert':
651
- result = await this.performInsert(targetLayer, values, onConflict);
749
+ result = await this.performInsert(targetLayer, values, onConflict, preCoerced);
652
750
  break;
653
751
  case 'update':
654
- result = await this.performUpdate(targetLayer, values, oldKeyValues, onConflict);
752
+ result = await this.performUpdate(targetLayer, values, oldKeyValues, onConflict, preCoerced);
655
753
  break;
656
754
  case 'delete':
657
755
  result = await this.performDelete(targetLayer, oldKeyValues);
@@ -692,20 +790,23 @@ export class MemoryTableManager {
692
790
  // transaction is auto-created (autocommit mode). Leave explicitTransaction flag as-is.
693
791
  }
694
792
  }
695
- async performInsert(targetLayer, values, onConflict) {
793
+ async performInsert(targetLayer, values, onConflict, preCoerced) {
696
794
  if (!values) {
697
795
  throw new QuereusError("INSERT requires values.", StatusCode.MISUSE);
698
796
  }
699
- // Validate and parse values according to column types
797
+ // Validate and parse values according to column types. Skip when the caller
798
+ // already converted to declared form and says so via preCoerced — the DML
799
+ // executor (whose emitters convert by static type at the top of the pipeline)
800
+ // and the isolation overlay's flush/tombstone writes. JSON conversion is not
801
+ // idempotent, so re-running it can throw or silently change the value. Direct
802
+ // API callers leave the flag unset and this conversion remains their contract.
803
+ // NOTE: skipping it also skips that helper's "too many values" width guard. Every
804
+ // preCoerced caller today builds its row from this same schema's column order
805
+ // (the DML builders project the source into full table-column shape), so the
806
+ // width is structural; if an externally-shaped row ever reaches here preCoerced,
807
+ // hoist the width check out of coerceRowToSchema and run it unconditionally.
700
808
  const schema = targetLayer.getSchema();
701
- const validatedRow = values.map((value, index) => {
702
- if (index >= schema.columns.length) {
703
- throw new QuereusError(`Too many values for INSERT into ${this._tableName}: expected ${schema.columns.length}, got ${values.length}`, StatusCode.ERROR);
704
- }
705
- const column = schema.columns[index];
706
- return validateAndParse(value, column.logicalType, column.name);
707
- });
708
- const newRowData = validatedRow;
809
+ const newRowData = preCoerced ? values : coerceRowToSchema(values, schema.columns, `INSERT into ${this._tableName}`);
709
810
  const primaryKey = this.primaryKeyFromRow(newRowData);
710
811
  const existingRow = this.lookupEffectiveRow(primaryKey, targetLayer);
711
812
  if (existingRow !== null) {
@@ -736,20 +837,14 @@ export class MemoryTableManager {
736
837
  targetLayer.recordUpsert(primaryKey, newRowData, null);
737
838
  return { status: 'ok', row: newRowData, evictedRows: evicted.length > 0 ? evicted : undefined };
738
839
  }
739
- async performUpdate(targetLayer, values, oldKeyValues, onConflict) {
840
+ async performUpdate(targetLayer, values, oldKeyValues, onConflict, preCoerced) {
740
841
  if (!values || !oldKeyValues) {
741
842
  throw new QuereusError("UPDATE requires new values and old key values.", StatusCode.MISUSE);
742
843
  }
743
- // Validate and parse values according to column types
844
+ // Validate and parse values according to column types. Skip when preCoerced
845
+ // (see performInsert).
744
846
  const schema = targetLayer.getSchema();
745
- const validatedRow = values.map((value, index) => {
746
- if (index >= schema.columns.length) {
747
- throw new QuereusError(`Too many values for UPDATE on ${this._tableName}: expected ${schema.columns.length}, got ${values.length}`, StatusCode.ERROR);
748
- }
749
- const column = schema.columns[index];
750
- return validateAndParse(value, column.logicalType, column.name);
751
- });
752
- const newRowData = validatedRow;
847
+ const newRowData = preCoerced ? values : coerceRowToSchema(values, schema.columns, `UPDATE on ${this._tableName}`);
753
848
  const targetPrimaryKey = buildPrimaryKeyFromValues(oldKeyValues, schema.primaryKeyDefinition);
754
849
  const oldRowData = this.lookupEffectiveRow(targetPrimaryKey, targetLayer);
755
850
  if (!oldRowData) {
@@ -833,6 +928,14 @@ export class MemoryTableManager {
833
928
  * Returns true if any column covered by a UNIQUE constraint changed between
834
929
  * old and new rows, or if any column referenced by a partial-UNIQUE predicate
835
930
  * changed (which may transition the row into or out of the predicate's scope).
931
+ *
932
+ * NOTE: the per-column test is byte-level `compareSqlValues`, not the enforcement
933
+ * comparator, so it OVER-triggers for a semantic-ordering column: rewriting a
934
+ * TIMESPAN 'PT1H' to 'PT60M' reports "changed" and re-runs the UNIQUE check, which
935
+ * then excludes the row's own primary key and passes. Correct — this only gates
936
+ * whether to re-check — just not minimal. If UPDATE-heavy workloads over
937
+ * semantic-ordering UNIQUE columns ever show the redundant re-check as hot, route
938
+ * this through `uniqueEnforcementComparators` too.
836
939
  */
837
940
  uniqueColumnsChanged(schema, oldRow, newRow) {
838
941
  if (!schema.uniqueConstraints)
@@ -988,8 +1091,11 @@ export class MemoryTableManager {
988
1091
  if (existingPKs.length === 0)
989
1092
  return null;
990
1093
  // Resolve the per-column enforcement collations once, ahead of the candidate loop
991
- // (which collation governs, and why, is spelled out at the compare below).
1094
+ // (which collation governs, and why, is spelled out at the compare below), then
1095
+ // build the per-column comparators from them through the shared helper the store
1096
+ // and isolation re-validators also use.
992
1097
  const enforcementCollations = uc.columns.map((col, i) => this.collationResolver(index.specColumns[i]?.collation ?? schema.columns[col].collation ?? 'BINARY'));
1098
+ const compares = uniqueEnforcementComparators(schema.columns, uc.columns, enforcementCollations);
993
1099
  for (const existingPK of existingPKs) {
994
1100
  if (this.comparePrimaryKeys(newPrimaryKey, existingPK) === 0)
995
1101
  continue;
@@ -1010,7 +1116,12 @@ export class MemoryTableManager {
1010
1116
  // the enforcing structure, and an explicit `create unique index …
1011
1117
  // (col collate nocase)` may declare a coarser collation than the
1012
1118
  // column — re-checking under the column's collation would skip the
1013
- // case-variant candidates the index legitimately unifies.
1119
+ // case-variant candidates the index legitimately unifies. A
1120
+ // semantic-ordering column (TIMESPAN, JSON) compares through its
1121
+ // declared type's `compare` instead, so the re-check agrees with the
1122
+ // typed BTree that produced the candidate — without it a 'PT60M' probe
1123
+ // found the 'PT1H' row's PK and then discarded it as unequal, and the
1124
+ // duplicate was admitted.
1014
1125
  // This is the authoritative LIVE-index source for the per-column
1015
1126
  // enforcement collation. The shared `uniqueEnforcementCollations(schema,
1016
1127
  // uc)` helper (which store/isolation import, and checkUniqueViaMaterializedView
@@ -1028,7 +1139,7 @@ export class MemoryTableManager {
1028
1139
  const conflictingRow = this.lookupEffectiveRow(existingPK, targetLayer);
1029
1140
  if (!conflictingRow)
1030
1141
  continue;
1031
- if (!uc.columns.every((col, i) => compareSqlValuesFast(newRowData[col], conflictingRow[col], enforcementCollations[i]) === 0))
1142
+ if (!uc.columns.every((col, i) => compares[i](newRowData[col], conflictingRow[col]) === 0))
1032
1143
  continue;
1033
1144
  if (index.predicate && !index.rowMatchesPredicate(conflictingRow))
1034
1145
  continue;
@@ -1072,7 +1183,13 @@ export class MemoryTableManager {
1072
1183
  * backing delete still lands within this statement.
1073
1184
  */
1074
1185
  async checkUniqueViaMaterializedView(targetLayer, schema, uc, mv, newRowData, newPrimaryKey, onConflict, evicted) {
1075
- const newSourcePk = Array.isArray(newPrimaryKey) ? newPrimaryKey : [newPrimaryKey];
1186
+ // Component-array view of the source PK. Shape comes from the PK arity, never
1187
+ // from `Array.isArray` — a single-column JSON PK holding a document like `[1]`
1188
+ // is a scalar key that is also a JS array (see the `BTreeKey` invariant).
1189
+ // A wrong shape here only WIDENS the candidate set (the row fails its own
1190
+ // self-exclusion), and the loop below re-excludes self through the real PK
1191
+ // comparator — which is why no query result can observe this line.
1192
+ const newSourcePk = keyParts(newPrimaryKey, primaryKeyArity(schema) !== 1);
1076
1193
  const conflicts = await this.db._lookupCoveringConflicts(mv, uc, newRowData, newSourcePk);
1077
1194
  // Re-validate under each column's enforcement collation — the index's per-column
1078
1195
  // COLLATE for an index-derived UNIQUE, else the declared column collation
@@ -1084,7 +1201,11 @@ export class MemoryTableManager {
1084
1201
  // index-derived UNIQUE whose declared candidate set could be a subset is declined
1085
1202
  // upstream by findRowTimeCoveringStructure's collation gate, so only BINARY-floor
1086
1203
  // or equal-collation MVs ever reach here.
1204
+ // A semantic-ordering column (TIMESPAN, JSON) re-checks through its declared
1205
+ // type's `compare` rather than the collation — same rule the index path and both
1206
+ // out-of-package re-validators follow, via the shared helper.
1087
1207
  const collations = uniqueEnforcementCollations(schema, uc).map(name => this.collationResolver(name ?? 'BINARY'));
1208
+ const compares = uniqueEnforcementComparators(schema.columns, uc.columns, collations);
1088
1209
  for (const conflict of conflicts) {
1089
1210
  const existingPK = buildPrimaryKeyFromValues(conflict.pk, schema.primaryKeyDefinition);
1090
1211
  if (this.comparePrimaryKeys(newPrimaryKey, existingPK) === 0)
@@ -1093,7 +1214,7 @@ export class MemoryTableManager {
1093
1214
  const conflictingRow = this.lookupEffectiveRow(existingPK, targetLayer);
1094
1215
  if (!conflictingRow)
1095
1216
  continue;
1096
- if (!uc.columns.every((col, i) => compareSqlValuesFast(newRowData[col], conflictingRow[col], collations[i]) === 0))
1217
+ if (!uc.columns.every((col, i) => compares[i](newRowData[col], conflictingRow[col]) === 0))
1097
1218
  continue;
1098
1219
  if (onConflict === ConflictResolution.IGNORE) {
1099
1220
  return { status: 'ok', row: undefined };
@@ -1123,8 +1244,19 @@ export class MemoryTableManager {
1123
1244
  const predicate = uc.predicate
1124
1245
  ? compilePredicate(uc.predicate, schema.columns, schema.name)
1125
1246
  : undefined;
1126
- // One resolve per column, not per scanned row.
1247
+ // One resolve per column, not per scanned row. A semantic-ordering column
1248
+ // (TIMESPAN, JSON) compares through its declared type's `compare` instead of the
1249
+ // collation — the shared rule across all three backends.
1250
+ //
1251
+ // NOTE: the collations come from the DECLARED column collation, not from
1252
+ // `uniqueEnforcementCollations(schema, uc)` as the index and MV paths use. For an
1253
+ // index-derived UNIQUE (`create unique index … collate x`) the two disagree — this
1254
+ // path would enforce under the column's collation instead of the index's. Harmless
1255
+ // today: this fallback fires only when no covering structure resolves at all, and
1256
+ // an index-derived UC always resolves its own index. If a shape ever reaches here
1257
+ // with `uc.derivedFromIndex` set, switch this line to the shared resolver.
1127
1258
  const collations = uc.columns.map(colIdx => this.collationResolver(schema.columns[colIdx].collation ?? 'BINARY'));
1259
+ const compares = uniqueEnforcementComparators(schema.columns, uc.columns, collations);
1128
1260
  for (const path of primaryTree.ascending(primaryTree.first())) {
1129
1261
  const existingRow = primaryTree.at(path);
1130
1262
  const existingPK = this.primaryKeyFromRow(existingRow);
@@ -1132,7 +1264,7 @@ export class MemoryTableManager {
1132
1264
  continue;
1133
1265
  if (predicate && predicate.evaluate(existingRow) !== true)
1134
1266
  continue;
1135
- const allMatch = uc.columns.every((colIdx, i) => compareSqlValuesFast(newRowData[colIdx], existingRow[colIdx], collations[i]) === 0);
1267
+ const allMatch = uc.columns.every((colIdx, i) => compares[i](newRowData[colIdx], existingRow[colIdx]) === 0);
1136
1268
  if (!allMatch)
1137
1269
  continue;
1138
1270
  if (onConflict === ConflictResolution.IGNORE) {
@@ -1156,10 +1288,25 @@ export class MemoryTableManager {
1156
1288
  }
1157
1289
  renameTable(newName) {
1158
1290
  logger.operation('Rename Table', this._tableName, { newName });
1159
- this._tableName = newName;
1160
1291
  const renamed = Object.freeze({ ...this.tableSchema, name: newName });
1292
+ // Re-key the Database connection registry FIRST, while it still answers to the old
1293
+ // name. Every registry lookup below (`adoptSchemaOnOpenLayers` walks
1294
+ // `openTransactionLayersOldestFirst` -> `ddlConnection` -> `registeredConnections`)
1295
+ // keys on `<schema>.<current name>`, so a sweep run after `_tableName` moves would
1296
+ // match nothing and silently no-op.
1297
+ this.rekeyRegisteredConnections(this._tableName, newName);
1298
+ this._tableName = newName;
1161
1299
  this.tableSchema = renamed;
1162
1300
  this.baseLayer.tableSchema = renamed;
1301
+ // Hand the renamed schema to the open transaction's layers, like every other
1302
+ // schema-mutating arm. A rename rebuilds no `IndexSchema`, so `adoptSchema` is the
1303
+ // right (cheapest) level — each layer keeps its `MemoryIndex` and only swaps the
1304
+ // schema pointer; the reshape pair ADD/DROP COLUMN need would be pure overhead.
1305
+ // Skipping it leaves an eager savepoint snapshot on its frozen pre-rename schema
1306
+ // object, which fails `commitTransaction`'s snapshot-wrap identity check
1307
+ // (`readLayer.getSchema() === this.tableSchema`) and drops the transaction's staged
1308
+ // rows at COMMIT.
1309
+ this.adoptSchemaOnOpenLayers(renamed);
1163
1310
  // Emit schema change event
1164
1311
  this.eventEmitter?.emitSchemaChange?.({
1165
1312
  type: 'alter',
@@ -1453,7 +1600,15 @@ export class MemoryTableManager {
1453
1600
  }
1454
1601
  }
1455
1602
  // --- Schema Operations (simplified with inherited BTrees) ---
1456
- async addColumn(columnDefAst, backfillEvaluator) {
1603
+ /**
1604
+ * ADD COLUMN. `insertAtIndex` — when supplied — puts the new column at that slot
1605
+ * instead of appending; every existing column at or after it shifts right by one,
1606
+ * and each index-bearing schema field is renumbered to match (see
1607
+ * {@link shiftSchemaIndicesForInsert}). Omitted ⇒ append, which is what SQL
1608
+ * `alter table … add column` always asks for and which leaves the schema rebuild
1609
+ * on exactly the path it took before positions existed.
1610
+ */
1611
+ async addColumn(columnDefAst, backfillEvaluator, insertAtIndex) {
1457
1612
  if (this.isReadOnly)
1458
1613
  throw new QuereusError(`Table '${this._tableName}' is read-only`, StatusCode.READONLY);
1459
1614
  const lockKey = `MemoryTable.SchemaChange:${this.schemaName}.${this._tableName}`;
@@ -1498,20 +1653,76 @@ export class MemoryTableManager {
1498
1653
  throw new QuereusError(`Cannot add NOT NULL column '${newColumnSchema.name}' to non-empty table `
1499
1654
  + `'${this.schemaName}.${this._tableName}' without a DEFAULT value`, StatusCode.CONSTRAINT);
1500
1655
  }
1501
- const updatedColumnsSchema = Object.freeze([...this.tableSchema.columns, newColumnSchema]);
1656
+ const appendIndex = this.tableSchema.columns.length;
1657
+ const targetIndex = insertAtIndex ?? appendIndex;
1658
+ if (!Number.isInteger(targetIndex) || targetIndex < 0 || targetIndex > appendIndex) {
1659
+ throw new QuereusError(`Cannot add column '${newColumnSchema.name}' at position ${insertAtIndex}: `
1660
+ + `expected an integer in [0, ${appendIndex}]`, StatusCode.MISUSE);
1661
+ }
1662
+ const updatedColumnsSchema = Object.freeze(insertValueAt(this.tableSchema.columns, targetIndex, newColumnSchema));
1663
+ // Appending never invalidates an existing column index, so the append path keeps
1664
+ // carrying every index-bearing field through the spread unchanged; only a real
1665
+ // insert pays for the renumber.
1666
+ const shiftedIndexFields = targetIndex < appendIndex
1667
+ ? shiftSchemaIndicesForInsert(this.tableSchema, targetIndex)
1668
+ : undefined;
1502
1669
  const finalNewTableSchema = Object.freeze({
1503
1670
  ...this.tableSchema,
1671
+ ...shiftedIndexFields,
1504
1672
  columns: updatedColumnsSchema,
1505
1673
  columnIndexMap: buildColumnIndexMap(updatedColumnsSchema),
1506
1674
  });
1675
+ // Phase 1 of reaching the open transaction's own pending rows: compute their
1676
+ // post-ADD shape before ANY mutation, since the per-row backfill below can throw
1677
+ // on a pending row (and there is no undo for a half-reshaped layer chain).
1678
+ //
1679
+ // The NOT NULL check mirrors the base backfill's per-row check in
1680
+ // `BaseLayer.recreatePrimaryTreeWithNewColumn`, extended to pending rows. It is
1681
+ // deliberately NOT gated on `backfillEvaluator`, which today makes the
1682
+ // literal-default arm redundant: `validateNotNullBackfill`
1683
+ // (runtime/emit/alter-table.ts) already rejects NOT NULL without a usable DEFAULT
1684
+ // by querying the DDL connection's EFFECTIVE rows — pending ones included — so the
1685
+ // only violation that reaches here is a per-row evaluator yielding NULL. Kept
1686
+ // ungated so the module enforces its own invariant instead of relying on that gate.
1687
+ const reshapePlans = await this.prepareReshapeOnOpenLayers(async (row) => {
1688
+ const value = backfillEvaluator ? await backfillEvaluator(row) : defaultValue;
1689
+ if (newColumnSchema.notNull && value === null) {
1690
+ throw new QuereusError(`NOT NULL constraint failed: adding column '${this._tableName}.${newColumnSchema.name}' would leave NULL in a row pending in the open transaction`, StatusCode.CONSTRAINT);
1691
+ }
1692
+ return insertValueAt(row, targetIndex, value);
1693
+ },
1694
+ // Event-log variant: same map applied to the log's historical images (oldRow
1695
+ // included — the evaluator is a function of a row and the pre-image is a row),
1696
+ // but BEST-EFFORT: an image the evaluator (or its CHECKs) rejects gets NULL —
1697
+ // the honest "column did not exist yet" placeholder — and the NOT NULL throw
1698
+ // above is omitted, so no historical image can abort the ALTER.
1699
+ async (row) => {
1700
+ let value = defaultValue;
1701
+ if (backfillEvaluator) {
1702
+ try {
1703
+ value = await backfillEvaluator(row);
1704
+ }
1705
+ catch {
1706
+ value = null;
1707
+ }
1708
+ }
1709
+ return insertValueAt(row, targetIndex, value);
1710
+ });
1507
1711
  this.baseLayer.updateSchema(finalNewTableSchema);
1508
1712
  // A non-foldable DEFAULT (e.g. `new.<col>`) backfills each existing row from
1509
1713
  // its own value via the engine-supplied evaluator; a literal/NULL default
1510
1714
  // uses the single folded `defaultValue` for every row.
1511
- await this.baseLayer.addColumnToBase(newColumnSchema, defaultValue, backfillEvaluator);
1715
+ await this.baseLayer.addColumnToBase(newColumnSchema, defaultValue, backfillEvaluator, targetIndex);
1512
1716
  this.tableSchema = finalNewTableSchema;
1513
1717
  this.initializePrimaryKeyFunctions();
1514
- // Emit schema change event
1718
+ this.installReshapeOnOpenLayers(finalNewTableSchema, reshapePlans);
1719
+ // Emit schema change event.
1720
+ // NOTE: the event names the added column but not its position, which is fine while
1721
+ // the only non-append caller is an in-process wrapper that rebuilds its view from the
1722
+ // post-change schema. An append never invalidates an existing column index; an insert
1723
+ // does. If a position ever becomes SQL-reachable, anything that caches a column index
1724
+ // across this swap (planner caches, prepared statements, a wrapper's own schema
1725
+ // snapshot) needs to learn the insert point — so the event would have to carry it.
1515
1726
  this.eventEmitter?.emitSchemaChange?.({
1516
1727
  type: 'alter',
1517
1728
  objectType: 'column',
@@ -1583,6 +1794,28 @@ export class MemoryTableManager {
1583
1794
  .filter(ic => ic.index !== colIndex)
1584
1795
  .map(ic => ({ ...ic, index: ic.index > colIndex ? ic.index - 1 : ic.index }))
1585
1796
  })).filter(idx => idx.columns.length > 0);
1797
+ // Shift or prune the table's own foreign keys. `foreignKeys[].columns` are indices
1798
+ // into THIS table, so they renumber over the removed slot exactly as the PK and the
1799
+ // UNIQUE constraints above do. A foreign key that loses ANY of its child columns is
1800
+ // removed outright, the same call the UNIQUE pruning makes: a key missing one of its
1801
+ // child columns is a different constraint against the parent's key, not a narrowed
1802
+ // one. Both halves matter — left unshifted, a surviving key's index either dangles
1803
+ // past the end of the column array or, worse, silently slides onto an unrelated
1804
+ // column and starts enforcing against the parent there.
1805
+ //
1806
+ // `referencedColumns` is not touched: enforcement resolves the parent indices on
1807
+ // demand from `referencedColumnNames` against the parent's current schema. See
1808
+ // `shiftSchemaIndicesForInsert`, the ADD-COLUMN-at-a-position mirror of this block.
1809
+ const remainingForeignKeys = (this.tableSchema.foreignKeys ?? [])
1810
+ .filter(fk => !fk.columns.includes(colIndex))
1811
+ .map(fk => ({ ...fk, columns: Object.freeze(fk.columns.map(i => i > colIndex ? i - 1 : i)) }));
1812
+ // NOTE: the generated-column bookkeeping (`generatedColumnDependencies` /
1813
+ // `generatedColumnTopoOrder`, also index-keyed) is likewise unshifted, but the
1814
+ // engine recomputes it from column names right after this returns
1815
+ // (`withGeneratedColumnGraph` in `runDropColumn`), so only a caller driving the
1816
+ // module API directly ever observes the stale map.
1817
+ //
1818
+ // `primaryKey` below is not a TableSchema field at all — nothing reads it.
1586
1819
  const finalNewTableSchema = Object.freeze({
1587
1820
  ...this.tableSchema,
1588
1821
  columns: Object.freeze(updatedColumnsSchema),
@@ -1593,7 +1826,16 @@ export class MemoryTableManager {
1593
1826
  uniqueConstraints: remainingUniqueConstraints.length > 0
1594
1827
  ? Object.freeze(remainingUniqueConstraints)
1595
1828
  : undefined,
1829
+ foreignKeys: remainingForeignKeys.length > 0
1830
+ ? Object.freeze(remainingForeignKeys)
1831
+ : undefined,
1596
1832
  });
1833
+ // Phase 1 of reaching the open transaction's own pending rows. The rewrite here is
1834
+ // a pure filter and cannot throw — the split exists for ADD COLUMN's fallible
1835
+ // backfill — but running it before any mutation keeps the two paths uniform. The
1836
+ // same filter reshapes the pending-change event log (second arg).
1837
+ const dropSlot = (row) => row.filter((_, idx) => idx !== colIndex);
1838
+ const reshapePlans = await this.prepareReshapeOnOpenLayers(dropSlot, dropSlot);
1597
1839
  this.baseLayer.updateSchema(finalNewTableSchema);
1598
1840
  await this.baseLayer.dropColumnFromBase(colIndex);
1599
1841
  this.tableSchema = finalNewTableSchema;
@@ -1602,6 +1844,7 @@ export class MemoryTableManager {
1602
1844
  for (const key of droppedUcKeys)
1603
1845
  this.implicitCoveringStructures.delete(key);
1604
1846
  this.initializePrimaryKeyFunctions();
1847
+ this.installReshapeOnOpenLayers(finalNewTableSchema, reshapePlans);
1605
1848
  // Emit schema change event
1606
1849
  this.eventEmitter?.emitSchemaChange?.({
1607
1850
  type: 'drop',
@@ -1685,6 +1928,20 @@ export class MemoryTableManager {
1685
1928
  await this.baseLayer.handleColumnRename();
1686
1929
  this.tableSchema = finalNewTableSchema;
1687
1930
  this.initializePrimaryKeyFunctions();
1931
+ // The DDL transaction's own layers froze their schema at creation; hand them the
1932
+ // renamed one, mirroring `handleColumnRename`'s base-side secondary rebuild —
1933
+ // `updatedIndexes` above rebuilt every `IndexSchema` object, which is exactly the
1934
+ // identity discriminator `adoptSchema` rebuilds a layer's `MemoryIndex` on.
1935
+ //
1936
+ // A rename touches neither the column set nor the primary key, so `adoptSchema` is
1937
+ // the right level (not the `prepareReshapeOnOpenLayers` / `installReshapeOnOpenLayers`
1938
+ // pair `add column` / `drop column` need) — but it is just as mandatory: without it an
1939
+ // eager savepoint snapshot keeps its frozen pre-rename schema, fails
1940
+ // `commitTransaction`'s snapshot-wrap identity check
1941
+ // (`readLayer.getSchema() === this.tableSchema`), and the transaction's staged rows are
1942
+ // silently dropped at COMMIT. Covered by
1943
+ // `test/logic/41.8-alter-savepoint-staged-rows.sqllogic`.
1944
+ this.adoptSchemaOnOpenLayers(finalNewTableSchema);
1688
1945
  // Emit schema change event
1689
1946
  this.eventEmitter?.emitSchemaChange?.({
1690
1947
  type: 'alter',
@@ -1717,8 +1974,15 @@ export class MemoryTableManager {
1717
1974
  * Apply a single-attribute ALTER COLUMN change (NOT NULL, DEFAULT, DATA TYPE).
1718
1975
  * The caller supplies exactly one populated change; multi-attribute combinations
1719
1976
  * are rejected by the runtime before reaching this method.
1977
+ *
1978
+ * `validateOnly` runs everything up to (and including) the pre-mutation validation
1979
+ * passes — the effective-row NULL / conversion scans, the re-keyed UNIQUE probe, and
1980
+ * the primary-key re-key legality/representability passes — and returns before the
1981
+ * first mutation, throwing exactly what the real application would throw. The
1982
+ * isolation layer uses it to pre-flight an overlay's migration before the shared
1983
+ * underlying table mutates irreversibly (see `VirtualTable.alterSchema`).
1720
1984
  */
1721
- async alterColumn(change, rows) {
1985
+ async alterColumn(change, rows, validateOnly = false) {
1722
1986
  if (this.isReadOnly)
1723
1987
  throw new QuereusError(`Table '${this._tableName}' is read-only`, StatusCode.READONLY);
1724
1988
  const lockKey = `MemoryTable.SchemaChange:${this.schemaName}.${this._tableName}`;
@@ -1738,12 +2002,23 @@ export class MemoryTableManager {
1738
2002
  // A collation change re-keys any PK / UNIQUE / index that orders by this
1739
2003
  // column, so it needs the structure re-sort + uniqueness re-validation below.
1740
2004
  let collationChanged = false;
2005
+ // Set when a SET DATA TYPE moves to a type that ORDERS values differently
2006
+ // (comparisonSemanticsDiffer). Same consequence as a collation change — every
2007
+ // structure keyed by the column has to be re-sorted and its uniqueness re-judged —
2008
+ // but a separate flag, because the index-column collation propagation and the
2009
+ // `pkColumnRekeyed` primary-tree path below belong to SET COLLATE alone. Usually
2010
+ // coincides with `valueConvert` (every non-alias retype sets that); the rebuild
2011
+ // chains below test `valueConvert` FIRST, whose full-rebuild path subsumes the
2012
+ // comparator-only re-sort.
2013
+ let comparatorChanged = false;
1741
2014
  // Set to the per-value conversion function when a physical value rewrite is needed:
1742
- // SET DATA TYPE (retype every value) or SET NOT NULL backfill (map null → DEFAULT).
1743
- // Both REPLACE the base primary tree with a freshly-built one (never an in-place
1744
- // mutation, which inheritree forbids while the open transaction's layers derive from
1745
- // it) and convert the transaction's own-written rows in every open layer, after
1746
- // `updateSchema` (see the rebuild section and convertColumnOnOpenLayers).
2015
+ // SET DATA TYPE (every non-alias retype values normalize to the new type's
2016
+ // canonical spelling even when the storage class is unchanged) or SET NOT NULL
2017
+ // backfill (map null DEFAULT). Both REPLACE the base primary tree with a
2018
+ // freshly-built one (never an in-place mutation, which inheritree forbids while the
2019
+ // open transaction's layers derive from it) and convert the transaction's
2020
+ // own-written rows in every open layer, after `updateSchema` (see the rebuild
2021
+ // section and convertColumnOnOpenLayers).
1747
2022
  let valueConvert = null;
1748
2023
  // SET NOT NULL backfill also routes NULL values through `valueConvert`; SET DATA TYPE
1749
2024
  // leaves nulls untouched. Selects the null-including path in convertBaseRows/convertColumn.
@@ -1821,19 +2096,40 @@ export class MemoryTableManager {
1821
2096
  }
1822
2097
  else if (change.setDataType !== undefined) {
1823
2098
  const newLogicalType = inferType(change.setDataType);
1824
- if (newLogicalType.physicalType === oldCol.logicalType.physicalType) {
1825
- newCol = { ...oldCol, logicalType: newLogicalType };
1826
- }
1827
- else {
1828
- // Retyping a PRIMARY KEY column changes the physical key bytes. The in-place
1829
- // base rewrite below cannot re-key the primary tree (an upsert of a row whose
1830
- // PK value changed physical type lands at a NEW key, orphaning the old entry),
1831
- // and the transaction-layer conversion deliberately leaves every layer's primary
1832
- // tree keyed as-is. Reject rather than corrupt it the type-change analogue of
1833
- // the SET COLLATE primary-key carve-out (`alter-collate-pk-in-transaction`).
2099
+ // Gate on logical-type IDENTITY, not the physical storage class: `inferType`
2100
+ // flattens aliases to the shared type object (`varchar(50)` IS `TEXT_TYPE`,
2101
+ // `bigint` IS `INTEGER_TYPE`), so an alias retype is schema-only — no scan, no
2102
+ // rewrite, no re-key. Every OTHER retype validates AND normalizes every value,
2103
+ // whether or not the storage class changes: a same-class move (text date)
2104
+ // must reject values the new type refuses ('hello') and rewrite the rest to
2105
+ // the new type's canonical spelling ('2024-06-05T00:00:00Z' '2024-06-05'),
2106
+ // or the column holds values no INSERT could have produced — unfindable by
2107
+ // equality (DATE compares BINARY over the stored text) and invisible to UNIQUE.
2108
+ // NOTE: the rewrite is unconditional once the types differ, even when every
2109
+ // converted value comes back byte-identical (already-canonical ISO durations on a
2110
+ // text → timespan). That costs a full base-tree + secondary-index rebuild here, and
2111
+ // a physical rewrite of every row on the store leg. If ALTER on large tables ever
2112
+ // shows up as slow, have the convert pre-pass record whether any value actually
2113
+ // moved and, when none did, fall through to the comparator-only re-key.
2114
+ if (newLogicalType !== oldCol.logicalType) {
2115
+ // Retyping a PRIMARY KEY column moves the key bytes (a class change), the
2116
+ // tree's ordering (a comparator move), or at minimum the key values'
2117
+ // canonical spelling — and neither the base rewrite below nor the
2118
+ // transaction-layer conversion re-keys the primary tree. The engine already
2119
+ // refuses SET DATA TYPE on any PK column (runtime/emit/alter-table.ts);
2120
+ // this guards direct module calls. Reject rather than corrupt — the
2121
+ // type-change analogue of the SET COLLATE primary-key carve-out
2122
+ // (`alter-collate-pk-in-transaction`).
1834
2123
  if (this.tableSchema.primaryKeyDefinition.some(def => def.index === colIndex)) {
1835
2124
  throw new QuereusError(`Cannot change the data type of primary key column '${change.columnName}' of table '${this._tableName}'.`, StatusCode.CONSTRAINT);
1836
2125
  }
2126
+ // Every MemoryIndex builds its comparator from the column's LOGICAL type
2127
+ // (`createTypedComparator(columnSchema.logicalType, …)` in vtab/memory/index.ts),
2128
+ // so a move to a differently-ordering type (text ↔ timespan: 'PT1H' ≡ 'PT60M';
2129
+ // text ↔ date/time/datetime: binary, ignoring the column's collation) re-sorts
2130
+ // every structure keyed by the column. Tracked separately from the value rewrite
2131
+ // because SET COLLATE shares the re-key machinery without rewriting values.
2132
+ comparatorChanged = comparisonSemanticsDiffer(oldCol.logicalType, newLogicalType);
1837
2133
  const convert = (v) => {
1838
2134
  try {
1839
2135
  return validateAndParse(v, newLogicalType, change.columnName);
@@ -1868,8 +2164,8 @@ export class MemoryTableManager {
1868
2164
  // derive from that tree (`MutatedBaseError`). `valueConvert` drives both the base
1869
2165
  // replacement and the transaction-layer conversion.
1870
2166
  valueConvert = convert;
1871
- newCol = { ...oldCol, logicalType: newLogicalType };
1872
2167
  }
2168
+ newCol = { ...oldCol, logicalType: newLogicalType };
1873
2169
  }
1874
2170
  else if (change.setDefault !== undefined) {
1875
2171
  newCol = { ...oldCol, defaultValue: change.setDefault };
@@ -1878,15 +2174,30 @@ export class MemoryTableManager {
1878
2174
  throw new QuereusError('ALTER COLUMN requires an attribute to change', StatusCode.INTERNAL);
1879
2175
  }
1880
2176
  const updatedCols = this.tableSchema.columns.map((c, i) => i === colIndex ? newCol : c);
2177
+ // The changes that move the COMPARATOR the structures are keyed by: SET COLLATE
2178
+ // (values untouched) and a retype into a differently-ordering type (which, unless it
2179
+ // is an alias retype, ALSO rewrites values — `valueConvert` — and its full-rebuild
2180
+ // path below subsumes the comparator-only re-sort). Both need fresh IndexSchema
2181
+ // objects and, when no rewrite runs, the re-validate / re-sort / adopt sequence.
2182
+ const structuresRekeyed = collationChanged || comparatorChanged;
1881
2183
  // Propagate a collation change into every PK-definition entry and index
1882
2184
  // column that orders by this column, so their comparators re-key under it.
1883
2185
  const updatedPkDef = collationChanged
1884
2186
  ? this.tableSchema.primaryKeyDefinition.map(def => def.index === colIndex ? { ...def, collation: newCol.collation } : def)
1885
2187
  : this.tableSchema.primaryKeyDefinition;
1886
- const updatedIndexes = (collationChanged && this.tableSchema.indexes)
2188
+ // Every re-keying change also REBUILDS the IndexSchema objects, because their identity is
2189
+ // the discriminator `TransactionLayer.adoptSchema` uses to decide an open layer's
2190
+ // MemoryIndex must be replaced rather than kept (an index it holds under an object the new
2191
+ // schema still carries is left alone). SET COLLATE has a field to write — the index
2192
+ // column's own collation. A comparator-only retype has none: an index column carries no
2193
+ // logical type, it reads the column's from `newSchema.columns`, so the fresh object IS the
2194
+ // whole signal. Without it the base rebuilt its trees under the new comparator while the
2195
+ // DDL transaction's own layers went on comparing the old way — and shadowed the base's
2196
+ // rebuilt trees once the pending layer became the committed head.
2197
+ const updatedIndexes = (structuresRekeyed && this.tableSchema.indexes)
1887
2198
  ? this.tableSchema.indexes.map(idx => ({
1888
2199
  ...idx,
1889
- columns: idx.columns.map(ic => ic.index === colIndex ? { ...ic, collation: newCol.collation } : ic),
2200
+ columns: idx.columns.map(ic => (ic.index === colIndex && collationChanged) ? { ...ic, collation: newCol.collation } : ic),
1890
2201
  }))
1891
2202
  : this.tableSchema.indexes;
1892
2203
  const finalNewTableSchema = Object.freeze({
@@ -1898,48 +2209,81 @@ export class MemoryTableManager {
1898
2209
  });
1899
2210
  const pkColumnRekeyed = collationChanged && updatedPkDef.some(def => def.index === colIndex);
1900
2211
  // Validate BEFORE any mutation, over the DDL transaction's EFFECTIVE rows: a pair
1901
- // the transaction inserted that collides under the NEW collation must reject the
2212
+ // the transaction inserted that collides under the NEW schema must reject the
1902
2213
  // change, and one it has deleted must not block it. A throw here leaves the schema,
1903
2214
  // the base layer and the index map exactly as they were.
1904
2215
  //
1905
- // The primary key gets a stricter pre-pass (`validateRekeyedPrimaryKey`): no layer in
1906
- // the chain, base included, may hold a collision. That is what lets every step below
1907
- // succeed unconditionally, and what `TransactionLayer.rekeyPrimaryKey` relies on.
1908
- if (collationChanged) {
2216
+ // A value-REWRITING change (`valueConvert`: SET DATA TYPE, or a SET NOT NULL null
2217
+ // DEFAULT backfill) probes the CONVERTED rows the same per-row conversion the base
2218
+ // rewrite below applies — under the NEW schema's comparators, so one call covers both
2219
+ // ways an ALTER can collapse two previously-distinct rows: the VALUES converge
2220
+ // (`'1'`/`'01'` → 1; two NULLs → one DEFAULT; '2024-06-05T00:00:00Z' → '2024-06-05')
2221
+ // and/or the COMPARATOR moves (text → timespan makes 'PT1H'/'PT60M' one value). A
2222
+ // same-storage-class retype sets `valueConvert` AND `structuresRekeyed`, so the
2223
+ // rewrite arm is tested FIRST — its converted-row probe subsumes the comparator-only
2224
+ // one. The rebuild that follows is deliberately non-enforcing, so this is the only
2225
+ // guard.
2226
+ //
2227
+ // The comparator-MOVING arm without a rewrite has one trigger left: SET COLLATE. The
2228
+ // values are untouched — hence no `mapRow` — and only the comparator the structures
2229
+ // are keyed by moves. `newSchema` carries the new collations, so the probe indexes
2230
+ // compare as the rebuilt ones will.
2231
+ //
2232
+ // The primary key: SET COLLATE on a PK member gets a stricter pre-pass
2233
+ // (`validateRekeyedPrimaryKey`): no layer in the chain, base included, may hold a
2234
+ // collision. That is what lets every step below succeed unconditionally, and what
2235
+ // `TransactionLayer.rekeyPrimaryKey` relies on. The rewrite arm needs no PK pre-pass
2236
+ // and cannot shadow one: a retype of a PK column is rejected upstream, the backfill
2237
+ // only fires on a column holding NULLs (which a PK member cannot — the engine
2238
+ // enforces NOT NULL on every PK member regardless of the declared nullability), and
2239
+ // `pkColumnRekeyed` is set only by SET COLLATE, which never sets `valueConvert` — so
2240
+ // whenever `pkColumnRekeyed` is true, the `structuresRekeyed` arm still runs.
2241
+ // NOTE: if PK members ever become genuinely nullable, the backfill arm gains a PK
2242
+ // collision path (two NULL keys → one DEFAULT) and needs `validateRekeyedPrimaryKey`
2243
+ // plus a primary-tree re-key of its own.
2244
+ if (valueConvert) {
2245
+ const convert = valueConvert;
2246
+ const nulls = convertNulls;
2247
+ await this.validateRekeyedUniqueStructures(finalNewTableSchema, colIndex, rows,
2248
+ // Unlike `convertBaseRows`, a conversion failure here is NOT swallowed. Every row
2249
+ // the probe sees is visible to the transaction, and the setDataType pre-pass above
2250
+ // already proved each one convertible, so a throw is unreachable; surfacing it as
2251
+ // MISMATCH still beats probing a stale value that could mask a collision.
2252
+ row => convertRowAtIndex(row, colIndex, convert, nulls));
2253
+ }
2254
+ else if (structuresRekeyed) {
1909
2255
  await this.validateRekeyedUniqueStructures(finalNewTableSchema, colIndex, rows);
1910
- // NOTE: `validateRekeyedPrimaryKey` deliberately ignores `rows`. It asserts that no
1911
- // LAYER of this manager's own chain holds a PK collision under the new comparator
1912
- // a physical property of the structures it is about to re-key, which a wrapper's
1913
- // merged row stream cannot speak to. A wrapper's staged rows live in the wrapper's
1914
- // own overlay, which enforces the PK itself, so the pair "one staged row + one
1915
- // committed row that collide only under the new collation" is checked by neither
1916
- // side. Rejecting it here would need the wrapper to expose its overlay's PK set, not
1917
- // just its merged rows.
2256
+ // Unlike the secondary-structure pass above which only ever judges the effective
2257
+ // rows the PK pass judges TWO row sets: the effective rows (`rows` when a wrapper
2258
+ // supplies them) decide whether the change is LEGAL, and this manager's own layer
2259
+ // chain decides whether the re-keyed trees can PHYSICALLY carry it. See
2260
+ // `validateRekeyedPrimaryKey` for why the sets differ and which status each raises.
1918
2261
  if (pkColumnRekeyed)
1919
- this.validateRekeyedPrimaryKey(finalNewTableSchema);
2262
+ await this.validateRekeyedPrimaryKey(finalNewTableSchema, rows);
1920
2263
  }
2264
+ // Dry run ends here: everything above validates without mutating (the one earlier
2265
+ // side effect, ensureSchemaChangeSafety's committed-layer drain, is semantically
2266
+ // neutral bookkeeping), and everything below mutates. See the method doc.
2267
+ if (validateOnly)
2268
+ return;
1921
2269
  this.baseLayer.updateSchema(finalNewTableSchema);
1922
- // A collation change re-sorts the structures that order by the column. The
1923
- // secondary rebuild is NON-enforcing (the pre-pass above owns uniqueness, and the
1924
- // base's rows are not a subset of the effective rows); the primary tree rebuild is
1925
- // strict, since a PK collision cannot be represented at all the pre-pass has
1926
- // already proved the base collision-free, so the strict rebuild is a live invariant
1927
- // check, not the enforcement path. It runs LAST so its throw leaves the live tree
1928
- // intact for the catch's rollback.
1929
- if (collationChanged) {
1930
- this.baseLayer.rebuildAllSecondaryIndexes();
1931
- if (pkColumnRekeyed) {
1932
- basePrimaryTreeBeforeRekey = this.baseLayer.primaryTree;
1933
- this.baseLayer.rebuildPrimaryTreeStrict();
1934
- }
1935
- }
1936
- else if (valueConvert) {
2270
+ // Both arms rebuild the base's structures under the schema `updateSchema` above just
2271
+ // swapped in each MemoryIndex rebuilds its comparator from the CURRENT schema's
2272
+ // collation and logical type. The secondary rebuilds are NON-enforcing (the pre-pass
2273
+ // above owns uniqueness, and the base's rows are not a subset of the effective rows);
2274
+ // the SET COLLATE PK rebuild is strict, since a PK collision cannot be represented at
2275
+ // all the pre-pass has already proved the base collision-free, so the strict
2276
+ // rebuild is a live invariant check, not the enforcement path. It runs LAST so its
2277
+ // throw leaves the live tree intact for the catch's rollback.
2278
+ if (valueConvert) {
1937
2279
  // SET DATA TYPE / SET NOT NULL backfill: convert the committed base rows and REPLACE
1938
2280
  // the primary tree with a fresh one holding them (not an in-place upsert — inheritree
1939
2281
  // forbids mutating a base while the open transaction's layers derive from it).
1940
2282
  // `rebuildPrimaryTreeFromRows` also rebuilds every secondary index from the converted
1941
- // rows, so index-backed lookups see the new values. The old tree is saved for the
1942
- // catch's rollback, exactly as the PK-rekey path does.
2283
+ // rows under the new comparators, so index-backed lookups see the new values which
2284
+ // is why this arm runs first and subsumes `rebuildAllSecondaryIndexes` when a
2285
+ // same-storage-class retype sets `structuresRekeyed` too. The old tree is saved for
2286
+ // the catch's rollback, exactly as the PK-rekey path does.
1943
2287
  // NOTE: rebuilds EVERY secondary index, not only those covering the altered column;
1944
2288
  // if a wide-index table ever shows this as slow, filter to indexes whose columns
1945
2289
  // include colIndex. Mirrors the collationChanged path's unconditional rebuild.
@@ -1954,31 +2298,56 @@ export class MemoryTableManager {
1954
2298
  basePrimaryTreeBeforeRekey = this.baseLayer.primaryTree;
1955
2299
  this.baseLayer.rebuildPrimaryTreeFromRows(convertedBaseRows);
1956
2300
  }
2301
+ else if (structuresRekeyed) {
2302
+ this.baseLayer.rebuildAllSecondaryIndexes();
2303
+ if (pkColumnRekeyed) {
2304
+ basePrimaryTreeBeforeRekey = this.baseLayer.primaryTree;
2305
+ this.baseLayer.rebuildPrimaryTreeStrict();
2306
+ }
2307
+ }
1957
2308
  this.tableSchema = finalNewTableSchema;
1958
2309
  this.initializePrimaryKeyFunctions();
1959
2310
  // The base rebuild handed every secondary index a fresh tree under the new
1960
- // collation; the DDL transaction's own layers still inherit the old ones and froze
2311
+ // comparator; the DDL transaction's own layers still inherit the old ones and froze
1961
2312
  // the old schema at construction. Re-key them, or the rest of the transaction — and
1962
2313
  // everything after the pending layer becomes the committed head at commit — keeps
1963
- // comparing under the old collation.
2314
+ // comparing under the old collation / old logical type.
1964
2315
  //
1965
2316
  // When the altered column is part of the primary key, `rebuildPrimaryTreeStrict` also
1966
2317
  // swapped the base primary tree object out from under those layers' copy-on-write
1967
2318
  // bases and invalidated their `pkFunctions`; `rekeyPrimaryKey` rebuilds both, plus
1968
2319
  // every secondary index (each derives its PK comparator/encoder from the PK
1969
2320
  // definition). Outside a transaction there are no open layers and both are no-ops.
1970
- if (collationChanged) {
1971
- this.adoptSchemaOnOpenLayers(finalNewTableSchema, pkColumnRekeyed);
1972
- }
1973
- else if (valueConvert) {
2321
+ if (valueConvert) {
1974
2322
  // SET DATA TYPE / SET NOT NULL backfill converted the base above; the DDL
1975
2323
  // transaction's own open layers still froze the old schema and hold their own-written
1976
2324
  // rows unconverted. Swap the schema and convert those values so the rest of the
1977
2325
  // transaction — and the committed head at commit — read the new value. For SET NOT
1978
2326
  // NULL this fills the transaction's OWN pending NULL rows (which live in the pending
1979
- // layer, never reached by the old in-place base upsert). No-op in autocommit.
2327
+ // layer, never reached by the old in-place base upsert). `convertColumn` installs the
2328
+ // new schema and rebuilds every one of the layer's secondary indexes from it, so it
2329
+ // subsumes `adoptSchema` when a same-storage-class retype moved the comparators too.
2330
+ // No-op in autocommit.
1980
2331
  this.convertColumnOnOpenLayers(finalNewTableSchema, colIndex, valueConvert, convertNulls);
1981
2332
  }
2333
+ else if (structuresRekeyed) {
2334
+ this.adoptSchemaOnOpenLayers(finalNewTableSchema, pkColumnRekeyed);
2335
+ }
2336
+ else {
2337
+ // The remaining changes (SET DEFAULT, DROP NOT NULL, a SET NOT NULL that needed no
2338
+ // backfill, a retype between aliases of one logical type) touch nothing a layer
2339
+ // DERIVES — its index set, its `uniqueConstraints` enforcement and its MemoryIndex
2340
+ // comparators all stay put, and DEFAULT application / NOT NULL enforcement happen
2341
+ // above the module, off the catalog schema. But the frozen schema OBJECT is itself
2342
+ // observable: `commitTransaction` only wraps a swapped-in savepoint snapshot back
2343
+ // into the committed chain when `readLayer.getSchema() === this.tableSchema`. Left
2344
+ // stale, a `rollback to savepoint` taken across one of these ALTERs restores a
2345
+ // snapshot the wrap then skips, and the transaction's staged rows are silently
2346
+ // dropped at COMMIT. Adoption is cheap here — every IndexSchema object is carried
2347
+ // over unchanged, so `adoptSchema` keeps each layer's MemoryIndex and only swaps
2348
+ // the schema reference.
2349
+ this.adoptSchemaOnOpenLayers(finalNewTableSchema);
2350
+ }
1982
2351
  this.eventEmitter?.emitSchemaChange?.({
1983
2352
  type: 'alter',
1984
2353
  objectType: 'column',
@@ -2067,12 +2436,15 @@ export class MemoryTableManager {
2067
2436
  // The DDL transaction's own layers froze their schema at creation; hand them the
2068
2437
  // new one so the rest of the transaction scans and enforces the new index.
2069
2438
  this.adoptSchemaOnOpenLayers(finalNewTableSchema);
2070
- // Emit schema change event
2439
+ // Emit schema change event. The `ddl` is what a sync peer re-executes to
2440
+ // replicate the index; rendered against the POST-create schema so it matches
2441
+ // what a receiving peer regenerates when comparing definitions.
2071
2442
  this.eventEmitter?.emitSchemaChange?.({
2072
2443
  type: 'create',
2073
2444
  objectType: 'index',
2074
2445
  schemaName: this.schemaName,
2075
2446
  objectName: indexName,
2447
+ ddl: generateIndexDDL(newIndexSchemaEntry, finalNewTableSchema),
2076
2448
  });
2077
2449
  logger.operation('Create Index', this._tableName, { indexName });
2078
2450
  }
@@ -2135,6 +2507,7 @@ export class MemoryTableManager {
2135
2507
  objectType: 'index',
2136
2508
  schemaName: this.schemaName,
2137
2509
  objectName: indexName,
2510
+ ddl: generateDropIndexDDL(this.schemaName, indexName),
2138
2511
  });
2139
2512
  logger.operation('Drop Index', this._tableName, { indexName });
2140
2513
  }
@@ -2285,6 +2658,14 @@ export class MemoryTableManager {
2285
2658
  this.baseLayer.rebuildAllSecondaryIndexes();
2286
2659
  this.tableSchema = newSchema;
2287
2660
  this.initializePrimaryKeyFunctions();
2661
+ // Hand the new schema to the DDL transaction's own frozen layers, as `dropConstraint`
2662
+ // does: a renamed covering index has to move under its new key in each layer's index
2663
+ // map (the old key is dropped, the new one rebuilt over the parent's already-renamed
2664
+ // tree — hence oldest-first), and even the pure schema-only classes (CHECK / FK) must
2665
+ // re-freeze the new schema OBJECT, or `commitTransaction`'s snapshot-wrap identity
2666
+ // check (`readLayer.getSchema() === this.tableSchema`) skips a savepoint snapshot
2667
+ // restored across this ALTER and drops the transaction's staged rows at COMMIT.
2668
+ this.adoptSchemaOnOpenLayers(newSchema);
2288
2669
  this.eventEmitter?.emitSchemaChange?.({
2289
2670
  type: 'alter',
2290
2671
  objectType: 'table',
@@ -2529,6 +2910,19 @@ export class MemoryTableManager {
2529
2910
  // bug. A detached connection always has `pendingTransactionLayer === null` (disconnect
2530
2911
  // defers while a pending layer is uncommitted), so this never discards in-flight writes.
2531
2912
  this.repointRegisteredConnections();
2913
+ // Re-pointing `readLayer` is not enough: a savepoint taken while a connection held no
2914
+ // pending layer stored the layer it was READING as the view to restore. Consolidation
2915
+ // has just drained that layer into the base, and the caller is about to reshape the
2916
+ // base's rows — so the stored reference is a pre-change snapshot of the committed rows.
2917
+ // Left alone, the transaction's `rollback to savepoint` reinstates it and the old column
2918
+ // shape commits over the new one.
2919
+ //
2920
+ // Every connection, not just the re-pointed ones: the DDL issuer is exempt from the
2921
+ // sweeps above (its read view holds its own uncommitted rows) but its own lazy markers
2922
+ // are just as stale.
2923
+ for (const connection of this.knownConnections()) {
2924
+ connection.repointLazySavepointsToCommittedHead(this.baseLayer);
2925
+ }
2532
2926
  logger.debugLog(`Schema change safety check passed for ${this._tableName}. Current committed layer is base.`);
2533
2927
  }
2534
2928
  /**
@@ -2548,6 +2942,34 @@ export class MemoryTableManager {
2548
2942
  mc.readLayer = this.baseLayer;
2549
2943
  }
2550
2944
  }
2945
+ /**
2946
+ * Moves this manager's Database-registered connections from `<schema>.<oldName>` to
2947
+ * `<schema>.<newName>`, for `ALTER TABLE ... RENAME TO`.
2948
+ *
2949
+ * `MemoryVirtualTableConnection.tableName` is set once at registration, and
2950
+ * `Database.getConnectionsForTable` matches on exactly that string — so without this
2951
+ * sweep a rename makes the transaction's own connection invisible to
2952
+ * {@link registeredConnections} and everything built on it: {@link ddlConnection},
2953
+ * {@link knownConnections}, {@link repointRegisteredConnections},
2954
+ * {@link openTransactionLayersOldestFirst}. The visible symptom is a second ALTER in
2955
+ * the same transaction failing with BUSY — {@link ensureSchemaChangeSafety} exempts
2956
+ * `ddlConnection()` from its "nobody else may hold open work" sweep, and with the
2957
+ * registry stale that is `undefined`, so the transaction's own connection is judged a
2958
+ * stranger. `MemoryTable.ensureConnection` would likewise stop reusing it and register
2959
+ * a second connection for the same table.
2960
+ *
2961
+ * Must run BEFORE `_tableName` moves — it looks the connections up under the old name.
2962
+ */
2963
+ rekeyRegisteredConnections(oldName, newName) {
2964
+ const newQualifiedName = `${this.schemaName}.${newName}`;
2965
+ for (const c of this.db.getConnectionsForTable(`${this.schemaName}.${oldName}`)) {
2966
+ if (!(c instanceof MemoryVirtualTableConnection))
2967
+ continue;
2968
+ if (c.getMemoryConnection().tableManager !== this)
2969
+ continue;
2970
+ c.rename(newQualifiedName);
2971
+ }
2972
+ }
2551
2973
  /** Every Database-registered {@link MemoryTableConnection} backed by this manager. */
2552
2974
  *registeredConnections() {
2553
2975
  const qualifiedName = `${this.schemaName}.${this._tableName}`;
@@ -2625,20 +3047,12 @@ export class MemoryTableManager {
2625
3047
  convertBaseRows(colIndex, convert, convertNulls = false) {
2626
3048
  const out = [];
2627
3049
  for (const row of iteratePrimaryRows(this.baseLayer.primaryTree)) {
2628
- const oldVal = row[colIndex];
2629
- if (oldVal === null && !convertNulls) {
2630
- out.push(row);
2631
- continue;
2632
- }
2633
- let newVal;
2634
3050
  try {
2635
- newVal = convert(oldVal);
3051
+ out.push(convertRowAtIndex(row, colIndex, convert, convertNulls));
2636
3052
  }
2637
3053
  catch {
2638
3054
  out.push(row); // shadowed unconvertible value — keep as-is
2639
- continue;
2640
3055
  }
2641
- out.push(row.map((v, i) => i === colIndex ? newVal : v));
2642
3056
  }
2643
3057
  return out;
2644
3058
  }
@@ -2652,31 +3066,47 @@ export class MemoryTableManager {
2652
3066
  * the transaction's pending rows outside this manager, so only it can name the rows the
2653
3067
  * issuing connection actually sees; when it supplies them, they are the judged set and this
2654
3068
  * manager's own committed rows are ignored. See `vtab/module.ts` {@link EffectiveRowSource}.
3069
+ *
3070
+ * `mapRow` judges the rows in a form they do not yet have on disk: an `alter column` that
3071
+ * REWRITES values passes the per-row conversion here, so the probe sees the post-ALTER
3072
+ * values. Applied to whichever stream is used; the mapping is a read-side wrap and mutates
3073
+ * nothing.
2655
3074
  */
2656
- async validateUniqueOverEffectiveRows(indexSchema, schema, rows) {
3075
+ async validateUniqueOverEffectiveRows(indexSchema, schema, rows, mapRow) {
2657
3076
  const probe = new MemoryIndex(indexSchema, schema.columns, this.collationResolver, this.primaryKeyFunctions.compare, this.primaryKeyFunctions.encode, schema.name);
2658
3077
  if (rows) {
2659
- await populateIndexFromRowsAsync(rows(), probe, this.primaryKeyFunctions.extractFromRow, true, this._tableName, schema.columns);
3078
+ const stream = mapRow ? mapRowsAsync(rows(), mapRow) : rows();
3079
+ await populateIndexFromRowsAsync(stream, probe, this.primaryKeyFunctions.extractFromRow, true, this._tableName, schema.columns);
2660
3080
  return;
2661
3081
  }
2662
- populateIndexFromRows(this.effectiveDdlRows(), probe, this.primaryKeyFunctions.extractFromRow, true, this._tableName, schema.columns);
3082
+ const own = this.effectiveDdlRows();
3083
+ populateIndexFromRows(mapRow ? mapRows(own, mapRow) : own, probe, this.primaryKeyFunctions.extractFromRow, true, this._tableName, schema.columns);
2663
3084
  }
2664
3085
  /**
2665
- * `ALTER COLUMN … SET COLLATE` arm of {@link validateUniqueOverEffectiveRows}: rejects the
2666
- * change when it makes two of the DDL transaction's effective rows collide under any
2667
- * uniqueness-enforcing structure that orders by the altered column. Runs before anything is
2668
- * mutated, so a rejection leaves the table and schema untouched and the transaction usable.
3086
+ * `ALTER COLUMN` arm of {@link validateUniqueOverEffectiveRows}: rejects the change when it
3087
+ * makes two of the DDL transaction's effective rows collide under any uniqueness-enforcing
3088
+ * structure that covers the altered column. Runs before anything is mutated, so a rejection
3089
+ * leaves the table and schema untouched and the transaction usable.
2669
3090
  *
2670
- * `newSchema` carries the post-change per-column collations, so each probe index compares
2671
- * exactly as the rebuilt structure will. Indexes that do not mention the column keep their
2672
- * keys and need no re-check.
3091
+ * Serves both families of change that can collapse two distinct values onto one:
3092
+ *
3093
+ * - `SET COLLATE` re-keys the structures under a new comparator; the stored values are
3094
+ * untouched, so no `mapRow` is passed.
3095
+ * - a value REWRITE (`SET DATA TYPE`, or a `SET NOT NULL` null → DEFAULT backfill) leaves
3096
+ * the comparators alone but changes the values; the caller passes a `mapRow` that applies
3097
+ * the same per-row conversion the base rewrite will, so the probe judges the post-ALTER
3098
+ * values.
3099
+ *
3100
+ * `newSchema` carries the post-change per-column collations AND logical types, so each probe
3101
+ * index compares exactly as the rebuilt structure will (it matters for e.g. `text → real`).
3102
+ * Indexes that do not mention the column keep their keys and need no re-check.
2673
3103
  *
2674
3104
  * NOTE: walks `schema.indexes`, so a UNIQUE constraint covered by a row-time materialized
2675
3105
  * view rather than its auto-index (`findIndexForConstraint` prefers the MV) is not re-checked
2676
3106
  * here. The auto-index always exists alongside, so the structure is still validated — but if
2677
3107
  * an MV-only covering shape ever becomes reachable, this walk must follow it.
2678
3108
  */
2679
- async validateRekeyedUniqueStructures(newSchema, alteredColumnIndex, rows) {
3109
+ async validateRekeyedUniqueStructures(newSchema, alteredColumnIndex, rows, mapRow) {
2680
3110
  // NOTE: the probe index carries the manager's PRE-change `primaryKeyFunctions` (the new
2681
3111
  // ones cannot exist before the schema swaps). Only the probe's per-entry PK bookkeeping
2682
3112
  // uses them, and every effective row already has a distinct PK under the old encoder, so
@@ -2688,28 +3118,42 @@ export class MemoryTableManager {
2688
3118
  if (!indexEnforcesUnique(newSchema, indexSchema))
2689
3119
  continue;
2690
3120
  // `rows` is re-callable, so each structure gets a fresh stream.
2691
- await this.validateUniqueOverEffectiveRows(indexSchema, newSchema, rows);
3121
+ await this.validateUniqueOverEffectiveRows(indexSchema, newSchema, rows, mapRow);
2692
3122
  }
2693
3123
  }
2694
3124
  /**
2695
3125
  * PRIMARY KEY arm of the `alter column … set collate` pre-pass. Runs before anything is
2696
3126
  * mutated, so a rejection leaves the table, the schema and the transaction untouched.
2697
3127
  *
2698
- * The primary tree is a map, not a multi-map, so unlike a secondary index — it cannot
2699
- * physically hold two rows whose keys collapse under the new comparator. Every layer the
2700
- * DDL connection still reads through therefore has to be collision-free, not just the
2701
- * transaction's effective view:
3128
+ * Asks two questions, in order, over two DIFFERENT row sets:
3129
+ *
3130
+ * 1. **Is the change legal at all?** probes the rows the DDL transaction can SEE: the
3131
+ * wrapper-supplied `rows` when a wrapper module (the isolation layer) holds the
3132
+ * transaction's pending rows outside this manager, otherwise this manager's own
3133
+ * layered view ({@link effectiveDdlRows}). A duplicate here is visible to a `select`
3134
+ * in this transaction, so the change is simply illegal → `CONSTRAINT`, naming the
3135
+ * colliding key.
3136
+ * 2. **Can the structures physically carry it?** — probes every layer of this manager's
3137
+ * own chain. The primary tree is a map, not a multi-map, so — unlike a secondary
3138
+ * index — no tree that a `rollback` / `rollback to savepoint` could restore may hold
3139
+ * two rows whose keys collapse under the new comparator. A collision confined to
3140
+ * committed rows the transaction has DELETED is therefore refused by physical
3141
+ * necessity, not because the data is invalid: the base must keep both rows for a
3142
+ * rollback, and a base re-keyed under the new collation could not represent the pair
3143
+ * at all → `BUSY` with the same "commit/rollback and retry" posture as
3144
+ * {@link ensureSchemaChangeSafety}. The persistent store backend refuses the same
3145
+ * shape for the same reason (its committed rows equally survive a rollback — see
3146
+ * `backlog/bug-store-pk-collate-rejects-deleted-row-collision`); accepting it would
3147
+ * take transaction-scoped DDL (`backlog/feat-transactional-ddl-native-backends`),
3148
+ * which Quereus does not have.
2702
3149
  *
2703
- * 1. **The effective view collides** `CONSTRAINT`. The duplicate is visible to a `select`
2704
- * in this transaction; the change is simply illegal.
2705
- * 2. **A layer beneath it collides** `BUSY`. Those rows are not visible now, but every
2706
- * such layer is the copy-on-write base the view reads through, and one of them may be a
2707
- * savepoint snapshot a `rollback to savepoint` must restore. A re-keyed tree could not
2708
- * represent the pair at all. Committing (or rolling back) settles the transaction and
2709
- * the ALTER can be retried — the same "commit/rollback and retry" posture as
2710
- * {@link ensureSchemaChangeSafety}.
3150
+ * The passes judge different sets because a wrapper's effective stream and this manager's
3151
+ * layers genuinely diverge: staged inserts exist only in the stream, deleted committed
3152
+ * rows only in the layers. When `rows` is supplied, the view layer holds the COMMITTED
3153
+ * rows not a subset of what pass 1 judged so pass 2 starts AT the view; when `rows`
3154
+ * is absent, pass 1 judged exactly the view's rows and pass 2 starts at its parent.
2711
3155
  *
2712
- * Case 2 is deliberately conservative. The chain holds one immutable layer per statement
3156
+ * Pass 2 is deliberately conservative. The chain holds one immutable layer per statement
2713
3157
  * boundary (see `MemoryTableConnection.createSavepoint`'s eager path), so it rejects any
2714
3158
  * transaction that has held a colliding pair at ANY statement boundary — even one whose
2715
3159
  * final view is clean and whose intermediate layer no savepoint can reach. Narrowing that
@@ -2721,40 +3165,78 @@ export class MemoryTableManager {
2721
3165
  * with no collisions anywhere in the chain, every primary key resolves to at most one row
2722
3166
  * in each layer under the new comparator.
2723
3167
  */
2724
- validateRekeyedPrimaryKey(newSchema) {
3168
+ async validateRekeyedPrimaryKey(newSchema, rows) {
2725
3169
  const newPkFunctions = createPrimaryKeyFunctions(newSchema, this.collationResolver);
2726
3170
  const connection = this.ddlConnection();
2727
3171
  const view = connection
2728
3172
  ? (connection.pendingTransactionLayer ?? connection.readLayer)
2729
3173
  : this.baseLayer;
2730
- this.assertNoPrimaryKeyCollision(view, newPkFunctions, StatusCode.CONSTRAINT, `UNIQUE constraint failed: ${this._tableName} primary key collides under new collation`);
3174
+ await this.assertNoPrimaryKeyCollisionInRows(rows ? rows() : this.effectiveDdlRows(), newPkFunctions, primaryKeyArity(newSchema) !== 1);
2731
3175
  // `ensureSchemaChangeSafety` has already drained every committed layer into the base and
2732
- // rejected sibling connections with open work, so the chain below the view holds only
2733
- // this transaction's own layers.
2734
- for (let layer = view.getParent(); layer; layer = layer.getParent()) {
2735
- this.assertNoPrimaryKeyCollision(layer, newPkFunctions, StatusCode.BUSY, `Cannot change the collation of a primary key column of table ${this._tableName}: `
2736
- + `rows this transaction has removed still collide under the new collation and must survive a rollback. `
2737
- + `Commit/rollback and retry.`);
3176
+ // rejected sibling connections with open work, so this walk covers the transaction's own
3177
+ // layers plus the base — every tree a rollback could restore.
3178
+ for (let layer = rows ? view : view.getParent(); layer; layer = layer.getParent()) {
3179
+ this.assertNoPrimaryKeyCollisionInLayer(layer, newPkFunctions);
3180
+ }
3181
+ }
3182
+ /**
3183
+ * A duplicate-key detector over the new primary key functions: feed it rows, and it returns
3184
+ * the key the moment one repeats. Shared by both arms of {@link validateRekeyedPrimaryKey}
3185
+ * so the async (effective-row) arm and the sync (layer-tree) arm cannot disagree about what
3186
+ * counts as a collision.
3187
+ *
3188
+ * NOTE: the probe holds every row it has seen, because the BTree derives its key from the
3189
+ * stored value — so an ALTER over a large table transiently doubles that table's row
3190
+ * references. If a wide table ever makes this the memory peak, key the probe by the PK
3191
+ * encoding (`primaryKeyFunctions.encode`) into a `Set` instead of by the row.
3192
+ */
3193
+ makePrimaryKeyProbe(pkFunctions) {
3194
+ const probe = new BTree((row) => pkFunctions.extractFromRow(row), pkFunctions.compare);
3195
+ return (row) => {
3196
+ const key = pkFunctions.extractFromRow(row);
3197
+ if (probe.get(key) !== undefined)
3198
+ return key;
3199
+ probe.insert(row);
3200
+ return undefined;
3201
+ };
3202
+ }
3203
+ /**
3204
+ * Legality arm of {@link validateRekeyedPrimaryKey}: raises `CONSTRAINT`, naming the key,
3205
+ * when two of the rows the DDL transaction can SEE share a primary key under `pkFunctions`.
3206
+ * Takes a wrapper's merged async stream or this manager's own synchronous layered view.
3207
+ */
3208
+ async assertNoPrimaryKeyCollisionInRows(rows, pkFunctions, keyIsTuple) {
3209
+ const seen = this.makePrimaryKeyProbe(pkFunctions);
3210
+ for await (const row of rows) {
3211
+ const key = seen(row);
3212
+ if (key === undefined)
3213
+ continue;
3214
+ const keyDesc = keyParts(key, keyIsTuple).map(formatKeyValue).join(', ');
3215
+ throw new QuereusError(`UNIQUE constraint failed: ${this._tableName} primary key collides under new collation (key: ${keyDesc})`, StatusCode.CONSTRAINT);
2738
3216
  }
2739
3217
  }
2740
3218
  /**
2741
- * Raises `code` when two of `layer`'s rows share a primary key under `pkFunctions`.
3219
+ * Representability arm of {@link validateRekeyedPrimaryKey}: raises `BUSY` when two of
3220
+ * `layer`'s physical rows share a primary key under `pkFunctions`. Its rows are ones a
3221
+ * rollback could restore, not ones the transaction can see, so this is never a statement
3222
+ * about the data's validity — hence the retryable status and wording.
2742
3223
  *
2743
3224
  * NOTE: O(rows) per layer, so O(layers × rows) for a whole chain — one more full pass than
2744
3225
  * the base rebuild the caller is about to do anyway. Fine for a statement this rare; if a
2745
3226
  * deep savepoint stack over a large table ever makes an ALTER slow, note that a layer's rows
2746
3227
  * differ from its parent's only at the keys it wrote, so the walk can be narrowed to those.
2747
3228
  */
2748
- assertNoPrimaryKeyCollision(layer, pkFunctions, code, message) {
3229
+ assertNoPrimaryKeyCollisionInLayer(layer, pkFunctions) {
2749
3230
  const tree = layer.getModificationTree('primary');
2750
3231
  if (!tree)
2751
3232
  return;
2752
- const probe = new BTree((row) => pkFunctions.extractFromRow(row), pkFunctions.compare);
3233
+ const seen = this.makePrimaryKeyProbe(pkFunctions);
2753
3234
  for (const row of iteratePrimaryRows(tree)) {
2754
- const key = pkFunctions.extractFromRow(row);
2755
- if (probe.get(key) !== undefined)
2756
- throw new QuereusError(message, code);
2757
- probe.insert(row);
3235
+ if (seen(row) !== undefined) {
3236
+ throw new QuereusError(`Cannot change the collation of a primary key column of table ${this._tableName}: `
3237
+ + `rows this transaction has removed still collide under the new collation and must survive a rollback. `
3238
+ + `Commit/rollback and retry.`, StatusCode.BUSY);
3239
+ }
2758
3240
  }
2759
3241
  }
2760
3242
  /**
@@ -2792,7 +3274,7 @@ export class MemoryTableManager {
2792
3274
  * `convertNulls` routes null own-values through `convert` (the SET NOT NULL null → DEFAULT map);
2793
3275
  * SET DATA TYPE leaves them untouched. No-op in autocommit.
2794
3276
  *
2795
- * PRIMARY-KEY columns never reach here — a physical retype of a key column is rejected before
3277
+ * PRIMARY-KEY columns never reach here — any retype of a key column is rejected before
2796
3278
  * any mutation, and SET NOT NULL leaves the key bytes unchanged — so the primary tree's keys are
2797
3279
  * stable and no re-key is needed.
2798
3280
  */
@@ -2801,14 +3283,58 @@ export class MemoryTableManager {
2801
3283
  layer.convertColumn(colIndex, convert, newSchema, convertNulls);
2802
3284
  }
2803
3285
  }
3286
+ /**
3287
+ * Phase 1 of propagating `add column` / `drop column` into every open transaction layer:
3288
+ * computes each layer's net own-writes rewritten to the new column set, WITHOUT mutating
3289
+ * anything (see {@link TransactionLayer.prepareReshapedColumns}). Runs BEFORE the base is
3290
+ * touched, because `reshapeRow` can throw — ADD COLUMN's per-row backfill evaluates an
3291
+ * engine-supplied `default (new.<col>)` expression against each pending row, and enforces
3292
+ * NOT NULL on the result — and a failure part-way through a mutation pass would leave the
3293
+ * base and the already-visited layers at the new arity with the rest at the old, with no
3294
+ * undo. Failing here instead rejects the ALTER with every structure untouched, exactly like
3295
+ * the pre-mutation validation passes of `alterColumn`.
3296
+ *
3297
+ * The returned plans pin the layer set: {@link installReshapeOnOpenLayers} walks the plans,
3298
+ * not a fresh chain snapshot, so the two phases cannot disagree about which layers exist.
3299
+ * Reading each layer's effective rows here — before the base rebuild — yields the same rows
3300
+ * the single-pass prototype read after it: a layer's pre-reshape tree resolves against the
3301
+ * parent's OLD tree either way (the base rebuild REPLACES the base's tree object; it never
3302
+ * mutates the one the layers inherit from).
3303
+ */
3304
+ async prepareReshapeOnOpenLayers(reshapeRow, reshapeEventRow) {
3305
+ const plans = [];
3306
+ for (const layer of this.openTransactionLayersOldestFirst()) {
3307
+ plans.push({ layer, prepared: await layer.prepareReshapedColumns(reshapeRow, reshapeEventRow) });
3308
+ }
3309
+ return plans;
3310
+ }
3311
+ /**
3312
+ * Phase 2: installs the prepared reshape into each open transaction layer, oldest-first
3313
+ * (the plans were gathered in that order), after the base has been rebuilt at the new arity
3314
+ * — each layer's rebuilt tree inherits copy-on-write from its parent's NEW one. Synchronous
3315
+ * and mutation-only; everything fallible ran in {@link prepareReshapeOnOpenLayers}.
3316
+ *
3317
+ * Beyond keeping the transaction's own reads correct, this is what makes the
3318
+ * `commitTransaction` snapshot-wrap identity check (`readLayer.getSchema() ===
3319
+ * this.tableSchema`) pass after a `rollback to savepoint`: without it the eager savepoint
3320
+ * snapshot keeps its frozen pre-ALTER schema, the wrap is skipped, and the snapshot's rows
3321
+ * are silently dropped at commit.
3322
+ */
3323
+ installReshapeOnOpenLayers(newSchema, plans) {
3324
+ for (const { layer, prepared } of plans) {
3325
+ layer.installReshapedColumns(newSchema, prepared);
3326
+ }
3327
+ }
2804
3328
  /**
2805
3329
  * The DDL connection's open {@link TransactionLayer}s, oldest-first, base excluded — its
2806
3330
  * pending layer and every savepoint snapshot below it, the layers on the view's parent chain
2807
3331
  * above the base. Empty in autocommit (no connection, or the view IS the base).
2808
3332
  *
2809
- * Both schema adoption ({@link adoptSchemaOnOpenLayers}) and value conversion
2810
- * ({@link convertColumnOnOpenLayers}) MUST apply oldest-first: each layer rebuilds its
2811
- * structures over its parent's, so the parent's must already be the new/converted ones.
3333
+ * Schema adoption ({@link adoptSchemaOnOpenLayers}), value conversion
3334
+ * ({@link convertColumnOnOpenLayers}), and column-set reshape
3335
+ * ({@link prepareReshapeOnOpenLayers} / {@link installReshapeOnOpenLayers}) MUST all apply
3336
+ * oldest-first: each layer rebuilds its structures over its parent's, so the parent's must
3337
+ * already be the new/converted/reshaped ones.
2812
3338
  *
2813
3339
  * NOTE: the walk takes every TransactionLayer below the view, which normally means the pending
2814
3340
  * layer and its savepoint snapshots. A committed layer already drained into the base by
@@ -2841,6 +3367,17 @@ export class MemoryTableManager {
2841
3367
  logger.debugLog(`[Consolidate] Copying data from transaction layer ${transactionLayer.getLayerId()} to base layer for ${this._tableName}`);
2842
3368
  // Copy all data from the transaction layer to the base layer
2843
3369
  await this.copyTransactionDataToBase(transactionLayer);
3370
+ // The drained chain's pending-change event logs were already delivered when
3371
+ // each layer committed, but the layers can remain in the DDL connection's own
3372
+ // open-transaction parent chain. Once the base becomes the committed head, the
3373
+ // commit-time collection (`collectPendingChanges`) walks that chain down to
3374
+ // the base and would re-deliver them — clear the logs so consolidation is
3375
+ // event-neutral. (Savepoint snapshots of the OPEN transaction sit above the
3376
+ // committed head, not in this chain, so their un-emitted events are kept.)
3377
+ for (let cur = transactionLayer; cur && cur !== this.baseLayer; cur = cur.getParent()) {
3378
+ if (cur instanceof TransactionLayer)
3379
+ cur.clearPendingChanges();
3380
+ }
2844
3381
  // Force all connections to read from the base layer
2845
3382
  for (const conn of this.connections.values()) {
2846
3383
  if (conn.readLayer === transactionLayer) {