@quereus/quereus 4.4.1 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (593) hide show
  1. package/README.md +5 -4
  2. package/dist/src/core/database-events.d.ts +99 -1
  3. package/dist/src/core/database-events.d.ts.map +1 -1
  4. package/dist/src/core/database-events.js +219 -3
  5. package/dist/src/core/database-events.js.map +1 -1
  6. package/dist/src/core/database-materialized-views-analysis.d.ts.map +1 -1
  7. package/dist/src/core/database-materialized-views-analysis.js +1 -0
  8. package/dist/src/core/database-materialized-views-analysis.js.map +1 -1
  9. package/dist/src/core/database-materialized-views-plan-builders.d.ts.map +1 -1
  10. package/dist/src/core/database-materialized-views-plan-builders.js +29 -2
  11. package/dist/src/core/database-materialized-views-plan-builders.js.map +1 -1
  12. package/dist/src/core/database-transaction.d.ts +9 -0
  13. package/dist/src/core/database-transaction.d.ts.map +1 -1
  14. package/dist/src/core/database-transaction.js +9 -0
  15. package/dist/src/core/database-transaction.js.map +1 -1
  16. package/dist/src/core/database.d.ts +7 -1
  17. package/dist/src/core/database.d.ts.map +1 -1
  18. package/dist/src/core/database.js +20 -5
  19. package/dist/src/core/database.js.map +1 -1
  20. package/dist/src/func/builtins/aggregate.js +11 -11
  21. package/dist/src/func/builtins/aggregate.js.map +1 -1
  22. package/dist/src/func/builtins/builtin-window-functions.d.ts.map +1 -1
  23. package/dist/src/func/builtins/builtin-window-functions.js +15 -85
  24. package/dist/src/func/builtins/builtin-window-functions.js.map +1 -1
  25. package/dist/src/func/builtins/conversion.d.ts.map +1 -1
  26. package/dist/src/func/builtins/conversion.js +10 -9
  27. package/dist/src/func/builtins/conversion.js.map +1 -1
  28. package/dist/src/func/builtins/datetime.d.ts.map +1 -1
  29. package/dist/src/func/builtins/datetime.js +28 -10
  30. package/dist/src/func/builtins/datetime.js.map +1 -1
  31. package/dist/src/func/builtins/json.d.ts.map +1 -1
  32. package/dist/src/func/builtins/json.js +23 -17
  33. package/dist/src/func/builtins/json.js.map +1 -1
  34. package/dist/src/func/builtins/mutation.js +2 -2
  35. package/dist/src/func/builtins/mutation.js.map +1 -1
  36. package/dist/src/func/builtins/return-types.d.ts +327 -0
  37. package/dist/src/func/builtins/return-types.d.ts.map +1 -0
  38. package/dist/src/func/builtins/return-types.js +43 -0
  39. package/dist/src/func/builtins/return-types.js.map +1 -0
  40. package/dist/src/func/builtins/scalar.d.ts.map +1 -1
  41. package/dist/src/func/builtins/scalar.js +193 -37
  42. package/dist/src/func/builtins/scalar.js.map +1 -1
  43. package/dist/src/func/builtins/schema.d.ts.map +1 -1
  44. package/dist/src/func/builtins/schema.js +11 -3
  45. package/dist/src/func/builtins/schema.js.map +1 -1
  46. package/dist/src/func/builtins/string.d.ts.map +1 -1
  47. package/dist/src/func/builtins/string.js +4 -2
  48. package/dist/src/func/builtins/string.js.map +1 -1
  49. package/dist/src/func/builtins/timespan.d.ts +3 -0
  50. package/dist/src/func/builtins/timespan.d.ts.map +1 -1
  51. package/dist/src/func/builtins/timespan.js +15 -11
  52. package/dist/src/func/builtins/timespan.js.map +1 -1
  53. package/dist/src/func/registration.d.ts +39 -1
  54. package/dist/src/func/registration.d.ts.map +1 -1
  55. package/dist/src/func/registration.js +236 -41
  56. package/dist/src/func/registration.js.map +1 -1
  57. package/dist/src/index.d.ts +14 -7
  58. package/dist/src/index.d.ts.map +1 -1
  59. package/dist/src/index.js +14 -5
  60. package/dist/src/index.js.map +1 -1
  61. package/dist/src/parser/parser.d.ts +46 -0
  62. package/dist/src/parser/parser.d.ts.map +1 -1
  63. package/dist/src/parser/parser.js +160 -44
  64. package/dist/src/parser/parser.js.map +1 -1
  65. package/dist/src/planner/analysis/check-extraction.d.ts.map +1 -1
  66. package/dist/src/planner/analysis/check-extraction.js +37 -4
  67. package/dist/src/planner/analysis/check-extraction.js.map +1 -1
  68. package/dist/src/planner/analysis/coarsened-key.d.ts +4 -3
  69. package/dist/src/planner/analysis/coarsened-key.d.ts.map +1 -1
  70. package/dist/src/planner/analysis/coarsened-key.js +37 -3
  71. package/dist/src/planner/analysis/coarsened-key.js.map +1 -1
  72. package/dist/src/planner/analysis/comparison-collation.d.ts +41 -13
  73. package/dist/src/planner/analysis/comparison-collation.d.ts.map +1 -1
  74. package/dist/src/planner/analysis/comparison-collation.js +51 -14
  75. package/dist/src/planner/analysis/comparison-collation.js.map +1 -1
  76. package/dist/src/planner/analysis/constraint-extractor.d.ts +0 -7
  77. package/dist/src/planner/analysis/constraint-extractor.d.ts.map +1 -1
  78. package/dist/src/planner/analysis/constraint-extractor.js +59 -59
  79. package/dist/src/planner/analysis/constraint-extractor.js.map +1 -1
  80. package/dist/src/planner/analysis/coverage-prover.d.ts +13 -2
  81. package/dist/src/planner/analysis/coverage-prover.d.ts.map +1 -1
  82. package/dist/src/planner/analysis/coverage-prover.js +10 -14
  83. package/dist/src/planner/analysis/coverage-prover.js.map +1 -1
  84. package/dist/src/planner/analysis/predicate-conjuncts.d.ts +10 -4
  85. package/dist/src/planner/analysis/predicate-conjuncts.d.ts.map +1 -1
  86. package/dist/src/planner/analysis/predicate-conjuncts.js +28 -7
  87. package/dist/src/planner/analysis/predicate-conjuncts.js.map +1 -1
  88. package/dist/src/planner/analysis/query-rewrite-matcher.d.ts.map +1 -1
  89. package/dist/src/planner/analysis/query-rewrite-matcher.js +8 -27
  90. package/dist/src/planner/analysis/query-rewrite-matcher.js.map +1 -1
  91. package/dist/src/planner/analysis/scalar-subqueries.d.ts +22 -3
  92. package/dist/src/planner/analysis/scalar-subqueries.d.ts.map +1 -1
  93. package/dist/src/planner/analysis/scalar-subqueries.js +32 -3
  94. package/dist/src/planner/analysis/scalar-subqueries.js.map +1 -1
  95. package/dist/src/planner/analysis/set-op-type-merge.d.ts +64 -0
  96. package/dist/src/planner/analysis/set-op-type-merge.d.ts.map +1 -0
  97. package/dist/src/planner/analysis/set-op-type-merge.js +97 -0
  98. package/dist/src/planner/analysis/set-op-type-merge.js.map +1 -0
  99. package/dist/src/planner/building/alter-table.d.ts.map +1 -1
  100. package/dist/src/planner/building/alter-table.js +65 -27
  101. package/dist/src/planner/building/alter-table.js.map +1 -1
  102. package/dist/src/planner/building/coercion.d.ts +54 -0
  103. package/dist/src/planner/building/coercion.d.ts.map +1 -0
  104. package/dist/src/planner/building/coercion.js +88 -0
  105. package/dist/src/planner/building/coercion.js.map +1 -0
  106. package/dist/src/planner/building/constraint-builder.d.ts.map +1 -1
  107. package/dist/src/planner/building/constraint-builder.js +5 -18
  108. package/dist/src/planner/building/constraint-builder.js.map +1 -1
  109. package/dist/src/planner/building/expression.d.ts +17 -0
  110. package/dist/src/planner/building/expression.d.ts.map +1 -1
  111. package/dist/src/planner/building/expression.js +23 -115
  112. package/dist/src/planner/building/expression.js.map +1 -1
  113. package/dist/src/planner/building/function-call.d.ts.map +1 -1
  114. package/dist/src/planner/building/function-call.js +13 -0
  115. package/dist/src/planner/building/function-call.js.map +1 -1
  116. package/dist/src/planner/building/select-aggregates.d.ts +1 -1
  117. package/dist/src/planner/building/select-aggregates.d.ts.map +1 -1
  118. package/dist/src/planner/building/select-aggregates.js +53 -2
  119. package/dist/src/planner/building/select-aggregates.js.map +1 -1
  120. package/dist/src/planner/building/select-compound.d.ts.map +1 -1
  121. package/dist/src/planner/building/select-compound.js +10 -6
  122. package/dist/src/planner/building/select-compound.js.map +1 -1
  123. package/dist/src/planner/building/select-context.d.ts +7 -0
  124. package/dist/src/planner/building/select-context.d.ts.map +1 -1
  125. package/dist/src/planner/building/select-context.js +10 -40
  126. package/dist/src/planner/building/select-context.js.map +1 -1
  127. package/dist/src/planner/building/select.d.ts +37 -1
  128. package/dist/src/planner/building/select.d.ts.map +1 -1
  129. package/dist/src/planner/building/select.js +112 -51
  130. package/dist/src/planner/building/select.js.map +1 -1
  131. package/dist/src/planner/cost/conjunct-cost.d.ts +82 -0
  132. package/dist/src/planner/cost/conjunct-cost.d.ts.map +1 -0
  133. package/dist/src/planner/cost/conjunct-cost.js +107 -0
  134. package/dist/src/planner/cost/conjunct-cost.js.map +1 -0
  135. package/dist/src/planner/cost/index.d.ts +10 -0
  136. package/dist/src/planner/cost/index.d.ts.map +1 -1
  137. package/dist/src/planner/cost/index.js +15 -0
  138. package/dist/src/planner/cost/index.js.map +1 -1
  139. package/dist/src/planner/mutation/propagate.d.ts.map +1 -1
  140. package/dist/src/planner/mutation/propagate.js +1 -0
  141. package/dist/src/planner/mutation/propagate.js.map +1 -1
  142. package/dist/src/planner/nodes/aggregate-node.d.ts +6 -0
  143. package/dist/src/planner/nodes/aggregate-node.d.ts.map +1 -1
  144. package/dist/src/planner/nodes/aggregate-node.js +10 -2
  145. package/dist/src/planner/nodes/aggregate-node.js.map +1 -1
  146. package/dist/src/planner/nodes/alias-node.d.ts.map +1 -1
  147. package/dist/src/planner/nodes/alias-node.js +2 -1
  148. package/dist/src/planner/nodes/alias-node.js.map +1 -1
  149. package/dist/src/planner/nodes/alter-table-node.d.ts +24 -8
  150. package/dist/src/planner/nodes/alter-table-node.d.ts.map +1 -1
  151. package/dist/src/planner/nodes/alter-table-node.js.map +1 -1
  152. package/dist/src/planner/nodes/asof-scan-node.d.ts.map +1 -1
  153. package/dist/src/planner/nodes/asof-scan-node.js +4 -1
  154. package/dist/src/planner/nodes/asof-scan-node.js.map +1 -1
  155. package/dist/src/planner/nodes/asserted-keys-node.d.ts.map +1 -1
  156. package/dist/src/planner/nodes/asserted-keys-node.js +2 -1
  157. package/dist/src/planner/nodes/asserted-keys-node.js.map +1 -1
  158. package/dist/src/planner/nodes/async-gather-node.d.ts.map +1 -1
  159. package/dist/src/planner/nodes/async-gather-node.js +19 -8
  160. package/dist/src/planner/nodes/async-gather-node.js.map +1 -1
  161. package/dist/src/planner/nodes/bloom-join-node.d.ts +2 -0
  162. package/dist/src/planner/nodes/bloom-join-node.d.ts.map +1 -1
  163. package/dist/src/planner/nodes/bloom-join-node.js +17 -7
  164. package/dist/src/planner/nodes/bloom-join-node.js.map +1 -1
  165. package/dist/src/planner/nodes/cache-node.d.ts.map +1 -1
  166. package/dist/src/planner/nodes/cache-node.js +2 -1
  167. package/dist/src/planner/nodes/cache-node.js.map +1 -1
  168. package/dist/src/planner/nodes/distinct-node.d.ts +6 -0
  169. package/dist/src/planner/nodes/distinct-node.d.ts.map +1 -1
  170. package/dist/src/planner/nodes/distinct-node.js +10 -2
  171. package/dist/src/planner/nodes/distinct-node.js.map +1 -1
  172. package/dist/src/planner/nodes/eager-prefetch-node.d.ts.map +1 -1
  173. package/dist/src/planner/nodes/eager-prefetch-node.js +2 -1
  174. package/dist/src/planner/nodes/eager-prefetch-node.js.map +1 -1
  175. package/dist/src/planner/nodes/fanout-lookup-join-node.d.ts +5 -0
  176. package/dist/src/planner/nodes/fanout-lookup-join-node.d.ts.map +1 -1
  177. package/dist/src/planner/nodes/fanout-lookup-join-node.js +13 -6
  178. package/dist/src/planner/nodes/fanout-lookup-join-node.js.map +1 -1
  179. package/dist/src/planner/nodes/filter.d.ts.map +1 -1
  180. package/dist/src/planner/nodes/filter.js +12 -5
  181. package/dist/src/planner/nodes/filter.js.map +1 -1
  182. package/dist/src/planner/nodes/hash-aggregate.d.ts +6 -0
  183. package/dist/src/planner/nodes/hash-aggregate.d.ts.map +1 -1
  184. package/dist/src/planner/nodes/hash-aggregate.js +10 -2
  185. package/dist/src/planner/nodes/hash-aggregate.js.map +1 -1
  186. package/dist/src/planner/nodes/join-node.d.ts +25 -1
  187. package/dist/src/planner/nodes/join-node.d.ts.map +1 -1
  188. package/dist/src/planner/nodes/join-node.js +38 -10
  189. package/dist/src/planner/nodes/join-node.js.map +1 -1
  190. package/dist/src/planner/nodes/join-utils.d.ts +78 -0
  191. package/dist/src/planner/nodes/join-utils.d.ts.map +1 -1
  192. package/dist/src/planner/nodes/join-utils.js +73 -0
  193. package/dist/src/planner/nodes/join-utils.js.map +1 -1
  194. package/dist/src/planner/nodes/key-set-semi-join-node.d.ts +87 -0
  195. package/dist/src/planner/nodes/key-set-semi-join-node.d.ts.map +1 -0
  196. package/dist/src/planner/nodes/key-set-semi-join-node.js +141 -0
  197. package/dist/src/planner/nodes/key-set-semi-join-node.js.map +1 -0
  198. package/dist/src/planner/nodes/lens-auxiliary-access-node.d.ts.map +1 -1
  199. package/dist/src/planner/nodes/lens-auxiliary-access-node.js +2 -1
  200. package/dist/src/planner/nodes/lens-auxiliary-access-node.js.map +1 -1
  201. package/dist/src/planner/nodes/limit-offset.d.ts +6 -0
  202. package/dist/src/planner/nodes/limit-offset.d.ts.map +1 -1
  203. package/dist/src/planner/nodes/limit-offset.js +10 -2
  204. package/dist/src/planner/nodes/limit-offset.js.map +1 -1
  205. package/dist/src/planner/nodes/merge-join-node.d.ts +2 -0
  206. package/dist/src/planner/nodes/merge-join-node.d.ts.map +1 -1
  207. package/dist/src/planner/nodes/merge-join-node.js +21 -8
  208. package/dist/src/planner/nodes/merge-join-node.js.map +1 -1
  209. package/dist/src/planner/nodes/ordinal-slice-node.d.ts +5 -0
  210. package/dist/src/planner/nodes/ordinal-slice-node.d.ts.map +1 -1
  211. package/dist/src/planner/nodes/ordinal-slice-node.js +9 -2
  212. package/dist/src/planner/nodes/ordinal-slice-node.js.map +1 -1
  213. package/dist/src/planner/nodes/plan-node-type.d.ts +1 -0
  214. package/dist/src/planner/nodes/plan-node-type.d.ts.map +1 -1
  215. package/dist/src/planner/nodes/plan-node-type.js +1 -0
  216. package/dist/src/planner/nodes/plan-node-type.js.map +1 -1
  217. package/dist/src/planner/nodes/plan-node.d.ts +24 -0
  218. package/dist/src/planner/nodes/plan-node.d.ts.map +1 -1
  219. package/dist/src/planner/nodes/plan-node.js.map +1 -1
  220. package/dist/src/planner/nodes/project-node.d.ts.map +1 -1
  221. package/dist/src/planner/nodes/project-node.js +2 -1
  222. package/dist/src/planner/nodes/project-node.js.map +1 -1
  223. package/dist/src/planner/nodes/reference.d.ts.map +1 -1
  224. package/dist/src/planner/nodes/reference.js +3 -2
  225. package/dist/src/planner/nodes/reference.js.map +1 -1
  226. package/dist/src/planner/nodes/retrieve-node.d.ts.map +1 -1
  227. package/dist/src/planner/nodes/retrieve-node.js +2 -1
  228. package/dist/src/planner/nodes/retrieve-node.js.map +1 -1
  229. package/dist/src/planner/nodes/scalar.d.ts.map +1 -1
  230. package/dist/src/planner/nodes/scalar.js +5 -18
  231. package/dist/src/planner/nodes/scalar.js.map +1 -1
  232. package/dist/src/planner/nodes/set-operation-node.d.ts +68 -16
  233. package/dist/src/planner/nodes/set-operation-node.d.ts.map +1 -1
  234. package/dist/src/planner/nodes/set-operation-node.js +147 -26
  235. package/dist/src/planner/nodes/set-operation-node.js.map +1 -1
  236. package/dist/src/planner/nodes/sort.d.ts.map +1 -1
  237. package/dist/src/planner/nodes/sort.js +3 -1
  238. package/dist/src/planner/nodes/sort.js.map +1 -1
  239. package/dist/src/planner/nodes/stream-aggregate.d.ts +6 -0
  240. package/dist/src/planner/nodes/stream-aggregate.d.ts.map +1 -1
  241. package/dist/src/planner/nodes/stream-aggregate.js +10 -2
  242. package/dist/src/planner/nodes/stream-aggregate.js.map +1 -1
  243. package/dist/src/planner/nodes/table-access-nodes.d.ts +19 -1
  244. package/dist/src/planner/nodes/table-access-nodes.d.ts.map +1 -1
  245. package/dist/src/planner/nodes/table-access-nodes.js +18 -2
  246. package/dist/src/planner/nodes/table-access-nodes.js.map +1 -1
  247. package/dist/src/planner/nodes/window-function.d.ts.map +1 -1
  248. package/dist/src/planner/nodes/window-function.js +8 -0
  249. package/dist/src/planner/nodes/window-function.js.map +1 -1
  250. package/dist/src/planner/nodes/window-node.d.ts +1 -1
  251. package/dist/src/planner/nodes/window-node.d.ts.map +1 -1
  252. package/dist/src/planner/nodes/window-node.js +3 -1
  253. package/dist/src/planner/nodes/window-node.js.map +1 -1
  254. package/dist/src/planner/optimizer.d.ts +9 -1
  255. package/dist/src/planner/optimizer.d.ts.map +1 -1
  256. package/dist/src/planner/optimizer.js +126 -6
  257. package/dist/src/planner/optimizer.js.map +1 -1
  258. package/dist/src/planner/rules/access/rule-key-set-seek.d.ts +68 -0
  259. package/dist/src/planner/rules/access/rule-key-set-seek.d.ts.map +1 -0
  260. package/dist/src/planner/rules/access/rule-key-set-seek.js +343 -0
  261. package/dist/src/planner/rules/access/rule-key-set-seek.js.map +1 -0
  262. package/dist/src/planner/rules/access/rule-monotonic-range-access.d.ts.map +1 -1
  263. package/dist/src/planner/rules/access/rule-monotonic-range-access.js +2 -2
  264. package/dist/src/planner/rules/access/rule-monotonic-range-access.js.map +1 -1
  265. package/dist/src/planner/rules/access/rule-select-access-path.d.ts +46 -0
  266. package/dist/src/planner/rules/access/rule-select-access-path.d.ts.map +1 -1
  267. package/dist/src/planner/rules/access/rule-select-access-path.js +54 -13
  268. package/dist/src/planner/rules/access/rule-select-access-path.js.map +1 -1
  269. package/dist/src/planner/rules/cache/rule-cte-optimization.d.ts.map +1 -1
  270. package/dist/src/planner/rules/cache/rule-cte-optimization.js +9 -0
  271. package/dist/src/planner/rules/cache/rule-cte-optimization.js.map +1 -1
  272. package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.d.ts.map +1 -1
  273. package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.js +6 -4
  274. package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.js.map +1 -1
  275. package/dist/src/planner/rules/join/equi-pair-extractor.d.ts +49 -49
  276. package/dist/src/planner/rules/join/equi-pair-extractor.d.ts.map +1 -1
  277. package/dist/src/planner/rules/join/equi-pair-extractor.js +54 -58
  278. package/dist/src/planner/rules/join/equi-pair-extractor.js.map +1 -1
  279. package/dist/src/planner/rules/join/index-nested-loop.d.ts +49 -0
  280. package/dist/src/planner/rules/join/index-nested-loop.d.ts.map +1 -0
  281. package/dist/src/planner/rules/join/index-nested-loop.js +285 -0
  282. package/dist/src/planner/rules/join/index-nested-loop.js.map +1 -0
  283. package/dist/src/planner/rules/join/rule-fanout-batched-outer.js +8 -8
  284. package/dist/src/planner/rules/join/rule-join-physical-selection.d.ts +10 -4
  285. package/dist/src/planner/rules/join/rule-join-physical-selection.d.ts.map +1 -1
  286. package/dist/src/planner/rules/join/rule-join-physical-selection.js +97 -29
  287. package/dist/src/planner/rules/join/rule-join-physical-selection.js.map +1 -1
  288. package/dist/src/planner/rules/join/rule-monotonic-merge-join.d.ts.map +1 -1
  289. package/dist/src/planner/rules/join/rule-monotonic-merge-join.js +15 -5
  290. package/dist/src/planner/rules/join/rule-monotonic-merge-join.js.map +1 -1
  291. package/dist/src/planner/rules/predicate/rule-empty-relation-folding.d.ts +1 -1
  292. package/dist/src/planner/rules/predicate/rule-empty-relation-folding.js +1 -1
  293. package/dist/src/planner/rules/predicate/rule-filter-conjunct-ordering.d.ts +43 -0
  294. package/dist/src/planner/rules/predicate/rule-filter-conjunct-ordering.d.ts.map +1 -0
  295. package/dist/src/planner/rules/predicate/rule-filter-conjunct-ordering.js +119 -0
  296. package/dist/src/planner/rules/predicate/rule-filter-conjunct-ordering.js.map +1 -0
  297. package/dist/src/planner/rules/predicate/rule-filter-selectivity.d.ts +44 -3
  298. package/dist/src/planner/rules/predicate/rule-filter-selectivity.d.ts.map +1 -1
  299. package/dist/src/planner/rules/predicate/rule-filter-selectivity.js +142 -18
  300. package/dist/src/planner/rules/predicate/rule-filter-selectivity.js.map +1 -1
  301. package/dist/src/planner/rules/predicate/rule-join-predicate-pushdown.d.ts +73 -0
  302. package/dist/src/planner/rules/predicate/rule-join-predicate-pushdown.d.ts.map +1 -0
  303. package/dist/src/planner/rules/predicate/rule-join-predicate-pushdown.js +205 -0
  304. package/dist/src/planner/rules/predicate/rule-join-predicate-pushdown.js.map +1 -0
  305. package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.d.ts +14 -11
  306. package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.d.ts.map +1 -1
  307. package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.js +21 -85
  308. package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.js.map +1 -1
  309. package/dist/src/planner/rules/predicate/rule-predicate-pushdown.d.ts +10 -2
  310. package/dist/src/planner/rules/predicate/rule-predicate-pushdown.d.ts.map +1 -1
  311. package/dist/src/planner/rules/predicate/rule-predicate-pushdown.js +20 -2
  312. package/dist/src/planner/rules/predicate/rule-predicate-pushdown.js.map +1 -1
  313. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.d.ts.map +1 -1
  314. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js +18 -3
  315. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js.map +1 -1
  316. package/dist/src/planner/rules/shared/access-leaf.d.ts +39 -0
  317. package/dist/src/planner/rules/shared/access-leaf.d.ts.map +1 -0
  318. package/dist/src/planner/rules/shared/access-leaf.js +79 -0
  319. package/dist/src/planner/rules/shared/access-leaf.js.map +1 -0
  320. package/dist/src/planner/rules/shared/index-style-context.d.ts +17 -0
  321. package/dist/src/planner/rules/shared/index-style-context.d.ts.map +1 -1
  322. package/dist/src/planner/rules/shared/index-style-context.js +7 -0
  323. package/dist/src/planner/rules/shared/index-style-context.js.map +1 -1
  324. package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.d.ts.map +1 -1
  325. package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.js +18 -8
  326. package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.js.map +1 -1
  327. package/dist/src/planner/rules/subquery/rule-semi-join-fk-trivial.d.ts +3 -1
  328. package/dist/src/planner/rules/subquery/rule-semi-join-fk-trivial.d.ts.map +1 -1
  329. package/dist/src/planner/rules/subquery/rule-semi-join-fk-trivial.js +27 -10
  330. package/dist/src/planner/rules/subquery/rule-semi-join-fk-trivial.js.map +1 -1
  331. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.d.ts +24 -6
  332. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.d.ts.map +1 -1
  333. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js +243 -70
  334. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js.map +1 -1
  335. package/dist/src/planner/rules/window/rule-monotonic-window.d.ts +17 -7
  336. package/dist/src/planner/rules/window/rule-monotonic-window.d.ts.map +1 -1
  337. package/dist/src/planner/rules/window/rule-monotonic-window.js +124 -87
  338. package/dist/src/planner/rules/window/rule-monotonic-window.js.map +1 -1
  339. package/dist/src/planner/scopes/aliased.d.ts.map +1 -1
  340. package/dist/src/planner/scopes/aliased.js +6 -0
  341. package/dist/src/planner/scopes/aliased.js.map +1 -1
  342. package/dist/src/planner/stats/catalog-stats.d.ts +87 -4
  343. package/dist/src/planner/stats/catalog-stats.d.ts.map +1 -1
  344. package/dist/src/planner/stats/catalog-stats.js +250 -28
  345. package/dist/src/planner/stats/catalog-stats.js.map +1 -1
  346. package/dist/src/planner/stats/conjunct-selectivity.d.ts +48 -0
  347. package/dist/src/planner/stats/conjunct-selectivity.d.ts.map +1 -0
  348. package/dist/src/planner/stats/conjunct-selectivity.js +168 -0
  349. package/dist/src/planner/stats/conjunct-selectivity.js.map +1 -0
  350. package/dist/src/planner/stats/index.d.ts +36 -2
  351. package/dist/src/planner/stats/index.d.ts.map +1 -1
  352. package/dist/src/planner/stats/index.js.map +1 -1
  353. package/dist/src/planner/stats/selectivity-combine.d.ts +53 -0
  354. package/dist/src/planner/stats/selectivity-combine.d.ts.map +1 -0
  355. package/dist/src/planner/stats/selectivity-combine.js +75 -0
  356. package/dist/src/planner/stats/selectivity-combine.js.map +1 -0
  357. package/dist/src/planner/stats/table-cardinality.d.ts +28 -0
  358. package/dist/src/planner/stats/table-cardinality.d.ts.map +1 -0
  359. package/dist/src/planner/stats/table-cardinality.js +29 -0
  360. package/dist/src/planner/stats/table-cardinality.js.map +1 -0
  361. package/dist/src/planner/util/column-origins.d.ts +79 -0
  362. package/dist/src/planner/util/column-origins.d.ts.map +1 -0
  363. package/dist/src/planner/util/column-origins.js +150 -0
  364. package/dist/src/planner/util/column-origins.js.map +1 -0
  365. package/dist/src/planner/util/fd-utils.d.ts +32 -8
  366. package/dist/src/planner/util/fd-utils.d.ts.map +1 -1
  367. package/dist/src/planner/util/fd-utils.js +46 -9
  368. package/dist/src/planner/util/fd-utils.js.map +1 -1
  369. package/dist/src/planner/util/ind-utils.d.ts +56 -17
  370. package/dist/src/planner/util/ind-utils.d.ts.map +1 -1
  371. package/dist/src/planner/util/ind-utils.js +82 -21
  372. package/dist/src/planner/util/ind-utils.js.map +1 -1
  373. package/dist/src/planner/util/key-utils.d.ts +20 -2
  374. package/dist/src/planner/util/key-utils.d.ts.map +1 -1
  375. package/dist/src/planner/util/key-utils.js +37 -3
  376. package/dist/src/planner/util/key-utils.js.map +1 -1
  377. package/dist/src/planner/util/row-estimates.d.ts +28 -0
  378. package/dist/src/planner/util/row-estimates.d.ts.map +1 -0
  379. package/dist/src/planner/util/row-estimates.js +40 -0
  380. package/dist/src/planner/util/row-estimates.js.map +1 -0
  381. package/dist/src/planner/util/row-population.d.ts +42 -0
  382. package/dist/src/planner/util/row-population.d.ts.map +1 -0
  383. package/dist/src/planner/util/row-population.js +71 -0
  384. package/dist/src/planner/util/row-population.js.map +1 -0
  385. package/dist/src/runtime/deferred-constraint-queue.d.ts +40 -0
  386. package/dist/src/runtime/deferred-constraint-queue.d.ts.map +1 -1
  387. package/dist/src/runtime/deferred-constraint-queue.js +90 -9
  388. package/dist/src/runtime/deferred-constraint-queue.js.map +1 -1
  389. package/dist/src/runtime/emit/add-constraint.d.ts.map +1 -1
  390. package/dist/src/runtime/emit/add-constraint.js +39 -1
  391. package/dist/src/runtime/emit/add-constraint.js.map +1 -1
  392. package/dist/src/runtime/emit/aggregate.d.ts.map +1 -1
  393. package/dist/src/runtime/emit/aggregate.js +4 -4
  394. package/dist/src/runtime/emit/aggregate.js.map +1 -1
  395. package/dist/src/runtime/emit/alter-schema-event.d.ts +42 -0
  396. package/dist/src/runtime/emit/alter-schema-event.d.ts.map +1 -0
  397. package/dist/src/runtime/emit/alter-schema-event.js +28 -0
  398. package/dist/src/runtime/emit/alter-schema-event.js.map +1 -0
  399. package/dist/src/runtime/emit/alter-table.d.ts.map +1 -1
  400. package/dist/src/runtime/emit/alter-table.js +386 -154
  401. package/dist/src/runtime/emit/alter-table.js.map +1 -1
  402. package/dist/src/runtime/emit/analyze.d.ts.map +1 -1
  403. package/dist/src/runtime/emit/analyze.js +35 -3
  404. package/dist/src/runtime/emit/analyze.js.map +1 -1
  405. package/dist/src/runtime/emit/asof-scan.d.ts.map +1 -1
  406. package/dist/src/runtime/emit/asof-scan.js +8 -3
  407. package/dist/src/runtime/emit/asof-scan.js.map +1 -1
  408. package/dist/src/runtime/emit/between.d.ts.map +1 -1
  409. package/dist/src/runtime/emit/between.js +4 -38
  410. package/dist/src/runtime/emit/between.js.map +1 -1
  411. package/dist/src/runtime/emit/binary.d.ts.map +1 -1
  412. package/dist/src/runtime/emit/binary.js +2 -23
  413. package/dist/src/runtime/emit/binary.js.map +1 -1
  414. package/dist/src/runtime/emit/bloom-join.d.ts.map +1 -1
  415. package/dist/src/runtime/emit/bloom-join.js +6 -35
  416. package/dist/src/runtime/emit/bloom-join.js.map +1 -1
  417. package/dist/src/runtime/emit/case.d.ts.map +1 -1
  418. package/dist/src/runtime/emit/case.js +40 -6
  419. package/dist/src/runtime/emit/case.js.map +1 -1
  420. package/dist/src/runtime/emit/cast.d.ts.map +1 -1
  421. package/dist/src/runtime/emit/cast.js +4 -19
  422. package/dist/src/runtime/emit/cast.js.map +1 -1
  423. package/dist/src/runtime/emit/constraint-check.d.ts.map +1 -1
  424. package/dist/src/runtime/emit/constraint-check.js +32 -8
  425. package/dist/src/runtime/emit/constraint-check.js.map +1 -1
  426. package/dist/src/runtime/emit/dml-executor.d.ts.map +1 -1
  427. package/dist/src/runtime/emit/dml-executor.js +90 -50
  428. package/dist/src/runtime/emit/dml-executor.js.map +1 -1
  429. package/dist/src/runtime/emit/drop-index.d.ts.map +1 -1
  430. package/dist/src/runtime/emit/drop-index.js +4 -3
  431. package/dist/src/runtime/emit/drop-index.js.map +1 -1
  432. package/dist/src/runtime/emit/filter.d.ts +18 -0
  433. package/dist/src/runtime/emit/filter.d.ts.map +1 -1
  434. package/dist/src/runtime/emit/filter.js +52 -10
  435. package/dist/src/runtime/emit/filter.js.map +1 -1
  436. package/dist/src/runtime/emit/hash-aggregate.d.ts.map +1 -1
  437. package/dist/src/runtime/emit/hash-aggregate.js +2 -2
  438. package/dist/src/runtime/emit/hash-aggregate.js.map +1 -1
  439. package/dist/src/runtime/emit/join-key-extractor.d.ts +46 -0
  440. package/dist/src/runtime/emit/join-key-extractor.d.ts.map +1 -0
  441. package/dist/src/runtime/emit/join-key-extractor.js +60 -0
  442. package/dist/src/runtime/emit/join-key-extractor.js.map +1 -0
  443. package/dist/src/runtime/emit/join.d.ts.map +1 -1
  444. package/dist/src/runtime/emit/join.js +8 -46
  445. package/dist/src/runtime/emit/join.js.map +1 -1
  446. package/dist/src/runtime/emit/key-set-semi-join.d.ts +29 -0
  447. package/dist/src/runtime/emit/key-set-semi-join.d.ts.map +1 -0
  448. package/dist/src/runtime/emit/key-set-semi-join.js +149 -0
  449. package/dist/src/runtime/emit/key-set-semi-join.js.map +1 -0
  450. package/dist/src/runtime/emit/merge-join.d.ts.map +1 -1
  451. package/dist/src/runtime/emit/merge-join.js +15 -8
  452. package/dist/src/runtime/emit/merge-join.js.map +1 -1
  453. package/dist/src/runtime/emit/operand-comparator.d.ts +70 -0
  454. package/dist/src/runtime/emit/operand-comparator.d.ts.map +1 -0
  455. package/dist/src/runtime/emit/operand-comparator.js +93 -0
  456. package/dist/src/runtime/emit/operand-comparator.js.map +1 -0
  457. package/dist/src/runtime/emit/scan.d.ts.map +1 -1
  458. package/dist/src/runtime/emit/scan.js +11 -2
  459. package/dist/src/runtime/emit/scan.js.map +1 -1
  460. package/dist/src/runtime/emit/schema-declarative.d.ts.map +1 -1
  461. package/dist/src/runtime/emit/schema-declarative.js +30 -0
  462. package/dist/src/runtime/emit/schema-declarative.js.map +1 -1
  463. package/dist/src/runtime/emit/subquery.d.ts.map +1 -1
  464. package/dist/src/runtime/emit/subquery.js +153 -52
  465. package/dist/src/runtime/emit/subquery.js.map +1 -1
  466. package/dist/src/runtime/emit/window.js +36 -23
  467. package/dist/src/runtime/emit/window.js.map +1 -1
  468. package/dist/src/runtime/parallel-driver.d.ts +5 -2
  469. package/dist/src/runtime/parallel-driver.d.ts.map +1 -1
  470. package/dist/src/runtime/parallel-driver.js +6 -2
  471. package/dist/src/runtime/parallel-driver.js.map +1 -1
  472. package/dist/src/runtime/register.d.ts.map +1 -1
  473. package/dist/src/runtime/register.js +2 -0
  474. package/dist/src/runtime/register.js.map +1 -1
  475. package/dist/src/runtime/types.d.ts +7 -0
  476. package/dist/src/runtime/types.d.ts.map +1 -1
  477. package/dist/src/runtime/types.js.map +1 -1
  478. package/dist/src/schema/catalog-persistability.d.ts +19 -16
  479. package/dist/src/schema/catalog-persistability.d.ts.map +1 -1
  480. package/dist/src/schema/catalog-persistability.js +76 -20
  481. package/dist/src/schema/catalog-persistability.js.map +1 -1
  482. package/dist/src/schema/catalog.d.ts +67 -2
  483. package/dist/src/schema/catalog.d.ts.map +1 -1
  484. package/dist/src/schema/catalog.js +194 -3
  485. package/dist/src/schema/catalog.js.map +1 -1
  486. package/dist/src/schema/column.d.ts +26 -3
  487. package/dist/src/schema/column.d.ts.map +1 -1
  488. package/dist/src/schema/column.js.map +1 -1
  489. package/dist/src/schema/ddl-generator.d.ts.map +1 -1
  490. package/dist/src/schema/ddl-generator.js +19 -6
  491. package/dist/src/schema/ddl-generator.js.map +1 -1
  492. package/dist/src/schema/function.d.ts +40 -0
  493. package/dist/src/schema/function.d.ts.map +1 -1
  494. package/dist/src/schema/function.js +10 -2
  495. package/dist/src/schema/function.js.map +1 -1
  496. package/dist/src/schema/manager.d.ts +84 -7
  497. package/dist/src/schema/manager.d.ts.map +1 -1
  498. package/dist/src/schema/manager.js +153 -53
  499. package/dist/src/schema/manager.js.map +1 -1
  500. package/dist/src/schema/schema-differ.d.ts.map +1 -1
  501. package/dist/src/schema/schema-differ.js +104 -28
  502. package/dist/src/schema/schema-differ.js.map +1 -1
  503. package/dist/src/schema/table.d.ts +149 -1
  504. package/dist/src/schema/table.d.ts.map +1 -1
  505. package/dist/src/schema/table.js +248 -1
  506. package/dist/src/schema/table.js.map +1 -1
  507. package/dist/src/schema/unique-enforcement.d.ts +5 -3
  508. package/dist/src/schema/unique-enforcement.d.ts.map +1 -1
  509. package/dist/src/schema/unique-enforcement.js +5 -3
  510. package/dist/src/schema/unique-enforcement.js.map +1 -1
  511. package/dist/src/types/builtin-types.d.ts +16 -0
  512. package/dist/src/types/builtin-types.d.ts.map +1 -1
  513. package/dist/src/types/builtin-types.js +76 -12
  514. package/dist/src/types/builtin-types.js.map +1 -1
  515. package/dist/src/types/cast-semantics.d.ts +42 -0
  516. package/dist/src/types/cast-semantics.d.ts.map +1 -1
  517. package/dist/src/types/cast-semantics.js +64 -0
  518. package/dist/src/types/cast-semantics.js.map +1 -1
  519. package/dist/src/types/comparison-coercion.d.ts +101 -0
  520. package/dist/src/types/comparison-coercion.d.ts.map +1 -0
  521. package/dist/src/types/comparison-coercion.js +162 -0
  522. package/dist/src/types/comparison-coercion.js.map +1 -0
  523. package/dist/src/types/index.d.ts +2 -2
  524. package/dist/src/types/index.d.ts.map +1 -1
  525. package/dist/src/types/index.js +2 -2
  526. package/dist/src/types/index.js.map +1 -1
  527. package/dist/src/types/logical-type.d.ts +7 -0
  528. package/dist/src/types/logical-type.d.ts.map +1 -1
  529. package/dist/src/types/logical-type.js.map +1 -1
  530. package/dist/src/types/validation.d.ts +56 -0
  531. package/dist/src/types/validation.d.ts.map +1 -1
  532. package/dist/src/types/validation.js +68 -0
  533. package/dist/src/types/validation.js.map +1 -1
  534. package/dist/src/util/coercion.d.ts +8 -0
  535. package/dist/src/util/coercion.d.ts.map +1 -1
  536. package/dist/src/util/coercion.js +8 -0
  537. package/dist/src/util/coercion.js.map +1 -1
  538. package/dist/src/util/comparison.d.ts +41 -3
  539. package/dist/src/util/comparison.d.ts.map +1 -1
  540. package/dist/src/util/comparison.js +52 -5
  541. package/dist/src/util/comparison.js.map +1 -1
  542. package/dist/src/util/key-serializer.d.ts +46 -0
  543. package/dist/src/util/key-serializer.d.ts.map +1 -1
  544. package/dist/src/util/key-serializer.js +47 -0
  545. package/dist/src/util/key-serializer.js.map +1 -1
  546. package/dist/src/util/value-set.d.ts +21 -0
  547. package/dist/src/util/value-set.d.ts.map +1 -0
  548. package/dist/src/util/value-set.js +22 -0
  549. package/dist/src/util/value-set.js.map +1 -0
  550. package/dist/src/vtab/best-access-plan.d.ts +87 -0
  551. package/dist/src/vtab/best-access-plan.d.ts.map +1 -1
  552. package/dist/src/vtab/best-access-plan.js +87 -0
  553. package/dist/src/vtab/best-access-plan.js.map +1 -1
  554. package/dist/src/vtab/capabilities.d.ts +1 -1
  555. package/dist/src/vtab/events.d.ts +5 -1
  556. package/dist/src/vtab/events.d.ts.map +1 -1
  557. package/dist/src/vtab/events.js.map +1 -1
  558. package/dist/src/vtab/memory/layer/alter-column.d.ts +147 -0
  559. package/dist/src/vtab/memory/layer/alter-column.d.ts.map +1 -0
  560. package/dist/src/vtab/memory/layer/alter-column.js +214 -0
  561. package/dist/src/vtab/memory/layer/alter-column.js.map +1 -0
  562. package/dist/src/vtab/memory/layer/base.d.ts +6 -4
  563. package/dist/src/vtab/memory/layer/base.d.ts.map +1 -1
  564. package/dist/src/vtab/memory/layer/base.js +10 -7
  565. package/dist/src/vtab/memory/layer/base.js.map +1 -1
  566. package/dist/src/vtab/memory/layer/interface.d.ts +2 -2
  567. package/dist/src/vtab/memory/layer/interface.d.ts.map +1 -1
  568. package/dist/src/vtab/memory/layer/manager.d.ts +232 -39
  569. package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
  570. package/dist/src/vtab/memory/layer/manager.js +657 -519
  571. package/dist/src/vtab/memory/layer/manager.js.map +1 -1
  572. package/dist/src/vtab/memory/layer/scan-layer.d.ts.map +1 -1
  573. package/dist/src/vtab/memory/layer/scan-layer.js +32 -5
  574. package/dist/src/vtab/memory/layer/scan-layer.js.map +1 -1
  575. package/dist/src/vtab/memory/layer/transaction.d.ts +111 -16
  576. package/dist/src/vtab/memory/layer/transaction.d.ts.map +1 -1
  577. package/dist/src/vtab/memory/layer/transaction.js +154 -51
  578. package/dist/src/vtab/memory/layer/transaction.js.map +1 -1
  579. package/dist/src/vtab/memory/module.d.ts +14 -2
  580. package/dist/src/vtab/memory/module.d.ts.map +1 -1
  581. package/dist/src/vtab/memory/module.js +53 -40
  582. package/dist/src/vtab/memory/module.js.map +1 -1
  583. package/dist/src/vtab/memory/table.d.ts +1 -1
  584. package/dist/src/vtab/memory/table.d.ts.map +1 -1
  585. package/dist/src/vtab/memory/table.js +15 -3
  586. package/dist/src/vtab/memory/table.js.map +1 -1
  587. package/dist/src/vtab/module.d.ts +51 -21
  588. package/dist/src/vtab/module.d.ts.map +1 -1
  589. package/dist/src/vtab/table.d.ts +10 -2
  590. package/dist/src/vtab/table.d.ts.map +1 -1
  591. package/dist/src/vtab/table.js +1 -1
  592. package/dist/src/vtab/table.js.map +1 -1
  593. package/package.json +3 -3
@@ -1,6 +1,7 @@
1
- import { buildColumnIndexMap, columnDefToSchema, resolvePkDefaultConflict, resolveNamedConstraintClass, validateCollationForType } from '../../../schema/table.js';
1
+ import { buildColumnIndexMap, columnDefToSchema, resolvePkDefaultConflict, resolveNamedConstraintClass, shiftSchemaIndicesForDrop, rekeySchemaPrimaryKey } from '../../../schema/table.js';
2
2
  import { keyParts } from '../types.js';
3
3
  import { BTree } from 'inheritree';
4
+ import { createValueSet } from '../../../util/value-set.js';
4
5
  import { StatusCode } from '../../../common/types.js';
5
6
  import { BaseLayer, iteratePrimaryRows, populateIndexFromRows, populateIndexFromRowsAsync } from './base.js';
6
7
  import { TransactionLayer } from './transaction.js';
@@ -8,22 +9,35 @@ import { MemoryTableConnection } from './connection.js';
8
9
  import { MemoryVirtualTableConnection } from '../connection.js';
9
10
  import { QuereusError } from '../../../common/errors.js';
10
11
  import { ConflictResolution } from '../../../common/constants.js';
11
- import { buildUniqueConstraintSchema, buildForeignKeyConstraintSchema, buildCheckConstraintSchema, validateForeignKeyOverExistingRows, maintainedTableUniqueViolationError } from '../../../schema/constraint-builder.js';
12
+ import { buildUniqueConstraintSchema, buildForeignKeyConstraintSchema, buildCheckConstraintSchema, validateForeignKeyOverExistingRows, maintainedTableUniqueViolationError, formatKeyValue } from '../../../schema/constraint-builder.js';
12
13
  import { indexEnforcesUnique, uniqueEnforcementCollations, uniqueEnforcementComparators } from '../../../schema/unique-enforcement.js';
13
14
  import { generateIndexDDL, generateDropIndexDDL } from '../../../schema/ddl-generator.js';
14
- import { compareSqlValues, rowsValueIdentical, normalizeCollationName, comparisonSemanticsDiffer } from '../../../util/comparison.js';
15
+ import { compareSqlValues, rowsValueIdentical, normalizeCollationName } from '../../../util/comparison.js';
15
16
  import { scanLayer as scanLayerImpl } from './scan-layer.js';
16
17
  import { createPrimaryKeyFunctions, buildPrimaryKeyFromValues, primaryKeyArity } from '../utils/primary-key.js';
17
18
  import { createMemoryTableLoggers } from '../utils/logging.js';
18
- import { tryFoldLiteral } from '../../../parser/utils.js';
19
- import { validateAndParse, coerceRowToSchema } from '../../../types/validation.js';
20
- import { inferType } from '../../../types/registry.js';
19
+ import { coerceRowToSchema, foldDefaultToType } from '../../../types/validation.js';
21
20
  import { compilePredicate } from '../utils/predicate.js';
22
21
  import { renameColumnInIndexPredicates } from '../../../schema/rename-rewriter.js';
23
22
  import { MemoryIndex } from '../index.js';
24
23
  import { convertRowAtIndex, mapRows, mapRowsAsync } from './row-convert.js';
24
+ import { buildAlterColumnPlan, planColumnAttributeChange } from './alter-column.js';
25
25
  let tableManagerCounter = 0;
26
26
  const logger = createMemoryTableLoggers('layer:manager');
27
+ /**
28
+ * The `_uc_<cols>` auto-name of the implicit covering structure realizing an UNNAMED
29
+ * UNIQUE constraint, resolved over an EXPLICIT column list rather than the manager's
30
+ * live one — `renameColumn` needs the name under the POST-rename columns, which
31
+ * `this.tableSchema` does not carry yet.
32
+ *
33
+ * The name is derived, never recorded, so it MOVES when a covered column is renamed.
34
+ * Every consumer re-derives it (see the catalog's `implicitIndexNameForColumns`, which
35
+ * this mirrors and must stay equal to, and `quereus-store`'s `implicitUniqueIndexName`);
36
+ * keep this the only spelling of the rule inside this file.
37
+ */
38
+ function implicitIndexNameOver(uc, columns) {
39
+ return `_uc_${uc.columns.map(i => columns[i]?.name ?? String(i)).join('_')}`;
40
+ }
27
41
  /** A copy of `items` with `value` inserted at `at` (`at === items.length` ⇒ append). */
28
42
  function insertValueAt(items, at, value) {
29
43
  const out = items.slice();
@@ -178,16 +192,36 @@ export class MemoryTableManager {
178
192
  // it would enforce this non-derived UC under the index's collation rather
179
193
  // than the declared one. A collation-mismatched index falls through to a
180
194
  // distinct `_uc_*` covering index and coexists as its own constraint.
181
- const matchingIndex = existingIndexes.find(idx => idx.columns.length === uc.columns.length &&
195
+ // A filtered (partial) index only covers rows matching its own predicate, so
196
+ // it's never valid backing for an unfiltered UNIQUE — and a filtered UNIQUE
197
+ // already owns its index, so it isn't part of this search either (mirrors
198
+ // `findReusableIndexForUnique` in quereus-store's implicit-unique-index.ts).
199
+ const matchingIndex = uc.predicate ? undefined : existingIndexes.find(idx => !idx.predicate &&
200
+ idx.columns.length === uc.columns.length &&
182
201
  idx.columns.every((col, i) => col.index === uc.columns[i]) &&
183
202
  this.indexCollationsMatchDeclared(idx, uc));
203
+ // The name this constraint's own structure would take, and whoever already holds
204
+ // it in `newIndexes` — a pre-existing index, or the structure an EARLIER
205
+ // constraint in this same loop just pushed. A held name is ADOPTED, never pushed
206
+ // a second time: two entries under one name is the state `importDDL` warns about
207
+ // (see `SchemaManager.importCatalog`) — `index_info()` then reports neither,
208
+ // `DROP INDEX` answers `no such index`, and a predicate over the covered column
209
+ // stops filtering. No write path can produce a duplicate unnamed UNIQUE any more
210
+ // (`assertNoDuplicateUniqueConstraints` / `assertUniqueConstraintNotDuplicated`
211
+ // refuse it at every declaration site), so this arm is defence in depth for a
212
+ // catalog written before those guards: it degrades to one shared structure and
213
+ // double enforcement rather than to a corrupt index list.
214
+ const wantedName = uc.name ?? this.implicitIndexNameFor(uc);
215
+ const claimedIndex = newIndexes.find(idx => idx.name.toLowerCase() === wantedName.toLowerCase());
184
216
  let indexName;
185
217
  if (matchingIndex) {
186
218
  indexName = matchingIndex.name;
187
219
  }
220
+ else if (claimedIndex) {
221
+ indexName = claimedIndex.name;
222
+ }
188
223
  else {
189
- const colNames = uc.columns.map(i => this.tableSchema.columns[i]?.name ?? String(i));
190
- indexName = uc.name ?? `_uc_${colNames.join('_')}`;
224
+ indexName = wantedName;
191
225
  newIndexes.push({
192
226
  name: indexName,
193
227
  // Carry each column's declared collation so the auto-index — and the
@@ -220,10 +254,70 @@ export class MemoryTableManager {
220
254
  const indexName = uc.name ?? this.implicitIndexNameFor(uc);
221
255
  return this.implicitCoveringStructures.get(indexName);
222
256
  }
223
- /** Conventional auto-index name for an unnamed UNIQUE constraint (mirrors {@link ensureUniqueConstraintIndexes}). */
257
+ /** Conventional auto-index name for an unnamed UNIQUE constraint, over the LIVE columns. */
224
258
  implicitIndexNameFor(uc) {
225
- const colNames = uc.columns.map(i => this.tableSchema.columns[i]?.name ?? String(i));
226
- return `_uc_${colNames.join('_')}`;
259
+ return implicitIndexNameOver(uc, this.tableSchema.columns);
260
+ }
261
+ /**
262
+ * The materialized covering-index renames a column rename forces: one per UNNAMED
263
+ * UNIQUE constraint covering the renamed column, from its `_uc_<old cols>` name to
264
+ * its `_uc_<new cols>` one.
265
+ *
266
+ * The auto-name is derived from the live column names and recomputed by every
267
+ * consumer ({@link implicitIndexNameFor} here, the catalog's exposure map behind
268
+ * `schema()` / `index_info()`), so leaving the entry under its pre-rename name
269
+ * severs the constraint↔structure link: the structure stops being recognized as
270
+ * hidden (it surfaces as a user index and becomes droppable, taking the
271
+ * constraint's enforcement structure with it) and
272
+ * {@link getImplicitCoveringStructure} falls through to its column-set fallback.
273
+ *
274
+ * Skipped for constraints that do not move: a NAMED one's structure takes the
275
+ * constraint's name, a `derivedFromIndex` one IS the user's index, and one realized
276
+ * by a REUSED user index has no `_uc_*` entry to rename (that index keeps its own
277
+ * name).
278
+ *
279
+ * A case-only column rename DOES produce a rename here, even though every name
280
+ * comparison folds case: the covering-structure map is keyed by the exact derived
281
+ * string, so leaving `_uc_a` in place while consumers derive `_uc_A` would break
282
+ * {@link getImplicitCoveringStructure} the same way. It cannot collide — an index
283
+ * named `_uc_A` could never have been created alongside `_uc_a` in the first place.
284
+ *
285
+ * Pure — the caller applies the result to the new index list and, once the schema
286
+ * swap has succeeded, to {@link implicitCoveringStructures}.
287
+ */
288
+ planImplicitCoveringIndexRenames(updatedCols, renamedColIndex) {
289
+ const renames = [];
290
+ const indexes = this.tableSchema.indexes ?? [];
291
+ for (const uc of this.tableSchema.uniqueConstraints ?? []) {
292
+ if (uc.name !== undefined || uc.derivedFromIndex !== undefined)
293
+ continue;
294
+ if (!uc.columns.includes(renamedColIndex))
295
+ continue;
296
+ const oldName = implicitIndexNameOver(uc, this.tableSchema.columns);
297
+ const newName = implicitIndexNameOver(uc, updatedCols);
298
+ if (oldName === newName)
299
+ continue;
300
+ if (!indexes.some(idx => idx.name.toLowerCase() === oldName.toLowerCase()))
301
+ continue;
302
+ renames.push({ oldName, newName });
303
+ }
304
+ return renames;
305
+ }
306
+ /**
307
+ * Re-keys {@link implicitCoveringStructures} for the renames
308
+ * {@link planImplicitCoveringIndexRenames} produced — the map is keyed by
309
+ * constraint identity, which for an unnamed constraint IS the derived index name.
310
+ * Runs only after the schema swap succeeded, so a failed rename leaves the map
311
+ * agreeing with the restored schema.
312
+ */
313
+ applyImplicitCoveringStructureRenames(renames) {
314
+ for (const { oldName, newName } of renames) {
315
+ const rec = this.implicitCoveringStructures.get(oldName);
316
+ if (!rec)
317
+ continue;
318
+ this.implicitCoveringStructures.delete(oldName);
319
+ this.implicitCoveringStructures.set(newName, { ...rec, indexName: newName });
320
+ }
227
321
  }
228
322
  /**
229
323
  * True when a same-column-set index's per-column collations are
@@ -504,21 +598,23 @@ export class MemoryTableManager {
504
598
  connection.clearSavepoints();
505
599
  // Emit data change events after successful commit
506
600
  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;
601
+ // The contract's `key` is the primary key projected out of the event's OWN row
602
+ // image — `newRow` for an insert or an update, `oldRow` for a delete — under the
603
+ // schema current at DELIVERY (docs/usage.md § Subscribing to Data Changes).
604
+ // Projecting here rather than replaying the key each write recorded is what
605
+ // makes an in-place rewrite that changes the key's VALUE but not the row's
606
+ // identity (a NOCASE 'apple' → 'APPLE', which `recordUpsert` files under the
607
+ // pre-image key) hand listeners the post-image the table actually holds. It also
608
+ // re-derives the key across a mid-transaction ALTER PRIMARY KEY for free: the
609
+ // pending-change images are already reshaped to the delivered schema, so
610
+ // projecting them through THAT schema's key is the re-key.
611
+ const pkIndices = this.tableSchema.primaryKeyDefinition.map(def => def.index);
516
612
  for (const change of changes) {
517
613
  const event = {
518
614
  type: change.type,
519
615
  schemaName: this.schemaName,
520
616
  tableName: this._tableName,
521
- key: keyParts(change.pk, eventKeyIsTuple),
617
+ key: this.eventKeyFromImage(change, pkIndices),
522
618
  oldRow: change.oldRow,
523
619
  newRow: change.newRow,
524
620
  };
@@ -535,6 +631,25 @@ export class MemoryTableManager {
535
631
  logger.debugLog(`[Commit ${connection.connectionId}] Released lock for ${this._tableName}`);
536
632
  }
537
633
  }
634
+ /**
635
+ * The delivered `key` of one pending change: the primary key projected out of the change's
636
+ * own row image (`newRow` for an insert or an update, `oldRow` for a delete) through
637
+ * `pkIndices`, the key columns of the schema current at delivery.
638
+ *
639
+ * Best-effort, exactly like the pending-change image reshape it consumes: an image the
640
+ * reshape had to leave at the retired arity cannot be projected, so the event ships with no
641
+ * key and logs rather than emitting an `undefined` key slot a listener would file a row
642
+ * under. (A `PendingChange` always carries at least one image — `recordUpsert` sets
643
+ * `newRow`, `recordDelete` sets `oldRow` — so a missing image means only that.)
644
+ */
645
+ eventKeyFromImage(change, pkIndices) {
646
+ const image = change.newRow ?? change.oldRow;
647
+ if (image && pkIndices.every(i => i >= 0 && i < image.length)) {
648
+ return pkIndices.map(i => image[i]);
649
+ }
650
+ logger.warn('Commit Transaction', this._tableName, `${change.type} event has no image the delivered primary key can be projected from; delivering it with no key`);
651
+ return undefined;
652
+ }
538
653
  /** All layers in `layer`'s parent chain, including `layer` itself. */
539
654
  layerChainSet(layer) {
540
655
  const chain = new Set();
@@ -1057,8 +1172,8 @@ export class MemoryTableManager {
1057
1172
  // finer index (e.g. BINARY over a NOCASE column) no longer collapses onto
1058
1173
  // the constraint; resolving by name is robust to `schema.indexes` order
1059
1174
  // (the finer index may be listed earlier, having been created first).
1060
- // Both fall through to the column-set scan only when the name does not
1061
- // resolve (defensive).
1175
+ // Both fall through to the column-set scan below when the name does not
1176
+ // resolve — see there for when that actually happens.
1062
1177
  if (uc.derivedFromIndex) {
1063
1178
  const index = targetLayer.getSecondaryIndex?.(uc.derivedFromIndex);
1064
1179
  if (index)
@@ -1072,14 +1187,40 @@ export class MemoryTableManager {
1072
1187
  return { kind: 'memory-index', index };
1073
1188
  }
1074
1189
  }
1075
- // Defensive fallback: match the auto-built `_uc_*` covering index by
1076
- // column-set when the by-name resolution above did not land.
1190
+ // Fallback: match the covering index by column-set when the by-name
1191
+ // resolution above did not land. Reached more often than "defensive"
1192
+ // suggests — the reuse arms of `ensureUniqueConstraintIndexes` /
1193
+ // `addUniqueConstraint` register an UNNAMED constraint's structure under the
1194
+ // REUSED index's name, which `getImplicitCoveringStructure` (keyed
1195
+ // `_uc_<cols>`) then cannot find; and a reused index that is later dropped
1196
+ // leaves the recorded name unresolvable.
1197
+ //
1198
+ // So this scan must apply the SAME admissibility rule as those two reuse
1199
+ // searches, or it re-opens the hole they close: a FILTERED index holds only
1200
+ // the rows its predicate admits, and `checkSingleUniqueConstraint` skips the
1201
+ // check outright for a row outside the covering index's predicate — adopting
1202
+ // one for an unfiltered UNIQUE silently narrows enforcement to the
1203
+ // predicate's scope. A collation-mismatched index enforces under ITS
1204
+ // collation rather than the declared one, the same way.
1205
+ //
1206
+ // Returning undefined is always SAFE: the caller falls back to the
1207
+ // predicate- and collation-aware full scan, which is slower but exact. So a
1208
+ // filtered constraint does not guess here at all — its own structure is
1209
+ // resolved by name above.
1210
+ if (uc.predicate)
1211
+ return undefined;
1077
1212
  for (const idx of schema.indexes) {
1078
- if (idx.columns.length === uc.columns.length &&
1079
- idx.columns.every((col, i) => col.index === uc.columns[i])) {
1080
- const index = targetLayer.getSecondaryIndex?.(idx.name);
1081
- return index ? { kind: 'memory-index', index } : undefined;
1082
- }
1213
+ if (idx.predicate)
1214
+ continue;
1215
+ if (idx.columns.length !== uc.columns.length)
1216
+ continue;
1217
+ if (!idx.columns.every((col, i) => col.index === uc.columns[i]))
1218
+ continue;
1219
+ if (!this.indexCollationsMatchDeclared(idx, uc))
1220
+ continue;
1221
+ const index = targetLayer.getSecondaryIndex?.(idx.name);
1222
+ if (index)
1223
+ return { kind: 'memory-index', index };
1083
1224
  }
1084
1225
  return undefined;
1085
1226
  }
@@ -1315,27 +1456,6 @@ export class MemoryTableManager {
1315
1456
  objectName: newName,
1316
1457
  });
1317
1458
  }
1318
- /** Iterates all committed rows from the current committed layer (for rebuild). */
1319
- scanAllRows() {
1320
- const tree = this._currentCommittedLayer.getModificationTree('primary');
1321
- if (!tree)
1322
- return [];
1323
- const rows = [];
1324
- for (const path of tree.ascending(tree.first())) {
1325
- rows.push(tree.at(path));
1326
- }
1327
- return rows;
1328
- }
1329
- /** Inserts a row directly into the base layer (for rebuild, bypasses transaction).
1330
- * Throws on duplicate primary key. */
1331
- insertRow(row) {
1332
- const key = this.primaryKeyFunctions.extractFromRow(row);
1333
- const path = this.baseLayer.primaryTree.find(key);
1334
- if (path.on) {
1335
- throw new QuereusError(`UNIQUE constraint failed: ${this._tableName} PK.`, StatusCode.CONSTRAINT);
1336
- }
1337
- this.baseLayer.primaryTree.insert(row);
1338
- }
1339
1459
  /**
1340
1460
  * Atomically replaces the entire committed contents with `rows` by building a
1341
1461
  * fresh {@link BaseLayer} and swapping it in under the SchemaChange latch.
@@ -1504,7 +1624,9 @@ export class MemoryTableManager {
1504
1624
  oldByKey.insert({ key: this.primaryKeyFunctions.extractFromRow(row), row });
1505
1625
  }
1506
1626
  // New-row keys (same PK comparator) for the delete pass's membership test.
1507
- const newKeys = new BTree(k => k, this.comparePrimaryKeys);
1627
+ // Entry is the bare PK value, so it must be a non-freezing set: a
1628
+ // single-column BLOB PK is a `Uint8Array`, which `Object.freeze` rejects.
1629
+ const newKeys = createValueSet(this.comparePrimaryKeys);
1508
1630
  // Insert/update/skip-identical pass, in new-row order.
1509
1631
  for (const newRow of op.rows) {
1510
1632
  const key = this.primaryKeyFunctions.extractFromRow(newRow);
@@ -1594,7 +1716,7 @@ export class MemoryTableManager {
1594
1716
  const result = await this.checkSingleUniqueConstraint(layer, schema, uc, change.newRow, newPrimaryKey, ConflictResolution.ABORT, noEvict, /*allowMvCovering*/ false);
1595
1717
  if (result) {
1596
1718
  const colNames = uc.columns.map(i => schema.columns[i]?.name ?? String(i));
1597
- throw maintainedTableUniqueViolationError(this.schemaName, this._tableName, uc.name ?? `_uc_${colNames.join('_')}`, colNames, uc.columns.map(i => change.newRow[i]));
1719
+ throw maintainedTableUniqueViolationError(this.schemaName, this._tableName, uc.name ?? implicitIndexNameOver(uc, schema.columns), colNames, uc.columns.map(i => change.newRow[i]));
1598
1720
  }
1599
1721
  }
1600
1722
  }
@@ -1628,13 +1750,14 @@ export class MemoryTableManager {
1628
1750
  throw new QuereusError(`Duplicate column name: ${newColumnSchema.name}`, StatusCode.ERROR);
1629
1751
  }
1630
1752
  let defaultValue = null;
1631
- let defaultIsLiteral = false;
1632
1753
  const defaultConstraint = columnDefAst.constraints.find(c => c.type === 'default');
1633
1754
  if (defaultConstraint && defaultConstraint.expr) {
1634
- const folded = tryFoldLiteral(defaultConstraint.expr);
1755
+ // Fold AND convert to the new column's declared type, so the backfilled cell
1756
+ // is the value a fresh INSERT under the same DEFAULT would store. An
1757
+ // unconvertible literal (`integer default 'abc'`) throws MISMATCH here.
1758
+ const folded = foldDefaultToType(defaultConstraint.expr, newColumnSchema.logicalType, newColumnSchema.name);
1635
1759
  if (folded !== undefined) {
1636
1760
  defaultValue = folded;
1637
- defaultIsLiteral = true;
1638
1761
  }
1639
1762
  else {
1640
1763
  // A non-literal expression default (e.g. `new.<col>`) is written as NULL
@@ -1643,13 +1766,27 @@ export class MemoryTableManager {
1643
1766
  }
1644
1767
  }
1645
1768
  // Check for NOT NULL constraint (could be explicit or from default behavior).
1646
- // Allow NOT NULL without DEFAULT if the table is empty (SQLite-compatible).
1647
- // A non-literal *expression* default (e.g. `new.<col>`) is backfilled per-row by
1648
- // the engine right after this returns, which then enforces NOT NULL on the
1649
- // backfilled values — so don't reject it here as "without a DEFAULT".
1769
+ // Allow NOT NULL without a value source if the table is empty (SQLite-compatible).
1770
+ //
1771
+ // `!backfillEvaluator` is the load-bearing clause, and mirrors
1772
+ // `StoreModuleBase.alterAddColumn`: an engine-supplied evaluator — a non-foldable
1773
+ // expression default (`new.<col>`) OR a `generated always as` expression — fills
1774
+ // each existing row right after this returns, and NOT NULL is then enforced on the
1775
+ // computed values (`BaseLayer.recreatePrimaryTreeWithNewColumn`, plus the
1776
+ // pending-row pass below). Asking only which KIND of DEFAULT was written used to
1777
+ // reject a NOT NULL generated column on a non-empty table the evaluator could fill,
1778
+ // and (in the other direction) to admit `default null` on a column that is NOT NULL
1779
+ // only by the session `default_column_nullability`. The gate is value-based now, so
1780
+ // it matches `StoreModuleBase.alterAddColumn` word for word.
1781
+ //
1782
+ // NOTE: that agreement — here, in the store module, and in the engine's own
1783
+ // pre-check in `runAddColumn` — is held together only by section 8 of
1784
+ // `test/logic/41.4-alter-add-column-constraints.sqllogic` running under both
1785
+ // `yarn test` and `yarn test:store`. Nothing enforces it mechanically. If a
1786
+ // fourth site ever needs the same rule, hoist it to one shared predicate
1787
+ // rather than adding another copy.
1650
1788
  const tableHasRows = this.baseLayer.primaryTree.at(this.baseLayer.primaryTree.first()) !== undefined;
1651
- const hasDefaultExpr = !!(defaultConstraint && defaultConstraint.expr);
1652
- if (newColumnSchema.notNull && defaultValue === null && !defaultIsLiteral && !hasDefaultExpr && tableHasRows) {
1789
+ if (newColumnSchema.notNull && defaultValue === null && !backfillEvaluator && tableHasRows) {
1653
1790
  throw new QuereusError(`Cannot add NOT NULL column '${newColumnSchema.name}' to non-empty table `
1654
1791
  + `'${this.schemaName}.${this._tableName}' without a DEFAULT value`, StatusCode.CONSTRAINT);
1655
1792
  }
@@ -1709,9 +1846,10 @@ export class MemoryTableManager {
1709
1846
  return insertValueAt(row, targetIndex, value);
1710
1847
  });
1711
1848
  this.baseLayer.updateSchema(finalNewTableSchema);
1712
- // A non-foldable DEFAULT (e.g. `new.<col>`) backfills each existing row from
1713
- // its own value via the engine-supplied evaluator; a literal/NULL default
1714
- // uses the single folded `defaultValue` for every row.
1849
+ // A per-row value source — a non-foldable DEFAULT (e.g. `new.<col>`) or a
1850
+ // `generated always as` expression — backfills each existing row from its own
1851
+ // value via the engine-supplied evaluator; a literal/NULL default uses the
1852
+ // single folded `defaultValue` for every row.
1715
1853
  await this.baseLayer.addColumnToBase(newColumnSchema, defaultValue, backfillEvaluator, targetIndex);
1716
1854
  this.tableSchema = finalNewTableSchema;
1717
1855
  this.initializePrimaryKeyFunctions();
@@ -1758,57 +1896,23 @@ export class MemoryTableManager {
1758
1896
  if (this.tableSchema.primaryKeyDefinition.some(def => def.index === colIndex)) {
1759
1897
  throw new QuereusError(`Cannot drop PK column "${columnName}".`, StatusCode.CONSTRAINT);
1760
1898
  }
1761
- const updatedColumnsSchema = this.tableSchema.columns.filter((_, idx) => idx !== colIndex);
1762
- const updatedPkDefinition = this.tableSchema.primaryKeyDefinition.map(def => ({
1763
- ...def, index: def.index > colIndex ? def.index - 1 : def.index
1764
- }));
1765
- const updatedPrimaryKeyNames = updatedPkDefinition.map(def => updatedColumnsSchema[def.index]?.name).filter(Boolean);
1766
- // Prune any UNIQUE constraint over the dropped column. A UNIQUE that includes the
1767
- // dropped column is removed outright (a UNIQUE missing one of its columns is a
1768
- // different, stronger constraint, not a silently-narrowed one); the auto-built
1769
- // covering index it backed is torn down with it (see the index exclusion below).
1770
- // Remaining constraints have their column indices shifted to track the removed slot.
1771
- // Without this, dropping a uniquely-constrained column (including the ADD COLUMN +
1772
- // inline-UNIQUE revert path) would strand a constraint whose column index dangles
1773
- // past the end of the column array.
1774
- const oldUniqueConstraints = this.tableSchema.uniqueConstraints ?? [];
1775
- const droppedUcKeys = oldUniqueConstraints
1776
- .filter(uc => uc.columns.includes(colIndex))
1777
- .map(uc => uc.name ?? this.implicitIndexNameFor(uc));
1778
- const remainingUniqueConstraints = oldUniqueConstraints
1779
- .filter(uc => !uc.columns.includes(colIndex))
1780
- .map(uc => ({ ...uc, columns: Object.freeze(uc.columns.map(i => i > colIndex ? i - 1 : i)) }));
1899
+ // Renumber every position-bearing field over the removed slot — shared with the
1900
+ // store module's `alterDropColumn`, the mirror of `shiftSchemaIndicesForInsert`.
1901
+ const shifted = shiftSchemaIndicesForDrop(this.tableSchema, colIndex);
1902
+ const updatedPrimaryKeyNames = shifted.primaryKeyDefinition.map(def => shifted.columns[def.index]?.name).filter(Boolean);
1781
1903
  // Drop the implicit covering index of each removed constraint outright (matched by
1782
1904
  // the same `uc.name ?? '_uc_<cols>'` convention DROP CONSTRAINT uses, so a user
1783
- // index that merely shares columns is left untouched), then shift/prune the rest
1784
- // over the removed slot. A *single*-column covering index collapses to empty and is
1785
- // filtered by the trailing `length > 0` regardless; the explicit name exclusion is
1786
- // what tears down a *multi*-column covering index, which would otherwise survive
1787
- // orphaned — narrowed to its surviving columns — in `index_info` and on every write.
1905
+ // index that merely shares columns is left untouched). Only this backend needs the
1906
+ // pass: the covering index deliberately carries no `unique: true` flag (enforcement
1907
+ // routes through `uniqueConstraints`), so `shiftSchemaIndicesForDrop`'s own
1908
+ // drop-unique-indexes-outright rule does not see it. A *single*-column covering index
1909
+ // collapses to empty and is dropped by the helper's `length > 0` filter regardless;
1910
+ // this exclusion is what tears down a *multi*-column one, which would otherwise
1911
+ // survive orphaned — narrowed to its surviving columns — in `index_info` and on every
1912
+ // write.
1913
+ const droppedUcKeys = shifted.removedUniqueConstraints.map(uc => uc.name ?? this.implicitIndexNameFor(uc));
1788
1914
  const droppedCoveringIndexNames = new Set(droppedUcKeys.map(k => k.toLowerCase()));
1789
- const updatedIndexes = (this.tableSchema.indexes || [])
1790
- .filter(idx => !droppedCoveringIndexNames.has(idx.name.toLowerCase()))
1791
- .map(idx => ({
1792
- ...idx,
1793
- columns: idx.columns
1794
- .filter(ic => ic.index !== colIndex)
1795
- .map(ic => ({ ...ic, index: ic.index > colIndex ? ic.index - 1 : ic.index }))
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)) }));
1915
+ const updatedIndexes = shifted.indexes.filter(idx => !droppedCoveringIndexNames.has(idx.name.toLowerCase()));
1812
1916
  // NOTE: the generated-column bookkeeping (`generatedColumnDependencies` /
1813
1917
  // `generatedColumnTopoOrder`, also index-keyed) is likewise unshifted, but the
1814
1918
  // engine recomputes it from column names right after this returns
@@ -1818,17 +1922,13 @@ export class MemoryTableManager {
1818
1922
  // `primaryKey` below is not a TableSchema field at all — nothing reads it.
1819
1923
  const finalNewTableSchema = Object.freeze({
1820
1924
  ...this.tableSchema,
1821
- columns: Object.freeze(updatedColumnsSchema),
1822
- columnIndexMap: buildColumnIndexMap(updatedColumnsSchema),
1823
- primaryKeyDefinition: Object.freeze(updatedPkDefinition),
1925
+ columns: shifted.columns,
1926
+ columnIndexMap: buildColumnIndexMap(shifted.columns),
1927
+ primaryKeyDefinition: shifted.primaryKeyDefinition,
1824
1928
  primaryKey: Object.freeze(updatedPrimaryKeyNames),
1825
1929
  indexes: Object.freeze(updatedIndexes),
1826
- uniqueConstraints: remainingUniqueConstraints.length > 0
1827
- ? Object.freeze(remainingUniqueConstraints)
1828
- : undefined,
1829
- foreignKeys: remainingForeignKeys.length > 0
1830
- ? Object.freeze(remainingForeignKeys)
1831
- : undefined,
1930
+ uniqueConstraints: shifted.uniqueConstraints,
1931
+ foreignKeys: shifted.foreignKeys,
1832
1932
  });
1833
1933
  // Phase 1 of reaching the open transaction's own pending rows. The rewrite here is
1834
1934
  // a pure filter and cannot throw — the split exists for ADD COLUMN's fallible
@@ -1891,8 +1991,17 @@ export class MemoryTableManager {
1891
1991
  const defaultNotNull = defaultNullability === 'not_null';
1892
1992
  const newColumnSchemaAtIndex = columnDefToSchema(newColumnDefAst, defaultNotNull, 'BINARY', (n) => this.db.isCollationRegistered(n));
1893
1993
  const updatedCols = this.tableSchema.columns.map((c, i) => i === colIndex ? newColumnSchemaAtIndex : c);
1994
+ // An unnamed UNIQUE's covering index is named `_uc_<column names>`, so renaming a
1995
+ // covered column moves that name — rewrite the materialized ENTRY, not just its
1996
+ // column references, or the structure is orphaned under the old name (see
1997
+ // {@link planImplicitCoveringIndexRenames}). The engine refuses the statement up
1998
+ // front when the post-rename name is already an index here, so no rename below can
1999
+ // collide with a sibling entry.
2000
+ const coveringRenames = this.planImplicitCoveringIndexRenames(updatedCols, colIndex);
2001
+ const renamedTo = new Map(coveringRenames.map(r => [r.oldName.toLowerCase(), r.newName]));
1894
2002
  const updatedIndexes = (this.tableSchema.indexes || []).map(idx => ({
1895
2003
  ...idx,
2004
+ name: renamedTo.get(idx.name.toLowerCase()) ?? idx.name,
1896
2005
  columns: idx.columns.map(ic => ic.index === colIndex ? { ...ic, name: newColumnName } : ic)
1897
2006
  }));
1898
2007
  const finalNewTableSchema = Object.freeze({
@@ -1925,8 +2034,13 @@ export class MemoryTableManager {
1925
2034
  predicatesRewritten = true;
1926
2035
  renameColumnInIndexPredicates(finalNewTableSchema.indexes, this._tableName, oldName, newColumnName, this.schemaName, resolveColumnInSource);
1927
2036
  this.baseLayer.updateSchema(finalNewTableSchema);
2037
+ // Rebuilds every secondary index from the new schema, so a renamed covering index
2038
+ // lands under its new key in the base's index map with no extra rebuild here.
1928
2039
  await this.baseLayer.handleColumnRename();
1929
2040
  this.tableSchema = finalNewTableSchema;
2041
+ // Only now that the swap has succeeded — the catch below restores the old schema
2042
+ // and the map must keep agreeing with it.
2043
+ this.applyImplicitCoveringStructureRenames(coveringRenames);
1930
2044
  this.initializePrimaryKeyFunctions();
1931
2045
  // The DDL transaction's own layers froze their schema at creation; hand them the
1932
2046
  // renamed one, mirroring `handleColumnRename`'s base-side secondary rebuild —
@@ -1971,18 +2085,31 @@ export class MemoryTableManager {
1971
2085
  }
1972
2086
  }
1973
2087
  /**
1974
- * Apply a single-attribute ALTER COLUMN change (NOT NULL, DEFAULT, DATA TYPE).
2088
+ * Apply a single-attribute ALTER COLUMN change (NOT NULL, DEFAULT, DATA TYPE, COLLATE).
1975
2089
  * The caller supplies exactly one populated change; multi-attribute combinations
1976
2090
  * are rejected by the runtime before reaching this method.
2091
+ *
2092
+ * The body is the ordering contract, and only that: resolve the column → decide the change
2093
+ * and pre-validate it (`alter-column.ts`, which mutates nothing) → probe the re-keyed
2094
+ * structures ({@link validateAlterColumnPlan}) → rebuild the base
2095
+ * ({@link applyAlterColumnToBase}, the first write) → swap this manager's schema →
2096
+ * propagate to open layers ({@link propagateAlterColumnToOpenLayers}) → emit. Each step's
2097
+ * own reasoning lives on the step.
2098
+ *
2099
+ * `validateOnly` runs everything up to (and including) the pre-mutation validation
2100
+ * passes — the effective-row NULL / conversion scans, the re-keyed UNIQUE probe, and
2101
+ * the primary-key re-key legality/representability passes — and returns before the
2102
+ * first mutation, throwing exactly what the real application would throw. The
2103
+ * isolation layer uses it to pre-flight an overlay's migration before the shared
2104
+ * underlying table mutates irreversibly (see `VirtualTable.alterSchema`).
1977
2105
  */
1978
- async alterColumn(change, rows) {
2106
+ async alterColumn(change, rows, validateOnly = false) {
1979
2107
  if (this.isReadOnly)
1980
2108
  throw new QuereusError(`Table '${this._tableName}' is read-only`, StatusCode.READONLY);
1981
2109
  const lockKey = `MemoryTable.SchemaChange:${this.schemaName}.${this._tableName}`;
1982
2110
  const release = await this.db.latches.acquire(lockKey);
1983
2111
  const originalManagerSchema = this.tableSchema;
1984
- /** The base primary tree `rebuildPrimaryTreeStrict` replaced, for the catch's rollback. */
1985
- let basePrimaryTreeBeforeRekey = null;
2112
+ const undo = { basePrimaryTree: null };
1986
2113
  try {
1987
2114
  await this.ensureSchemaChangeSafety();
1988
2115
  const colNameLower = change.columnName.toLowerCase();
@@ -1990,355 +2117,32 @@ export class MemoryTableManager {
1990
2117
  if (colIndex === -1) {
1991
2118
  throw new QuereusError(`Column '${change.columnName}' not found.`, StatusCode.ERROR);
1992
2119
  }
1993
- const oldCol = this.tableSchema.columns[colIndex];
1994
- let newCol = oldCol;
1995
- // A collation change re-keys any PK / UNIQUE / index that orders by this
1996
- // column, so it needs the structure re-sort + uniqueness re-validation below.
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;
2007
- // Set to the per-value conversion function when a physical value rewrite is needed:
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).
2015
- let valueConvert = null;
2016
- // SET NOT NULL backfill also routes NULL values through `valueConvert`; SET DATA TYPE
2017
- // leaves nulls untouched. Selects the null-including path in convertBaseRows/convertColumn.
2018
- let convertNulls = false;
2019
- if (change.setCollation !== undefined) {
2020
- const normalized = validateCollationForType(change.setCollation, oldCol.logicalType, change.columnName, (n) => this.db.isCollationRegistered(n));
2021
- const nameMatches = normalized === (oldCol.collation || 'BINARY');
2022
- if (nameMatches && oldCol.collationExplicit) {
2023
- return; // already explicit in the desired collation — nothing to do
2024
- }
2025
- // SET COLLATE is a user declaration with the same standing as a
2026
- // CREATE-time COLLATE clause, so mark the collation explicit (rank 2 in
2027
- // the comparison lattice) regardless of the column's creation history —
2028
- // including SET COLLATE binary. When only the name matches but the column
2029
- // was not yet explicit (a defaulted collation, or one inherited from
2030
- // session default_collation), flip the flag as a METADATA-ONLY change:
2031
- // the collation bytes are unchanged, so keep collationChanged false and
2032
- // skip the physical re-sort / re-key / UNIQUE re-validation below. A
2033
- // different name takes the full path AND sets the flag.
2034
- newCol = { ...oldCol, collation: normalized, collationExplicit: true };
2035
- collationChanged = !nameMatches;
2036
- }
2037
- else if (change.setNotNull !== undefined) {
2038
- if (change.setNotNull === true && !oldCol.notNull) {
2039
- // Tightening: scan the DDL transaction's EFFECTIVE rows for NULLs — committed rows
2040
- // overlaid with this transaction's own pending writes — exactly as the setDataType
2041
- // arm does. A pending NULL the transaction can SEE rejects the ALTER; one only in a
2042
- // row it has DELETED does not block it. `rows` is the wrapper-supplied effective set
2043
- // (the isolation overlay); when absent, effectiveDdlRows() is the layered view.
2044
- const defaultExpr = oldCol.defaultValue;
2045
- const defaultLiteral = defaultExpr ? tryFoldLiteral(defaultExpr) : undefined;
2046
- let anyNull = false;
2047
- if (rows) {
2048
- for await (const row of rows()) {
2049
- if (row[colIndex] === null) {
2050
- anyNull = true;
2051
- break;
2052
- }
2053
- }
2054
- }
2055
- else {
2056
- for (const row of this.effectiveDdlRows()) {
2057
- if (row[colIndex] === null) {
2058
- anyNull = true;
2059
- break;
2060
- }
2061
- }
2062
- }
2063
- if (anyNull) {
2064
- if (defaultLiteral === undefined || defaultLiteral === null) {
2065
- throw new QuereusError(`column ${change.columnName} contains NULL values`, StatusCode.CONSTRAINT);
2066
- }
2067
- // Backfill: map null → the folded DEFAULT literal, routed through the SAME
2068
- // base-replacement + open-layer conversion machinery setDataType uses — no
2069
- // logical-type change, no PK re-key, no in-place base mutation. The old in-place
2070
- // `tree.upsert` backfill could not fill the transaction's pending rows (they live
2071
- // in the pending layer, not the base) and mutated a base the open layers derive
2072
- // from — both fixed by the unified path.
2073
- const literal = defaultLiteral;
2074
- valueConvert = (v) => (v === null ? literal : v);
2075
- convertNulls = true;
2076
- }
2077
- newCol = { ...oldCol, notNull: true };
2078
- }
2079
- else if (change.setNotNull === false && oldCol.notNull) {
2080
- if (this.tableSchema.primaryKeyDefinition.some(def => def.index === colIndex)) {
2081
- throw new QuereusError(`Cannot DROP NOT NULL on PRIMARY KEY column '${change.columnName}'`, StatusCode.CONSTRAINT);
2082
- }
2083
- newCol = { ...oldCol, notNull: false };
2084
- }
2085
- else {
2086
- // No-op (already in desired state).
2087
- return;
2088
- }
2089
- }
2090
- else if (change.setDataType !== undefined) {
2091
- const newLogicalType = inferType(change.setDataType);
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`).
2116
- if (this.tableSchema.primaryKeyDefinition.some(def => def.index === colIndex)) {
2117
- throw new QuereusError(`Cannot change the data type of primary key column '${change.columnName}' of table '${this._tableName}'.`, StatusCode.CONSTRAINT);
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);
2126
- const convert = (v) => {
2127
- try {
2128
- return validateAndParse(v, newLogicalType, change.columnName);
2129
- }
2130
- catch {
2131
- throw new QuereusError(`Cannot convert value in '${change.columnName}' to ${change.setDataType}`, StatusCode.MISMATCH);
2132
- }
2133
- };
2134
- // Validate BEFORE any mutation, over the DDL transaction's EFFECTIVE rows
2135
- // (committed rows overlaid with this transaction's own pending writes): an
2136
- // unconvertible value the transaction can SEE rejects the ALTER, and one only in
2137
- // a row it has DELETED does not block it. A throw here leaves the schema, the base
2138
- // and the transaction untouched. `rows` is the wrapper-supplied effective set (the
2139
- // isolation overlay); when absent, `effectiveDdlRows()` is the layered view.
2140
- if (rows) {
2141
- for await (const row of rows()) {
2142
- const val = row[colIndex];
2143
- if (val !== null)
2144
- convert(val);
2145
- }
2146
- }
2147
- else {
2148
- for (const row of this.effectiveDdlRows()) {
2149
- const val = row[colIndex];
2150
- if (val !== null)
2151
- convert(val);
2152
- }
2153
- }
2154
- // The base rows are converted below, AFTER `updateSchema`, by REPLACING the base
2155
- // primary tree with a fresh one (see the `valueConvert` branch) — never by an
2156
- // in-place mutation, which inheritree forbids while the open transaction's layers
2157
- // derive from that tree (`MutatedBaseError`). `valueConvert` drives both the base
2158
- // replacement and the transaction-layer conversion.
2159
- valueConvert = convert;
2160
- }
2161
- newCol = { ...oldCol, logicalType: newLogicalType };
2162
- }
2163
- else if (change.setDefault !== undefined) {
2164
- newCol = { ...oldCol, defaultValue: change.setDefault };
2165
- }
2166
- else {
2167
- throw new QuereusError('ALTER COLUMN requires an attribute to change', StatusCode.INTERNAL);
2168
- }
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;
2176
- // Propagate a collation change into every PK-definition entry and index
2177
- // column that orders by this column, so their comparators re-key under it.
2178
- const updatedPkDef = collationChanged
2179
- ? this.tableSchema.primaryKeyDefinition.map(def => def.index === colIndex ? { ...def, collation: newCol.collation } : def)
2180
- : this.tableSchema.primaryKeyDefinition;
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)
2191
- ? this.tableSchema.indexes.map(idx => ({
2192
- ...idx,
2193
- columns: idx.columns.map(ic => (ic.index === colIndex && collationChanged) ? { ...ic, collation: newCol.collation } : ic),
2194
- }))
2195
- : this.tableSchema.indexes;
2196
- const finalNewTableSchema = Object.freeze({
2197
- ...this.tableSchema,
2198
- columns: Object.freeze(updatedCols),
2199
- columnIndexMap: buildColumnIndexMap(updatedCols),
2200
- primaryKeyDefinition: Object.freeze(updatedPkDef),
2201
- indexes: updatedIndexes ? Object.freeze(updatedIndexes) : updatedIndexes,
2202
- });
2203
- const pkColumnRekeyed = collationChanged && updatedPkDef.some(def => def.index === colIndex);
2204
- // Validate BEFORE any mutation, over the DDL transaction's EFFECTIVE rows: a pair
2205
- // the transaction inserted that collides under the NEW schema must reject the
2206
- // change, and one it has deleted must not block it. A throw here leaves the schema,
2207
- // the base layer and the index map exactly as they were.
2208
- //
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) {
2248
- await this.validateRekeyedUniqueStructures(finalNewTableSchema, colIndex, rows);
2249
- // NOTE: `validateRekeyedPrimaryKey` deliberately ignores `rows`. It asserts that no
2250
- // LAYER of this manager's own chain holds a PK collision under the new comparator —
2251
- // a physical property of the structures it is about to re-key, which a wrapper's
2252
- // merged row stream cannot speak to. A wrapper's staged rows live in the wrapper's
2253
- // own overlay, which enforces the PK itself, so the pair "one staged row + one
2254
- // committed row that collide only under the new collation" is checked by neither
2255
- // side. Rejecting it here would need the wrapper to expose its overlay's PK set, not
2256
- // just its merged rows.
2257
- if (pkColumnRekeyed)
2258
- this.validateRekeyedPrimaryKey(finalNewTableSchema);
2259
- }
2260
- this.baseLayer.updateSchema(finalNewTableSchema);
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) {
2270
- // SET DATA TYPE / SET NOT NULL backfill: convert the committed base rows and REPLACE
2271
- // the primary tree with a fresh one holding them (not an in-place upsert — inheritree
2272
- // forbids mutating a base while the open transaction's layers derive from it).
2273
- // `rebuildPrimaryTreeFromRows` also rebuilds every secondary index from the converted
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.
2278
- // NOTE: rebuilds EVERY secondary index, not only those covering the altered column;
2279
- // if a wide-index table ever shows this as slow, filter to indexes whose columns
2280
- // include colIndex. Mirrors the collationChanged path's unconditional rebuild.
2281
- //
2282
- // SET DATA TYPE: a base value that fails to convert is kept as-is — validation over
2283
- // the effective view already rejected any unconvertible value the transaction can SEE,
2284
- // so a surviving one is shadowed by a pending delete/overwrite and is never read back
2285
- // (a ROLLBACK re-exposes it under the new type — DDL is not undone by rollback, the
2286
- // known behavior of `feat-ddl-transaction-capability`).
2287
- // SET NOT NULL (`convertNulls`): null base values are mapped to the DEFAULT literal.
2288
- const convertedBaseRows = this.convertBaseRows(colIndex, valueConvert, convertNulls);
2289
- basePrimaryTreeBeforeRekey = this.baseLayer.primaryTree;
2290
- this.baseLayer.rebuildPrimaryTreeFromRows(convertedBaseRows);
2291
- }
2292
- else if (structuresRekeyed) {
2293
- this.baseLayer.rebuildAllSecondaryIndexes();
2294
- if (pkColumnRekeyed) {
2295
- basePrimaryTreeBeforeRekey = this.baseLayer.primaryTree;
2296
- this.baseLayer.rebuildPrimaryTreeStrict();
2297
- }
2298
- }
2299
- this.tableSchema = finalNewTableSchema;
2120
+ // Decide + validate, in that order and both before any mutation: `planColumnAttributeChange`
2121
+ // runs the attribute's own pre-validation (the NULL scan, the convertibility scan, the
2122
+ // primary-key carve-outs) over the effective rows, and `validateAlterColumnPlan` runs the
2123
+ // re-keyed UNIQUE / primary-key probes over the schema it produced. Nothing above
2124
+ // `applyAlterColumnToBase` writes, so a throw from either leaves the table as it was.
2125
+ const attributeChange = await planColumnAttributeChange({
2126
+ schema: this.tableSchema,
2127
+ tableName: this._tableName,
2128
+ columnName: change.columnName,
2129
+ colIndex,
2130
+ effectiveRows: this.effectiveRowSource(rows),
2131
+ isCollationRegistered: (n) => this.db.isCollationRegistered(n),
2132
+ }, change);
2133
+ if (attributeChange === null)
2134
+ return; // already in the requested state — nothing to do
2135
+ const plan = buildAlterColumnPlan(this.tableSchema, colIndex, attributeChange);
2136
+ await this.validateAlterColumnPlan(plan, rows);
2137
+ // Dry run ends here: everything above validates without mutating (the one earlier
2138
+ // side effect, ensureSchemaChangeSafety's committed-layer drain, is semantically
2139
+ // neutral bookkeeping), and everything below mutates. See the method doc.
2140
+ if (validateOnly)
2141
+ return;
2142
+ this.applyAlterColumnToBase(plan, undo);
2143
+ this.tableSchema = plan.newSchema;
2300
2144
  this.initializePrimaryKeyFunctions();
2301
- // The base rebuild handed every secondary index a fresh tree under the new
2302
- // comparator; the DDL transaction's own layers still inherit the old ones and froze
2303
- // the old schema at construction. Re-key them, or the rest of the transaction — and
2304
- // everything after the pending layer becomes the committed head at commit — keeps
2305
- // comparing under the old collation / old logical type.
2306
- //
2307
- // When the altered column is part of the primary key, `rebuildPrimaryTreeStrict` also
2308
- // swapped the base primary tree object out from under those layers' copy-on-write
2309
- // bases and invalidated their `pkFunctions`; `rekeyPrimaryKey` rebuilds both, plus
2310
- // every secondary index (each derives its PK comparator/encoder from the PK
2311
- // definition). Outside a transaction there are no open layers and both are no-ops.
2312
- if (valueConvert) {
2313
- // SET DATA TYPE / SET NOT NULL backfill converted the base above; the DDL
2314
- // transaction's own open layers still froze the old schema and hold their own-written
2315
- // rows unconverted. Swap the schema and convert those values so the rest of the
2316
- // transaction — and the committed head at commit — read the new value. For SET NOT
2317
- // NULL this fills the transaction's OWN pending NULL rows (which live in the pending
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.
2322
- this.convertColumnOnOpenLayers(finalNewTableSchema, colIndex, valueConvert, convertNulls);
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
- }
2145
+ this.propagateAlterColumnToOpenLayers(plan);
2342
2146
  this.eventEmitter?.emitSchemaChange?.({
2343
2147
  type: 'alter',
2344
2148
  objectType: 'column',
@@ -2350,20 +2154,20 @@ export class MemoryTableManager {
2350
2154
  }
2351
2155
  catch (e) {
2352
2156
  // Restore the prior schema and primary tree, then re-key the secondary indexes back
2353
- // to it. Both pre-passes now run before any mutation, so nothing below `updateSchema`
2354
- // is expected to throw; the restores are the safety net for an unexpected one (and
2355
- // for `rebuildPrimaryTreeStrict`'s invariant check, whose precondition
2356
- // `validateRekeyedPrimaryKey` has already established).
2157
+ // to it. Both pre-passes run before any mutation, so nothing inside
2158
+ // `applyAlterColumnToBase` is expected to throw; the restores are the safety net for
2159
+ // an unexpected one (and for `rebuildPrimaryTreeStrict`'s invariant check, whose
2160
+ // precondition `validateRekeyedPrimaryKey` has already established).
2357
2161
  //
2358
- // NOTE: a throw from a pre-pass (or from `setNotNull`'s NULL scan) mutated nothing,
2162
+ // NOTE: a throw from a pre-pass (or from `set not null`'s NULL scan) mutated nothing,
2359
2163
  // so this rebuild only swaps the base's index trees for fresh, content-identical
2360
2164
  // ones — an O(rows) cost on a pure rejection. Harmless (a pending layer keeps
2361
2165
  // reading its orphaned but content-correct copy-on-write base), but if a rejected
2362
2166
  // ALTER on a large table ever shows up as slow, gate the rebuild on a "mutation
2363
- // started" flag set just before `updateSchema`.
2167
+ // started" flag set just before `applyAlterColumnToBase`.
2364
2168
  this.baseLayer.updateSchema(originalManagerSchema);
2365
- if (basePrimaryTreeBeforeRekey)
2366
- this.baseLayer.primaryTree = basePrimaryTreeBeforeRekey;
2169
+ if (undo.basePrimaryTree)
2170
+ this.baseLayer.primaryTree = undo.basePrimaryTree;
2367
2171
  this.baseLayer.rebuildAllSecondaryIndexes();
2368
2172
  this.tableSchema = originalManagerSchema;
2369
2173
  this.initializePrimaryKeyFunctions();
@@ -2374,6 +2178,278 @@ export class MemoryTableManager {
2374
2178
  release();
2375
2179
  }
2376
2180
  }
2181
+ /**
2182
+ * Apply `alter table … alter primary key` in place — the table's rows, the open
2183
+ * transaction's pending writes, and its pending change events all survive under the new
2184
+ * key. Follows {@link alterColumn}'s ordering contract: resolve and pre-validate the new
2185
+ * definition (mutating nothing) → probe the re-keyed primary structures
2186
+ * ({@link validateRekeyedPrimaryKey}, the same pass the `set collate` re-key runs) →
2187
+ * rebuild the base → swap this manager's schema → propagate to the open transaction's
2188
+ * layers → emit. What differs from the `set collate` re-key is WHAT moves: the key's
2189
+ * columns rather than its comparator, so each open layer's net deletions and its pending
2190
+ * event log must be re-derived from row images — a two-phase prepare/install pair
2191
+ * ({@link TransactionLayer.prepareRekeyedPrimaryKeyColumns}), with every prepare running
2192
+ * BEFORE the first mutation because a deletion's image resolves only through the intact
2193
+ * old layer chain.
2194
+ *
2195
+ * `validateOnly` runs everything up to (and including) the pre-mutation validation and
2196
+ * returns before the first mutation, throwing exactly what the real application would
2197
+ * throw — the same dry-run contract as {@link alterColumn}'s, for a wrapper (the
2198
+ * isolation layer) that must pre-flight before a shared underlying mutates irreversibly.
2199
+ */
2200
+ async alterPrimaryKey(newPkColumns, rows, validateOnly = false) {
2201
+ if (this.isReadOnly)
2202
+ throw new QuereusError(`Table '${this._tableName}' is read-only`, StatusCode.READONLY);
2203
+ const lockKey = `MemoryTable.SchemaChange:${this.schemaName}.${this._tableName}`;
2204
+ const release = await this.db.latches.acquire(lockKey);
2205
+ const originalManagerSchema = this.tableSchema;
2206
+ const undo = { basePrimaryTree: null };
2207
+ try {
2208
+ await this.ensureSchemaChangeSafety();
2209
+ // NOTE: no early return for a definition equal to the current one (unlike
2210
+ // `alterColumn`'s already-in-the-requested-state exit) — the statement is rare and
2211
+ // its only producer, the declarative differ, emits it solely on a genuine change.
2212
+ // If a caller ever re-issues an unchanged key, add the comparison here: the work
2213
+ // below is a full O(rows × layers) rebuild.
2214
+ const newSchema = this.buildRekeyedPrimaryKeySchema(newPkColumns);
2215
+ // Pre-validate before any mutation: the effective rows decide whether the change
2216
+ // is LEGAL (CONSTRAINT on a visible collision), the manager's own layer chain
2217
+ // whether the re-keyed trees can PHYSICALLY carry it (BUSY when only rows a
2218
+ // rollback must restore collide). A throw leaves everything as it was.
2219
+ await this.validateRekeyedPrimaryKey(newSchema, rows);
2220
+ // Dry run ends here: everything above validates without mutating, everything
2221
+ // below mutates. See the method doc.
2222
+ if (validateOnly)
2223
+ return;
2224
+ // Compute every open layer's re-derived net writes and event log BEFORE the
2225
+ // first mutation: a net deletion's new key comes from the parent's row image at
2226
+ // the old key, which only the intact old chain can still resolve.
2227
+ const newPkFunctions = createPrimaryKeyFunctions(newSchema, this.collationResolver);
2228
+ const plans = this.openTransactionLayersOldestFirst().map(layer => ({
2229
+ layer,
2230
+ prepared: layer.prepareRekeyedPrimaryKeyColumns(newPkFunctions),
2231
+ }));
2232
+ // Base rebuild, mirroring alterColumn's `set collate` arm: secondary indexes
2233
+ // first (each entry's key encoding embeds the primary key), then the strict
2234
+ // primary rebuild LAST so its throw — a live invariant check; the pre-pass has
2235
+ // already proven the base collision-free — leaves the live tree intact for the
2236
+ // rollback below.
2237
+ this.baseLayer.updateSchema(newSchema);
2238
+ this.baseLayer.rebuildAllSecondaryIndexes();
2239
+ undo.basePrimaryTree = this.baseLayer.primaryTree;
2240
+ this.baseLayer.rebuildPrimaryTreeStrict();
2241
+ this.tableSchema = newSchema;
2242
+ this.initializePrimaryKeyFunctions();
2243
+ // Oldest-first (the plans were gathered in that order): each layer's rebuilt tree
2244
+ // inherits copy-on-write from its parent's NEW one. Synchronous and mutation-only.
2245
+ for (const { layer, prepared } of plans) {
2246
+ layer.installRekeyedPrimaryKeyColumns(newSchema, prepared);
2247
+ }
2248
+ this.eventEmitter?.emitSchemaChange?.({
2249
+ type: 'alter',
2250
+ objectType: 'table',
2251
+ schemaName: this.schemaName,
2252
+ objectName: this._tableName,
2253
+ });
2254
+ logger.operation('Alter Primary Key', this._tableName, {
2255
+ columns: newSchema.primaryKeyDefinition.map(def => def.index),
2256
+ });
2257
+ }
2258
+ catch (e) {
2259
+ // Restore the prior schema and primary tree, then re-key the secondary indexes
2260
+ // back to it — the same safety net as alterColumn's catch: both pre-passes run
2261
+ // before any mutation, so nothing in the apply steps is expected to throw.
2262
+ this.baseLayer.updateSchema(originalManagerSchema);
2263
+ if (undo.basePrimaryTree)
2264
+ this.baseLayer.primaryTree = undo.basePrimaryTree;
2265
+ this.baseLayer.rebuildAllSecondaryIndexes();
2266
+ this.tableSchema = originalManagerSchema;
2267
+ this.initializePrimaryKeyFunctions();
2268
+ logger.error('Alter Primary Key', this._tableName, e);
2269
+ throw e;
2270
+ }
2271
+ finally {
2272
+ release();
2273
+ }
2274
+ }
2275
+ /**
2276
+ * Resolve `alter primary key`'s column list against the current schema — bounds, no
2277
+ * duplicates, every member NOT NULL — and return the re-keyed schema. The engine's
2278
+ * emitter validates the same three by column NAME before dispatch; re-checking by index
2279
+ * here keeps a wrapper driving the module API directly (the isolation layer) from
2280
+ * installing an unkeyable definition. The schema construction itself (definition +
2281
+ * per-column flag rebuild, member collations carried) is delegated to the shared
2282
+ * {@link rekeySchemaPrimaryKey}, which the store's native arm uses too.
2283
+ */
2284
+ buildRekeyedPrimaryKeySchema(newPkColumns) {
2285
+ const columns = this.tableSchema.columns;
2286
+ const seen = new Set();
2287
+ for (const pk of newPkColumns) {
2288
+ if (pk.index < 0 || pk.index >= columns.length) {
2289
+ throw new QuereusError(`Primary key column index ${pk.index} is out of bounds for table '${this._tableName}'.`, StatusCode.ERROR);
2290
+ }
2291
+ if (seen.has(pk.index)) {
2292
+ throw new QuereusError(`Duplicate column '${columns[pk.index].name}' in PRIMARY KEY definition`, StatusCode.ERROR);
2293
+ }
2294
+ seen.add(pk.index);
2295
+ if (!columns[pk.index].notNull) {
2296
+ throw new QuereusError(`Column '${columns[pk.index].name}' must be NOT NULL to participate in PRIMARY KEY`, StatusCode.CONSTRAINT);
2297
+ }
2298
+ }
2299
+ return rekeySchemaPrimaryKey(this.tableSchema, newPkColumns);
2300
+ }
2301
+ /**
2302
+ * The re-keyed-structure pre-pass for {@link alterColumn}, over the DDL transaction's EFFECTIVE
2303
+ * rows: a pair the transaction inserted that collides under the NEW schema must reject the
2304
+ * change, and one it has deleted must not block it. Runs before any mutation, so a throw leaves
2305
+ * the schema, the base layer and the index map exactly as they were.
2306
+ *
2307
+ * A value-REWRITING change (`set data type`, or a `set not null` null → DEFAULT backfill) probes
2308
+ * the CONVERTED rows — the same per-row conversion the base rewrite applies — under the NEW
2309
+ * schema's comparators, so one call covers both ways an ALTER can collapse two previously-distinct
2310
+ * rows: the VALUES converge (`'1'`/`'01'` → 1; two NULLs → one DEFAULT;
2311
+ * '2024-06-05T00:00:00Z' → '2024-06-05') and/or the COMPARATOR moves (text → timespan makes
2312
+ * 'PT1H'/'PT60M' one value). A same-storage-class retype sets both `rewrite` and
2313
+ * `structuresRekeyed`, so the rewrite arm is tested FIRST — its converted-row probe subsumes the
2314
+ * comparator-only one. The rebuild that follows is deliberately non-enforcing, so this is the
2315
+ * only guard.
2316
+ *
2317
+ * The comparator-MOVING arm without a rewrite has one trigger left: `set collate`. The values are
2318
+ * untouched — hence no `mapRow` — and only the comparator the structures are keyed by moves.
2319
+ * `newSchema` carries the new collations, so the probe indexes compare as the rebuilt ones will.
2320
+ *
2321
+ * The primary key: `set collate` on a PK member gets a stricter pre-pass
2322
+ * ({@link validateRekeyedPrimaryKey}): no layer in the chain, base included, may hold a collision.
2323
+ * That is what lets every apply step succeed unconditionally, and what
2324
+ * `TransactionLayer.rekeyPrimaryKey` relies on. The rewrite arm needs no PK pre-pass and cannot
2325
+ * shadow one: a retype of a PK column is rejected upstream, the backfill only fires on a column
2326
+ * holding NULLs (which a PK member cannot — the engine enforces NOT NULL on every PK member
2327
+ * regardless of the declared nullability), and `pkColumnRekeyed` is set only by `set collate`,
2328
+ * which never sets `rewrite` — so whenever `pkColumnRekeyed` is true, the `structuresRekeyed` arm
2329
+ * still runs.
2330
+ * NOTE: if PK members ever become genuinely nullable, the backfill arm gains a PK collision path
2331
+ * (two NULL keys → one DEFAULT) and needs `validateRekeyedPrimaryKey` plus a primary-tree re-key
2332
+ * of its own.
2333
+ */
2334
+ async validateAlterColumnPlan(plan, rows) {
2335
+ if (plan.rewrite) {
2336
+ const { convert, convertNulls } = plan.rewrite;
2337
+ await this.validateRekeyedUniqueStructures(plan.newSchema, plan.colIndex, rows,
2338
+ // Unlike `convertBaseRows`, a conversion failure here is NOT swallowed. Every row
2339
+ // the probe sees is visible to the transaction, and the `set data type` pre-pass
2340
+ // already proved each one convertible, so a throw is unreachable; surfacing it as
2341
+ // MISMATCH still beats probing a stale value that could mask a collision.
2342
+ row => convertRowAtIndex(row, plan.colIndex, convert, convertNulls));
2343
+ }
2344
+ else if (plan.structuresRekeyed) {
2345
+ await this.validateRekeyedUniqueStructures(plan.newSchema, plan.colIndex, rows);
2346
+ // Unlike the secondary-structure pass above — which only ever judges the effective
2347
+ // rows — the PK pass judges TWO row sets: the effective rows (`rows` when a wrapper
2348
+ // supplies them) decide whether the change is LEGAL, and this manager's own layer
2349
+ // chain decides whether the re-keyed trees can PHYSICALLY carry it. See
2350
+ // `validateRekeyedPrimaryKey` for why the sets differ and which status each raises.
2351
+ if (plan.pkColumnRekeyed)
2352
+ await this.validateRekeyedPrimaryKey(plan.newSchema, rows);
2353
+ }
2354
+ }
2355
+ /**
2356
+ * First mutation of {@link alterColumn}: install the new schema on the base layer and rebuild its
2357
+ * structures under it — each MemoryIndex rebuilds its comparator from the CURRENT schema's
2358
+ * collation and logical type. The secondary rebuilds are NON-enforcing ({@link validateAlterColumnPlan}
2359
+ * owns uniqueness, and the base's rows are not a subset of the effective rows); the `set collate`
2360
+ * PK rebuild is strict, since a PK collision cannot be represented at all — the pre-pass has
2361
+ * already proved the base collision-free, so the strict rebuild is a live invariant check, not
2362
+ * the enforcement path. It runs LAST so its throw leaves the live tree intact for the rollback.
2363
+ *
2364
+ * Records the primary tree it is about to REPLACE in `undo` *before* replacing it, so a throw
2365
+ * from either rebuild still leaves the caller's `catch` a tree to restore.
2366
+ */
2367
+ applyAlterColumnToBase(plan, undo) {
2368
+ this.baseLayer.updateSchema(plan.newSchema);
2369
+ if (plan.rewrite) {
2370
+ // `set data type` / `set not null` backfill: convert the committed base rows and REPLACE
2371
+ // the primary tree with a fresh one holding them (not an in-place upsert — inheritree
2372
+ // forbids mutating a base while the open transaction's layers derive from it).
2373
+ // `rebuildPrimaryTreeFromRows` also rebuilds every secondary index from the converted
2374
+ // rows under the new comparators, so index-backed lookups see the new values — which
2375
+ // is why this arm runs first and subsumes `rebuildAllSecondaryIndexes` when a
2376
+ // same-storage-class retype sets `structuresRekeyed` too.
2377
+ // NOTE: rebuilds EVERY secondary index, not only those covering the altered column;
2378
+ // if a wide-index table ever shows this as slow, filter to indexes whose columns
2379
+ // include colIndex. Mirrors the `set collate` path's unconditional rebuild.
2380
+ //
2381
+ // `set data type`: a base value that fails to convert is kept as-is — validation over
2382
+ // the effective view already rejected any unconvertible value the transaction can SEE,
2383
+ // so a surviving one is shadowed by a pending delete/overwrite and is never read back
2384
+ // (a ROLLBACK re-exposes it under the new type — DDL is not undone by rollback, the
2385
+ // known behavior of `feat-ddl-transaction-capability`).
2386
+ // `set not null` (`convertNulls`): null base values are mapped to the DEFAULT literal.
2387
+ const convertedBaseRows = this.convertBaseRows(plan.colIndex, plan.rewrite.convert, plan.rewrite.convertNulls);
2388
+ undo.basePrimaryTree = this.baseLayer.primaryTree;
2389
+ this.baseLayer.rebuildPrimaryTreeFromRows(convertedBaseRows);
2390
+ }
2391
+ else if (plan.structuresRekeyed) {
2392
+ this.baseLayer.rebuildAllSecondaryIndexes();
2393
+ if (plan.pkColumnRekeyed) {
2394
+ undo.basePrimaryTree = this.baseLayer.primaryTree;
2395
+ this.baseLayer.rebuildPrimaryTreeStrict();
2396
+ }
2397
+ }
2398
+ }
2399
+ /**
2400
+ * Hand the post-{@link alterColumn} schema to the DDL transaction's own open layers.
2401
+ *
2402
+ * The base rebuild handed every secondary index a fresh tree under the new comparator; those
2403
+ * layers still inherit the old ones and froze the old schema at construction. Re-key them, or
2404
+ * the rest of the transaction — and everything after the pending layer becomes the committed
2405
+ * head at commit — keeps comparing under the old collation / old logical type.
2406
+ *
2407
+ * When the altered column is part of the primary key, `rebuildPrimaryTreeStrict` also swapped the
2408
+ * base primary tree object out from under those layers' copy-on-write bases and invalidated their
2409
+ * `pkFunctions`; `rekeyPrimaryKey` rebuilds both, plus every secondary index (each derives its PK
2410
+ * comparator/encoder from the PK definition). Outside a transaction there are no open layers and
2411
+ * every arm is a no-op.
2412
+ */
2413
+ propagateAlterColumnToOpenLayers(plan) {
2414
+ if (plan.rewrite) {
2415
+ // `set data type` / `set not null` backfill converted the base; the open layers still hold
2416
+ // their own-written rows unconverted. Swap the schema and convert those values so the rest
2417
+ // of the transaction — and the committed head at commit — read the new value. For
2418
+ // `set not null` this fills the transaction's OWN pending NULL rows (which live in the
2419
+ // pending layer, never reached by an in-place base upsert). `convertColumn` installs the
2420
+ // new schema and rebuilds every one of the layer's secondary indexes from it, so it
2421
+ // subsumes `adoptSchema` when a same-storage-class retype moved the comparators too.
2422
+ this.convertColumnOnOpenLayers(plan.newSchema, plan.colIndex, plan.rewrite.convert, plan.rewrite.convertNulls);
2423
+ }
2424
+ else if (plan.structuresRekeyed) {
2425
+ this.adoptSchemaOnOpenLayers(plan.newSchema, plan.pkColumnRekeyed);
2426
+ }
2427
+ else {
2428
+ // The remaining changes (`set default`, `drop not null`, a `set not null` that needed no
2429
+ // backfill, a retype between aliases of one logical type) touch nothing a layer
2430
+ // DERIVES — its index set, its `uniqueConstraints` enforcement and its MemoryIndex
2431
+ // comparators all stay put, and DEFAULT application / NOT NULL enforcement happen
2432
+ // above the module, off the catalog schema. But the frozen schema OBJECT is itself
2433
+ // observable: `commitTransaction` only wraps a swapped-in savepoint snapshot back
2434
+ // into the committed chain when `readLayer.getSchema() === this.tableSchema`. Left
2435
+ // stale, a `rollback to savepoint` taken across one of these ALTERs restores a
2436
+ // snapshot the wrap then skips, and the transaction's staged rows are silently
2437
+ // dropped at COMMIT. Adoption is cheap here — every IndexSchema object is carried
2438
+ // over unchanged, so `adoptSchema` keeps each layer's MemoryIndex and only swaps
2439
+ // the schema reference.
2440
+ this.adoptSchemaOnOpenLayers(plan.newSchema);
2441
+ }
2442
+ }
2443
+ /**
2444
+ * The rows the `alter column` pre-validation scans judge: `rows` when a wrapper module supplies
2445
+ * them (the isolation overlay holds the transaction's pending rows outside this manager, so only
2446
+ * it can name the rows the issuing connection actually sees), else this manager's own layered
2447
+ * view ({@link effectiveDdlRows}) — which stays a SYNC iterable on purpose, so a scan over it
2448
+ * cannot be interleaved with another connection's write. See `scanEffectiveRows`.
2449
+ */
2450
+ effectiveRowSource(rows) {
2451
+ return rows ?? (() => this.effectiveDdlRows());
2452
+ }
2377
2453
  async createIndex(newIndexSchemaEntry, ifNotExistsFromAst, rows) {
2378
2454
  if (this.isReadOnly)
2379
2455
  throw new QuereusError(`Table '${this._tableName}' is read-only`, StatusCode.READONLY);
@@ -2774,7 +2850,11 @@ export class MemoryTableManager {
2774
2850
  // would under-enforce this non-derived UNIQUE. A collation mismatch falls
2775
2851
  // through to build the distinct `_uc_*` covering index; the user index keeps
2776
2852
  // enforcing its own (stricter) uniqueness independently (matches SQLite).
2777
- const matchingUniqueIndex = existingIndexes.find(idx => idx.unique &&
2853
+ // A FILTERED (partial) unique index only guarantees uniqueness among the rows
2854
+ // its own predicate admits, so it can't back a full (unfiltered) UNIQUE either —
2855
+ // same reasoning as `ensureUniqueConstraintIndexes` above.
2856
+ const matchingUniqueIndex = uc.predicate ? undefined : existingIndexes.find(idx => idx.unique &&
2857
+ !idx.predicate &&
2778
2858
  idx.columns.length === uc.columns.length &&
2779
2859
  idx.columns.every((col, i) => col.index === uc.columns[i]) &&
2780
2860
  this.indexCollationsMatchDeclared(idx, uc));
@@ -2794,8 +2874,7 @@ export class MemoryTableManager {
2794
2874
  this.adoptSchemaOnOpenLayers(newSchema);
2795
2875
  return;
2796
2876
  }
2797
- const colNames = uc.columns.map(i => columns[i]?.name ?? String(i));
2798
- const indexName = uc.name ?? `_uc_${colNames.join('_')}`;
2877
+ const indexName = uc.name ?? implicitIndexNameOver(uc, columns);
2799
2878
  const indexSchema = {
2800
2879
  name: indexName,
2801
2880
  // Carry per-column collation so enforcement honors e.g. NOCASE (mirrors
@@ -3113,24 +3192,41 @@ export class MemoryTableManager {
3113
3192
  }
3114
3193
  }
3115
3194
  /**
3116
- * PRIMARY KEY arm of the `alter column … set collate` pre-pass. Runs before anything is
3117
- * mutated, so a rejection leaves the table, the schema and the transaction untouched.
3195
+ * The primary-key re-key pre-pass, shared by both statements that move the key:
3196
+ * `alter column … set collate` on a PK member (the comparator moves) and
3197
+ * `alter table … alter primary key` (the key columns move — {@link alterPrimaryKey}).
3198
+ * Runs before anything is mutated, so a rejection leaves the table, the schema and the
3199
+ * transaction untouched.
3118
3200
  *
3119
- * The primary tree is a map, not a multi-map, so — unlike a secondary index — it cannot
3120
- * physically hold two rows whose keys collapse under the new comparator. Every layer the
3121
- * DDL connection still reads through therefore has to be collision-free, not just the
3122
- * transaction's effective view:
3201
+ * Asks two questions, in order, over two DIFFERENT row sets:
3123
3202
  *
3124
- * 1. **The effective view collides** → `CONSTRAINT`. The duplicate is visible to a `select`
3125
- * in this transaction; the change is simply illegal.
3126
- * 2. **A layer beneath it collides** → `BUSY`. Those rows are not visible now, but every
3127
- * such layer is the copy-on-write base the view reads through, and one of them may be a
3128
- * savepoint snapshot a `rollback to savepoint` must restore. A re-keyed tree could not
3129
- * represent the pair at all. Committing (or rolling back) settles the transaction and
3130
- * the ALTER can be retried — the same "commit/rollback and retry" posture as
3131
- * {@link ensureSchemaChangeSafety}.
3203
+ * 1. **Is the change legal at all?** — probes the rows the DDL transaction can SEE: the
3204
+ * wrapper-supplied `rows` when a wrapper module (the isolation layer) holds the
3205
+ * transaction's pending rows outside this manager, otherwise this manager's own
3206
+ * layered view ({@link effectiveDdlRows}). A duplicate here is visible to a `select`
3207
+ * in this transaction, so the change is simply illegal → `CONSTRAINT`, naming the
3208
+ * colliding key.
3209
+ * 2. **Can the structures physically carry it?** — probes every layer of this manager's
3210
+ * own chain. The primary tree is a map, not a multi-map, so — unlike a secondary
3211
+ * index — no tree that a `rollback` / `rollback to savepoint` could restore may hold
3212
+ * two rows whose keys collapse under the new comparator. A collision confined to
3213
+ * committed rows the transaction has DELETED is therefore refused by physical
3214
+ * necessity, not because the data is invalid: the base must keep both rows for a
3215
+ * rollback, and a base re-keyed under the new collation could not represent the pair
3216
+ * at all → `BUSY` with the same "commit/rollback and retry" posture as
3217
+ * {@link ensureSchemaChangeSafety}. The persistent store backend asks the same two
3218
+ * questions with the same statuses (`StoreTable.validateRekeyedPrimaryKey` in
3219
+ * quereus-store — its committed rows equally survive a rollback); accepting the
3220
+ * deleted-collider shape instead would take transaction-scoped DDL
3221
+ * (`backlog/feat-transactional-ddl-native-backends`), which Quereus does not have.
3132
3222
  *
3133
- * Case 2 is deliberately conservative. The chain holds one immutable layer per statement
3223
+ * The passes judge different sets because a wrapper's effective stream and this manager's
3224
+ * layers genuinely diverge: staged inserts exist only in the stream, deleted committed
3225
+ * rows only in the layers. When `rows` is supplied, the view layer holds the COMMITTED
3226
+ * rows — not a subset of what pass 1 judged — so pass 2 starts AT the view; when `rows`
3227
+ * is absent, pass 1 judged exactly the view's rows and pass 2 starts at its parent.
3228
+ *
3229
+ * Pass 2 is deliberately conservative. The chain holds one immutable layer per statement
3134
3230
  * boundary (see `MemoryTableConnection.createSavepoint`'s eager path), so it rejects any
3135
3231
  * transaction that has held a colliding pair at ANY statement boundary — even one whose
3136
3232
  * final view is clean and whose intermediate layer no savepoint can reach. Narrowing that
@@ -3138,44 +3234,86 @@ export class MemoryTableManager {
3138
3234
  * rebase that savepoint snapshots exist to avoid. The tradeoff is a rare false BUSY on a
3139
3235
  * statement sequence the user can retry after committing, versus losing a row on rollback.
3140
3236
  *
3141
- * This precondition is also what makes `TransactionLayer.rekeyPrimaryKey`'s replay sound:
3142
- * with no collisions anywhere in the chain, every primary key resolves to at most one row
3143
- * in each layer under the new comparator.
3237
+ * This precondition is also what makes the open-layer replays sound — both
3238
+ * `TransactionLayer.rekeyPrimaryKey`'s and `installRekeyedPrimaryKeyColumns`'s: with no
3239
+ * collisions anywhere in the chain, every primary key resolves to at most one row in
3240
+ * each layer under the new definition.
3144
3241
  */
3145
- validateRekeyedPrimaryKey(newSchema) {
3242
+ async validateRekeyedPrimaryKey(newSchema, rows) {
3146
3243
  const newPkFunctions = createPrimaryKeyFunctions(newSchema, this.collationResolver);
3147
3244
  const connection = this.ddlConnection();
3148
3245
  const view = connection
3149
3246
  ? (connection.pendingTransactionLayer ?? connection.readLayer)
3150
3247
  : this.baseLayer;
3151
- this.assertNoPrimaryKeyCollision(view, newPkFunctions, StatusCode.CONSTRAINT, `UNIQUE constraint failed: ${this._tableName} primary key collides under new collation`);
3248
+ await this.assertNoPrimaryKeyCollisionInRows(rows ? rows() : this.effectiveDdlRows(), newPkFunctions, primaryKeyArity(newSchema) !== 1);
3152
3249
  // `ensureSchemaChangeSafety` has already drained every committed layer into the base and
3153
- // rejected sibling connections with open work, so the chain below the view holds only
3154
- // this transaction's own layers.
3155
- for (let layer = view.getParent(); layer; layer = layer.getParent()) {
3156
- this.assertNoPrimaryKeyCollision(layer, newPkFunctions, StatusCode.BUSY, `Cannot change the collation of a primary key column of table ${this._tableName}: `
3157
- + `rows this transaction has removed still collide under the new collation and must survive a rollback. `
3158
- + `Commit/rollback and retry.`);
3250
+ // rejected sibling connections with open work, so this walk covers the transaction's own
3251
+ // layers plus the base — every tree a rollback could restore.
3252
+ for (let layer = rows ? view : view.getParent(); layer; layer = layer.getParent()) {
3253
+ this.assertNoPrimaryKeyCollisionInLayer(layer, newPkFunctions);
3254
+ }
3255
+ }
3256
+ /**
3257
+ * A duplicate-key detector over the new primary key functions: feed it rows, and it returns
3258
+ * the key the moment one repeats. Shared by both arms of {@link validateRekeyedPrimaryKey}
3259
+ * so the async (effective-row) arm and the sync (layer-tree) arm cannot disagree about what
3260
+ * counts as a collision.
3261
+ *
3262
+ * NOTE: the probe holds every row it has seen, because the BTree derives its key from the
3263
+ * stored value — so an ALTER over a large table transiently doubles that table's row
3264
+ * references. If a wide table ever makes this the memory peak, key the probe by the PK
3265
+ * encoding (`primaryKeyFunctions.encode`) into a `Set` instead of by the row.
3266
+ */
3267
+ makePrimaryKeyProbe(pkFunctions) {
3268
+ const probe = new BTree((row) => pkFunctions.extractFromRow(row), pkFunctions.compare);
3269
+ return (row) => {
3270
+ const key = pkFunctions.extractFromRow(row);
3271
+ if (probe.get(key) !== undefined)
3272
+ return key;
3273
+ probe.insert(row);
3274
+ return undefined;
3275
+ };
3276
+ }
3277
+ /**
3278
+ * Legality arm of {@link validateRekeyedPrimaryKey}: raises `CONSTRAINT`, naming the key,
3279
+ * when two of the rows the DDL transaction can SEE share a primary key under `pkFunctions`.
3280
+ * Takes a wrapper's merged async stream or this manager's own synchronous layered view.
3281
+ */
3282
+ async assertNoPrimaryKeyCollisionInRows(rows, pkFunctions, keyIsTuple) {
3283
+ const seen = this.makePrimaryKeyProbe(pkFunctions);
3284
+ for await (const row of rows) {
3285
+ const key = seen(row);
3286
+ if (key === undefined)
3287
+ continue;
3288
+ // An empty new key (`alter primary key ()`, the singleton table) has no components
3289
+ // to name: every row IS the one key, so say that rather than render "(key: )".
3290
+ const parts = keyParts(key, keyIsTuple).map(formatKeyValue);
3291
+ const keyDesc = parts.length > 0 ? `(key: ${parts.join(', ')})` : '(the empty key admits one row)';
3292
+ throw new QuereusError(`UNIQUE constraint failed: ${this._tableName} primary key collides under the new key definition ${keyDesc}`, StatusCode.CONSTRAINT);
3159
3293
  }
3160
3294
  }
3161
3295
  /**
3162
- * Raises `code` when two of `layer`'s rows share a primary key under `pkFunctions`.
3296
+ * Representability arm of {@link validateRekeyedPrimaryKey}: raises `BUSY` when two of
3297
+ * `layer`'s physical rows share a primary key under `pkFunctions`. Its rows are ones a
3298
+ * rollback could restore, not ones the transaction can see, so this is never a statement
3299
+ * about the data's validity — hence the retryable status and wording.
3163
3300
  *
3164
3301
  * NOTE: O(rows) per layer, so O(layers × rows) for a whole chain — one more full pass than
3165
3302
  * the base rebuild the caller is about to do anyway. Fine for a statement this rare; if a
3166
3303
  * deep savepoint stack over a large table ever makes an ALTER slow, note that a layer's rows
3167
3304
  * differ from its parent's only at the keys it wrote, so the walk can be narrowed to those.
3168
3305
  */
3169
- assertNoPrimaryKeyCollision(layer, pkFunctions, code, message) {
3306
+ assertNoPrimaryKeyCollisionInLayer(layer, pkFunctions) {
3170
3307
  const tree = layer.getModificationTree('primary');
3171
3308
  if (!tree)
3172
3309
  return;
3173
- const probe = new BTree((row) => pkFunctions.extractFromRow(row), pkFunctions.compare);
3310
+ const seen = this.makePrimaryKeyProbe(pkFunctions);
3174
3311
  for (const row of iteratePrimaryRows(tree)) {
3175
- const key = pkFunctions.extractFromRow(row);
3176
- if (probe.get(key) !== undefined)
3177
- throw new QuereusError(message, code);
3178
- probe.insert(row);
3312
+ if (seen(row) !== undefined) {
3313
+ throw new QuereusError(`Cannot re-key the primary key of table ${this._tableName}: `
3314
+ + `rows this transaction has removed still collide under the new key definition and must survive a rollback. `
3315
+ + `Commit/rollback and retry.`, StatusCode.BUSY);
3316
+ }
3179
3317
  }
3180
3318
  }
3181
3319
  /**