@quereus/quereus 4.3.2 → 4.4.1

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 (396) hide show
  1. package/README.md +5 -0
  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-external-changes.d.ts +2 -2
  10. package/dist/src/core/database-external-changes.d.ts.map +1 -1
  11. package/dist/src/core/database-external-changes.js +12 -9
  12. package/dist/src/core/database-external-changes.js.map +1 -1
  13. package/dist/src/core/database-materialized-views-apply.d.ts +156 -14
  14. package/dist/src/core/database-materialized-views-apply.d.ts.map +1 -1
  15. package/dist/src/core/database-materialized-views-apply.js +370 -65
  16. package/dist/src/core/database-materialized-views-apply.js.map +1 -1
  17. package/dist/src/core/database-materialized-views-plan-builders.d.ts +66 -1
  18. package/dist/src/core/database-materialized-views-plan-builders.d.ts.map +1 -1
  19. package/dist/src/core/database-materialized-views-plan-builders.js +368 -8
  20. package/dist/src/core/database-materialized-views-plan-builders.js.map +1 -1
  21. package/dist/src/core/database-materialized-views-plans.d.ts +257 -30
  22. package/dist/src/core/database-materialized-views-plans.d.ts.map +1 -1
  23. package/dist/src/core/database-materialized-views.d.ts +99 -42
  24. package/dist/src/core/database-materialized-views.d.ts.map +1 -1
  25. package/dist/src/core/database-materialized-views.js +198 -121
  26. package/dist/src/core/database-materialized-views.js.map +1 -1
  27. package/dist/src/core/database.d.ts +5 -0
  28. package/dist/src/core/database.d.ts.map +1 -1
  29. package/dist/src/core/database.js +41 -18
  30. package/dist/src/core/database.js.map +1 -1
  31. package/dist/src/core/internal-statement-cache.d.ts +18 -0
  32. package/dist/src/core/internal-statement-cache.d.ts.map +1 -0
  33. package/dist/src/core/internal-statement-cache.js +211 -0
  34. package/dist/src/core/internal-statement-cache.js.map +1 -0
  35. package/dist/src/func/builtins/aggregate.d.ts +13 -12
  36. package/dist/src/func/builtins/aggregate.d.ts.map +1 -1
  37. package/dist/src/func/builtins/aggregate.js +146 -62
  38. package/dist/src/func/builtins/aggregate.js.map +1 -1
  39. package/dist/src/func/builtins/builtin-window-functions.d.ts.map +1 -1
  40. package/dist/src/func/builtins/builtin-window-functions.js +63 -48
  41. package/dist/src/func/builtins/builtin-window-functions.js.map +1 -1
  42. package/dist/src/func/registration.d.ts +19 -1
  43. package/dist/src/func/registration.d.ts.map +1 -1
  44. package/dist/src/func/registration.js +41 -0
  45. package/dist/src/func/registration.js.map +1 -1
  46. package/dist/src/index.d.ts +8 -8
  47. package/dist/src/index.d.ts.map +1 -1
  48. package/dist/src/index.js +12 -11
  49. package/dist/src/index.js.map +1 -1
  50. package/dist/src/parser/parser.d.ts +6 -0
  51. package/dist/src/parser/parser.d.ts.map +1 -1
  52. package/dist/src/parser/parser.js +45 -34
  53. package/dist/src/parser/parser.js.map +1 -1
  54. package/dist/src/planner/analysis/comparison-collation.d.ts +2 -0
  55. package/dist/src/planner/analysis/comparison-collation.d.ts.map +1 -1
  56. package/dist/src/planner/analysis/comparison-collation.js +1 -1
  57. package/dist/src/planner/analysis/comparison-collation.js.map +1 -1
  58. package/dist/src/planner/analysis/equi-correlation.d.ts +26 -0
  59. package/dist/src/planner/analysis/equi-correlation.d.ts.map +1 -0
  60. package/dist/src/planner/analysis/equi-correlation.js +62 -0
  61. package/dist/src/planner/analysis/equi-correlation.js.map +1 -0
  62. package/dist/src/planner/analysis/expression-fingerprint.d.ts.map +1 -1
  63. package/dist/src/planner/analysis/expression-fingerprint.js +29 -0
  64. package/dist/src/planner/analysis/expression-fingerprint.js.map +1 -1
  65. package/dist/src/planner/analysis/query-rewrite-matcher.d.ts +60 -18
  66. package/dist/src/planner/analysis/query-rewrite-matcher.d.ts.map +1 -1
  67. package/dist/src/planner/analysis/query-rewrite-matcher.js +95 -42
  68. package/dist/src/planner/analysis/query-rewrite-matcher.js.map +1 -1
  69. package/dist/src/planner/analysis/sat-checker.d.ts.map +1 -1
  70. package/dist/src/planner/analysis/sat-checker.js +43 -4
  71. package/dist/src/planner/analysis/sat-checker.js.map +1 -1
  72. package/dist/src/planner/analysis/scalar-subqueries.d.ts +28 -0
  73. package/dist/src/planner/analysis/scalar-subqueries.d.ts.map +1 -0
  74. package/dist/src/planner/analysis/scalar-subqueries.js +60 -0
  75. package/dist/src/planner/analysis/scalar-subqueries.js.map +1 -0
  76. package/dist/src/planner/building/delete.d.ts.map +1 -1
  77. package/dist/src/planner/building/delete.js +10 -3
  78. package/dist/src/planner/building/delete.js.map +1 -1
  79. package/dist/src/planner/building/expression.d.ts.map +1 -1
  80. package/dist/src/planner/building/expression.js +87 -6
  81. package/dist/src/planner/building/expression.js.map +1 -1
  82. package/dist/src/planner/building/foreign-key-builder.d.ts +44 -1
  83. package/dist/src/planner/building/foreign-key-builder.d.ts.map +1 -1
  84. package/dist/src/planner/building/foreign-key-builder.js +54 -0
  85. package/dist/src/planner/building/foreign-key-builder.js.map +1 -1
  86. package/dist/src/planner/building/insert.d.ts.map +1 -1
  87. package/dist/src/planner/building/insert.js +9 -12
  88. package/dist/src/planner/building/insert.js.map +1 -1
  89. package/dist/src/planner/building/select.d.ts.map +1 -1
  90. package/dist/src/planner/building/select.js +15 -1
  91. package/dist/src/planner/building/select.js.map +1 -1
  92. package/dist/src/planner/building/update.d.ts.map +1 -1
  93. package/dist/src/planner/building/update.js +13 -4
  94. package/dist/src/planner/building/update.js.map +1 -1
  95. package/dist/src/planner/building/with.d.ts.map +1 -1
  96. package/dist/src/planner/building/with.js +5 -8
  97. package/dist/src/planner/building/with.js.map +1 -1
  98. package/dist/src/planner/cache/materialization-advisory.d.ts +33 -0
  99. package/dist/src/planner/cache/materialization-advisory.d.ts.map +1 -1
  100. package/dist/src/planner/cache/materialization-advisory.js +164 -28
  101. package/dist/src/planner/cache/materialization-advisory.js.map +1 -1
  102. package/dist/src/planner/cache/reference-graph.d.ts +0 -4
  103. package/dist/src/planner/cache/reference-graph.d.ts.map +1 -1
  104. package/dist/src/planner/cache/reference-graph.js +5 -13
  105. package/dist/src/planner/cache/reference-graph.js.map +1 -1
  106. package/dist/src/planner/cost/index.d.ts +28 -7
  107. package/dist/src/planner/cost/index.d.ts.map +1 -1
  108. package/dist/src/planner/cost/index.js +26 -0
  109. package/dist/src/planner/cost/index.js.map +1 -1
  110. package/dist/src/planner/framework/pass.js +3 -3
  111. package/dist/src/planner/nodes/cache-node.d.ts +18 -2
  112. package/dist/src/planner/nodes/cache-node.d.ts.map +1 -1
  113. package/dist/src/planner/nodes/cache-node.js +42 -3
  114. package/dist/src/planner/nodes/cache-node.js.map +1 -1
  115. package/dist/src/planner/nodes/cte-node.d.ts +15 -1
  116. package/dist/src/planner/nodes/cte-node.d.ts.map +1 -1
  117. package/dist/src/planner/nodes/cte-node.js +12 -2
  118. package/dist/src/planner/nodes/cte-node.js.map +1 -1
  119. package/dist/src/planner/nodes/dml-executor-node.d.ts +0 -9
  120. package/dist/src/planner/nodes/dml-executor-node.d.ts.map +1 -1
  121. package/dist/src/planner/nodes/dml-executor-node.js.map +1 -1
  122. package/dist/src/planner/nodes/recursive-cte-node.d.ts +30 -1
  123. package/dist/src/planner/nodes/recursive-cte-node.d.ts.map +1 -1
  124. package/dist/src/planner/nodes/recursive-cte-node.js +30 -3
  125. package/dist/src/planner/nodes/recursive-cte-node.js.map +1 -1
  126. package/dist/src/planner/nodes/scalar.d.ts.map +1 -1
  127. package/dist/src/planner/nodes/scalar.js +26 -3
  128. package/dist/src/planner/nodes/scalar.js.map +1 -1
  129. package/dist/src/planner/optimizer.d.ts.map +1 -1
  130. package/dist/src/planner/optimizer.js +129 -8
  131. package/dist/src/planner/optimizer.js.map +1 -1
  132. package/dist/src/planner/rules/access/rule-select-access-path.d.ts.map +1 -1
  133. package/dist/src/planner/rules/access/rule-select-access-path.js +43 -18
  134. package/dist/src/planner/rules/access/rule-select-access-path.js.map +1 -1
  135. package/dist/src/planner/rules/cache/rule-cte-optimization.d.ts.map +1 -1
  136. package/dist/src/planner/rules/cache/rule-cte-optimization.js +8 -1
  137. package/dist/src/planner/rules/cache/rule-cte-optimization.js.map +1 -1
  138. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.d.ts.map +1 -1
  139. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js +72 -54
  140. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js.map +1 -1
  141. package/dist/src/planner/rules/cache/rule-mutating-subquery-cache.d.ts.map +1 -1
  142. package/dist/src/planner/rules/cache/rule-mutating-subquery-cache.js +6 -0
  143. package/dist/src/planner/rules/cache/rule-mutating-subquery-cache.js.map +1 -1
  144. package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.d.ts +25 -0
  145. package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.d.ts.map +1 -0
  146. package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.js +176 -0
  147. package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.js.map +1 -0
  148. package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.d.ts +31 -0
  149. package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.d.ts.map +1 -0
  150. package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.js +68 -0
  151. package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.js.map +1 -0
  152. package/dist/src/planner/rules/join/rule-fanout-lookup-join.d.ts.map +1 -1
  153. package/dist/src/planner/rules/join/rule-fanout-lookup-join.js +1 -54
  154. package/dist/src/planner/rules/join/rule-fanout-lookup-join.js.map +1 -1
  155. package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.js +8 -1
  156. package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.js.map +1 -1
  157. package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.d.ts +191 -0
  158. package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.d.ts.map +1 -0
  159. package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.js +627 -0
  160. package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.js.map +1 -0
  161. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.d.ts +10 -1
  162. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.d.ts.map +1 -1
  163. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js +242 -51
  164. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js.map +1 -1
  165. package/dist/src/runtime/cache/shared-cache.d.ts +7 -0
  166. package/dist/src/runtime/cache/shared-cache.d.ts.map +1 -1
  167. package/dist/src/runtime/cache/shared-cache.js +34 -1
  168. package/dist/src/runtime/cache/shared-cache.js.map +1 -1
  169. package/dist/src/runtime/deferred-constraint-queue.d.ts.map +1 -1
  170. package/dist/src/runtime/deferred-constraint-queue.js +10 -0
  171. package/dist/src/runtime/deferred-constraint-queue.js.map +1 -1
  172. package/dist/src/runtime/emit/aggregate-setup.d.ts +49 -0
  173. package/dist/src/runtime/emit/aggregate-setup.d.ts.map +1 -0
  174. package/dist/src/runtime/emit/aggregate-setup.js +99 -0
  175. package/dist/src/runtime/emit/aggregate-setup.js.map +1 -0
  176. package/dist/src/runtime/emit/aggregate.d.ts +2 -3
  177. package/dist/src/runtime/emit/aggregate.d.ts.map +1 -1
  178. package/dist/src/runtime/emit/aggregate.js +10 -84
  179. package/dist/src/runtime/emit/aggregate.js.map +1 -1
  180. package/dist/src/runtime/emit/alter-table.d.ts.map +1 -1
  181. package/dist/src/runtime/emit/alter-table.js +348 -165
  182. package/dist/src/runtime/emit/alter-table.js.map +1 -1
  183. package/dist/src/runtime/emit/asof-scan.d.ts.map +1 -1
  184. package/dist/src/runtime/emit/asof-scan.js +6 -0
  185. package/dist/src/runtime/emit/asof-scan.js.map +1 -1
  186. package/dist/src/runtime/emit/between.d.ts.map +1 -1
  187. package/dist/src/runtime/emit/between.js +32 -3
  188. package/dist/src/runtime/emit/between.js.map +1 -1
  189. package/dist/src/runtime/emit/binary.d.ts.map +1 -1
  190. package/dist/src/runtime/emit/binary.js +22 -2
  191. package/dist/src/runtime/emit/binary.js.map +1 -1
  192. package/dist/src/runtime/emit/bloom-join.d.ts.map +1 -1
  193. package/dist/src/runtime/emit/bloom-join.js +24 -3
  194. package/dist/src/runtime/emit/bloom-join.js.map +1 -1
  195. package/dist/src/runtime/emit/cache.d.ts +7 -3
  196. package/dist/src/runtime/emit/cache.d.ts.map +1 -1
  197. package/dist/src/runtime/emit/cache.js +23 -6
  198. package/dist/src/runtime/emit/cache.js.map +1 -1
  199. package/dist/src/runtime/emit/cast.d.ts.map +1 -1
  200. package/dist/src/runtime/emit/cast.js +10 -23
  201. package/dist/src/runtime/emit/cast.js.map +1 -1
  202. package/dist/src/runtime/emit/constraint-check.d.ts.map +1 -1
  203. package/dist/src/runtime/emit/constraint-check.js +41 -44
  204. package/dist/src/runtime/emit/constraint-check.js.map +1 -1
  205. package/dist/src/runtime/emit/create-view.d.ts.map +1 -1
  206. package/dist/src/runtime/emit/create-view.js +6 -0
  207. package/dist/src/runtime/emit/create-view.js.map +1 -1
  208. package/dist/src/runtime/emit/cte.d.ts.map +1 -1
  209. package/dist/src/runtime/emit/cte.js +43 -20
  210. package/dist/src/runtime/emit/cte.js.map +1 -1
  211. package/dist/src/runtime/emit/distinct.d.ts.map +1 -1
  212. package/dist/src/runtime/emit/distinct.js +6 -3
  213. package/dist/src/runtime/emit/distinct.js.map +1 -1
  214. package/dist/src/runtime/emit/dml-executor.d.ts.map +1 -1
  215. package/dist/src/runtime/emit/dml-executor.js +241 -115
  216. package/dist/src/runtime/emit/dml-executor.js.map +1 -1
  217. package/dist/src/runtime/emit/hash-aggregate.d.ts.map +1 -1
  218. package/dist/src/runtime/emit/hash-aggregate.js +16 -65
  219. package/dist/src/runtime/emit/hash-aggregate.js.map +1 -1
  220. package/dist/src/runtime/emit/insert.d.ts.map +1 -1
  221. package/dist/src/runtime/emit/insert.js +18 -7
  222. package/dist/src/runtime/emit/insert.js.map +1 -1
  223. package/dist/src/runtime/emit/join.js +5 -0
  224. package/dist/src/runtime/emit/join.js.map +1 -1
  225. package/dist/src/runtime/emit/materialized-view-helpers.d.ts +14 -1
  226. package/dist/src/runtime/emit/materialized-view-helpers.d.ts.map +1 -1
  227. package/dist/src/runtime/emit/materialized-view-helpers.js +38 -7
  228. package/dist/src/runtime/emit/materialized-view-helpers.js.map +1 -1
  229. package/dist/src/runtime/emit/materialized-view.d.ts.map +1 -1
  230. package/dist/src/runtime/emit/materialized-view.js +47 -6
  231. package/dist/src/runtime/emit/materialized-view.js.map +1 -1
  232. package/dist/src/runtime/emit/merge-join.d.ts.map +1 -1
  233. package/dist/src/runtime/emit/merge-join.js +18 -8
  234. package/dist/src/runtime/emit/merge-join.js.map +1 -1
  235. package/dist/src/runtime/emit/recursive-cte.d.ts.map +1 -1
  236. package/dist/src/runtime/emit/recursive-cte.js +59 -1
  237. package/dist/src/runtime/emit/recursive-cte.js.map +1 -1
  238. package/dist/src/runtime/emit/set-object-tags.d.ts +8 -0
  239. package/dist/src/runtime/emit/set-object-tags.d.ts.map +1 -1
  240. package/dist/src/runtime/emit/set-object-tags.js +8 -0
  241. package/dist/src/runtime/emit/set-object-tags.js.map +1 -1
  242. package/dist/src/runtime/emit/set-operation.d.ts.map +1 -1
  243. package/dist/src/runtime/emit/set-operation.js +7 -4
  244. package/dist/src/runtime/emit/set-operation.js.map +1 -1
  245. package/dist/src/runtime/emit/sort.d.ts.map +1 -1
  246. package/dist/src/runtime/emit/sort.js +5 -2
  247. package/dist/src/runtime/emit/sort.js.map +1 -1
  248. package/dist/src/runtime/emit/subquery.d.ts.map +1 -1
  249. package/dist/src/runtime/emit/subquery.js +113 -13
  250. package/dist/src/runtime/emit/subquery.js.map +1 -1
  251. package/dist/src/runtime/emit/temporal-arithmetic.d.ts +7 -0
  252. package/dist/src/runtime/emit/temporal-arithmetic.d.ts.map +1 -1
  253. package/dist/src/runtime/emit/temporal-arithmetic.js +15 -7
  254. package/dist/src/runtime/emit/temporal-arithmetic.js.map +1 -1
  255. package/dist/src/runtime/emit/update.d.ts.map +1 -1
  256. package/dist/src/runtime/emit/update.js +44 -3
  257. package/dist/src/runtime/emit/update.js.map +1 -1
  258. package/dist/src/runtime/emit/window.d.ts.map +1 -1
  259. package/dist/src/runtime/emit/window.js +72 -55
  260. package/dist/src/runtime/emit/window.js.map +1 -1
  261. package/dist/src/runtime/foreign-key-actions.d.ts +36 -1
  262. package/dist/src/runtime/foreign-key-actions.d.ts.map +1 -1
  263. package/dist/src/runtime/foreign-key-actions.js +238 -116
  264. package/dist/src/runtime/foreign-key-actions.js.map +1 -1
  265. package/dist/src/runtime/parallel-driver.d.ts +20 -1
  266. package/dist/src/runtime/parallel-driver.d.ts.map +1 -1
  267. package/dist/src/runtime/parallel-driver.js +23 -1
  268. package/dist/src/runtime/parallel-driver.js.map +1 -1
  269. package/dist/src/runtime/types.d.ts +54 -0
  270. package/dist/src/runtime/types.d.ts.map +1 -1
  271. package/dist/src/runtime/types.js.map +1 -1
  272. package/dist/src/schema/catalog-persistability.d.ts +58 -0
  273. package/dist/src/schema/catalog-persistability.d.ts.map +1 -0
  274. package/dist/src/schema/catalog-persistability.js +87 -0
  275. package/dist/src/schema/catalog-persistability.js.map +1 -0
  276. package/dist/src/schema/catalog.d.ts +17 -1
  277. package/dist/src/schema/catalog.d.ts.map +1 -1
  278. package/dist/src/schema/catalog.js +25 -3
  279. package/dist/src/schema/catalog.js.map +1 -1
  280. package/dist/src/schema/constraint-builder.d.ts +20 -22
  281. package/dist/src/schema/constraint-builder.d.ts.map +1 -1
  282. package/dist/src/schema/constraint-builder.js +43 -36
  283. package/dist/src/schema/constraint-builder.js.map +1 -1
  284. package/dist/src/schema/ddl-generator.d.ts +23 -0
  285. package/dist/src/schema/ddl-generator.d.ts.map +1 -1
  286. package/dist/src/schema/ddl-generator.js +27 -0
  287. package/dist/src/schema/ddl-generator.js.map +1 -1
  288. package/dist/src/schema/function.d.ts +109 -1
  289. package/dist/src/schema/function.d.ts.map +1 -1
  290. package/dist/src/schema/function.js.map +1 -1
  291. package/dist/src/schema/manager.d.ts +28 -0
  292. package/dist/src/schema/manager.d.ts.map +1 -1
  293. package/dist/src/schema/manager.js +82 -14
  294. package/dist/src/schema/manager.js.map +1 -1
  295. package/dist/src/schema/schema-differ.d.ts.map +1 -1
  296. package/dist/src/schema/schema-differ.js +51 -12
  297. package/dist/src/schema/schema-differ.js.map +1 -1
  298. package/dist/src/schema/unique-enforcement.d.ts +31 -0
  299. package/dist/src/schema/unique-enforcement.d.ts.map +1 -1
  300. package/dist/src/schema/unique-enforcement.js +37 -1
  301. package/dist/src/schema/unique-enforcement.js.map +1 -1
  302. package/dist/src/schema/window-function.d.ts +30 -10
  303. package/dist/src/schema/window-function.d.ts.map +1 -1
  304. package/dist/src/schema/window-function.js +20 -16
  305. package/dist/src/schema/window-function.js.map +1 -1
  306. package/dist/src/types/cast-semantics.d.ts +43 -0
  307. package/dist/src/types/cast-semantics.d.ts.map +1 -0
  308. package/dist/src/types/cast-semantics.js +59 -0
  309. package/dist/src/types/cast-semantics.js.map +1 -0
  310. package/dist/src/types/index.d.ts +1 -1
  311. package/dist/src/types/index.d.ts.map +1 -1
  312. package/dist/src/types/index.js +1 -1
  313. package/dist/src/types/index.js.map +1 -1
  314. package/dist/src/types/json-type.d.ts.map +1 -1
  315. package/dist/src/types/json-type.js +31 -10
  316. package/dist/src/types/json-type.js.map +1 -1
  317. package/dist/src/types/logical-type.d.ts +22 -0
  318. package/dist/src/types/logical-type.d.ts.map +1 -1
  319. package/dist/src/types/logical-type.js.map +1 -1
  320. package/dist/src/types/temporal-types.d.ts.map +1 -1
  321. package/dist/src/types/temporal-types.js +33 -11
  322. package/dist/src/types/temporal-types.js.map +1 -1
  323. package/dist/src/types/validation.d.ts +39 -1
  324. package/dist/src/types/validation.d.ts.map +1 -1
  325. package/dist/src/types/validation.js +60 -0
  326. package/dist/src/types/validation.js.map +1 -1
  327. package/dist/src/util/ast-spine-clone.d.ts +23 -0
  328. package/dist/src/util/ast-spine-clone.d.ts.map +1 -0
  329. package/dist/src/util/ast-spine-clone.js +41 -0
  330. package/dist/src/util/ast-spine-clone.js.map +1 -0
  331. package/dist/src/util/coercion.d.ts +13 -0
  332. package/dist/src/util/coercion.d.ts.map +1 -1
  333. package/dist/src/util/coercion.js +10 -1
  334. package/dist/src/util/coercion.js.map +1 -1
  335. package/dist/src/util/comparison.d.ts +89 -0
  336. package/dist/src/util/comparison.d.ts.map +1 -1
  337. package/dist/src/util/comparison.js +156 -27
  338. package/dist/src/util/comparison.js.map +1 -1
  339. package/dist/src/vtab/manifest.d.ts +7 -0
  340. package/dist/src/vtab/manifest.d.ts.map +1 -1
  341. package/dist/src/vtab/memory/connection.d.ts +14 -1
  342. package/dist/src/vtab/memory/connection.d.ts.map +1 -1
  343. package/dist/src/vtab/memory/connection.js +20 -2
  344. package/dist/src/vtab/memory/connection.js.map +1 -1
  345. package/dist/src/vtab/memory/layer/base.d.ts +6 -1
  346. package/dist/src/vtab/memory/layer/base.d.ts.map +1 -1
  347. package/dist/src/vtab/memory/layer/base.js +14 -5
  348. package/dist/src/vtab/memory/layer/base.js.map +1 -1
  349. package/dist/src/vtab/memory/layer/connection.d.ts +17 -0
  350. package/dist/src/vtab/memory/layer/connection.d.ts.map +1 -1
  351. package/dist/src/vtab/memory/layer/connection.js +26 -0
  352. package/dist/src/vtab/memory/layer/connection.js.map +1 -1
  353. package/dist/src/vtab/memory/layer/manager.d.ts +96 -13
  354. package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
  355. package/dist/src/vtab/memory/layer/manager.js +595 -119
  356. package/dist/src/vtab/memory/layer/manager.js.map +1 -1
  357. package/dist/src/vtab/memory/layer/plan-filter.d.ts +41 -20
  358. package/dist/src/vtab/memory/layer/plan-filter.d.ts.map +1 -1
  359. package/dist/src/vtab/memory/layer/plan-filter.js +57 -27
  360. package/dist/src/vtab/memory/layer/plan-filter.js.map +1 -1
  361. package/dist/src/vtab/memory/layer/row-convert.d.ts +21 -0
  362. package/dist/src/vtab/memory/layer/row-convert.d.ts.map +1 -0
  363. package/dist/src/vtab/memory/layer/row-convert.js +30 -0
  364. package/dist/src/vtab/memory/layer/row-convert.js.map +1 -0
  365. package/dist/src/vtab/memory/layer/scan-layer.d.ts +6 -3
  366. package/dist/src/vtab/memory/layer/scan-layer.d.ts.map +1 -1
  367. package/dist/src/vtab/memory/layer/scan-layer.js +69 -36
  368. package/dist/src/vtab/memory/layer/scan-layer.js.map +1 -1
  369. package/dist/src/vtab/memory/layer/transaction.d.ts +127 -13
  370. package/dist/src/vtab/memory/layer/transaction.d.ts.map +1 -1
  371. package/dist/src/vtab/memory/layer/transaction.js +214 -65
  372. package/dist/src/vtab/memory/layer/transaction.js.map +1 -1
  373. package/dist/src/vtab/memory/module.d.ts.map +1 -1
  374. package/dist/src/vtab/memory/module.js +9 -2
  375. package/dist/src/vtab/memory/module.js.map +1 -1
  376. package/dist/src/vtab/memory/table.d.ts.map +1 -1
  377. package/dist/src/vtab/memory/table.js +3 -2
  378. package/dist/src/vtab/memory/table.js.map +1 -1
  379. package/dist/src/vtab/memory/types.d.ts +27 -1
  380. package/dist/src/vtab/memory/types.d.ts.map +1 -1
  381. package/dist/src/vtab/memory/types.js +16 -1
  382. package/dist/src/vtab/memory/types.js.map +1 -1
  383. package/dist/src/vtab/memory/utils/primary-key.d.ts +9 -0
  384. package/dist/src/vtab/memory/utils/primary-key.d.ts.map +1 -1
  385. package/dist/src/vtab/memory/utils/primary-key.js +11 -0
  386. package/dist/src/vtab/memory/utils/primary-key.js.map +1 -1
  387. package/dist/src/vtab/module.d.ts +63 -0
  388. package/dist/src/vtab/module.d.ts.map +1 -1
  389. package/dist/src/vtab/table.d.ts +24 -6
  390. package/dist/src/vtab/table.d.ts.map +1 -1
  391. package/dist/src/vtab/table.js.map +1 -1
  392. package/package.json +3 -3
  393. package/dist/src/planner/rules/cache/rule-in-subquery-cache.d.ts +0 -19
  394. package/dist/src/planner/rules/cache/rule-in-subquery-cache.d.ts.map +0 -1
  395. package/dist/src/planner/rules/cache/rule-in-subquery-cache.js +0 -53
  396. 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';
@@ -8,19 +9,83 @@ import { MemoryVirtualTableConnection } from '../connection.js';
8
9
  import { QuereusError } from '../../../common/errors.js';
9
10
  import { ConflictResolution } from '../../../common/constants.js';
10
11
  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';
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',
@@ -1738,12 +1995,23 @@ export class MemoryTableManager {
1738
1995
  // A collation change re-keys any PK / UNIQUE / index that orders by this
1739
1996
  // column, so it needs the structure re-sort + uniqueness re-validation below.
1740
1997
  let collationChanged = false;
1998
+ // Set when a SET DATA TYPE moves to a type that ORDERS values differently
1999
+ // (comparisonSemanticsDiffer). Same consequence as a collation change — every
2000
+ // structure keyed by the column has to be re-sorted and its uniqueness re-judged —
2001
+ // but a separate flag, because the index-column collation propagation and the
2002
+ // `pkColumnRekeyed` primary-tree path below belong to SET COLLATE alone. Usually
2003
+ // coincides with `valueConvert` (every non-alias retype sets that); the rebuild
2004
+ // chains below test `valueConvert` FIRST, whose full-rebuild path subsumes the
2005
+ // comparator-only re-sort.
2006
+ let comparatorChanged = false;
1741
2007
  // 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).
2008
+ // SET DATA TYPE (every non-alias retype values normalize to the new type's
2009
+ // canonical spelling even when the storage class is unchanged) or SET NOT NULL
2010
+ // backfill (map null DEFAULT). Both REPLACE the base primary tree with a
2011
+ // freshly-built one (never an in-place mutation, which inheritree forbids while the
2012
+ // open transaction's layers derive from it) and convert the transaction's
2013
+ // own-written rows in every open layer, after `updateSchema` (see the rebuild
2014
+ // section and convertColumnOnOpenLayers).
1747
2015
  let valueConvert = null;
1748
2016
  // SET NOT NULL backfill also routes NULL values through `valueConvert`; SET DATA TYPE
1749
2017
  // leaves nulls untouched. Selects the null-including path in convertBaseRows/convertColumn.
@@ -1821,19 +2089,40 @@ export class MemoryTableManager {
1821
2089
  }
1822
2090
  else if (change.setDataType !== undefined) {
1823
2091
  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`).
2092
+ // Gate on logical-type IDENTITY, not the physical storage class: `inferType`
2093
+ // flattens aliases to the shared type object (`varchar(50)` IS `TEXT_TYPE`,
2094
+ // `bigint` IS `INTEGER_TYPE`), so an alias retype is schema-only — no scan, no
2095
+ // rewrite, no re-key. Every OTHER retype validates AND normalizes every value,
2096
+ // whether or not the storage class changes: a same-class move (text date)
2097
+ // must reject values the new type refuses ('hello') and rewrite the rest to
2098
+ // the new type's canonical spelling ('2024-06-05T00:00:00Z' '2024-06-05'),
2099
+ // or the column holds values no INSERT could have produced — unfindable by
2100
+ // equality (DATE compares BINARY over the stored text) and invisible to UNIQUE.
2101
+ // NOTE: the rewrite is unconditional once the types differ, even when every
2102
+ // converted value comes back byte-identical (already-canonical ISO durations on a
2103
+ // text → timespan). That costs a full base-tree + secondary-index rebuild here, and
2104
+ // a physical rewrite of every row on the store leg. If ALTER on large tables ever
2105
+ // shows up as slow, have the convert pre-pass record whether any value actually
2106
+ // moved and, when none did, fall through to the comparator-only re-key.
2107
+ if (newLogicalType !== oldCol.logicalType) {
2108
+ // Retyping a PRIMARY KEY column moves the key bytes (a class change), the
2109
+ // tree's ordering (a comparator move), or at minimum the key values'
2110
+ // canonical spelling — and neither the base rewrite below nor the
2111
+ // transaction-layer conversion re-keys the primary tree. The engine already
2112
+ // refuses SET DATA TYPE on any PK column (runtime/emit/alter-table.ts);
2113
+ // this guards direct module calls. Reject rather than corrupt — the
2114
+ // type-change analogue of the SET COLLATE primary-key carve-out
2115
+ // (`alter-collate-pk-in-transaction`).
1834
2116
  if (this.tableSchema.primaryKeyDefinition.some(def => def.index === colIndex)) {
1835
2117
  throw new QuereusError(`Cannot change the data type of primary key column '${change.columnName}' of table '${this._tableName}'.`, StatusCode.CONSTRAINT);
1836
2118
  }
2119
+ // Every MemoryIndex builds its comparator from the column's LOGICAL type
2120
+ // (`createTypedComparator(columnSchema.logicalType, …)` in vtab/memory/index.ts),
2121
+ // so a move to a differently-ordering type (text ↔ timespan: 'PT1H' ≡ 'PT60M';
2122
+ // text ↔ date/time/datetime: binary, ignoring the column's collation) re-sorts
2123
+ // every structure keyed by the column. Tracked separately from the value rewrite
2124
+ // because SET COLLATE shares the re-key machinery without rewriting values.
2125
+ comparatorChanged = comparisonSemanticsDiffer(oldCol.logicalType, newLogicalType);
1837
2126
  const convert = (v) => {
1838
2127
  try {
1839
2128
  return validateAndParse(v, newLogicalType, change.columnName);
@@ -1868,8 +2157,8 @@ export class MemoryTableManager {
1868
2157
  // derive from that tree (`MutatedBaseError`). `valueConvert` drives both the base
1869
2158
  // replacement and the transaction-layer conversion.
1870
2159
  valueConvert = convert;
1871
- newCol = { ...oldCol, logicalType: newLogicalType };
1872
2160
  }
2161
+ newCol = { ...oldCol, logicalType: newLogicalType };
1873
2162
  }
1874
2163
  else if (change.setDefault !== undefined) {
1875
2164
  newCol = { ...oldCol, defaultValue: change.setDefault };
@@ -1878,15 +2167,30 @@ export class MemoryTableManager {
1878
2167
  throw new QuereusError('ALTER COLUMN requires an attribute to change', StatusCode.INTERNAL);
1879
2168
  }
1880
2169
  const updatedCols = this.tableSchema.columns.map((c, i) => i === colIndex ? newCol : c);
2170
+ // The changes that move the COMPARATOR the structures are keyed by: SET COLLATE
2171
+ // (values untouched) and a retype into a differently-ordering type (which, unless it
2172
+ // is an alias retype, ALSO rewrites values — `valueConvert` — and its full-rebuild
2173
+ // path below subsumes the comparator-only re-sort). Both need fresh IndexSchema
2174
+ // objects and, when no rewrite runs, the re-validate / re-sort / adopt sequence.
2175
+ const structuresRekeyed = collationChanged || comparatorChanged;
1881
2176
  // Propagate a collation change into every PK-definition entry and index
1882
2177
  // column that orders by this column, so their comparators re-key under it.
1883
2178
  const updatedPkDef = collationChanged
1884
2179
  ? this.tableSchema.primaryKeyDefinition.map(def => def.index === colIndex ? { ...def, collation: newCol.collation } : def)
1885
2180
  : this.tableSchema.primaryKeyDefinition;
1886
- const updatedIndexes = (collationChanged && this.tableSchema.indexes)
2181
+ // Every re-keying change also REBUILDS the IndexSchema objects, because their identity is
2182
+ // the discriminator `TransactionLayer.adoptSchema` uses to decide an open layer's
2183
+ // MemoryIndex must be replaced rather than kept (an index it holds under an object the new
2184
+ // schema still carries is left alone). SET COLLATE has a field to write — the index
2185
+ // column's own collation. A comparator-only retype has none: an index column carries no
2186
+ // logical type, it reads the column's from `newSchema.columns`, so the fresh object IS the
2187
+ // whole signal. Without it the base rebuilt its trees under the new comparator while the
2188
+ // DDL transaction's own layers went on comparing the old way — and shadowed the base's
2189
+ // rebuilt trees once the pending layer became the committed head.
2190
+ const updatedIndexes = (structuresRekeyed && this.tableSchema.indexes)
1887
2191
  ? this.tableSchema.indexes.map(idx => ({
1888
2192
  ...idx,
1889
- columns: idx.columns.map(ic => ic.index === colIndex ? { ...ic, collation: newCol.collation } : ic),
2193
+ columns: idx.columns.map(ic => (ic.index === colIndex && collationChanged) ? { ...ic, collation: newCol.collation } : ic),
1890
2194
  }))
1891
2195
  : this.tableSchema.indexes;
1892
2196
  const finalNewTableSchema = Object.freeze({
@@ -1898,14 +2202,49 @@ export class MemoryTableManager {
1898
2202
  });
1899
2203
  const pkColumnRekeyed = collationChanged && updatedPkDef.some(def => def.index === colIndex);
1900
2204
  // 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
2205
+ // the transaction inserted that collides under the NEW schema must reject the
1902
2206
  // change, and one it has deleted must not block it. A throw here leaves the schema,
1903
2207
  // the base layer and the index map exactly as they were.
1904
2208
  //
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) {
2209
+ // A value-REWRITING change (`valueConvert`: SET DATA TYPE, or a SET NOT NULL null
2210
+ // DEFAULT backfill) probes the CONVERTED rows the same per-row conversion the base
2211
+ // rewrite below applies — under the NEW schema's comparators, so one call covers both
2212
+ // ways an ALTER can collapse two previously-distinct rows: the VALUES converge
2213
+ // (`'1'`/`'01'` → 1; two NULLs → one DEFAULT; '2024-06-05T00:00:00Z' → '2024-06-05')
2214
+ // and/or the COMPARATOR moves (text → timespan makes 'PT1H'/'PT60M' one value). A
2215
+ // same-storage-class retype sets `valueConvert` AND `structuresRekeyed`, so the
2216
+ // rewrite arm is tested FIRST — its converted-row probe subsumes the comparator-only
2217
+ // one. The rebuild that follows is deliberately non-enforcing, so this is the only
2218
+ // guard.
2219
+ //
2220
+ // The comparator-MOVING arm without a rewrite has one trigger left: SET COLLATE. The
2221
+ // values are untouched — hence no `mapRow` — and only the comparator the structures
2222
+ // are keyed by moves. `newSchema` carries the new collations, so the probe indexes
2223
+ // compare as the rebuilt ones will.
2224
+ //
2225
+ // The primary key: SET COLLATE on a PK member gets a stricter pre-pass
2226
+ // (`validateRekeyedPrimaryKey`): no layer in the chain, base included, may hold a
2227
+ // collision. That is what lets every step below succeed unconditionally, and what
2228
+ // `TransactionLayer.rekeyPrimaryKey` relies on. The rewrite arm needs no PK pre-pass
2229
+ // and cannot shadow one: a retype of a PK column is rejected upstream, the backfill
2230
+ // only fires on a column holding NULLs (which a PK member cannot — the engine
2231
+ // enforces NOT NULL on every PK member regardless of the declared nullability), and
2232
+ // `pkColumnRekeyed` is set only by SET COLLATE, which never sets `valueConvert` — so
2233
+ // whenever `pkColumnRekeyed` is true, the `structuresRekeyed` arm still runs.
2234
+ // NOTE: if PK members ever become genuinely nullable, the backfill arm gains a PK
2235
+ // collision path (two NULL keys → one DEFAULT) and needs `validateRekeyedPrimaryKey`
2236
+ // plus a primary-tree re-key of its own.
2237
+ if (valueConvert) {
2238
+ const convert = valueConvert;
2239
+ const nulls = convertNulls;
2240
+ await this.validateRekeyedUniqueStructures(finalNewTableSchema, colIndex, rows,
2241
+ // Unlike `convertBaseRows`, a conversion failure here is NOT swallowed. Every row
2242
+ // the probe sees is visible to the transaction, and the setDataType pre-pass above
2243
+ // already proved each one convertible, so a throw is unreachable; surfacing it as
2244
+ // MISMATCH still beats probing a stale value that could mask a collision.
2245
+ row => convertRowAtIndex(row, colIndex, convert, nulls));
2246
+ }
2247
+ else if (structuresRekeyed) {
1909
2248
  await this.validateRekeyedUniqueStructures(finalNewTableSchema, colIndex, rows);
1910
2249
  // NOTE: `validateRekeyedPrimaryKey` deliberately ignores `rows`. It asserts that no
1911
2250
  // LAYER of this manager's own chain holds a PK collision under the new comparator —
@@ -1919,27 +2258,23 @@ export class MemoryTableManager {
1919
2258
  this.validateRekeyedPrimaryKey(finalNewTableSchema);
1920
2259
  }
1921
2260
  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) {
2261
+ // Both arms rebuild the base's structures under the schema `updateSchema` above just
2262
+ // swapped in each MemoryIndex rebuilds its comparator from the CURRENT schema's
2263
+ // collation and logical type. The secondary rebuilds are NON-enforcing (the pre-pass
2264
+ // above owns uniqueness, and the base's rows are not a subset of the effective rows);
2265
+ // the SET COLLATE PK rebuild is strict, since a PK collision cannot be represented at
2266
+ // all the pre-pass has already proved the base collision-free, so the strict
2267
+ // rebuild is a live invariant check, not the enforcement path. It runs LAST so its
2268
+ // throw leaves the live tree intact for the catch's rollback.
2269
+ if (valueConvert) {
1937
2270
  // SET DATA TYPE / SET NOT NULL backfill: convert the committed base rows and REPLACE
1938
2271
  // the primary tree with a fresh one holding them (not an in-place upsert — inheritree
1939
2272
  // forbids mutating a base while the open transaction's layers derive from it).
1940
2273
  // `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.
2274
+ // rows under the new comparators, so index-backed lookups see the new values which
2275
+ // is why this arm runs first and subsumes `rebuildAllSecondaryIndexes` when a
2276
+ // same-storage-class retype sets `structuresRekeyed` too. The old tree is saved for
2277
+ // the catch's rollback, exactly as the PK-rekey path does.
1943
2278
  // NOTE: rebuilds EVERY secondary index, not only those covering the altered column;
1944
2279
  // if a wide-index table ever shows this as slow, filter to indexes whose columns
1945
2280
  // include colIndex. Mirrors the collationChanged path's unconditional rebuild.
@@ -1954,31 +2289,56 @@ export class MemoryTableManager {
1954
2289
  basePrimaryTreeBeforeRekey = this.baseLayer.primaryTree;
1955
2290
  this.baseLayer.rebuildPrimaryTreeFromRows(convertedBaseRows);
1956
2291
  }
2292
+ else if (structuresRekeyed) {
2293
+ this.baseLayer.rebuildAllSecondaryIndexes();
2294
+ if (pkColumnRekeyed) {
2295
+ basePrimaryTreeBeforeRekey = this.baseLayer.primaryTree;
2296
+ this.baseLayer.rebuildPrimaryTreeStrict();
2297
+ }
2298
+ }
1957
2299
  this.tableSchema = finalNewTableSchema;
1958
2300
  this.initializePrimaryKeyFunctions();
1959
2301
  // 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
2302
+ // comparator; the DDL transaction's own layers still inherit the old ones and froze
1961
2303
  // the old schema at construction. Re-key them, or the rest of the transaction — and
1962
2304
  // everything after the pending layer becomes the committed head at commit — keeps
1963
- // comparing under the old collation.
2305
+ // comparing under the old collation / old logical type.
1964
2306
  //
1965
2307
  // When the altered column is part of the primary key, `rebuildPrimaryTreeStrict` also
1966
2308
  // swapped the base primary tree object out from under those layers' copy-on-write
1967
2309
  // bases and invalidated their `pkFunctions`; `rekeyPrimaryKey` rebuilds both, plus
1968
2310
  // every secondary index (each derives its PK comparator/encoder from the PK
1969
2311
  // 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) {
2312
+ if (valueConvert) {
1974
2313
  // SET DATA TYPE / SET NOT NULL backfill converted the base above; the DDL
1975
2314
  // transaction's own open layers still froze the old schema and hold their own-written
1976
2315
  // rows unconverted. Swap the schema and convert those values so the rest of the
1977
2316
  // transaction — and the committed head at commit — read the new value. For SET NOT
1978
2317
  // 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.
2318
+ // layer, never reached by the old in-place base upsert). `convertColumn` installs the
2319
+ // new schema and rebuilds every one of the layer's secondary indexes from it, so it
2320
+ // subsumes `adoptSchema` when a same-storage-class retype moved the comparators too.
2321
+ // No-op in autocommit.
1980
2322
  this.convertColumnOnOpenLayers(finalNewTableSchema, colIndex, valueConvert, convertNulls);
1981
2323
  }
2324
+ else if (structuresRekeyed) {
2325
+ this.adoptSchemaOnOpenLayers(finalNewTableSchema, pkColumnRekeyed);
2326
+ }
2327
+ else {
2328
+ // The remaining changes (SET DEFAULT, DROP NOT NULL, a SET NOT NULL that needed no
2329
+ // backfill, a retype between aliases of one logical type) touch nothing a layer
2330
+ // DERIVES — its index set, its `uniqueConstraints` enforcement and its MemoryIndex
2331
+ // comparators all stay put, and DEFAULT application / NOT NULL enforcement happen
2332
+ // above the module, off the catalog schema. But the frozen schema OBJECT is itself
2333
+ // observable: `commitTransaction` only wraps a swapped-in savepoint snapshot back
2334
+ // into the committed chain when `readLayer.getSchema() === this.tableSchema`. Left
2335
+ // stale, a `rollback to savepoint` taken across one of these ALTERs restores a
2336
+ // snapshot the wrap then skips, and the transaction's staged rows are silently
2337
+ // dropped at COMMIT. Adoption is cheap here — every IndexSchema object is carried
2338
+ // over unchanged, so `adoptSchema` keeps each layer's MemoryIndex and only swaps
2339
+ // the schema reference.
2340
+ this.adoptSchemaOnOpenLayers(finalNewTableSchema);
2341
+ }
1982
2342
  this.eventEmitter?.emitSchemaChange?.({
1983
2343
  type: 'alter',
1984
2344
  objectType: 'column',
@@ -2067,12 +2427,15 @@ export class MemoryTableManager {
2067
2427
  // The DDL transaction's own layers froze their schema at creation; hand them the
2068
2428
  // new one so the rest of the transaction scans and enforces the new index.
2069
2429
  this.adoptSchemaOnOpenLayers(finalNewTableSchema);
2070
- // Emit schema change event
2430
+ // Emit schema change event. The `ddl` is what a sync peer re-executes to
2431
+ // replicate the index; rendered against the POST-create schema so it matches
2432
+ // what a receiving peer regenerates when comparing definitions.
2071
2433
  this.eventEmitter?.emitSchemaChange?.({
2072
2434
  type: 'create',
2073
2435
  objectType: 'index',
2074
2436
  schemaName: this.schemaName,
2075
2437
  objectName: indexName,
2438
+ ddl: generateIndexDDL(newIndexSchemaEntry, finalNewTableSchema),
2076
2439
  });
2077
2440
  logger.operation('Create Index', this._tableName, { indexName });
2078
2441
  }
@@ -2135,6 +2498,7 @@ export class MemoryTableManager {
2135
2498
  objectType: 'index',
2136
2499
  schemaName: this.schemaName,
2137
2500
  objectName: indexName,
2501
+ ddl: generateDropIndexDDL(this.schemaName, indexName),
2138
2502
  });
2139
2503
  logger.operation('Drop Index', this._tableName, { indexName });
2140
2504
  }
@@ -2285,6 +2649,14 @@ export class MemoryTableManager {
2285
2649
  this.baseLayer.rebuildAllSecondaryIndexes();
2286
2650
  this.tableSchema = newSchema;
2287
2651
  this.initializePrimaryKeyFunctions();
2652
+ // Hand the new schema to the DDL transaction's own frozen layers, as `dropConstraint`
2653
+ // does: a renamed covering index has to move under its new key in each layer's index
2654
+ // map (the old key is dropped, the new one rebuilt over the parent's already-renamed
2655
+ // tree — hence oldest-first), and even the pure schema-only classes (CHECK / FK) must
2656
+ // re-freeze the new schema OBJECT, or `commitTransaction`'s snapshot-wrap identity
2657
+ // check (`readLayer.getSchema() === this.tableSchema`) skips a savepoint snapshot
2658
+ // restored across this ALTER and drops the transaction's staged rows at COMMIT.
2659
+ this.adoptSchemaOnOpenLayers(newSchema);
2288
2660
  this.eventEmitter?.emitSchemaChange?.({
2289
2661
  type: 'alter',
2290
2662
  objectType: 'table',
@@ -2529,6 +2901,19 @@ export class MemoryTableManager {
2529
2901
  // bug. A detached connection always has `pendingTransactionLayer === null` (disconnect
2530
2902
  // defers while a pending layer is uncommitted), so this never discards in-flight writes.
2531
2903
  this.repointRegisteredConnections();
2904
+ // Re-pointing `readLayer` is not enough: a savepoint taken while a connection held no
2905
+ // pending layer stored the layer it was READING as the view to restore. Consolidation
2906
+ // has just drained that layer into the base, and the caller is about to reshape the
2907
+ // base's rows — so the stored reference is a pre-change snapshot of the committed rows.
2908
+ // Left alone, the transaction's `rollback to savepoint` reinstates it and the old column
2909
+ // shape commits over the new one.
2910
+ //
2911
+ // Every connection, not just the re-pointed ones: the DDL issuer is exempt from the
2912
+ // sweeps above (its read view holds its own uncommitted rows) but its own lazy markers
2913
+ // are just as stale.
2914
+ for (const connection of this.knownConnections()) {
2915
+ connection.repointLazySavepointsToCommittedHead(this.baseLayer);
2916
+ }
2532
2917
  logger.debugLog(`Schema change safety check passed for ${this._tableName}. Current committed layer is base.`);
2533
2918
  }
2534
2919
  /**
@@ -2548,6 +2933,34 @@ export class MemoryTableManager {
2548
2933
  mc.readLayer = this.baseLayer;
2549
2934
  }
2550
2935
  }
2936
+ /**
2937
+ * Moves this manager's Database-registered connections from `<schema>.<oldName>` to
2938
+ * `<schema>.<newName>`, for `ALTER TABLE ... RENAME TO`.
2939
+ *
2940
+ * `MemoryVirtualTableConnection.tableName` is set once at registration, and
2941
+ * `Database.getConnectionsForTable` matches on exactly that string — so without this
2942
+ * sweep a rename makes the transaction's own connection invisible to
2943
+ * {@link registeredConnections} and everything built on it: {@link ddlConnection},
2944
+ * {@link knownConnections}, {@link repointRegisteredConnections},
2945
+ * {@link openTransactionLayersOldestFirst}. The visible symptom is a second ALTER in
2946
+ * the same transaction failing with BUSY — {@link ensureSchemaChangeSafety} exempts
2947
+ * `ddlConnection()` from its "nobody else may hold open work" sweep, and with the
2948
+ * registry stale that is `undefined`, so the transaction's own connection is judged a
2949
+ * stranger. `MemoryTable.ensureConnection` would likewise stop reusing it and register
2950
+ * a second connection for the same table.
2951
+ *
2952
+ * Must run BEFORE `_tableName` moves — it looks the connections up under the old name.
2953
+ */
2954
+ rekeyRegisteredConnections(oldName, newName) {
2955
+ const newQualifiedName = `${this.schemaName}.${newName}`;
2956
+ for (const c of this.db.getConnectionsForTable(`${this.schemaName}.${oldName}`)) {
2957
+ if (!(c instanceof MemoryVirtualTableConnection))
2958
+ continue;
2959
+ if (c.getMemoryConnection().tableManager !== this)
2960
+ continue;
2961
+ c.rename(newQualifiedName);
2962
+ }
2963
+ }
2551
2964
  /** Every Database-registered {@link MemoryTableConnection} backed by this manager. */
2552
2965
  *registeredConnections() {
2553
2966
  const qualifiedName = `${this.schemaName}.${this._tableName}`;
@@ -2625,20 +3038,12 @@ export class MemoryTableManager {
2625
3038
  convertBaseRows(colIndex, convert, convertNulls = false) {
2626
3039
  const out = [];
2627
3040
  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
3041
  try {
2635
- newVal = convert(oldVal);
3042
+ out.push(convertRowAtIndex(row, colIndex, convert, convertNulls));
2636
3043
  }
2637
3044
  catch {
2638
3045
  out.push(row); // shadowed unconvertible value — keep as-is
2639
- continue;
2640
3046
  }
2641
- out.push(row.map((v, i) => i === colIndex ? newVal : v));
2642
3047
  }
2643
3048
  return out;
2644
3049
  }
@@ -2652,31 +3057,47 @@ export class MemoryTableManager {
2652
3057
  * the transaction's pending rows outside this manager, so only it can name the rows the
2653
3058
  * issuing connection actually sees; when it supplies them, they are the judged set and this
2654
3059
  * manager's own committed rows are ignored. See `vtab/module.ts` {@link EffectiveRowSource}.
3060
+ *
3061
+ * `mapRow` judges the rows in a form they do not yet have on disk: an `alter column` that
3062
+ * REWRITES values passes the per-row conversion here, so the probe sees the post-ALTER
3063
+ * values. Applied to whichever stream is used; the mapping is a read-side wrap and mutates
3064
+ * nothing.
2655
3065
  */
2656
- async validateUniqueOverEffectiveRows(indexSchema, schema, rows) {
3066
+ async validateUniqueOverEffectiveRows(indexSchema, schema, rows, mapRow) {
2657
3067
  const probe = new MemoryIndex(indexSchema, schema.columns, this.collationResolver, this.primaryKeyFunctions.compare, this.primaryKeyFunctions.encode, schema.name);
2658
3068
  if (rows) {
2659
- await populateIndexFromRowsAsync(rows(), probe, this.primaryKeyFunctions.extractFromRow, true, this._tableName, schema.columns);
3069
+ const stream = mapRow ? mapRowsAsync(rows(), mapRow) : rows();
3070
+ await populateIndexFromRowsAsync(stream, probe, this.primaryKeyFunctions.extractFromRow, true, this._tableName, schema.columns);
2660
3071
  return;
2661
3072
  }
2662
- populateIndexFromRows(this.effectiveDdlRows(), probe, this.primaryKeyFunctions.extractFromRow, true, this._tableName, schema.columns);
3073
+ const own = this.effectiveDdlRows();
3074
+ populateIndexFromRows(mapRow ? mapRows(own, mapRow) : own, probe, this.primaryKeyFunctions.extractFromRow, true, this._tableName, schema.columns);
2663
3075
  }
2664
3076
  /**
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.
3077
+ * `ALTER COLUMN` arm of {@link validateUniqueOverEffectiveRows}: rejects the change when it
3078
+ * makes two of the DDL transaction's effective rows collide under any uniqueness-enforcing
3079
+ * structure that covers the altered column. Runs before anything is mutated, so a rejection
3080
+ * leaves the table and schema untouched and the transaction usable.
3081
+ *
3082
+ * Serves both families of change that can collapse two distinct values onto one:
3083
+ *
3084
+ * - `SET COLLATE` re-keys the structures under a new comparator; the stored values are
3085
+ * untouched, so no `mapRow` is passed.
3086
+ * - a value REWRITE (`SET DATA TYPE`, or a `SET NOT NULL` null → DEFAULT backfill) leaves
3087
+ * the comparators alone but changes the values; the caller passes a `mapRow` that applies
3088
+ * the same per-row conversion the base rewrite will, so the probe judges the post-ALTER
3089
+ * values.
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
+ * `newSchema` carries the post-change per-column collations AND logical types, so each probe
3092
+ * index compares exactly as the rebuilt structure will (it matters for e.g. `text real`).
3093
+ * Indexes that do not mention the column keep their keys and need no re-check.
2673
3094
  *
2674
3095
  * NOTE: walks `schema.indexes`, so a UNIQUE constraint covered by a row-time materialized
2675
3096
  * view rather than its auto-index (`findIndexForConstraint` prefers the MV) is not re-checked
2676
3097
  * here. The auto-index always exists alongside, so the structure is still validated — but if
2677
3098
  * an MV-only covering shape ever becomes reachable, this walk must follow it.
2678
3099
  */
2679
- async validateRekeyedUniqueStructures(newSchema, alteredColumnIndex, rows) {
3100
+ async validateRekeyedUniqueStructures(newSchema, alteredColumnIndex, rows, mapRow) {
2680
3101
  // NOTE: the probe index carries the manager's PRE-change `primaryKeyFunctions` (the new
2681
3102
  // ones cannot exist before the schema swaps). Only the probe's per-entry PK bookkeeping
2682
3103
  // uses them, and every effective row already has a distinct PK under the old encoder, so
@@ -2688,7 +3109,7 @@ export class MemoryTableManager {
2688
3109
  if (!indexEnforcesUnique(newSchema, indexSchema))
2689
3110
  continue;
2690
3111
  // `rows` is re-callable, so each structure gets a fresh stream.
2691
- await this.validateUniqueOverEffectiveRows(indexSchema, newSchema, rows);
3112
+ await this.validateUniqueOverEffectiveRows(indexSchema, newSchema, rows, mapRow);
2692
3113
  }
2693
3114
  }
2694
3115
  /**
@@ -2792,7 +3213,7 @@ export class MemoryTableManager {
2792
3213
  * `convertNulls` routes null own-values through `convert` (the SET NOT NULL null → DEFAULT map);
2793
3214
  * SET DATA TYPE leaves them untouched. No-op in autocommit.
2794
3215
  *
2795
- * PRIMARY-KEY columns never reach here — a physical retype of a key column is rejected before
3216
+ * PRIMARY-KEY columns never reach here — any retype of a key column is rejected before
2796
3217
  * any mutation, and SET NOT NULL leaves the key bytes unchanged — so the primary tree's keys are
2797
3218
  * stable and no re-key is needed.
2798
3219
  */
@@ -2801,14 +3222,58 @@ export class MemoryTableManager {
2801
3222
  layer.convertColumn(colIndex, convert, newSchema, convertNulls);
2802
3223
  }
2803
3224
  }
3225
+ /**
3226
+ * Phase 1 of propagating `add column` / `drop column` into every open transaction layer:
3227
+ * computes each layer's net own-writes rewritten to the new column set, WITHOUT mutating
3228
+ * anything (see {@link TransactionLayer.prepareReshapedColumns}). Runs BEFORE the base is
3229
+ * touched, because `reshapeRow` can throw — ADD COLUMN's per-row backfill evaluates an
3230
+ * engine-supplied `default (new.<col>)` expression against each pending row, and enforces
3231
+ * NOT NULL on the result — and a failure part-way through a mutation pass would leave the
3232
+ * base and the already-visited layers at the new arity with the rest at the old, with no
3233
+ * undo. Failing here instead rejects the ALTER with every structure untouched, exactly like
3234
+ * the pre-mutation validation passes of `alterColumn`.
3235
+ *
3236
+ * The returned plans pin the layer set: {@link installReshapeOnOpenLayers} walks the plans,
3237
+ * not a fresh chain snapshot, so the two phases cannot disagree about which layers exist.
3238
+ * Reading each layer's effective rows here — before the base rebuild — yields the same rows
3239
+ * the single-pass prototype read after it: a layer's pre-reshape tree resolves against the
3240
+ * parent's OLD tree either way (the base rebuild REPLACES the base's tree object; it never
3241
+ * mutates the one the layers inherit from).
3242
+ */
3243
+ async prepareReshapeOnOpenLayers(reshapeRow, reshapeEventRow) {
3244
+ const plans = [];
3245
+ for (const layer of this.openTransactionLayersOldestFirst()) {
3246
+ plans.push({ layer, prepared: await layer.prepareReshapedColumns(reshapeRow, reshapeEventRow) });
3247
+ }
3248
+ return plans;
3249
+ }
3250
+ /**
3251
+ * Phase 2: installs the prepared reshape into each open transaction layer, oldest-first
3252
+ * (the plans were gathered in that order), after the base has been rebuilt at the new arity
3253
+ * — each layer's rebuilt tree inherits copy-on-write from its parent's NEW one. Synchronous
3254
+ * and mutation-only; everything fallible ran in {@link prepareReshapeOnOpenLayers}.
3255
+ *
3256
+ * Beyond keeping the transaction's own reads correct, this is what makes the
3257
+ * `commitTransaction` snapshot-wrap identity check (`readLayer.getSchema() ===
3258
+ * this.tableSchema`) pass after a `rollback to savepoint`: without it the eager savepoint
3259
+ * snapshot keeps its frozen pre-ALTER schema, the wrap is skipped, and the snapshot's rows
3260
+ * are silently dropped at commit.
3261
+ */
3262
+ installReshapeOnOpenLayers(newSchema, plans) {
3263
+ for (const { layer, prepared } of plans) {
3264
+ layer.installReshapedColumns(newSchema, prepared);
3265
+ }
3266
+ }
2804
3267
  /**
2805
3268
  * The DDL connection's open {@link TransactionLayer}s, oldest-first, base excluded — its
2806
3269
  * pending layer and every savepoint snapshot below it, the layers on the view's parent chain
2807
3270
  * above the base. Empty in autocommit (no connection, or the view IS the base).
2808
3271
  *
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.
3272
+ * Schema adoption ({@link adoptSchemaOnOpenLayers}), value conversion
3273
+ * ({@link convertColumnOnOpenLayers}), and column-set reshape
3274
+ * ({@link prepareReshapeOnOpenLayers} / {@link installReshapeOnOpenLayers}) MUST all apply
3275
+ * oldest-first: each layer rebuilds its structures over its parent's, so the parent's must
3276
+ * already be the new/converted/reshaped ones.
2812
3277
  *
2813
3278
  * NOTE: the walk takes every TransactionLayer below the view, which normally means the pending
2814
3279
  * layer and its savepoint snapshots. A committed layer already drained into the base by
@@ -2841,6 +3306,17 @@ export class MemoryTableManager {
2841
3306
  logger.debugLog(`[Consolidate] Copying data from transaction layer ${transactionLayer.getLayerId()} to base layer for ${this._tableName}`);
2842
3307
  // Copy all data from the transaction layer to the base layer
2843
3308
  await this.copyTransactionDataToBase(transactionLayer);
3309
+ // The drained chain's pending-change event logs were already delivered when
3310
+ // each layer committed, but the layers can remain in the DDL connection's own
3311
+ // open-transaction parent chain. Once the base becomes the committed head, the
3312
+ // commit-time collection (`collectPendingChanges`) walks that chain down to
3313
+ // the base and would re-deliver them — clear the logs so consolidation is
3314
+ // event-neutral. (Savepoint snapshots of the OPEN transaction sit above the
3315
+ // committed head, not in this chain, so their un-emitted events are kept.)
3316
+ for (let cur = transactionLayer; cur && cur !== this.baseLayer; cur = cur.getParent()) {
3317
+ if (cur instanceof TransactionLayer)
3318
+ cur.clearPendingChanges();
3319
+ }
2844
3320
  // Force all connections to read from the base layer
2845
3321
  for (const conn of this.connections.values()) {
2846
3322
  if (conn.readLayer === transactionLayer) {