@quereus/quereus 3.2.1 → 4.0.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 (935) hide show
  1. package/README.md +7 -0
  2. package/dist/src/common/datatype.d.ts +12 -0
  3. package/dist/src/common/datatype.d.ts.map +1 -1
  4. package/dist/src/common/datatype.js.map +1 -1
  5. package/dist/src/common/types.d.ts +24 -0
  6. package/dist/src/common/types.d.ts.map +1 -1
  7. package/dist/src/common/types.js.map +1 -1
  8. package/dist/src/core/database-assertions.d.ts +37 -9
  9. package/dist/src/core/database-assertions.d.ts.map +1 -1
  10. package/dist/src/core/database-assertions.js +62 -106
  11. package/dist/src/core/database-assertions.js.map +1 -1
  12. package/dist/src/core/database-events.d.ts +163 -0
  13. package/dist/src/core/database-events.d.ts.map +1 -1
  14. package/dist/src/core/database-events.js +235 -21
  15. package/dist/src/core/database-events.js.map +1 -1
  16. package/dist/src/core/database-external-changes.d.ts +28 -0
  17. package/dist/src/core/database-external-changes.d.ts.map +1 -0
  18. package/dist/src/core/database-external-changes.js +242 -0
  19. package/dist/src/core/database-external-changes.js.map +1 -0
  20. package/dist/src/core/database-internal.d.ts +50 -1
  21. package/dist/src/core/database-internal.d.ts.map +1 -1
  22. package/dist/src/core/database-materialized-views.d.ts +1253 -0
  23. package/dist/src/core/database-materialized-views.d.ts.map +1 -0
  24. package/dist/src/core/database-materialized-views.js +3064 -0
  25. package/dist/src/core/database-materialized-views.js.map +1 -0
  26. package/dist/src/core/database-options.d.ts +4 -0
  27. package/dist/src/core/database-options.d.ts.map +1 -1
  28. package/dist/src/core/database-options.js +10 -0
  29. package/dist/src/core/database-options.js.map +1 -1
  30. package/dist/src/core/database-transaction.d.ts +19 -3
  31. package/dist/src/core/database-transaction.d.ts.map +1 -1
  32. package/dist/src/core/database-transaction.js +30 -3
  33. package/dist/src/core/database-transaction.js.map +1 -1
  34. package/dist/src/core/database-watchers.d.ts +19 -0
  35. package/dist/src/core/database-watchers.d.ts.map +1 -1
  36. package/dist/src/core/database-watchers.js +63 -3
  37. package/dist/src/core/database-watchers.js.map +1 -1
  38. package/dist/src/core/database.d.ts +203 -11
  39. package/dist/src/core/database.d.ts.map +1 -1
  40. package/dist/src/core/database.js +493 -29
  41. package/dist/src/core/database.js.map +1 -1
  42. package/dist/src/core/derived-row-validator.d.ts +137 -0
  43. package/dist/src/core/derived-row-validator.d.ts.map +1 -0
  44. package/dist/src/core/derived-row-validator.js +314 -0
  45. package/dist/src/core/derived-row-validator.js.map +1 -0
  46. package/dist/src/core/statement.d.ts.map +1 -1
  47. package/dist/src/core/statement.js +30 -9
  48. package/dist/src/core/statement.js.map +1 -1
  49. package/dist/src/emit/ast-stringify.d.ts +135 -1
  50. package/dist/src/emit/ast-stringify.d.ts.map +1 -1
  51. package/dist/src/emit/ast-stringify.js +795 -120
  52. package/dist/src/emit/ast-stringify.js.map +1 -1
  53. package/dist/src/func/builtins/aggregate.d.ts.map +1 -1
  54. package/dist/src/func/builtins/aggregate.js +11 -10
  55. package/dist/src/func/builtins/aggregate.js.map +1 -1
  56. package/dist/src/func/builtins/builtin-window-functions.d.ts.map +1 -1
  57. package/dist/src/func/builtins/builtin-window-functions.js +32 -0
  58. package/dist/src/func/builtins/builtin-window-functions.js.map +1 -1
  59. package/dist/src/func/builtins/explain.d.ts +3 -0
  60. package/dist/src/func/builtins/explain.d.ts.map +1 -1
  61. package/dist/src/func/builtins/explain.js +229 -0
  62. package/dist/src/func/builtins/explain.js.map +1 -1
  63. package/dist/src/func/builtins/index.d.ts.map +1 -1
  64. package/dist/src/func/builtins/index.js +10 -2
  65. package/dist/src/func/builtins/index.js.map +1 -1
  66. package/dist/src/func/builtins/json.d.ts.map +1 -1
  67. package/dist/src/func/builtins/json.js +3 -2
  68. package/dist/src/func/builtins/json.js.map +1 -1
  69. package/dist/src/func/builtins/mutation.d.ts +2 -0
  70. package/dist/src/func/builtins/mutation.d.ts.map +1 -0
  71. package/dist/src/func/builtins/mutation.js +53 -0
  72. package/dist/src/func/builtins/mutation.js.map +1 -0
  73. package/dist/src/func/builtins/schema.d.ts +2 -0
  74. package/dist/src/func/builtins/schema.d.ts.map +1 -1
  75. package/dist/src/func/builtins/schema.js +713 -26
  76. package/dist/src/func/builtins/schema.js.map +1 -1
  77. package/dist/src/func/builtins/string.js +1 -1
  78. package/dist/src/func/builtins/string.js.map +1 -1
  79. package/dist/src/func/registration.d.ts +9 -0
  80. package/dist/src/func/registration.d.ts.map +1 -1
  81. package/dist/src/func/registration.js +4 -0
  82. package/dist/src/func/registration.js.map +1 -1
  83. package/dist/src/index.d.ts +25 -6
  84. package/dist/src/index.d.ts.map +1 -1
  85. package/dist/src/index.js +27 -3
  86. package/dist/src/index.js.map +1 -1
  87. package/dist/src/parser/ast.d.ts +353 -21
  88. package/dist/src/parser/ast.d.ts.map +1 -1
  89. package/dist/src/parser/index.d.ts +14 -1
  90. package/dist/src/parser/index.d.ts.map +1 -1
  91. package/dist/src/parser/index.js +19 -0
  92. package/dist/src/parser/index.js.map +1 -1
  93. package/dist/src/parser/lexer.d.ts +9 -0
  94. package/dist/src/parser/lexer.d.ts.map +1 -1
  95. package/dist/src/parser/lexer.js +9 -0
  96. package/dist/src/parser/lexer.js.map +1 -1
  97. package/dist/src/parser/parser.d.ts +277 -8
  98. package/dist/src/parser/parser.d.ts.map +1 -1
  99. package/dist/src/parser/parser.js +1393 -471
  100. package/dist/src/parser/parser.js.map +1 -1
  101. package/dist/src/parser/visitor.d.ts.map +1 -1
  102. package/dist/src/parser/visitor.js +12 -8
  103. package/dist/src/parser/visitor.js.map +1 -1
  104. package/dist/src/planner/analysis/assertion-classifier.d.ts.map +1 -1
  105. package/dist/src/planner/analysis/assertion-classifier.js +4 -0
  106. package/dist/src/planner/analysis/assertion-classifier.js.map +1 -1
  107. package/dist/src/planner/analysis/assertion-hoist-cache.d.ts.map +1 -1
  108. package/dist/src/planner/analysis/assertion-hoist-cache.js +8 -4
  109. package/dist/src/planner/analysis/assertion-hoist-cache.js.map +1 -1
  110. package/dist/src/planner/analysis/authored-inverse.d.ts +22 -0
  111. package/dist/src/planner/analysis/authored-inverse.d.ts.map +1 -0
  112. package/dist/src/planner/analysis/authored-inverse.js +267 -0
  113. package/dist/src/planner/analysis/authored-inverse.js.map +1 -0
  114. package/dist/src/planner/analysis/binding-extractor.d.ts.map +1 -1
  115. package/dist/src/planner/analysis/binding-extractor.js +9 -6
  116. package/dist/src/planner/analysis/binding-extractor.js.map +1 -1
  117. package/dist/src/planner/analysis/change-scope.d.ts +34 -4
  118. package/dist/src/planner/analysis/change-scope.d.ts.map +1 -1
  119. package/dist/src/planner/analysis/change-scope.js +115 -7
  120. package/dist/src/planner/analysis/change-scope.js.map +1 -1
  121. package/dist/src/planner/analysis/check-extraction.d.ts +36 -2
  122. package/dist/src/planner/analysis/check-extraction.d.ts.map +1 -1
  123. package/dist/src/planner/analysis/check-extraction.js +174 -46
  124. package/dist/src/planner/analysis/check-extraction.js.map +1 -1
  125. package/dist/src/planner/analysis/coarsened-key.d.ts +109 -0
  126. package/dist/src/planner/analysis/coarsened-key.d.ts.map +1 -0
  127. package/dist/src/planner/analysis/coarsened-key.js +228 -0
  128. package/dist/src/planner/analysis/coarsened-key.js.map +1 -0
  129. package/dist/src/planner/analysis/comparison-collation.d.ts +216 -0
  130. package/dist/src/planner/analysis/comparison-collation.d.ts.map +1 -0
  131. package/dist/src/planner/analysis/comparison-collation.js +341 -0
  132. package/dist/src/planner/analysis/comparison-collation.js.map +1 -0
  133. package/dist/src/planner/analysis/constraint-extractor.d.ts +13 -1
  134. package/dist/src/planner/analysis/constraint-extractor.d.ts.map +1 -1
  135. package/dist/src/planner/analysis/constraint-extractor.js +220 -21
  136. package/dist/src/planner/analysis/constraint-extractor.js.map +1 -1
  137. package/dist/src/planner/analysis/coverage-prover.d.ts +321 -0
  138. package/dist/src/planner/analysis/coverage-prover.d.ts.map +1 -0
  139. package/dist/src/planner/analysis/coverage-prover.js +1038 -0
  140. package/dist/src/planner/analysis/coverage-prover.js.map +1 -0
  141. package/dist/src/planner/analysis/key-filter.d.ts +22 -0
  142. package/dist/src/planner/analysis/key-filter.d.ts.map +1 -0
  143. package/dist/src/planner/analysis/key-filter.js +105 -0
  144. package/dist/src/planner/analysis/key-filter.js.map +1 -0
  145. package/dist/src/planner/analysis/partial-unique-extraction.d.ts +36 -1
  146. package/dist/src/planner/analysis/partial-unique-extraction.d.ts.map +1 -1
  147. package/dist/src/planner/analysis/partial-unique-extraction.js +148 -22
  148. package/dist/src/planner/analysis/partial-unique-extraction.js.map +1 -1
  149. package/dist/src/planner/analysis/predicate-normalizer.d.ts.map +1 -1
  150. package/dist/src/planner/analysis/predicate-normalizer.js +30 -1
  151. package/dist/src/planner/analysis/predicate-normalizer.js.map +1 -1
  152. package/dist/src/planner/analysis/predicate-shape.d.ts +36 -1
  153. package/dist/src/planner/analysis/predicate-shape.d.ts.map +1 -1
  154. package/dist/src/planner/analysis/predicate-shape.js +51 -13
  155. package/dist/src/planner/analysis/predicate-shape.js.map +1 -1
  156. package/dist/src/planner/analysis/query-rewrite-matcher.d.ts +314 -0
  157. package/dist/src/planner/analysis/query-rewrite-matcher.d.ts.map +1 -0
  158. package/dist/src/planner/analysis/query-rewrite-matcher.js +1081 -0
  159. package/dist/src/planner/analysis/query-rewrite-matcher.js.map +1 -0
  160. package/dist/src/planner/analysis/scalar-invertibility.d.ts +92 -0
  161. package/dist/src/planner/analysis/scalar-invertibility.d.ts.map +1 -0
  162. package/dist/src/planner/analysis/scalar-invertibility.js +129 -0
  163. package/dist/src/planner/analysis/scalar-invertibility.js.map +1 -0
  164. package/dist/src/planner/analysis/update-lineage.d.ts +196 -0
  165. package/dist/src/planner/analysis/update-lineage.d.ts.map +1 -0
  166. package/dist/src/planner/analysis/update-lineage.js +322 -0
  167. package/dist/src/planner/analysis/update-lineage.js.map +1 -0
  168. package/dist/src/planner/analysis/view-complement.d.ts +42 -0
  169. package/dist/src/planner/analysis/view-complement.d.ts.map +1 -0
  170. package/dist/src/planner/analysis/view-complement.js +54 -0
  171. package/dist/src/planner/analysis/view-complement.js.map +1 -0
  172. package/dist/src/planner/building/alter-table.d.ts +1 -1
  173. package/dist/src/planner/building/alter-table.d.ts.map +1 -1
  174. package/dist/src/planner/building/alter-table.js +211 -2
  175. package/dist/src/planner/building/alter-table.js.map +1 -1
  176. package/dist/src/planner/building/block.d.ts.map +1 -1
  177. package/dist/src/planner/building/block.js +18 -1
  178. package/dist/src/planner/building/block.js.map +1 -1
  179. package/dist/src/planner/building/constraint-builder.d.ts +33 -5
  180. package/dist/src/planner/building/constraint-builder.d.ts.map +1 -1
  181. package/dist/src/planner/building/constraint-builder.js +63 -28
  182. package/dist/src/planner/building/constraint-builder.js.map +1 -1
  183. package/dist/src/planner/building/create-view.d.ts +9 -0
  184. package/dist/src/planner/building/create-view.d.ts.map +1 -1
  185. package/dist/src/planner/building/create-view.js +41 -12
  186. package/dist/src/planner/building/create-view.js.map +1 -1
  187. package/dist/src/planner/building/ddl.d.ts.map +1 -1
  188. package/dist/src/planner/building/ddl.js +94 -0
  189. package/dist/src/planner/building/ddl.js.map +1 -1
  190. package/dist/src/planner/building/declare-schema.d.ts +1 -0
  191. package/dist/src/planner/building/declare-schema.d.ts.map +1 -1
  192. package/dist/src/planner/building/declare-schema.js +4 -1
  193. package/dist/src/planner/building/declare-schema.js.map +1 -1
  194. package/dist/src/planner/building/default-scope.d.ts +26 -0
  195. package/dist/src/planner/building/default-scope.d.ts.map +1 -0
  196. package/dist/src/planner/building/default-scope.js +41 -0
  197. package/dist/src/planner/building/default-scope.js.map +1 -0
  198. package/dist/src/planner/building/delete.d.ts +19 -1
  199. package/dist/src/planner/building/delete.d.ts.map +1 -1
  200. package/dist/src/planner/building/delete.js +116 -34
  201. package/dist/src/planner/building/delete.js.map +1 -1
  202. package/dist/src/planner/building/dml-target.d.ts +118 -0
  203. package/dist/src/planner/building/dml-target.d.ts.map +1 -0
  204. package/dist/src/planner/building/dml-target.js +282 -0
  205. package/dist/src/planner/building/dml-target.js.map +1 -0
  206. package/dist/src/planner/building/drop-index.d.ts.map +1 -1
  207. package/dist/src/planner/building/drop-index.js +4 -1
  208. package/dist/src/planner/building/drop-index.js.map +1 -1
  209. package/dist/src/planner/building/drop-view.d.ts.map +1 -1
  210. package/dist/src/planner/building/drop-view.js +4 -2
  211. package/dist/src/planner/building/drop-view.js.map +1 -1
  212. package/dist/src/planner/building/expression.d.ts.map +1 -1
  213. package/dist/src/planner/building/expression.js +60 -21
  214. package/dist/src/planner/building/expression.js.map +1 -1
  215. package/dist/src/planner/building/foreign-key-builder.d.ts +30 -0
  216. package/dist/src/planner/building/foreign-key-builder.d.ts.map +1 -1
  217. package/dist/src/planner/building/foreign-key-builder.js +160 -129
  218. package/dist/src/planner/building/foreign-key-builder.js.map +1 -1
  219. package/dist/src/planner/building/insert.d.ts +45 -2
  220. package/dist/src/planner/building/insert.d.ts.map +1 -1
  221. package/dist/src/planner/building/insert.js +257 -88
  222. package/dist/src/planner/building/insert.js.map +1 -1
  223. package/dist/src/planner/building/lens-auxiliary-access.d.ts +22 -0
  224. package/dist/src/planner/building/lens-auxiliary-access.d.ts.map +1 -0
  225. package/dist/src/planner/building/lens-auxiliary-access.js +132 -0
  226. package/dist/src/planner/building/lens-auxiliary-access.js.map +1 -0
  227. package/dist/src/planner/building/materialized-view.d.ts +16 -0
  228. package/dist/src/planner/building/materialized-view.d.ts.map +1 -0
  229. package/dist/src/planner/building/materialized-view.js +57 -0
  230. package/dist/src/planner/building/materialized-view.js.map +1 -0
  231. package/dist/src/planner/building/returning-star.d.ts +32 -0
  232. package/dist/src/planner/building/returning-star.d.ts.map +1 -0
  233. package/dist/src/planner/building/returning-star.js +45 -0
  234. package/dist/src/planner/building/returning-star.js.map +1 -0
  235. package/dist/src/planner/building/select-aggregates.d.ts.map +1 -1
  236. package/dist/src/planner/building/select-aggregates.js +51 -13
  237. package/dist/src/planner/building/select-aggregates.js.map +1 -1
  238. package/dist/src/planner/building/select-compound.d.ts.map +1 -1
  239. package/dist/src/planner/building/select-compound.js +84 -11
  240. package/dist/src/planner/building/select-compound.js.map +1 -1
  241. package/dist/src/planner/building/select-context.d.ts +10 -2
  242. package/dist/src/planner/building/select-context.d.ts.map +1 -1
  243. package/dist/src/planner/building/select-context.js +7 -1
  244. package/dist/src/planner/building/select-context.js.map +1 -1
  245. package/dist/src/planner/building/select-modifiers.js +6 -0
  246. package/dist/src/planner/building/select-modifiers.js.map +1 -1
  247. package/dist/src/planner/building/select-ordinal.d.ts +18 -0
  248. package/dist/src/planner/building/select-ordinal.d.ts.map +1 -1
  249. package/dist/src/planner/building/select-ordinal.js +30 -0
  250. package/dist/src/planner/building/select-ordinal.js.map +1 -1
  251. package/dist/src/planner/building/select-projections.d.ts +8 -2
  252. package/dist/src/planner/building/select-projections.d.ts.map +1 -1
  253. package/dist/src/planner/building/select-projections.js +26 -4
  254. package/dist/src/planner/building/select-projections.js.map +1 -1
  255. package/dist/src/planner/building/select-window.d.ts.map +1 -1
  256. package/dist/src/planner/building/select-window.js +8 -5
  257. package/dist/src/planner/building/select-window.js.map +1 -1
  258. package/dist/src/planner/building/select.d.ts.map +1 -1
  259. package/dist/src/planner/building/select.js +164 -59
  260. package/dist/src/planner/building/select.js.map +1 -1
  261. package/dist/src/planner/building/set-object-tags.d.ts +7 -0
  262. package/dist/src/planner/building/set-object-tags.d.ts.map +1 -0
  263. package/dist/src/planner/building/set-object-tags.js +38 -0
  264. package/dist/src/planner/building/set-object-tags.js.map +1 -0
  265. package/dist/src/planner/building/tag-diagnostics.d.ts +27 -0
  266. package/dist/src/planner/building/tag-diagnostics.d.ts.map +1 -0
  267. package/dist/src/planner/building/tag-diagnostics.js +37 -0
  268. package/dist/src/planner/building/tag-diagnostics.js.map +1 -0
  269. package/dist/src/planner/building/update.d.ts +18 -1
  270. package/dist/src/planner/building/update.d.ts.map +1 -1
  271. package/dist/src/planner/building/update.js +134 -58
  272. package/dist/src/planner/building/update.js.map +1 -1
  273. package/dist/src/planner/building/view-mutation-builder.d.ts +15 -0
  274. package/dist/src/planner/building/view-mutation-builder.d.ts.map +1 -0
  275. package/dist/src/planner/building/view-mutation-builder.js +1158 -0
  276. package/dist/src/planner/building/view-mutation-builder.js.map +1 -0
  277. package/dist/src/planner/building/with.d.ts +11 -0
  278. package/dist/src/planner/building/with.d.ts.map +1 -1
  279. package/dist/src/planner/building/with.js +48 -10
  280. package/dist/src/planner/building/with.js.map +1 -1
  281. package/dist/src/planner/cost/index.d.ts +83 -0
  282. package/dist/src/planner/cost/index.d.ts.map +1 -1
  283. package/dist/src/planner/cost/index.js +114 -0
  284. package/dist/src/planner/cost/index.js.map +1 -1
  285. package/dist/src/planner/framework/characteristics.d.ts +38 -4
  286. package/dist/src/planner/framework/characteristics.d.ts.map +1 -1
  287. package/dist/src/planner/framework/characteristics.js +50 -6
  288. package/dist/src/planner/framework/characteristics.js.map +1 -1
  289. package/dist/src/planner/framework/pass.d.ts.map +1 -1
  290. package/dist/src/planner/framework/pass.js +2 -1
  291. package/dist/src/planner/framework/pass.js.map +1 -1
  292. package/dist/src/planner/framework/physical-utils.d.ts.map +1 -1
  293. package/dist/src/planner/framework/physical-utils.js +7 -1
  294. package/dist/src/planner/framework/physical-utils.js.map +1 -1
  295. package/dist/src/planner/framework/registry.d.ts +39 -1
  296. package/dist/src/planner/framework/registry.d.ts.map +1 -1
  297. package/dist/src/planner/framework/registry.js +18 -2
  298. package/dist/src/planner/framework/registry.js.map +1 -1
  299. package/dist/src/planner/mutation/backward-body.d.ts +131 -0
  300. package/dist/src/planner/mutation/backward-body.d.ts.map +1 -0
  301. package/dist/src/planner/mutation/backward-body.js +135 -0
  302. package/dist/src/planner/mutation/backward-body.js.map +1 -0
  303. package/dist/src/planner/mutation/cte-flatten.d.ts +17 -0
  304. package/dist/src/planner/mutation/cte-flatten.d.ts.map +1 -0
  305. package/dist/src/planner/mutation/cte-flatten.js +364 -0
  306. package/dist/src/planner/mutation/cte-flatten.js.map +1 -0
  307. package/dist/src/planner/mutation/decomposition.d.ts +273 -0
  308. package/dist/src/planner/mutation/decomposition.d.ts.map +1 -0
  309. package/dist/src/planner/mutation/decomposition.js +1719 -0
  310. package/dist/src/planner/mutation/decomposition.js.map +1 -0
  311. package/dist/src/planner/mutation/lens-enforcement.d.ts +165 -0
  312. package/dist/src/planner/mutation/lens-enforcement.d.ts.map +1 -0
  313. package/dist/src/planner/mutation/lens-enforcement.js +745 -0
  314. package/dist/src/planner/mutation/lens-enforcement.js.map +1 -0
  315. package/dist/src/planner/mutation/multi-source.d.ts +568 -0
  316. package/dist/src/planner/mutation/multi-source.d.ts.map +1 -0
  317. package/dist/src/planner/mutation/multi-source.js +2915 -0
  318. package/dist/src/planner/mutation/multi-source.js.map +1 -0
  319. package/dist/src/planner/mutation/mutation-diagnostic.d.ts +37 -0
  320. package/dist/src/planner/mutation/mutation-diagnostic.d.ts.map +1 -0
  321. package/dist/src/planner/mutation/mutation-diagnostic.js +24 -0
  322. package/dist/src/planner/mutation/mutation-diagnostic.js.map +1 -0
  323. package/dist/src/planner/mutation/mutation-tags.d.ts +33 -0
  324. package/dist/src/planner/mutation/mutation-tags.d.ts.map +1 -0
  325. package/dist/src/planner/mutation/mutation-tags.js +31 -0
  326. package/dist/src/planner/mutation/mutation-tags.js.map +1 -0
  327. package/dist/src/planner/mutation/propagate.d.ts +97 -0
  328. package/dist/src/planner/mutation/propagate.d.ts.map +1 -0
  329. package/dist/src/planner/mutation/propagate.js +220 -0
  330. package/dist/src/planner/mutation/propagate.js.map +1 -0
  331. package/dist/src/planner/mutation/scope-transform.d.ts +181 -0
  332. package/dist/src/planner/mutation/scope-transform.d.ts.map +1 -0
  333. package/dist/src/planner/mutation/scope-transform.js +574 -0
  334. package/dist/src/planner/mutation/scope-transform.js.map +1 -0
  335. package/dist/src/planner/mutation/set-op.d.ts +242 -0
  336. package/dist/src/planner/mutation/set-op.d.ts.map +1 -0
  337. package/dist/src/planner/mutation/set-op.js +1687 -0
  338. package/dist/src/planner/mutation/set-op.js.map +1 -0
  339. package/dist/src/planner/mutation/single-source.d.ts +261 -0
  340. package/dist/src/planner/mutation/single-source.d.ts.map +1 -0
  341. package/dist/src/planner/mutation/single-source.js +1096 -0
  342. package/dist/src/planner/mutation/single-source.js.map +1 -0
  343. package/dist/src/planner/nodes/aggregate-node.d.ts +6 -4
  344. package/dist/src/planner/nodes/aggregate-node.d.ts.map +1 -1
  345. package/dist/src/planner/nodes/aggregate-node.js +11 -9
  346. package/dist/src/planner/nodes/aggregate-node.js.map +1 -1
  347. package/dist/src/planner/nodes/alias-node.d.ts.map +1 -1
  348. package/dist/src/planner/nodes/alias-node.js +5 -1
  349. package/dist/src/planner/nodes/alias-node.js.map +1 -1
  350. package/dist/src/planner/nodes/alter-table-node.d.ts +124 -1
  351. package/dist/src/planner/nodes/alter-table-node.d.ts.map +1 -1
  352. package/dist/src/planner/nodes/alter-table-node.js +27 -0
  353. package/dist/src/planner/nodes/alter-table-node.js.map +1 -1
  354. package/dist/src/planner/nodes/analyze-node.d.ts +2 -1
  355. package/dist/src/planner/nodes/analyze-node.d.ts.map +1 -1
  356. package/dist/src/planner/nodes/analyze-node.js +21 -1
  357. package/dist/src/planner/nodes/analyze-node.js.map +1 -1
  358. package/dist/src/planner/nodes/asserted-keys-node.d.ts +43 -0
  359. package/dist/src/planner/nodes/asserted-keys-node.d.ts.map +1 -0
  360. package/dist/src/planner/nodes/asserted-keys-node.js +99 -0
  361. package/dist/src/planner/nodes/asserted-keys-node.js.map +1 -0
  362. package/dist/src/planner/nodes/async-gather-node.d.ts.map +1 -1
  363. package/dist/src/planner/nodes/async-gather-node.js +33 -8
  364. package/dist/src/planner/nodes/async-gather-node.js.map +1 -1
  365. package/dist/src/planner/nodes/bloom-join-node.d.ts.map +1 -1
  366. package/dist/src/planner/nodes/bloom-join-node.js +2 -1
  367. package/dist/src/planner/nodes/bloom-join-node.js.map +1 -1
  368. package/dist/src/planner/nodes/create-view-node.d.ts +7 -2
  369. package/dist/src/planner/nodes/create-view-node.d.ts.map +1 -1
  370. package/dist/src/planner/nodes/create-view-node.js +4 -1
  371. package/dist/src/planner/nodes/create-view-node.js.map +1 -1
  372. package/dist/src/planner/nodes/declarative-schema.d.ts +13 -1
  373. package/dist/src/planner/nodes/declarative-schema.d.ts.map +1 -1
  374. package/dist/src/planner/nodes/declarative-schema.js +32 -0
  375. package/dist/src/planner/nodes/declarative-schema.js.map +1 -1
  376. package/dist/src/planner/nodes/distinct-node.d.ts.map +1 -1
  377. package/dist/src/planner/nodes/distinct-node.js +2 -0
  378. package/dist/src/planner/nodes/distinct-node.js.map +1 -1
  379. package/dist/src/planner/nodes/dml-executor-node.d.ts +29 -1
  380. package/dist/src/planner/nodes/dml-executor-node.d.ts.map +1 -1
  381. package/dist/src/planner/nodes/dml-executor-node.js +27 -3
  382. package/dist/src/planner/nodes/dml-executor-node.js.map +1 -1
  383. package/dist/src/planner/nodes/eager-prefetch-node.d.ts.map +1 -1
  384. package/dist/src/planner/nodes/eager-prefetch-node.js +2 -0
  385. package/dist/src/planner/nodes/eager-prefetch-node.js.map +1 -1
  386. package/dist/src/planner/nodes/envelope-scan-node.d.ts +42 -0
  387. package/dist/src/planner/nodes/envelope-scan-node.d.ts.map +1 -0
  388. package/dist/src/planner/nodes/envelope-scan-node.js +62 -0
  389. package/dist/src/planner/nodes/envelope-scan-node.js.map +1 -0
  390. package/dist/src/planner/nodes/fanout-lookup-join-node.d.ts.map +1 -1
  391. package/dist/src/planner/nodes/fanout-lookup-join-node.js +11 -1
  392. package/dist/src/planner/nodes/fanout-lookup-join-node.js.map +1 -1
  393. package/dist/src/planner/nodes/filter.d.ts.map +1 -1
  394. package/dist/src/planner/nodes/filter.js +63 -13
  395. package/dist/src/planner/nodes/filter.js.map +1 -1
  396. package/dist/src/planner/nodes/hash-aggregate.d.ts.map +1 -1
  397. package/dist/src/planner/nodes/hash-aggregate.js +6 -16
  398. package/dist/src/planner/nodes/hash-aggregate.js.map +1 -1
  399. package/dist/src/planner/nodes/join-node.d.ts +41 -1
  400. package/dist/src/planner/nodes/join-node.d.ts.map +1 -1
  401. package/dist/src/planner/nodes/join-node.js +78 -8
  402. package/dist/src/planner/nodes/join-node.js.map +1 -1
  403. package/dist/src/planner/nodes/join-utils.d.ts +33 -6
  404. package/dist/src/planner/nodes/join-utils.d.ts.map +1 -1
  405. package/dist/src/planner/nodes/join-utils.js +131 -10
  406. package/dist/src/planner/nodes/join-utils.js.map +1 -1
  407. package/dist/src/planner/nodes/lens-auxiliary-access-node.d.ts +104 -0
  408. package/dist/src/planner/nodes/lens-auxiliary-access-node.d.ts.map +1 -0
  409. package/dist/src/planner/nodes/lens-auxiliary-access-node.js +91 -0
  410. package/dist/src/planner/nodes/lens-auxiliary-access-node.js.map +1 -0
  411. package/dist/src/planner/nodes/limit-offset.d.ts +12 -0
  412. package/dist/src/planner/nodes/limit-offset.d.ts.map +1 -1
  413. package/dist/src/planner/nodes/limit-offset.js +52 -3
  414. package/dist/src/planner/nodes/limit-offset.js.map +1 -1
  415. package/dist/src/planner/nodes/materialized-view-nodes.d.ts +69 -0
  416. package/dist/src/planner/nodes/materialized-view-nodes.d.ts.map +1 -0
  417. package/dist/src/planner/nodes/materialized-view-nodes.js +111 -0
  418. package/dist/src/planner/nodes/materialized-view-nodes.js.map +1 -0
  419. package/dist/src/planner/nodes/merge-join-node.d.ts.map +1 -1
  420. package/dist/src/planner/nodes/merge-join-node.js +2 -1
  421. package/dist/src/planner/nodes/merge-join-node.js.map +1 -1
  422. package/dist/src/planner/nodes/ordinal-slice-node.d.ts.map +1 -1
  423. package/dist/src/planner/nodes/ordinal-slice-node.js +2 -0
  424. package/dist/src/planner/nodes/ordinal-slice-node.js.map +1 -1
  425. package/dist/src/planner/nodes/plan-node-type.d.ts +9 -0
  426. package/dist/src/planner/nodes/plan-node-type.d.ts.map +1 -1
  427. package/dist/src/planner/nodes/plan-node-type.js +9 -0
  428. package/dist/src/planner/nodes/plan-node-type.js.map +1 -1
  429. package/dist/src/planner/nodes/plan-node.d.ts +265 -5
  430. package/dist/src/planner/nodes/plan-node.d.ts.map +1 -1
  431. package/dist/src/planner/nodes/plan-node.js.map +1 -1
  432. package/dist/src/planner/nodes/pragma.d.ts +2 -1
  433. package/dist/src/planner/nodes/pragma.d.ts.map +1 -1
  434. package/dist/src/planner/nodes/pragma.js +12 -0
  435. package/dist/src/planner/nodes/pragma.js.map +1 -1
  436. package/dist/src/planner/nodes/project-node.d.ts +14 -1
  437. package/dist/src/planner/nodes/project-node.d.ts.map +1 -1
  438. package/dist/src/planner/nodes/project-node.js +103 -16
  439. package/dist/src/planner/nodes/project-node.js.map +1 -1
  440. package/dist/src/planner/nodes/reference.d.ts.map +1 -1
  441. package/dist/src/planner/nodes/reference.js +63 -30
  442. package/dist/src/planner/nodes/reference.js.map +1 -1
  443. package/dist/src/planner/nodes/retrieve-node.d.ts.map +1 -1
  444. package/dist/src/planner/nodes/retrieve-node.js +7 -0
  445. package/dist/src/planner/nodes/retrieve-node.js.map +1 -1
  446. package/dist/src/planner/nodes/returning-node.d.ts.map +1 -1
  447. package/dist/src/planner/nodes/returning-node.js +10 -3
  448. package/dist/src/planner/nodes/returning-node.js.map +1 -1
  449. package/dist/src/planner/nodes/scalar.d.ts +20 -0
  450. package/dist/src/planner/nodes/scalar.d.ts.map +1 -1
  451. package/dist/src/planner/nodes/scalar.js +71 -14
  452. package/dist/src/planner/nodes/scalar.js.map +1 -1
  453. package/dist/src/planner/nodes/set-object-tags-node.d.ts +39 -0
  454. package/dist/src/planner/nodes/set-object-tags-node.d.ts.map +1 -0
  455. package/dist/src/planner/nodes/set-object-tags-node.js +41 -0
  456. package/dist/src/planner/nodes/set-object-tags-node.js.map +1 -0
  457. package/dist/src/planner/nodes/set-operation-node.d.ts +123 -1
  458. package/dist/src/planner/nodes/set-operation-node.d.ts.map +1 -1
  459. package/dist/src/planner/nodes/set-operation-node.js +302 -18
  460. package/dist/src/planner/nodes/set-operation-node.js.map +1 -1
  461. package/dist/src/planner/nodes/single-row.d.ts.map +1 -1
  462. package/dist/src/planner/nodes/single-row.js +3 -0
  463. package/dist/src/planner/nodes/single-row.js.map +1 -1
  464. package/dist/src/planner/nodes/sort.d.ts.map +1 -1
  465. package/dist/src/planner/nodes/sort.js +8 -7
  466. package/dist/src/planner/nodes/sort.js.map +1 -1
  467. package/dist/src/planner/nodes/stream-aggregate.d.ts.map +1 -1
  468. package/dist/src/planner/nodes/stream-aggregate.js +8 -23
  469. package/dist/src/planner/nodes/stream-aggregate.js.map +1 -1
  470. package/dist/src/planner/nodes/subquery.d.ts +2 -0
  471. package/dist/src/planner/nodes/subquery.d.ts.map +1 -1
  472. package/dist/src/planner/nodes/subquery.js +18 -2
  473. package/dist/src/planner/nodes/subquery.js.map +1 -1
  474. package/dist/src/planner/nodes/table-access-nodes.d.ts.map +1 -1
  475. package/dist/src/planner/nodes/table-access-nodes.js +23 -3
  476. package/dist/src/planner/nodes/table-access-nodes.js.map +1 -1
  477. package/dist/src/planner/nodes/table-function-call.js +6 -0
  478. package/dist/src/planner/nodes/table-function-call.js.map +1 -1
  479. package/dist/src/planner/nodes/values-node.d.ts +3 -1
  480. package/dist/src/planner/nodes/values-node.d.ts.map +1 -1
  481. package/dist/src/planner/nodes/values-node.js +26 -0
  482. package/dist/src/planner/nodes/values-node.js.map +1 -1
  483. package/dist/src/planner/nodes/view-mutation-node.d.ts +259 -0
  484. package/dist/src/planner/nodes/view-mutation-node.d.ts.map +1 -0
  485. package/dist/src/planner/nodes/view-mutation-node.js +273 -0
  486. package/dist/src/planner/nodes/view-mutation-node.js.map +1 -0
  487. package/dist/src/planner/nodes/window-function.d.ts +17 -1
  488. package/dist/src/planner/nodes/window-function.d.ts.map +1 -1
  489. package/dist/src/planner/nodes/window-function.js +15 -1
  490. package/dist/src/planner/nodes/window-function.js.map +1 -1
  491. package/dist/src/planner/nodes/window-node.js +3 -3
  492. package/dist/src/planner/nodes/window-node.js.map +1 -1
  493. package/dist/src/planner/optimizer.d.ts.map +1 -1
  494. package/dist/src/planner/optimizer.js +372 -39
  495. package/dist/src/planner/optimizer.js.map +1 -1
  496. package/dist/src/planner/planning-context.d.ts +1 -1
  497. package/dist/src/planner/planning-context.d.ts.map +1 -1
  498. package/dist/src/planner/rules/access/lens-access-form-matcher.d.ts +70 -0
  499. package/dist/src/planner/rules/access/lens-access-form-matcher.d.ts.map +1 -0
  500. package/dist/src/planner/rules/access/lens-access-form-matcher.js +156 -0
  501. package/dist/src/planner/rules/access/lens-access-form-matcher.js.map +1 -0
  502. package/dist/src/planner/rules/access/rule-lens-auxiliary-access.d.ts +31 -0
  503. package/dist/src/planner/rules/access/rule-lens-auxiliary-access.d.ts.map +1 -0
  504. package/dist/src/planner/rules/access/rule-lens-auxiliary-access.js +176 -0
  505. package/dist/src/planner/rules/access/rule-lens-auxiliary-access.js.map +1 -0
  506. package/dist/src/planner/rules/access/rule-select-access-path.d.ts.map +1 -1
  507. package/dist/src/planner/rules/access/rule-select-access-path.js +435 -37
  508. package/dist/src/planner/rules/access/rule-select-access-path.js.map +1 -1
  509. package/dist/src/planner/rules/aggregate/rule-aggregate-streaming.d.ts.map +1 -1
  510. package/dist/src/planner/rules/aggregate/rule-aggregate-streaming.js +8 -27
  511. package/dist/src/planner/rules/aggregate/rule-aggregate-streaming.js.map +1 -1
  512. package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.d.ts +9 -3
  513. package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.d.ts.map +1 -1
  514. package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.js +56 -5
  515. package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.js.map +1 -1
  516. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.d.ts +39 -0
  517. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.d.ts.map +1 -0
  518. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js +616 -0
  519. package/dist/src/planner/rules/cache/rule-materialized-view-rewrite.js.map +1 -0
  520. package/dist/src/planner/rules/cache/rule-scalar-cse.d.ts.map +1 -1
  521. package/dist/src/planner/rules/cache/rule-scalar-cse.js +8 -1
  522. package/dist/src/planner/rules/cache/rule-scalar-cse.js.map +1 -1
  523. package/dist/src/planner/rules/distinct/rule-distinct-elimination.d.ts +8 -7
  524. package/dist/src/planner/rules/distinct/rule-distinct-elimination.d.ts.map +1 -1
  525. package/dist/src/planner/rules/distinct/rule-distinct-elimination.js +14 -21
  526. package/dist/src/planner/rules/distinct/rule-distinct-elimination.js.map +1 -1
  527. package/dist/src/planner/rules/join/equi-pair-extractor.d.ts +36 -0
  528. package/dist/src/planner/rules/join/equi-pair-extractor.d.ts.map +1 -1
  529. package/dist/src/planner/rules/join/equi-pair-extractor.js +42 -5
  530. package/dist/src/planner/rules/join/equi-pair-extractor.js.map +1 -1
  531. package/dist/src/planner/rules/join/rule-fanout-batched-outer.d.ts.map +1 -1
  532. package/dist/src/planner/rules/join/rule-fanout-batched-outer.js +10 -0
  533. package/dist/src/planner/rules/join/rule-fanout-batched-outer.js.map +1 -1
  534. package/dist/src/planner/rules/join/rule-fanout-lookup-join.js +25 -9
  535. package/dist/src/planner/rules/join/rule-fanout-lookup-join.js.map +1 -1
  536. package/dist/src/planner/rules/join/rule-inner-join-existence-recovery.d.ts +130 -0
  537. package/dist/src/planner/rules/join/rule-inner-join-existence-recovery.d.ts.map +1 -0
  538. package/dist/src/planner/rules/join/rule-inner-join-existence-recovery.js +206 -0
  539. package/dist/src/planner/rules/join/rule-inner-join-existence-recovery.js.map +1 -0
  540. package/dist/src/planner/rules/join/rule-join-elimination.d.ts +67 -14
  541. package/dist/src/planner/rules/join/rule-join-elimination.d.ts.map +1 -1
  542. package/dist/src/planner/rules/join/rule-join-elimination.js +81 -25
  543. package/dist/src/planner/rules/join/rule-join-elimination.js.map +1 -1
  544. package/dist/src/planner/rules/join/rule-join-existence-pruning.d.ts +84 -0
  545. package/dist/src/planner/rules/join/rule-join-existence-pruning.d.ts.map +1 -0
  546. package/dist/src/planner/rules/join/rule-join-existence-pruning.js +138 -0
  547. package/dist/src/planner/rules/join/rule-join-existence-pruning.js.map +1 -0
  548. package/dist/src/planner/rules/join/rule-join-greedy-commute.d.ts.map +1 -1
  549. package/dist/src/planner/rules/join/rule-join-greedy-commute.js +19 -1
  550. package/dist/src/planner/rules/join/rule-join-greedy-commute.js.map +1 -1
  551. package/dist/src/planner/rules/join/rule-join-physical-selection.d.ts.map +1 -1
  552. package/dist/src/planner/rules/join/rule-join-physical-selection.js +14 -2
  553. package/dist/src/planner/rules/join/rule-join-physical-selection.js.map +1 -1
  554. package/dist/src/planner/rules/join/rule-lateral-top1-asof.d.ts.map +1 -1
  555. package/dist/src/planner/rules/join/rule-lateral-top1-asof.js +5 -2
  556. package/dist/src/planner/rules/join/rule-lateral-top1-asof.js.map +1 -1
  557. package/dist/src/planner/rules/join/rule-monotonic-merge-join.d.ts.map +1 -1
  558. package/dist/src/planner/rules/join/rule-monotonic-merge-join.js +4 -0
  559. package/dist/src/planner/rules/join/rule-monotonic-merge-join.js.map +1 -1
  560. package/dist/src/planner/rules/join/rule-quickpick-enumeration.d.ts.map +1 -1
  561. package/dist/src/planner/rules/join/rule-quickpick-enumeration.js +10 -0
  562. package/dist/src/planner/rules/join/rule-quickpick-enumeration.js.map +1 -1
  563. package/dist/src/planner/rules/join/rule-semijoin-existence-recovery.d.ts +286 -0
  564. package/dist/src/planner/rules/join/rule-semijoin-existence-recovery.d.ts.map +1 -0
  565. package/dist/src/planner/rules/join/rule-semijoin-existence-recovery.js +548 -0
  566. package/dist/src/planner/rules/join/rule-semijoin-existence-recovery.js.map +1 -0
  567. package/dist/src/planner/rules/parallel/rule-async-gather-union-all.d.ts.map +1 -1
  568. package/dist/src/planner/rules/parallel/rule-async-gather-union-all.js +9 -1
  569. package/dist/src/planner/rules/parallel/rule-async-gather-union-all.js.map +1 -1
  570. package/dist/src/planner/rules/parallel/rule-async-gather-zip-by-key.d.ts.map +1 -1
  571. package/dist/src/planner/rules/parallel/rule-async-gather-zip-by-key.js +7 -0
  572. package/dist/src/planner/rules/parallel/rule-async-gather-zip-by-key.js.map +1 -1
  573. package/dist/src/planner/rules/parallel/rule-eager-prefetch-probe.d.ts.map +1 -1
  574. package/dist/src/planner/rules/parallel/rule-eager-prefetch-probe.js +10 -1
  575. package/dist/src/planner/rules/parallel/rule-eager-prefetch-probe.js.map +1 -1
  576. package/dist/src/planner/rules/predicate/rule-aggregate-predicate-pushdown.d.ts.map +1 -1
  577. package/dist/src/planner/rules/predicate/rule-aggregate-predicate-pushdown.js +10 -1
  578. package/dist/src/planner/rules/predicate/rule-aggregate-predicate-pushdown.js.map +1 -1
  579. package/dist/src/planner/rules/predicate/rule-empty-relation-folding.d.ts.map +1 -1
  580. package/dist/src/planner/rules/predicate/rule-empty-relation-folding.js +18 -0
  581. package/dist/src/planner/rules/predicate/rule-empty-relation-folding.js.map +1 -1
  582. package/dist/src/planner/rules/predicate/rule-filter-contradiction.d.ts.map +1 -1
  583. package/dist/src/planner/rules/predicate/rule-filter-contradiction.js +7 -0
  584. package/dist/src/planner/rules/predicate/rule-filter-contradiction.js.map +1 -1
  585. package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.d.ts.map +1 -1
  586. package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.js +9 -0
  587. package/dist/src/planner/rules/predicate/rule-predicate-inference-equivalence.js.map +1 -1
  588. package/dist/src/planner/rules/predicate/rule-predicate-pushdown.js +13 -3
  589. package/dist/src/planner/rules/predicate/rule-predicate-pushdown.js.map +1 -1
  590. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js +2 -2
  591. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js.map +1 -1
  592. package/dist/src/planner/rules/retrieve/rule-projection-pruning.d.ts.map +1 -1
  593. package/dist/src/planner/rules/retrieve/rule-projection-pruning.js +14 -0
  594. package/dist/src/planner/rules/retrieve/rule-projection-pruning.js.map +1 -1
  595. package/dist/src/planner/rules/sort/rule-orderby-fd-pruning.d.ts +16 -0
  596. package/dist/src/planner/rules/sort/rule-orderby-fd-pruning.d.ts.map +1 -1
  597. package/dist/src/planner/rules/sort/rule-orderby-fd-pruning.js +47 -4
  598. package/dist/src/planner/rules/sort/rule-orderby-fd-pruning.js.map +1 -1
  599. package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.d.ts.map +1 -1
  600. package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.js +8 -0
  601. package/dist/src/planner/rules/subquery/rule-anti-join-fk-empty.js.map +1 -1
  602. package/dist/src/planner/rules/subquery/rule-semi-join-fk-trivial.d.ts.map +1 -1
  603. package/dist/src/planner/rules/subquery/rule-semi-join-fk-trivial.js +7 -0
  604. package/dist/src/planner/rules/subquery/rule-semi-join-fk-trivial.js.map +1 -1
  605. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.d.ts.map +1 -1
  606. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js +12 -0
  607. package/dist/src/planner/rules/subquery/rule-subquery-decorrelation.js.map +1 -1
  608. package/dist/src/planner/rules/window/rule-monotonic-window.js +1 -1
  609. package/dist/src/planner/rules/window/rule-monotonic-window.js.map +1 -1
  610. package/dist/src/planner/type-utils.d.ts +14 -0
  611. package/dist/src/planner/type-utils.d.ts.map +1 -1
  612. package/dist/src/planner/type-utils.js +66 -21
  613. package/dist/src/planner/type-utils.js.map +1 -1
  614. package/dist/src/planner/util/fd-utils.d.ts +228 -36
  615. package/dist/src/planner/util/fd-utils.d.ts.map +1 -1
  616. package/dist/src/planner/util/fd-utils.js +501 -84
  617. package/dist/src/planner/util/fd-utils.js.map +1 -1
  618. package/dist/src/planner/util/ind-utils.d.ts +27 -1
  619. package/dist/src/planner/util/ind-utils.d.ts.map +1 -1
  620. package/dist/src/planner/util/ind-utils.js +80 -6
  621. package/dist/src/planner/util/ind-utils.js.map +1 -1
  622. package/dist/src/planner/util/key-utils.d.ts +26 -3
  623. package/dist/src/planner/util/key-utils.d.ts.map +1 -1
  624. package/dist/src/planner/util/key-utils.js +182 -33
  625. package/dist/src/planner/util/key-utils.js.map +1 -1
  626. package/dist/src/planner/util/set-op-wrapper.d.ts +37 -0
  627. package/dist/src/planner/util/set-op-wrapper.d.ts.map +1 -0
  628. package/dist/src/planner/util/set-op-wrapper.js +82 -0
  629. package/dist/src/planner/util/set-op-wrapper.js.map +1 -0
  630. package/dist/src/planner/validation/plan-validator.d.ts.map +1 -1
  631. package/dist/src/planner/validation/plan-validator.js +1 -0
  632. package/dist/src/planner/validation/plan-validator.js.map +1 -1
  633. package/dist/src/runtime/context-helpers.d.ts +13 -1
  634. package/dist/src/runtime/context-helpers.d.ts.map +1 -1
  635. package/dist/src/runtime/context-helpers.js +7 -1
  636. package/dist/src/runtime/context-helpers.js.map +1 -1
  637. package/dist/src/runtime/delta-executor.d.ts +30 -1
  638. package/dist/src/runtime/delta-executor.d.ts.map +1 -1
  639. package/dist/src/runtime/delta-executor.js +38 -4
  640. package/dist/src/runtime/delta-executor.js.map +1 -1
  641. package/dist/src/runtime/emit/add-constraint.d.ts.map +1 -1
  642. package/dist/src/runtime/emit/add-constraint.js +38 -5
  643. package/dist/src/runtime/emit/add-constraint.js.map +1 -1
  644. package/dist/src/runtime/emit/aggregate.d.ts.map +1 -1
  645. package/dist/src/runtime/emit/aggregate.js +10 -8
  646. package/dist/src/runtime/emit/aggregate.js.map +1 -1
  647. package/dist/src/runtime/emit/alter-table.d.ts +1 -1
  648. package/dist/src/runtime/emit/alter-table.d.ts.map +1 -1
  649. package/dist/src/runtime/emit/alter-table.js +664 -108
  650. package/dist/src/runtime/emit/alter-table.js.map +1 -1
  651. package/dist/src/runtime/emit/analyze.d.ts.map +1 -1
  652. package/dist/src/runtime/emit/analyze.js +2 -1
  653. package/dist/src/runtime/emit/analyze.js.map +1 -1
  654. package/dist/src/runtime/emit/asof-scan.d.ts.map +1 -1
  655. package/dist/src/runtime/emit/asof-scan.js +24 -9
  656. package/dist/src/runtime/emit/asof-scan.js.map +1 -1
  657. package/dist/src/runtime/emit/asserted-keys.d.ts +13 -0
  658. package/dist/src/runtime/emit/asserted-keys.d.ts.map +1 -0
  659. package/dist/src/runtime/emit/asserted-keys.js +13 -0
  660. package/dist/src/runtime/emit/asserted-keys.js.map +1 -0
  661. package/dist/src/runtime/emit/between.d.ts.map +1 -1
  662. package/dist/src/runtime/emit/between.js +24 -19
  663. package/dist/src/runtime/emit/between.js.map +1 -1
  664. package/dist/src/runtime/emit/binary.d.ts.map +1 -1
  665. package/dist/src/runtime/emit/binary.js +24 -36
  666. package/dist/src/runtime/emit/binary.js.map +1 -1
  667. package/dist/src/runtime/emit/block.d.ts.map +1 -1
  668. package/dist/src/runtime/emit/block.js +11 -2
  669. package/dist/src/runtime/emit/block.js.map +1 -1
  670. package/dist/src/runtime/emit/bloom-join.d.ts.map +1 -1
  671. package/dist/src/runtime/emit/bloom-join.js +12 -4
  672. package/dist/src/runtime/emit/bloom-join.js.map +1 -1
  673. package/dist/src/runtime/emit/constraint-check.d.ts.map +1 -1
  674. package/dist/src/runtime/emit/constraint-check.js +50 -1
  675. package/dist/src/runtime/emit/constraint-check.js.map +1 -1
  676. package/dist/src/runtime/emit/create-table.d.ts.map +1 -1
  677. package/dist/src/runtime/emit/create-table.js +8 -0
  678. package/dist/src/runtime/emit/create-table.js.map +1 -1
  679. package/dist/src/runtime/emit/create-view.d.ts.map +1 -1
  680. package/dist/src/runtime/emit/create-view.js +16 -1
  681. package/dist/src/runtime/emit/create-view.js.map +1 -1
  682. package/dist/src/runtime/emit/delete.d.ts.map +1 -1
  683. package/dist/src/runtime/emit/delete.js +15 -5
  684. package/dist/src/runtime/emit/delete.js.map +1 -1
  685. package/dist/src/runtime/emit/dml-executor.d.ts +27 -0
  686. package/dist/src/runtime/emit/dml-executor.d.ts.map +1 -1
  687. package/dist/src/runtime/emit/dml-executor.js +413 -193
  688. package/dist/src/runtime/emit/dml-executor.js.map +1 -1
  689. package/dist/src/runtime/emit/drop-table.d.ts.map +1 -1
  690. package/dist/src/runtime/emit/drop-table.js +10 -0
  691. package/dist/src/runtime/emit/drop-table.js.map +1 -1
  692. package/dist/src/runtime/emit/drop-view.d.ts.map +1 -1
  693. package/dist/src/runtime/emit/drop-view.js +17 -0
  694. package/dist/src/runtime/emit/drop-view.js.map +1 -1
  695. package/dist/src/runtime/emit/envelope-scan.d.ts +13 -0
  696. package/dist/src/runtime/emit/envelope-scan.d.ts.map +1 -0
  697. package/dist/src/runtime/emit/envelope-scan.js +22 -0
  698. package/dist/src/runtime/emit/envelope-scan.js.map +1 -0
  699. package/dist/src/runtime/emit/join.d.ts +10 -2
  700. package/dist/src/runtime/emit/join.d.ts.map +1 -1
  701. package/dist/src/runtime/emit/join.js +128 -38
  702. package/dist/src/runtime/emit/join.js.map +1 -1
  703. package/dist/src/runtime/emit/lens-auxiliary-access.d.ts +16 -0
  704. package/dist/src/runtime/emit/lens-auxiliary-access.d.ts.map +1 -0
  705. package/dist/src/runtime/emit/lens-auxiliary-access.js +16 -0
  706. package/dist/src/runtime/emit/lens-auxiliary-access.js.map +1 -0
  707. package/dist/src/runtime/emit/materialized-view-helpers.d.ts +640 -0
  708. package/dist/src/runtime/emit/materialized-view-helpers.d.ts.map +1 -0
  709. package/dist/src/runtime/emit/materialized-view-helpers.js +2576 -0
  710. package/dist/src/runtime/emit/materialized-view-helpers.js.map +1 -0
  711. package/dist/src/runtime/emit/materialized-view.d.ts +31 -0
  712. package/dist/src/runtime/emit/materialized-view.d.ts.map +1 -0
  713. package/dist/src/runtime/emit/materialized-view.js +187 -0
  714. package/dist/src/runtime/emit/materialized-view.js.map +1 -0
  715. package/dist/src/runtime/emit/merge-join.d.ts.map +1 -1
  716. package/dist/src/runtime/emit/merge-join.js +19 -5
  717. package/dist/src/runtime/emit/merge-join.js.map +1 -1
  718. package/dist/src/runtime/emit/project.d.ts.map +1 -1
  719. package/dist/src/runtime/emit/project.js +10 -5
  720. package/dist/src/runtime/emit/project.js.map +1 -1
  721. package/dist/src/runtime/emit/schema-declarative.d.ts +1 -0
  722. package/dist/src/runtime/emit/schema-declarative.d.ts.map +1 -1
  723. package/dist/src/runtime/emit/schema-declarative.js +101 -5
  724. package/dist/src/runtime/emit/schema-declarative.js.map +1 -1
  725. package/dist/src/runtime/emit/set-object-tags.d.ts +16 -0
  726. package/dist/src/runtime/emit/set-object-tags.d.ts.map +1 -0
  727. package/dist/src/runtime/emit/set-object-tags.js +57 -0
  728. package/dist/src/runtime/emit/set-object-tags.js.map +1 -0
  729. package/dist/src/runtime/emit/set-operation.d.ts.map +1 -1
  730. package/dist/src/runtime/emit/set-operation.js +140 -24
  731. package/dist/src/runtime/emit/set-operation.js.map +1 -1
  732. package/dist/src/runtime/emit/subquery.d.ts.map +1 -1
  733. package/dist/src/runtime/emit/subquery.js +110 -5
  734. package/dist/src/runtime/emit/subquery.js.map +1 -1
  735. package/dist/src/runtime/emit/unary.d.ts.map +1 -1
  736. package/dist/src/runtime/emit/unary.js +34 -6
  737. package/dist/src/runtime/emit/unary.js.map +1 -1
  738. package/dist/src/runtime/emit/view-mutation.d.ts +70 -0
  739. package/dist/src/runtime/emit/view-mutation.d.ts.map +1 -0
  740. package/dist/src/runtime/emit/view-mutation.js +299 -0
  741. package/dist/src/runtime/emit/view-mutation.js.map +1 -0
  742. package/dist/src/runtime/emit/window.js +29 -5
  743. package/dist/src/runtime/emit/window.js.map +1 -1
  744. package/dist/src/runtime/foreign-key-actions.d.ts +66 -3
  745. package/dist/src/runtime/foreign-key-actions.d.ts.map +1 -1
  746. package/dist/src/runtime/foreign-key-actions.js +580 -172
  747. package/dist/src/runtime/foreign-key-actions.js.map +1 -1
  748. package/dist/src/runtime/parallel-driver.d.ts +4 -1
  749. package/dist/src/runtime/parallel-driver.d.ts.map +1 -1
  750. package/dist/src/runtime/parallel-driver.js +5 -1
  751. package/dist/src/runtime/parallel-driver.js.map +1 -1
  752. package/dist/src/runtime/register.d.ts.map +1 -1
  753. package/dist/src/runtime/register.js +17 -1
  754. package/dist/src/runtime/register.js.map +1 -1
  755. package/dist/src/runtime/types.d.ts +10 -0
  756. package/dist/src/runtime/types.d.ts.map +1 -1
  757. package/dist/src/runtime/types.js.map +1 -1
  758. package/dist/src/schema/basis-backfill.d.ts +63 -0
  759. package/dist/src/schema/basis-backfill.d.ts.map +1 -0
  760. package/dist/src/schema/basis-backfill.js +161 -0
  761. package/dist/src/schema/basis-backfill.js.map +1 -0
  762. package/dist/src/schema/catalog.d.ts +115 -1
  763. package/dist/src/schema/catalog.d.ts.map +1 -1
  764. package/dist/src/schema/catalog.js +249 -22
  765. package/dist/src/schema/catalog.js.map +1 -1
  766. package/dist/src/schema/change-events.d.ts +42 -1
  767. package/dist/src/schema/change-events.d.ts.map +1 -1
  768. package/dist/src/schema/change-events.js.map +1 -1
  769. package/dist/src/schema/column.d.ts +16 -0
  770. package/dist/src/schema/column.d.ts.map +1 -1
  771. package/dist/src/schema/column.js.map +1 -1
  772. package/dist/src/schema/constraint-builder.d.ts +182 -0
  773. package/dist/src/schema/constraint-builder.d.ts.map +1 -0
  774. package/dist/src/schema/constraint-builder.js +424 -0
  775. package/dist/src/schema/constraint-builder.js.map +1 -0
  776. package/dist/src/schema/ddl-generator.d.ts +86 -1
  777. package/dist/src/schema/ddl-generator.d.ts.map +1 -1
  778. package/dist/src/schema/ddl-generator.js +316 -20
  779. package/dist/src/schema/ddl-generator.js.map +1 -1
  780. package/dist/src/schema/declared-schema-manager.d.ts +51 -0
  781. package/dist/src/schema/declared-schema-manager.d.ts.map +1 -1
  782. package/dist/src/schema/declared-schema-manager.js +61 -0
  783. package/dist/src/schema/declared-schema-manager.js.map +1 -1
  784. package/dist/src/schema/derivation.d.ts +106 -0
  785. package/dist/src/schema/derivation.d.ts.map +1 -0
  786. package/dist/src/schema/derivation.js +25 -0
  787. package/dist/src/schema/derivation.js.map +1 -0
  788. package/dist/src/schema/function.d.ts +13 -0
  789. package/dist/src/schema/function.d.ts.map +1 -1
  790. package/dist/src/schema/function.js.map +1 -1
  791. package/dist/src/schema/lens-ack.d.ts +90 -0
  792. package/dist/src/schema/lens-ack.d.ts.map +1 -0
  793. package/dist/src/schema/lens-ack.js +361 -0
  794. package/dist/src/schema/lens-ack.js.map +1 -0
  795. package/dist/src/schema/lens-compiler.d.ts +62 -0
  796. package/dist/src/schema/lens-compiler.d.ts.map +1 -0
  797. package/dist/src/schema/lens-compiler.js +1594 -0
  798. package/dist/src/schema/lens-compiler.js.map +1 -0
  799. package/dist/src/schema/lens-fk-discovery.d.ts +175 -0
  800. package/dist/src/schema/lens-fk-discovery.d.ts.map +1 -0
  801. package/dist/src/schema/lens-fk-discovery.js +336 -0
  802. package/dist/src/schema/lens-fk-discovery.js.map +1 -0
  803. package/dist/src/schema/lens-prover.d.ts +336 -0
  804. package/dist/src/schema/lens-prover.d.ts.map +1 -0
  805. package/dist/src/schema/lens-prover.js +1988 -0
  806. package/dist/src/schema/lens-prover.js.map +1 -0
  807. package/dist/src/schema/lens.d.ts +254 -0
  808. package/dist/src/schema/lens.d.ts.map +1 -0
  809. package/dist/src/schema/lens.js +21 -0
  810. package/dist/src/schema/lens.js.map +1 -0
  811. package/dist/src/schema/manager.d.ts +676 -18
  812. package/dist/src/schema/manager.d.ts.map +1 -1
  813. package/dist/src/schema/manager.js +1573 -238
  814. package/dist/src/schema/manager.js.map +1 -1
  815. package/dist/src/schema/mapping-advertisement-tags.d.ts +39 -0
  816. package/dist/src/schema/mapping-advertisement-tags.d.ts.map +1 -0
  817. package/dist/src/schema/mapping-advertisement-tags.js +216 -0
  818. package/dist/src/schema/mapping-advertisement-tags.js.map +1 -0
  819. package/dist/src/schema/rename-rewriter.d.ts +45 -4
  820. package/dist/src/schema/rename-rewriter.d.ts.map +1 -1
  821. package/dist/src/schema/rename-rewriter.js +412 -19
  822. package/dist/src/schema/rename-rewriter.js.map +1 -1
  823. package/dist/src/schema/reserved-tags-policy.d.ts +32 -0
  824. package/dist/src/schema/reserved-tags-policy.d.ts.map +1 -0
  825. package/dist/src/schema/reserved-tags-policy.js +34 -0
  826. package/dist/src/schema/reserved-tags-policy.js.map +1 -0
  827. package/dist/src/schema/reserved-tags.d.ts +170 -0
  828. package/dist/src/schema/reserved-tags.d.ts.map +1 -0
  829. package/dist/src/schema/reserved-tags.js +507 -0
  830. package/dist/src/schema/reserved-tags.js.map +1 -0
  831. package/dist/src/schema/schema-differ.d.ts +158 -2
  832. package/dist/src/schema/schema-differ.d.ts.map +1 -1
  833. package/dist/src/schema/schema-differ.js +1460 -78
  834. package/dist/src/schema/schema-differ.js.map +1 -1
  835. package/dist/src/schema/schema-hasher.d.ts +8 -3
  836. package/dist/src/schema/schema-hasher.d.ts.map +1 -1
  837. package/dist/src/schema/schema-hasher.js +22 -2
  838. package/dist/src/schema/schema-hasher.js.map +1 -1
  839. package/dist/src/schema/schema.d.ts +25 -1
  840. package/dist/src/schema/schema.d.ts.map +1 -1
  841. package/dist/src/schema/schema.js +36 -2
  842. package/dist/src/schema/schema.js.map +1 -1
  843. package/dist/src/schema/table.d.ts +259 -10
  844. package/dist/src/schema/table.d.ts.map +1 -1
  845. package/dist/src/schema/table.js +309 -26
  846. package/dist/src/schema/table.js.map +1 -1
  847. package/dist/src/schema/unique-enforcement.d.ts +78 -0
  848. package/dist/src/schema/unique-enforcement.d.ts.map +1 -0
  849. package/dist/src/schema/unique-enforcement.js +93 -0
  850. package/dist/src/schema/unique-enforcement.js.map +1 -0
  851. package/dist/src/schema/view.d.ts +83 -2
  852. package/dist/src/schema/view.d.ts.map +1 -1
  853. package/dist/src/schema/view.js +67 -1
  854. package/dist/src/schema/view.js.map +1 -1
  855. package/dist/src/schema/window-function.d.ts +9 -1
  856. package/dist/src/schema/window-function.d.ts.map +1 -1
  857. package/dist/src/schema/window-function.js.map +1 -1
  858. package/dist/src/types/temporal-types.d.ts.map +1 -1
  859. package/dist/src/types/temporal-types.js +71 -36
  860. package/dist/src/types/temporal-types.js.map +1 -1
  861. package/dist/src/util/comparison.d.ts +24 -0
  862. package/dist/src/util/comparison.d.ts.map +1 -1
  863. package/dist/src/util/comparison.js +34 -0
  864. package/dist/src/util/comparison.js.map +1 -1
  865. package/dist/src/util/mutation-statement.d.ts.map +1 -1
  866. package/dist/src/util/mutation-statement.js +4 -1
  867. package/dist/src/util/mutation-statement.js.map +1 -1
  868. package/dist/src/util/serialization.d.ts +9 -0
  869. package/dist/src/util/serialization.d.ts.map +1 -1
  870. package/dist/src/util/serialization.js +26 -0
  871. package/dist/src/util/serialization.js.map +1 -1
  872. package/dist/src/vtab/backing-host.d.ts +286 -0
  873. package/dist/src/vtab/backing-host.d.ts.map +1 -0
  874. package/dist/src/vtab/backing-host.js +118 -0
  875. package/dist/src/vtab/backing-host.js.map +1 -0
  876. package/dist/src/vtab/best-access-plan.d.ts +21 -0
  877. package/dist/src/vtab/best-access-plan.d.ts.map +1 -1
  878. package/dist/src/vtab/best-access-plan.js.map +1 -1
  879. package/dist/src/vtab/capabilities.d.ts +5 -5
  880. package/dist/src/vtab/capabilities.d.ts.map +1 -1
  881. package/dist/src/vtab/mapping-advertisement.d.ts +163 -0
  882. package/dist/src/vtab/mapping-advertisement.d.ts.map +1 -0
  883. package/dist/src/vtab/mapping-advertisement.js +2 -0
  884. package/dist/src/vtab/mapping-advertisement.js.map +1 -0
  885. package/dist/src/vtab/memory/index.d.ts +64 -4
  886. package/dist/src/vtab/memory/index.d.ts.map +1 -1
  887. package/dist/src/vtab/memory/index.js +119 -12
  888. package/dist/src/vtab/memory/index.js.map +1 -1
  889. package/dist/src/vtab/memory/layer/base.d.ts +38 -1
  890. package/dist/src/vtab/memory/layer/base.d.ts.map +1 -1
  891. package/dist/src/vtab/memory/layer/base.js +112 -24
  892. package/dist/src/vtab/memory/layer/base.js.map +1 -1
  893. package/dist/src/vtab/memory/layer/manager.d.ts +291 -4
  894. package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
  895. package/dist/src/vtab/memory/layer/manager.js +1050 -91
  896. package/dist/src/vtab/memory/layer/manager.js.map +1 -1
  897. package/dist/src/vtab/memory/layer/plan-filter.d.ts.map +1 -1
  898. package/dist/src/vtab/memory/layer/plan-filter.js +35 -6
  899. package/dist/src/vtab/memory/layer/plan-filter.js.map +1 -1
  900. package/dist/src/vtab/memory/layer/scan-layer.d.ts.map +1 -1
  901. package/dist/src/vtab/memory/layer/scan-layer.js +66 -14
  902. package/dist/src/vtab/memory/layer/scan-layer.js.map +1 -1
  903. package/dist/src/vtab/memory/layer/scan-plan.d.ts +14 -0
  904. package/dist/src/vtab/memory/layer/scan-plan.d.ts.map +1 -1
  905. package/dist/src/vtab/memory/layer/scan-plan.js +27 -4
  906. package/dist/src/vtab/memory/layer/scan-plan.js.map +1 -1
  907. package/dist/src/vtab/memory/layer/transaction.d.ts.map +1 -1
  908. package/dist/src/vtab/memory/layer/transaction.js +5 -1
  909. package/dist/src/vtab/memory/layer/transaction.js.map +1 -1
  910. package/dist/src/vtab/memory/module.d.ts +17 -0
  911. package/dist/src/vtab/memory/module.d.ts.map +1 -1
  912. package/dist/src/vtab/memory/module.js +82 -3
  913. package/dist/src/vtab/memory/module.js.map +1 -1
  914. package/dist/src/vtab/memory/table.d.ts.map +1 -1
  915. package/dist/src/vtab/memory/table.js +15 -5
  916. package/dist/src/vtab/memory/table.js.map +1 -1
  917. package/dist/src/vtab/memory/types.d.ts +20 -2
  918. package/dist/src/vtab/memory/types.d.ts.map +1 -1
  919. package/dist/src/vtab/memory/utils/predicate.d.ts.map +1 -1
  920. package/dist/src/vtab/memory/utils/predicate.js +46 -24
  921. package/dist/src/vtab/memory/utils/predicate.js.map +1 -1
  922. package/dist/src/vtab/memory/utils/primary-key-encode.d.ts +31 -0
  923. package/dist/src/vtab/memory/utils/primary-key-encode.d.ts.map +1 -0
  924. package/dist/src/vtab/memory/utils/primary-key-encode.js +101 -0
  925. package/dist/src/vtab/memory/utils/primary-key-encode.js.map +1 -0
  926. package/dist/src/vtab/memory/utils/primary-key.d.ts +8 -0
  927. package/dist/src/vtab/memory/utils/primary-key.d.ts.map +1 -1
  928. package/dist/src/vtab/memory/utils/primary-key.js +12 -5
  929. package/dist/src/vtab/memory/utils/primary-key.js.map +1 -1
  930. package/dist/src/vtab/module.d.ts +203 -4
  931. package/dist/src/vtab/module.d.ts.map +1 -1
  932. package/dist/src/vtab/table.d.ts +9 -0
  933. package/dist/src/vtab/table.d.ts.map +1 -1
  934. package/dist/src/vtab/table.js.map +1 -1
  935. package/package.json +6 -5
@@ -1,5 +1,5 @@
1
1
  import { createLogger } from '../common/logger.js'; // Import logger
2
- import { Lexer, TokenType } from './lexer.js';
2
+ import { Lexer, TokenType, CONTEXTUAL_KEYWORDS } from './lexer.js';
3
3
  import { ConflictResolution } from '../common/constants.js';
4
4
  import { quereusError, QuereusError } from '../common/errors.js';
5
5
  import { StatusCode } from '../common/types.js';
@@ -32,8 +32,8 @@ function _createLoc(startToken, endToken) {
32
32
  /**
33
33
  * IMPORTANT: Any changes to parsed syntax must also be reflected in the corresponding emitters:
34
34
  * - packages/quereus/src/emit/ast-stringify.ts (AST-to-SQL string conversion)
35
- * - packages/quereus/src/schema/catalog.ts (DDL generation for catalog/hashing)
36
- * - packages/quereus-store/src/common/ddl-generator.ts (DDL generation for persistence)
35
+ * - packages/quereus/src/schema/catalog.ts (CREATE ASSERTION DDL for catalog/hashing)
36
+ * - packages/quereus/src/schema/ddl-generator.ts (canonical DDL generation for persistence)
37
37
  * If only the parser is updated, SQL round-trips and persisted schemas will silently lose the new syntax.
38
38
  */
39
39
  export class Parser {
@@ -168,20 +168,108 @@ export class Parser {
168
168
  const endToken = this.previous(); // Last token of the WITH clause
169
169
  return { type: 'with', recursive, ctes, options, loc: _createLoc(startToken, endToken) };
170
170
  }
171
+ /**
172
+ * Parses a relation-producing query expression (`QueryExpr`):
173
+ * `[WITH …] (SELECT | VALUES | INSERT|UPDATE|DELETE)`.
174
+ *
175
+ * `outerWithContext` is an outer WITH already consumed by the caller and
176
+ * forwarded into inner statements purely for CTE-resolution context (the
177
+ * planner reads CTE definitions out of `select.withClause`). It is NOT
178
+ * stored on the returned node — that already happened at the outer site.
179
+ *
180
+ * If the body itself leads with `WITH`, that inner WITH is consumed here
181
+ * and attached to the resulting statement. The two WITH clauses do not
182
+ * mix: an inner body-level WITH wins.
183
+ *
184
+ * `requireReturning` enforces the rule that DML used in non-top-level
185
+ * relation positions (FROM subquery, scalar / IN / EXISTS subquery,
186
+ * compound leg, CTE body, view body) must carry a RETURNING clause —
187
+ * the outer position consumes a relation, not a side-effect.
188
+ */
189
+ parseQueryExpr(outerWithContext, requireReturning = false) {
190
+ // Inner body-level WITH (e.g. `(WITH t AS (…) SELECT … FROM t)`). The
191
+ // inner WITH is owned by the produced statement; we still pass it down
192
+ // to the inner builder for the same resolution-context reason and
193
+ // re-attach explicitly so callers that swap it out (rename rewriter,
194
+ // declared-schema canonicaliser) see a consistent shape.
195
+ let innerWith;
196
+ if (this.check(TokenType.WITH)) {
197
+ innerWith = this.tryParseWithClause();
198
+ }
199
+ const resolutionContext = innerWith ?? outerWithContext;
200
+ const startToken = this.peek();
201
+ let stmt;
202
+ if (this.check(TokenType.LPAREN)) {
203
+ // A parenthesized query expression `( <query-expr> )` is a valid first
204
+ // operand (view body / CTE body / FROM-subquery / scalar subquery all
205
+ // funnel here). Read it, then run the shared left-associative tail so
206
+ // `(…) union …` chains. Pure grouping (no compound follows) returns the
207
+ // inner expression unchanged — `(select 1)` ≡ `select 1`.
208
+ const firstOperand = this.parseCompoundOperand(resolutionContext, requireReturning);
209
+ stmt = this.checkCompoundOperator()
210
+ ? this.parseCompoundTail(firstOperand, resolutionContext, startToken)
211
+ : firstOperand;
212
+ }
213
+ else {
214
+ const kw = startToken.lexeme.toUpperCase();
215
+ switch (kw) {
216
+ case 'SELECT':
217
+ this.advance();
218
+ stmt = this.selectStatement(startToken, resolutionContext);
219
+ break;
220
+ case 'VALUES':
221
+ this.advance();
222
+ stmt = this.valuesStatementWithOptionalCompound(startToken, resolutionContext);
223
+ break;
224
+ case 'INSERT':
225
+ this.advance();
226
+ stmt = this.insertStatement(startToken, resolutionContext);
227
+ break;
228
+ case 'UPDATE':
229
+ this.advance();
230
+ stmt = this.updateStatement(startToken, resolutionContext);
231
+ break;
232
+ case 'DELETE':
233
+ this.advance();
234
+ stmt = this.deleteStatement(startToken, resolutionContext);
235
+ break;
236
+ default:
237
+ throw this.error(startToken, "Expected SELECT, VALUES, INSERT, UPDATE, or DELETE in query expression.");
238
+ }
239
+ }
240
+ if (innerWith) {
241
+ if (this.statementSupportsWithClause(stmt)) {
242
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
243
+ stmt.withClause = innerWith;
244
+ if (innerWith.loc && stmt.loc) {
245
+ stmt.loc.start = innerWith.loc.start;
246
+ }
247
+ }
248
+ else {
249
+ throw this.error(this.previous(), `WITH clause cannot be used with ${stmt.type} statement.`);
250
+ }
251
+ }
252
+ if (requireReturning && (stmt.type === 'insert' || stmt.type === 'update' || stmt.type === 'delete')) {
253
+ if (!stmt.returning || stmt.returning.length === 0) {
254
+ throw this.error(this.previous(), `${stmt.type.toUpperCase()} in a relation position must have a RETURNING clause.`);
255
+ }
256
+ }
257
+ return stmt;
258
+ }
171
259
  /**
172
260
  * Parses a single Common Table Expression (CTE).
173
261
  * cte_name [(col1, col2, ...)] AS (query)
174
262
  */
175
263
  commonTableExpression() {
176
264
  const startToken = this.peek(); // Peek before consuming name
177
- const name = this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'like'], "Expected CTE name.");
265
+ const name = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected CTE name.");
178
266
  let endToken = this.previous(); // End token initially is the name
179
267
  let columns;
180
268
  if (this.match(TokenType.LPAREN)) {
181
269
  columns = [];
182
270
  if (!this.check(TokenType.RPAREN)) {
183
271
  do {
184
- columns.push(this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'like'], "Expected column name in CTE definition."));
272
+ columns.push(this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name in CTE definition."));
185
273
  } while (this.match(TokenType.COMMA) && !this.check(TokenType.RPAREN));
186
274
  }
187
275
  endToken = this.consume(TokenType.RPAREN, "Expected ')' after CTE column list.");
@@ -196,29 +284,8 @@ export class Parser {
196
284
  materializationHint = 'not_materialized';
197
285
  }
198
286
  this.consume(TokenType.LPAREN, "Expected '(' before CTE query.");
199
- // Parse the CTE query (can be SELECT, VALUES (via SELECT), INSERT, UPDATE, DELETE)
200
- const queryStartToken = this.peek();
201
- let query;
202
- if (this.check(TokenType.SELECT)) {
203
- this.advance(); // Consume SELECT token
204
- query = this.selectStatement(queryStartToken); // Pass start token
205
- }
206
- else if (this.check(TokenType.INSERT)) {
207
- this.advance(); // Consume INSERT token
208
- query = this.insertStatement(queryStartToken);
209
- }
210
- else if (this.check(TokenType.UPDATE)) {
211
- this.advance(); // Consume UPDATE token
212
- query = this.updateStatement(queryStartToken);
213
- }
214
- else if (this.check(TokenType.DELETE)) {
215
- this.advance(); // Consume DELETE token
216
- query = this.deleteStatement(queryStartToken);
217
- }
218
- // TODO: Add support for VALUES directly if needed (though VALUES is usually part of SELECT)
219
- else {
220
- throw this.error(this.peek(), "Expected SELECT, INSERT, UPDATE, or DELETE statement for CTE query.");
221
- }
287
+ // CTE body is any QueryExpr; DML bodies must carry RETURNING.
288
+ const query = this.parseQueryExpr(undefined, /*requireReturning*/ true);
222
289
  endToken = this.consume(TokenType.RPAREN, "Expected ')' after CTE query."); // Capture ')' as end token
223
290
  return { type: 'commonTableExpr', name, columns, query, materializationHint, loc: _createLoc(startToken, endToken) };
224
291
  }
@@ -235,89 +302,106 @@ export class Parser {
235
302
  // --- Check for specific keywords first ---
236
303
  const currentKeyword = startToken.lexeme.toUpperCase();
237
304
  let stmt;
238
- switch (currentKeyword) {
239
- case 'SELECT':
240
- this.advance();
241
- stmt = this.selectStatement(startToken, withClause);
242
- break;
243
- case 'INSERT':
244
- this.advance();
245
- stmt = this.insertStatement(startToken, withClause);
246
- break;
247
- case 'UPDATE':
248
- this.advance();
249
- stmt = this.updateStatement(startToken, withClause);
250
- break;
251
- case 'DELETE':
252
- this.advance();
253
- stmt = this.deleteStatement(startToken, withClause);
254
- break;
255
- case 'VALUES':
256
- this.advance();
257
- stmt = this.valuesStatement(startToken);
258
- break;
259
- case 'CREATE':
260
- this.advance();
261
- stmt = this.createStatement(startToken, withClause);
262
- break;
263
- case 'DROP':
264
- this.advance();
265
- stmt = this.dropStatement(startToken, withClause);
266
- break;
267
- case 'ALTER':
268
- this.advance();
269
- stmt = this.alterTableStatement(startToken, withClause);
270
- break;
271
- case 'BEGIN':
272
- this.advance();
273
- stmt = this.beginStatement(startToken, withClause);
274
- break;
275
- case 'COMMIT':
276
- this.advance();
277
- stmt = this.commitStatement(startToken, withClause);
278
- break;
279
- case 'ROLLBACK':
280
- this.advance();
281
- stmt = this.rollbackStatement(startToken, withClause);
282
- break;
283
- case 'SAVEPOINT':
284
- this.advance();
285
- stmt = this.savepointStatement(startToken, withClause);
286
- break;
287
- case 'RELEASE':
288
- this.advance();
289
- stmt = this.releaseStatement(startToken, withClause);
290
- break;
291
- // TODO: Replace pragmas with build-in functions
292
- case 'PRAGMA':
293
- this.advance();
294
- stmt = this.pragmaStatement(startToken, withClause);
295
- break;
296
- case 'ANALYZE':
297
- this.advance();
298
- stmt = this.analyzeStatement(startToken);
299
- break;
300
- case 'DECLARE':
301
- this.advance();
302
- stmt = this.declareSchemaStatement(startToken);
303
- break;
304
- case 'DIFF':
305
- this.advance();
306
- stmt = this.diffSchemaStatement(startToken);
307
- break;
308
- case 'APPLY':
309
- this.advance();
310
- stmt = this.applySchemaStatement(startToken);
311
- break;
312
- case 'EXPLAIN':
313
- this.advance();
314
- stmt = this.explainSchemaStatement(startToken);
315
- break;
316
- // --- Add default case ---
317
- default:
318
- // If it wasn't a recognized keyword starting the statement
319
- throw this.error(startToken, `Expected statement type (SELECT, INSERT, UPDATE, DELETE, VALUES, CREATE, etc.), got '${startToken.lexeme}'.`);
305
+ // A leading `(` is a parenthesized query expression at top level
306
+ // (`(select 1) union (select 2);` — SQLite parity). `parseQueryExpr`
307
+ // reads the operand and any compound tail; the post-switch WITH-attach
308
+ // below still folds an outer `with … (select ) union …` in.
309
+ if (this.check(TokenType.LPAREN)) {
310
+ stmt = this.parseQueryExpr(withClause);
320
311
  }
312
+ else
313
+ switch (currentKeyword) {
314
+ case 'SELECT':
315
+ this.advance();
316
+ stmt = this.selectStatement(startToken, withClause);
317
+ break;
318
+ case 'INSERT':
319
+ this.advance();
320
+ stmt = this.insertStatement(startToken, withClause);
321
+ break;
322
+ case 'UPDATE':
323
+ this.advance();
324
+ stmt = this.updateStatement(startToken, withClause);
325
+ break;
326
+ case 'DELETE':
327
+ this.advance();
328
+ stmt = this.deleteStatement(startToken, withClause);
329
+ break;
330
+ case 'VALUES':
331
+ this.advance();
332
+ stmt = this.valuesStatementWithOptionalCompound(startToken, withClause);
333
+ break;
334
+ case 'CREATE':
335
+ this.advance();
336
+ stmt = this.createStatement(startToken, withClause);
337
+ break;
338
+ case 'REFRESH':
339
+ this.advance();
340
+ stmt = this.refreshStatement(startToken, withClause);
341
+ break;
342
+ case 'DROP':
343
+ this.advance();
344
+ stmt = this.dropStatement(startToken, withClause);
345
+ break;
346
+ case 'ALTER':
347
+ this.advance();
348
+ stmt = this.alterStatement(startToken, withClause);
349
+ break;
350
+ case 'BEGIN':
351
+ this.advance();
352
+ stmt = this.beginStatement(startToken, withClause);
353
+ break;
354
+ case 'COMMIT':
355
+ this.advance();
356
+ stmt = this.commitStatement(startToken, withClause);
357
+ break;
358
+ case 'ROLLBACK':
359
+ this.advance();
360
+ stmt = this.rollbackStatement(startToken, withClause);
361
+ break;
362
+ case 'SAVEPOINT':
363
+ this.advance();
364
+ stmt = this.savepointStatement(startToken, withClause);
365
+ break;
366
+ case 'RELEASE':
367
+ this.advance();
368
+ stmt = this.releaseStatement(startToken, withClause);
369
+ break;
370
+ // TODO: Replace pragmas with build-in functions
371
+ case 'PRAGMA':
372
+ this.advance();
373
+ stmt = this.pragmaStatement(startToken, withClause);
374
+ break;
375
+ case 'ANALYZE':
376
+ this.advance();
377
+ stmt = this.analyzeStatement(startToken);
378
+ break;
379
+ case 'DECLARE': {
380
+ this.advance();
381
+ // `declare lens …` is a sibling statement, not a `declare schema`
382
+ // variant: branch on the contextual LENS keyword.
383
+ stmt = this.peekKeyword('LENS')
384
+ ? this.declareLensStatement(startToken)
385
+ : this.declareSchemaStatement(startToken);
386
+ break;
387
+ }
388
+ case 'DIFF':
389
+ this.advance();
390
+ stmt = this.diffSchemaStatement(startToken);
391
+ break;
392
+ case 'APPLY':
393
+ this.advance();
394
+ stmt = this.applySchemaStatement(startToken);
395
+ break;
396
+ case 'EXPLAIN':
397
+ this.advance();
398
+ stmt = this.explainSchemaStatement(startToken);
399
+ break;
400
+ // --- Add default case ---
401
+ default:
402
+ // If it wasn't a recognized keyword starting the statement
403
+ throw this.error(startToken, `Expected statement type (SELECT, INSERT, UPDATE, DELETE, VALUES, CREATE, etc.), got '${startToken.lexeme}'.`);
404
+ }
321
405
  // Attach WITH clause if present and supported
322
406
  if (withClause && this.statementSupportsWithClause(stmt)) {
323
407
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -356,62 +440,56 @@ export class Parser {
356
440
  this.matchKeyword('INTO'); // Handle missing keyword gracefully
357
441
  // Parse the table reference
358
442
  const table = this.tableIdentifier();
359
- const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
360
443
  // Parse column list if provided
361
444
  let columns;
362
445
  if (this.match(TokenType.LPAREN)) {
363
446
  columns = [];
364
447
  do {
365
- if (!this.checkIdentifierLike(contextualKeywords)) {
448
+ if (!this.checkIdentifierLike(CONTEXTUAL_KEYWORDS)) {
366
449
  throw this.error(this.peek(), "Expected column name.");
367
450
  }
368
451
  columns.push(this.getIdentifierValue(this.advance()));
369
452
  } while (this.match(TokenType.COMMA));
370
453
  this.consume(TokenType.RPAREN, "Expected ')' after column list.");
371
454
  }
372
- // Parse mutation context assignments if present (after column list, before VALUES/SELECT)
373
- // Note: Can also appear after VALUES/SELECT via parseTrailingWithClauses
455
+ // Parse mutation context assignments and/or tags if present (after column
456
+ // list, before VALUES/SELECT). Either may also appear trailing (after
457
+ // VALUES/SELECT) via parseTrailingWithClauses.
374
458
  let contextValues;
375
- if (this.matchKeyword('WITH')) {
459
+ let tags;
460
+ while (this.matchKeyword('WITH')) {
376
461
  if (this.matchKeyword('CONTEXT')) {
377
462
  contextValues = this.parseContextAssignments();
378
463
  }
464
+ else if (this.matchKeyword('TAGS')) {
465
+ tags = this.parseTags();
466
+ }
379
467
  else {
380
- // Not a WITH CONTEXT clause, backtrack
468
+ // Not a WITH CONTEXT / WITH TAGS clause, backtrack
381
469
  this.current--;
470
+ break;
382
471
  }
383
472
  }
384
- // Parse VALUES clause
385
- let values;
386
- let select;
387
- let lastConsumedToken = this.previous(); // After columns or table id
388
- if (this.match(TokenType.VALUES)) {
389
- values = [];
390
- do {
391
- this.consume(TokenType.LPAREN, "Expected '(' before values.");
392
- const valueList = [];
393
- if (!this.check(TokenType.RPAREN)) { // Check for empty value list
394
- do {
395
- valueList.push(this.expression());
396
- } while (this.match(TokenType.COMMA));
397
- }
398
- this.consume(TokenType.RPAREN, "Expected ')' after values.");
399
- values.push(valueList);
400
- lastConsumedToken = this.previous(); // Update after closing paren of value list
401
- } while (this.match(TokenType.COMMA));
402
- }
403
- else if (this.check(TokenType.SELECT)) { // If current token is SELECT
404
- // Handle INSERT ... SELECT
405
- // Consume the SELECT token, as selectStatement expects to start parsing after it.
406
- // selectKeywordToken will be the actual 'SELECT' token object, used for location.
407
- const selectKeywordToken = this.advance(); // Consume 'SELECT'
408
- // Pass the withClause so the embedded SELECT can (via the planner) resolve CTEs defined for the INSERT.
409
- select = this.selectStatement(selectKeywordToken, withClause);
410
- lastConsumedToken = this.previous(); // After SELECT statement is parsed
411
- }
412
- else {
413
- throw this.error(this.peek(), "Expected VALUES or SELECT after INSERT.");
473
+ // Parse the source: VALUES / SELECT / INSERT|UPDATE|DELETE with RETURNING.
474
+ // The outer INSERT consumes the resulting relation, so DML sources must
475
+ // carry RETURNING — parseQueryExpr enforces that when requireReturning
476
+ // is true. Pure VALUES / SELECT are passed through unchanged.
477
+ const sourceStartKeyword = this.peek().lexeme.toUpperCase();
478
+ let source;
479
+ switch (sourceStartKeyword) {
480
+ case 'VALUES':
481
+ case 'SELECT':
482
+ source = this.parseQueryExpr(withClause, /*requireReturning*/ false);
483
+ break;
484
+ case 'INSERT':
485
+ case 'UPDATE':
486
+ case 'DELETE':
487
+ source = this.parseQueryExpr(withClause, /*requireReturning*/ true);
488
+ break;
489
+ default:
490
+ throw this.error(this.peek(), "Expected VALUES, SELECT, or DML (with RETURNING) after INSERT.");
414
491
  }
492
+ let lastConsumedToken = this.previous(); // After source statement is parsed
415
493
  // Parse UPSERT clauses (ON CONFLICT DO ...) - can have multiple
416
494
  let upsertClauses;
417
495
  while (this.match(TokenType.ON)) {
@@ -438,6 +516,13 @@ export class Parser {
438
516
  }
439
517
  contextValues = trailingClauses.contextValues;
440
518
  }
519
+ if (trailingClauses.tags) {
520
+ if (tags) {
521
+ throw this.error(this.previous(), "Duplicate WITH TAGS clause");
522
+ }
523
+ tags = trailingClauses.tags;
524
+ lastConsumedToken = this.previous(); // After tags
525
+ }
441
526
  const schemaPath = trailingClauses.schemaPath;
442
527
  if (schemaPath) {
443
528
  lastConsumedToken = this.previous(); // After schema path
@@ -452,13 +537,13 @@ export class Parser {
452
537
  type: 'insert',
453
538
  table,
454
539
  columns,
455
- values,
456
- select,
540
+ source,
457
541
  onConflict,
458
542
  upsertClauses,
459
543
  returning,
460
544
  contextValues,
461
545
  schemaPath,
546
+ tags,
462
547
  loc: _createLoc(startToken, lastConsumedToken),
463
548
  };
464
549
  }
@@ -524,29 +609,19 @@ export class Parser {
524
609
  */
525
610
  selectStatement(startToken, withClause, isCompoundSubquery = false) {
526
611
  const start = startToken ?? this.previous(); // Use provided or the keyword token
527
- let lastConsumedToken = start; // Initialize lastConsumed
528
612
  const distinct = this.matchKeyword('DISTINCT');
529
- if (distinct)
530
- lastConsumedToken = this.previous();
531
613
  const all = !distinct && this.matchKeyword('ALL');
532
- if (all)
533
- lastConsumedToken = this.previous();
534
614
  // Parse column list
535
615
  const columns = this.columnList();
536
- if (columns.length > 0)
537
- lastConsumedToken = this.previous(); // Update after last column element
538
616
  // Parse FROM clause if present
539
617
  let from;
540
618
  if (this.match(TokenType.FROM)) {
541
619
  from = this.tableSourceList(withClause);
542
- if (from.length > 0)
543
- lastConsumedToken = this.previous(); // After last source/join
544
620
  }
545
621
  // Parse WHERE clause if present
546
622
  let where;
547
623
  if (this.match(TokenType.WHERE)) {
548
624
  where = this.expression();
549
- lastConsumedToken = this.previous(); // After where expression
550
625
  }
551
626
  // Parse GROUP BY clause if present
552
627
  let groupBy;
@@ -555,69 +630,180 @@ export class Parser {
555
630
  do {
556
631
  groupBy.push(this.expression());
557
632
  } while (this.match(TokenType.COMMA));
558
- lastConsumedToken = this.previous(); // After last group by expression
559
633
  }
560
634
  // Parse HAVING clause if present
561
635
  let having;
562
636
  if (this.match(TokenType.HAVING)) {
563
637
  having = this.expression();
564
- lastConsumedToken = this.previous(); // After having expression
565
638
  }
566
- // Parse WITH SCHEMA clause if present (must come before ORDER BY/LIMIT)
639
+ // Parse WITH SCHEMA clause if present (must come before compound / ORDER BY / LIMIT
640
+ // and is suppressed in compound-subquery position).
567
641
  let schemaPath;
568
642
  if (!isCompoundSubquery) {
569
643
  schemaPath = this.parseSchemaPath();
570
- if (schemaPath) {
571
- lastConsumedToken = this.previous(); // After schema path
572
- }
573
- }
574
- // Check for compound set operations (UNION / INTERSECT / EXCEPT) BEFORE ORDER BY/LIMIT
575
- let compound;
576
- if (this.match(TokenType.UNION, TokenType.INTERSECT, TokenType.EXCEPT, TokenType.DIFF)) {
577
- const tok = this.previous();
578
- let op;
579
- if (tok.type === TokenType.UNION) {
580
- if (this.match(TokenType.ALL)) {
581
- op = 'unionAll';
582
- }
583
- else {
584
- op = 'union';
585
- }
586
- }
587
- else if (tok.type === TokenType.INTERSECT) {
588
- op = 'intersect';
589
- }
590
- else if (tok.type === TokenType.EXCEPT) {
591
- op = 'except';
592
- }
593
- else {
594
- op = 'diff';
595
- }
596
- let rightSelect;
597
- // Handle parenthesized subquery after set operation
598
- if (this.match(TokenType.LPAREN)) {
599
- const selectToken = this.consume(TokenType.SELECT, "Expected 'SELECT' in parenthesized set operation.");
600
- rightSelect = this.selectStatement(selectToken, withClause, true); // Pass true to indicate compound subquery
601
- this.consume(TokenType.RPAREN, "Expected ')' after parenthesized set operation.");
602
- }
603
- else {
604
- // Handle direct SELECT statement
605
- const selectStartToken = this.peek();
606
- if (this.match(TokenType.SELECT)) {
607
- rightSelect = this.selectStatement(selectStartToken, withClause, true); // Pass true to indicate compound subquery
608
- }
609
- else {
610
- throw this.error(this.peek(), "Expected 'SELECT' or '(' after set operation keyword.");
611
- }
612
- }
613
- lastConsumedToken = this.previous();
614
- compound = { op, select: rightSelect };
615
644
  }
616
- // Parse ORDER BY clause if present (applies to final result after compound operations)
617
- // Skip if this is a compound subquery as ORDER BY belongs to the outer compound
618
- let orderBy;
619
- if (!isCompoundSubquery && this.match(TokenType.ORDER) && this.consume(TokenType.BY, "Expected 'BY' after 'ORDER'.")) {
620
- orderBy = [];
645
+ const sel = {
646
+ type: 'select',
647
+ columns,
648
+ from,
649
+ where,
650
+ groupBy,
651
+ having,
652
+ distinct,
653
+ all,
654
+ schemaPath,
655
+ loc: _createLoc(start, this.previous()),
656
+ };
657
+ // Compound set operations (UNION / INTERSECT / EXCEPT / DIFF) bind BEFORE
658
+ // ORDER BY / LIMIT — delegate to the shared left-associative tail parser.
659
+ const result = this.parseCompoundTail(sel, withClause, start);
660
+ // ORDER BY / LIMIT / OFFSET apply to the final compound result; suppressed
661
+ // for a compound subquery (they bind to the outer compound).
662
+ this.parseTrailingOrderLimit(result, isCompoundSubquery);
663
+ // Trailing `with defaults (col = expr, …)` — binds to the whole compound,
664
+ // after limit/offset and before any DDL-level `with tags`. Suppressed on a
665
+ // compound leg (it belongs to the outer compound, like trailing ORDER BY).
666
+ if (!isCompoundSubquery) {
667
+ const defaults = this.parseDefaultsClause();
668
+ if (defaults)
669
+ result.defaults = defaults;
670
+ }
671
+ result.loc = _createLoc(start, this.previous());
672
+ return result;
673
+ }
674
+ /** True when the next token is a compound set-operation keyword. */
675
+ checkCompoundOperator() {
676
+ return this.check(TokenType.UNION)
677
+ || this.check(TokenType.INTERSECT)
678
+ || this.check(TokenType.EXCEPT)
679
+ || this.check(TokenType.DIFF);
680
+ }
681
+ /**
682
+ * Decode a compound set-operation operator the caller has just consumed
683
+ * (via `match`), plus the optional `<setop> exists <branch> as <name>`
684
+ * membership-column clause(s) that sit between the operator keyword and the
685
+ * right leg. One-token lookahead (`exists` followed by `left`/`right`, never
686
+ * `(`) distinguishes the membership clause from the `exists (<subquery>)`
687
+ * predicate, which never legally begins a compound leg.
688
+ */
689
+ parseCompoundOperator() {
690
+ const tok = this.previous();
691
+ let op;
692
+ if (tok.type === TokenType.UNION) {
693
+ op = this.match(TokenType.ALL) ? 'unionAll' : 'union';
694
+ }
695
+ else if (tok.type === TokenType.INTERSECT) {
696
+ op = 'intersect';
697
+ }
698
+ else if (tok.type === TokenType.EXCEPT) {
699
+ op = 'except';
700
+ }
701
+ else {
702
+ op = 'diff';
703
+ }
704
+ const existence = this.setOpMembershipClauses(op);
705
+ return existence ? { op, existence } : { op };
706
+ }
707
+ /**
708
+ * Reads a single `query-term` operand of a set operation: either a
709
+ * parenthesized query expression `( <query-expr> )` (full recursion — WITH,
710
+ * nested compound, and the inner's OWN trailing ORDER BY / LIMIT all bind
711
+ * inside the parens) or a bare keyword-led operand (SELECT / VALUES / DML).
712
+ *
713
+ * Bare SELECT / VALUES legs parse with `isCompoundSubquery=true` so a
714
+ * trailing ORDER BY / LIMIT binds to the OUTER compound, and a bare SELECT
715
+ * leg greedily consumes the remaining unparenthesized chain (preserving the
716
+ * historical right-leaning shape). `requireReturning` is propagated so a
717
+ * DML operand without RETURNING in a leg position is rejected.
718
+ */
719
+ parseCompoundOperand(withClause, requireReturning) {
720
+ if (this.check(TokenType.LPAREN)) {
721
+ this.advance();
722
+ const inner = this.parseQueryExpr(withClause, requireReturning);
723
+ this.consume(TokenType.RPAREN, "Expected ')' after parenthesized query expression.");
724
+ return inner;
725
+ }
726
+ const start = this.peek();
727
+ if (this.check(TokenType.SELECT)) {
728
+ this.advance();
729
+ return this.selectStatement(start, withClause, /*isCompoundSubquery*/ true);
730
+ }
731
+ if (this.check(TokenType.VALUES)) {
732
+ this.advance();
733
+ return this.valuesStatementWithOptionalCompound(start, withClause, /*isCompoundSubquery*/ true);
734
+ }
735
+ if (this.check(TokenType.WITH) || this.check(TokenType.INSERT) || this.check(TokenType.UPDATE) || this.check(TokenType.DELETE)) {
736
+ return this.parseQueryExpr(undefined, requireReturning);
737
+ }
738
+ throw this.error(this.peek(), "Expected SELECT, VALUES, a DML statement, or '(' to begin a query operand.");
739
+ }
740
+ /**
741
+ * Wraps an arbitrary query expression as `select * from (<inner>) as
742
+ * <synthetic alias>` so the SELECT-level `compound` / ORDER BY / LIMIT slots
743
+ * apply to it. Generalized from the VALUES-compound wrapper; the synthetic
744
+ * alias is collision-proof per the inner's start offset (nested wraps live in
745
+ * distinct subquery scopes, so identical aliases never collide).
746
+ */
747
+ wrapAsSubquerySelect(inner, startToken) {
748
+ const syntheticAlias = `values_${startToken.startOffset}`;
749
+ return {
750
+ type: 'select',
751
+ columns: [{ type: 'all' }],
752
+ from: [{
753
+ type: 'subquerySource',
754
+ subquery: inner,
755
+ alias: syntheticAlias,
756
+ loc: inner.loc,
757
+ }],
758
+ loc: inner.loc,
759
+ };
760
+ }
761
+ /**
762
+ * Left-associative tail parser shared by every compound site. Consumes a
763
+ * chain of `compound-op [membership] query-term` after an already-parsed
764
+ * left operand and returns the resulting SELECT.
765
+ *
766
+ * A parenthesized operand iterates the loop (left-associative), wrapping the
767
+ * accumulator whenever it cannot host the new compound — either it is not a
768
+ * plain SELECT (e.g. a parenthesized VALUES/DML/compound operand) or its
769
+ * `compound` slot is already taken. The first BARE keyword operand greedily
770
+ * consumes the rest of the chain itself (its own `selectStatement` /
771
+ * `valuesStatementWithOptionalCompound` recursion), so an unparenthesized
772
+ * chain keeps today's right-leaning shape byte-for-byte. The user's explicit
773
+ * parentheses are the only escape hatch into left-grouping.
774
+ */
775
+ parseCompoundTail(left, withClause, startToken) {
776
+ let acc = left;
777
+ while (this.match(TokenType.UNION, TokenType.INTERSECT, TokenType.EXCEPT, TokenType.DIFF)) {
778
+ const { op, existence } = this.parseCompoundOperator();
779
+ const parenthesized = this.check(TokenType.LPAREN);
780
+ const right = this.parseCompoundOperand(withClause, /*requireReturning*/ true);
781
+ if (acc.type !== 'select' || acc.compound) {
782
+ acc = this.wrapAsSubquerySelect(acc, startToken);
783
+ }
784
+ acc.compound = existence ? { op, select: right, existence } : { op, select: right };
785
+ if (!parenthesized)
786
+ break; // a bare operand already consumed the remaining chain
787
+ }
788
+ // `left` is a SelectStmt at every call site that may take zero iterations
789
+ // (selectStatement / continueSelectAfterFrom); the parenthesized entry only
790
+ // delegates here once a compound operator follows, and any iteration leaves
791
+ // `acc` a SELECT — so the result is always a SelectStmt.
792
+ return acc;
793
+ }
794
+ /**
795
+ * Parses the trailing ORDER BY / LIMIT / OFFSET clauses that apply to the
796
+ * final result of a (possibly compound) SELECT, mutating `sel` in place.
797
+ * Suppressed entirely when `isCompoundSubquery` is set — those clauses then
798
+ * bind to the enclosing compound, not this leg. Shared by `selectStatement`
799
+ * and `continueSelectAfterFrom`.
800
+ */
801
+ parseTrailingOrderLimit(sel, isCompoundSubquery) {
802
+ if (isCompoundSubquery)
803
+ return;
804
+ // ORDER BY applies to the final result after compound operations.
805
+ if (this.match(TokenType.ORDER) && this.consume(TokenType.BY, "Expected 'BY' after 'ORDER'.")) {
806
+ sel.orderBy = [];
621
807
  do {
622
808
  const expr = this.expression();
623
809
  const direction = this.match(TokenType.DESC) ? 'desc' :
@@ -639,64 +825,42 @@ export class Parser {
639
825
  if (nulls) {
640
826
  orderClause.nulls = nulls;
641
827
  }
642
- orderBy.push(orderClause);
828
+ sel.orderBy.push(orderClause);
643
829
  } while (this.match(TokenType.COMMA));
644
- lastConsumedToken = this.previous(); // After last order by clause
645
- }
646
- // Parse LIMIT clause if present (applies to final result after compound operations)
647
- // Skip if this is a compound subquery as LIMIT belongs to the outer compound
648
- let limit;
649
- let offset;
650
- if (!isCompoundSubquery && this.match(TokenType.LIMIT)) {
651
- limit = this.expression();
652
- lastConsumedToken = this.previous(); // After limit expression
830
+ }
831
+ // LIMIT applies to the final result after compound operations.
832
+ if (this.match(TokenType.LIMIT)) {
833
+ sel.limit = this.expression();
653
834
  // LIMIT x OFFSET y syntax
654
835
  if (this.match(TokenType.OFFSET)) {
655
- offset = this.expression();
656
- lastConsumedToken = this.previous(); // After offset expression
836
+ sel.offset = this.expression();
657
837
  }
658
838
  // LIMIT x, y syntax (x is offset, y is limit)
659
839
  else if (this.match(TokenType.COMMA)) {
660
- offset = limit;
661
- limit = this.expression();
662
- lastConsumedToken = this.previous(); // After second limit expression
840
+ sel.offset = sel.limit;
841
+ sel.limit = this.expression();
663
842
  }
664
843
  }
665
- return {
666
- type: 'select',
667
- columns,
668
- from,
669
- where,
670
- groupBy,
671
- having,
672
- orderBy,
673
- limit,
674
- offset,
675
- distinct,
676
- all,
677
- compound,
678
- schemaPath,
679
- loc: _createLoc(start, lastConsumedToken),
680
- };
681
844
  }
682
845
  /**
683
846
  * Parse a comma-separated list of result columns for SELECT
684
847
  */
685
848
  columnList() {
686
849
  const columns = [];
687
- const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
688
850
  do {
689
851
  // Handle wildcard: * or table.*
690
852
  if (this.match(TokenType.ASTERISK)) {
691
853
  columns.push({ type: 'all' });
854
+ this.rejectInverseClauseOnStar();
692
855
  }
693
856
  // Handle table.* syntax
694
- else if (this.checkIdentifierLike(contextualKeywords) && this.checkNext(1, TokenType.DOT) &&
857
+ else if (this.checkIdentifierLike(CONTEXTUAL_KEYWORDS) && this.checkNext(1, TokenType.DOT) &&
695
858
  this.checkNext(2, TokenType.ASTERISK)) {
696
- const table = this.consumeIdentifier(contextualKeywords, "Expected table name before '.*'.");
859
+ const table = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected table name before '.*'.");
697
860
  this.advance(); // consume DOT
698
861
  this.advance(); // consume ASTERISK
699
862
  columns.push({ type: 'all', table });
863
+ this.rejectInverseClauseOnStar();
700
864
  }
701
865
  // Handle regular column expression
702
866
  else {
@@ -704,7 +868,7 @@ export class Parser {
704
868
  let alias;
705
869
  // Handle AS alias or just alias
706
870
  if (this.match(TokenType.AS)) {
707
- if (this.checkIdentifierLike(contextualKeywords) || this.check(TokenType.STRING)) {
871
+ if (this.checkIdentifierLike(CONTEXTUAL_KEYWORDS) || this.check(TokenType.STRING)) {
708
872
  const aliasToken = this.advance();
709
873
  // For STRING tokens, use literal; for identifiers, use getIdentifierValue
710
874
  alias = aliasToken.type === TokenType.STRING
@@ -724,7 +888,10 @@ export class Parser {
724
888
  const aliasToken = this.advance();
725
889
  alias = this.getIdentifierValue(aliasToken);
726
890
  }
727
- columns.push({ type: 'column', expr, alias });
891
+ // Optional trailing `with inverse (col = expr, …)` — authored
892
+ // write-back expressions (docs/view-updateability.md § Authored inverses)
893
+ const inverse = this.parseInverseClause();
894
+ columns.push({ type: 'column', expr, alias, inverse });
728
895
  }
729
896
  } while (this.match(TokenType.COMMA));
730
897
  return columns;
@@ -737,7 +904,7 @@ export class Parser {
737
904
  let schema;
738
905
  let name;
739
906
  let endToken = startToken;
740
- const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like', 'temp', 'temporary'];
907
+ const contextualKeywords = [...CONTEXTUAL_KEYWORDS, 'temp', 'temporary'];
741
908
  // Check for schema.table pattern
742
909
  if (this.checkIdentifierLike(contextualKeywords) && this.checkNext(1, TokenType.DOT)) {
743
910
  schema = this.consumeIdentifier(contextualKeywords, "Expected schema name.");
@@ -780,23 +947,15 @@ export class Parser {
780
947
  */
781
948
  tableSource(withClause) {
782
949
  const startToken = this.peek();
783
- const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
784
- // Check for subquery: ( SELECT ... or ( VALUES ... or ( WITH ... or ( INSERT/UPDATE/DELETE ...
785
- if (this.check(TokenType.LPAREN)) {
786
- // Look ahead to see if this is a subquery
787
- const lookahead = this.current + 1;
788
- if (lookahead < this.tokens.length) {
789
- const nextTokenType = this.tokens[lookahead].type;
790
- if (nextTokenType === TokenType.SELECT || nextTokenType === TokenType.VALUES || nextTokenType === TokenType.WITH) {
791
- return this.subquerySource(startToken, withClause);
792
- }
793
- else if (nextTokenType === TokenType.INSERT || nextTokenType === TokenType.UPDATE || nextTokenType === TokenType.DELETE) {
794
- return this.mutatingSubquerySource(startToken, withClause);
795
- }
796
- }
950
+ // Subquery: any QueryExpr in parens. Decision is made on the token
951
+ // immediately after `(`; all relation-producing forms (SELECT, VALUES,
952
+ // WITH …, INSERT|UPDATE|DELETE w/ RETURNING) flow through the same
953
+ // subquerySource path.
954
+ if (this.startsParenSubquery()) {
955
+ return this.subquerySource(startToken, withClause);
797
956
  }
798
957
  // Check for function call syntax: IDENTIFIER (
799
- if (this.checkIdentifierLike(contextualKeywords) && this.checkNext(1, TokenType.LPAREN)) {
958
+ if (this.checkIdentifierLike(CONTEXTUAL_KEYWORDS) && this.checkNext(1, TokenType.LPAREN)) {
800
959
  return this.functionSource(startToken);
801
960
  }
802
961
  // Otherwise, assume it's a standard table source
@@ -804,31 +963,22 @@ export class Parser {
804
963
  return this.standardTableSource(startToken);
805
964
  }
806
965
  }
807
- /** Parses a subquery source: (SELECT ...) AS alias */
808
- subquerySource(startToken, withClause) {
966
+ /**
967
+ * Parses a subquery source: `(<QueryExpr>) [AS alias [(cols)]]`.
968
+ *
969
+ * Accepts any relation-producing form (SELECT, VALUES, WITH …, or
970
+ * INSERT/UPDATE/DELETE with RETURNING). The DML branch is enforced to
971
+ * carry RETURNING because the outer FROM-clause position consumes a
972
+ * relation, not a side-effect.
973
+ *
974
+ * `requireAlias` mandates an explicit user-written alias rather than synthesizing
975
+ * a default — set for an inline-subquery DML *write target* (`update (select …) as
976
+ * v set …`), whose `alias` is user-meaningful (the `where`/`set` reference it). A
977
+ * FROM-clause subquery leaves it false and keeps the generated-alias fallback.
978
+ */
979
+ subquerySource(startToken, withClause, requireAlias = false) {
809
980
  this.consume(TokenType.LPAREN, "Expected '(' before subquery.");
810
- let subquery;
811
- if (this.check(TokenType.WITH)) {
812
- // (WITH ... SELECT ...) — the WITH attaches to the inner SELECT.
813
- const innerWith = this.tryParseWithClause();
814
- const selectToken = this.consume(TokenType.SELECT, "Expected 'SELECT' after WITH in subquery.");
815
- const sel = this.selectStatement(selectToken, innerWith);
816
- sel.withClause = innerWith;
817
- subquery = sel;
818
- }
819
- else if (this.check(TokenType.SELECT)) {
820
- // Consume the SELECT token and pass it as startToken to selectStatement
821
- const selectToken = this.advance();
822
- subquery = this.selectStatement(selectToken, withClause);
823
- }
824
- else if (this.check(TokenType.VALUES)) {
825
- // Handle VALUES subquery
826
- const valuesToken = this.advance();
827
- subquery = this.valuesStatement(valuesToken);
828
- }
829
- else {
830
- throw this.error(this.peek(), "Expected 'SELECT', 'VALUES', or 'WITH' in subquery.");
831
- }
981
+ const subquery = this.parseQueryExpr(withClause, /*requireReturning*/ true);
832
982
  this.consume(TokenType.RPAREN, "Expected ')' after subquery.");
833
983
  // Parse optional alias for subquery
834
984
  let alias;
@@ -846,17 +996,24 @@ export class Parser {
846
996
  !this.isEndOfClause()) {
847
997
  alias = this.getIdentifierValue(this.advance());
848
998
  }
999
+ else if (requireAlias) {
1000
+ // A subquery write target's alias is mandatory and user-meaningful — never a
1001
+ // generated default (the `where`/`set` reference it). Reject the bare form.
1002
+ throw this.error(this.peek(), "a subquery UPDATE/DELETE write target requires an alias, e.g. (select …) as v");
1003
+ }
849
1004
  else {
850
- // Generate a default alias if none provided
851
- alias = `subquery_${startToken.startOffset}`;
1005
+ // Generate a default alias if none provided. Keep separate prefixes
1006
+ // for read-only vs mutating bodies so generated aliases stay
1007
+ // distinguishable when surfacing in diagnostics.
1008
+ const isMutating = subquery.type === 'insert' || subquery.type === 'update' || subquery.type === 'delete';
1009
+ alias = `${isMutating ? 'mutating_subquery' : 'subquery'}_${startToken.startOffset}`;
852
1010
  }
853
1011
  // Parse optional column list after alias: AS alias(col1, col2, ...)
854
1012
  if (this.match(TokenType.LPAREN)) {
855
1013
  columns = [];
856
- const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
857
1014
  if (!this.check(TokenType.RPAREN)) {
858
1015
  do {
859
- columns.push(this.consumeIdentifier(contextualKeywords, "Expected column name in alias column list."));
1016
+ columns.push(this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name in alias column list."));
860
1017
  } while (this.match(TokenType.COMMA) && !this.check(TokenType.RPAREN));
861
1018
  }
862
1019
  this.consume(TokenType.RPAREN, "Expected ')' after alias column list.");
@@ -870,80 +1027,49 @@ export class Parser {
870
1027
  loc: _createLoc(startToken, endToken),
871
1028
  };
872
1029
  }
873
- /** Parses a mutating subquery source: (INSERT/UPDATE/DELETE ... RETURNING ...) AS alias */
874
- mutatingSubquerySource(startToken, withClause) {
875
- this.consume(TokenType.LPAREN, "Expected '(' before mutating subquery.");
876
- let stmt;
877
- if (this.check(TokenType.INSERT)) {
878
- const insertToken = this.advance();
879
- stmt = this.insertStatement(insertToken, withClause);
880
- }
881
- else if (this.check(TokenType.UPDATE)) {
882
- const updateToken = this.advance();
883
- stmt = this.updateStatement(updateToken, withClause);
884
- }
885
- else if (this.check(TokenType.DELETE)) {
886
- const deleteToken = this.advance();
887
- stmt = this.deleteStatement(deleteToken, withClause);
888
- }
889
- else {
890
- throw this.error(this.peek(), "Expected 'INSERT', 'UPDATE', or 'DELETE' in mutating subquery.");
891
- }
892
- // Validate that the statement has a RETURNING clause
893
- if (!stmt.returning || stmt.returning.length === 0) {
894
- throw this.error(this.previous(), "Mutating subqueries must have a RETURNING clause to be used as table sources.");
895
- }
896
- this.consume(TokenType.RPAREN, "Expected ')' after mutating subquery.");
897
- // Parse optional alias for mutating subquery
898
- let alias;
899
- let columns;
900
- if (this.match(TokenType.AS)) {
901
- if (!this.checkIdentifierLike([])) {
902
- throw this.error(this.peek(), "Expected alias after 'AS'.");
903
- }
904
- alias = this.getIdentifierValue(this.advance());
905
- }
906
- else if (this.checkIdentifierLike([]) &&
907
- !this.checkNext(1, TokenType.DOT) &&
908
- !this.checkNext(1, TokenType.COMMA) &&
909
- !this.isJoinToken() &&
910
- !this.isEndOfClause()) {
911
- alias = this.getIdentifierValue(this.advance());
912
- }
913
- else {
914
- // Generate a default alias if none provided
915
- alias = `mutating_subquery_${startToken.startOffset}`;
916
- }
917
- // Parse optional column list after alias: AS alias(col1, col2, ...)
918
- if (this.match(TokenType.LPAREN)) {
919
- columns = [];
920
- const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
921
- if (!this.check(TokenType.RPAREN)) {
922
- do {
923
- columns.push(this.consumeIdentifier(contextualKeywords, "Expected column name in alias column list."));
924
- } while (this.match(TokenType.COMMA) && !this.check(TokenType.RPAREN));
925
- }
926
- this.consume(TokenType.RPAREN, "Expected ')' after alias column list.");
927
- }
928
- const endToken = this.previous();
929
- return {
930
- type: 'mutatingSubquerySource',
931
- stmt,
932
- alias,
933
- columns,
934
- loc: _createLoc(startToken, endToken),
935
- };
1030
+ /**
1031
+ * Detect and parse a leading inline-subquery DML write target — `(<query>) as v` —
1032
+ * for `update`/`delete`, or `undefined` when the next tokens are not a `(` followed
1033
+ * by a relation-producing keyword (the SAME lookahead {@link tableSource} uses for a
1034
+ * FROM subquery). The alias is mandatory (`requireAlias`); the body re-uses the
1035
+ * shared {@link subquerySource} production, so it requires RETURNING on a DML body
1036
+ * (rejected as a write target downstream) and parses the `as v` / `v(a,b)` alias.
1037
+ */
1038
+ subqueryDmlTarget(withClause) {
1039
+ if (!this.startsParenSubquery())
1040
+ return undefined;
1041
+ return this.subquerySource(this.peek(), withClause, /*requireAlias*/ true);
1042
+ }
1043
+ /**
1044
+ * True when the cursor is at `(` followed by a relation-producing keyword
1045
+ * (`SELECT` / `VALUES` / `WITH` / a DML keyword) — i.e. the start of a
1046
+ * parenthesized subquery source. Shared by the FROM-clause {@link tableSource}
1047
+ * and the inline-subquery DML target {@link subqueryDmlTarget} so the lookahead
1048
+ * set stays defined once.
1049
+ */
1050
+ startsParenSubquery() {
1051
+ if (!this.check(TokenType.LPAREN))
1052
+ return false;
1053
+ const lookahead = this.current + 1;
1054
+ if (lookahead >= this.tokens.length)
1055
+ return false;
1056
+ const nextTokenType = this.tokens[lookahead].type;
1057
+ return (nextTokenType === TokenType.SELECT
1058
+ || nextTokenType === TokenType.VALUES
1059
+ || nextTokenType === TokenType.WITH
1060
+ || nextTokenType === TokenType.INSERT
1061
+ || nextTokenType === TokenType.UPDATE
1062
+ || nextTokenType === TokenType.DELETE);
936
1063
  }
937
1064
  /** Parses a standard table source (schema.table or table) */
938
1065
  standardTableSource(startToken) {
939
- const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
940
1066
  // Parse table name (potentially schema-qualified)
941
1067
  const table = this.tableIdentifier();
942
1068
  let endToken = this.previous(); // Initialize endToken after parsing table identifier
943
1069
  // Parse optional alias
944
1070
  let alias;
945
1071
  if (this.match(TokenType.AS)) {
946
- if (!this.checkIdentifierLike(contextualKeywords)) {
1072
+ if (!this.checkIdentifierLike(CONTEXTUAL_KEYWORDS)) {
947
1073
  throw this.error(this.peek(), "Expected alias after 'AS'.");
948
1074
  }
949
1075
  const aliasToken = this.advance();
@@ -968,7 +1094,6 @@ export class Parser {
968
1094
  }
969
1095
  /** Parses a table-valued function source: name(arg1, ...) [AS alias] */
970
1096
  functionSource(startToken) {
971
- const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
972
1097
  const name = this.tableIdentifier(); // name has its own loc
973
1098
  let endToken = this.previous(); // Initialize endToken after parsing function identifier
974
1099
  this.consume(TokenType.LPAREN, "Expected '(' after table function name.");
@@ -999,7 +1124,7 @@ export class Parser {
999
1124
  let alias;
1000
1125
  let columns;
1001
1126
  if (this.match(TokenType.AS)) {
1002
- if (!this.checkIdentifierLike(contextualKeywords)) {
1127
+ if (!this.checkIdentifierLike(CONTEXTUAL_KEYWORDS)) {
1003
1128
  throw this.error(this.peek(), "Expected alias after 'AS'.");
1004
1129
  }
1005
1130
  const aliasToken = this.advance();
@@ -1018,10 +1143,9 @@ export class Parser {
1018
1143
  // Optional column list after alias: alias(col1, col2, ...)
1019
1144
  if (alias && this.match(TokenType.LPAREN)) {
1020
1145
  columns = [];
1021
- const colKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
1022
1146
  if (!this.check(TokenType.RPAREN)) {
1023
1147
  do {
1024
- columns.push(this.consumeIdentifier(colKeywords, "Expected column name in alias column list."));
1148
+ columns.push(this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name in alias column list."));
1025
1149
  } while (this.match(TokenType.COMMA) && !this.check(TokenType.RPAREN));
1026
1150
  }
1027
1151
  endToken = this.consume(TokenType.RPAREN, "Expected ')' after alias column list.");
@@ -1077,15 +1201,22 @@ export class Parser {
1077
1201
  else if (this.match(TokenType.USING)) {
1078
1202
  this.consume(TokenType.LPAREN, "Expected '(' after 'USING'.");
1079
1203
  columns = [];
1080
- const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
1081
1204
  do {
1082
- columns.push(this.consumeIdentifier(contextualKeywords, "Expected column name."));
1205
+ columns.push(this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name."));
1083
1206
  } while (this.match(TokenType.COMMA));
1084
1207
  endToken = this.consume(TokenType.RPAREN, "Expected ')' after columns.");
1085
1208
  }
1086
1209
  else if (joinType !== 'cross') {
1087
1210
  throw this.error(this.peek(), "Expected 'ON' or 'USING' after JOIN.");
1088
1211
  }
1212
+ // Optional `exists [left|right] as <name>` existence column clause(s), after a
1213
+ // complete ON/USING predicate. One-token lookahead after `exists` (an `as` or
1214
+ // side token, never `(`) distinguishes this from the `exists (<subquery>)`
1215
+ // predicate; the comma form is recognised only when followed by another
1216
+ // `exists`, so a genuine new FROM source comma is left for `tableSourceList`.
1217
+ const existence = this.joinExistenceClauses(joinType);
1218
+ if (existence)
1219
+ endToken = this.previous();
1089
1220
  return {
1090
1221
  type: 'join',
1091
1222
  joinType,
@@ -1094,9 +1225,99 @@ export class Parser {
1094
1225
  condition,
1095
1226
  columns,
1096
1227
  isLateral: isLateral || undefined,
1228
+ existence,
1097
1229
  loc: _createLoc(joinStartToken, endToken),
1098
1230
  };
1099
1231
  }
1232
+ /**
1233
+ * Parse the optional comma-separated `exists [left|right] as <name>` clauses
1234
+ * trailing a join. Returns `undefined` when none are present. Resolves and
1235
+ * validates the side against the join type (default = the unique non-preserved
1236
+ * side; explicit side required for `full`; `inner`/`cross` rejected — no
1237
+ * null-extension means the flag would be a meaningless constant `true`).
1238
+ */
1239
+ joinExistenceClauses(joinType) {
1240
+ // `exists` here must be followed by `as` or a side token, never `(` (which
1241
+ // would be the `exists (<subquery>)` predicate). Bail before consuming if the
1242
+ // lookahead does not match the clause shape.
1243
+ const atExistenceClause = () => this.check(TokenType.EXISTS) &&
1244
+ (this.checkNext(1, TokenType.AS) || this.checkNext(1, TokenType.LEFT) || this.checkNext(1, TokenType.RIGHT));
1245
+ if (!atExistenceClause())
1246
+ return undefined;
1247
+ const result = [];
1248
+ do {
1249
+ this.consume(TokenType.EXISTS, "Expected 'exists'.");
1250
+ let explicitSide;
1251
+ if (this.match(TokenType.LEFT))
1252
+ explicitSide = 'left';
1253
+ else if (this.match(TokenType.RIGHT))
1254
+ explicitSide = 'right';
1255
+ this.consume(TokenType.AS, "Expected 'as' after 'exists' join existence clause.");
1256
+ const name = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected name after 'exists ... as'.");
1257
+ result.push({ side: this.resolveExistenceSide(joinType, explicitSide), name });
1258
+ // Continue only on `, exists ...`; a plain comma starts a new FROM source.
1259
+ } while (this.check(TokenType.COMMA) && this.checkNext(1, TokenType.EXISTS) && this.advance());
1260
+ return result;
1261
+ }
1262
+ /**
1263
+ * Resolve and validate the side of an `exists [<side>] as` join existence
1264
+ * clause. The flag must reference a null-extendable (non-preserved) side.
1265
+ */
1266
+ resolveExistenceSide(joinType, explicitSide) {
1267
+ // Non-preserved (null-extendable) sides per join type.
1268
+ const nonPreserved = joinType === 'left' ? ['right']
1269
+ : joinType === 'right' ? ['left']
1270
+ : joinType === 'full' ? ['left', 'right']
1271
+ : []; // inner / cross: neither side null-extends
1272
+ if (nonPreserved.length === 0) {
1273
+ throw this.error(this.previous(), `'exists ... as' is not valid on an ${joinType.toUpperCase()} join (no side is null-extended, so the flag would be a constant true)`);
1274
+ }
1275
+ if (explicitSide) {
1276
+ if (!nonPreserved.includes(explicitSide)) {
1277
+ throw this.error(this.previous(), `'exists ${explicitSide} as' references the preserved side of a ${joinType.toUpperCase()} join; only the non-preserved side (${nonPreserved.join('/')}) has a meaningful match flag`);
1278
+ }
1279
+ return explicitSide;
1280
+ }
1281
+ if (nonPreserved.length > 1) {
1282
+ throw this.error(this.previous(), `'exists as' is ambiguous on a ${joinType.toUpperCase()} join — specify 'exists left as' or 'exists right as'`);
1283
+ }
1284
+ return nonPreserved[0];
1285
+ }
1286
+ /**
1287
+ * Parse the optional comma-separated `exists <branch> as <name>` membership
1288
+ * clauses that sit between a set-operation keyword and its right leg. Returns
1289
+ * `undefined` when none are present. The `branch` is mandatory (`left` = the leg
1290
+ * already parsed before the operator, `right` = the operand that follows) — there
1291
+ * is no elided form, so `exists` here is ALWAYS followed by `left`/`right`, never
1292
+ * `(`; that one-token lookahead distinguishes the clause from the
1293
+ * `exists (<subquery>)` predicate. Rejected on `diff` (symmetric difference
1294
+ * desugars to two `except`s, so membership is ambiguous).
1295
+ */
1296
+ setOpMembershipClauses(op) {
1297
+ const atMembershipClause = () => this.check(TokenType.EXISTS) &&
1298
+ (this.checkNext(1, TokenType.LEFT) || this.checkNext(1, TokenType.RIGHT));
1299
+ if (!atMembershipClause())
1300
+ return undefined;
1301
+ if (op === 'diff') {
1302
+ throw this.error(this.peek(), "'exists <branch> as' membership columns are not valid on DIFF — symmetric difference desugars to two EXCEPTs, so branch membership is ambiguous");
1303
+ }
1304
+ const result = [];
1305
+ do {
1306
+ this.consume(TokenType.EXISTS, "Expected 'exists'.");
1307
+ let branch;
1308
+ if (this.match(TokenType.LEFT))
1309
+ branch = 'left';
1310
+ else if (this.match(TokenType.RIGHT))
1311
+ branch = 'right';
1312
+ else
1313
+ throw this.error(this.peek(), "Expected 'left' or 'right' after 'exists' in a set-operation membership clause.");
1314
+ this.consume(TokenType.AS, "Expected 'as' after 'exists <branch>' membership clause.");
1315
+ const name = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected name after 'exists <branch> as'.");
1316
+ result.push({ branch, name });
1317
+ // Continue only on `, exists ...`; a plain comma starts the next leg / clause boundary.
1318
+ } while (this.check(TokenType.COMMA) && this.checkNext(1, TokenType.EXISTS) && this.advance());
1319
+ return result;
1320
+ }
1100
1321
  /**
1101
1322
  * Parse a left-associative chain of binary operators.
1102
1323
  * Captures the start token before parsing the first operand, avoiding O(n) token lookups.
@@ -1146,12 +1367,16 @@ export class Parser {
1146
1367
  const right = this.notExpression();
1147
1368
  return { type: 'unary', operator: 'NOT', expr: right, loc: _createLoc(operatorToken, this.previous()) };
1148
1369
  }
1149
- return this.isNull();
1370
+ return this.isPredicate();
1150
1371
  }
1151
1372
  /**
1152
- * Parse IS NULL / IS NOT NULL expressions
1373
+ * Parse the postfix IS predicates, each a unary postfix operator on the
1374
+ * operand: `IS [NOT] NULL`, `IS [NOT] TRUE`, `IS [NOT] FALSE`. A general
1375
+ * `IS <expr>` (anything other than NULL/TRUE/FALSE) is unsupported — we
1376
+ * backtrack the consumed `IS [NOT]` so the caller surfaces the same error
1377
+ * as before.
1153
1378
  */
1154
- isNull() {
1379
+ isPredicate() {
1155
1380
  const startToken = this.peek();
1156
1381
  const expr = this.equality();
1157
1382
  if (this.match(TokenType.IS)) {
@@ -1160,7 +1385,15 @@ export class Parser {
1160
1385
  const operator = isNot ? 'IS NOT NULL' : 'IS NULL';
1161
1386
  return { type: 'unary', operator, expr, loc: _createLoc(startToken, this.previous()) };
1162
1387
  }
1163
- // IS [NOT] not followed by NULL — backtrack
1388
+ if (this.match(TokenType.TRUE)) {
1389
+ const operator = isNot ? 'IS NOT TRUE' : 'IS TRUE';
1390
+ return { type: 'unary', operator, expr, loc: _createLoc(startToken, this.previous()) };
1391
+ }
1392
+ if (this.match(TokenType.FALSE)) {
1393
+ const operator = isNot ? 'IS NOT FALSE' : 'IS FALSE';
1394
+ return { type: 'unary', operator, expr, loc: _createLoc(startToken, this.previous()) };
1395
+ }
1396
+ // IS [NOT] not followed by NULL/TRUE/FALSE — backtrack the IS [NOT].
1164
1397
  if (isNot)
1165
1398
  this.current--;
1166
1399
  this.current--;
@@ -1193,10 +1426,9 @@ export class Parser {
1193
1426
  if (this.match(TokenType.IN)) {
1194
1427
  // NOT IN
1195
1428
  this.consume(TokenType.LPAREN, "Expected '(' after NOT IN.");
1196
- if (this.check(TokenType.SELECT)) {
1197
- // NOT IN subquery: expr NOT IN (SELECT ...)
1198
- const selectToken = this.advance(); // Consume SELECT
1199
- const subquery = this.selectStatement(selectToken);
1429
+ if (this.checkSubqueryStart()) {
1430
+ // NOT IN subquery: expr NOT IN (<QueryExpr>)
1431
+ const subquery = this.parseQueryExpr(undefined, /*requireReturning*/ true);
1200
1432
  const endToken = this.consume(TokenType.RPAREN, "Expected ')' after NOT IN subquery.");
1201
1433
  // Create an IN expression with subquery, then wrap in NOT
1202
1434
  const inExpr = {
@@ -1306,13 +1538,12 @@ export class Parser {
1306
1538
  };
1307
1539
  }
1308
1540
  else if (operatorToken.type === TokenType.IN) {
1309
- // Parse IN expression: expr IN (value1, value2, ...) or expr IN (subquery)
1541
+ // Parse IN expression: expr IN (value1, value2, ...) or expr IN (<QueryExpr>)
1310
1542
  this.consume(TokenType.LPAREN, "Expected '(' after IN.");
1311
1543
  // Check if this is a subquery or value list
1312
- if (this.check(TokenType.SELECT)) {
1313
- // IN subquery: expr IN (SELECT ...)
1314
- const selectToken = this.advance(); // Consume SELECT
1315
- const subquery = this.selectStatement(selectToken);
1544
+ if (this.checkSubqueryStart()) {
1545
+ // IN subquery: expr IN (<QueryExpr>)
1546
+ const subquery = this.parseQueryExpr(undefined, /*requireReturning*/ true);
1316
1547
  const endToken = this.consume(TokenType.RPAREN, "Expected ')' after IN subquery.");
1317
1548
  // Create an IN expression with subquery
1318
1549
  expr = {
@@ -1410,7 +1641,9 @@ export class Parser {
1410
1641
  const expr = this.jsonPath();
1411
1642
  if (this.matchKeyword('COLLATE')) {
1412
1643
  const collationToken = this.consume(TokenType.IDENTIFIER, "Expected collation name after COLLATE.");
1413
- return { type: 'collate', expr, collation: collationToken.lexeme, loc: _createLoc(startToken, collationToken) };
1644
+ // getIdentifierValue strips the quotes from a quoted collation name (e.g.
1645
+ // `collate "select"`); using the raw lexeme would embed them in the value.
1646
+ return { type: 'collate', expr, collation: this.getIdentifierValue(collationToken), loc: _createLoc(startToken, collationToken) };
1414
1647
  }
1415
1648
  return expr;
1416
1649
  }
@@ -1493,12 +1726,11 @@ export class Parser {
1493
1726
  const endToken = this.consume(TokenType.RPAREN, "Expected ')' after CAST expression type.");
1494
1727
  return { type: 'cast', expr, targetType, loc: _createLoc(castToken, endToken) };
1495
1728
  }
1496
- // EXISTS expression: EXISTS(SELECT ...)
1729
+ // EXISTS expression: EXISTS(<QueryExpr>)
1497
1730
  if (this.match(TokenType.EXISTS)) {
1498
1731
  const existsToken = this.previous();
1499
1732
  this.consume(TokenType.LPAREN, "Expected '(' after EXISTS.");
1500
- const selectToken = this.consume(TokenType.SELECT, "Expected 'SELECT' in EXISTS subquery.");
1501
- const subquery = this.selectStatement(selectToken);
1733
+ const subquery = this.parseQueryExpr(undefined, /*requireReturning*/ true);
1502
1734
  const endToken = this.consume(TokenType.RPAREN, "Expected ')' after EXISTS subquery.");
1503
1735
  return {
1504
1736
  type: 'exists',
@@ -1558,8 +1790,8 @@ export class Parser {
1558
1790
  return { type: 'parameter', name: nameToken.lexeme, loc: _createLoc(startToken, nameToken) };
1559
1791
  }
1560
1792
  // Function call (with optional window function support)
1561
- if (this.checkIdentifierLike(['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like', 'replace']) && this.checkNext(1, TokenType.LPAREN)) {
1562
- const name = this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like', 'replace'], "Expected function name.");
1793
+ if (this.checkIdentifierLike([...CONTEXTUAL_KEYWORDS, 'replace']) && this.checkNext(1, TokenType.LPAREN)) {
1794
+ const name = this.consumeIdentifier([...CONTEXTUAL_KEYWORDS, 'replace'], "Expected function name.");
1563
1795
  this.consume(TokenType.LPAREN, "Expected '(' after function name.");
1564
1796
  const args = [];
1565
1797
  let distinct = false;
@@ -1609,16 +1841,15 @@ export class Parser {
1609
1841
  return funcExpr;
1610
1842
  }
1611
1843
  // Column/identifier expressions
1612
- const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
1613
- if (this.checkIdentifierLike(contextualKeywords)) {
1844
+ if (this.checkIdentifierLike(CONTEXTUAL_KEYWORDS)) {
1614
1845
  // Schema.table.column
1615
- if (this.checkNext(1, TokenType.DOT) && this.checkIdentifierLikeAt(2, contextualKeywords) &&
1616
- this.checkNext(3, TokenType.DOT) && this.checkIdentifierLikeAt(4, contextualKeywords)) {
1617
- const schema = this.consumeIdentifier(contextualKeywords, "Expected schema name.");
1846
+ if (this.checkNext(1, TokenType.DOT) && this.checkIdentifierLikeAt(2, CONTEXTUAL_KEYWORDS) &&
1847
+ this.checkNext(3, TokenType.DOT) && this.checkIdentifierLikeAt(4, CONTEXTUAL_KEYWORDS)) {
1848
+ const schema = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected schema name.");
1618
1849
  this.advance(); // Consume DOT
1619
- const table = this.consumeIdentifier(contextualKeywords, "Expected table name.");
1850
+ const table = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected table name.");
1620
1851
  this.advance(); // Consume DOT
1621
- const name = this.consumeIdentifier(contextualKeywords, "Expected column name.");
1852
+ const name = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name.");
1622
1853
  const nameToken = this.previous();
1623
1854
  return {
1624
1855
  type: 'column',
@@ -1629,10 +1860,10 @@ export class Parser {
1629
1860
  };
1630
1861
  }
1631
1862
  // table.column
1632
- else if (this.checkNext(1, TokenType.DOT) && this.checkIdentifierLikeAt(2, contextualKeywords)) {
1633
- const table = this.consumeIdentifier(contextualKeywords, "Expected table name.");
1863
+ else if (this.checkNext(1, TokenType.DOT) && this.checkIdentifierLikeAt(2, CONTEXTUAL_KEYWORDS)) {
1864
+ const table = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected table name.");
1634
1865
  this.advance(); // Consume DOT
1635
- const name = this.consumeIdentifier(contextualKeywords, "Expected column name.");
1866
+ const name = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name.");
1636
1867
  const nameToken = this.previous();
1637
1868
  return {
1638
1869
  type: 'column',
@@ -1643,7 +1874,7 @@ export class Parser {
1643
1874
  }
1644
1875
  // just column
1645
1876
  else {
1646
- const name = this.consumeIdentifier(contextualKeywords, "Expected column name.");
1877
+ const name = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name.");
1647
1878
  const nameToken = this.previous();
1648
1879
  return {
1649
1880
  type: 'column',
@@ -1652,12 +1883,12 @@ export class Parser {
1652
1883
  };
1653
1884
  }
1654
1885
  }
1655
- // Parenthesized expression or scalar subquery
1886
+ // Parenthesized expression or scalar / row subquery.
1887
+ // A leading SELECT/VALUES/WITH/INSERT/UPDATE/DELETE here disambiguates
1888
+ // to a subquery; anything else is a parenthesized scalar expression.
1656
1889
  if (this.match(TokenType.LPAREN)) {
1657
- // Look ahead to see if this is a scalar subquery (SELECT ...)
1658
- if (this.check(TokenType.SELECT)) {
1659
- const selectToken = this.consume(TokenType.SELECT, "Expected 'SELECT' in subquery.");
1660
- const subquery = this.selectStatement(selectToken);
1890
+ if (this.checkSubqueryStart()) {
1891
+ const subquery = this.parseQueryExpr(undefined, /*requireReturning*/ true);
1661
1892
  this.consume(TokenType.RPAREN, "Expected ')' after subquery.");
1662
1893
  return {
1663
1894
  type: 'subquery',
@@ -1810,6 +2041,23 @@ export class Parser {
1810
2041
  return false;
1811
2042
  return this.tokens[this.current + n].type === type;
1812
2043
  }
2044
+ /**
2045
+ * Non-consuming lookahead for a trailing `with defaults` clause — `WITH`
2046
+ * followed by the contextual `DEFAULTS` keyword (an IDENTIFIER lexeme, since
2047
+ * DEFAULTS is not reserved). Used by the VALUES trailing-clause path to decide
2048
+ * whether to wrap `VALUES …` as `SELECT * FROM (VALUES …)` so the select spine's
2049
+ * {@link parseDefaultsClause} can consume the clause — exactly how a trailing
2050
+ * ORDER BY / LIMIT on VALUES already wraps. A trailing `WITH TAGS` (DDL-level)
2051
+ * never matches, so it stays with the outer DDL parser.
2052
+ */
2053
+ checkWithDefaults() {
2054
+ if (!this.check(TokenType.WITH))
2055
+ return false;
2056
+ const next = this.tokens[this.current + 1];
2057
+ return next !== undefined
2058
+ && next.type === TokenType.IDENTIFIER
2059
+ && next.lexeme.toUpperCase() === 'DEFAULTS';
2060
+ }
1813
2061
  advance() {
1814
2062
  if (!this.isAtEnd())
1815
2063
  this.current++;
@@ -1870,6 +2118,19 @@ export class Parser {
1870
2118
  this.check(TokenType.FULL) ||
1871
2119
  this.check(TokenType.CROSS);
1872
2120
  }
2121
+ /**
2122
+ * True when the current token starts a `QueryExpr` (SELECT, VALUES, WITH,
2123
+ * INSERT, UPDATE, DELETE). Used by callers that have already consumed an
2124
+ * `(` to decide between a subquery and a parenthesized scalar expression.
2125
+ */
2126
+ checkSubqueryStart() {
2127
+ return this.check(TokenType.SELECT)
2128
+ || this.check(TokenType.VALUES)
2129
+ || this.check(TokenType.WITH)
2130
+ || this.check(TokenType.INSERT)
2131
+ || this.check(TokenType.UPDATE)
2132
+ || this.check(TokenType.DELETE);
2133
+ }
1873
2134
  isEndOfClause() {
1874
2135
  const token = this.peek().type;
1875
2136
  return token === TokenType.FROM ||
@@ -1884,23 +2145,36 @@ export class Parser {
1884
2145
  }
1885
2146
  // --- Statement Parsing Stubs ---
1886
2147
  /** @internal */
1887
- updateStatement(startToken, _withClause) {
1888
- const table = this.tableIdentifier();
1889
- // Parse mutation context assignments if present (can also appear after WHERE)
2148
+ updateStatement(startToken, withClause) {
2149
+ // Inline subquery target: `update (select …) as v set …`. When present, the
2150
+ // alias is the target's correlation name; mirror it into a synthetic placeholder
2151
+ // `table` (= the alias) so generic `stmt.table.name` reads stay total, and carry
2152
+ // it on `alias`. Otherwise fall through to the ordinary named/CTE target.
2153
+ const targetSource = this.subqueryDmlTarget(withClause);
2154
+ const table = targetSource
2155
+ ? { type: 'identifier', name: targetSource.alias, loc: targetSource.loc }
2156
+ : this.tableIdentifier();
2157
+ // Parse mutation context assignments and/or tags if present (either may also
2158
+ // appear trailing, after WHERE, via parseTrailingWithClauses).
1890
2159
  let contextValues;
1891
- if (this.matchKeyword('WITH')) {
2160
+ let tags;
2161
+ while (this.matchKeyword('WITH')) {
1892
2162
  if (this.matchKeyword('CONTEXT')) {
1893
2163
  contextValues = this.parseContextAssignments();
1894
2164
  }
2165
+ else if (this.matchKeyword('TAGS')) {
2166
+ tags = this.parseTags();
2167
+ }
1895
2168
  else {
1896
- // Not a WITH CONTEXT clause, backtrack
2169
+ // Not a WITH CONTEXT / WITH TAGS clause, backtrack
1897
2170
  this.current--;
2171
+ break;
1898
2172
  }
1899
2173
  }
1900
2174
  this.consume(TokenType.SET, "Expected 'SET' after table name in UPDATE.");
1901
2175
  const assignments = [];
1902
2176
  do {
1903
- const column = this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'], "Expected column name in SET clause.");
2177
+ const column = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name in SET clause.");
1904
2178
  this.consume(TokenType.EQUAL, "Expected '=' after column name in SET clause.");
1905
2179
  const value = this.expression();
1906
2180
  assignments.push({ column, value });
@@ -1917,6 +2191,12 @@ export class Parser {
1917
2191
  }
1918
2192
  contextValues = trailingClauses.contextValues;
1919
2193
  }
2194
+ if (trailingClauses.tags) {
2195
+ if (tags) {
2196
+ throw this.error(this.previous(), "Duplicate WITH TAGS clause");
2197
+ }
2198
+ tags = trailingClauses.tags;
2199
+ }
1920
2200
  const schemaPath = trailingClauses.schemaPath;
1921
2201
  // Parse RETURNING clause if present
1922
2202
  let returning;
@@ -1924,21 +2204,33 @@ export class Parser {
1924
2204
  returning = this.columnList();
1925
2205
  }
1926
2206
  const endToken = this.previous();
1927
- return { type: 'update', table, assignments, where, returning, contextValues, schemaPath, loc: _createLoc(startToken, endToken) };
2207
+ return { type: 'update', table, targetSource, alias: targetSource?.alias, assignments, where, returning, contextValues, schemaPath, tags, loc: _createLoc(startToken, endToken) };
1928
2208
  }
1929
2209
  /** @internal */
1930
- deleteStatement(startToken, _withClause) {
2210
+ deleteStatement(startToken, withClause) {
2211
+ // `delete` consumes an optional leading `FROM`; the inline-subquery target check
2212
+ // runs AFTER it so `delete from (select …) as v` works (and `delete (select …) as
2213
+ // v` too, matching the optional-FROM behavior). See updateStatement.
1931
2214
  this.matchKeyword('FROM');
1932
- const table = this.tableIdentifier();
1933
- // Parse mutation context assignments if present (can also appear after WHERE)
2215
+ const targetSource = this.subqueryDmlTarget(withClause);
2216
+ const table = targetSource
2217
+ ? { type: 'identifier', name: targetSource.alias, loc: targetSource.loc }
2218
+ : this.tableIdentifier();
2219
+ // Parse mutation context assignments and/or tags if present (either may also
2220
+ // appear trailing, after WHERE, via parseTrailingWithClauses).
1934
2221
  let contextValues;
1935
- if (this.matchKeyword('WITH')) {
2222
+ let tags;
2223
+ while (this.matchKeyword('WITH')) {
1936
2224
  if (this.matchKeyword('CONTEXT')) {
1937
2225
  contextValues = this.parseContextAssignments();
1938
2226
  }
2227
+ else if (this.matchKeyword('TAGS')) {
2228
+ tags = this.parseTags();
2229
+ }
1939
2230
  else {
1940
- // Not a WITH CONTEXT clause, backtrack
2231
+ // Not a WITH CONTEXT / WITH TAGS clause, backtrack
1941
2232
  this.current--;
2233
+ break;
1942
2234
  }
1943
2235
  }
1944
2236
  let where;
@@ -1953,6 +2245,12 @@ export class Parser {
1953
2245
  }
1954
2246
  contextValues = trailingClauses.contextValues;
1955
2247
  }
2248
+ if (trailingClauses.tags) {
2249
+ if (tags) {
2250
+ throw this.error(this.previous(), "Duplicate WITH TAGS clause");
2251
+ }
2252
+ tags = trailingClauses.tags;
2253
+ }
1956
2254
  const schemaPath = trailingClauses.schemaPath;
1957
2255
  // Parse RETURNING clause if present
1958
2256
  let returning;
@@ -1960,7 +2258,7 @@ export class Parser {
1960
2258
  returning = this.columnList();
1961
2259
  }
1962
2260
  const endToken = this.previous();
1963
- return { type: 'delete', table, where, returning, contextValues, schemaPath, loc: _createLoc(startToken, endToken) };
2261
+ return { type: 'delete', table, targetSource, alias: targetSource?.alias, where, returning, contextValues, schemaPath, tags, loc: _createLoc(startToken, endToken) };
1964
2262
  }
1965
2263
  /** @internal */
1966
2264
  valuesStatement(startToken) {
@@ -1979,23 +2277,85 @@ export class Parser {
1979
2277
  const endToken = this.previous();
1980
2278
  return { type: 'values', values, loc: _createLoc(startToken, endToken) };
1981
2279
  }
2280
+ /**
2281
+ * Parses VALUES at a position that also accepts trailing compound
2282
+ * (UNION / INTERSECT / EXCEPT / DIFF) and — outside compound-leg
2283
+ * position — trailing ORDER BY / LIMIT / OFFSET. Used at the top-level
2284
+ * statement dispatch, inside `parseQueryExpr`, and as a compound right
2285
+ * leg so that chains like `VALUES (1) UNION VALUES (2) UNION VALUES (3)`
2286
+ * and `VALUES (1) ORDER BY 1 LIMIT 2` parse uniformly.
2287
+ *
2288
+ * Implementation note: the AST `compound` / `orderBy` / `limit` fields
2289
+ * live on `SelectStmt`, not on `ValuesStmt`. When any of those clauses
2290
+ * follow VALUES we synthesize a `SELECT * FROM (VALUES …)` wrapper so the
2291
+ * existing SELECT machinery applies. The wrapper is structurally
2292
+ * indistinguishable from what a user-written `SELECT * FROM (VALUES …)`
2293
+ * would produce.
2294
+ *
2295
+ * `isCompoundSubquery` suppresses ORDER BY / LIMIT consumption — those
2296
+ * belong to the outer compound when VALUES appears as a right leg.
2297
+ */
2298
+ valuesStatementWithOptionalCompound(startToken, withClause, isCompoundSubquery = false) {
2299
+ const values = this.valuesStatement(startToken);
2300
+ const hasCompound = this.checkCompoundOperator();
2301
+ // A trailing `with defaults (…)` wraps just like ORDER BY / LIMIT so the
2302
+ // select spine consumes it — a VALUES-bodied view's defaults are inert
2303
+ // (the view is non-updateable), but the clause must still parse + store.
2304
+ const hasTrailing = !isCompoundSubquery && (this.check(TokenType.ORDER) || this.check(TokenType.LIMIT) || this.checkWithDefaults());
2305
+ if (!hasCompound && !hasTrailing) {
2306
+ return values;
2307
+ }
2308
+ // Wrap as `SELECT * FROM (<values>) AS <synthetic alias>` and continue
2309
+ // parsing as a SELECT so the trailing clauses fold in naturally.
2310
+ const wrapped = this.wrapAsSubquerySelect(values, startToken);
2311
+ return this.continueSelectAfterFrom(wrapped, withClause, startToken, isCompoundSubquery);
2312
+ }
2313
+ /**
2314
+ * Picks up an in-progress SELECT after its FROM clause is already populated
2315
+ * and parses any remaining trailing clauses by delegating to the shared
2316
+ * compound-tail / ORDER-LIMIT parsers. Used by
2317
+ * `valuesStatementWithOptionalCompound` to graft a compound chain and
2318
+ * trailing clauses onto a synthesized SELECT-from-VALUES wrapper. The
2319
+ * synthesized wrapper never carries its own WHERE / GROUP BY / HAVING — bare
2320
+ * VALUES at top level does not accept those clauses, so they fall through as
2321
+ * a statement-boundary parse error rather than being silently absorbed.
2322
+ *
2323
+ * `startToken` seeds the synthetic alias of any further subquery wrapper the
2324
+ * tail parser mints; `isCompoundSubquery` suppresses ORDER BY / LIMIT
2325
+ * consumption — those belong to the outer compound when this wrapper is a
2326
+ * right leg.
2327
+ */
2328
+ continueSelectAfterFrom(sel, withClause, startToken, isCompoundSubquery = false) {
2329
+ // Compound chain (left-associative tail) binds before ORDER BY / LIMIT.
2330
+ const result = this.parseCompoundTail(sel, withClause, startToken);
2331
+ this.parseTrailingOrderLimit(result, isCompoundSubquery);
2332
+ // Trailing `with defaults (…)` binds to the whole compound (see selectStatement).
2333
+ if (!isCompoundSubquery) {
2334
+ const defaults = this.parseDefaultsClause();
2335
+ if (defaults)
2336
+ result.defaults = defaults;
2337
+ }
2338
+ return result;
2339
+ }
1982
2340
  /** @internal */
1983
2341
  createStatement(startToken, withClause) {
1984
- let isTemporary = false;
2342
+ // TEMP/TEMPORARY is not a Quereus concept — the schema is already transient
2343
+ // and temp placement was never wired. Reject it rather than silently ignore.
1985
2344
  if (this.peekKeyword('TEMP') || this.peekKeyword('TEMPORARY')) {
1986
- isTemporary = true;
1987
- this.advance();
2345
+ throw this.error(this.peek(), "TEMP/TEMPORARY is not supported.");
1988
2346
  }
1989
2347
  if (this.peekKeyword('TABLE')) {
1990
2348
  this.consumeKeyword('TABLE', "Expected 'TABLE' after CREATE.");
1991
- return this.createTableStatement(startToken, isTemporary, withClause);
2349
+ return this.createTableStatement(startToken, withClause);
1992
2350
  }
1993
2351
  else if (this.peekKeyword('VIEW')) {
1994
2352
  this.consumeKeyword('VIEW', "Expected 'VIEW' after CREATE.");
1995
- return this.createViewStatement(startToken, isTemporary, withClause);
2353
+ return this.createViewStatement(startToken, withClause);
1996
2354
  }
1997
- else if (isTemporary) {
1998
- throw this.error(this.peek(), "Expected TABLE or VIEW after CREATE TEMP/TEMPORARY.");
2355
+ else if (this.peekKeyword('MATERIALIZED')) {
2356
+ this.consumeKeyword('MATERIALIZED', "Expected 'MATERIALIZED' after CREATE.");
2357
+ this.consumeKeyword('VIEW', "Expected 'VIEW' after CREATE MATERIALIZED.");
2358
+ return this.createMaterializedViewStatement(startToken, withClause);
1999
2359
  }
2000
2360
  else if (this.peekKeyword('INDEX')) {
2001
2361
  this.consumeKeyword('INDEX', "Expected 'INDEX' after CREATE.");
@@ -2010,13 +2370,13 @@ export class Parser {
2010
2370
  this.consumeKeyword('INDEX', "Expected 'INDEX' after CREATE UNIQUE.");
2011
2371
  return this.createIndexStatement(startToken, true, withClause);
2012
2372
  }
2013
- throw this.error(this.peek(), "Expected TABLE, [UNIQUE] INDEX, VIEW, ASSERTION, or VIRTUAL after CREATE.");
2373
+ throw this.error(this.peek(), "Expected TABLE, [UNIQUE] INDEX, VIEW, MATERIALIZED VIEW, ASSERTION, or VIRTUAL after CREATE.");
2014
2374
  }
2015
2375
  /**
2016
2376
  * Parse CREATE TABLE statement
2017
2377
  * @returns AST for CREATE TABLE
2018
2378
  */
2019
- createTableStatement(startToken, isTemporary, _withClause) {
2379
+ createTableStatement(startToken, _withClause) {
2020
2380
  let ifNotExists = false;
2021
2381
  if (this.matchKeyword('IF')) {
2022
2382
  this.consumeKeyword('NOT', "Expected 'NOT' after 'IF'.");
@@ -2040,8 +2400,7 @@ export class Parser {
2040
2400
  // If we didn't see a comma and the next token looks like the start of another
2041
2401
  // column or table constraint, provide a clearer error about a missing comma.
2042
2402
  if (!this.check(TokenType.RPAREN)) {
2043
- const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
2044
- const nextLooksLikeAnotherItem = this.peekKeyword('PRIMARY') || this.peekKeyword('UNIQUE') || this.peekKeyword('CHECK') || this.peekKeyword('FOREIGN') || this.peekKeyword('CONSTRAINT') || this.checkIdentifierLike(contextualKeywords);
2403
+ const nextLooksLikeAnotherItem = this.peekKeyword('PRIMARY') || this.peekKeyword('UNIQUE') || this.peekKeyword('CHECK') || this.peekKeyword('FOREIGN') || this.peekKeyword('CONSTRAINT') || this.checkIdentifierLike(CONTEXTUAL_KEYWORDS);
2045
2404
  if (nextLooksLikeAnotherItem) {
2046
2405
  const next = this.peek();
2047
2406
  throw this.error(next, `Expected ',' between table elements. Did you forget a comma before '${next.lexeme}'?`);
@@ -2085,6 +2444,11 @@ export class Parser {
2085
2444
  this.consume(TokenType.RPAREN, "Expected ')' after module arguments.");
2086
2445
  }
2087
2446
  }
2447
+ // Optional `maintained as <body … with defaults (…)>` clause — the declared-shape
2448
+ // maintained-table form. `maintained` is contextual (an IDENTIFIER lexeme match), and
2449
+ // this position (after the column list / USING clause, before the WITH clauses) is
2450
+ // unambiguous, so no look-ahead guard is needed.
2451
+ const maintained = this.parseMaintainedClause();
2088
2452
  // Parse trailing WITH clauses (CONTEXT, TAGS) in any order
2089
2453
  let contextDefinitions;
2090
2454
  let tags;
@@ -2113,14 +2477,57 @@ export class Parser {
2113
2477
  ifNotExists,
2114
2478
  columns,
2115
2479
  constraints,
2116
- isTemporary,
2117
2480
  moduleName,
2118
2481
  moduleArgs,
2119
2482
  contextDefinitions,
2120
2483
  tags,
2484
+ maintained,
2121
2485
  loc: _createLoc(startToken, this.previous()),
2122
2486
  };
2123
2487
  }
2488
+ /**
2489
+ * Parse the optional `maintained as <query-expr>` clause of a CREATE TABLE (or
2490
+ * a `declare schema` table item) — the declared-shape maintained-table form.
2491
+ * Any omitted-insert defaults ride inside the body select's trailing
2492
+ * `with defaults (…)` clause. Returns undefined when the clause is absent.
2493
+ */
2494
+ parseMaintainedClause() {
2495
+ if (!this.matchKeyword('MAINTAINED'))
2496
+ return undefined;
2497
+ // Optional explicit output-column rename list before AS — the lossless
2498
+ // table-form encoding of the MV-sugar `(a, b)` renames. Absent ⇒ implicit
2499
+ // (the body follows its source shape on reopen). The required AS that
2500
+ // follows keeps this unambiguous against a parenthesized body.
2501
+ const columns = this.parseMaintainedColumnList();
2502
+ this.consumeKeyword('AS', "Expected 'AS' after MAINTAINED.");
2503
+ // Body is any QueryExpr — bare SELECT / VALUES / WITH … SELECT all qualify.
2504
+ // DML bodies parse here but the planner rejects them. A trailing
2505
+ // `with defaults (…)` is consumed by the select spine into `select.defaults`.
2506
+ const select = this.parseQueryExpr(undefined, /*requireReturning*/ true);
2507
+ return { columns, select };
2508
+ }
2509
+ /**
2510
+ * Parse the optional parenthesized output-column rename list that may precede the
2511
+ * `AS` of a maintained-table form — shared by `create table … maintained (cols) as`
2512
+ * and `alter table … set maintained (cols) as`. Returns undefined when absent
2513
+ * (the implicit form). An empty `()` is rejected: it could not round-trip
2514
+ * (`maintainedClauseToString` drops an empty list) and the clause's absence is
2515
+ * already the implicit signal, so require at least one column.
2516
+ */
2517
+ parseMaintainedColumnList() {
2518
+ if (!this.check(TokenType.LPAREN))
2519
+ return undefined;
2520
+ this.consume(TokenType.LPAREN, "Expected '(' to start maintained column list.");
2521
+ if (this.check(TokenType.RPAREN)) {
2522
+ throw this.error(this.peek(), "Expected at least one column name in the maintained column list.");
2523
+ }
2524
+ const columns = [];
2525
+ do {
2526
+ columns.push(this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name in maintained column list."));
2527
+ } while (this.match(TokenType.COMMA) && !this.check(TokenType.RPAREN));
2528
+ this.consume(TokenType.RPAREN, "Expected ')' after maintained column list.");
2529
+ return columns;
2530
+ }
2124
2531
  /**
2125
2532
  * Parse CREATE INDEX statement
2126
2533
  * @param isUnique Flag indicating if UNIQUE keyword was already parsed
@@ -2173,7 +2580,7 @@ export class Parser {
2173
2580
  * Parse CREATE VIEW statement
2174
2581
  * @returns AST for CREATE VIEW
2175
2582
  */
2176
- createViewStatement(startToken, isTemporary, withClause) {
2583
+ createViewStatement(startToken, withClause) {
2177
2584
  let ifNotExists = false;
2178
2585
  if (this.matchKeyword('IF')) {
2179
2586
  this.consumeKeyword('NOT', "Expected 'NOT' after 'IF'.");
@@ -2185,29 +2592,20 @@ export class Parser {
2185
2592
  if (this.check(TokenType.LPAREN)) {
2186
2593
  this.consume(TokenType.LPAREN, "Expected '(' to start view column list.");
2187
2594
  columns = [];
2188
- const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
2189
2595
  if (!this.check(TokenType.RPAREN)) {
2190
2596
  do {
2191
- columns.push(this.consumeIdentifier(contextualKeywords, "Expected column name in view column list."));
2597
+ columns.push(this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name in view column list."));
2192
2598
  } while (this.match(TokenType.COMMA) && !this.check(TokenType.RPAREN));
2193
2599
  }
2194
2600
  this.consume(TokenType.RPAREN, "Expected ')' after view column list.");
2195
2601
  }
2196
- this.consumeKeyword('AS', "Expected 'AS' before SELECT statement for CREATE VIEW.");
2197
- // CREATE VIEW body may optionally start with a WITH (CTE) clause; the WITH attaches to
2198
- // the inner SELECT, not to the CREATE VIEW itself.
2199
- let innerWith;
2200
- if (this.check(TokenType.WITH)) {
2201
- innerWith = this.tryParseWithClause();
2202
- }
2203
- const selectStartToken = this.consume(TokenType.SELECT, "Expected 'SELECT' after 'AS' in CREATE VIEW.");
2204
- const select = this.selectStatement(selectStartToken, innerWith ?? withClause);
2205
- if (innerWith) {
2206
- select.withClause = innerWith;
2207
- if (innerWith.loc && select.loc) {
2208
- select.loc.start = innerWith.loc.start;
2209
- }
2210
- }
2602
+ this.consumeKeyword('AS', "Expected 'AS' before view body in CREATE VIEW.");
2603
+ // CREATE VIEW body is any QueryExpr bare SELECT / VALUES / WITH
2604
+ // SELECT all qualify. DML bodies parse here but the planner rejects
2605
+ // them (mutating views are out of scope for this milestone).
2606
+ const select = this.parseQueryExpr(withClause, /*requireReturning*/ true);
2607
+ // A trailing `with defaults ()` rides inside the select body
2608
+ // (`select.defaults`); only the DDL-level WITH TAGS is parsed here.
2211
2609
  // Parse optional WITH TAGS
2212
2610
  let tags;
2213
2611
  if (this.matchKeyword('WITH')) {
@@ -2224,11 +2622,195 @@ export class Parser {
2224
2622
  ifNotExists,
2225
2623
  columns,
2226
2624
  select,
2227
- isTemporary,
2228
2625
  tags,
2229
2626
  loc: _createLoc(startToken, this.previous()),
2230
2627
  };
2231
2628
  }
2629
+ /**
2630
+ * Parse the optional trailing `with defaults ( col = expr , … )` clause of a
2631
+ * core select — per-column omitted-insert defaults for view write-through.
2632
+ * Modeled byte-for-byte on {@link parseInverseClause}: commits only once
2633
+ * DEFAULTS follows WITH, so a statement-trailing `with tags` / `with schema` /
2634
+ * `with context` stays with the outer parser (the rewound token is WITH, which
2635
+ * never touches the parenStack — so the bare cursor rewind is safe). Returns
2636
+ * undefined when the clause is absent. An empty assignment list is a parse error.
2637
+ */
2638
+ parseDefaultsClause() {
2639
+ if (!this.check(TokenType.WITH))
2640
+ return undefined;
2641
+ this.advance();
2642
+ if (!this.peekKeyword('DEFAULTS')) {
2643
+ // Not our clause — back up the WITH and stop. Bare cursor rewind is safe
2644
+ // only because the rewound token is WITH: advance() has one non-cursor
2645
+ // side effect (LPAREN/RPAREN parenStack maintenance), which WITH never hits.
2646
+ this.current--;
2647
+ return undefined;
2648
+ }
2649
+ this.advance();
2650
+ this.consume(TokenType.LPAREN, "Expected '(' after WITH DEFAULTS.");
2651
+ const defaults = [];
2652
+ const seen = new Set();
2653
+ do {
2654
+ const columnToken = this.peek();
2655
+ const column = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name in WITH DEFAULTS.");
2656
+ if (seen.has(column.toLowerCase())) {
2657
+ throw this.error(columnToken, `Duplicate column '${column}' in WITH DEFAULTS.`);
2658
+ }
2659
+ seen.add(column.toLowerCase());
2660
+ this.consume(TokenType.EQUAL, `Expected '=' after WITH DEFAULTS column '${column}'.`);
2661
+ const expr = this.expression();
2662
+ defaults.push({ column, expr });
2663
+ } while (this.match(TokenType.COMMA));
2664
+ this.consume(TokenType.RPAREN, "Expected ')' after WITH DEFAULTS list.");
2665
+ return defaults;
2666
+ }
2667
+ /**
2668
+ * Parse the optional `with inverse ( col = expr , … )` clause trailing an
2669
+ * expression result column — authored write-back expressions for view
2670
+ * write-through (inert metadata until the write path consumes it; validation
2671
+ * is build-time). Returns undefined when the clause is absent. `inverse` is
2672
+ * contextual: commits only once INVERSE follows WITH, so a statement-trailing
2673
+ * `with schema` / `with context` / `with tags` after the column list stays
2674
+ * with the outer parser. An empty assignment list is a parse error.
2675
+ */
2676
+ parseInverseClause() {
2677
+ if (!this.check(TokenType.WITH))
2678
+ return undefined;
2679
+ this.advance();
2680
+ if (!this.peekKeyword('INVERSE')) {
2681
+ // Not our clause — back up the WITH and stop. Bare cursor rewind is safe
2682
+ // only because the rewound token is WITH: advance() has one non-cursor
2683
+ // side effect (LPAREN/RPAREN parenStack maintenance), which WITH never hits.
2684
+ this.current--;
2685
+ return undefined;
2686
+ }
2687
+ this.advance();
2688
+ this.consume(TokenType.LPAREN, "Expected '(' after WITH INVERSE.");
2689
+ const assignments = [];
2690
+ const seen = new Set();
2691
+ do {
2692
+ const columnToken = this.peek();
2693
+ const column = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name in WITH INVERSE.");
2694
+ if (seen.has(column.toLowerCase())) {
2695
+ throw this.error(columnToken, `Duplicate column '${column}' in WITH INVERSE.`);
2696
+ }
2697
+ seen.add(column.toLowerCase());
2698
+ this.consume(TokenType.EQUAL, `Expected '=' after WITH INVERSE column '${column}'.`);
2699
+ const expr = this.expression();
2700
+ assignments.push({ column, expr });
2701
+ } while (this.match(TokenType.COMMA));
2702
+ this.consume(TokenType.RPAREN, "Expected ')' after WITH INVERSE list.");
2703
+ return assignments;
2704
+ }
2705
+ /**
2706
+ * A `*` / `t.*` result column cannot carry WITH INVERSE (it names no single
2707
+ * expression to invert). Name the clause in the diagnostic rather than letting
2708
+ * the leftover WITH garble into a downstream CTE/clause error. A trailing
2709
+ * `with schema` / `with tags` is untouched — parseInverseClause commits only
2710
+ * on WITH followed by INVERSE.
2711
+ */
2712
+ rejectInverseClauseOnStar() {
2713
+ const withToken = this.peek();
2714
+ if (this.parseInverseClause()) {
2715
+ throw this.error(withToken, "WITH INVERSE cannot apply to a '*' result column.");
2716
+ }
2717
+ }
2718
+ /**
2719
+ * Parse CREATE MATERIALIZED VIEW statement.
2720
+ *
2721
+ * Syntax: `create materialized view <name> [(cols)] [using <module>(args)] as <query-expr> [with tags ...]`.
2722
+ * The optional `using` clause is parsed before `as` to stay unambiguous with the query body;
2723
+ * v1 restricts the backing module to `memory` at build time (the AST keeps the slot forward-compatible).
2724
+ */
2725
+ createMaterializedViewStatement(startToken, withClause) {
2726
+ let ifNotExists = false;
2727
+ if (this.matchKeyword('IF')) {
2728
+ this.consumeKeyword('NOT', "Expected 'NOT' after 'IF'.");
2729
+ this.consumeKeyword('EXISTS', "Expected 'EXISTS' after 'IF NOT'.");
2730
+ ifNotExists = true;
2731
+ }
2732
+ const view = this.tableIdentifier();
2733
+ let columns;
2734
+ if (this.check(TokenType.LPAREN)) {
2735
+ this.consume(TokenType.LPAREN, "Expected '(' to start view column list.");
2736
+ columns = [];
2737
+ if (!this.check(TokenType.RPAREN)) {
2738
+ do {
2739
+ columns.push(this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name in view column list."));
2740
+ } while (this.match(TokenType.COMMA) && !this.check(TokenType.RPAREN));
2741
+ }
2742
+ this.consume(TokenType.RPAREN, "Expected ')' after view column list.");
2743
+ }
2744
+ // Optional backing-module clause (`using mem(...)`) before the body.
2745
+ let moduleName;
2746
+ const moduleArgs = {};
2747
+ if (this.matchKeyword('USING')) {
2748
+ moduleName = this.consumeIdentifier("Expected module name after 'USING'.");
2749
+ if (this.match(TokenType.LPAREN)) {
2750
+ let positionalIndex = 0;
2751
+ if (!this.check(TokenType.RPAREN)) {
2752
+ do {
2753
+ if (this.check(TokenType.STRING) || this.check(TokenType.INTEGER) || this.check(TokenType.FLOAT)) {
2754
+ const token = this.advance();
2755
+ moduleArgs[String(positionalIndex++)] = token.literal;
2756
+ }
2757
+ else if (this.check(TokenType.IDENTIFIER)) {
2758
+ const nameValue = this.nameValueItem('module argument');
2759
+ moduleArgs[nameValue.name] = nameValue.value && nameValue.value.type === 'literal'
2760
+ ? getSyncLiteral(nameValue.value)
2761
+ : (nameValue.value && nameValue.value.type === 'identifier' ? nameValue.value.name : nameValue.name);
2762
+ }
2763
+ else {
2764
+ throw this.error(this.peek(), "Expected module argument (string, number, or name=value pair).");
2765
+ }
2766
+ } while (this.match(TokenType.COMMA));
2767
+ }
2768
+ this.consume(TokenType.RPAREN, "Expected ')' after module arguments.");
2769
+ }
2770
+ }
2771
+ this.consumeKeyword('AS', "Expected 'AS' before view body in CREATE MATERIALIZED VIEW.");
2772
+ // Body is any QueryExpr — bare SELECT / VALUES / WITH … SELECT all qualify.
2773
+ // DML bodies parse here but the planner rejects them.
2774
+ const select = this.parseQueryExpr(withClause, /*requireReturning*/ true);
2775
+ // A trailing `with defaults (…)` rides inside the select body
2776
+ // (`select.defaults`); only the DDL-level WITH TAGS is parsed here.
2777
+ // Parse the trailing `with tags (...)` metadata clause.
2778
+ let tags;
2779
+ while (this.matchKeyword('WITH')) {
2780
+ if (this.matchKeyword('TAGS')) {
2781
+ tags = this.parseTags();
2782
+ }
2783
+ else {
2784
+ this.current--; // Not a clause we own — back up the WITH and stop.
2785
+ break;
2786
+ }
2787
+ }
2788
+ return {
2789
+ type: 'createMaterializedView',
2790
+ view,
2791
+ ifNotExists,
2792
+ columns,
2793
+ select,
2794
+ moduleName,
2795
+ moduleArgs: moduleName && Object.keys(moduleArgs).length > 0 ? moduleArgs : undefined,
2796
+ tags,
2797
+ loc: _createLoc(startToken, this.previous()),
2798
+ };
2799
+ }
2800
+ /**
2801
+ * Parse REFRESH MATERIALIZED VIEW statement.
2802
+ * Syntax: `refresh materialized view <name>`.
2803
+ */
2804
+ refreshStatement(startToken, _withClause) {
2805
+ this.consumeKeyword('MATERIALIZED', "Expected 'MATERIALIZED' after REFRESH.");
2806
+ this.consumeKeyword('VIEW', "Expected 'VIEW' after REFRESH MATERIALIZED.");
2807
+ const name = this.tableIdentifier();
2808
+ return {
2809
+ type: 'refreshMaterializedView',
2810
+ name,
2811
+ loc: _createLoc(startToken, this.previous()),
2812
+ };
2813
+ }
2232
2814
  /**
2233
2815
  * Parse CREATE ASSERTION statement
2234
2816
  * @returns AST for CREATE ASSERTION
@@ -2256,6 +2838,11 @@ export class Parser {
2256
2838
  this.consumeKeyword('TABLE', "Expected TABLE after DROP.");
2257
2839
  objectType = 'table';
2258
2840
  }
2841
+ else if (this.peekKeyword('MATERIALIZED')) {
2842
+ this.consumeKeyword('MATERIALIZED', "Expected MATERIALIZED after DROP.");
2843
+ this.consumeKeyword('VIEW', "Expected VIEW after DROP MATERIALIZED.");
2844
+ objectType = 'materializedView';
2845
+ }
2259
2846
  else if (this.peekKeyword('VIEW')) {
2260
2847
  this.consumeKeyword('VIEW', "Expected VIEW after DROP.");
2261
2848
  objectType = 'view';
@@ -2269,7 +2856,7 @@ export class Parser {
2269
2856
  objectType = 'assertion';
2270
2857
  }
2271
2858
  else {
2272
- throw this.error(this.peek(), "Expected TABLE, VIEW, INDEX, or ASSERTION after DROP.");
2859
+ throw this.error(this.peek(), "Expected TABLE, VIEW, MATERIALIZED VIEW, INDEX, or ASSERTION after DROP.");
2273
2860
  }
2274
2861
  let ifExists = false;
2275
2862
  if (this.matchKeyword('IF')) {
@@ -2285,6 +2872,74 @@ export class Parser {
2285
2872
  loc: _createLoc(startToken, this.previous()),
2286
2873
  };
2287
2874
  }
2875
+ /**
2876
+ * Top-level ALTER dispatch. `ALTER` has already been consumed. Branches on the
2877
+ * object keyword: TABLE → the full ALTER TABLE grammar; VIEW / MATERIALIZED VIEW
2878
+ * / INDEX → the v1 `SET TAGS`-only metadata grammar. MATERIALIZED is checked
2879
+ * before VIEW so `MATERIALIZED VIEW` is not mis-parsed as a plain view.
2880
+ */
2881
+ alterStatement(startToken, withClause) {
2882
+ if (this.peekKeyword('TABLE')) {
2883
+ return this.alterTableStatement(startToken, withClause);
2884
+ }
2885
+ if (this.peekKeyword('MATERIALIZED')) {
2886
+ return this.alterMaterializedViewStatement(startToken);
2887
+ }
2888
+ if (this.peekKeyword('VIEW')) {
2889
+ return this.alterViewStatement(startToken);
2890
+ }
2891
+ if (this.peekKeyword('INDEX')) {
2892
+ return this.alterIndexStatement(startToken);
2893
+ }
2894
+ throw this.error(this.peek(), "Expected 'TABLE', 'VIEW', 'MATERIALIZED VIEW', or 'INDEX' after ALTER.");
2895
+ }
2896
+ /**
2897
+ * Parse the trailing `{SET|ADD|DROP} TAGS (...)` of an ALTER VIEW /
2898
+ * MATERIALIZED VIEW / INDEX statement (object name already consumed):
2899
+ * SET TAGS → whole-set replace (empty list clears),
2900
+ * ADD TAGS → per-key merge (empty list is a no-op),
2901
+ * DROP TAGS → per-key delete (atomic; empty list is a no-op).
2902
+ * No `(` look-ahead guard is needed (unlike the ALTER TABLE table level):
2903
+ * after `ALTER VIEW <name>` the only legal grammar is a tag op, so the
2904
+ * leading keyword is unambiguous.
2905
+ */
2906
+ parseObjectTagsAction() {
2907
+ if (this.matchKeyword('SET')) {
2908
+ this.consumeKeyword('TAGS', "Expected 'TAGS' after SET.");
2909
+ return { type: 'setTags', mode: 'replace', tags: this.parseTags() };
2910
+ }
2911
+ if (this.matchKeyword('ADD')) {
2912
+ this.consumeKeyword('TAGS', "Expected 'TAGS' after ADD.");
2913
+ return { type: 'setTags', mode: 'merge', tags: this.parseTags() };
2914
+ }
2915
+ if (this.matchKeyword('DROP')) {
2916
+ this.consumeKeyword('TAGS', "Expected 'TAGS' after DROP.");
2917
+ return { type: 'dropTags', keys: this.parseTagKeys() };
2918
+ }
2919
+ throw this.error(this.peek(), "Expected SET, ADD, or DROP TAGS after object name.");
2920
+ }
2921
+ /** Parse `ALTER VIEW <name> {SET|ADD|DROP} TAGS (...)`. */
2922
+ alterViewStatement(startToken) {
2923
+ this.consumeKeyword('VIEW', "Expected 'VIEW' after ALTER.");
2924
+ const name = this.tableIdentifier();
2925
+ const action = this.parseObjectTagsAction();
2926
+ return { type: 'alterView', name, action, loc: _createLoc(startToken, this.previous()) };
2927
+ }
2928
+ /** Parse `ALTER MATERIALIZED VIEW <name> {SET|ADD|DROP} TAGS (...)`. */
2929
+ alterMaterializedViewStatement(startToken) {
2930
+ this.consumeKeyword('MATERIALIZED', "Expected 'MATERIALIZED' after ALTER.");
2931
+ this.consumeKeyword('VIEW', "Expected 'VIEW' after MATERIALIZED.");
2932
+ const name = this.tableIdentifier();
2933
+ const action = this.parseObjectTagsAction();
2934
+ return { type: 'alterMaterializedView', name, action, loc: _createLoc(startToken, this.previous()) };
2935
+ }
2936
+ /** Parse `ALTER INDEX <name> {SET|ADD|DROP} TAGS (...)`. */
2937
+ alterIndexStatement(startToken) {
2938
+ this.consumeKeyword('INDEX', "Expected 'INDEX' after ALTER.");
2939
+ const name = this.tableIdentifier();
2940
+ const action = this.parseObjectTagsAction();
2941
+ return { type: 'alterIndex', name, action, loc: _createLoc(startToken, this.previous()) };
2942
+ }
2288
2943
  /**
2289
2944
  * Parse ALTER TABLE statement
2290
2945
  * @returns AST for ALTER TABLE statement
@@ -2296,21 +2951,44 @@ export class Parser {
2296
2951
  if (this.peekKeyword('RENAME')) {
2297
2952
  this.consumeKeyword('RENAME', "Expected RENAME.");
2298
2953
  if (this.matchKeyword('COLUMN')) {
2299
- const oldName = this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'], "Expected old column name after RENAME COLUMN.");
2954
+ const oldName = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected old column name after RENAME COLUMN.");
2300
2955
  this.consumeKeyword('TO', "Expected 'TO' after old column name.");
2301
- const newName = this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'], "Expected new column name after TO.");
2956
+ const newName = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected new column name after TO.");
2302
2957
  action = { type: 'renameColumn', oldName, newName };
2303
2958
  }
2959
+ else if (this.matchKeyword('CONSTRAINT')) {
2960
+ // RENAME CONSTRAINT <old> TO <new> — name-level rename of a named
2961
+ // table-level constraint (CHECK / UNIQUE / FOREIGN KEY).
2962
+ const oldName = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected old constraint name after RENAME CONSTRAINT.");
2963
+ this.consumeKeyword('TO', "Expected 'TO' after old constraint name.");
2964
+ const newName = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected new constraint name after TO.");
2965
+ action = { type: 'renameConstraint', oldName, newName };
2966
+ }
2304
2967
  else {
2305
2968
  this.consumeKeyword('TO', "Expected 'TO' after RENAME.");
2306
- const newName = this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'], "Expected new table name after RENAME TO.");
2969
+ const newName = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected new table name after RENAME TO.");
2307
2970
  action = { type: 'renameTable', newName };
2308
2971
  }
2309
2972
  }
2310
2973
  else if (this.peekKeyword('ADD')) {
2311
2974
  this.consumeKeyword('ADD', "Expected ADD.");
2312
- if (this.peekKeyword('CONSTRAINT')) {
2313
- // ADD CONSTRAINT ... - let tableConstraint parse everything including CONSTRAINT keyword
2975
+ if (this.peekKeyword('TAGS') && this.checkNext(1, TokenType.LPAREN)) {
2976
+ // ADD TAGS (...) — per-key merge of table tags. Gated on TAGS being
2977
+ // immediately followed by '(' so a column literally named `tags`
2978
+ // (e.g. `ADD tags integer` / `ADD COLUMN tags ...`) still parses as
2979
+ // ADD COLUMN. `TAGS` is a contextual keyword (a plain identifier), so
2980
+ // without the '(' guard it would shadow such columns.
2981
+ this.consumeKeyword('TAGS', "Expected 'TAGS'.");
2982
+ const tags = this.parseTags();
2983
+ action = { type: 'setTags', target: { kind: 'table' }, mode: 'merge', tags };
2984
+ }
2985
+ else if (this.peekKeyword('CONSTRAINT')
2986
+ || this.check(TokenType.UNIQUE)
2987
+ || this.check(TokenType.FOREIGN)
2988
+ || this.check(TokenType.CHECK)) {
2989
+ // ADD CONSTRAINT <name> <body>, or the unnamed table-constraint forms
2990
+ // ADD UNIQUE (...) / ADD FOREIGN KEY (...) / ADD CHECK (...).
2991
+ // tableConstraint() consumes the optional CONSTRAINT <name> prefix.
2314
2992
  const constraint = this.tableConstraint();
2315
2993
  action = { type: 'addConstraint', constraint };
2316
2994
  }
@@ -2323,9 +3001,52 @@ export class Parser {
2323
3001
  }
2324
3002
  else if (this.peekKeyword('DROP')) {
2325
3003
  this.consumeKeyword('DROP', "Expected DROP.");
2326
- this.matchKeyword('COLUMN');
2327
- const name = this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'], "Expected column name after DROP COLUMN.");
2328
- action = { type: 'dropColumn', name };
3004
+ if (this.peekKeyword('TAGS') && this.checkNext(1, TokenType.LPAREN)) {
3005
+ // DROP TAGS (...) per-key delete of table tags. Same '(' guard as
3006
+ // ADD TAGS so `DROP COLUMN tags` / `DROP tags` (a column named `tags`)
3007
+ // still parse as DROP COLUMN.
3008
+ this.consumeKeyword('TAGS', "Expected 'TAGS'.");
3009
+ action = { type: 'dropTags', target: { kind: 'table' }, keys: this.parseTagKeys() };
3010
+ }
3011
+ else if (this.peekKeyword('MAINTAINED')) {
3012
+ // DROP MAINTAINED — detach the table's derivation. The verb wins over a
3013
+ // column literally named `maintained`; use DROP COLUMN maintained for that.
3014
+ this.consumeKeyword('MAINTAINED', "Expected 'MAINTAINED'.");
3015
+ action = { type: 'dropMaintained' };
3016
+ }
3017
+ else if (this.matchKeyword('CONSTRAINT')) {
3018
+ // DROP CONSTRAINT <name> — drop a named table-level constraint
3019
+ // (CHECK / UNIQUE / FOREIGN KEY).
3020
+ const name = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected constraint name after DROP CONSTRAINT.");
3021
+ action = { type: 'dropConstraint', name };
3022
+ }
3023
+ else {
3024
+ this.matchKeyword('COLUMN');
3025
+ const name = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name after DROP COLUMN.");
3026
+ action = { type: 'dropColumn', name };
3027
+ }
3028
+ }
3029
+ else if (this.peekKeyword('SET')) {
3030
+ this.consumeKeyword('SET', "Expected SET.");
3031
+ if (this.matchKeyword('MAINTAINED')) {
3032
+ // SET MAINTAINED [(cols)] AS <body> — attach / re-attach a derivation.
3033
+ // No `using` clause: the module is the table's identity. The optional
3034
+ // `(cols)` is the explicit output-column rename list (the differ's
3035
+ // lossless encoding of an MV-sugar `(a, c)` rename): present ⇒ positional
3036
+ // rename + recorded explicit; absent ⇒ implicit (the body's natural
3037
+ // names). The required AS keeps it unambiguous. A trailing
3038
+ // `with defaults (…)` rides inside the body (`select.defaults`).
3039
+ const columns = this.parseMaintainedColumnList();
3040
+ this.consumeKeyword('AS', "Expected 'AS' after SET MAINTAINED.");
3041
+ const select = this.parseQueryExpr(undefined, /*requireReturning*/ true);
3042
+ action = { type: 'setMaintained', columns, select };
3043
+ }
3044
+ else {
3045
+ // Table-level `SET TAGS (...)` — whole-set tag replacement on the table.
3046
+ this.consumeKeyword('TAGS', "Expected 'TAGS' or 'MAINTAINED' after SET.");
3047
+ const tags = this.parseTags();
3048
+ action = { type: 'setTags', target: { kind: 'table' }, mode: 'replace', tags };
3049
+ }
2329
3050
  }
2330
3051
  else if (this.peekKeyword('ALTER')) {
2331
3052
  this.consumeKeyword('ALTER', "Expected ALTER.");
@@ -2333,14 +3054,37 @@ export class Parser {
2333
3054
  this.consumeKeyword('COLUMN', "Expected COLUMN.");
2334
3055
  action = this.alterColumnAction();
2335
3056
  }
3057
+ else if (this.peekKeyword('CONSTRAINT')) {
3058
+ // ALTER CONSTRAINT <name> {SET|ADD|DROP} TAGS (...) — tag mutation on a
3059
+ // named table-level constraint (only named constraints are addressable):
3060
+ // SET TAGS → whole-set replace, ADD TAGS → per-key merge,
3061
+ // DROP TAGS → per-key delete.
3062
+ this.consumeKeyword('CONSTRAINT', "Expected CONSTRAINT.");
3063
+ const constraintName = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected constraint name after ALTER CONSTRAINT.");
3064
+ if (this.matchKeyword('SET')) {
3065
+ this.consumeKeyword('TAGS', "Expected 'TAGS' after SET.");
3066
+ action = { type: 'setTags', target: { kind: 'constraint', constraintName }, mode: 'replace', tags: this.parseTags() };
3067
+ }
3068
+ else if (this.matchKeyword('ADD')) {
3069
+ this.consumeKeyword('TAGS', "Expected 'TAGS' after ADD.");
3070
+ action = { type: 'setTags', target: { kind: 'constraint', constraintName }, mode: 'merge', tags: this.parseTags() };
3071
+ }
3072
+ else if (this.matchKeyword('DROP')) {
3073
+ this.consumeKeyword('TAGS', "Expected 'TAGS' after DROP.");
3074
+ action = { type: 'dropTags', target: { kind: 'constraint', constraintName }, keys: this.parseTagKeys() };
3075
+ }
3076
+ else {
3077
+ throw this.error(this.peek(), `Expected SET, ADD, or DROP after ALTER CONSTRAINT ${constraintName}.`);
3078
+ }
3079
+ }
2336
3080
  else {
2337
- this.consumeKeyword('PRIMARY', "Expected 'PRIMARY' or 'COLUMN' after ALTER.");
3081
+ this.consumeKeyword('PRIMARY', "Expected 'PRIMARY', 'COLUMN', or 'CONSTRAINT' after ALTER.");
2338
3082
  this.consumeKeyword('KEY', "Expected 'KEY' after PRIMARY.");
2339
3083
  this.consume(TokenType.LPAREN, "Expected '(' after PRIMARY KEY.");
2340
3084
  const columns = [];
2341
3085
  if (!this.check(TokenType.RPAREN)) {
2342
3086
  do {
2343
- const colName = this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'], "Expected column name in PRIMARY KEY definition.");
3087
+ const colName = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name in PRIMARY KEY definition.");
2344
3088
  let direction;
2345
3089
  if (this.matchKeyword('ASC')) {
2346
3090
  direction = 'asc';
@@ -2371,7 +3115,7 @@ export class Parser {
2371
3115
  * Caller has already consumed ALTER COLUMN.
2372
3116
  */
2373
3117
  alterColumnAction() {
2374
- const columnName = this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'], "Expected column name after ALTER COLUMN.");
3118
+ const columnName = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name after ALTER COLUMN.");
2375
3119
  if (this.matchKeyword('SET')) {
2376
3120
  if (this.matchKeyword('NOT')) {
2377
3121
  this.consumeKeyword('NULL', "Expected 'NULL' after SET NOT.");
@@ -2386,7 +3130,29 @@ export class Parser {
2386
3130
  const expr = this.expression();
2387
3131
  return { type: 'alterColumn', columnName, setDefault: expr };
2388
3132
  }
2389
- throw this.error(this.peek(), "Expected NOT NULL, DATA TYPE, or DEFAULT after SET.");
3133
+ if (this.match(TokenType.COLLATE)) {
3134
+ // ALTER COLUMN <name> SET COLLATE <name> — change the column's collation,
3135
+ // re-sorting / re-validating any PK / UNIQUE / index that orders by it.
3136
+ if (!this.check(TokenType.IDENTIFIER)) {
3137
+ throw this.error(this.peek(), "Expected collation name after SET COLLATE.");
3138
+ }
3139
+ const collation = this.getIdentifierValue(this.advance());
3140
+ return { type: 'alterColumn', columnName, setCollation: collation };
3141
+ }
3142
+ if (this.matchKeyword('TAGS')) {
3143
+ // ALTER COLUMN <name> SET TAGS (...) — whole-set tag replacement on the column.
3144
+ const tags = this.parseTags();
3145
+ return { type: 'setTags', target: { kind: 'column', columnName }, mode: 'replace', tags };
3146
+ }
3147
+ throw this.error(this.peek(), "Expected NOT NULL, DATA TYPE, DEFAULT, COLLATE, or TAGS after SET.");
3148
+ }
3149
+ if (this.matchKeyword('ADD')) {
3150
+ // ALTER COLUMN <name> ADD TAGS (...) — per-key merge on the column. TAGS is
3151
+ // unambiguous here (the grammar after ALTER COLUMN <name> ADD is fixed), so
3152
+ // no '(' look-ahead guard is needed as it is at the table level.
3153
+ this.consumeKeyword('TAGS', "Expected 'TAGS' after ADD.");
3154
+ const tags = this.parseTags();
3155
+ return { type: 'setTags', target: { kind: 'column', columnName }, mode: 'merge', tags };
2390
3156
  }
2391
3157
  if (this.matchKeyword('DROP')) {
2392
3158
  if (this.matchKeyword('NOT')) {
@@ -2396,9 +3162,13 @@ export class Parser {
2396
3162
  if (this.matchKeyword('DEFAULT')) {
2397
3163
  return { type: 'alterColumn', columnName, setDefault: null };
2398
3164
  }
2399
- throw this.error(this.peek(), "Expected NOT NULL or DEFAULT after DROP.");
3165
+ if (this.matchKeyword('TAGS')) {
3166
+ // ALTER COLUMN <name> DROP TAGS (...) — per-key delete on the column.
3167
+ return { type: 'dropTags', target: { kind: 'column', columnName }, keys: this.parseTagKeys() };
3168
+ }
3169
+ throw this.error(this.peek(), "Expected NOT NULL, DEFAULT, or TAGS after DROP.");
2400
3170
  }
2401
- throw this.error(this.peek(), "Expected SET or DROP after ALTER COLUMN name.");
3171
+ throw this.error(this.peek(), "Expected SET, ADD, or DROP after ALTER COLUMN name.");
2402
3172
  }
2403
3173
  /**
2404
3174
  * Parse a data-type name as used in column definitions. Supports optional
@@ -2486,16 +3256,20 @@ export class Parser {
2486
3256
  return { type: 'pragma', ...nameValue, loc: _createLoc(startToken, this.previous()) };
2487
3257
  }
2488
3258
  /**
2489
- * Parse ANALYZE statement: ANALYZE [schema.]table | ANALYZE
3259
+ * Parse ANALYZE statement: ANALYZE [schema.]table | ANALYZE schema.* | ANALYZE
2490
3260
  */
2491
3261
  analyzeStatement(startToken) {
2492
3262
  // ANALYZE with no arguments → analyze all tables
2493
3263
  if (this.isAtEnd() || this.check(TokenType.SEMICOLON)) {
2494
3264
  return { type: 'analyze', loc: _createLoc(startToken, this.previous()) };
2495
3265
  }
2496
- // Parse optional schema.table or just table
3266
+ // Parse optional schema.table, schema.* (all tables in schema), or just table
2497
3267
  const name1 = this.consumeIdentifier([], "Expected table name after ANALYZE.");
2498
3268
  if (this.match(TokenType.DOT)) {
3269
+ // ANALYZE schema.* → analyze every table in the schema (schema-only shape)
3270
+ if (this.match(TokenType.ASTERISK)) {
3271
+ return { type: 'analyze', schemaName: name1, loc: _createLoc(startToken, this.previous()) };
3272
+ }
2499
3273
  const name2 = this.consumeIdentifier([], "Expected table name after schema qualifier.");
2500
3274
  return { type: 'analyze', schemaName: name1, tableName: name2, loc: _createLoc(startToken, this.previous()) };
2501
3275
  }
@@ -2503,6 +3277,8 @@ export class Parser {
2503
3277
  }
2504
3278
  // === Declarative schema parsing ===
2505
3279
  declareSchemaStatement(startToken) {
3280
+ // Optional contextual keyword: `declare logical schema X { ... }`.
3281
+ const isLogical = this.matchKeyword('LOGICAL');
2506
3282
  this.consumeKeyword('SCHEMA', "Expected 'SCHEMA' after DECLARE.");
2507
3283
  const schemaName = this.consumeIdentifier(['temp', 'temporary'], "Expected schema name after DECLARE.");
2508
3284
  let version;
@@ -2560,6 +3336,11 @@ export class Parser {
2560
3336
  this.consumeKeyword('INDEX', "Expected 'INDEX' after 'UNIQUE'.");
2561
3337
  items.push(this.declareIndexItem(true));
2562
3338
  }
3339
+ else if (this.peekKeyword('MATERIALIZED')) {
3340
+ this.advance();
3341
+ this.consumeKeyword('VIEW', "Expected 'VIEW' after 'MATERIALIZED'.");
3342
+ items.push(this.declareMaterializedViewItem());
3343
+ }
2563
3344
  else if (this.peekKeyword('VIEW')) {
2564
3345
  this.advance();
2565
3346
  items.push(this.declareViewItem());
@@ -2586,10 +3367,52 @@ export class Parser {
2586
3367
  }
2587
3368
  this.consume(TokenType.RBRACE, "Expected '}' to close schema declaration block.");
2588
3369
  const endTok = this.previous();
2589
- return { type: 'declareSchema', schemaName, version, using, items, loc: _createLoc(startToken, endTok) };
3370
+ return { type: 'declareSchema', schemaName, version, using, items, ...(isLogical ? { isLogical: true } : {}), loc: _createLoc(startToken, endTok) };
3371
+ }
3372
+ /**
3373
+ * Parses `declare lens for <X> over <Y> { ( view <T> as <select> ;? )* }`.
3374
+ * The DECLARE token is already consumed by {@link statement}. `lens` and `for`
3375
+ * are contextual keywords (matched via peekKeyword's IDENTIFIER fallback);
3376
+ * `over` is the existing window-function keyword.
3377
+ */
3378
+ declareLensStatement(startToken) {
3379
+ this.consumeKeyword('LENS', "Expected 'LENS' after DECLARE.");
3380
+ this.consumeKeyword('FOR', "Expected 'FOR' after DECLARE LENS.");
3381
+ const logicalSchema = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected logical schema name after 'FOR'.");
3382
+ this.consumeKeyword('OVER', "Expected 'OVER' after the logical schema name.");
3383
+ const basisSchema = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected basis schema name after 'OVER'.");
3384
+ this.consume(TokenType.LBRACE, "Expected '{' to start the lens declaration block.");
3385
+ const overrides = [];
3386
+ while (!this.check(TokenType.RBRACE)) {
3387
+ if (this.isAtEnd())
3388
+ break;
3389
+ this.consumeKeyword('VIEW', "Expected 'view' to begin a lens override.");
3390
+ const table = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected the logical table name after 'view'.");
3391
+ this.consumeKeyword('AS', "Expected 'AS' after the logical table name.");
3392
+ const body = this.parseQueryExpr();
3393
+ if (body.type !== 'select') {
3394
+ throw this.error(this.previous(), `A lens override body must be a SELECT; got '${body.type}'.`);
3395
+ }
3396
+ // A compound set-operation parses as a single `select` node carrying a
3397
+ // `compound` (or legacy `union`) pointer; the override merger composes
3398
+ // only the top leg, so reject the shape rather than silently mis-map.
3399
+ if (body.compound || body.union) {
3400
+ throw this.error(this.previous(), `A lens override body must be a single SELECT; compound set-operations (union/intersect/except) are not supported in v1 lens overrides.`);
3401
+ }
3402
+ overrides.push({ table, select: body });
3403
+ this.match(TokenType.SEMICOLON);
3404
+ }
3405
+ this.consume(TokenType.RBRACE, "Expected '}' to close the lens declaration block.");
3406
+ return {
3407
+ type: 'declareLens',
3408
+ logicalSchema,
3409
+ basisSchema,
3410
+ overrides,
3411
+ loc: _createLoc(startToken, this.previous()),
3412
+ };
2590
3413
  }
2591
3414
  declareTableItem() {
2592
- const tableName = this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'], 'Expected table name in declaration.');
3415
+ const tableName = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, 'Expected table name in declaration.');
2593
3416
  let moduleName;
2594
3417
  let moduleArgs;
2595
3418
  const columns = [];
@@ -2648,6 +3471,10 @@ export class Parser {
2648
3471
  else {
2649
3472
  this.consume(TokenType.RPAREN, "Expected ')' after table definition.");
2650
3473
  }
3474
+ // Optional `maintained as <body … with defaults (…)>` — the declared-shape
3475
+ // maintained-table form, carried through the CreateTableStmt reuse. (Differ
3476
+ // transition handling is a follow-up; the clause parses and round-trips.)
3477
+ const maintained = this.parseMaintainedClause();
2651
3478
  // Parse trailing WITH clauses (CONTEXT, TAGS) in any order
2652
3479
  let contextDefinitions;
2653
3480
  let tags;
@@ -2676,11 +3503,11 @@ export class Parser {
2676
3503
  ifNotExists: false,
2677
3504
  columns,
2678
3505
  constraints,
2679
- isTemporary: false,
2680
3506
  moduleName,
2681
3507
  moduleArgs,
2682
3508
  contextDefinitions,
2683
- tags
3509
+ tags,
3510
+ maintained
2684
3511
  };
2685
3512
  return { type: 'declaredTable', tableStmt };
2686
3513
  }
@@ -2691,6 +3518,11 @@ export class Parser {
2691
3518
  this.consume(TokenType.LPAREN, "Expected '(' before index columns.");
2692
3519
  const columns = this.indexedColumnList();
2693
3520
  this.consume(TokenType.RPAREN, "Expected ')' after index columns.");
3521
+ // Parse optional WHERE <predicate> (partial index), before WITH TAGS
3522
+ let where;
3523
+ if (this.matchKeyword('WHERE')) {
3524
+ where = this.expression();
3525
+ }
2694
3526
  // Parse optional WITH TAGS
2695
3527
  let tags;
2696
3528
  if (this.matchKeyword('WITH')) {
@@ -2707,6 +3539,7 @@ export class Parser {
2707
3539
  table: { type: 'identifier', name: tableName },
2708
3540
  ifNotExists: false,
2709
3541
  columns,
3542
+ where,
2710
3543
  isUnique,
2711
3544
  tags
2712
3545
  };
@@ -2719,9 +3552,10 @@ export class Parser {
2719
3552
  columns = this.identifierList();
2720
3553
  this.consume(TokenType.RPAREN, "Expected ')' after view columns.");
2721
3554
  }
2722
- this.consumeKeyword('AS', "Expected AS before SELECT in view declaration.");
2723
- const selTok = this.consume(TokenType.SELECT, "Expected SELECT after AS in view declaration.");
2724
- const select = this.selectStatement(selTok);
3555
+ this.consumeKeyword('AS', "Expected AS before view body in view declaration.");
3556
+ const select = this.parseQueryExpr(undefined, /*requireReturning*/ true);
3557
+ // A trailing `with defaults ()` rides inside the select body
3558
+ // (`select.defaults`); only the DDL-level WITH TAGS is parsed here.
2725
3559
  // Parse optional WITH TAGS
2726
3560
  let tags;
2727
3561
  if (this.matchKeyword('WITH')) {
@@ -2738,11 +3572,71 @@ export class Parser {
2738
3572
  ifNotExists: false,
2739
3573
  columns,
2740
3574
  select,
2741
- isTemporary: false,
2742
3575
  tags
2743
3576
  };
2744
3577
  return { type: 'declaredView', viewStmt };
2745
3578
  }
3579
+ declareMaterializedViewItem() {
3580
+ const viewName = this.consumeIdentifier('Expected materialized view name.');
3581
+ let columns;
3582
+ if (this.match(TokenType.LPAREN)) {
3583
+ columns = this.identifierList();
3584
+ this.consume(TokenType.RPAREN, "Expected ')' after materialized view columns.");
3585
+ }
3586
+ // Optional backing-module clause (`using mem(...)`) before the body — same
3587
+ // shape as the top-level CREATE MATERIALIZED VIEW form.
3588
+ let moduleName;
3589
+ const moduleArgs = {};
3590
+ if (this.matchKeyword('USING')) {
3591
+ moduleName = this.consumeIdentifier("Expected module name after 'USING'.");
3592
+ if (this.match(TokenType.LPAREN)) {
3593
+ let positionalIndex = 0;
3594
+ if (!this.check(TokenType.RPAREN)) {
3595
+ do {
3596
+ if (this.check(TokenType.STRING) || this.check(TokenType.INTEGER) || this.check(TokenType.FLOAT)) {
3597
+ const token = this.advance();
3598
+ moduleArgs[String(positionalIndex++)] = token.literal;
3599
+ }
3600
+ else if (this.check(TokenType.IDENTIFIER)) {
3601
+ const nameValue = this.nameValueItem('module argument');
3602
+ moduleArgs[nameValue.name] = nameValue.value && nameValue.value.type === 'literal'
3603
+ ? getSyncLiteral(nameValue.value)
3604
+ : (nameValue.value && nameValue.value.type === 'identifier' ? nameValue.value.name : nameValue.name);
3605
+ }
3606
+ else {
3607
+ throw this.error(this.peek(), "Expected module argument (string, number, or name=value pair).");
3608
+ }
3609
+ } while (this.match(TokenType.COMMA));
3610
+ }
3611
+ this.consume(TokenType.RPAREN, "Expected ')' after module arguments.");
3612
+ }
3613
+ }
3614
+ this.consumeKeyword('AS', "Expected AS before view body in materialized view declaration.");
3615
+ const select = this.parseQueryExpr(undefined, /*requireReturning*/ true);
3616
+ // A trailing `with defaults (…)` rides inside the select body
3617
+ // (`select.defaults`); only the DDL-level WITH TAGS is parsed here.
3618
+ // Parse optional WITH TAGS
3619
+ let tags;
3620
+ if (this.matchKeyword('WITH')) {
3621
+ if (this.matchKeyword('TAGS')) {
3622
+ tags = this.parseTags();
3623
+ }
3624
+ else {
3625
+ this.current--;
3626
+ }
3627
+ }
3628
+ const viewStmt = {
3629
+ type: 'createMaterializedView',
3630
+ view: { type: 'identifier', name: viewName },
3631
+ ifNotExists: false,
3632
+ columns,
3633
+ select,
3634
+ moduleName,
3635
+ moduleArgs: moduleName && Object.keys(moduleArgs).length > 0 ? moduleArgs : undefined,
3636
+ tags
3637
+ };
3638
+ return { type: 'declaredMaterializedView', viewStmt };
3639
+ }
2746
3640
  declareSeedItem() {
2747
3641
  // seed <table> ( (...), (...) ) or seed <table> values (col, ...) values (...), (...)
2748
3642
  const tableName = this.consumeIdentifier('Expected table name after SEED.');
@@ -3027,7 +3921,7 @@ export class Parser {
3027
3921
  // --- Stubs for required helpers (implement fully for CREATE TABLE) ---
3028
3922
  /** @internal Parses a column definition */
3029
3923
  columnDefinition() {
3030
- const name = this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'], "Expected column name.");
3924
+ const name = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected column name.");
3031
3925
  let dataType;
3032
3926
  if (this.check(TokenType.IDENTIFIER)) {
3033
3927
  dataType = this.advance().lexeme;
@@ -3184,6 +4078,22 @@ export class Parser {
3184
4078
  this.consume(TokenType.RPAREN, "Expected ')' after tag list.");
3185
4079
  return tags;
3186
4080
  }
4081
+ /**
4082
+ * @internal Parses a bare comma-list of tag keys — `(key [, key ...])` with no
4083
+ * `= value`, used by the DROP TAGS form. Mirrors {@link parseTags} but yields
4084
+ * just the keys. An empty list `()` yields `[]`.
4085
+ */
4086
+ parseTagKeys() {
4087
+ this.consume(TokenType.LPAREN, "Expected '(' after TAGS.");
4088
+ const keys = [];
4089
+ if (!this.check(TokenType.RPAREN)) {
4090
+ do {
4091
+ keys.push(this.consumeIdentifier("Expected tag key identifier."));
4092
+ } while (this.match(TokenType.COMMA));
4093
+ }
4094
+ this.consume(TokenType.RPAREN, "Expected ')' after tag key list.");
4095
+ return keys;
4096
+ }
3187
4097
  /** @internal Parses a tag value: string, number, TRUE, FALSE, or NULL */
3188
4098
  parseTagValue() {
3189
4099
  if (this.match(TokenType.STRING)) {
@@ -3418,7 +4328,19 @@ export class Parser {
3418
4328
  if (this.check(TokenType.REFERENCES)) {
3419
4329
  this.advance();
3420
4330
  }
3421
- const table = this.consumeIdentifier("Expected foreign table name.");
4331
+ // Optional `schema.` qualifier on the parent table (cross-schema FK),
4332
+ // mirroring tableIdentifier() so a schema named `temp` parses.
4333
+ const contextualKeywords = [...CONTEXTUAL_KEYWORDS, 'temp', 'temporary'];
4334
+ let schema;
4335
+ let table;
4336
+ if (this.checkIdentifierLike(contextualKeywords) && this.checkNext(1, TokenType.DOT)) {
4337
+ schema = this.consumeIdentifier(contextualKeywords, "Expected schema name.");
4338
+ this.advance(); // Consume DOT
4339
+ table = this.consumeIdentifier(contextualKeywords, "Expected foreign table name after schema.");
4340
+ }
4341
+ else {
4342
+ table = this.consumeIdentifier(contextualKeywords, "Expected foreign table name.");
4343
+ }
3422
4344
  let columns;
3423
4345
  if (this.match(TokenType.LPAREN)) {
3424
4346
  columns = this.identifierList();
@@ -3473,7 +4395,7 @@ export class Parser {
3473
4395
  break;
3474
4396
  }
3475
4397
  }
3476
- return { table, columns, onDelete, onUpdate, deferrable, initiallyDeferred };
4398
+ return { table, schema, columns, onDelete, onUpdate, deferrable, initiallyDeferred };
3477
4399
  }
3478
4400
  /** @internal Parses the ON CONFLICT clause */
3479
4401
  parseConflictClause() {
@@ -3518,7 +4440,7 @@ export class Parser {
3518
4440
  identifierList() {
3519
4441
  const identifiers = [];
3520
4442
  do {
3521
- identifiers.push(this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'], "Expected identifier in list."));
4443
+ identifiers.push(this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected identifier in list."));
3522
4444
  } while (this.match(TokenType.COMMA));
3523
4445
  return identifiers;
3524
4446
  }
@@ -3526,7 +4448,7 @@ export class Parser {
3526
4448
  identifierListWithDirection() {
3527
4449
  const identifiers = [];
3528
4450
  do {
3529
- const name = this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'], "Expected identifier in list.");
4451
+ const name = this.consumeIdentifier(CONTEXTUAL_KEYWORDS, "Expected identifier in list.");
3530
4452
  const direction = this.match(TokenType.ASC) ? 'asc' : this.match(TokenType.DESC) ? 'desc' : undefined;
3531
4453
  identifiers.push({ name, direction });
3532
4454
  } while (this.match(TokenType.COMMA));