@quereus/quereus 4.3.2 → 4.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (396) hide show
  1. package/README.md +5 -0
  2. package/dist/src/common/types.d.ts +9 -0
  3. package/dist/src/common/types.d.ts.map +1 -1
  4. package/dist/src/common/types.js.map +1 -1
  5. package/dist/src/core/database-events.d.ts +49 -0
  6. package/dist/src/core/database-events.d.ts.map +1 -1
  7. package/dist/src/core/database-events.js +148 -0
  8. package/dist/src/core/database-events.js.map +1 -1
  9. package/dist/src/core/database-external-changes.d.ts +2 -2
  10. package/dist/src/core/database-external-changes.d.ts.map +1 -1
  11. package/dist/src/core/database-external-changes.js +12 -9
  12. package/dist/src/core/database-external-changes.js.map +1 -1
  13. package/dist/src/core/database-materialized-views-apply.d.ts +156 -14
  14. package/dist/src/core/database-materialized-views-apply.d.ts.map +1 -1
  15. package/dist/src/core/database-materialized-views-apply.js +370 -65
  16. package/dist/src/core/database-materialized-views-apply.js.map +1 -1
  17. package/dist/src/core/database-materialized-views-plan-builders.d.ts +66 -1
  18. package/dist/src/core/database-materialized-views-plan-builders.d.ts.map +1 -1
  19. package/dist/src/core/database-materialized-views-plan-builders.js +368 -8
  20. package/dist/src/core/database-materialized-views-plan-builders.js.map +1 -1
  21. package/dist/src/core/database-materialized-views-plans.d.ts +257 -30
  22. package/dist/src/core/database-materialized-views-plans.d.ts.map +1 -1
  23. package/dist/src/core/database-materialized-views.d.ts +99 -42
  24. package/dist/src/core/database-materialized-views.d.ts.map +1 -1
  25. package/dist/src/core/database-materialized-views.js +198 -121
  26. package/dist/src/core/database-materialized-views.js.map +1 -1
  27. package/dist/src/core/database.d.ts +5 -0
  28. package/dist/src/core/database.d.ts.map +1 -1
  29. package/dist/src/core/database.js +41 -18
  30. package/dist/src/core/database.js.map +1 -1
  31. package/dist/src/core/internal-statement-cache.d.ts +18 -0
  32. package/dist/src/core/internal-statement-cache.d.ts.map +1 -0
  33. package/dist/src/core/internal-statement-cache.js +211 -0
  34. package/dist/src/core/internal-statement-cache.js.map +1 -0
  35. package/dist/src/func/builtins/aggregate.d.ts +13 -12
  36. package/dist/src/func/builtins/aggregate.d.ts.map +1 -1
  37. package/dist/src/func/builtins/aggregate.js +146 -62
  38. package/dist/src/func/builtins/aggregate.js.map +1 -1
  39. package/dist/src/func/builtins/builtin-window-functions.d.ts.map +1 -1
  40. package/dist/src/func/builtins/builtin-window-functions.js +63 -48
  41. package/dist/src/func/builtins/builtin-window-functions.js.map +1 -1
  42. package/dist/src/func/registration.d.ts +19 -1
  43. package/dist/src/func/registration.d.ts.map +1 -1
  44. package/dist/src/func/registration.js +41 -0
  45. package/dist/src/func/registration.js.map +1 -1
  46. package/dist/src/index.d.ts +8 -8
  47. package/dist/src/index.d.ts.map +1 -1
  48. package/dist/src/index.js +12 -11
  49. package/dist/src/index.js.map +1 -1
  50. package/dist/src/parser/parser.d.ts +6 -0
  51. package/dist/src/parser/parser.d.ts.map +1 -1
  52. package/dist/src/parser/parser.js +45 -34
  53. package/dist/src/parser/parser.js.map +1 -1
  54. package/dist/src/planner/analysis/comparison-collation.d.ts +2 -0
  55. package/dist/src/planner/analysis/comparison-collation.d.ts.map +1 -1
  56. package/dist/src/planner/analysis/comparison-collation.js +1 -1
  57. package/dist/src/planner/analysis/comparison-collation.js.map +1 -1
  58. package/dist/src/planner/analysis/equi-correlation.d.ts +26 -0
  59. package/dist/src/planner/analysis/equi-correlation.d.ts.map +1 -0
  60. package/dist/src/planner/analysis/equi-correlation.js +62 -0
  61. package/dist/src/planner/analysis/equi-correlation.js.map +1 -0
  62. package/dist/src/planner/analysis/expression-fingerprint.d.ts.map +1 -1
  63. package/dist/src/planner/analysis/expression-fingerprint.js +29 -0
  64. package/dist/src/planner/analysis/expression-fingerprint.js.map +1 -1
  65. package/dist/src/planner/analysis/query-rewrite-matcher.d.ts +60 -18
  66. package/dist/src/planner/analysis/query-rewrite-matcher.d.ts.map +1 -1
  67. package/dist/src/planner/analysis/query-rewrite-matcher.js +95 -42
  68. package/dist/src/planner/analysis/query-rewrite-matcher.js.map +1 -1
  69. package/dist/src/planner/analysis/sat-checker.d.ts.map +1 -1
  70. package/dist/src/planner/analysis/sat-checker.js +43 -4
  71. package/dist/src/planner/analysis/sat-checker.js.map +1 -1
  72. package/dist/src/planner/analysis/scalar-subqueries.d.ts +28 -0
  73. package/dist/src/planner/analysis/scalar-subqueries.d.ts.map +1 -0
  74. package/dist/src/planner/analysis/scalar-subqueries.js +60 -0
  75. package/dist/src/planner/analysis/scalar-subqueries.js.map +1 -0
  76. package/dist/src/planner/building/delete.d.ts.map +1 -1
  77. package/dist/src/planner/building/delete.js +10 -3
  78. package/dist/src/planner/building/delete.js.map +1 -1
  79. package/dist/src/planner/building/expression.d.ts.map +1 -1
  80. package/dist/src/planner/building/expression.js +87 -6
  81. package/dist/src/planner/building/expression.js.map +1 -1
  82. package/dist/src/planner/building/foreign-key-builder.d.ts +44 -1
  83. package/dist/src/planner/building/foreign-key-builder.d.ts.map +1 -1
  84. package/dist/src/planner/building/foreign-key-builder.js +54 -0
  85. package/dist/src/planner/building/foreign-key-builder.js.map +1 -1
  86. package/dist/src/planner/building/insert.d.ts.map +1 -1
  87. package/dist/src/planner/building/insert.js +9 -12
  88. package/dist/src/planner/building/insert.js.map +1 -1
  89. package/dist/src/planner/building/select.d.ts.map +1 -1
  90. package/dist/src/planner/building/select.js +15 -1
  91. package/dist/src/planner/building/select.js.map +1 -1
  92. package/dist/src/planner/building/update.d.ts.map +1 -1
  93. package/dist/src/planner/building/update.js +13 -4
  94. package/dist/src/planner/building/update.js.map +1 -1
  95. package/dist/src/planner/building/with.d.ts.map +1 -1
  96. package/dist/src/planner/building/with.js +5 -8
  97. package/dist/src/planner/building/with.js.map +1 -1
  98. package/dist/src/planner/cache/materialization-advisory.d.ts +33 -0
  99. package/dist/src/planner/cache/materialization-advisory.d.ts.map +1 -1
  100. package/dist/src/planner/cache/materialization-advisory.js +164 -28
  101. package/dist/src/planner/cache/materialization-advisory.js.map +1 -1
  102. package/dist/src/planner/cache/reference-graph.d.ts +0 -4
  103. package/dist/src/planner/cache/reference-graph.d.ts.map +1 -1
  104. package/dist/src/planner/cache/reference-graph.js +5 -13
  105. package/dist/src/planner/cache/reference-graph.js.map +1 -1
  106. package/dist/src/planner/cost/index.d.ts +28 -7
  107. package/dist/src/planner/cost/index.d.ts.map +1 -1
  108. package/dist/src/planner/cost/index.js +26 -0
  109. package/dist/src/planner/cost/index.js.map +1 -1
  110. package/dist/src/planner/framework/pass.js +3 -3
  111. package/dist/src/planner/nodes/cache-node.d.ts +18 -2
  112. package/dist/src/planner/nodes/cache-node.d.ts.map +1 -1
  113. package/dist/src/planner/nodes/cache-node.js +42 -3
  114. package/dist/src/planner/nodes/cache-node.js.map +1 -1
  115. package/dist/src/planner/nodes/cte-node.d.ts +15 -1
  116. package/dist/src/planner/nodes/cte-node.d.ts.map +1 -1
  117. package/dist/src/planner/nodes/cte-node.js +12 -2
  118. package/dist/src/planner/nodes/cte-node.js.map +1 -1
  119. package/dist/src/planner/nodes/dml-executor-node.d.ts +0 -9
  120. package/dist/src/planner/nodes/dml-executor-node.d.ts.map +1 -1
  121. package/dist/src/planner/nodes/dml-executor-node.js.map +1 -1
  122. package/dist/src/planner/nodes/recursive-cte-node.d.ts +30 -1
  123. package/dist/src/planner/nodes/recursive-cte-node.d.ts.map +1 -1
  124. package/dist/src/planner/nodes/recursive-cte-node.js +30 -3
  125. package/dist/src/planner/nodes/recursive-cte-node.js.map +1 -1
  126. package/dist/src/planner/nodes/scalar.d.ts.map +1 -1
  127. package/dist/src/planner/nodes/scalar.js +26 -3
  128. package/dist/src/planner/nodes/scalar.js.map +1 -1
  129. package/dist/src/planner/optimizer.d.ts.map +1 -1
  130. package/dist/src/planner/optimizer.js +129 -8
  131. package/dist/src/planner/optimizer.js.map +1 -1
  132. package/dist/src/planner/rules/access/rule-select-access-path.d.ts.map +1 -1
  133. package/dist/src/planner/rules/access/rule-select-access-path.js +43 -18
  134. package/dist/src/planner/rules/access/rule-select-access-path.js.map +1 -1
  135. package/dist/src/planner/rules/cache/rule-cte-optimization.d.ts.map +1 -1
  136. package/dist/src/planner/rules/cache/rule-cte-optimization.js +8 -1
  137. package/dist/src/planner/rules/cache/rule-cte-optimization.js.map +1 -1
  138. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.d.ts.map +1 -1
  139. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js +72 -54
  140. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js.map +1 -1
  141. package/dist/src/planner/rules/cache/rule-mutating-subquery-cache.d.ts.map +1 -1
  142. package/dist/src/planner/rules/cache/rule-mutating-subquery-cache.js +6 -0
  143. package/dist/src/planner/rules/cache/rule-mutating-subquery-cache.js.map +1 -1
  144. package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.d.ts +25 -0
  145. package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.d.ts.map +1 -0
  146. package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.js +176 -0
  147. package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.js.map +1 -0
  148. package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.d.ts +31 -0
  149. package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.d.ts.map +1 -0
  150. package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.js +68 -0
  151. package/dist/src/planner/rules/cache/rule-scalar-subquery-cache.js.map +1 -0
  152. package/dist/src/planner/rules/join/rule-fanout-lookup-join.d.ts.map +1 -1
  153. package/dist/src/planner/rules/join/rule-fanout-lookup-join.js +1 -54
  154. package/dist/src/planner/rules/join/rule-fanout-lookup-join.js.map +1 -1
  155. package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.js +8 -1
  156. package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.js.map +1 -1
  157. package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.d.ts +191 -0
  158. package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.d.ts.map +1 -0
  159. package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.js +627 -0
  160. package/dist/src/planner/rules/subquery/rule-scalar-agg-decorrelation.js.map +1 -0
  161. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.d.ts +10 -1
  162. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.d.ts.map +1 -1
  163. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js +242 -51
  164. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js.map +1 -1
  165. package/dist/src/runtime/cache/shared-cache.d.ts +7 -0
  166. package/dist/src/runtime/cache/shared-cache.d.ts.map +1 -1
  167. package/dist/src/runtime/cache/shared-cache.js +34 -1
  168. package/dist/src/runtime/cache/shared-cache.js.map +1 -1
  169. package/dist/src/runtime/deferred-constraint-queue.d.ts.map +1 -1
  170. package/dist/src/runtime/deferred-constraint-queue.js +10 -0
  171. package/dist/src/runtime/deferred-constraint-queue.js.map +1 -1
  172. package/dist/src/runtime/emit/aggregate-setup.d.ts +49 -0
  173. package/dist/src/runtime/emit/aggregate-setup.d.ts.map +1 -0
  174. package/dist/src/runtime/emit/aggregate-setup.js +99 -0
  175. package/dist/src/runtime/emit/aggregate-setup.js.map +1 -0
  176. package/dist/src/runtime/emit/aggregate.d.ts +2 -3
  177. package/dist/src/runtime/emit/aggregate.d.ts.map +1 -1
  178. package/dist/src/runtime/emit/aggregate.js +10 -84
  179. package/dist/src/runtime/emit/aggregate.js.map +1 -1
  180. package/dist/src/runtime/emit/alter-table.d.ts.map +1 -1
  181. package/dist/src/runtime/emit/alter-table.js +348 -165
  182. package/dist/src/runtime/emit/alter-table.js.map +1 -1
  183. package/dist/src/runtime/emit/asof-scan.d.ts.map +1 -1
  184. package/dist/src/runtime/emit/asof-scan.js +6 -0
  185. package/dist/src/runtime/emit/asof-scan.js.map +1 -1
  186. package/dist/src/runtime/emit/between.d.ts.map +1 -1
  187. package/dist/src/runtime/emit/between.js +32 -3
  188. package/dist/src/runtime/emit/between.js.map +1 -1
  189. package/dist/src/runtime/emit/binary.d.ts.map +1 -1
  190. package/dist/src/runtime/emit/binary.js +22 -2
  191. package/dist/src/runtime/emit/binary.js.map +1 -1
  192. package/dist/src/runtime/emit/bloom-join.d.ts.map +1 -1
  193. package/dist/src/runtime/emit/bloom-join.js +24 -3
  194. package/dist/src/runtime/emit/bloom-join.js.map +1 -1
  195. package/dist/src/runtime/emit/cache.d.ts +7 -3
  196. package/dist/src/runtime/emit/cache.d.ts.map +1 -1
  197. package/dist/src/runtime/emit/cache.js +23 -6
  198. package/dist/src/runtime/emit/cache.js.map +1 -1
  199. package/dist/src/runtime/emit/cast.d.ts.map +1 -1
  200. package/dist/src/runtime/emit/cast.js +10 -23
  201. package/dist/src/runtime/emit/cast.js.map +1 -1
  202. package/dist/src/runtime/emit/constraint-check.d.ts.map +1 -1
  203. package/dist/src/runtime/emit/constraint-check.js +41 -44
  204. package/dist/src/runtime/emit/constraint-check.js.map +1 -1
  205. package/dist/src/runtime/emit/create-view.d.ts.map +1 -1
  206. package/dist/src/runtime/emit/create-view.js +6 -0
  207. package/dist/src/runtime/emit/create-view.js.map +1 -1
  208. package/dist/src/runtime/emit/cte.d.ts.map +1 -1
  209. package/dist/src/runtime/emit/cte.js +43 -20
  210. package/dist/src/runtime/emit/cte.js.map +1 -1
  211. package/dist/src/runtime/emit/distinct.d.ts.map +1 -1
  212. package/dist/src/runtime/emit/distinct.js +6 -3
  213. package/dist/src/runtime/emit/distinct.js.map +1 -1
  214. package/dist/src/runtime/emit/dml-executor.d.ts.map +1 -1
  215. package/dist/src/runtime/emit/dml-executor.js +241 -115
  216. package/dist/src/runtime/emit/dml-executor.js.map +1 -1
  217. package/dist/src/runtime/emit/hash-aggregate.d.ts.map +1 -1
  218. package/dist/src/runtime/emit/hash-aggregate.js +16 -65
  219. package/dist/src/runtime/emit/hash-aggregate.js.map +1 -1
  220. package/dist/src/runtime/emit/insert.d.ts.map +1 -1
  221. package/dist/src/runtime/emit/insert.js +18 -7
  222. package/dist/src/runtime/emit/insert.js.map +1 -1
  223. package/dist/src/runtime/emit/join.js +5 -0
  224. package/dist/src/runtime/emit/join.js.map +1 -1
  225. package/dist/src/runtime/emit/materialized-view-helpers.d.ts +14 -1
  226. package/dist/src/runtime/emit/materialized-view-helpers.d.ts.map +1 -1
  227. package/dist/src/runtime/emit/materialized-view-helpers.js +38 -7
  228. package/dist/src/runtime/emit/materialized-view-helpers.js.map +1 -1
  229. package/dist/src/runtime/emit/materialized-view.d.ts.map +1 -1
  230. package/dist/src/runtime/emit/materialized-view.js +47 -6
  231. package/dist/src/runtime/emit/materialized-view.js.map +1 -1
  232. package/dist/src/runtime/emit/merge-join.d.ts.map +1 -1
  233. package/dist/src/runtime/emit/merge-join.js +18 -8
  234. package/dist/src/runtime/emit/merge-join.js.map +1 -1
  235. package/dist/src/runtime/emit/recursive-cte.d.ts.map +1 -1
  236. package/dist/src/runtime/emit/recursive-cte.js +59 -1
  237. package/dist/src/runtime/emit/recursive-cte.js.map +1 -1
  238. package/dist/src/runtime/emit/set-object-tags.d.ts +8 -0
  239. package/dist/src/runtime/emit/set-object-tags.d.ts.map +1 -1
  240. package/dist/src/runtime/emit/set-object-tags.js +8 -0
  241. package/dist/src/runtime/emit/set-object-tags.js.map +1 -1
  242. package/dist/src/runtime/emit/set-operation.d.ts.map +1 -1
  243. package/dist/src/runtime/emit/set-operation.js +7 -4
  244. package/dist/src/runtime/emit/set-operation.js.map +1 -1
  245. package/dist/src/runtime/emit/sort.d.ts.map +1 -1
  246. package/dist/src/runtime/emit/sort.js +5 -2
  247. package/dist/src/runtime/emit/sort.js.map +1 -1
  248. package/dist/src/runtime/emit/subquery.d.ts.map +1 -1
  249. package/dist/src/runtime/emit/subquery.js +113 -13
  250. package/dist/src/runtime/emit/subquery.js.map +1 -1
  251. package/dist/src/runtime/emit/temporal-arithmetic.d.ts +7 -0
  252. package/dist/src/runtime/emit/temporal-arithmetic.d.ts.map +1 -1
  253. package/dist/src/runtime/emit/temporal-arithmetic.js +15 -7
  254. package/dist/src/runtime/emit/temporal-arithmetic.js.map +1 -1
  255. package/dist/src/runtime/emit/update.d.ts.map +1 -1
  256. package/dist/src/runtime/emit/update.js +44 -3
  257. package/dist/src/runtime/emit/update.js.map +1 -1
  258. package/dist/src/runtime/emit/window.d.ts.map +1 -1
  259. package/dist/src/runtime/emit/window.js +72 -55
  260. package/dist/src/runtime/emit/window.js.map +1 -1
  261. package/dist/src/runtime/foreign-key-actions.d.ts +36 -1
  262. package/dist/src/runtime/foreign-key-actions.d.ts.map +1 -1
  263. package/dist/src/runtime/foreign-key-actions.js +238 -116
  264. package/dist/src/runtime/foreign-key-actions.js.map +1 -1
  265. package/dist/src/runtime/parallel-driver.d.ts +20 -1
  266. package/dist/src/runtime/parallel-driver.d.ts.map +1 -1
  267. package/dist/src/runtime/parallel-driver.js +23 -1
  268. package/dist/src/runtime/parallel-driver.js.map +1 -1
  269. package/dist/src/runtime/types.d.ts +54 -0
  270. package/dist/src/runtime/types.d.ts.map +1 -1
  271. package/dist/src/runtime/types.js.map +1 -1
  272. package/dist/src/schema/catalog-persistability.d.ts +58 -0
  273. package/dist/src/schema/catalog-persistability.d.ts.map +1 -0
  274. package/dist/src/schema/catalog-persistability.js +87 -0
  275. package/dist/src/schema/catalog-persistability.js.map +1 -0
  276. package/dist/src/schema/catalog.d.ts +17 -1
  277. package/dist/src/schema/catalog.d.ts.map +1 -1
  278. package/dist/src/schema/catalog.js +25 -3
  279. package/dist/src/schema/catalog.js.map +1 -1
  280. package/dist/src/schema/constraint-builder.d.ts +20 -22
  281. package/dist/src/schema/constraint-builder.d.ts.map +1 -1
  282. package/dist/src/schema/constraint-builder.js +43 -36
  283. package/dist/src/schema/constraint-builder.js.map +1 -1
  284. package/dist/src/schema/ddl-generator.d.ts +23 -0
  285. package/dist/src/schema/ddl-generator.d.ts.map +1 -1
  286. package/dist/src/schema/ddl-generator.js +27 -0
  287. package/dist/src/schema/ddl-generator.js.map +1 -1
  288. package/dist/src/schema/function.d.ts +109 -1
  289. package/dist/src/schema/function.d.ts.map +1 -1
  290. package/dist/src/schema/function.js.map +1 -1
  291. package/dist/src/schema/manager.d.ts +28 -0
  292. package/dist/src/schema/manager.d.ts.map +1 -1
  293. package/dist/src/schema/manager.js +82 -14
  294. package/dist/src/schema/manager.js.map +1 -1
  295. package/dist/src/schema/schema-differ.d.ts.map +1 -1
  296. package/dist/src/schema/schema-differ.js +51 -12
  297. package/dist/src/schema/schema-differ.js.map +1 -1
  298. package/dist/src/schema/unique-enforcement.d.ts +31 -0
  299. package/dist/src/schema/unique-enforcement.d.ts.map +1 -1
  300. package/dist/src/schema/unique-enforcement.js +37 -1
  301. package/dist/src/schema/unique-enforcement.js.map +1 -1
  302. package/dist/src/schema/window-function.d.ts +30 -10
  303. package/dist/src/schema/window-function.d.ts.map +1 -1
  304. package/dist/src/schema/window-function.js +20 -16
  305. package/dist/src/schema/window-function.js.map +1 -1
  306. package/dist/src/types/cast-semantics.d.ts +43 -0
  307. package/dist/src/types/cast-semantics.d.ts.map +1 -0
  308. package/dist/src/types/cast-semantics.js +59 -0
  309. package/dist/src/types/cast-semantics.js.map +1 -0
  310. package/dist/src/types/index.d.ts +1 -1
  311. package/dist/src/types/index.d.ts.map +1 -1
  312. package/dist/src/types/index.js +1 -1
  313. package/dist/src/types/index.js.map +1 -1
  314. package/dist/src/types/json-type.d.ts.map +1 -1
  315. package/dist/src/types/json-type.js +31 -10
  316. package/dist/src/types/json-type.js.map +1 -1
  317. package/dist/src/types/logical-type.d.ts +22 -0
  318. package/dist/src/types/logical-type.d.ts.map +1 -1
  319. package/dist/src/types/logical-type.js.map +1 -1
  320. package/dist/src/types/temporal-types.d.ts.map +1 -1
  321. package/dist/src/types/temporal-types.js +33 -11
  322. package/dist/src/types/temporal-types.js.map +1 -1
  323. package/dist/src/types/validation.d.ts +39 -1
  324. package/dist/src/types/validation.d.ts.map +1 -1
  325. package/dist/src/types/validation.js +60 -0
  326. package/dist/src/types/validation.js.map +1 -1
  327. package/dist/src/util/ast-spine-clone.d.ts +23 -0
  328. package/dist/src/util/ast-spine-clone.d.ts.map +1 -0
  329. package/dist/src/util/ast-spine-clone.js +41 -0
  330. package/dist/src/util/ast-spine-clone.js.map +1 -0
  331. package/dist/src/util/coercion.d.ts +13 -0
  332. package/dist/src/util/coercion.d.ts.map +1 -1
  333. package/dist/src/util/coercion.js +10 -1
  334. package/dist/src/util/coercion.js.map +1 -1
  335. package/dist/src/util/comparison.d.ts +89 -0
  336. package/dist/src/util/comparison.d.ts.map +1 -1
  337. package/dist/src/util/comparison.js +156 -27
  338. package/dist/src/util/comparison.js.map +1 -1
  339. package/dist/src/vtab/manifest.d.ts +7 -0
  340. package/dist/src/vtab/manifest.d.ts.map +1 -1
  341. package/dist/src/vtab/memory/connection.d.ts +14 -1
  342. package/dist/src/vtab/memory/connection.d.ts.map +1 -1
  343. package/dist/src/vtab/memory/connection.js +20 -2
  344. package/dist/src/vtab/memory/connection.js.map +1 -1
  345. package/dist/src/vtab/memory/layer/base.d.ts +6 -1
  346. package/dist/src/vtab/memory/layer/base.d.ts.map +1 -1
  347. package/dist/src/vtab/memory/layer/base.js +14 -5
  348. package/dist/src/vtab/memory/layer/base.js.map +1 -1
  349. package/dist/src/vtab/memory/layer/connection.d.ts +17 -0
  350. package/dist/src/vtab/memory/layer/connection.d.ts.map +1 -1
  351. package/dist/src/vtab/memory/layer/connection.js +26 -0
  352. package/dist/src/vtab/memory/layer/connection.js.map +1 -1
  353. package/dist/src/vtab/memory/layer/manager.d.ts +96 -13
  354. package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
  355. package/dist/src/vtab/memory/layer/manager.js +595 -119
  356. package/dist/src/vtab/memory/layer/manager.js.map +1 -1
  357. package/dist/src/vtab/memory/layer/plan-filter.d.ts +41 -20
  358. package/dist/src/vtab/memory/layer/plan-filter.d.ts.map +1 -1
  359. package/dist/src/vtab/memory/layer/plan-filter.js +57 -27
  360. package/dist/src/vtab/memory/layer/plan-filter.js.map +1 -1
  361. package/dist/src/vtab/memory/layer/row-convert.d.ts +21 -0
  362. package/dist/src/vtab/memory/layer/row-convert.d.ts.map +1 -0
  363. package/dist/src/vtab/memory/layer/row-convert.js +30 -0
  364. package/dist/src/vtab/memory/layer/row-convert.js.map +1 -0
  365. package/dist/src/vtab/memory/layer/scan-layer.d.ts +6 -3
  366. package/dist/src/vtab/memory/layer/scan-layer.d.ts.map +1 -1
  367. package/dist/src/vtab/memory/layer/scan-layer.js +69 -36
  368. package/dist/src/vtab/memory/layer/scan-layer.js.map +1 -1
  369. package/dist/src/vtab/memory/layer/transaction.d.ts +127 -13
  370. package/dist/src/vtab/memory/layer/transaction.d.ts.map +1 -1
  371. package/dist/src/vtab/memory/layer/transaction.js +214 -65
  372. package/dist/src/vtab/memory/layer/transaction.js.map +1 -1
  373. package/dist/src/vtab/memory/module.d.ts.map +1 -1
  374. package/dist/src/vtab/memory/module.js +9 -2
  375. package/dist/src/vtab/memory/module.js.map +1 -1
  376. package/dist/src/vtab/memory/table.d.ts.map +1 -1
  377. package/dist/src/vtab/memory/table.js +3 -2
  378. package/dist/src/vtab/memory/table.js.map +1 -1
  379. package/dist/src/vtab/memory/types.d.ts +27 -1
  380. package/dist/src/vtab/memory/types.d.ts.map +1 -1
  381. package/dist/src/vtab/memory/types.js +16 -1
  382. package/dist/src/vtab/memory/types.js.map +1 -1
  383. package/dist/src/vtab/memory/utils/primary-key.d.ts +9 -0
  384. package/dist/src/vtab/memory/utils/primary-key.d.ts.map +1 -1
  385. package/dist/src/vtab/memory/utils/primary-key.js +11 -0
  386. package/dist/src/vtab/memory/utils/primary-key.js.map +1 -1
  387. package/dist/src/vtab/module.d.ts +63 -0
  388. package/dist/src/vtab/module.d.ts.map +1 -1
  389. package/dist/src/vtab/table.d.ts +24 -6
  390. package/dist/src/vtab/table.d.ts.map +1 -1
  391. package/dist/src/vtab/table.js.map +1 -1
  392. package/package.json +3 -3
  393. package/dist/src/planner/rules/cache/rule-in-subquery-cache.d.ts +0 -19
  394. package/dist/src/planner/rules/cache/rule-in-subquery-cache.d.ts.map +0 -1
  395. package/dist/src/planner/rules/cache/rule-in-subquery-cache.js +0 -53
  396. package/dist/src/planner/rules/cache/rule-in-subquery-cache.js.map +0 -1
@@ -11,7 +11,9 @@ import { hasNativeEventSupport } from '../../util/event-support.js';
11
11
  import { sqlValueIdentical, compareSqlValuesFast, BINARY_COLLATION } from '../../util/comparison.js';
12
12
  import { validateAndParse } from '../../types/validation.js';
13
13
  import { withAsyncRowContext } from '../context-helpers.js';
14
- import { executeForeignKeyActionsAndLens, assertTransitiveRestrictsForParentMutation } from '../foreign-key-actions.js';
14
+ import { executeForeignKeyActionsAndLens, assertTransitiveRestrictsForParentMutation, createParentRestrictBatch, accumulateParentRestrictKeys, flushParentRestrictBatch } from '../foreign-key-actions.js';
15
+ import { getBatchableRestrictFks } from '../../planner/building/foreign-key-builder.js';
16
+ import { poisonResidualDeltaAccumulations } from '../../core/database-materialized-views-apply.js';
15
17
  /**
16
18
  * Module-scope counter producing unique statement-savepoint names across
17
19
  * concurrent emissions. Names must be unique within the TransactionManager's
@@ -21,36 +23,19 @@ import { executeForeignKeyActionsAndLens, assertTransitiveRestrictsForParentMuta
21
23
  let stmtSavepointCounter = 0;
22
24
  /**
23
25
  * True when a proposed value equals the stored existing value at one conflict-target
24
- * column the way the targeted constraint ENFORCES: the column's affinity is applied to
25
- * the proposed value (which reaches the executor pre-affinity-coercion, while
26
- * `existing` is the already-coerced stored value), then the two are compared under the
27
- * column's precomputed enforcement collation.
26
+ * column the way the targeted constraint ENFORCES: compared under the column's
27
+ * precomputed enforcement collation. Both sides are already in declared (stored)
28
+ * form — `existing` is a stored row and the proposed row was converted by the
29
+ * INSERT emitter's static-type pass — so no per-row conversion happens here.
28
30
  *
29
- * `targetPos` indexes the clause's per-target metadata arrays (which are aligned with
30
- * `conflictTargetIndices`). When that metadata is absent — a defensively-old plan — it
31
- * degrades to BINARY with no coercion, i.e. exactly the byte-identity semantics of
32
- * `sqlValueIdentical`, preserving its numeric-storage-class tolerance via
33
- * {@link compareSqlValuesFast}. A coercion failure likewise falls back to the raw
34
- * proposed value (it then compares unequal and the caller aborts, as before).
31
+ * `targetPos` indexes the clause's per-target collation array (aligned with
32
+ * `conflictTargetIndices`). When absent — a defensively-old plan — it degrades to
33
+ * BINARY, i.e. the byte-identity semantics of `sqlValueIdentical`, preserving its
34
+ * numeric-storage-class tolerance via {@link compareSqlValuesFast}.
35
35
  */
36
36
  function conflictTargetValuesMatch(existing, proposed, clause, targetPos) {
37
- const type = clause.conflictTargetTypes?.[targetPos];
38
37
  const collationFn = clause.conflictTargetCollationFns?.[targetPos] ?? BINARY_COLLATION;
39
- // NOTE: re-coerces the proposed value per conflicting row via validateAndParse; only
40
- // reached on the (cold) UNIQUE-violation-with-upsert-clause path, so it is off the
41
- // happy-path insert. If a workload ever hammers ON CONFLICT on a wide composite target,
42
- // precompute per-target coercion closures at emit instead of dispatching on type here.
43
- let coercedProposed = proposed;
44
- if (type) {
45
- try {
46
- coercedProposed = validateAndParse(proposed, type);
47
- }
48
- catch {
49
- // Coercion failed — compare the raw proposed value (aborts, as pre-fix).
50
- coercedProposed = proposed;
51
- }
52
- }
53
- return compareSqlValuesFast(existing, coercedProposed, collationFn) === 0;
38
+ return compareSqlValuesFast(existing, proposed, collationFn) === 0;
54
39
  }
55
40
  /**
56
41
  * Returns true when the table's owning *module* natively emits data events.
@@ -119,6 +104,44 @@ async function resolveDmlSourceRows(ctx, vtab, tableSchema, rows) {
119
104
  throw e;
120
105
  }
121
106
  }
107
+ /**
108
+ * The row the substrate actually STORED for this write, which is what every
109
+ * post-write consumer must see. The executor's own writes are converted by the
110
+ * emitters up front (and passed with `preCoerced`), so for the standard
111
+ * backends `result.row` and the row we handed in now usually agree — but a
112
+ * module remains free to normalize further (or to ignore `preCoerced` and
113
+ * convert on its own), and `result.row` is its word on what landed.
114
+ *
115
+ * Falls back to the raw row when the substrate's row is the wrong width — a
116
+ * minimal test/sample module that echoes its input in a different shape. The
117
+ * absent case is only reached defensively: every caller has already
118
+ * short-circuited on a missing `result.row`, which the module contract
119
+ * reserves for "nothing was written" (see `VirtualTable.update`).
120
+ */
121
+ function storedRowOrRaw(resultRow, rawRow, columnCount) {
122
+ return resultRow && resultRow.length === columnCount ? resultRow : rawRow;
123
+ }
124
+ /**
125
+ * Rebuild the flat OLD/NEW row with its NEW section (indices n..2n-1) replaced by
126
+ * the substrate's stored row, so `RETURNING new.<col>` reports the same value and
127
+ * `typeof` as a subsequent `select`. Returns `flatRow` untouched when the stored
128
+ * row IS the raw NEW section (the fallback above), avoiding a per-row copy.
129
+ *
130
+ * NOTE: on a coercing substrate this allocates one array per written row (the
131
+ * DML executor previously yielded `flatRow` verbatim). It is one copy against a
132
+ * per-row `vtab.update()` await, so it does not register today; if bulk-INSERT
133
+ * throughput ever shows this as hot, have the substrates report which columns
134
+ * coercion actually changed and patch only those in place.
135
+ */
136
+ function withStoredNewSection(flatRow, storedRow, rawNewRow, columnCount) {
137
+ if (storedRow === rawNewRow)
138
+ return flatRow;
139
+ const out = flatRow.slice();
140
+ for (let i = 0; i < columnCount; i++) {
141
+ out[columnCount + i] = storedRow[i];
142
+ }
143
+ return out;
144
+ }
122
145
  /**
123
146
  * Emit an automatic data change event for modules without native event support.
124
147
  */
@@ -150,13 +173,20 @@ function emitAutoDataEvent(ctx, tableSchema, type, key, oldRow, newRow, changedC
150
173
  *
151
174
  * `deferred` is the per-statement deferred-rebuild set the generator owns: a full-rebuild
152
175
  * covering MV is marked dirty here (not applied per row) and rebuilt once at the
153
- * end-of-statement flush ({@link runWithStatementSavepoints}). Bounded-delta arms stay
154
- * per-row-immediate.
176
+ * end-of-statement flush ({@link runWithStatementSavepoints}).
177
+ *
178
+ * `residualBatch` is the per-statement residual key batch the generator owns: a
179
+ * residual-arm covering MV (`'residual-recompute'` / `'prefix-delete'` /
180
+ * `'join-residual'`) accumulates the changed row's affected binding key(s) here (deduped
181
+ * across the statement) instead of recomputing per row, and each distinct key's residual
182
+ * runs once at the same end-of-statement flush. Only the `'inverse-projection'` arm stays
183
+ * per-row-immediate — the covering-UNIQUE enforcement scan reads its backing
184
+ * mid-statement, and its per-row delta is a cheap pure projection.
155
185
  */
156
- async function maintainRowTimeStructures(ctx, tableKey, change, cache, deferred) {
186
+ async function maintainRowTimeStructures(ctx, tableKey, change, cache, deferred, residualBatch) {
157
187
  if (!ctx.db._hasRowTimeCoveringStructures(tableKey))
158
188
  return;
159
- await ctx.db._maintainRowTimeCoveringStructures(tableKey, change, cache, deferred);
189
+ await ctx.db._maintainRowTimeCoveringStructures(tableKey, change, cache, deferred, residualBatch);
160
190
  }
161
191
  /**
162
192
  * Evaluate the per-statement mutation-context evaluators once, producing the
@@ -238,21 +268,26 @@ export function emitDmlExecutor(plan, ctx) {
238
268
  existingRowDescriptor: clause.existingRowDescriptor,
239
269
  };
240
270
  // Resolve the per-target-column enforcement collation NAMES to functions once
241
- // here (recording the collation dependency so a redefined collation re-emits),
242
- // and carry the per-column logical types for affinity coercion. Consumed by
243
- // matchUpsertClause so a collation-equal / affinity-coerced conflict on the
244
- // targeted constraint routes to DO UPDATE / DO NOTHING instead of aborting.
271
+ // here (recording the collation dependency so a redefined collation re-emits).
272
+ // Consumed by matchUpsertClause so a collation-equal conflict on the targeted
273
+ // constraint routes to DO UPDATE / DO NOTHING instead of aborting.
245
274
  if (clause.conflictTargetIndices && clause.conflictTargetCollations) {
246
275
  runtime.conflictTargetCollationFns = clause.conflictTargetCollations.map(name => ctx.resolveCollation(name ?? 'BINARY'));
247
- runtime.conflictTargetTypes = clause.conflictTargetTypes;
248
276
  }
249
277
  if (clause.action === 'update' && clause.assignments) {
250
278
  runtime.assignmentIndices = new Map();
279
+ runtime.assignmentCoercions = new Map();
251
280
  for (const [colIndex, valueNode] of clause.assignments) {
252
281
  const evaluatorIndex = upsertEvaluatorInstructions.length;
253
282
  const instruction = emitCallFromPlan(valueNode, ctx);
254
283
  upsertEvaluatorInstructions.push(instruction);
255
284
  runtime.assignmentIndices.set(colIndex, evaluatorIndex);
285
+ // Same static-type rule as emitUpdate: convert only when the
286
+ // assignment expression's type is not the column's declared type.
287
+ const column = tableSchema.columns[colIndex];
288
+ if (valueNode.getType().logicalType !== column.logicalType) {
289
+ runtime.assignmentCoercions.set(colIndex, column);
290
+ }
256
291
  }
257
292
  }
258
293
  if (clause.whereCondition) {
@@ -287,15 +322,16 @@ export function emitDmlExecutor(plan, ctx) {
287
322
  }
288
323
  // Match when the proposed values equal the existing row at the clause's
289
324
  // conflict-target columns — i.e. the conflict is on those columns —
290
- // compared the way the constraint ENFORCES: the column's affinity is
291
- // applied to the (pre-affinity-coercion) proposed value, then it is compared
292
- // under the constraint's enforcement collation. This routes a collation-equal
293
- // (NOCASE case-variant, RTRIM trailing-space) or affinity-coerced (`'1'` vs a
294
- // stored INTEGER `1`) conflict on the targeted constraint to the DO UPDATE /
295
- // DO NOTHING arm rather than aborting with a UNIQUE error. The per-column
296
- // collation functions + logical types are precomputed at emit; a BINARY,
297
- // byte-identical key still compares 0 (well-formed seeds re-present
298
- // byte-identical literals, so seed idempotency is unaffected).
325
+ // compared the way the constraint ENFORCES: under the constraint's
326
+ // enforcement collation. Both rows are already in declared (stored)
327
+ // form the INSERT emitter converted the proposed row by static type —
328
+ // so an affinity-coerced conflict (`'1'` proposed into an INTEGER key
329
+ // holding `1`) arrives converted, and a collation-equal conflict (NOCASE
330
+ // case-variant, RTRIM trailing-space) is routed by the collation compare
331
+ // to the DO UPDATE / DO NOTHING arm rather than aborting with a UNIQUE
332
+ // error. The per-column collation functions are precomputed at emit; a
333
+ // BINARY, byte-identical key still compares 0 (well-formed seeds
334
+ // re-present byte-identical literals, so seed idempotency is unaffected).
299
335
  //
300
336
  // NOTE: one residual corner remains out of scope — multi-constraint
301
337
  // coincidence. If an insert violates the targeted constraint AND another
@@ -336,22 +372,31 @@ export function emitDmlExecutor(plan, ctx) {
336
372
  // Evaluate assignment expressions with proper contexts
337
373
  for (const [colIndex, evaluatorIndex] of clause.assignmentIndices) {
338
374
  const evaluator = upsertEvaluators[evaluatorIndex];
339
- const value = await withAsyncRowContext(rctx, clause.existingRowDescriptor, () => existingRow, async () => {
375
+ let value = await withAsyncRowContext(rctx, clause.existingRowDescriptor, () => existingRow, async () => {
340
376
  return await withAsyncRowContext(rctx, clause.newRowDescriptor, () => proposedRow, async () => {
341
377
  return await evaluator(rctx);
342
378
  });
343
379
  });
380
+ // Convert to the declared column type when the assignment's static
381
+ // type requires it (see assignmentCoercions) — `existingRow` is a
382
+ // stored row, so the composed updatedRow is then fully declared-form.
383
+ const coerceColumn = clause.assignmentCoercions?.get(colIndex);
384
+ if (coerceColumn) {
385
+ value = validateAndParse(value, coerceColumn.logicalType, coerceColumn.name);
386
+ }
344
387
  updatedRow[colIndex] = value;
345
388
  }
346
389
  }
347
390
  // Extract the primary key from existing row
348
391
  const keyValues = pkColumnIndicesInSchema.map(idx => existingRow[idx]);
349
- // Perform the UPDATE operation
392
+ // Perform the UPDATE operation. `preCoerced`: the row is composed of
393
+ // stored cells plus assignments already converted above.
350
394
  const updateArgs = {
351
395
  operation: 'update',
352
396
  values: updatedRow,
353
397
  oldKeyValues: keyValues,
354
398
  onConflict: ConflictResolution.ABORT,
399
+ preCoerced: true,
355
400
  mutationStatement: vtab.wantStatements ?
356
401
  buildUpdateStatement(tableSchema, updatedRow, keyValues, contextRow) : undefined
357
402
  };
@@ -362,9 +407,12 @@ export function emitDmlExecutor(plan, ctx) {
362
407
  if (!updateResult.row) {
363
408
  return undefined;
364
409
  }
365
- // Build a flat row for RETURNING (OLD = existing, NEW = updated)
366
- const flatRow = [...existingRow, ...updatedRow];
367
- return { updatedRow, flatRow };
410
+ // Report the row the substrate STORED, not the one we composed — see
411
+ // storedRowOrRaw. `existingRow` is already a stored (coerced) row.
412
+ const storedRow = storedRowOrRaw(updateResult.row, updatedRow, tableSchema.columns.length);
413
+ // Build a flat row for RETURNING (OLD = existing, NEW = stored)
414
+ const flatRow = [...existingRow, ...storedRow];
415
+ return { updatedRow: storedRow, flatRow };
368
416
  }
369
417
  /**
370
418
  * Translate a generic ConstraintError into the right OR-clause subclass
@@ -419,7 +467,12 @@ export function emitDmlExecutor(plan, ctx) {
419
467
  // non-snapshot-isolated UPDATE/DELETE has drained its source up front to
420
468
  // separate the read phase from the write phase. `for await ... of`
421
469
  // consumes either — the savepoint/FAIL-mode logic below is unchanged.
422
- rows, isFailMode, processRow, deferredRebuilds, backingConnCache) {
470
+ rows, isFailMode, processRow, deferredRebuilds, residualBatch, backingConnCache,
471
+ // Set only when the statement uses batched parent-side RESTRICT
472
+ // enforcement (see getBatchableRestrictFks): flushes the accumulated
473
+ // key batch at the end-of-statement boundary. The gate excludes FAIL,
474
+ // so a non-null flush always runs under the statement savepoint.
475
+ flushFkBatch) {
423
476
  let failSavepointCounter = 0;
424
477
  // For non-FAIL modes wrap the whole statement so a mid-statement failure
425
478
  // unwinds partial writes from earlier rows. FAIL keeps prior rows, so it
@@ -464,6 +517,14 @@ export function emitDmlExecutor(plan, ctx) {
464
517
  if (savepointName) {
465
518
  await ctx.db._rollbackAndReleaseSavepointBroadcast(savepointName);
466
519
  savepointName = undefined;
520
+ // The savepoint reverted the failing row's source/backing writes,
521
+ // but the residual batch's JS-side DELTA accumulations cannot be
522
+ // unwound and may carry the reverted row's contribution. The
523
+ // residual KEYS stay valid (a reverted key recomputes from live
524
+ // state to a value-identical row, which the host suppresses), so
525
+ // drop only the delta fast path — the FAIL-mode flush below then
526
+ // routes those entries through the plain residual.
527
+ poisonResidualDeltaAccumulations(residualBatch);
467
528
  }
468
529
  // Translate plain constraint violations to FAIL/ROLLBACK error
469
530
  // subclasses so the iterator-level cleanup picks the right
@@ -477,13 +538,22 @@ export function emitDmlExecutor(plan, ctx) {
477
538
  yield rowToYield;
478
539
  }
479
540
  }
480
- // End-of-statement boundary: drain the deferred full-rebuild set NOW — after
481
- // every source row has been applied (so each rebuild reads all this
482
- // statement's writes) and BEFORE the statement savepoint releases (so a
483
- // failed rebuild rolls the whole statement back). Inside the try, so a flush
484
- // error routes to the rollback branch below. A no-op when nothing deferred.
485
- if (deferredRebuilds.size > 0) {
486
- await ctx.db._flushDeferredRebuilds(deferredRebuilds, backingConnCache);
541
+ // End-of-statement boundary. First the batched parent-side RESTRICT
542
+ // probe (fail fast skip wasted MV work on a violation), then the
543
+ // deferred maintenance; both BEFORE the statement savepoint releases
544
+ // so a failure rolls the whole statement back. Inside the try, so an
545
+ // error routes to the rollback branch below.
546
+ if (flushFkBatch) {
547
+ await flushFkBatch();
548
+ }
549
+ // Drain the deferred maintenance (residual key
550
+ // batch + full-rebuild set) NOW — after every source row has been applied
551
+ // (so each recompute/rebuild reads all this statement's writes) and BEFORE
552
+ // the statement savepoint releases (so a failed flush rolls the whole
553
+ // statement back). Inside the try, so a flush error routes to the rollback
554
+ // branch below. A no-op when nothing deferred.
555
+ if (deferredRebuilds.size > 0 || residualBatch.size > 0) {
556
+ await ctx.db._flushDeferredMaintenance(deferredRebuilds, residualBatch, backingConnCache);
487
557
  }
488
558
  if (stmtSavepointName) {
489
559
  await ctx.db._releaseSavepointBroadcast(stmtSavepointName);
@@ -493,18 +563,20 @@ export function emitDmlExecutor(plan, ctx) {
493
563
  if (stmtSavepointName) {
494
564
  await ctx.db._rollbackAndReleaseSavepointBroadcast(stmtSavepointName);
495
565
  }
496
- else if (deferredRebuilds.size > 0) {
566
+ else if (deferredRebuilds.size > 0 || residualBatch.size > 0) {
497
567
  // OR FAIL keeps the rows that already succeeded (it runs with no
498
568
  // statement-scope savepoint), so a mid-statement abort does NOT unwind
499
- // them. Their deferred full-rebuild MVs must therefore still be flushed
500
- // before the conflict error propagates — otherwise the backing would lag
501
- // the surviving source rows mid-transaction (read(MV) != evaluate(body)).
502
- // The failing row's own per-row savepoint already reverted its writes, so
503
- // the rebuild re-evaluates over exactly the surviving rows. The original
504
- // conflict error is re-thrown after the flush. (A flush failure here is a
505
- // genuine maintenance error and supersedes the conflict error, matching
506
- // "a maintenance error fails the source write".)
507
- await ctx.db._flushDeferredRebuilds(deferredRebuilds, backingConnCache);
569
+ // them. Their deferred residual/full-rebuild MVs must therefore still be
570
+ // flushed before the conflict error propagates — otherwise the backing
571
+ // would lag the surviving source rows mid-transaction
572
+ // (read(MV) != evaluate(body)). The failing row's own per-row savepoint
573
+ // already reverted its writes, so the flush recomputes over exactly the
574
+ // surviving rows (a reverted row's accumulated key is harmless its
575
+ // recompute is value-identical and suppressed). The original conflict
576
+ // error is re-thrown after the flush. (A flush failure here is a genuine
577
+ // maintenance error and supersedes the conflict error, matching "a
578
+ // maintenance error fails the source write".)
579
+ await ctx.db._flushDeferredMaintenance(deferredRebuilds, residualBatch, backingConnCache);
508
580
  }
509
581
  throw e;
510
582
  }
@@ -528,17 +600,19 @@ export function emitDmlExecutor(plan, ctx) {
528
600
  // Per-statement backing-connection cache: resolve each covering MV's backing
529
601
  // connection once for the whole statement rather than once per source row.
530
602
  const backingConnCache = new Map();
531
- // Per-statement deferred full-rebuild set (MV keys): full-rebuild covering MVs
532
- // are marked dirty during the row loop and rebuilt once at the end-of-statement
533
- // flush in runWithStatementSavepoints.
603
+ // Per-statement deferred full-rebuild set (MV keys) + residual key batch:
604
+ // full-rebuild covering MVs are marked dirty and residual-arm covering MVs
605
+ // accumulate their affected binding keys during the row loop; both drain once
606
+ // at the end-of-statement flush in runWithStatementSavepoints.
534
607
  const deferredRebuilds = new Set();
608
+ const residualBatch = new Map();
535
609
  const isFailMode = plan.onConflict === ConflictResolution.FAIL;
536
610
  // Stamp the per-row mutation ordinal so a column `default` referencing
537
611
  // `mutation_ordinal()` (a per-row surrogate allocator) resolves to the 1-based
538
612
  // position of the row being produced. The defaults are evaluated upstream as
539
613
  // each row is *pulled*, so the ordinal is set BEFORE pulling — see
540
614
  // `stampMutationOrdinal`.
541
- yield* runWithStatementSavepoints(ctx, vtab, stampMutationOrdinal(ctx, rows), isFailMode, (flatRow) => processInsertRow(ctx, vtab, needsAutoEvents, flatRow, contextRow, runtimeUpsertClauses, upsertEvaluators, backingConnCache, deferredRebuilds), deferredRebuilds, backingConnCache);
615
+ yield* runWithStatementSavepoints(ctx, vtab, stampMutationOrdinal(ctx, rows), isFailMode, (flatRow) => processInsertRow(ctx, vtab, needsAutoEvents, flatRow, contextRow, runtimeUpsertClauses, upsertEvaluators, backingConnCache, deferredRebuilds, residualBatch), deferredRebuilds, residualBatch, backingConnCache);
542
616
  }
543
617
  /**
544
618
  * Wrap the INSERT source so `rctx.mutationOrdinal` holds the 1-based ordinal of the
@@ -576,7 +650,7 @@ export function emitDmlExecutor(plan, ctx) {
576
650
  *
577
651
  * Throws ConstraintError on violations the executor cannot recover from.
578
652
  */
579
- async function processInsertRow(ctx, vtab, needsAutoEvents, flatRow, contextRow, runtimeUpsertClauses, upsertEvaluators, backingConnCache, deferredRebuilds) {
653
+ async function processInsertRow(ctx, vtab, needsAutoEvents, flatRow, contextRow, runtimeUpsertClauses, upsertEvaluators, backingConnCache, deferredRebuilds, residualBatch) {
580
654
  const newRow = extractNewRowFromFlat(flatRow, tableSchema.columns.length);
581
655
  const tableKey = `${tableSchema.schemaName}.${tableSchema.name}`;
582
656
  let mutationStatement;
@@ -591,6 +665,10 @@ export function emitDmlExecutor(plan, ctx) {
591
665
  // can fall back to per-constraint defaultConflict directives. The memory
592
666
  // module treats undefined as ABORT when no constraint default is set.
593
667
  onConflict: plan.onConflict,
668
+ // The emitters converted the NEW section to declared types up front
669
+ // (emitInsert / constraint-check DEFAULT substitution), so the storage
670
+ // layer must not convert again — JSON conversion is not repeatable.
671
+ preCoerced: true,
594
672
  mutationStatement
595
673
  };
596
674
  const result = await vtab.update(args);
@@ -606,7 +684,7 @@ export function emitDmlExecutor(plan, ctx) {
606
684
  return undefined;
607
685
  const existingKeyValues = pkColumnIndicesInSchema.map(idx => result.existingRow[idx]);
608
686
  ctx.db._recordUpdate(tableKey, result.existingRow, updateResult.updatedRow, pkColumnIndicesInSchema);
609
- await maintainRowTimeStructures(ctx, tableKey, { op: 'update', oldRow: result.existingRow, newRow: updateResult.updatedRow }, backingConnCache, deferredRebuilds);
687
+ await maintainRowTimeStructures(ctx, tableKey, { op: 'update', oldRow: result.existingRow, newRow: updateResult.updatedRow }, backingConnCache, deferredRebuilds, residualBatch);
610
688
  await executeForeignKeyActionsAndLens(ctx.db, tableSchema, 'update', result.existingRow, updateResult.updatedRow, plan.lensRouted);
611
689
  if (needsAutoEvents) {
612
690
  const changedColumns = [];
@@ -627,35 +705,41 @@ export function emitDmlExecutor(plan, ctx) {
627
705
  if (!result.row) {
628
706
  return undefined;
629
707
  }
708
+ // Everything downstream of the write — change tracking, row-time MV
709
+ // maintenance, auto-events, and the row RETURNING projects — must see the
710
+ // row the substrate STORED, not the raw input we handed it. See
711
+ // storedRowOrRaw.
712
+ const numCols = tableSchema.columns.length;
713
+ const storedRow = storedRowOrRaw(result.row, newRow, numCols);
630
714
  // Internal REPLACE evictions (rows at OTHER PKs removed to resolve a non-PK
631
715
  // UNIQUE conflict) run the full delete pipeline here, before the new row's
632
716
  // own bookkeeping — evict-then-write, matching the substrate journal order.
633
- await processEvictions(ctx, needsAutoEvents, tableKey, result.evictedRows, backingConnCache, deferredRebuilds);
717
+ await processEvictions(ctx, needsAutoEvents, tableKey, result.evictedRows, backingConnCache, deferredRebuilds, residualBatch);
634
718
  const replacedRow = result.replacedRow;
635
719
  if (replacedRow) {
636
- const newKeyValues = pkColumnIndicesInSchema.map(idx => newRow[idx]);
637
- ctx.db._recordUpdate(tableKey, replacedRow, newRow, pkColumnIndicesInSchema);
638
- await maintainRowTimeStructures(ctx, tableKey, { op: 'update', oldRow: replacedRow, newRow }, backingConnCache, deferredRebuilds);
720
+ const newKeyValues = pkColumnIndicesInSchema.map(idx => storedRow[idx]);
721
+ ctx.db._recordUpdate(tableKey, replacedRow, storedRow, pkColumnIndicesInSchema);
722
+ await maintainRowTimeStructures(ctx, tableKey, { op: 'update', oldRow: replacedRow, newRow: storedRow }, backingConnCache, deferredRebuilds, residualBatch);
639
723
  await executeForeignKeyActionsAndLens(ctx.db, tableSchema, 'delete', replacedRow, undefined, plan.lensRouted);
640
724
  if (needsAutoEvents) {
641
725
  const changedColumns = [];
642
- for (let i = 0; i < tableSchema.columns.length; i++) {
643
- if (!sqlValueIdentical(replacedRow[i], newRow[i])) {
726
+ for (let i = 0; i < numCols; i++) {
727
+ if (!sqlValueIdentical(replacedRow[i], storedRow[i])) {
644
728
  changedColumns.push(tableSchema.columns[i].name);
645
729
  }
646
730
  }
647
- emitAutoDataEvent(ctx, tableSchema, 'update', newKeyValues, [...replacedRow], [...newRow], changedColumns);
731
+ emitAutoDataEvent(ctx, tableSchema, 'update', newKeyValues, [...replacedRow], [...storedRow], changedColumns);
648
732
  }
649
733
  }
650
734
  else {
651
- const pkValues = pkColumnIndicesInSchema.map(idx => newRow[idx]);
652
- ctx.db._recordInsert(tableKey, newRow, pkColumnIndicesInSchema);
653
- await maintainRowTimeStructures(ctx, tableKey, { op: 'insert', newRow }, backingConnCache, deferredRebuilds);
735
+ const pkValues = pkColumnIndicesInSchema.map(idx => storedRow[idx]);
736
+ ctx.db._recordInsert(tableKey, storedRow, pkColumnIndicesInSchema);
737
+ await maintainRowTimeStructures(ctx, tableKey, { op: 'insert', newRow: storedRow }, backingConnCache, deferredRebuilds, residualBatch);
654
738
  if (needsAutoEvents) {
655
- emitAutoDataEvent(ctx, tableSchema, 'insert', pkValues, undefined, [...newRow]);
739
+ emitAutoDataEvent(ctx, tableSchema, 'insert', pkValues, undefined, [...storedRow]);
656
740
  }
657
741
  }
658
- return flatRow;
742
+ return withStoredNewSection(flatRow, storedRow, newRow, numCols);
659
743
  }
660
744
  /**
661
745
  * Drive the full delete pipeline for every internal REPLACE eviction reported
@@ -671,7 +755,7 @@ export function emitDmlExecutor(plan, ctx) {
671
755
  * evict-then-write order (a later same-key backing write must not be undone by
672
756
  * an earlier-PK eviction's delete).
673
757
  */
674
- async function processEvictions(ctx, needsAutoEvents, tableKey, evictedRows, backingConnCache, deferredRebuilds) {
758
+ async function processEvictions(ctx, needsAutoEvents, tableKey, evictedRows, backingConnCache, deferredRebuilds, residualBatch) {
675
759
  if (!evictedRows || evictedRows.length === 0)
676
760
  return;
677
761
  for (const evicted of evictedRows) {
@@ -690,7 +774,7 @@ export function emitDmlExecutor(plan, ctx) {
690
774
  await assertTransitiveRestrictsForParentMutation(ctx.db, tableSchema, 'delete', evicted);
691
775
  const evictedKeyValues = pkColumnIndicesInSchema.map(idx => evicted[idx]);
692
776
  ctx.db._recordDelete(tableKey, evicted, pkColumnIndicesInSchema);
693
- await maintainRowTimeStructures(ctx, tableKey, { op: 'delete', oldRow: evicted }, backingConnCache, deferredRebuilds);
777
+ await maintainRowTimeStructures(ctx, tableKey, { op: 'delete', oldRow: evicted }, backingConnCache, deferredRebuilds, residualBatch);
694
778
  await executeForeignKeyActionsAndLens(ctx.db, tableSchema, 'delete', evicted);
695
779
  if (needsAutoEvents) {
696
780
  emitAutoDataEvent(ctx, tableSchema, 'delete', evictedKeyValues, [...evicted]);
@@ -704,10 +788,20 @@ export function emitDmlExecutor(plan, ctx) {
704
788
  const vtab = await getVTable(ctx, tableSchema);
705
789
  const needsAutoEvents = ctx.db._needsDataEvents() && !moduleHasNativeDataEvents(ctx, tableSchema);
706
790
  const contextRow = await evaluateContextRow(ctx, contextEvaluators);
707
- // Per-statement backing-connection cache + deferred full-rebuild set (see runInsert).
791
+ // Per-statement backing-connection cache + deferred full-rebuild set + residual
792
+ // key batch (see runInsert).
708
793
  const backingConnCache = new Map();
709
794
  const deferredRebuilds = new Set();
795
+ const residualBatch = new Map();
710
796
  const isFailMode = plan.onConflict === ConflictResolution.FAIL;
797
+ // Batched parent-side RESTRICT enforcement (per-execution state, never on
798
+ // the emit closure — a re-run prepared statement must start empty). When
799
+ // the gate admits the statement, the per-row transitive pre-walk is
800
+ // replaced by key accumulation + one chunked probe per FK at the
801
+ // end-of-statement boundary. The plan side made the same gate decision
802
+ // and skipped the per-row NOT EXISTS checks.
803
+ const batchableFks = getBatchableRestrictFks(ctx.db.schemaManager, tableSchema, 'update', plan.onConflict, plan.lensRouted);
804
+ const fkRestrictBatch = batchableFks ? createParentRestrictBatch(batchableFks) : undefined;
711
805
  // Physical Halloween avoidance: unless the target module guarantees per-scan
712
806
  // snapshot isolation, fully drain the source match set (closing the scan
713
807
  // cursor) BEFORE applying any write — otherwise the first UPDATE invalidates
@@ -717,7 +811,7 @@ export function emitDmlExecutor(plan, ctx) {
717
811
  // the accepted cost of correctness (such a module cannot safely stream-update
718
812
  // anyway); memory tables set scanSnapshotIsolation and keep streaming.
719
813
  const sourceRows = await resolveDmlSourceRows(ctx, vtab, tableSchema, rows);
720
- yield* runWithStatementSavepoints(ctx, vtab, sourceRows, isFailMode, (flatRow) => processUpdateRow(ctx, vtab, needsAutoEvents, flatRow, contextRow, backingConnCache, deferredRebuilds), deferredRebuilds, backingConnCache);
814
+ yield* runWithStatementSavepoints(ctx, vtab, sourceRows, isFailMode, (flatRow) => processUpdateRow(ctx, vtab, needsAutoEvents, flatRow, contextRow, backingConnCache, deferredRebuilds, residualBatch, fkRestrictBatch), deferredRebuilds, residualBatch, backingConnCache, fkRestrictBatch ? () => flushParentRestrictBatch(ctx.db, tableSchema, 'update', fkRestrictBatch) : undefined);
721
815
  }
722
816
  /**
723
817
  * Performs a single row's UPDATE side-effects (RESTRICT pre-check +
@@ -727,7 +821,7 @@ export function emitDmlExecutor(plan, ctx) {
727
821
  * cannot recover from; the caller (runWithStatementSavepoints) translates it
728
822
  * for FAIL/ROLLBACK modes.
729
823
  */
730
- async function processUpdateRow(ctx, vtab, needsAutoEvents, flatRow, contextRow, backingConnCache, deferredRebuilds) {
824
+ async function processUpdateRow(ctx, vtab, needsAutoEvents, flatRow, contextRow, backingConnCache, deferredRebuilds, residualBatch, fkRestrictBatch) {
731
825
  const oldRow = extractOldRowFromFlat(flatRow, tableSchema.columns.length);
732
826
  const newRow = extractNewRowFromFlat(flatRow, tableSchema.columns.length);
733
827
  const tableKey = `${tableSchema.schemaName}.${tableSchema.name}`;
@@ -743,14 +837,23 @@ export function emitDmlExecutor(plan, ctx) {
743
837
  if (vtab.wantStatements) {
744
838
  mutationStatement = buildUpdateStatement(tableSchema, newRow, keyValues, contextRow);
745
839
  }
746
- // Defense-in-depth RESTRICT enforcement: the plan-time `NOT EXISTS`
747
- // check is the primary path, but some vtab modules evaluate the
748
- // embedded subquery differently from a plain row scan. Pre-walk
749
- // the transitive cascade closure so RESTRICTs at any depth fire
750
- // BEFORE vtab.updateneeded for rowid-mode backends (lamina)
751
- // where post-mutation OLD-value scans dereference through the
752
- // just-mutated parent and find zero rows.
753
- await assertTransitiveRestrictsForParentMutation(ctx.db, tableSchema, 'update', oldRow, newRow, plan.lensRouted);
840
+ // Parent-side RESTRICT enforcement. Batched statements accumulate the
841
+ // OLD referenced keys (probed once per FK at the end-of-statement
842
+ // boundary same pre-vtab observation point per row, so a row the vtab
843
+ // later reports not-found contributes identically to the per-row path).
844
+ // Otherwise: defense-in-depth per-row pre-walk the plan-time
845
+ // `NOT EXISTS` check is the primary path, but some vtab modules evaluate
846
+ // the embedded subquery differently from a plain row scan. Pre-walk the
847
+ // transitive cascade closure so RESTRICTs at any depth fire BEFORE
848
+ // vtab.update — needed for rowid-mode backends (lamina) where
849
+ // post-mutation OLD-value scans dereference through the just-mutated
850
+ // parent and find zero rows.
851
+ if (fkRestrictBatch) {
852
+ accumulateParentRestrictKeys(fkRestrictBatch, 'update', oldRow, newRow);
853
+ }
854
+ else {
855
+ await assertTransitiveRestrictsForParentMutation(ctx.db, tableSchema, 'update', oldRow, newRow, plan.lensRouted);
856
+ }
754
857
  const args = {
755
858
  operation: 'update',
756
859
  values: newRow,
@@ -759,6 +862,10 @@ export function emitDmlExecutor(plan, ctx) {
759
862
  // can fall back to per-constraint defaultConflict directives. The memory
760
863
  // module treats undefined as ABORT when no constraint default is set.
761
864
  onConflict: plan.onConflict,
865
+ // emitUpdate converted the NEW section to declared types (assigned
866
+ // columns by static type, unassigned columns are stored values) — the
867
+ // storage layer must not convert again.
868
+ preCoerced: true,
762
869
  mutationStatement
763
870
  };
764
871
  const result = await vtab.update(args);
@@ -770,6 +877,11 @@ export function emitDmlExecutor(plan, ctx) {
770
877
  if (!result.row) {
771
878
  return undefined;
772
879
  }
880
+ // Post-write bookkeeping and the RETURNING row must see the row the
881
+ // substrate STORED, not the raw input — see storedRowOrRaw. `oldRow` came
882
+ // from the source scan, so it is already a stored (coerced) row.
883
+ const numCols = tableSchema.columns.length;
884
+ const storedRow = storedRowOrRaw(result.row, newRow, numCols);
773
885
  // If the UPDATE moved this row onto an occupied PK under REPLACE,
774
886
  // the vtab returns the displaced row. Surface its deletion BEFORE
775
887
  // the move bookkeeping so change tracking, FK cascade, and auto-events
@@ -781,7 +893,7 @@ export function emitDmlExecutor(plan, ctx) {
781
893
  if (result.replacedRow) {
782
894
  const evictedKeyValues = pkColumnIndicesInSchema.map(idx => result.replacedRow[idx]);
783
895
  ctx.db._recordDelete(tableKey, result.replacedRow, pkColumnIndicesInSchema);
784
- await maintainRowTimeStructures(ctx, tableKey, { op: 'delete', oldRow: result.replacedRow }, backingConnCache, deferredRebuilds);
896
+ await maintainRowTimeStructures(ctx, tableKey, { op: 'delete', oldRow: result.replacedRow }, backingConnCache, deferredRebuilds, residualBatch);
785
897
  await executeForeignKeyActionsAndLens(ctx.db, tableSchema, 'delete', result.replacedRow, undefined, plan.lensRouted);
786
898
  if (needsAutoEvents) {
787
899
  emitAutoDataEvent(ctx, tableSchema, 'delete', evictedKeyValues, [...result.replacedRow]);
@@ -791,27 +903,27 @@ export function emitDmlExecutor(plan, ctx) {
791
903
  // UNIQUE conflict for this same update) run the full delete pipeline here,
792
904
  // after any same-PK replacedRow handling and before the moved row's own
793
905
  // bookkeeping — evict-then-write, matching the substrate journal order.
794
- await processEvictions(ctx, needsAutoEvents, tableKey, result.evictedRows, backingConnCache, deferredRebuilds);
906
+ await processEvictions(ctx, needsAutoEvents, tableKey, result.evictedRows, backingConnCache, deferredRebuilds, residualBatch);
795
907
  // Track change (UPDATE): pass full rows so the change capture can
796
908
  // project the columns any active subscription cares about.
797
- ctx.db._recordUpdate(tableKey, oldRow, newRow, pkColumnIndicesInSchema);
798
- await maintainRowTimeStructures(ctx, tableKey, { op: 'update', oldRow, newRow }, backingConnCache, deferredRebuilds);
909
+ ctx.db._recordUpdate(tableKey, oldRow, storedRow, pkColumnIndicesInSchema);
910
+ await maintainRowTimeStructures(ctx, tableKey, { op: 'update', oldRow, newRow: storedRow }, backingConnCache, deferredRebuilds, residualBatch);
799
911
  // Execute FK cascading actions (CASCADE, SET NULL, SET DEFAULT)
800
- await executeForeignKeyActionsAndLens(ctx.db, tableSchema, 'update', oldRow, newRow, plan.lensRouted);
912
+ await executeForeignKeyActionsAndLens(ctx.db, tableSchema, 'update', oldRow, storedRow, plan.lensRouted);
801
913
  // Emit auto event for modules without native event support
802
914
  if (needsAutoEvents) {
803
915
  // Compute changed columns
804
916
  const changedColumns = [];
805
- for (let i = 0; i < tableSchema.columns.length; i++) {
917
+ for (let i = 0; i < numCols; i++) {
806
918
  const oldVal = oldRow[i];
807
- const newVal = newRow[i];
919
+ const newVal = storedRow[i];
808
920
  if (!sqlValueIdentical(oldVal, newVal)) {
809
921
  changedColumns.push(tableSchema.columns[i].name);
810
922
  }
811
923
  }
812
- emitAutoDataEvent(ctx, tableSchema, 'update', keyValues, [...oldRow], [...newRow], changedColumns);
924
+ emitAutoDataEvent(ctx, tableSchema, 'update', keyValues, [...oldRow], [...storedRow], changedColumns);
813
925
  }
814
- return flatRow;
926
+ return withStoredNewSection(flatRow, storedRow, newRow, numCols);
815
927
  }
816
928
  // DELETE ----------------------------------------------------
817
929
  async function* runDelete(ctx, rows, ...contextEvaluators) {
@@ -820,16 +932,23 @@ export function emitDmlExecutor(plan, ctx) {
820
932
  const vtab = await getVTable(ctx, tableSchema);
821
933
  const needsAutoEvents = ctx.db._needsDataEvents() && !moduleHasNativeDataEvents(ctx, tableSchema);
822
934
  const contextRow = await evaluateContextRow(ctx, contextEvaluators);
823
- // Per-statement backing-connection cache + deferred full-rebuild set (see runInsert).
935
+ // Per-statement backing-connection cache + deferred full-rebuild set + residual
936
+ // key batch (see runInsert).
824
937
  const backingConnCache = new Map();
825
938
  const deferredRebuilds = new Set();
939
+ const residualBatch = new Map();
826
940
  const isFailMode = plan.onConflict === ConflictResolution.FAIL;
941
+ // Batched parent-side RESTRICT enforcement — see the matching note in
942
+ // runUpdate (per-execution state; one chunked probe per FK at the
943
+ // end-of-statement boundary when the gate admits the statement).
944
+ const batchableFks = getBatchableRestrictFks(ctx.db.schemaManager, tableSchema, 'delete', plan.onConflict, plan.lensRouted);
945
+ const fkRestrictBatch = batchableFks ? createParentRestrictBatch(batchableFks) : undefined;
827
946
  // Physical Halloween avoidance — see the matching note in runUpdate. Unless
828
947
  // the target module guarantees per-scan snapshot isolation, drain the source
829
948
  // match set (closing the scan cursor) before applying any DELETE, so the
830
949
  // first delete cannot invalidate the cursor path the scan is still walking.
831
950
  const sourceRows = await resolveDmlSourceRows(ctx, vtab, tableSchema, rows);
832
- yield* runWithStatementSavepoints(ctx, vtab, sourceRows, isFailMode, (flatRow) => processDeleteRow(ctx, vtab, needsAutoEvents, flatRow, contextRow, backingConnCache, deferredRebuilds), deferredRebuilds, backingConnCache);
951
+ yield* runWithStatementSavepoints(ctx, vtab, sourceRows, isFailMode, (flatRow) => processDeleteRow(ctx, vtab, needsAutoEvents, flatRow, contextRow, backingConnCache, deferredRebuilds, residualBatch, fkRestrictBatch), deferredRebuilds, residualBatch, backingConnCache, fkRestrictBatch ? () => flushParentRestrictBatch(ctx.db, tableSchema, 'delete', fkRestrictBatch) : undefined);
833
952
  }
834
953
  /**
835
954
  * Performs a single row's DELETE side-effects (RESTRICT pre-check +
@@ -839,7 +958,7 @@ export function emitDmlExecutor(plan, ctx) {
839
958
  * (runWithStatementSavepoints) translates constraint violations for
840
959
  * FAIL/ROLLBACK modes.
841
960
  */
842
- async function processDeleteRow(ctx, vtab, needsAutoEvents, flatRow, contextRow, backingConnCache, deferredRebuilds) {
961
+ async function processDeleteRow(ctx, vtab, needsAutoEvents, flatRow, contextRow, backingConnCache, deferredRebuilds, residualBatch, fkRestrictBatch) {
843
962
  const oldRow = extractOldRowFromFlat(flatRow, tableSchema.columns.length);
844
963
  const tableKey = `${tableSchema.schemaName}.${tableSchema.name}`;
845
964
  const keyValues = pkColumnIndicesInSchema.map(pkColIdx => {
@@ -853,14 +972,21 @@ export function emitDmlExecutor(plan, ctx) {
853
972
  if (vtab.wantStatements) {
854
973
  mutationStatement = buildDeleteStatement(tableSchema, keyValues, contextRow);
855
974
  }
856
- // Defense-in-depth RESTRICT enforcement see comment on the UPDATE
857
- // path above.
858
- await assertTransitiveRestrictsForParentMutation(ctx.db, tableSchema, 'delete', oldRow, undefined, plan.lensRouted);
975
+ // Parent-side RESTRICT enforcement: batched key accumulation, or the
976
+ // per-row defense-in-depth pre-walk — see comment on the UPDATE path above.
977
+ if (fkRestrictBatch) {
978
+ accumulateParentRestrictKeys(fkRestrictBatch, 'delete', oldRow);
979
+ }
980
+ else {
981
+ await assertTransitiveRestrictsForParentMutation(ctx.db, tableSchema, 'delete', oldRow, undefined, plan.lensRouted);
982
+ }
859
983
  const args = {
860
984
  operation: 'delete',
861
985
  values: undefined,
862
986
  oldKeyValues: keyValues,
863
987
  onConflict: plan.onConflict ?? ConflictResolution.ABORT,
988
+ // The key cells come from the scanned OLD row — already stored form.
989
+ preCoerced: true,
864
990
  mutationStatement
865
991
  };
866
992
  const result = await vtab.update(args);
@@ -875,7 +1001,7 @@ export function emitDmlExecutor(plan, ctx) {
875
1001
  // Track change (DELETE): record OLD row + PK indices so capture
876
1002
  // can project the columns subscribers care about.
877
1003
  ctx.db._recordDelete(tableKey, oldRow, pkColumnIndicesInSchema);
878
- await maintainRowTimeStructures(ctx, tableKey, { op: 'delete', oldRow }, backingConnCache, deferredRebuilds);
1004
+ await maintainRowTimeStructures(ctx, tableKey, { op: 'delete', oldRow }, backingConnCache, deferredRebuilds, residualBatch);
879
1005
  // Execute FK cascading actions (CASCADE, SET NULL, SET DEFAULT)
880
1006
  await executeForeignKeyActionsAndLens(ctx.db, tableSchema, 'delete', oldRow, undefined, plan.lensRouted);
881
1007
  // Emit auto event for modules without native event support