@quereus/quereus 0.7.3 → 0.7.4

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 (566) hide show
  1. package/package.json +3 -3
  2. package/dist/src/common/constants.js.map +0 -1
  3. package/dist/src/common/datatype.js.map +0 -1
  4. package/dist/src/common/errors.js.map +0 -1
  5. package/dist/src/common/json-types.js.map +0 -1
  6. package/dist/src/common/logger.js.map +0 -1
  7. package/dist/src/common/type-inference.js.map +0 -1
  8. package/dist/src/common/types.js.map +0 -1
  9. package/dist/src/core/database-options.js.map +0 -1
  10. package/dist/src/core/database.js.map +0 -1
  11. package/dist/src/core/param.js.map +0 -1
  12. package/dist/src/core/statement.js.map +0 -1
  13. package/dist/src/func/builtins/aggregate.js.map +0 -1
  14. package/dist/src/func/builtins/builtin-window-functions.js.map +0 -1
  15. package/dist/src/func/builtins/conversion.js.map +0 -1
  16. package/dist/src/func/builtins/datetime.js.map +0 -1
  17. package/dist/src/func/builtins/explain.js.map +0 -1
  18. package/dist/src/func/builtins/generation.js.map +0 -1
  19. package/dist/src/func/builtins/index.js.map +0 -1
  20. package/dist/src/func/builtins/json-helpers.js.map +0 -1
  21. package/dist/src/func/builtins/json-tvf.js.map +0 -1
  22. package/dist/src/func/builtins/json.js.map +0 -1
  23. package/dist/src/func/builtins/scalar.js.map +0 -1
  24. package/dist/src/func/builtins/schema.js.map +0 -1
  25. package/dist/src/func/builtins/string.js.map +0 -1
  26. package/dist/src/func/builtins/timespan.js.map +0 -1
  27. package/dist/src/func/context.js.map +0 -1
  28. package/dist/src/func/registration.js.map +0 -1
  29. package/dist/src/index.js.map +0 -1
  30. package/dist/src/parser/ast.js.map +0 -1
  31. package/dist/src/parser/index.js.map +0 -1
  32. package/dist/src/parser/lexer.js.map +0 -1
  33. package/dist/src/parser/parser.js.map +0 -1
  34. package/dist/src/parser/utils.js.map +0 -1
  35. package/dist/src/parser/visitor.js.map +0 -1
  36. package/dist/src/planner/analysis/binding-collector.js.map +0 -1
  37. package/dist/src/planner/analysis/const-evaluator.js.map +0 -1
  38. package/dist/src/planner/analysis/const-pass.js.map +0 -1
  39. package/dist/src/planner/analysis/constraint-extractor.js.map +0 -1
  40. package/dist/src/planner/analysis/predicate-normalizer.js.map +0 -1
  41. package/dist/src/planner/building/alter-table.js.map +0 -1
  42. package/dist/src/planner/building/block.js.map +0 -1
  43. package/dist/src/planner/building/constraint-builder.js.map +0 -1
  44. package/dist/src/planner/building/create-assertion.js.map +0 -1
  45. package/dist/src/planner/building/create-view.js.map +0 -1
  46. package/dist/src/planner/building/ddl.js.map +0 -1
  47. package/dist/src/planner/building/declare-schema.js.map +0 -1
  48. package/dist/src/planner/building/delete.js.map +0 -1
  49. package/dist/src/planner/building/drop-assertion.js.map +0 -1
  50. package/dist/src/planner/building/drop-table.js.map +0 -1
  51. package/dist/src/planner/building/drop-view.js.map +0 -1
  52. package/dist/src/planner/building/expression.js.map +0 -1
  53. package/dist/src/planner/building/function-call.js.map +0 -1
  54. package/dist/src/planner/building/insert.js.map +0 -1
  55. package/dist/src/planner/building/pragma.js.map +0 -1
  56. package/dist/src/planner/building/schema-resolution.js.map +0 -1
  57. package/dist/src/planner/building/select-aggregates.js.map +0 -1
  58. package/dist/src/planner/building/select-compound.js.map +0 -1
  59. package/dist/src/planner/building/select-context.js.map +0 -1
  60. package/dist/src/planner/building/select-modifiers.js.map +0 -1
  61. package/dist/src/planner/building/select-projections.js.map +0 -1
  62. package/dist/src/planner/building/select-window.js.map +0 -1
  63. package/dist/src/planner/building/select.js.map +0 -1
  64. package/dist/src/planner/building/table-function.js.map +0 -1
  65. package/dist/src/planner/building/table.js.map +0 -1
  66. package/dist/src/planner/building/transaction.js.map +0 -1
  67. package/dist/src/planner/building/update.js.map +0 -1
  68. package/dist/src/planner/building/with.js.map +0 -1
  69. package/dist/src/planner/cache/correlation-detector.js.map +0 -1
  70. package/dist/src/planner/cache/materialization-advisory.js.map +0 -1
  71. package/dist/src/planner/cache/reference-graph.js.map +0 -1
  72. package/dist/src/planner/cost/index.js.map +0 -1
  73. package/dist/src/planner/debug/logger-utils.js.map +0 -1
  74. package/dist/src/planner/debug.js.map +0 -1
  75. package/dist/src/planner/framework/characteristics.js.map +0 -1
  76. package/dist/src/planner/framework/context.js.map +0 -1
  77. package/dist/src/planner/framework/pass.js.map +0 -1
  78. package/dist/src/planner/framework/physical-utils.js.map +0 -1
  79. package/dist/src/planner/framework/registry.js.map +0 -1
  80. package/dist/src/planner/framework/trace.js.map +0 -1
  81. package/dist/src/planner/nodes/add-constraint-node.js.map +0 -1
  82. package/dist/src/planner/nodes/aggregate-function.js.map +0 -1
  83. package/dist/src/planner/nodes/aggregate-node.js.map +0 -1
  84. package/dist/src/planner/nodes/array-index-node.js.map +0 -1
  85. package/dist/src/planner/nodes/block.js.map +0 -1
  86. package/dist/src/planner/nodes/cache-node.js.map +0 -1
  87. package/dist/src/planner/nodes/constraint-check-node.js.map +0 -1
  88. package/dist/src/planner/nodes/create-assertion-node.js.map +0 -1
  89. package/dist/src/planner/nodes/create-index-node.js.map +0 -1
  90. package/dist/src/planner/nodes/create-table-node.js.map +0 -1
  91. package/dist/src/planner/nodes/create-view-node.js.map +0 -1
  92. package/dist/src/planner/nodes/cte-node.js.map +0 -1
  93. package/dist/src/planner/nodes/cte-reference-node.js.map +0 -1
  94. package/dist/src/planner/nodes/declarative-schema.js.map +0 -1
  95. package/dist/src/planner/nodes/delete-node.js.map +0 -1
  96. package/dist/src/planner/nodes/distinct-node.js.map +0 -1
  97. package/dist/src/planner/nodes/dml-executor-node.js.map +0 -1
  98. package/dist/src/planner/nodes/drop-assertion-node.js.map +0 -1
  99. package/dist/src/planner/nodes/drop-table-node.js.map +0 -1
  100. package/dist/src/planner/nodes/drop-view-node.js.map +0 -1
  101. package/dist/src/planner/nodes/filter.js.map +0 -1
  102. package/dist/src/planner/nodes/function.js.map +0 -1
  103. package/dist/src/planner/nodes/insert-node.js.map +0 -1
  104. package/dist/src/planner/nodes/internal-recursive-cte-ref-node.js.map +0 -1
  105. package/dist/src/planner/nodes/join-node.js.map +0 -1
  106. package/dist/src/planner/nodes/limit-offset.js.map +0 -1
  107. package/dist/src/planner/nodes/plan-node-type.js.map +0 -1
  108. package/dist/src/planner/nodes/plan-node.js.map +0 -1
  109. package/dist/src/planner/nodes/pragma.js.map +0 -1
  110. package/dist/src/planner/nodes/project-node.js.map +0 -1
  111. package/dist/src/planner/nodes/recursive-cte-node.js.map +0 -1
  112. package/dist/src/planner/nodes/reference.js.map +0 -1
  113. package/dist/src/planner/nodes/remote-query-node.js.map +0 -1
  114. package/dist/src/planner/nodes/retrieve-node.js.map +0 -1
  115. package/dist/src/planner/nodes/returning-node.js.map +0 -1
  116. package/dist/src/planner/nodes/scalar.js.map +0 -1
  117. package/dist/src/planner/nodes/sequencing-node.js.map +0 -1
  118. package/dist/src/planner/nodes/set-operation-node.js.map +0 -1
  119. package/dist/src/planner/nodes/single-row.js.map +0 -1
  120. package/dist/src/planner/nodes/sink-node.js.map +0 -1
  121. package/dist/src/planner/nodes/sort.js.map +0 -1
  122. package/dist/src/planner/nodes/stream-aggregate.js.map +0 -1
  123. package/dist/src/planner/nodes/subquery.js.map +0 -1
  124. package/dist/src/planner/nodes/table-access-nodes.js.map +0 -1
  125. package/dist/src/planner/nodes/table-function-call.js.map +0 -1
  126. package/dist/src/planner/nodes/transaction-node.js.map +0 -1
  127. package/dist/src/planner/nodes/update-node.js.map +0 -1
  128. package/dist/src/planner/nodes/values-node.js.map +0 -1
  129. package/dist/src/planner/nodes/view-reference-node.js.map +0 -1
  130. package/dist/src/planner/nodes/window-function.js.map +0 -1
  131. package/dist/src/planner/nodes/window-node.js.map +0 -1
  132. package/dist/src/planner/optimizer-tuning.js.map +0 -1
  133. package/dist/src/planner/optimizer.js.map +0 -1
  134. package/dist/src/planner/planning-context.js.map +0 -1
  135. package/dist/src/planner/resolve.js.map +0 -1
  136. package/dist/src/planner/rules/access/rule-select-access-path.js.map +0 -1
  137. package/dist/src/planner/rules/aggregate/rule-aggregate-streaming.js.map +0 -1
  138. package/dist/src/planner/rules/cache/rule-cte-optimization.js.map +0 -1
  139. package/dist/src/planner/rules/cache/rule-materialization-advisory.js.map +0 -1
  140. package/dist/src/planner/rules/cache/rule-mutating-subquery-cache.js.map +0 -1
  141. package/dist/src/planner/rules/join/rule-join-greedy-commute.js.map +0 -1
  142. package/dist/src/planner/rules/join/rule-join-key-inference.js.map +0 -1
  143. package/dist/src/planner/rules/join/rule-quickpick-enumeration.js.map +0 -1
  144. package/dist/src/planner/rules/predicate/rule-predicate-pushdown.js.map +0 -1
  145. package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js.map +0 -1
  146. package/dist/src/planner/scopes/aliased.js.map +0 -1
  147. package/dist/src/planner/scopes/base.js.map +0 -1
  148. package/dist/src/planner/scopes/empty.js.map +0 -1
  149. package/dist/src/planner/scopes/global.js.map +0 -1
  150. package/dist/src/planner/scopes/multi.js.map +0 -1
  151. package/dist/src/planner/scopes/param.js.map +0 -1
  152. package/dist/src/planner/scopes/registered.js.map +0 -1
  153. package/dist/src/planner/scopes/scope.js.map +0 -1
  154. package/dist/src/planner/stats/basic-estimates.js.map +0 -1
  155. package/dist/src/planner/stats/index.js.map +0 -1
  156. package/dist/src/planner/type-utils.js.map +0 -1
  157. package/dist/src/planner/util/key-utils.js.map +0 -1
  158. package/dist/src/planner/validation/determinism-validator.js.map +0 -1
  159. package/dist/src/planner/validation/plan-validator.js.map +0 -1
  160. package/dist/src/runtime/async-util.js.map +0 -1
  161. package/dist/src/runtime/cache/shared-cache.js.map +0 -1
  162. package/dist/src/runtime/context-helpers.js.map +0 -1
  163. package/dist/src/runtime/deferred-constraint-queue.js.map +0 -1
  164. package/dist/src/runtime/emission-context.js.map +0 -1
  165. package/dist/src/runtime/emit/add-constraint.js.map +0 -1
  166. package/dist/src/runtime/emit/aggregate.js.map +0 -1
  167. package/dist/src/runtime/emit/array-index.js.map +0 -1
  168. package/dist/src/runtime/emit/between.js.map +0 -1
  169. package/dist/src/runtime/emit/binary.js.map +0 -1
  170. package/dist/src/runtime/emit/block.js.map +0 -1
  171. package/dist/src/runtime/emit/cache.js.map +0 -1
  172. package/dist/src/runtime/emit/case.js.map +0 -1
  173. package/dist/src/runtime/emit/cast.js.map +0 -1
  174. package/dist/src/runtime/emit/collate.js.map +0 -1
  175. package/dist/src/runtime/emit/column-reference.js.map +0 -1
  176. package/dist/src/runtime/emit/constraint-check.js.map +0 -1
  177. package/dist/src/runtime/emit/create-assertion.js.map +0 -1
  178. package/dist/src/runtime/emit/create-index.js.map +0 -1
  179. package/dist/src/runtime/emit/create-table.js.map +0 -1
  180. package/dist/src/runtime/emit/create-view.js.map +0 -1
  181. package/dist/src/runtime/emit/cte-reference.js.map +0 -1
  182. package/dist/src/runtime/emit/cte.js.map +0 -1
  183. package/dist/src/runtime/emit/delete.js.map +0 -1
  184. package/dist/src/runtime/emit/distinct.js.map +0 -1
  185. package/dist/src/runtime/emit/dml-executor.js.map +0 -1
  186. package/dist/src/runtime/emit/drop-assertion.js.map +0 -1
  187. package/dist/src/runtime/emit/drop-table.js.map +0 -1
  188. package/dist/src/runtime/emit/drop-view.js.map +0 -1
  189. package/dist/src/runtime/emit/filter.js.map +0 -1
  190. package/dist/src/runtime/emit/insert.js.map +0 -1
  191. package/dist/src/runtime/emit/internal-recursive-cte-ref.js.map +0 -1
  192. package/dist/src/runtime/emit/join.js.map +0 -1
  193. package/dist/src/runtime/emit/limit-offset.js.map +0 -1
  194. package/dist/src/runtime/emit/literal.js.map +0 -1
  195. package/dist/src/runtime/emit/parameter.js.map +0 -1
  196. package/dist/src/runtime/emit/pragma.js.map +0 -1
  197. package/dist/src/runtime/emit/project.js.map +0 -1
  198. package/dist/src/runtime/emit/recursive-cte.js.map +0 -1
  199. package/dist/src/runtime/emit/remote-query.js.map +0 -1
  200. package/dist/src/runtime/emit/retrieve.js.map +0 -1
  201. package/dist/src/runtime/emit/returning.js.map +0 -1
  202. package/dist/src/runtime/emit/scalar-function.js.map +0 -1
  203. package/dist/src/runtime/emit/scan.js.map +0 -1
  204. package/dist/src/runtime/emit/schema-declarative.js.map +0 -1
  205. package/dist/src/runtime/emit/sequencing.js.map +0 -1
  206. package/dist/src/runtime/emit/set-operation.js.map +0 -1
  207. package/dist/src/runtime/emit/sink.js.map +0 -1
  208. package/dist/src/runtime/emit/sort.js.map +0 -1
  209. package/dist/src/runtime/emit/subquery.js.map +0 -1
  210. package/dist/src/runtime/emit/table-valued-function.js.map +0 -1
  211. package/dist/src/runtime/emit/temporal-arithmetic.js.map +0 -1
  212. package/dist/src/runtime/emit/transaction.js.map +0 -1
  213. package/dist/src/runtime/emit/unary.js.map +0 -1
  214. package/dist/src/runtime/emit/update.js.map +0 -1
  215. package/dist/src/runtime/emit/values.js.map +0 -1
  216. package/dist/src/runtime/emit/window-function.js.map +0 -1
  217. package/dist/src/runtime/emit/window.js.map +0 -1
  218. package/dist/src/runtime/emitters.js.map +0 -1
  219. package/dist/src/runtime/register.js.map +0 -1
  220. package/dist/src/runtime/scheduler.js.map +0 -1
  221. package/dist/src/runtime/types.js.map +0 -1
  222. package/dist/src/runtime/utils.js.map +0 -1
  223. package/dist/src/schema/assertion.js.map +0 -1
  224. package/dist/src/schema/catalog.js.map +0 -1
  225. package/dist/src/schema/change-events.js.map +0 -1
  226. package/dist/src/schema/column.js.map +0 -1
  227. package/dist/src/schema/declared-schema-manager.js.map +0 -1
  228. package/dist/src/schema/function.js.map +0 -1
  229. package/dist/src/schema/manager.js.map +0 -1
  230. package/dist/src/schema/schema-differ.js.map +0 -1
  231. package/dist/src/schema/schema-hasher.js.map +0 -1
  232. package/dist/src/schema/schema.js.map +0 -1
  233. package/dist/src/schema/table.js.map +0 -1
  234. package/dist/src/schema/view.js.map +0 -1
  235. package/dist/src/schema/window-function.js.map +0 -1
  236. package/dist/src/types/builtin-types.js.map +0 -1
  237. package/dist/src/types/index.js.map +0 -1
  238. package/dist/src/types/json-type.js.map +0 -1
  239. package/dist/src/types/logical-type.js.map +0 -1
  240. package/dist/src/types/plugin-interface.js.map +0 -1
  241. package/dist/src/types/registry.js.map +0 -1
  242. package/dist/src/types/temporal-types.js.map +0 -1
  243. package/dist/src/types/validation.js.map +0 -1
  244. package/dist/src/util/affinity.js.map +0 -1
  245. package/dist/src/util/ast-stringify.js.map +0 -1
  246. package/dist/src/util/cached.js.map +0 -1
  247. package/dist/src/util/coercion.js.map +0 -1
  248. package/dist/src/util/comparison.js.map +0 -1
  249. package/dist/src/util/environment.js.map +0 -1
  250. package/dist/src/util/hash.js.map +0 -1
  251. package/dist/src/util/latches.js.map +0 -1
  252. package/dist/src/util/mutation-statement.js.map +0 -1
  253. package/dist/src/util/patterns.js.map +0 -1
  254. package/dist/src/util/plan-formatter.js.map +0 -1
  255. package/dist/src/util/plugin-helper.js.map +0 -1
  256. package/dist/src/util/row-descriptor.js.map +0 -1
  257. package/dist/src/util/serialization.js.map +0 -1
  258. package/dist/src/util/sql-literal.js.map +0 -1
  259. package/dist/src/util/working-table-iterable.js.map +0 -1
  260. package/dist/src/vtab/best-access-plan.js.map +0 -1
  261. package/dist/src/vtab/connection.js.map +0 -1
  262. package/dist/src/vtab/filter-info.js.map +0 -1
  263. package/dist/src/vtab/index-info.js.map +0 -1
  264. package/dist/src/vtab/manifest.js.map +0 -1
  265. package/dist/src/vtab/memory/connection.js.map +0 -1
  266. package/dist/src/vtab/memory/index.js.map +0 -1
  267. package/dist/src/vtab/memory/layer/base-cursor.js.map +0 -1
  268. package/dist/src/vtab/memory/layer/base.js.map +0 -1
  269. package/dist/src/vtab/memory/layer/connection.js.map +0 -1
  270. package/dist/src/vtab/memory/layer/interface.js.map +0 -1
  271. package/dist/src/vtab/memory/layer/manager.js.map +0 -1
  272. package/dist/src/vtab/memory/layer/safe-iterate.js.map +0 -1
  273. package/dist/src/vtab/memory/layer/scan-plan.js.map +0 -1
  274. package/dist/src/vtab/memory/layer/transaction-cursor.js.map +0 -1
  275. package/dist/src/vtab/memory/layer/transaction.js.map +0 -1
  276. package/dist/src/vtab/memory/module.js.map +0 -1
  277. package/dist/src/vtab/memory/table.js.map +0 -1
  278. package/dist/src/vtab/memory/types.js.map +0 -1
  279. package/dist/src/vtab/memory/utils/logging.js.map +0 -1
  280. package/dist/src/vtab/memory/utils/primary-key.js.map +0 -1
  281. package/dist/src/vtab/module.js.map +0 -1
  282. package/dist/src/vtab/table.js.map +0 -1
  283. package/src/common/constants.ts +0 -60
  284. package/src/common/datatype.ts +0 -85
  285. package/src/common/errors.ts +0 -189
  286. package/src/common/json-types.ts +0 -16
  287. package/src/common/logger.ts +0 -97
  288. package/src/common/type-inference.ts +0 -39
  289. package/src/common/types.ts +0 -151
  290. package/src/core/database-options.ts +0 -258
  291. package/src/core/database.ts +0 -1461
  292. package/src/core/param.ts +0 -56
  293. package/src/core/statement.ts +0 -528
  294. package/src/func/builtins/aggregate.ts +0 -269
  295. package/src/func/builtins/builtin-window-functions.ts +0 -166
  296. package/src/func/builtins/conversion.ts +0 -226
  297. package/src/func/builtins/datetime.ts +0 -500
  298. package/src/func/builtins/explain.ts +0 -779
  299. package/src/func/builtins/generation.ts +0 -43
  300. package/src/func/builtins/index.ts +0 -167
  301. package/src/func/builtins/json-helpers.ts +0 -237
  302. package/src/func/builtins/json-tvf.ts +0 -224
  303. package/src/func/builtins/json.ts +0 -588
  304. package/src/func/builtins/scalar.ts +0 -423
  305. package/src/func/builtins/schema.ts +0 -213
  306. package/src/func/builtins/string.ts +0 -306
  307. package/src/func/builtins/timespan.ts +0 -179
  308. package/src/func/context.ts +0 -258
  309. package/src/func/registration.ts +0 -201
  310. package/src/index.ts +0 -172
  311. package/src/parser/ast.ts +0 -581
  312. package/src/parser/index.ts +0 -65
  313. package/src/parser/lexer.ts +0 -806
  314. package/src/parser/parser.ts +0 -3352
  315. package/src/parser/utils.ts +0 -10
  316. package/src/parser/visitor.ts +0 -188
  317. package/src/planner/analysis/README.md +0 -93
  318. package/src/planner/analysis/binding-collector.ts +0 -83
  319. package/src/planner/analysis/const-evaluator.ts +0 -63
  320. package/src/planner/analysis/const-pass.ts +0 -282
  321. package/src/planner/analysis/constraint-extractor.ts +0 -764
  322. package/src/planner/analysis/predicate-normalizer.ts +0 -237
  323. package/src/planner/building/alter-table.ts +0 -49
  324. package/src/planner/building/block.ts +0 -93
  325. package/src/planner/building/constraint-builder.ts +0 -178
  326. package/src/planner/building/create-assertion.ts +0 -7
  327. package/src/planner/building/create-view.ts +0 -29
  328. package/src/planner/building/ddl.ts +0 -24
  329. package/src/planner/building/declare-schema.ts +0 -22
  330. package/src/planner/building/delete.ts +0 -218
  331. package/src/planner/building/drop-assertion.ts +0 -11
  332. package/src/planner/building/drop-table.ts +0 -13
  333. package/src/planner/building/drop-view.ts +0 -19
  334. package/src/planner/building/expression.ts +0 -205
  335. package/src/planner/building/function-call.ts +0 -129
  336. package/src/planner/building/insert.ts +0 -435
  337. package/src/planner/building/pragma.ts +0 -34
  338. package/src/planner/building/schema-resolution.ts +0 -176
  339. package/src/planner/building/select-aggregates.ts +0 -318
  340. package/src/planner/building/select-compound.ts +0 -119
  341. package/src/planner/building/select-context.ts +0 -85
  342. package/src/planner/building/select-modifiers.ts +0 -236
  343. package/src/planner/building/select-projections.ts +0 -177
  344. package/src/planner/building/select-window.ts +0 -259
  345. package/src/planner/building/select.ts +0 -567
  346. package/src/planner/building/table-function.ts +0 -49
  347. package/src/planner/building/table.ts +0 -40
  348. package/src/planner/building/transaction.ts +0 -23
  349. package/src/planner/building/update.ts +0 -331
  350. package/src/planner/building/with.ts +0 -180
  351. package/src/planner/cache/correlation-detector.ts +0 -83
  352. package/src/planner/cache/materialization-advisory.ts +0 -265
  353. package/src/planner/cache/reference-graph.ts +0 -196
  354. package/src/planner/cost/index.ts +0 -169
  355. package/src/planner/debug/logger-utils.ts +0 -68
  356. package/src/planner/debug.ts +0 -480
  357. package/src/planner/framework/README.md +0 -132
  358. package/src/planner/framework/characteristics.ts +0 -503
  359. package/src/planner/framework/context.ts +0 -239
  360. package/src/planner/framework/pass.ts +0 -354
  361. package/src/planner/framework/physical-utils.ts +0 -210
  362. package/src/planner/framework/registry.ts +0 -261
  363. package/src/planner/framework/trace.ts +0 -259
  364. package/src/planner/nodes/add-constraint-node.ts +0 -62
  365. package/src/planner/nodes/aggregate-function.ts +0 -155
  366. package/src/planner/nodes/aggregate-node.ts +0 -267
  367. package/src/planner/nodes/array-index-node.ts +0 -50
  368. package/src/planner/nodes/block.ts +0 -80
  369. package/src/planner/nodes/cache-node.ts +0 -103
  370. package/src/planner/nodes/constraint-check-node.ts +0 -138
  371. package/src/planner/nodes/create-assertion-node.ts +0 -51
  372. package/src/planner/nodes/create-index-node.ts +0 -41
  373. package/src/planner/nodes/create-table-node.ts +0 -35
  374. package/src/planner/nodes/create-view-node.ts +0 -44
  375. package/src/planner/nodes/cte-node.ts +0 -168
  376. package/src/planner/nodes/cte-reference-node.ts +0 -125
  377. package/src/planner/nodes/declarative-schema.ts +0 -221
  378. package/src/planner/nodes/delete-node.ts +0 -102
  379. package/src/planner/nodes/distinct-node.ts +0 -107
  380. package/src/planner/nodes/dml-executor-node.ts +0 -104
  381. package/src/planner/nodes/drop-assertion-node.ts +0 -50
  382. package/src/planner/nodes/drop-table-node.ts +0 -36
  383. package/src/planner/nodes/drop-view-node.ts +0 -37
  384. package/src/planner/nodes/filter.ts +0 -144
  385. package/src/planner/nodes/function.ts +0 -98
  386. package/src/planner/nodes/insert-node.ts +0 -126
  387. package/src/planner/nodes/internal-recursive-cte-ref-node.ts +0 -61
  388. package/src/planner/nodes/join-node.ts +0 -336
  389. package/src/planner/nodes/limit-offset.ts +0 -144
  390. package/src/planner/nodes/plan-node-type.ts +0 -95
  391. package/src/planner/nodes/plan-node.ts +0 -503
  392. package/src/planner/nodes/pragma.ts +0 -98
  393. package/src/planner/nodes/project-node.ts +0 -337
  394. package/src/planner/nodes/recursive-cte-node.ts +0 -158
  395. package/src/planner/nodes/reference.ts +0 -334
  396. package/src/planner/nodes/remote-query-node.ts +0 -73
  397. package/src/planner/nodes/retrieve-node.ts +0 -86
  398. package/src/planner/nodes/returning-node.ts +0 -269
  399. package/src/planner/nodes/scalar.ts +0 -772
  400. package/src/planner/nodes/sequencing-node.ts +0 -113
  401. package/src/planner/nodes/set-operation-node.ts +0 -87
  402. package/src/planner/nodes/single-row.ts +0 -85
  403. package/src/planner/nodes/sink-node.ts +0 -61
  404. package/src/planner/nodes/sort.ts +0 -166
  405. package/src/planner/nodes/stream-aggregate.ts +0 -293
  406. package/src/planner/nodes/subquery.ts +0 -268
  407. package/src/planner/nodes/table-access-nodes.ts +0 -323
  408. package/src/planner/nodes/table-function-call.ts +0 -134
  409. package/src/planner/nodes/transaction-node.ts +0 -55
  410. package/src/planner/nodes/update-node.ts +0 -138
  411. package/src/planner/nodes/values-node.ts +0 -244
  412. package/src/planner/nodes/view-reference-node.ts +0 -97
  413. package/src/planner/nodes/window-function.ts +0 -73
  414. package/src/planner/nodes/window-node.ts +0 -199
  415. package/src/planner/optimizer-tuning.ts +0 -105
  416. package/src/planner/optimizer.ts +0 -332
  417. package/src/planner/planning-context.ts +0 -190
  418. package/src/planner/resolve.ts +0 -101
  419. package/src/planner/rules/README.md +0 -96
  420. package/src/planner/rules/access/rule-select-access-path.ts +0 -399
  421. package/src/planner/rules/aggregate/rule-aggregate-streaming.ts +0 -162
  422. package/src/planner/rules/cache/rule-cte-optimization.ts +0 -79
  423. package/src/planner/rules/cache/rule-materialization-advisory.ts +0 -77
  424. package/src/planner/rules/cache/rule-mutating-subquery-cache.ts +0 -104
  425. package/src/planner/rules/join/rule-join-greedy-commute.ts +0 -48
  426. package/src/planner/rules/join/rule-join-key-inference.ts +0 -35
  427. package/src/planner/rules/join/rule-quickpick-enumeration.ts +0 -267
  428. package/src/planner/rules/predicate/rule-predicate-pushdown.ts +0 -144
  429. package/src/planner/rules/retrieve/rule-grow-retrieve.ts +0 -337
  430. package/src/planner/scopes/aliased.ts +0 -50
  431. package/src/planner/scopes/base.ts +0 -10
  432. package/src/planner/scopes/empty.ts +0 -12
  433. package/src/planner/scopes/global.ts +0 -73
  434. package/src/planner/scopes/multi.ts +0 -40
  435. package/src/planner/scopes/param.ts +0 -95
  436. package/src/planner/scopes/registered.ts +0 -67
  437. package/src/planner/scopes/scope.ts +0 -16
  438. package/src/planner/stats/basic-estimates.ts +0 -107
  439. package/src/planner/stats/index.ts +0 -158
  440. package/src/planner/type-utils.ts +0 -87
  441. package/src/planner/util/key-utils.ts +0 -46
  442. package/src/planner/validation/determinism-validator.ts +0 -104
  443. package/src/planner/validation/plan-validator.ts +0 -335
  444. package/src/runtime/async-util.ts +0 -283
  445. package/src/runtime/cache/shared-cache.ts +0 -169
  446. package/src/runtime/context-helpers.ts +0 -191
  447. package/src/runtime/deferred-constraint-queue.ts +0 -196
  448. package/src/runtime/emission-context.ts +0 -319
  449. package/src/runtime/emit/add-constraint.ts +0 -78
  450. package/src/runtime/emit/aggregate.ts +0 -581
  451. package/src/runtime/emit/array-index.ts +0 -25
  452. package/src/runtime/emit/between.ts +0 -51
  453. package/src/runtime/emit/binary.ts +0 -357
  454. package/src/runtime/emit/block.ts +0 -23
  455. package/src/runtime/emit/cache.ts +0 -64
  456. package/src/runtime/emit/case.ts +0 -87
  457. package/src/runtime/emit/cast.ts +0 -151
  458. package/src/runtime/emit/collate.ts +0 -9
  459. package/src/runtime/emit/column-reference.ts +0 -17
  460. package/src/runtime/emit/constraint-check.ts +0 -290
  461. package/src/runtime/emit/create-assertion.ts +0 -82
  462. package/src/runtime/emit/create-index.ts +0 -15
  463. package/src/runtime/emit/create-table.ts +0 -15
  464. package/src/runtime/emit/create-view.ts +0 -52
  465. package/src/runtime/emit/cte-reference.ts +0 -38
  466. package/src/runtime/emit/cte.ts +0 -39
  467. package/src/runtime/emit/delete.ts +0 -24
  468. package/src/runtime/emit/distinct.ts +0 -40
  469. package/src/runtime/emit/dml-executor.ts +0 -198
  470. package/src/runtime/emit/drop-assertion.ts +0 -45
  471. package/src/runtime/emit/drop-table.ts +0 -27
  472. package/src/runtime/emit/drop-view.ts +0 -49
  473. package/src/runtime/emit/filter.ts +0 -30
  474. package/src/runtime/emit/insert.ts +0 -42
  475. package/src/runtime/emit/internal-recursive-cte-ref.ts +0 -37
  476. package/src/runtime/emit/join.ts +0 -148
  477. package/src/runtime/emit/limit-offset.ts +0 -73
  478. package/src/runtime/emit/literal.ts +0 -17
  479. package/src/runtime/emit/parameter.ts +0 -59
  480. package/src/runtime/emit/pragma.ts +0 -56
  481. package/src/runtime/emit/project.ts +0 -46
  482. package/src/runtime/emit/recursive-cte.ts +0 -111
  483. package/src/runtime/emit/remote-query.ts +0 -47
  484. package/src/runtime/emit/retrieve.ts +0 -15
  485. package/src/runtime/emit/returning.ts +0 -41
  486. package/src/runtime/emit/scalar-function.ts +0 -69
  487. package/src/runtime/emit/scan.ts +0 -106
  488. package/src/runtime/emit/schema-declarative.ts +0 -215
  489. package/src/runtime/emit/sequencing.ts +0 -24
  490. package/src/runtime/emit/set-operation.ts +0 -141
  491. package/src/runtime/emit/sink.ts +0 -27
  492. package/src/runtime/emit/sort.ts +0 -75
  493. package/src/runtime/emit/subquery.ts +0 -203
  494. package/src/runtime/emit/table-valued-function.ts +0 -106
  495. package/src/runtime/emit/temporal-arithmetic.ts +0 -302
  496. package/src/runtime/emit/transaction.ts +0 -205
  497. package/src/runtime/emit/unary.ts +0 -101
  498. package/src/runtime/emit/update.ts +0 -66
  499. package/src/runtime/emit/values.ts +0 -66
  500. package/src/runtime/emit/window-function.ts +0 -42
  501. package/src/runtime/emit/window.ts +0 -458
  502. package/src/runtime/emitters.ts +0 -183
  503. package/src/runtime/register.ts +0 -150
  504. package/src/runtime/scheduler.ts +0 -488
  505. package/src/runtime/types.ts +0 -242
  506. package/src/runtime/utils.ts +0 -177
  507. package/src/schema/assertion.ts +0 -21
  508. package/src/schema/catalog.ts +0 -269
  509. package/src/schema/change-events.ts +0 -80
  510. package/src/schema/column.ts +0 -51
  511. package/src/schema/declared-schema-manager.ts +0 -82
  512. package/src/schema/function.ts +0 -188
  513. package/src/schema/manager.ts +0 -1034
  514. package/src/schema/schema-differ.ts +0 -214
  515. package/src/schema/schema-hasher.ts +0 -26
  516. package/src/schema/schema.ts +0 -222
  517. package/src/schema/table.ts +0 -409
  518. package/src/schema/view.ts +0 -19
  519. package/src/schema/window-function.ts +0 -56
  520. package/src/types/builtin-types.ts +0 -350
  521. package/src/types/index.ts +0 -17
  522. package/src/types/json-type.ts +0 -152
  523. package/src/types/logical-type.ts +0 -91
  524. package/src/types/plugin-interface.ts +0 -10
  525. package/src/types/registry.ts +0 -204
  526. package/src/types/temporal-types.ts +0 -290
  527. package/src/types/validation.ts +0 -120
  528. package/src/util/affinity.ts +0 -151
  529. package/src/util/ast-stringify.ts +0 -887
  530. package/src/util/cached.ts +0 -25
  531. package/src/util/coercion.ts +0 -113
  532. package/src/util/comparison.ts +0 -510
  533. package/src/util/environment.ts +0 -52
  534. package/src/util/hash.ts +0 -90
  535. package/src/util/latches.ts +0 -47
  536. package/src/util/mutation-statement.ts +0 -135
  537. package/src/util/patterns.ts +0 -56
  538. package/src/util/plan-formatter.ts +0 -48
  539. package/src/util/plugin-helper.ts +0 -110
  540. package/src/util/row-descriptor.ts +0 -105
  541. package/src/util/serialization.ts +0 -47
  542. package/src/util/sql-literal.ts +0 -22
  543. package/src/util/working-table-iterable.ts +0 -38
  544. package/src/vtab/best-access-plan.ts +0 -244
  545. package/src/vtab/connection.ts +0 -36
  546. package/src/vtab/filter-info.ts +0 -23
  547. package/src/vtab/index-info.ts +0 -84
  548. package/src/vtab/manifest.ts +0 -86
  549. package/src/vtab/memory/connection.ts +0 -73
  550. package/src/vtab/memory/index.ts +0 -191
  551. package/src/vtab/memory/layer/base-cursor.ts +0 -124
  552. package/src/vtab/memory/layer/base.ts +0 -275
  553. package/src/vtab/memory/layer/connection.ts +0 -203
  554. package/src/vtab/memory/layer/interface.ts +0 -47
  555. package/src/vtab/memory/layer/manager.ts +0 -909
  556. package/src/vtab/memory/layer/safe-iterate.ts +0 -49
  557. package/src/vtab/memory/layer/scan-plan.ts +0 -84
  558. package/src/vtab/memory/layer/transaction-cursor.ts +0 -162
  559. package/src/vtab/memory/layer/transaction.ts +0 -229
  560. package/src/vtab/memory/module.ts +0 -667
  561. package/src/vtab/memory/table.ts +0 -251
  562. package/src/vtab/memory/types.ts +0 -23
  563. package/src/vtab/memory/utils/logging.ts +0 -36
  564. package/src/vtab/memory/utils/primary-key.ts +0 -163
  565. package/src/vtab/module.ts +0 -162
  566. package/src/vtab/table.ts +0 -177
@@ -1,3352 +0,0 @@
1
- import { createLogger } from '../common/logger.js'; // Import logger
2
- import { Lexer, type Token, TokenType } from './lexer.js';
3
- import * as AST from './ast.js';
4
- import { ConflictResolution } from '../common/constants.js';
5
- import type { RowOp, SqlValue } from '../common/types.js';
6
- import { quereusError } from '../common/errors.js';
7
- import { StatusCode } from '../common/types.js';
8
- import { getSyncLiteral } from './utils.js';
9
-
10
- const log = createLogger('parser:parser'); // Create logger instance
11
- const errorLog = log.extend('error');
12
-
13
- export class ParseError extends Error {
14
- token: Token;
15
-
16
- constructor(token: Token, message: string) {
17
- super(message);
18
- this.token = token;
19
- this.name = 'ParseError';
20
- }
21
- }
22
-
23
- // Helper function to create the location object
24
- function _createLoc(startToken: Token, endToken: Token): AST.AstNode['loc'] {
25
- return {
26
- start: {
27
- line: startToken.startLine,
28
- column: startToken.startColumn,
29
- offset: startToken.startOffset,
30
- },
31
- end: {
32
- line: endToken.endLine,
33
- column: endToken.endColumn,
34
- offset: endToken.endOffset,
35
- },
36
- };
37
- }
38
-
39
- export class Parser {
40
- private tokens: Token[] = [];
41
- private current = 0;
42
- // Counter for positional parameters
43
- private parameterPosition = 1;
44
- // Track opening parentheses for accurate error locations
45
- private parenStack: Token[] = [];
46
-
47
- /**
48
- * Initialize the parser with tokens from a SQL string
49
- * @param sql SQL string to parse
50
- * @returns this parser instance for chaining
51
- */
52
- initialize(sql: string): Parser {
53
- const lexer = new Lexer(sql);
54
- this.tokens = lexer.scanTokens();
55
- this.current = 0;
56
- this.parameterPosition = 1; // Reset parameter counter
57
- this.parenStack = [];
58
-
59
- // Check for errors from lexer
60
- const errorToken = this.tokens.find(t => t.type === TokenType.ERROR);
61
- if (errorToken) {
62
- quereusError(
63
- `Lexer error: ${errorToken.lexeme}`,
64
- StatusCode.ERROR,
65
- undefined,
66
- {
67
- loc: {
68
- start: {
69
- line: errorToken.startLine,
70
- column: errorToken.startColumn,
71
- },
72
- end: {
73
- line: errorToken.endLine,
74
- column: errorToken.endColumn,
75
- }
76
- }
77
- }
78
- );
79
- }
80
-
81
- return this;
82
- }
83
-
84
- /**
85
- * Parse SQL text into an array of ASTs
86
- */
87
- parseAll(sql: string): AST.Statement[] {
88
- this.initialize(sql);
89
- const statements: AST.Statement[] = [];
90
-
91
- while (!this.isAtEnd()) {
92
- try {
93
- const stmt = this.statement();
94
- statements.push(stmt as AST.Statement); // Cast needed as statement() returns AstNode
95
-
96
- // Consume optional semicolon at the end of the statement
97
- this.match(TokenType.SEMICOLON);
98
-
99
- } catch (e) {
100
- // error() method now throws QuereusError directly with location info
101
- if (e instanceof Error && e.name === 'QuereusError') {
102
- throw e;
103
- }
104
-
105
- // Handle unexpected non-QuereusError exceptions
106
- errorLog("Unhandled parser error: %O", e);
107
- quereusError(
108
- `Parser error: ${e instanceof Error ? e.message : e}`,
109
- StatusCode.ERROR,
110
- e instanceof Error ? e : undefined
111
- );
112
- }
113
- }
114
-
115
- // Report any unterminated parenthesis at EOF with pointer to opening location
116
- if (this.parenStack.length > 0) {
117
- const openToken = this.parenStack[this.parenStack.length - 1];
118
- quereusError(
119
- `Unterminated '(' opened at line ${openToken.startLine}, column ${openToken.startColumn}. Expected ')' before end of input.`,
120
- StatusCode.ERROR,
121
- undefined,
122
- {
123
- loc: {
124
- start: { line: openToken.startLine, column: openToken.startColumn },
125
- end: { line: this.peek().endLine, column: this.peek().endColumn },
126
- },
127
- }
128
- );
129
- }
130
-
131
- // If we consumed all tokens and didn't parse any statements (e.g., empty input or only comments/whitespace),
132
- // return an empty array instead of throwing an error.
133
- return statements;
134
- }
135
-
136
- /**
137
- * Parse SQL text into a single AST node.
138
- * Use parseAll instead for potentially multi-statement strings.
139
- * Throws error if more than one statement is found after the first.
140
- */
141
- parse(sql: string): AST.Statement {
142
- const statements = this.parseAll(sql);
143
- if (statements.length === 0) {
144
- // Handle case of empty input or input with only comments/whitespace
145
- // Depending on desired behavior, could return null, undefined, or throw.
146
- // Throwing seems reasonable as prepare/eval expect a statement.
147
- quereusError("No SQL statement found to parse.", StatusCode.ERROR);
148
- }
149
- if (statements.length > 1) {
150
- // Find the token that starts the second statement for better error location
151
- const secondStatementStartToken = statements[1]?.loc?.start;
152
- const errToken = this.tokens.find(t => t.startOffset === secondStatementStartToken?.offset) ?? this.peek();
153
- this.error(errToken, "Provided SQL string contains multiple statements. Use exec() for multi-statement execution.");
154
- }
155
- return statements[0];
156
- }
157
-
158
- /**
159
- * Attempts to parse a WITH clause if present.
160
- * @returns The WithClause AST node or undefined if no WITH clause is found.
161
- */
162
- private tryParseWithClause(): AST.WithClause | undefined {
163
- if (!this.check(TokenType.WITH)) {
164
- return undefined;
165
- }
166
- const startToken = this.advance(); // Consume WITH
167
-
168
- const recursive = this.match(TokenType.RECURSIVE);
169
-
170
- const ctes: AST.CommonTableExpr[] = [];
171
- do {
172
- ctes.push(this.commonTableExpression());
173
- } while (this.match(TokenType.COMMA));
174
-
175
- // Parse optional OPTION clause
176
- let options: AST.WithClauseOptions | undefined;
177
- if (this.matchKeyword('OPTION')) {
178
- this.consume(TokenType.LPAREN, "Expected '(' after OPTION.");
179
-
180
- // Parse MAXRECURSION option
181
- if (this.matchKeyword('MAXRECURSION')) {
182
- if (!this.check(TokenType.INTEGER)) {
183
- this.error(this.peek(), "Expected integer value after MAXRECURSION.");
184
- }
185
- const maxRecursionToken = this.advance();
186
- const maxRecursion = maxRecursionToken.literal as number;
187
-
188
- if (maxRecursion < 0) {
189
- this.error(maxRecursionToken, "MAXRECURSION value must be non-negative.");
190
- }
191
-
192
- options = { maxRecursion };
193
- } else {
194
- throw this.error(this.peek(), "Expected MAXRECURSION in OPTION clause.");
195
- }
196
-
197
- this.consume(TokenType.RPAREN, "Expected ')' after OPTION clause.");
198
- }
199
-
200
- const endToken = this.previous(); // Last token of the WITH clause
201
-
202
- return { type: 'with', recursive, ctes, options, loc: _createLoc(startToken, endToken) };
203
- }
204
-
205
- /**
206
- * Parses a single Common Table Expression (CTE).
207
- * cte_name [(col1, col2, ...)] AS (query)
208
- */
209
- private commonTableExpression(): AST.CommonTableExpr {
210
- const startToken = this.peek(); // Peek before consuming name
211
- const name = this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'like'], "Expected CTE name.");
212
- let endToken = this.previous(); // End token initially is the name
213
-
214
- let columns: string[] | undefined;
215
- if (this.match(TokenType.LPAREN)) {
216
- columns = [];
217
- if (!this.check(TokenType.RPAREN)) {
218
- do {
219
- columns.push(this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'like'], "Expected column name in CTE definition."));
220
- } while (this.match(TokenType.COMMA) && !this.check(TokenType.RPAREN));
221
- }
222
- endToken = this.consume(TokenType.RPAREN, "Expected ')' after CTE column list.");
223
- }
224
-
225
- this.consume(TokenType.AS, "Expected 'AS' after CTE name.");
226
-
227
- let materializationHint: AST.CommonTableExpr['materializationHint'];
228
- if (this.matchKeyword('MATERIALIZED')) {
229
- materializationHint = 'materialized';
230
- } else if (this.matchKeyword('NOT')) {
231
- this.consumeKeyword('MATERIALIZED', "Expected 'MATERIALIZED' after 'NOT'.");
232
- materializationHint = 'not_materialized';
233
- }
234
-
235
- this.consume(TokenType.LPAREN, "Expected '(' before CTE query.");
236
-
237
- // Parse the CTE query (can be SELECT, VALUES (via SELECT), INSERT, UPDATE, DELETE)
238
- const queryStartToken = this.peek();
239
- let query: AST.SelectStmt | AST.InsertStmt | AST.UpdateStmt | AST.DeleteStmt;
240
- if (this.check(TokenType.SELECT)) {
241
- this.advance(); // Consume SELECT token
242
- query = this.selectStatement(queryStartToken); // Pass start token
243
- } else if (this.check(TokenType.INSERT)) {
244
- this.advance(); // Consume INSERT token
245
- query = this.insertStatement(queryStartToken);
246
- } else if (this.check(TokenType.UPDATE)) {
247
- this.advance(); // Consume UPDATE token
248
- query = this.updateStatement(queryStartToken);
249
- } else if (this.check(TokenType.DELETE)) {
250
- this.advance(); // Consume DELETE token
251
- query = this.deleteStatement(queryStartToken);
252
- }
253
- // TODO: Add support for VALUES directly if needed (though VALUES is usually part of SELECT)
254
- else {
255
- throw this.error(this.peek(), "Expected SELECT, INSERT, UPDATE, or DELETE statement for CTE query.");
256
- }
257
-
258
- endToken = this.consume(TokenType.RPAREN, "Expected ')' after CTE query."); // Capture ')' as end token
259
-
260
- return { type: 'commonTableExpr', name, columns, query, materializationHint, loc: _createLoc(startToken, endToken) };
261
- }
262
-
263
- /**
264
- * Parse a single SQL statement
265
- */
266
- private statement(): AST.AstNode {
267
- // Check for WITH clause first
268
- let withClause: AST.WithClause | undefined;
269
- if (this.check(TokenType.WITH)) {
270
- withClause = this.tryParseWithClause();
271
- }
272
-
273
- const startToken = this.peek();
274
- // --- Check for specific keywords first ---
275
- const currentKeyword = startToken.lexeme.toUpperCase();
276
- let stmt: AST.AstNode;
277
-
278
- switch (currentKeyword) {
279
- case 'SELECT': this.advance(); stmt = this.selectStatement(startToken, withClause); break;
280
- case 'INSERT': this.advance(); stmt = this.insertStatement(startToken, withClause); break;
281
- case 'UPDATE': this.advance(); stmt = this.updateStatement(startToken, withClause); break;
282
- case 'DELETE': this.advance(); stmt = this.deleteStatement(startToken, withClause); break;
283
- case 'VALUES': this.advance(); stmt = this.valuesStatement(startToken); break;
284
- case 'CREATE': this.advance(); stmt = this.createStatement(startToken, withClause); break;
285
- case 'DROP': this.advance(); stmt = this.dropStatement(startToken, withClause); break;
286
- case 'ALTER': this.advance(); stmt = this.alterTableStatement(startToken, withClause); break;
287
- case 'BEGIN': this.advance(); stmt = this.beginStatement(startToken, withClause); break;
288
- case 'COMMIT': this.advance(); stmt = this.commitStatement(startToken, withClause); break;
289
- case 'ROLLBACK': this.advance(); stmt = this.rollbackStatement(startToken, withClause); break;
290
- case 'SAVEPOINT': this.advance(); stmt = this.savepointStatement(startToken, withClause); break;
291
- case 'RELEASE': this.advance(); stmt = this.releaseStatement(startToken, withClause); break;
292
- // TODO: Replace pragmas with build-in functions
293
- case 'PRAGMA': this.advance(); stmt = this.pragmaStatement(startToken, withClause); break;
294
- case 'DECLARE': this.advance(); stmt = this.declareSchemaStatement(startToken); break;
295
- case 'DIFF': this.advance(); stmt = this.diffSchemaStatement(startToken); break;
296
- case 'APPLY': this.advance(); stmt = this.applySchemaStatement(startToken); break;
297
- case 'EXPLAIN': this.advance(); stmt = this.explainSchemaStatement(startToken); break;
298
- // --- Add default case ---
299
- default:
300
- // If it wasn't a recognized keyword starting the statement
301
- throw this.error(startToken, `Expected statement type (SELECT, INSERT, UPDATE, DELETE, VALUES, CREATE, etc.), got '${startToken.lexeme}'.`);
302
- }
303
-
304
- // Attach WITH clause if present and supported
305
- if (withClause && this.statementSupportsWithClause(stmt)) {
306
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
307
- (stmt as any).withClause = withClause;
308
- if (withClause.loc && stmt.loc) {
309
- stmt.loc.start = withClause.loc.start;
310
- }
311
- } else if (withClause) {
312
- throw this.error(this.previous(), `WITH clause cannot be used with ${stmt.type} statement.`);
313
- }
314
-
315
- return stmt;
316
- }
317
-
318
- /**
319
- * Parse an INSERT statement
320
- * @returns AST for the INSERT statement
321
- */
322
- insertStatement(startToken: Token, withClause?: AST.WithClause): AST.InsertStmt {
323
- // INTO keyword is optional in SQLite
324
- this.matchKeyword('INTO'); // Handle missing keyword gracefully
325
-
326
- // Parse the table reference
327
- const table = this.tableIdentifier();
328
- const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
329
-
330
- // Parse column list if provided
331
- let columns: string[] | undefined;
332
- if (this.match(TokenType.LPAREN)) {
333
- columns = [];
334
- do {
335
- if (!this.checkIdentifierLike(contextualKeywords)) {
336
- throw this.error(this.peek(), "Expected column name.");
337
- }
338
- columns.push(this.getIdentifierValue(this.advance()));
339
- } while (this.match(TokenType.COMMA));
340
-
341
- this.consume(TokenType.RPAREN, "Expected ')' after column list.");
342
- }
343
-
344
- // Parse mutation context assignments if present (after column list, before VALUES/SELECT)
345
- let contextValues: AST.ContextAssignment[] | undefined;
346
- if (this.matchKeyword('WITH')) {
347
- if (this.matchKeyword('CONTEXT')) {
348
- contextValues = this.parseContextAssignments();
349
- } else {
350
- // Not a WITH CONTEXT clause, backtrack
351
- this.current--;
352
- }
353
- }
354
-
355
- // Parse VALUES clause
356
- let values: AST.Expression[][] | undefined;
357
- let select: AST.SelectStmt | undefined;
358
- let lastConsumedToken = this.previous(); // After columns or table id
359
-
360
- if (this.match(TokenType.VALUES)) {
361
- values = [];
362
- do {
363
- this.consume(TokenType.LPAREN, "Expected '(' before values.");
364
- const valueList: AST.Expression[] = [];
365
-
366
- if (!this.check(TokenType.RPAREN)) { // Check for empty value list
367
- do {
368
- valueList.push(this.expression());
369
- } while (this.match(TokenType.COMMA));
370
- }
371
-
372
- this.consume(TokenType.RPAREN, "Expected ')' after values.");
373
- values.push(valueList);
374
- lastConsumedToken = this.previous(); // Update after closing paren of value list
375
- } while (this.match(TokenType.COMMA));
376
- } else if (this.check(TokenType.SELECT)) { // If current token is SELECT
377
- // Handle INSERT ... SELECT
378
- // Consume the SELECT token, as selectStatement expects to start parsing after it.
379
- // selectKeywordToken will be the actual 'SELECT' token object, used for location.
380
- const selectKeywordToken = this.advance(); // Consume 'SELECT'
381
- // Pass the withClause so the embedded SELECT can (via the planner) resolve CTEs defined for the INSERT.
382
- select = this.selectStatement(selectKeywordToken, withClause);
383
- lastConsumedToken = this.previous(); // After SELECT statement is parsed
384
- } else {
385
- throw this.error(this.peek(), "Expected VALUES or SELECT after INSERT.");
386
- }
387
-
388
- // Parse RETURNING clause if present
389
- let returning: AST.ResultColumn[] | undefined;
390
- if (this.matchKeyword('RETURNING')) {
391
- returning = this.columnList();
392
- lastConsumedToken = this.previous(); // Update after RETURNING clause
393
- }
394
-
395
- return {
396
- type: 'insert',
397
- table,
398
- columns,
399
- values,
400
- select,
401
- returning,
402
- contextValues,
403
- loc: _createLoc(startToken, lastConsumedToken),
404
- };
405
- }
406
-
407
- /**
408
- * Parse a SELECT statement
409
- * @param startToken The 'SELECT' token or start token of a sub-query
410
- * @param withClause The WITH clause context for CTE access
411
- * @param isCompoundSubquery If true, don't parse ORDER BY/LIMIT as they belong to the outer compound
412
- * @returns AST for the SELECT statement
413
- */
414
- selectStatement(startToken?: Token, withClause?: AST.WithClause, isCompoundSubquery: boolean = false): AST.SelectStmt {
415
- const start = startToken ?? this.previous(); // Use provided or the keyword token
416
- let lastConsumedToken = start; // Initialize lastConsumed
417
-
418
- const distinct = this.matchKeyword('DISTINCT');
419
- if (distinct) lastConsumedToken = this.previous();
420
- const all = !distinct && this.matchKeyword('ALL');
421
- if (all) lastConsumedToken = this.previous();
422
-
423
- // Parse column list
424
- const columns = this.columnList();
425
- if (columns.length > 0) lastConsumedToken = this.previous(); // Update after last column element
426
-
427
- // Parse FROM clause if present
428
- let from: AST.FromClause[] | undefined;
429
- if (this.match(TokenType.FROM)) {
430
- from = this.tableSourceList(withClause);
431
- if (from.length > 0) lastConsumedToken = this.previous(); // After last source/join
432
- }
433
-
434
- // Parse WHERE clause if present
435
- let where: AST.Expression | undefined;
436
- if (this.match(TokenType.WHERE)) {
437
- where = this.expression();
438
- lastConsumedToken = this.previous(); // After where expression
439
- }
440
-
441
- // Parse GROUP BY clause if present
442
- let groupBy: AST.Expression[] | undefined;
443
- if (this.match(TokenType.GROUP) && this.consume(TokenType.BY, "Expected 'BY' after 'GROUP'.")) {
444
- groupBy = [];
445
- do {
446
- groupBy.push(this.expression());
447
- } while (this.match(TokenType.COMMA));
448
- lastConsumedToken = this.previous(); // After last group by expression
449
- }
450
-
451
- // Parse HAVING clause if present
452
- let having: AST.Expression | undefined;
453
- if (this.match(TokenType.HAVING)) {
454
- having = this.expression();
455
- lastConsumedToken = this.previous(); // After having expression
456
- }
457
-
458
- // Check for compound set operations (UNION / INTERSECT / EXCEPT) BEFORE ORDER BY/LIMIT
459
- let compound: { op: 'union' | 'unionAll' | 'intersect' | 'except' | 'diff'; select: AST.SelectStmt } | undefined;
460
- if (this.match(TokenType.UNION, TokenType.INTERSECT, TokenType.EXCEPT, TokenType.DIFF)) {
461
- const tok = this.previous();
462
- let op: 'union' | 'unionAll' | 'intersect' | 'except' | 'diff';
463
- if (tok.type === TokenType.UNION) {
464
- if (this.match(TokenType.ALL)) {
465
- op = 'unionAll';
466
- } else {
467
- op = 'union';
468
- }
469
- } else if (tok.type === TokenType.INTERSECT) {
470
- op = 'intersect';
471
- } else if (tok.type === TokenType.EXCEPT) {
472
- op = 'except';
473
- } else {
474
- op = 'diff';
475
- }
476
-
477
- let rightSelect: AST.SelectStmt;
478
-
479
- // Handle parenthesized subquery after set operation
480
- if (this.match(TokenType.LPAREN)) {
481
- const selectToken = this.consume(TokenType.SELECT, "Expected 'SELECT' in parenthesized set operation.");
482
- rightSelect = this.selectStatement(selectToken, withClause, true); // Pass true to indicate compound subquery
483
- this.consume(TokenType.RPAREN, "Expected ')' after parenthesized set operation.");
484
- } else {
485
- // Handle direct SELECT statement
486
- const selectStartToken = this.peek();
487
- if (this.match(TokenType.SELECT)) {
488
- rightSelect = this.selectStatement(selectStartToken, withClause, true); // Pass true to indicate compound subquery
489
- } else {
490
- throw this.error(this.peek(), "Expected 'SELECT' or '(' after set operation keyword.");
491
- }
492
- }
493
-
494
- lastConsumedToken = this.previous();
495
- compound = { op, select: rightSelect };
496
- }
497
-
498
- // Parse ORDER BY clause if present (applies to final result after compound operations)
499
- // Skip if this is a compound subquery as ORDER BY belongs to the outer compound
500
- let orderBy: AST.OrderByClause[] | undefined;
501
- if (!isCompoundSubquery && this.match(TokenType.ORDER) && this.consume(TokenType.BY, "Expected 'BY' after 'ORDER'.")) {
502
- orderBy = [];
503
- do {
504
- const expr = this.expression();
505
- const direction = this.match(TokenType.DESC) ? 'desc' :
506
- (this.match(TokenType.ASC) ? 'asc' : 'asc'); // Default to ASC
507
-
508
- // Handle NULLS FIRST/LAST
509
- let nulls: 'first' | 'last' | undefined;
510
- if (this.matchKeyword('NULLS')) {
511
- if (this.matchKeyword('FIRST')) {
512
- nulls = 'first';
513
- } else if (this.matchKeyword('LAST')) {
514
- nulls = 'last';
515
- } else {
516
- throw this.error(this.peek(), "Expected 'FIRST' or 'LAST' after 'NULLS'.");
517
- }
518
- }
519
-
520
- const orderClause: AST.OrderByClause = { expr, direction };
521
- if (nulls) {
522
- orderClause.nulls = nulls;
523
- }
524
- orderBy.push(orderClause);
525
- } while (this.match(TokenType.COMMA));
526
- lastConsumedToken = this.previous(); // After last order by clause
527
- }
528
-
529
- // Parse LIMIT clause if present (applies to final result after compound operations)
530
- // Skip if this is a compound subquery as LIMIT belongs to the outer compound
531
- let limit: AST.Expression | undefined;
532
- let offset: AST.Expression | undefined;
533
- if (!isCompoundSubquery && this.match(TokenType.LIMIT)) {
534
- limit = this.expression();
535
- lastConsumedToken = this.previous(); // After limit expression
536
-
537
- // LIMIT x OFFSET y syntax
538
- if (this.match(TokenType.OFFSET)) {
539
- offset = this.expression();
540
- lastConsumedToken = this.previous(); // After offset expression
541
- }
542
- // LIMIT x, y syntax (x is offset, y is limit)
543
- else if (this.match(TokenType.COMMA)) {
544
- offset = limit;
545
- limit = this.expression();
546
- lastConsumedToken = this.previous(); // After second limit expression
547
- }
548
- }
549
-
550
- return {
551
- type: 'select',
552
- columns,
553
- from,
554
- where,
555
- groupBy,
556
- having,
557
- orderBy,
558
- limit,
559
- offset,
560
- distinct,
561
- all,
562
- compound,
563
- loc: _createLoc(start, lastConsumedToken),
564
- };
565
- }
566
-
567
- /**
568
- * Parse a comma-separated list of result columns for SELECT
569
- */
570
- private columnList(): AST.ResultColumn[] {
571
- const columns: AST.ResultColumn[] = [];
572
- const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
573
-
574
- do {
575
- log(`columnList: Loop start. Current token: ${this.peek().lexeme} (${this.peek().type})`); // DEBUG
576
- // Handle wildcard: * or table.*
577
- if (this.match(TokenType.ASTERISK)) {
578
- columns.push({ type: 'all' });
579
- }
580
- // Handle table.* syntax
581
- else if (this.checkIdentifierLike(contextualKeywords) && this.checkNext(1, TokenType.DOT) &&
582
- this.checkNext(2, TokenType.ASTERISK)) {
583
- const table = this.consumeIdentifier(contextualKeywords, "Expected table name before '.*'.");
584
- this.advance(); // consume DOT
585
- this.advance(); // consume ASTERISK
586
- columns.push({ type: 'all', table });
587
- }
588
- // Handle regular column expression
589
- else {
590
- log(`columnList: Parsing expression...`); // DEBUG
591
- const expr = this.expression();
592
- log(`columnList: Parsed expression. Current token: ${this.peek().lexeme} (${this.peek().type})`); // DEBUG
593
- let alias: string | undefined;
594
-
595
- // Handle AS alias or just alias
596
- if (this.match(TokenType.AS)) {
597
- if (this.checkIdentifierLike(contextualKeywords) || this.check(TokenType.STRING)) {
598
- const aliasToken = this.advance();
599
- // For STRING tokens, use literal; for identifiers, use getIdentifierValue
600
- alias = aliasToken.type === TokenType.STRING
601
- ? aliasToken.literal
602
- : this.getIdentifierValue(aliasToken);
603
- } else {
604
- throw this.error(this.peek(), "Expected identifier or string after 'AS'.");
605
- }
606
- }
607
- // Implicit alias (no AS keyword)
608
- else if (this.checkIdentifierLike([]) &&
609
- !this.checkNext(1, TokenType.LPAREN) &&
610
- !this.checkNext(1, TokenType.DOT) &&
611
- !this.checkNext(1, TokenType.COMMA) &&
612
- !this.isEndOfClause()) {
613
- const aliasToken = this.advance();
614
- alias = this.getIdentifierValue(aliasToken);
615
- }
616
-
617
- columns.push({ type: 'column', expr, alias });
618
- }
619
- log(`columnList: Checking for comma. Current token: ${this.peek().lexeme} (${this.peek().type})`); // DEBUG
620
- } while (this.match(TokenType.COMMA));
621
-
622
- log(`columnList: Loop ended. Current token: ${this.peek().lexeme} (${this.peek().type})`); // DEBUG
623
- return columns;
624
- }
625
-
626
- /**
627
- * Parse a table identifier (possibly schema-qualified)
628
- */
629
- private tableIdentifier(): AST.IdentifierExpr {
630
- const startToken = this.peek();
631
- let schema: string | undefined;
632
- let name: string;
633
- let endToken = startToken;
634
- const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like', 'temp', 'temporary'];
635
-
636
- // Check for schema.table pattern
637
- if (this.checkIdentifierLike(contextualKeywords) && this.checkNext(1, TokenType.DOT)) {
638
- schema = this.consumeIdentifier(contextualKeywords, "Expected schema name.");
639
- this.advance(); // Consume DOT
640
- name = this.consumeIdentifier(contextualKeywords, "Expected table name after schema.");
641
- endToken = this.previous();
642
- } else if (this.checkIdentifierLike(contextualKeywords)) {
643
- name = this.consumeIdentifier(contextualKeywords, "Expected table name.");
644
- endToken = this.previous();
645
- } else {
646
- throw this.error(this.peek(), "Expected table name.");
647
- }
648
-
649
- return {
650
- type: 'identifier',
651
- name,
652
- schema,
653
- loc: _createLoc(startToken, endToken),
654
- };
655
- }
656
-
657
- /**
658
- * Parse a comma-separated list of table sources (FROM clause)
659
- */
660
- private tableSourceList(withClause?: AST.WithClause): AST.FromClause[] {
661
- const sources: AST.FromClause[] = [];
662
-
663
- do {
664
- // Get the base table source
665
- let source: AST.FromClause = this.tableSource(withClause);
666
-
667
- // Look for JOINs
668
- while (this.isJoinToken()) {
669
- source = this.joinClause(source, withClause);
670
- }
671
-
672
- sources.push(source);
673
- } while (this.match(TokenType.COMMA));
674
-
675
- return sources;
676
- }
677
-
678
- /**
679
- * Parse a single table source, which can now be a table name, table-valued function call, or subquery
680
- */
681
- private tableSource(withClause?: AST.WithClause): AST.FromClause {
682
- const startToken = this.peek();
683
- const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
684
-
685
- // Check for subquery: ( SELECT ... or ( VALUES ... or ( INSERT/UPDATE/DELETE ...
686
- if (this.check(TokenType.LPAREN)) {
687
- // Look ahead to see if this is a subquery
688
- const lookahead = this.current + 1;
689
- if (lookahead < this.tokens.length) {
690
- const nextTokenType = this.tokens[lookahead].type;
691
- if (nextTokenType === TokenType.SELECT || nextTokenType === TokenType.VALUES) {
692
- return this.subquerySource(startToken, withClause);
693
- } else if (nextTokenType === TokenType.INSERT || nextTokenType === TokenType.UPDATE || nextTokenType === TokenType.DELETE) {
694
- return this.mutatingSubquerySource(startToken, withClause);
695
- }
696
- }
697
- }
698
-
699
- // Check for function call syntax: IDENTIFIER (
700
- if (this.checkIdentifierLike(contextualKeywords) && this.checkNext(1, TokenType.LPAREN)) {
701
- return this.functionSource(startToken);
702
- }
703
- // Otherwise, assume it's a standard table source
704
- else {
705
- return this.standardTableSource(startToken);
706
- }
707
- }
708
-
709
- /** Parses a subquery source: (SELECT ...) AS alias */
710
- private subquerySource(startToken: Token, withClause?: AST.WithClause): AST.SubquerySource {
711
- this.consume(TokenType.LPAREN, "Expected '(' before subquery.");
712
-
713
- let subquery: AST.SelectStmt | AST.ValuesStmt;
714
-
715
- if (this.check(TokenType.SELECT)) {
716
- // Consume the SELECT token and pass it as startToken to selectStatement
717
- const selectToken = this.advance();
718
- subquery = this.selectStatement(selectToken, withClause);
719
- } else if (this.check(TokenType.VALUES)) {
720
- // Handle VALUES subquery
721
- const valuesToken = this.advance();
722
- subquery = this.valuesStatement(valuesToken);
723
- } else {
724
- throw this.error(this.peek(), "Expected 'SELECT' or 'VALUES' in subquery.");
725
- }
726
-
727
- this.consume(TokenType.RPAREN, "Expected ')' after subquery.");
728
-
729
- // Parse optional alias for subquery
730
- let alias: string;
731
- let columns: string[] | undefined;
732
-
733
- if (this.match(TokenType.AS)) {
734
- if (!this.checkIdentifierLike([])) {
735
- throw this.error(this.peek(), "Expected alias after 'AS'.");
736
- }
737
- alias = this.getIdentifierValue(this.advance());
738
- } else if (this.checkIdentifierLike([]) &&
739
- !this.checkNext(1, TokenType.DOT) &&
740
- !this.checkNext(1, TokenType.COMMA) &&
741
- !this.isJoinToken() &&
742
- !this.isEndOfClause()) {
743
- alias = this.getIdentifierValue(this.advance());
744
- } else {
745
- // Generate a default alias if none provided
746
- alias = `subquery_${startToken.startOffset}`;
747
- }
748
-
749
- // Parse optional column list after alias: AS alias(col1, col2, ...)
750
- if (this.match(TokenType.LPAREN)) {
751
- columns = [];
752
- const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
753
-
754
- if (!this.check(TokenType.RPAREN)) {
755
- do {
756
- columns.push(this.consumeIdentifier(contextualKeywords, "Expected column name in alias column list."));
757
- } while (this.match(TokenType.COMMA) && !this.check(TokenType.RPAREN));
758
- }
759
- this.consume(TokenType.RPAREN, "Expected ')' after alias column list.");
760
- }
761
-
762
- const endToken = this.previous();
763
- return {
764
- type: 'subquerySource',
765
- subquery,
766
- alias,
767
- columns,
768
- loc: _createLoc(startToken, endToken),
769
- };
770
- }
771
-
772
- /** Parses a mutating subquery source: (INSERT/UPDATE/DELETE ... RETURNING ...) AS alias */
773
- private mutatingSubquerySource(startToken: Token, withClause?: AST.WithClause): AST.MutatingSubquerySource {
774
- this.consume(TokenType.LPAREN, "Expected '(' before mutating subquery.");
775
-
776
- let stmt: AST.InsertStmt | AST.UpdateStmt | AST.DeleteStmt;
777
-
778
- if (this.check(TokenType.INSERT)) {
779
- const insertToken = this.advance();
780
- stmt = this.insertStatement(insertToken, withClause);
781
- } else if (this.check(TokenType.UPDATE)) {
782
- const updateToken = this.advance();
783
- stmt = this.updateStatement(updateToken, withClause);
784
- } else if (this.check(TokenType.DELETE)) {
785
- const deleteToken = this.advance();
786
- stmt = this.deleteStatement(deleteToken, withClause);
787
- } else {
788
- throw this.error(this.peek(), "Expected 'INSERT', 'UPDATE', or 'DELETE' in mutating subquery.");
789
- }
790
-
791
- // Validate that the statement has a RETURNING clause
792
- if (!stmt.returning || stmt.returning.length === 0) {
793
- throw this.error(this.previous(), "Mutating subqueries must have a RETURNING clause to be used as table sources.");
794
- }
795
-
796
- this.consume(TokenType.RPAREN, "Expected ')' after mutating subquery.");
797
-
798
- // Parse optional alias for mutating subquery
799
- let alias: string;
800
- let columns: string[] | undefined;
801
-
802
- if (this.match(TokenType.AS)) {
803
- if (!this.checkIdentifierLike([])) {
804
- throw this.error(this.peek(), "Expected alias after 'AS'.");
805
- }
806
- alias = this.getIdentifierValue(this.advance());
807
- } else if (this.checkIdentifierLike([]) &&
808
- !this.checkNext(1, TokenType.DOT) &&
809
- !this.checkNext(1, TokenType.COMMA) &&
810
- !this.isJoinToken() &&
811
- !this.isEndOfClause()) {
812
- alias = this.getIdentifierValue(this.advance());
813
- } else {
814
- // Generate a default alias if none provided
815
- alias = `mutating_subquery_${startToken.startOffset}`;
816
- }
817
-
818
- // Parse optional column list after alias: AS alias(col1, col2, ...)
819
- if (this.match(TokenType.LPAREN)) {
820
- columns = [];
821
- const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
822
-
823
- if (!this.check(TokenType.RPAREN)) {
824
- do {
825
- columns.push(this.consumeIdentifier(contextualKeywords, "Expected column name in alias column list."));
826
- } while (this.match(TokenType.COMMA) && !this.check(TokenType.RPAREN));
827
- }
828
- this.consume(TokenType.RPAREN, "Expected ')' after alias column list.");
829
- }
830
-
831
- const endToken = this.previous();
832
- return {
833
- type: 'mutatingSubquerySource',
834
- stmt,
835
- alias,
836
- columns,
837
- loc: _createLoc(startToken, endToken),
838
- };
839
- }
840
-
841
- /** Parses a standard table source (schema.table or table) */
842
- private standardTableSource(startToken: Token): AST.TableSource {
843
- const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
844
-
845
- // Parse table name (potentially schema-qualified)
846
- const table = this.tableIdentifier();
847
- let endToken = this.previous(); // Initialize endToken after parsing table identifier
848
-
849
- // Parse optional alias
850
- let alias: string | undefined;
851
- if (this.match(TokenType.AS)) {
852
- if (!this.checkIdentifierLike(contextualKeywords)) {
853
- throw this.error(this.peek(), "Expected alias after 'AS'.");
854
- }
855
- const aliasToken = this.advance();
856
- alias = this.getIdentifierValue(aliasToken);
857
- endToken = aliasToken;
858
- } else if (this.checkIdentifierLike([]) &&
859
- !this.checkNext(1, TokenType.DOT) &&
860
- !this.checkNext(1, TokenType.COMMA) &&
861
- !this.isJoinToken() &&
862
- !this.isEndOfClause()) {
863
- const aliasToken = this.advance();
864
- alias = this.getIdentifierValue(aliasToken);
865
- endToken = aliasToken;
866
- }
867
-
868
- return {
869
- type: 'table',
870
- table,
871
- alias,
872
- loc: _createLoc(startToken, endToken),
873
- };
874
- }
875
-
876
- /** Parses a table-valued function source: name(arg1, ...) [AS alias] */
877
- private functionSource(startToken: Token): AST.FunctionSource {
878
- const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
879
-
880
- const name = this.tableIdentifier(); // name has its own loc
881
- let endToken = this.previous(); // Initialize endToken after parsing function identifier
882
-
883
- this.consume(TokenType.LPAREN, "Expected '(' after table function name.");
884
-
885
- const args: AST.Expression[] = [];
886
- if (!this.check(TokenType.RPAREN)) {
887
- // Handle DISTINCT inside function calls like COUNT(DISTINCT col)
888
- const distinct = this.matchKeyword('DISTINCT');
889
- // Handle * argument AFTER checking for distinct
890
- if (this.match(TokenType.ASTERISK)) {
891
- // Do not add '*' as an argument to the list for aggregates like COUNT(*)
892
- if (args.length > 0 || distinct) {
893
- // '*' is only valid as the *only* argument, potentially after DISTINCT
894
- // e.g. COUNT(*), COUNT(DISTINCT *) - though DISTINCT * might not be standard SQL?
895
- // For now, disallow '*' if other args exist.
896
- throw this.error(this.previous(), "'*' cannot be used with other arguments in function call.");
897
- }
898
- // If we parsed '*', the args list remains empty.
899
- } else {
900
- // Parse regular arguments if '*' wasn't found
901
- do {
902
- args.push(this.expression());
903
- } while (this.match(TokenType.COMMA));
904
- }
905
- }
906
-
907
- endToken = this.consume(TokenType.RPAREN, "Expected ')' after table function arguments.");
908
-
909
- // Parse optional alias (same logic as for standard tables)
910
- let alias: string | undefined;
911
- let columns: string[] | undefined;
912
- if (this.match(TokenType.AS)) {
913
- if (!this.checkIdentifierLike(contextualKeywords)) {
914
- throw this.error(this.peek(), "Expected alias after 'AS'.");
915
- }
916
- const aliasToken = this.advance();
917
- alias = this.getIdentifierValue(aliasToken);
918
- endToken = aliasToken;
919
- } else if (this.checkIdentifierLike([]) &&
920
- !this.checkNext(1, TokenType.DOT) &&
921
- !this.checkNext(1, TokenType.COMMA) &&
922
- !this.isJoinToken() &&
923
- !this.isEndOfClause()) {
924
- const aliasToken = this.advance();
925
- alias = this.getIdentifierValue(aliasToken);
926
- endToken = aliasToken;
927
- }
928
-
929
- // Optional column list after alias: alias(col1, col2, ...)
930
- if (alias && this.match(TokenType.LPAREN)) {
931
- columns = [];
932
- const colKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
933
- if (!this.check(TokenType.RPAREN)) {
934
- do {
935
- columns.push(this.consumeIdentifier(colKeywords, "Expected column name in alias column list."));
936
- } while (this.match(TokenType.COMMA) && !this.check(TokenType.RPAREN));
937
- }
938
- endToken = this.consume(TokenType.RPAREN, "Expected ')' after alias column list.");
939
- }
940
-
941
- return {
942
- type: 'functionSource',
943
- name,
944
- args,
945
- alias,
946
- columns,
947
- loc: _createLoc(startToken, endToken),
948
- };
949
- }
950
-
951
- /**
952
- * Parse a JOIN clause
953
- */
954
- private joinClause(left: AST.FromClause, withClause?: AST.WithClause): AST.JoinClause {
955
- const joinStartToken = this.peek(); // Capture token before parsing JOIN type
956
-
957
- // Determine join type
958
- let joinType: 'inner' | 'left' | 'right' | 'full' | 'cross' = 'inner';
959
-
960
- if (this.match(TokenType.LEFT)) {
961
- this.match(TokenType.OUTER); // optional
962
- joinType = 'left';
963
- } else if (this.match(TokenType.RIGHT)) {
964
- this.match(TokenType.OUTER); // optional
965
- joinType = 'right';
966
- } else if (this.match(TokenType.FULL)) {
967
- this.match(TokenType.OUTER); // optional
968
- joinType = 'full';
969
- } else if (this.match(TokenType.CROSS)) {
970
- joinType = 'cross';
971
- } else if (this.match(TokenType.INNER)) {
972
- joinType = 'inner';
973
- }
974
-
975
- // Consume JOIN token
976
- this.consume(TokenType.JOIN, "Expected 'JOIN'.");
977
-
978
- // Optional LATERAL before right side
979
- const _isLateral = this.match(TokenType.LATERAL);
980
- // Parse right side of join
981
- const right = this.tableSource(withClause);
982
-
983
- // Parse join condition
984
- let condition: AST.Expression | undefined;
985
- let columns: string[] | undefined;
986
- let endToken = this.previous(); // End token is end of right source initially
987
-
988
- if (this.match(TokenType.ON)) {
989
- condition = this.expression();
990
- endToken = this.previous(); // End token is end of ON expression
991
- } else if (this.match(TokenType.USING)) {
992
- this.consume(TokenType.LPAREN, "Expected '(' after 'USING'.");
993
- columns = [];
994
- const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
995
-
996
- do {
997
- columns.push(this.consumeIdentifier(contextualKeywords, "Expected column name."));
998
- } while (this.match(TokenType.COMMA));
999
-
1000
- endToken = this.consume(TokenType.RPAREN, "Expected ')' after columns.");
1001
- } else if (joinType !== 'cross') {
1002
- throw this.error(this.peek(), "Expected 'ON' or 'USING' after JOIN.");
1003
- }
1004
-
1005
- return {
1006
- type: 'join',
1007
- joinType,
1008
- left,
1009
- right,
1010
- condition,
1011
- columns,
1012
- loc: _createLoc(joinStartToken, endToken),
1013
- };
1014
- }
1015
-
1016
- /**
1017
- * Parse an expression
1018
- */
1019
- private expression(): AST.Expression {
1020
- return this.logicalXorOr();
1021
- }
1022
-
1023
- /**
1024
- * Parse logical OR and XOR expressions (lowest precedence)
1025
- */
1026
- private logicalXorOr(): AST.Expression {
1027
- let expr = this.logicalAnd();
1028
- const startToken = expr.loc ? this.tokens.find(t => t.startOffset === expr.loc!.start.offset) ?? this.peek() : this.peek(); // Get start token of left expr
1029
-
1030
- while (this.match(TokenType.OR, TokenType.XOR)) { // Added XOR
1031
- const operator = this.previous().type === TokenType.XOR ? 'XOR' : 'OR'; // Determine operator
1032
- const right = this.logicalAnd();
1033
- const endToken = this.previous(); // End token is end of right expr
1034
- expr = {
1035
- type: 'binary',
1036
- operator,
1037
- left: expr,
1038
- right,
1039
- loc: _createLoc(startToken, endToken),
1040
- };
1041
- }
1042
-
1043
- return expr;
1044
- }
1045
-
1046
- /**
1047
- * Parse logical AND expression
1048
- */
1049
- private logicalAnd(): AST.Expression {
1050
- let expr = this.isNull();
1051
- const startToken = expr.loc ? this.tokens.find(t => t.startOffset === expr.loc!.start.offset) ?? this.peek() : this.peek(); // Get start token of left expr
1052
-
1053
- while (this.match(TokenType.AND)) {
1054
- const operator = 'AND';
1055
- const right = this.isNull();
1056
- const endToken = this.previous(); // End token is end of right expr
1057
- expr = {
1058
- type: 'binary',
1059
- operator,
1060
- left: expr,
1061
- right,
1062
- loc: _createLoc(startToken, endToken),
1063
- };
1064
- }
1065
-
1066
- return expr;
1067
- }
1068
-
1069
- /**
1070
- * Parse IS NULL / IS NOT NULL expressions
1071
- */
1072
- private isNull(): AST.Expression {
1073
- const expr = this.equality();
1074
- const startToken = expr.loc ? this.tokens.find(t => t.startOffset === expr.loc!.start.offset) ?? this.peek() : this.peek(); // Get start token of left expr
1075
-
1076
- if (this.match(TokenType.IS)) {
1077
- let isNot = false;
1078
- if (this.match(TokenType.NOT)) {
1079
- isNot = true;
1080
- }
1081
- if (this.match(TokenType.NULL)) {
1082
- const endToken = this.previous(); // End token is NULL
1083
- const operator = isNot ? 'IS NOT NULL' : 'IS NULL';
1084
- // Represent IS NULL / IS NOT NULL as UnaryExpr for simplicity
1085
- return { type: 'unary', operator, expr, loc: _createLoc(startToken, endToken) };
1086
- }
1087
- // If it was IS or IS NOT but not followed by NULL, maybe it's IS TRUE/FALSE/DISTINCT FROM?
1088
- // For now, assume standard comparison if NULL doesn't follow IS [NOT]
1089
- // We need to "unread" the IS and optional NOT token if we didn't match NULL.
1090
- // Backtrack current position.
1091
- if (isNot) this.current--; // Backtrack NOT
1092
- this.current--; // Backtrack IS
1093
- }
1094
-
1095
- return expr;
1096
- }
1097
-
1098
- /**
1099
- * Parse equality expression
1100
- */
1101
- private equality(): AST.Expression {
1102
- let expr = this.comparison();
1103
- const startToken = expr.loc ? this.tokens.find(t => t.startOffset === expr.loc!.start.offset) ?? this.peek() : this.peek(); // Get start token of left expr
1104
-
1105
- while (this.match(TokenType.EQUAL, TokenType.EQUAL_EQUAL, TokenType.NOT_EQUAL)) {
1106
- let operator: string;
1107
- switch (this.previous().type) {
1108
- case TokenType.NOT_EQUAL: operator = '!='; break;
1109
- case TokenType.EQUAL_EQUAL: operator = '=='; break;
1110
- default: operator = '='; break;
1111
- }
1112
- const right = this.comparison();
1113
- const endToken = this.previous(); // End token is end of right expr
1114
- expr = {
1115
- type: 'binary',
1116
- operator,
1117
- left: expr,
1118
- right,
1119
- loc: _createLoc(startToken, endToken),
1120
- };
1121
- }
1122
-
1123
- return expr;
1124
- }
1125
-
1126
- /**
1127
- * Parse comparison expression
1128
- */
1129
- private comparison(): AST.Expression {
1130
- let expr = this.term();
1131
- const startToken = expr.loc ? this.tokens.find(t => t.startOffset === expr.loc!.start.offset) ?? this.peek() : this.peek(); // Get start token of left expr
1132
-
1133
- while (this.match(
1134
- TokenType.LESS, TokenType.LESS_EQUAL,
1135
- TokenType.GREATER, TokenType.GREATER_EQUAL,
1136
- TokenType.BETWEEN, TokenType.IN, TokenType.NOT,
1137
- TokenType.LIKE
1138
- )) {
1139
- const operatorToken = this.previous();
1140
-
1141
- // Handle NOT IN, NOT BETWEEN, and NOT LIKE
1142
- if (operatorToken.type === TokenType.NOT) {
1143
- const notStartToken = operatorToken;
1144
- if (this.match(TokenType.IN)) {
1145
- // NOT IN
1146
- this.consume(TokenType.LPAREN, "Expected '(' after NOT IN.");
1147
-
1148
- if (this.check(TokenType.SELECT)) {
1149
- // NOT IN subquery: expr NOT IN (SELECT ...)
1150
- const selectToken = this.advance(); // Consume SELECT
1151
- const subquery = this.selectStatement(selectToken);
1152
- const endToken = this.consume(TokenType.RPAREN, "Expected ')' after NOT IN subquery.");
1153
-
1154
- // Create an IN expression with subquery, then wrap in NOT
1155
- const inExpr: AST.InExpr = {
1156
- type: 'in',
1157
- expr,
1158
- subquery,
1159
- loc: _createLoc(startToken, endToken),
1160
- };
1161
-
1162
- expr = {
1163
- type: 'unary',
1164
- operator: 'NOT',
1165
- expr: inExpr,
1166
- loc: _createLoc(notStartToken, endToken),
1167
- };
1168
- } else {
1169
- // NOT IN value list: expr NOT IN (value1, value2, ...)
1170
- const values: AST.Expression[] = [];
1171
- if (!this.check(TokenType.RPAREN)) {
1172
- do {
1173
- values.push(this.expression());
1174
- } while (this.match(TokenType.COMMA));
1175
- }
1176
- const endToken = this.consume(TokenType.RPAREN, "Expected ')' after NOT IN values.");
1177
-
1178
- // Create an IN expression with value list, then wrap in NOT
1179
- const inExpr: AST.InExpr = {
1180
- type: 'in',
1181
- expr,
1182
- values,
1183
- loc: _createLoc(startToken, endToken),
1184
- };
1185
-
1186
- expr = {
1187
- type: 'unary',
1188
- operator: 'NOT',
1189
- expr: inExpr,
1190
- loc: _createLoc(notStartToken, endToken),
1191
- };
1192
- }
1193
- } else if (this.match(TokenType.BETWEEN)) {
1194
- // NOT BETWEEN
1195
- const lower = this.term();
1196
- this.consume(TokenType.AND, "Expected 'AND' after NOT BETWEEN lower bound.");
1197
- const upper = this.term();
1198
- const endToken = this.previous(); // End token is end of upper expr
1199
-
1200
- // Create the NOT BETWEEN expression as a dedicated node type
1201
- expr = {
1202
- type: 'between',
1203
- expr,
1204
- lower,
1205
- upper,
1206
- not: true,
1207
- loc: _createLoc(notStartToken, endToken),
1208
- };
1209
- } else if (this.match(TokenType.LIKE)) {
1210
- // NOT LIKE
1211
- const pattern = this.term();
1212
- const endToken = this.previous(); // End token is end of pattern expr
1213
-
1214
- // Create the LIKE expression as a binary expression, then wrap in NOT
1215
- const likeExpr: AST.BinaryExpr = {
1216
- type: 'binary',
1217
- operator: 'LIKE',
1218
- left: expr,
1219
- right: pattern,
1220
- loc: _createLoc(startToken, endToken),
1221
- };
1222
-
1223
- expr = {
1224
- type: 'unary',
1225
- operator: 'NOT',
1226
- expr: likeExpr,
1227
- loc: _createLoc(notStartToken, endToken),
1228
- };
1229
- } else {
1230
- // Put back the NOT token and break out of the loop
1231
- this.current--;
1232
- break;
1233
- }
1234
- } else if (operatorToken.type === TokenType.LIKE) {
1235
- // Parse LIKE expression: expr LIKE pattern
1236
- const pattern = this.term();
1237
- const endToken = this.previous(); // End token is end of pattern expr
1238
-
1239
- // Create the LIKE expression as a binary expression
1240
- expr = {
1241
- type: 'binary',
1242
- operator: 'LIKE',
1243
- left: expr,
1244
- right: pattern,
1245
- loc: _createLoc(startToken, endToken),
1246
- };
1247
- } else if (operatorToken.type === TokenType.BETWEEN) {
1248
- // Parse BETWEEN expression: expr BETWEEN low AND high
1249
- const lower = this.term();
1250
- this.consume(TokenType.AND, "Expected 'AND' after BETWEEN lower bound.");
1251
- const upper = this.term();
1252
- const endToken = this.previous(); // End token is end of upper expr
1253
-
1254
- // Create the BETWEEN expression as a dedicated node type
1255
- expr = {
1256
- type: 'between',
1257
- expr,
1258
- lower,
1259
- upper,
1260
- loc: _createLoc(startToken, endToken),
1261
- };
1262
- } else if (operatorToken.type === TokenType.IN) {
1263
- // Parse IN expression: expr IN (value1, value2, ...) or expr IN (subquery)
1264
- this.consume(TokenType.LPAREN, "Expected '(' after IN.");
1265
-
1266
- // Check if this is a subquery or value list
1267
- if (this.check(TokenType.SELECT)) {
1268
- // IN subquery: expr IN (SELECT ...)
1269
- const selectToken = this.advance(); // Consume SELECT
1270
- const subquery = this.selectStatement(selectToken);
1271
- const endToken = this.consume(TokenType.RPAREN, "Expected ')' after IN subquery.");
1272
-
1273
- // Create an IN expression with subquery
1274
- expr = {
1275
- type: 'in',
1276
- expr,
1277
- subquery,
1278
- loc: _createLoc(startToken, endToken),
1279
- };
1280
- } else {
1281
- // IN value list: expr IN (value1, value2, ...)
1282
- const values: AST.Expression[] = [];
1283
- if (!this.check(TokenType.RPAREN)) {
1284
- do {
1285
- values.push(this.expression());
1286
- } while (this.match(TokenType.COMMA));
1287
- }
1288
- const endToken = this.consume(TokenType.RPAREN, "Expected ')' after IN values.");
1289
-
1290
- // Create an IN expression with value list
1291
- expr = {
1292
- type: 'in',
1293
- expr,
1294
- values,
1295
- loc: _createLoc(startToken, endToken),
1296
- };
1297
- }
1298
- } else {
1299
- // Handle other comparison operators
1300
- let operator: string;
1301
- switch (operatorToken.type) {
1302
- case TokenType.LESS: operator = '<'; break;
1303
- case TokenType.LESS_EQUAL: operator = '<='; break;
1304
- case TokenType.GREATER: operator = '>'; break;
1305
- case TokenType.GREATER_EQUAL: operator = '>='; break;
1306
- default: operator = '?';
1307
- }
1308
-
1309
- const right = this.term();
1310
- const endToken = this.previous(); // End token is end of right expr
1311
- expr = {
1312
- type: 'binary',
1313
- operator,
1314
- left: expr,
1315
- right,
1316
- loc: _createLoc(startToken, endToken),
1317
- };
1318
- }
1319
- }
1320
-
1321
- return expr;
1322
- }
1323
-
1324
- /**
1325
- * Parse addition and subtraction
1326
- */
1327
- private term(): AST.Expression {
1328
- let expr = this.factor();
1329
- const startToken = expr.loc ? this.tokens.find(t => t.startOffset === expr.loc!.start.offset) ?? this.peek() : this.peek(); // Get start token of left expr
1330
-
1331
- while (this.match(TokenType.PLUS, TokenType.MINUS)) {
1332
- const operator = this.previous().type === TokenType.PLUS ? '+' : '-';
1333
- const right = this.factor();
1334
- const endToken = this.previous(); // End token is end of right expr
1335
- expr = {
1336
- type: 'binary',
1337
- operator,
1338
- left: expr,
1339
- right,
1340
- loc: _createLoc(startToken, endToken),
1341
- };
1342
- }
1343
-
1344
- return expr;
1345
- }
1346
-
1347
- /**
1348
- * Parse multiplication and division
1349
- */
1350
- private factor(): AST.Expression {
1351
- // First, handle unary operators
1352
- if (this.match(TokenType.MINUS, TokenType.PLUS, TokenType.TILDE, TokenType.NOT)) { // Added NOT
1353
- const operatorToken = this.previous();
1354
- const operatorStartToken = operatorToken; // Start token is the operator itself
1355
- const operator = operatorToken.lexeme;
1356
- // Unary operator applies to the result of the *next* precedence level (concatenation)
1357
- const right = this.concatenation(); // Should call concatenation (higher precedence than factor)
1358
- const endToken = this.previous(); // End token is end of the operand
1359
- return { type: 'unary', operator, expr: right, loc: _createLoc(operatorStartToken, endToken) };
1360
- }
1361
-
1362
- let expr = this.concatenation(); // Factor operands have higher precedence (concatenation)
1363
- const startToken = expr.loc ? this.tokens.find(t => t.startOffset === expr.loc!.start.offset) ?? this.peek() : this.peek(); // Get start token of left expr
1364
-
1365
- while (this.match(TokenType.ASTERISK, TokenType.SLASH, TokenType.PERCENT)) {
1366
- const operatorToken = this.previous();
1367
- const operator = operatorToken.lexeme;
1368
- const right = this.concatenation(); // Factor operands have higher precedence (concatenation)
1369
- const endToken = this.previous(); // End token is end of right expr
1370
- expr = { type: 'binary', operator, left: expr, right, loc: _createLoc(startToken, endToken) };
1371
- }
1372
-
1373
- return expr;
1374
- }
1375
-
1376
- /**
1377
- * Parse concatenation expression (||)
1378
- */
1379
- private concatenation(): AST.Expression {
1380
- let expr = this.collateExpression(); // Concatenation operands have higher precedence (collate)
1381
- const startToken = expr.loc ? this.tokens.find(t => t.startOffset === expr.loc!.start.offset) ?? this.peek() : this.peek(); // Get start token of left expr
1382
-
1383
- while (this.match(TokenType.PIPE_PIPE)) {
1384
- const operator = '||';
1385
- const right = this.collateExpression(); // Concatenation operands have higher precedence (collate)
1386
- const endToken = this.previous(); // End token is end of right expr
1387
- expr = {
1388
- type: 'binary',
1389
- operator,
1390
- left: expr,
1391
- right,
1392
- loc: _createLoc(startToken, endToken),
1393
- };
1394
- }
1395
-
1396
- return expr;
1397
- }
1398
-
1399
- /**
1400
- * Parse COLLATE expression
1401
- */
1402
- private collateExpression(): AST.Expression {
1403
- const expr = this.primary(); // Parse primary expression first
1404
-
1405
- if (this.matchKeyword('COLLATE')) {
1406
- const collationToken = this.consume(TokenType.IDENTIFIER, "Expected collation name after COLLATE.");
1407
- const collation = collationToken.lexeme;
1408
- // Use the start of the original expression and end of collation name for location
1409
- const startLocToken = expr.loc?.start ? this.tokens.find(t => t.startOffset === expr.loc!.start.offset) ?? this.peek() : this.peek();
1410
- return { type: 'collate', expr, collation, loc: _createLoc(startLocToken, collationToken) };
1411
- }
1412
-
1413
- return expr;
1414
- }
1415
-
1416
- /**
1417
- * Parse primary expressions (literals, identifiers, etc.)
1418
- */
1419
- private primary(): AST.Expression {
1420
- const startToken = this.peek();
1421
-
1422
- // Case expression
1423
- if (this.matchKeyword('CASE')) {
1424
- return this.parseCaseExpression(startToken);
1425
- }
1426
-
1427
- // CAST expression: CAST(expr AS type)
1428
- if (this.peekKeyword('CAST') && this.checkNext(1, TokenType.LPAREN)) {
1429
- const castToken = this.advance(); // Consume CAST
1430
- this.consume(TokenType.LPAREN, "Expected '(' after CAST.");
1431
- const expr = this.expression();
1432
- this.consumeKeyword('AS', "Expected 'AS' in CAST expression.");
1433
- // Allow type names that might be keywords (e.g., TEXT, INTEGER, REAL, BLOB)
1434
- // or multi-word type names if supported (e.g., "VARCHAR(255)") - for now, simple identifier
1435
- if (!this.check(TokenType.IDENTIFIER) &&
1436
- !this.isTypeNameKeyword(this.peek().lexeme.toUpperCase())) {
1437
- throw this.error(this.peek(), "Expected type name after 'AS' in CAST expression.");
1438
- }
1439
- const typeToken = this.advance(); // Consume type name
1440
- const targetType = typeToken.lexeme;
1441
- const endToken = this.consume(TokenType.RPAREN, "Expected ')' after CAST expression type.");
1442
- return { type: 'cast', expr, targetType, loc: _createLoc(castToken, endToken) };
1443
- }
1444
-
1445
- // EXISTS expression: EXISTS(SELECT ...)
1446
- if (this.match(TokenType.EXISTS)) {
1447
- const existsToken = this.previous();
1448
- this.consume(TokenType.LPAREN, "Expected '(' after EXISTS.");
1449
- const selectToken = this.consume(TokenType.SELECT, "Expected 'SELECT' in EXISTS subquery.");
1450
- const subquery = this.selectStatement(selectToken);
1451
- const endToken = this.consume(TokenType.RPAREN, "Expected ')' after EXISTS subquery.");
1452
- return {
1453
- type: 'exists',
1454
- subquery,
1455
- loc: _createLoc(existsToken, endToken)
1456
- };
1457
- }
1458
-
1459
- // Literals
1460
- if (this.match(TokenType.INTEGER, TokenType.FLOAT, TokenType.STRING, TokenType.NULL, TokenType.TRUE, TokenType.FALSE, TokenType.BLOB)) {
1461
- const token = this.previous();
1462
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1463
- let value: any;
1464
- let lexeme: string | undefined = undefined;
1465
-
1466
- if (token.type === TokenType.NULL) {
1467
- value = null;
1468
- lexeme = token.lexeme; // Store original case (NULL vs null)
1469
- } else if (token.type === TokenType.TRUE) {
1470
- value = true;
1471
- lexeme = token.lexeme; // Store original case (TRUE vs true)
1472
- } else if (token.type === TokenType.FALSE) {
1473
- value = false;
1474
- lexeme = token.lexeme; // Store original case (FALSE vs false)
1475
- } else if (token.type === TokenType.FLOAT) {
1476
- // For FLOAT, parse the literal (which is the original string)
1477
- value = parseFloat(token.literal as string);
1478
- lexeme = token.literal as string; // Store original string as lexeme
1479
- } else if (token.type === TokenType.INTEGER) {
1480
- value = token.literal; // Already number or BigInt
1481
- if (token.lexeme !== String(value)) { // Store lexeme only if different
1482
- lexeme = token.lexeme;
1483
- }
1484
- } else {
1485
- value = token.literal; // STRING, BLOB
1486
- }
1487
-
1488
- const node: AST.LiteralExpr = { type: 'literal', value, loc: _createLoc(startToken, token) };
1489
- if (lexeme !== undefined) {
1490
- node.lexeme = lexeme;
1491
- }
1492
- return node;
1493
- }
1494
-
1495
- // Parameter expressions (?, :name, $name)
1496
- if (this.match(TokenType.QUESTION)) {
1497
- const token = this.previous();
1498
- return { type: 'parameter', index: this.parameterPosition++, loc: _createLoc(startToken, token) };
1499
- }
1500
-
1501
- if (this.match(TokenType.COLON, TokenType.DOLLAR)) {
1502
- // Named parameter (can be identifier like :name or integer like :1)
1503
- if (!this.check(TokenType.IDENTIFIER) && !this.check(TokenType.INTEGER)) {
1504
- throw this.error(this.peek(), "Expected identifier or number after parameter prefix.");
1505
- }
1506
- const nameToken = this.advance();
1507
- return { type: 'parameter', name: nameToken.lexeme, loc: _createLoc(startToken, nameToken) };
1508
- }
1509
-
1510
- // Function call (with optional window function support)
1511
- if (this.checkIdentifierLike(['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like', 'replace']) && this.checkNext(1, TokenType.LPAREN)) {
1512
- const name = this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like', 'replace'], "Expected function name.");
1513
-
1514
- this.consume(TokenType.LPAREN, "Expected '(' after function name.");
1515
-
1516
- const args: AST.Expression[] = [];
1517
- let distinct = false;
1518
- if (!this.check(TokenType.RPAREN)) {
1519
- // Handle DISTINCT inside function calls like COUNT(DISTINCT col)
1520
- distinct = this.matchKeyword('DISTINCT');
1521
- // Handle * argument AFTER checking for distinct
1522
- if (this.match(TokenType.ASTERISK)) {
1523
- // Do not add '*' as an argument to the list for aggregates like COUNT(*)
1524
- if (args.length > 0 || distinct) {
1525
- // '*' is only valid as the *only* argument, potentially after DISTINCT
1526
- // e.g. COUNT(*), COUNT(DISTINCT *) - though DISTINCT * might not be standard SQL?
1527
- // For now, disallow '*' if other args exist.
1528
- throw this.error(this.previous(), "'*' cannot be used with other arguments in function call.");
1529
- }
1530
- // If we parsed '*', the args list remains empty.
1531
- } else {
1532
- // Parse regular arguments if '*' wasn't found
1533
- do {
1534
- args.push(this.expression());
1535
- } while (this.match(TokenType.COMMA));
1536
- }
1537
- }
1538
-
1539
- const endToken = this.consume(TokenType.RPAREN, "Expected ')' after function arguments.");
1540
-
1541
- const funcExpr: AST.FunctionExpr = {
1542
- type: 'function',
1543
- name,
1544
- args,
1545
- loc: _createLoc(startToken, endToken)
1546
- };
1547
-
1548
- // Add distinct field if it was parsed
1549
- if (distinct) {
1550
- funcExpr.distinct = true;
1551
- }
1552
-
1553
- // Check for OVER clause (window function)
1554
- if (this.matchKeyword('OVER')) {
1555
- const window = this.parseWindowSpecification();
1556
- const overEndToken = this.previous();
1557
- return {
1558
- type: 'windowFunction',
1559
- function: funcExpr,
1560
- window,
1561
- loc: _createLoc(startToken, overEndToken)
1562
- };
1563
- }
1564
-
1565
- return funcExpr;
1566
- }
1567
-
1568
- // Column/identifier expressions
1569
- const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
1570
- if (this.checkIdentifierLike(contextualKeywords)) {
1571
- // Schema.table.column
1572
- if (this.checkNext(1, TokenType.DOT) && this.checkIdentifierLikeAt(2, contextualKeywords) &&
1573
- this.checkNext(3, TokenType.DOT) && this.checkIdentifierLikeAt(4, contextualKeywords)) {
1574
- const schema = this.consumeIdentifier(contextualKeywords, "Expected schema name.");
1575
- this.advance(); // Consume DOT
1576
- const table = this.consumeIdentifier(contextualKeywords, "Expected table name.");
1577
- this.advance(); // Consume DOT
1578
- const name = this.consumeIdentifier(contextualKeywords, "Expected column name.");
1579
- const nameToken = this.previous();
1580
-
1581
- return {
1582
- type: 'column',
1583
- name,
1584
- table,
1585
- schema,
1586
- loc: _createLoc(startToken, nameToken),
1587
- };
1588
- }
1589
- // table.column
1590
- else if (this.checkNext(1, TokenType.DOT) && this.checkIdentifierLikeAt(2, contextualKeywords)) {
1591
- const table = this.consumeIdentifier(contextualKeywords, "Expected table name.");
1592
- this.advance(); // Consume DOT
1593
- const name = this.consumeIdentifier(contextualKeywords, "Expected column name.");
1594
- const nameToken = this.previous();
1595
-
1596
- return {
1597
- type: 'column',
1598
- name,
1599
- table,
1600
- loc: _createLoc(startToken, nameToken),
1601
- };
1602
- }
1603
- // just column
1604
- else {
1605
- const name = this.consumeIdentifier(contextualKeywords, "Expected column name.");
1606
- const nameToken = this.previous();
1607
-
1608
- return {
1609
- type: 'column',
1610
- name,
1611
- loc: _createLoc(startToken, nameToken),
1612
- };
1613
- }
1614
- }
1615
-
1616
- // Parenthesized expression or scalar subquery
1617
- if (this.match(TokenType.LPAREN)) {
1618
- // Look ahead to see if this is a scalar subquery (SELECT ...)
1619
- if (this.check(TokenType.SELECT)) {
1620
- const selectToken = this.consume(TokenType.SELECT, "Expected 'SELECT' in subquery.");
1621
- const subquery = this.selectStatement(selectToken);
1622
- this.consume(TokenType.RPAREN, "Expected ')' after subquery.");
1623
- return {
1624
- type: 'subquery',
1625
- query: subquery,
1626
- loc: _createLoc(startToken, this.previous())
1627
- };
1628
- } else {
1629
- // Regular parenthesized expression
1630
- const expr = this.expression();
1631
- this.consume(TokenType.RPAREN, "Expected ')' after expression.");
1632
- return expr;
1633
- }
1634
- }
1635
-
1636
- throw this.error(this.peek(), "Expected expression.");
1637
- }
1638
-
1639
- /**
1640
- * Parses a window specification: (PARTITION BY ... ORDER BY ... [frame])
1641
- */
1642
- private parseWindowSpecification(): AST.WindowDefinition {
1643
- if (this.match(TokenType.LPAREN)) {
1644
- let partitionBy: AST.Expression[] | undefined;
1645
- let orderBy: AST.OrderByClause[] | undefined;
1646
- let frame: AST.WindowFrame | undefined;
1647
-
1648
- if (this.matchKeyword('PARTITION')) {
1649
- this.consumeKeyword('BY', "Expected 'BY' after 'PARTITION'.");
1650
- partitionBy = [];
1651
- do {
1652
- partitionBy.push(this.expression());
1653
- } while (this.match(TokenType.COMMA));
1654
- }
1655
-
1656
- if (this.matchKeyword('ORDER')) {
1657
- this.consumeKeyword('BY', "Expected 'BY' after 'ORDER'.");
1658
- orderBy = [];
1659
- do {
1660
- const expr = this.expression();
1661
- const direction = this.match(TokenType.DESC) ? 'desc' : (this.match(TokenType.ASC) ? 'asc' : 'asc');
1662
-
1663
- // Handle NULLS FIRST/LAST
1664
- let nulls: 'first' | 'last' | undefined;
1665
- if (this.matchKeyword('NULLS')) {
1666
- if (this.matchKeyword('FIRST')) {
1667
- nulls = 'first';
1668
- } else if (this.matchKeyword('LAST')) {
1669
- nulls = 'last';
1670
- } else {
1671
- throw this.error(this.peek(), "Expected 'FIRST' or 'LAST' after 'NULLS'.");
1672
- }
1673
- }
1674
-
1675
- const orderClause: AST.OrderByClause = { expr, direction };
1676
- if (nulls) {
1677
- orderClause.nulls = nulls;
1678
- }
1679
- orderBy.push(orderClause);
1680
- } while (this.match(TokenType.COMMA));
1681
- }
1682
-
1683
- // Frame clause (ROWS|RANGE ...)
1684
- if (this.matchKeyword('ROWS') || this.matchKeyword('RANGE')) {
1685
- const frameType = this.previous().lexeme.toLowerCase() as 'rows' | 'range';
1686
-
1687
- // Handle both BETWEEN...AND and single bound syntax
1688
- if (this.matchKeyword('BETWEEN')) {
1689
- // ROWS BETWEEN start_bound AND end_bound
1690
- const start = this.parseWindowFrameBound();
1691
- this.consumeKeyword('AND', "Expected 'AND' after frame start bound.");
1692
- const end = this.parseWindowFrameBound();
1693
- frame = { type: frameType, start, end };
1694
- } else {
1695
- // ROWS start_bound (shorthand for ROWS BETWEEN start_bound AND CURRENT ROW)
1696
- const start = this.parseWindowFrameBound();
1697
- frame = { type: frameType, start, end: null };
1698
- }
1699
- }
1700
-
1701
- this.consume(TokenType.RPAREN, "Expected ')' after window specification.");
1702
- return { type: 'windowDefinition', partitionBy, orderBy, frame };
1703
- } else {
1704
- // Window name (not implemented)
1705
- throw this.error(this.peek(), 'Window name references are not yet supported. Use explicit window specs.');
1706
- }
1707
- }
1708
-
1709
- /**
1710
- * Parses a window frame bound (UNBOUNDED PRECEDING, CURRENT ROW, n PRECEDING/FOLLOWING)
1711
- */
1712
- private parseWindowFrameBound(): AST.WindowFrameBound {
1713
- if (this.matchKeyword('UNBOUNDED')) {
1714
- if (this.matchKeyword('PRECEDING')) {
1715
- return { type: 'unboundedPreceding' };
1716
- } else if (this.matchKeyword('FOLLOWING')) {
1717
- return { type: 'unboundedFollowing' };
1718
- } else {
1719
- throw this.error(this.peek(), "Expected PRECEDING or FOLLOWING after UNBOUNDED.");
1720
- }
1721
- } else if (this.matchKeyword('CURRENT')) {
1722
- this.consumeKeyword('ROW', "Expected 'ROW' after 'CURRENT'.");
1723
- return { type: 'currentRow' };
1724
- } else {
1725
- const value = this.expression();
1726
- if (this.matchKeyword('PRECEDING')) {
1727
- return { type: 'preceding', value };
1728
- } else if (this.matchKeyword('FOLLOWING')) {
1729
- return { type: 'following', value };
1730
- } else {
1731
- throw this.error(this.peek(), "Expected PRECEDING or FOLLOWING after frame value.");
1732
- }
1733
- }
1734
- }
1735
-
1736
- // Helper methods for token management
1737
-
1738
- private match(...types: TokenType[]): boolean {
1739
- for (const type of types) {
1740
- if (this.check(type)) {
1741
- this.advance();
1742
- return true;
1743
- }
1744
- }
1745
- return false;
1746
- }
1747
-
1748
- private consume(type: TokenType, message: string): Token {
1749
- if (this.check(type)) {
1750
- return this.advance();
1751
- }
1752
-
1753
- // If a ')' was expected, point back to the matching '('
1754
- if (type === TokenType.RPAREN && this.parenStack.length > 0) {
1755
- const openToken = this.parenStack[this.parenStack.length - 1];
1756
- const got = this.peek();
1757
- quereusError(
1758
- `${message} Unterminated '(' opened at line ${openToken.startLine}, column ${openToken.startColumn}. Got '${got.lexeme}'.`,
1759
- StatusCode.ERROR,
1760
- undefined,
1761
- {
1762
- loc: {
1763
- start: { line: openToken.startLine, column: openToken.startColumn },
1764
- end: { line: this.peek().endLine, column: this.peek().endColumn },
1765
- },
1766
- }
1767
- );
1768
- }
1769
-
1770
- const got = this.peek();
1771
- this.error(got, `${message} Got '${got.lexeme}'.`);
1772
- }
1773
-
1774
- private check(type: TokenType): boolean {
1775
- if (this.isAtEnd()) return false;
1776
- return this.peek().type === type;
1777
- }
1778
-
1779
- private checkNext(n: number, type: TokenType): boolean {
1780
- if (this.current + n >= this.tokens.length) return false;
1781
- return this.tokens[this.current + n].type === type;
1782
- }
1783
-
1784
- private advance(): Token {
1785
- if (!this.isAtEnd()) this.current++;
1786
- const tok = this.previous();
1787
- // Maintain parenthesis balance for precise diagnostics
1788
- if (tok.type === TokenType.LPAREN) {
1789
- this.parenStack.push(tok);
1790
- } else if (tok.type === TokenType.RPAREN) {
1791
- if (this.parenStack.length === 0) {
1792
- quereusError(
1793
- `Unmatched ')' at line ${tok.startLine}, column ${tok.startColumn}.`,
1794
- StatusCode.ERROR,
1795
- undefined,
1796
- { loc: { start: { line: tok.startLine, column: tok.startColumn }, end: { line: tok.endLine, column: tok.endColumn } } }
1797
- );
1798
- } else {
1799
- this.parenStack.pop();
1800
- }
1801
- }
1802
- return tok;
1803
- }
1804
-
1805
- private isAtEnd(): boolean {
1806
- return this.peek().type === TokenType.EOF;
1807
- }
1808
-
1809
- private peek(): Token {
1810
- return this.tokens[this.current];
1811
- }
1812
-
1813
- private previous(): Token {
1814
- return this.tokens[this.current - 1];
1815
- }
1816
-
1817
- private error(token: Token, message: string): never {
1818
- // If we see common starter tokens for a different clause where a separator/comma or keyword was expected,
1819
- // enhance the message to hint at likely fixes instead of generic parenthesis errors.
1820
- const nextLex = token.lexeme?.toUpperCase?.() || token.lexeme;
1821
- const hintParts: string[] = [];
1822
- if (this.peekKeyword('CONSTRAINT') || this.peekKeyword('PRIMARY') || this.peekKeyword('UNIQUE') || this.peekKeyword('CHECK') || this.peekKeyword('FOREIGN')) {
1823
- hintParts.push("If you're in CREATE TABLE, you might be missing a comma between elements.");
1824
- }
1825
- if (nextLex === 'ON' && !this.peekKeyword('JOIN')) {
1826
- hintParts.push("'ON' must follow a JOIN. Use WHERE for filters in subqueries.");
1827
- }
1828
- const fullMessage = hintParts.length > 0 ? `${message} ${hintParts.join(' ')}` : message;
1829
- quereusError(
1830
- fullMessage,
1831
- StatusCode.ERROR,
1832
- undefined,
1833
- {
1834
- loc: {
1835
- start: {
1836
- line: token.startLine,
1837
- column: token.startColumn,
1838
- },
1839
- end: {
1840
- line: token.endLine,
1841
- column: token.endColumn,
1842
- }
1843
- }
1844
- }
1845
- );
1846
- }
1847
-
1848
- private isJoinToken(): boolean {
1849
- return this.check(TokenType.JOIN) ||
1850
- this.check(TokenType.INNER) ||
1851
- this.check(TokenType.LEFT) ||
1852
- this.check(TokenType.RIGHT) ||
1853
- this.check(TokenType.FULL) ||
1854
- this.check(TokenType.CROSS);
1855
- }
1856
-
1857
- private isEndOfClause(): boolean {
1858
- const token = this.peek().type;
1859
- return token === TokenType.FROM ||
1860
- token === TokenType.WHERE ||
1861
- token === TokenType.GROUP ||
1862
- token === TokenType.HAVING ||
1863
- token === TokenType.ORDER ||
1864
- token === TokenType.LIMIT ||
1865
- token === TokenType.UNION || token === TokenType.DIFF || token === TokenType.INTERSECT || token === TokenType.EXCEPT ||
1866
- token === TokenType.SEMICOLON ||
1867
- token === TokenType.EOF;
1868
- }
1869
-
1870
- // --- Statement Parsing Stubs ---
1871
-
1872
- /** @internal */
1873
- private updateStatement(startToken: Token, _withClause?: AST.WithClause): AST.UpdateStmt {
1874
- const table = this.tableIdentifier();
1875
-
1876
- // Parse mutation context assignments if present
1877
- let contextValues: AST.ContextAssignment[] | undefined;
1878
- if (this.matchKeyword('WITH')) {
1879
- if (this.matchKeyword('CONTEXT')) {
1880
- contextValues = this.parseContextAssignments();
1881
- } else {
1882
- // Not a WITH CONTEXT clause, backtrack
1883
- this.current--;
1884
- }
1885
- }
1886
-
1887
- this.consume(TokenType.SET, "Expected 'SET' after table name in UPDATE.");
1888
- const assignments: { column: string; value: AST.Expression }[] = [];
1889
- do {
1890
- const column = this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'], "Expected column name in SET clause.");
1891
- this.consume(TokenType.EQUAL, "Expected '=' after column name in SET clause.");
1892
- const value = this.expression();
1893
- assignments.push({ column, value });
1894
- } while (this.match(TokenType.COMMA));
1895
- let where: AST.Expression | undefined;
1896
- if (this.match(TokenType.WHERE)) {
1897
- where = this.expression();
1898
- }
1899
- // Parse RETURNING clause if present
1900
- let returning: AST.ResultColumn[] | undefined;
1901
- if (this.matchKeyword('RETURNING')) {
1902
- returning = this.columnList();
1903
- }
1904
- const endToken = this.previous();
1905
- return { type: 'update', table, assignments, where, returning, contextValues, loc: _createLoc(startToken, endToken) };
1906
- }
1907
-
1908
- /** @internal */
1909
- private deleteStatement(startToken: Token, _withClause?: AST.WithClause): AST.DeleteStmt {
1910
- this.matchKeyword('FROM');
1911
- const table = this.tableIdentifier();
1912
-
1913
- // Parse mutation context assignments if present
1914
- let contextValues: AST.ContextAssignment[] | undefined;
1915
- if (this.matchKeyword('WITH')) {
1916
- if (this.matchKeyword('CONTEXT')) {
1917
- contextValues = this.parseContextAssignments();
1918
- } else {
1919
- // Not a WITH CONTEXT clause, backtrack
1920
- this.current--;
1921
- }
1922
- }
1923
-
1924
- let where: AST.Expression | undefined;
1925
- if (this.match(TokenType.WHERE)) {
1926
- where = this.expression();
1927
- }
1928
-
1929
- // Parse RETURNING clause if present
1930
- let returning: AST.ResultColumn[] | undefined;
1931
- if (this.matchKeyword('RETURNING')) {
1932
- returning = this.columnList();
1933
- }
1934
-
1935
- const endToken = this.previous();
1936
- return { type: 'delete', table, where, returning, contextValues, loc: _createLoc(startToken, endToken) };
1937
- }
1938
-
1939
- /** @internal */
1940
- private valuesStatement(startToken: Token): AST.ValuesStmt {
1941
- const values: AST.Expression[][] = [];
1942
-
1943
- do {
1944
- this.consume(TokenType.LPAREN, "Expected '(' before values.");
1945
- const valueList: AST.Expression[] = [];
1946
-
1947
- if (!this.check(TokenType.RPAREN)) { // Check for empty value list
1948
- do {
1949
- valueList.push(this.expression());
1950
- } while (this.match(TokenType.COMMA));
1951
- }
1952
-
1953
- this.consume(TokenType.RPAREN, "Expected ')' after values.");
1954
- values.push(valueList);
1955
- } while (this.match(TokenType.COMMA));
1956
-
1957
- const endToken = this.previous();
1958
- return { type: 'values', values, loc: _createLoc(startToken, endToken) };
1959
- }
1960
-
1961
- /** @internal */
1962
- private createStatement(startToken: Token, withClause?: AST.WithClause): AST.CreateTableStmt | AST.CreateIndexStmt | AST.CreateViewStmt | AST.CreateAssertionStmt {
1963
- if (this.peekKeyword('TABLE')) {
1964
- this.consumeKeyword('TABLE', "Expected 'TABLE' after CREATE.");
1965
- return this.createTableStatement(startToken, withClause);
1966
- } else if (this.peekKeyword('INDEX')) {
1967
- this.consumeKeyword('INDEX', "Expected 'INDEX' after CREATE.");
1968
- return this.createIndexStatement(startToken, false, withClause);
1969
- } else if (this.peekKeyword('VIEW')) {
1970
- this.consumeKeyword('VIEW', "Expected 'VIEW' after CREATE.");
1971
- return this.createViewStatement(startToken, withClause);
1972
- } else if (this.peekKeyword('ASSERTION')) {
1973
- this.consumeKeyword('ASSERTION', "Expected 'ASSERTION' after CREATE.");
1974
- return this.createAssertionStatement(startToken, withClause);
1975
- } else if (this.peekKeyword('UNIQUE')) {
1976
- this.consumeKeyword('UNIQUE', "Expected 'UNIQUE' after CREATE.");
1977
- this.consumeKeyword('INDEX', "Expected 'INDEX' after CREATE UNIQUE.");
1978
- return this.createIndexStatement(startToken, true, withClause);
1979
- }
1980
- throw this.error(this.peek(), "Expected TABLE, [UNIQUE] INDEX, VIEW, ASSERTION, or VIRTUAL after CREATE.");
1981
- }
1982
-
1983
- /**
1984
- * Parse CREATE TABLE statement
1985
- * @returns AST for CREATE TABLE
1986
- */
1987
- private createTableStatement(startToken: Token, _withClause?: AST.WithClause): AST.CreateTableStmt {
1988
- let isTemporary = false;
1989
- if (this.peekKeyword('TEMP') || this.peekKeyword('TEMPORARY')) {
1990
- isTemporary = true;
1991
- this.advance();
1992
- }
1993
-
1994
- let ifNotExists = false;
1995
- if (this.matchKeyword('IF')) {
1996
- this.consumeKeyword('NOT', "Expected 'NOT' after 'IF'.");
1997
- this.consumeKeyword('EXISTS', "Expected 'EXISTS' after 'IF NOT'.");
1998
- ifNotExists = true;
1999
- }
2000
-
2001
- const table = this.tableIdentifier();
2002
-
2003
- const columns: AST.ColumnDef[] = [];
2004
- const constraints: AST.TableConstraint[] = [];
2005
-
2006
- if (this.check(TokenType.LPAREN)) {
2007
- this.consume(TokenType.LPAREN, "Expected '(' to start table definition.");
2008
- do {
2009
- if (this.peekKeyword('PRIMARY') || this.peekKeyword('UNIQUE') || this.peekKeyword('CHECK') || this.peekKeyword('FOREIGN') || this.peekKeyword('CONSTRAINT')) {
2010
- constraints.push(this.tableConstraint());
2011
- } else {
2012
- columns.push(this.columnDefinition());
2013
- }
2014
- // Allow trailing comma before ')'
2015
- } while (this.match(TokenType.COMMA) && !this.check(TokenType.RPAREN));
2016
-
2017
- // If we didn't see a comma and the next token looks like the start of another
2018
- // column or table constraint, provide a clearer error about a missing comma.
2019
- if (!this.check(TokenType.RPAREN)) {
2020
- const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
2021
- const nextLooksLikeAnotherItem = this.peekKeyword('PRIMARY') || this.peekKeyword('UNIQUE') || this.peekKeyword('CHECK') || this.peekKeyword('FOREIGN') || this.peekKeyword('CONSTRAINT') || this.checkIdentifierLike(contextualKeywords);
2022
- if (nextLooksLikeAnotherItem) {
2023
- const next = this.peek();
2024
- throw this.error(next, `Expected ',' between table elements. Did you forget a comma before '${next.lexeme}'?`);
2025
- }
2026
- }
2027
-
2028
- this.consume(TokenType.RPAREN, "Expected ')' after table definition.");
2029
-
2030
- } else if (this.matchKeyword('AS')) {
2031
- const token = this.previous();
2032
- quereusError(
2033
- 'CREATE TABLE AS SELECT is not supported.',
2034
- StatusCode.UNSUPPORTED,
2035
- undefined,
2036
- { loc: { start: { line: token.startLine, column: token.startColumn } } }
2037
- );
2038
- } else {
2039
- throw this.error(this.peek(), "Expected '(' or 'AS' after table name.");
2040
- }
2041
-
2042
- let moduleName: string | undefined;
2043
- const moduleArgs: Record<string, SqlValue> = {};
2044
- if (this.matchKeyword('USING')) {
2045
- moduleName = this.consumeIdentifier("Expected module name after 'USING'.");
2046
- if (this.match(TokenType.LPAREN)) {
2047
- let positionalIndex = 0;
2048
- if (!this.check(TokenType.RPAREN)) {
2049
- do {
2050
- // Check if this is a positional argument (string/number literal) or named argument (identifier=value)
2051
- if (this.check(TokenType.STRING) || this.check(TokenType.INTEGER) || this.check(TokenType.FLOAT)) {
2052
- // Positional argument
2053
- const token = this.advance();
2054
- moduleArgs[String(positionalIndex++)] = token.literal;
2055
- } else if (this.check(TokenType.IDENTIFIER)) {
2056
- // Could be named argument or identifier value
2057
- const nameValue = this.nameValueItem('module argument');
2058
- moduleArgs[nameValue.name] = nameValue.value && nameValue.value.type === 'literal'
2059
- ? getSyncLiteral(nameValue.value)
2060
- : (nameValue.value && nameValue.value.type === 'identifier' ? nameValue.value.name : nameValue.name);
2061
- } else {
2062
- throw this.error(this.peek(), "Expected module argument (string, number, or name=value pair).");
2063
- }
2064
- } while (this.match(TokenType.COMMA));
2065
- }
2066
- this.consume(TokenType.RPAREN, "Expected ')' after module arguments.");
2067
- }
2068
- }
2069
-
2070
- // Parse mutation context definitions if present
2071
- let contextDefinitions: AST.MutationContextVar[] | undefined;
2072
- if (this.matchKeyword('WITH')) {
2073
- if (this.matchKeyword('CONTEXT')) {
2074
- contextDefinitions = this.parseMutationContextDefinitions();
2075
- } else {
2076
- // Not a WITH CONTEXT clause, backtrack
2077
- this.current--;
2078
- }
2079
- }
2080
-
2081
- return {
2082
- type: 'createTable',
2083
- table,
2084
- ifNotExists,
2085
- columns,
2086
- constraints,
2087
- isTemporary,
2088
- moduleName,
2089
- moduleArgs,
2090
- contextDefinitions,
2091
- loc: _createLoc(startToken, this.previous()),
2092
- };
2093
- }
2094
-
2095
- /**
2096
- * Parse CREATE INDEX statement
2097
- * @param isUnique Flag indicating if UNIQUE keyword was already parsed
2098
- * @returns AST for CREATE INDEX
2099
- */
2100
- private createIndexStatement(startToken: Token, isUnique = false, _withClause?: AST.WithClause): AST.CreateIndexStmt {
2101
- if (!isUnique && this.peekKeyword('UNIQUE')) {
2102
- isUnique = true;
2103
- this.advance();
2104
- }
2105
-
2106
- let ifNotExists = false;
2107
- if (this.matchKeyword('IF')) {
2108
- this.consumeKeyword('NOT', "Expected 'NOT' after 'IF'.");
2109
- this.consumeKeyword('EXISTS', "Expected 'EXISTS' after 'IF NOT'.");
2110
- ifNotExists = true;
2111
- }
2112
-
2113
- const index = this.tableIdentifier();
2114
-
2115
- this.consumeKeyword('ON', "Expected 'ON' after index name.");
2116
-
2117
- const table = this.tableIdentifier();
2118
-
2119
- this.consume(TokenType.LPAREN, "Expected '(' before indexed columns.");
2120
- const columns = this.indexedColumnList();
2121
- this.consume(TokenType.RPAREN, "Expected ')' after indexed columns.");
2122
-
2123
- let where: AST.Expression | undefined;
2124
- if (this.matchKeyword('WHERE')) {
2125
- where = this.expression();
2126
- }
2127
-
2128
- return {
2129
- type: 'createIndex',
2130
- index,
2131
- table,
2132
- ifNotExists,
2133
- columns,
2134
- where,
2135
- isUnique,
2136
- loc: _createLoc(startToken, this.previous()),
2137
- };
2138
- }
2139
-
2140
- /**
2141
- * Parse CREATE VIEW statement
2142
- * @returns AST for CREATE VIEW
2143
- */
2144
- private createViewStatement(startToken: Token, withClause?: AST.WithClause): AST.CreateViewStmt {
2145
- let isTemporary = false;
2146
- if (this.peekKeyword('TEMP') || this.peekKeyword('TEMPORARY')) {
2147
- isTemporary = true;
2148
- this.advance();
2149
- }
2150
-
2151
- let ifNotExists = false;
2152
- if (this.matchKeyword('IF')) {
2153
- this.consumeKeyword('NOT', "Expected 'NOT' after 'IF'.");
2154
- this.consumeKeyword('EXISTS', "Expected 'EXISTS' after 'IF NOT'.");
2155
- ifNotExists = true;
2156
- }
2157
-
2158
- const view = this.tableIdentifier();
2159
-
2160
- let columns: string[] | undefined;
2161
- if (this.check(TokenType.LPAREN)) {
2162
- this.consume(TokenType.LPAREN, "Expected '(' to start view column list.");
2163
- columns = [];
2164
- const contextualKeywords = ['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'];
2165
- if (!this.check(TokenType.RPAREN)) {
2166
- do {
2167
- columns.push(this.consumeIdentifier(contextualKeywords, "Expected column name in view column list."));
2168
- } while (this.match(TokenType.COMMA) && !this.check(TokenType.RPAREN));
2169
- }
2170
- this.consume(TokenType.RPAREN, "Expected ')' after view column list.");
2171
- }
2172
-
2173
- this.consumeKeyword('AS', "Expected 'AS' before SELECT statement for CREATE VIEW.");
2174
-
2175
- const selectStartToken = this.consume(TokenType.SELECT, "Expected 'SELECT' after 'AS' in CREATE VIEW.");
2176
- const select = this.selectStatement(selectStartToken, withClause);
2177
-
2178
- return {
2179
- type: 'createView',
2180
- view,
2181
- ifNotExists,
2182
- columns,
2183
- select,
2184
- isTemporary,
2185
- loc: _createLoc(startToken, this.previous()),
2186
- };
2187
- }
2188
-
2189
- /**
2190
- * Parse CREATE ASSERTION statement
2191
- * @returns AST for CREATE ASSERTION
2192
- */
2193
- private createAssertionStatement(startToken: Token, _withClause?: AST.WithClause): AST.CreateAssertionStmt {
2194
- const name = this.consumeIdentifier("Expected assertion name.");
2195
-
2196
- this.consumeKeyword('CHECK', "Expected 'CHECK' after assertion name.");
2197
- this.consume(TokenType.LPAREN, "Expected '(' after CHECK.");
2198
-
2199
- const check = this.expression();
2200
-
2201
- this.consume(TokenType.RPAREN, "Expected ')' after CHECK expression.");
2202
-
2203
- return {
2204
- type: 'createAssertion',
2205
- name,
2206
- check,
2207
- loc: _createLoc(startToken, this.previous()),
2208
- };
2209
- }
2210
-
2211
- /**
2212
- * Parse DROP statement
2213
- * @returns AST for DROP statement
2214
- */
2215
- private dropStatement(startToken: Token, _withClause?: AST.WithClause): AST.DropStmt {
2216
- let objectType: 'table' | 'view' | 'index' | 'trigger' | 'assertion';
2217
-
2218
- if (this.peekKeyword('TABLE')) {
2219
- this.consumeKeyword('TABLE', "Expected TABLE after DROP.");
2220
- objectType = 'table';
2221
- } else if (this.peekKeyword('VIEW')) {
2222
- this.consumeKeyword('VIEW', "Expected VIEW after DROP.");
2223
- objectType = 'view';
2224
- } else if (this.peekKeyword('INDEX')) {
2225
- this.consumeKeyword('INDEX', "Expected INDEX after DROP.");
2226
- objectType = 'index';
2227
- } else if (this.peekKeyword('ASSERTION')) {
2228
- this.consumeKeyword('ASSERTION', "Expected ASSERTION after DROP.");
2229
- objectType = 'assertion';
2230
- } else {
2231
- throw this.error(this.peek(), "Expected TABLE, VIEW, INDEX, or ASSERTION after DROP.");
2232
- }
2233
-
2234
- let ifExists = false;
2235
- if (this.matchKeyword('IF')) {
2236
- this.consumeKeyword('EXISTS', "Expected 'EXISTS' after 'IF'.");
2237
- ifExists = true;
2238
- }
2239
-
2240
- const name = this.tableIdentifier();
2241
-
2242
- return {
2243
- type: 'drop',
2244
- objectType,
2245
- name,
2246
- ifExists,
2247
- loc: _createLoc(startToken, this.previous()),
2248
- };
2249
- }
2250
-
2251
- /**
2252
- * Parse ALTER TABLE statement
2253
- * @returns AST for ALTER TABLE statement
2254
- */
2255
- private alterTableStatement(startToken: Token, _withClause?: AST.WithClause): AST.AlterTableStmt {
2256
- this.consumeKeyword('TABLE', "Expected 'TABLE' after ALTER.");
2257
-
2258
- const table = this.tableIdentifier();
2259
-
2260
- let action: AST.AlterTableAction;
2261
-
2262
- if (this.peekKeyword('RENAME')) {
2263
- this.consumeKeyword('RENAME', "Expected RENAME.");
2264
- if (this.matchKeyword('COLUMN')) {
2265
- const oldName = this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'], "Expected old column name after RENAME COLUMN.");
2266
- this.consumeKeyword('TO', "Expected 'TO' after old column name.");
2267
- const newName = this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'], "Expected new column name after TO.");
2268
- action = { type: 'renameColumn', oldName, newName };
2269
- } else {
2270
- this.consumeKeyword('TO', "Expected 'TO' after RENAME.");
2271
- const newName = this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'], "Expected new table name after RENAME TO.");
2272
- action = { type: 'renameTable', newName };
2273
- }
2274
- } else if (this.peekKeyword('ADD')) {
2275
- this.consumeKeyword('ADD', "Expected ADD.");
2276
- if (this.peekKeyword('CONSTRAINT')) {
2277
- // ADD CONSTRAINT ... - let tableConstraint parse everything including CONSTRAINT keyword
2278
- const constraint = this.tableConstraint();
2279
- action = { type: 'addConstraint', constraint };
2280
- } else {
2281
- // ADD [COLUMN] column_def
2282
- this.matchKeyword('COLUMN');
2283
- const column = this.columnDefinition();
2284
- action = { type: 'addColumn', column };
2285
- }
2286
- } else if (this.peekKeyword('DROP')) {
2287
- this.consumeKeyword('DROP', "Expected DROP.");
2288
- this.matchKeyword('COLUMN');
2289
- const name = this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'], "Expected column name after DROP COLUMN.");
2290
- action = { type: 'dropColumn', name };
2291
- } else {
2292
- throw this.error(this.peek(), "Expected RENAME, ADD, or DROP after table name in ALTER TABLE.");
2293
- }
2294
-
2295
- return {
2296
- type: 'alterTable',
2297
- table,
2298
- action,
2299
- loc: _createLoc(startToken, this.previous()),
2300
- };
2301
- }
2302
-
2303
- /**
2304
- * Parse BEGIN statement
2305
- * @returns AST for BEGIN statement
2306
- */
2307
- private beginStatement(startToken: Token, _withClause?: AST.WithClause): AST.BeginStmt {
2308
- // Skip optional TRANSACTION keyword
2309
- this.matchKeyword('TRANSACTION');
2310
-
2311
- return { type: 'begin', loc: _createLoc(startToken, this.previous()) };
2312
- }
2313
-
2314
- /**
2315
- * Parse COMMIT statement
2316
- * @returns AST for COMMIT statement
2317
- */
2318
- private commitStatement(startToken: Token, _withClause?: AST.WithClause): AST.CommitStmt {
2319
- this.matchKeyword('TRANSACTION');
2320
- return { type: 'commit', loc: _createLoc(startToken, this.previous()) };
2321
- }
2322
-
2323
- /**
2324
- * Parse ROLLBACK statement
2325
- * @returns AST for ROLLBACK statement
2326
- */
2327
- private rollbackStatement(startToken: Token, _withClause?: AST.WithClause): AST.RollbackStmt {
2328
- this.matchKeyword('TRANSACTION');
2329
-
2330
- let savepoint: string | undefined;
2331
- if (this.matchKeyword('TO')) {
2332
- this.matchKeyword('SAVEPOINT');
2333
- if (!this.check(TokenType.IDENTIFIER)) {
2334
- throw this.error(this.peek(), "Expected savepoint name after ROLLBACK TO.");
2335
- }
2336
- savepoint = this.getIdentifierValue(this.advance());
2337
- }
2338
- return { type: 'rollback', savepoint, loc: _createLoc(startToken, this.previous()) };
2339
- }
2340
-
2341
- /**
2342
- * Parse SAVEPOINT statement
2343
- * @returns AST for SAVEPOINT statement
2344
- */
2345
- private savepointStatement(startToken: Token, _withClause?: AST.WithClause): AST.SavepointStmt {
2346
- const name = this.consumeIdentifier("Expected savepoint name after SAVEPOINT.");
2347
- return { type: 'savepoint', name, loc: _createLoc(startToken, this.previous()) };
2348
- }
2349
-
2350
- /**
2351
- * Parse RELEASE statement
2352
- * @returns AST for RELEASE statement
2353
- */
2354
- private releaseStatement(startToken: Token, _withClause?: AST.WithClause): AST.ReleaseStmt {
2355
- this.matchKeyword('SAVEPOINT');
2356
- const name = this.consumeIdentifier("Expected savepoint name after RELEASE [SAVEPOINT].");
2357
- return { type: 'release', savepoint: name, loc: _createLoc(startToken, this.previous()) };
2358
- }
2359
-
2360
- /**
2361
- * Parse PRAGMA statement
2362
- * @returns AST for PRAGMA statement
2363
- */
2364
- private pragmaStatement(startToken: Token, _withClause?: AST.WithClause): AST.PragmaStmt {
2365
- const nameValue = this.nameValueItem("pragma");
2366
- return { type: 'pragma', ...nameValue, loc: _createLoc(startToken, this.previous()) };
2367
- }
2368
-
2369
- // === Declarative schema parsing ===
2370
-
2371
- private declareSchemaStatement(startToken: Token): AST.DeclareSchemaStmt {
2372
- this.consumeKeyword('SCHEMA', "Expected 'SCHEMA' after DECLARE.");
2373
- const schemaName = this.consumeIdentifier(['temp', 'temporary'], "Expected schema name after DECLARE.");
2374
- let version: string | undefined;
2375
- let using: { defaultVtabModule?: string; defaultVtabArgs?: string } | undefined;
2376
-
2377
- // Optional: version 'semver'
2378
- // no-op
2379
- if (this.matchKeyword('VERSION')) {
2380
- const tok = this.consume(TokenType.STRING, "Expected version string after VERSION.");
2381
- version = String(tok.literal);
2382
- }
2383
-
2384
- // Optional: using ( default_vtab_module = 'memory', default_vtab_args = '[]' )
2385
- if (this.match(TokenType.USING)) {
2386
- this.consume(TokenType.LPAREN, "Expected '(' after USING.");
2387
- using = {};
2388
- if (!this.check(TokenType.RPAREN)) {
2389
- do {
2390
- const optName = this.consumeIdentifier("Expected option name inside USING().").toLowerCase();
2391
- this.consume(TokenType.EQUAL, "Expected '=' after option name in USING().");
2392
- if (optName === 'default_vtab_module') {
2393
- const t = this.consume(TokenType.STRING, "Expected string for default_vtab_module.");
2394
- using.defaultVtabModule = String(t.literal);
2395
- } else if (optName === 'default_vtab_args') {
2396
- const t = this.consume(TokenType.STRING, "Expected JSON string for default_vtab_args.");
2397
- using.defaultVtabArgs = String(t.literal);
2398
- } else {
2399
- // Consume simple literal/identifier for forward compatibility
2400
- if (this.check(TokenType.STRING) || this.check(TokenType.INTEGER) || this.check(TokenType.FLOAT) || this.check(TokenType.IDENTIFIER)) {
2401
- this.advance();
2402
- }
2403
- }
2404
- } while (this.match(TokenType.COMMA));
2405
- }
2406
- this.consume(TokenType.RPAREN, "Expected ')' after USING options.");
2407
- }
2408
-
2409
- // Block
2410
- // Parse declaration block delimited by '{' '}'
2411
- this.consume(TokenType.LBRACE, "Expected '{' to start schema declaration block.");
2412
- const items: AST.DeclareItem[] = [];
2413
-
2414
- while (!this.check(TokenType.RBRACE)) {
2415
- if (this.isAtEnd()) break;
2416
- // table ...
2417
- if (this.peekKeyword('TABLE')) {
2418
- this.advance();
2419
- items.push(this.declareTableItem());
2420
- } else if (this.peekKeyword('INDEX')) {
2421
- this.advance();
2422
- items.push(this.declareIndexItem());
2423
- } else if (this.peekKeyword('VIEW')) {
2424
- this.advance();
2425
- items.push(this.declareViewItem());
2426
- } else if (this.peekKeyword('SEED')) {
2427
- this.advance();
2428
- items.push(this.declareSeedItem());
2429
- } else {
2430
- // Fallback: ignore unrecognized item (domain, collation, import)
2431
- const start = this.peek();
2432
- // consume until semicolon
2433
- while (!this.isAtEnd() && !this.check(TokenType.SEMICOLON) && !(this.check(TokenType.IDENTIFIER) && this.peek().lexeme === '}')) {
2434
- this.advance();
2435
- }
2436
- const endTok = this.previous();
2437
- items.push({ type: 'declareIgnored', kind: 'domain', text: this.sourceSlice(start.startOffset, endTok.endOffset) } as unknown as AST.DeclareIgnoredItem);
2438
- }
2439
- this.match(TokenType.SEMICOLON);
2440
- }
2441
-
2442
- this.consume(TokenType.RBRACE, "Expected '}' to close schema declaration block.");
2443
-
2444
- const endTok = this.previous();
2445
- return { type: 'declareSchema', schemaName, version, using, items, loc: _createLoc(startToken, endTok) };
2446
- }
2447
-
2448
- private declareTableItem(): AST.DeclaredTable {
2449
- const tableName = this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'], 'Expected table name in declaration.');
2450
- let moduleName: string | undefined;
2451
- let moduleArgs: Record<string, SqlValue> | undefined;
2452
- const columns: AST.ColumnDef[] = [];
2453
- const constraints: AST.TableConstraint[] = [];
2454
-
2455
- // Optional USING module
2456
- if (this.match(TokenType.USING)) {
2457
- if (this.check(TokenType.IDENTIFIER)) {
2458
- moduleName = this.getIdentifierValue(this.advance());
2459
- }
2460
- if (this.match(TokenType.LPAREN)) {
2461
- moduleArgs = {};
2462
- let positionalIndex = 0;
2463
- if (!this.check(TokenType.RPAREN)) {
2464
- do {
2465
- // Check if this is a positional argument (string/number literal) or named argument (identifier=value)
2466
- if (this.check(TokenType.STRING) || this.check(TokenType.INTEGER) || this.check(TokenType.FLOAT)) {
2467
- // Positional argument
2468
- const token = this.advance();
2469
- moduleArgs[String(positionalIndex++)] = token.literal;
2470
- } else if (this.check(TokenType.IDENTIFIER)) {
2471
- // Could be named argument or identifier value
2472
- const nv = this.nameValueItem('module argument');
2473
- moduleArgs[nv.name] = nv.value && nv.value.type === 'literal' ? getSyncLiteral(nv.value) : (nv.value && nv.value.type === 'identifier' ? nv.value.name : null);
2474
- } else {
2475
- throw this.error(this.peek(), "Expected module argument (string, number, or name=value pair).");
2476
- }
2477
- } while (this.match(TokenType.COMMA));
2478
- }
2479
- this.consume(TokenType.RPAREN, "Expected ')' after module arguments.");
2480
- }
2481
- }
2482
-
2483
- // Column list can be in parens (...) or braces {...}
2484
- const useBraces = this.check(TokenType.LBRACE);
2485
- if (useBraces) {
2486
- this.consume(TokenType.LBRACE, "Expected '{' before column definitions.");
2487
- } else {
2488
- this.consume(TokenType.LPAREN, "Expected '(' or '{' before column definitions.");
2489
- }
2490
-
2491
- if (!this.check(useBraces ? TokenType.RBRACE : TokenType.RPAREN)) {
2492
- do {
2493
- // Distinguish table constraint vs column definition by lookahead for '(' or constraint keywords
2494
- if (this.peekKeyword('CONSTRAINT') || this.peekKeyword('PRIMARY') || this.peekKeyword('UNIQUE') || this.peekKeyword('CHECK') || this.peekKeyword('FOREIGN')) {
2495
- constraints.push(this.tableConstraint());
2496
- } else {
2497
- columns.push(this.columnDefinition());
2498
- }
2499
- } while (this.match(TokenType.COMMA) && !this.check(useBraces ? TokenType.RBRACE : TokenType.RPAREN));
2500
- }
2501
-
2502
- if (useBraces) {
2503
- this.consume(TokenType.RBRACE, "Expected '}' after table definition.");
2504
- } else {
2505
- this.consume(TokenType.RPAREN, "Expected ')' after table definition.");
2506
- }
2507
-
2508
- // Build the CREATE TABLE AST node for this declared table
2509
- const tableStmt: AST.CreateTableStmt = {
2510
- type: 'createTable',
2511
- table: { type: 'identifier', name: tableName },
2512
- ifNotExists: false,
2513
- columns,
2514
- constraints,
2515
- isTemporary: false,
2516
- moduleName,
2517
- moduleArgs
2518
- };
2519
-
2520
- return { type: 'declaredTable', tableStmt };
2521
- }
2522
-
2523
- private declareIndexItem(): AST.DeclaredIndex {
2524
- const indexName = this.consumeIdentifier('Expected index name.');
2525
- this.consumeKeyword('ON', "Expected 'ON' after index name.");
2526
- const tableName = this.consumeIdentifier('Expected table name after ON.');
2527
- this.consume(TokenType.LPAREN, "Expected '(' before index columns.");
2528
- const columns = this.indexedColumnList();
2529
- this.consume(TokenType.RPAREN, "Expected ')' after index columns.");
2530
-
2531
- const indexStmt: AST.CreateIndexStmt = {
2532
- type: 'createIndex',
2533
- index: { type: 'identifier', name: indexName },
2534
- table: { type: 'identifier', name: tableName },
2535
- ifNotExists: false,
2536
- columns,
2537
- isUnique: false
2538
- };
2539
-
2540
- return { type: 'declaredIndex', indexStmt };
2541
- }
2542
-
2543
- private declareViewItem(): AST.DeclaredView {
2544
- const viewName = this.consumeIdentifier('Expected view name.');
2545
- let columns: string[] | undefined;
2546
- if (this.match(TokenType.LPAREN)) {
2547
- columns = this.identifierList();
2548
- this.consume(TokenType.RPAREN, "Expected ')' after view columns.");
2549
- }
2550
- this.consumeKeyword('AS', "Expected AS before SELECT in view declaration.");
2551
- const selTok = this.consume(TokenType.SELECT, "Expected SELECT after AS in view declaration.");
2552
- const select = this.selectStatement(selTok);
2553
-
2554
- const viewStmt: AST.CreateViewStmt = {
2555
- type: 'createView',
2556
- view: { type: 'identifier', name: viewName },
2557
- ifNotExists: false,
2558
- columns,
2559
- select,
2560
- isTemporary: false
2561
- };
2562
-
2563
- return { type: 'declaredView', viewStmt };
2564
- }
2565
-
2566
- private declareSeedItem(): AST.DeclaredSeed {
2567
- // seed <table> ( (...), (...) ) or seed <table> values (col, ...) values (...), (...)
2568
- const tableName = this.consumeIdentifier('Expected table name after SEED.');
2569
-
2570
- let columns: string[] | undefined;
2571
- const rows: SqlValue[][] = [];
2572
-
2573
- // Check for column list syntax: seed table (cols...) values (...)
2574
- if (this.matchKeyword('VALUES')) {
2575
- this.consume(TokenType.LPAREN, "Expected '(' before seed column list.");
2576
- columns = this.identifierList();
2577
- this.consume(TokenType.RPAREN, "Expected ')' after seed column list.");
2578
- this.consumeKeyword('VALUES', "Expected VALUES to introduce seed rows.");
2579
- }
2580
-
2581
- // Parse seed rows: ( (...), (...) )
2582
- this.consume(TokenType.LPAREN, "Expected '(' before seed rows.");
2583
-
2584
- do {
2585
- this.consume(TokenType.LPAREN, "Expected '(' before seed row values.");
2586
- const rowValues: SqlValue[] = [];
2587
- if (!this.check(TokenType.RPAREN)) {
2588
- do {
2589
- const expr = this.expression();
2590
- // Evaluate literal expressions to SqlValue
2591
- if (expr.type === 'literal') {
2592
- rowValues.push(getSyncLiteral(expr));
2593
- } else {
2594
- throw this.error(this.peek(), "Seed data must contain only literal values.");
2595
- }
2596
- } while (this.match(TokenType.COMMA));
2597
- }
2598
- this.consume(TokenType.RPAREN, "Expected ')' after seed row values.");
2599
- rows.push(rowValues);
2600
- } while (this.match(TokenType.COMMA));
2601
-
2602
- this.consume(TokenType.RPAREN, "Expected ')' after seed rows.");
2603
-
2604
- return { type: 'declaredSeed', tableName, columns, seedData: rows };
2605
- }
2606
-
2607
- private diffSchemaStatement(startToken: Token): AST.DiffSchemaStmt {
2608
- this.consumeKeyword('SCHEMA', "Expected SCHEMA after DIFF.");
2609
- const schemaName = this.consumeIdentifier(['temp', 'temporary'], 'Expected schema name after DIFF SCHEMA.');
2610
- return { type: 'diffSchema', schemaName, loc: _createLoc(startToken, this.previous()) };
2611
- }
2612
-
2613
- private applySchemaStatement(startToken: Token): AST.ApplySchemaStmt {
2614
- this.consumeKeyword('SCHEMA', "Expected SCHEMA after APPLY.");
2615
- const schemaName = this.consumeIdentifier(['temp', 'temporary'], 'Expected schema name after APPLY SCHEMA.');
2616
- let toVersion: string | undefined;
2617
- let withSeed = false;
2618
- let options: AST.ApplySchemaStmt['options'] | undefined;
2619
-
2620
- if (this.matchKeyword('TO')) {
2621
- this.consumeKeyword('VERSION', "Expected VERSION after TO.");
2622
- const tok = this.consume(TokenType.STRING, "Expected version string after TO VERSION.");
2623
- toVersion = String(tok.literal);
2624
- }
2625
-
2626
- // Check for WITH SEED
2627
- if (this.matchKeyword('WITH')) {
2628
- this.consumeKeyword('SEED', "Expected SEED after WITH.");
2629
- withSeed = true;
2630
- }
2631
-
2632
- if (this.matchKeyword('OPTIONS')) {
2633
- this.consume(TokenType.LPAREN, "Expected '(' after OPTIONS.");
2634
- options = {};
2635
- if (!this.check(TokenType.RPAREN)) {
2636
- do {
2637
- const key = this.consumeIdentifier('Expected option key.').toLowerCase();
2638
- this.consume(TokenType.EQUAL, "Expected '=' after option key.");
2639
- if (key === 'dry_run') options.dryRun = this.consumeBooleanLiteral();
2640
- else if (key === 'validate_only') options.validateOnly = this.consumeBooleanLiteral();
2641
- else if (key === 'allow_destructive') options.allowDestructive = this.consumeBooleanLiteral();
2642
- else if (key === 'rename_policy') {
2643
- const vtok = this.consume(TokenType.STRING, "Expected string for rename_policy.");
2644
- options.renamePolicy = String(vtok.literal) as 'require-hint' | 'infer-id';
2645
- } else {
2646
- // consume literal
2647
- if (this.check(TokenType.STRING) || this.check(TokenType.INTEGER) || this.check(TokenType.FLOAT) || this.check(TokenType.IDENTIFIER)) this.advance();
2648
- }
2649
- } while (this.match(TokenType.COMMA));
2650
- }
2651
- this.consume(TokenType.RPAREN, "Expected ')' after OPTIONS.");
2652
- }
2653
-
2654
- return { type: 'applySchema', schemaName, toVersion, withSeed, options, loc: _createLoc(startToken, this.previous()) };
2655
- }
2656
-
2657
- private explainSchemaStatement(startToken: Token): AST.ExplainSchemaStmt {
2658
- this.consumeKeyword('SCHEMA', "Expected SCHEMA after EXPLAIN.");
2659
- const schemaName = this.consumeIdentifier(['temp', 'temporary'], 'Expected schema name after EXPLAIN SCHEMA.');
2660
- let version: string | undefined;
2661
-
2662
- if (this.matchKeyword('VERSION')) {
2663
- const tok = this.consume(TokenType.STRING, "Expected version string after VERSION.");
2664
- version = String(tok.literal);
2665
- }
2666
-
2667
- return { type: 'explainSchema', schemaName, version, loc: _createLoc(startToken, this.previous()) };
2668
- }
2669
-
2670
- private consumeBooleanLiteral(): boolean {
2671
- if (this.match(TokenType.TRUE)) return true;
2672
- if (this.match(TokenType.FALSE)) return false;
2673
- if (this.check(TokenType.STRING)) {
2674
- const t = this.advance();
2675
- const v = String(t.literal).toLowerCase();
2676
- return v === 'true' || v === '1';
2677
- }
2678
- if (this.check(TokenType.INTEGER)) {
2679
- const t = this.advance();
2680
- return Number(t.literal) !== 0;
2681
- }
2682
- return false;
2683
- }
2684
-
2685
- private sourceSlice(_start: number, _end: number): string {
2686
- // Lexer tokens include offsets; this.tokens array belongs to this parser, but we don't have direct source here.
2687
- // Return an empty string as placeholder; canonicalization is future work.
2688
- return '';
2689
- }
2690
-
2691
- private nameValueItem(context: string): { name: string, value?: AST.IdentifierExpr | AST.LiteralExpr } {
2692
- const name = this.consumeIdentifier(`Expected ${context} name.`);
2693
-
2694
- let value: AST.LiteralExpr | AST.IdentifierExpr | undefined;
2695
- if (this.match(TokenType.EQUAL)) {
2696
- if (this.check(TokenType.IDENTIFIER)) {
2697
- value = { type: 'identifier', name: this.getIdentifierValue(this.advance()) };
2698
- } else if (this.match(TokenType.STRING, TokenType.INTEGER, TokenType.FLOAT, TokenType.NULL, TokenType.TRUE, TokenType.FALSE)) {
2699
- const token = this.previous();
2700
- let literal_value: SqlValue;
2701
- if (token.type === TokenType.NULL) {
2702
- literal_value = null;
2703
- } else if (token.type === TokenType.TRUE) {
2704
- literal_value = 1;
2705
- } else if (token.type === TokenType.FALSE) {
2706
- literal_value = 0;
2707
- } else {
2708
- literal_value = token.literal;
2709
- }
2710
- value = { type: 'literal', value: literal_value };
2711
- } else if (this.match(TokenType.MINUS)) {
2712
- if (this.check(TokenType.INTEGER) || this.check(TokenType.FLOAT)) {
2713
- const token = this.advance();
2714
- value = { type: 'literal', value: -token.literal };
2715
- } else {
2716
- throw this.error(this.peek(), "Expected number after '-'.");
2717
- }
2718
- } else {
2719
- throw this.error(this.peek(), `Expected ${context} value (identifier, string, number, or NULL).`);
2720
- }
2721
- }
2722
- // If no '=' is found, value remains undefined (reading mode)
2723
-
2724
- return { name: name.toLowerCase(), value };
2725
- }
2726
-
2727
- // --- Supporting Clause / Definition Parsers ---
2728
-
2729
- /** @internal Parses a comma-separated list of indexed columns */
2730
- private indexedColumnList(): AST.IndexedColumn[] {
2731
- const columns: AST.IndexedColumn[] = [];
2732
- do {
2733
- columns.push(this.indexedColumn());
2734
- } while (this.match(TokenType.COMMA));
2735
- return columns;
2736
- }
2737
-
2738
- /** @internal Parses a single indexed column definition */
2739
- private indexedColumn(): AST.IndexedColumn {
2740
- const expr = this.expression();
2741
-
2742
- let name: string | undefined;
2743
- if (expr.type === 'column' && !expr.table && !expr.schema) {
2744
- name = expr.name;
2745
- }
2746
-
2747
- let direction: 'asc' | 'desc' | undefined;
2748
- if (this.match(TokenType.ASC)) {
2749
- direction = 'asc';
2750
- } else if (this.match(TokenType.DESC)) {
2751
- direction = 'desc';
2752
- }
2753
-
2754
- if (name) {
2755
- return { name, direction };
2756
- } else {
2757
- return { expr, direction };
2758
- }
2759
- }
2760
-
2761
- /**
2762
- * @internal Helper to extract the identifier value from a token.
2763
- * For quoted identifiers (double-quoted, backtick, bracket), returns the unquoted value.
2764
- * For unquoted identifiers, returns the lexeme.
2765
- */
2766
- private getIdentifierValue(token: Token): string {
2767
- return token.literal !== undefined ? String(token.literal) : token.lexeme;
2768
- }
2769
-
2770
- /** @internal Helper to consume an IDENTIFIER token and return its lexeme */
2771
- private consumeIdentifier(errorMessage: string): string;
2772
- private consumeIdentifier(availableKeywords: string[], errorMessage: string): string;
2773
- private consumeIdentifier(errorMessageOrKeywords: string | string[], errorMessage?: string): string {
2774
- if (typeof errorMessageOrKeywords === 'string') {
2775
- // Single parameter version - no contextual keywords
2776
- return this.consumeIdentifierOrContextualKeyword([], errorMessageOrKeywords);
2777
- } else {
2778
- // Two parameter version - with contextual keywords
2779
- return this.consumeIdentifierOrContextualKeyword(errorMessageOrKeywords, errorMessage!);
2780
- }
2781
- }
2782
-
2783
- /**
2784
- * @internal Helper to consume an IDENTIFIER token or specified contextual keywords
2785
- * @param availableKeywords Array of keyword strings that can be used as identifiers in this context
2786
- * @param errorMessage Error message if no valid token is found
2787
- * @returns The identifier value (unquoted for quoted identifiers)
2788
- */
2789
- private consumeIdentifierOrContextualKeyword(availableKeywords: string[], errorMessage: string): string {
2790
- const token = this.peek();
2791
-
2792
- // First check for regular identifier
2793
- if (this.check(TokenType.IDENTIFIER)) {
2794
- return this.getIdentifierValue(this.advance());
2795
- }
2796
-
2797
- // Then check for available contextual keywords
2798
- for (const keyword of availableKeywords) {
2799
- const keywordUpper = keyword.toUpperCase();
2800
- const expectedTokenType = TokenType[keywordUpper as keyof typeof TokenType];
2801
-
2802
- if (expectedTokenType && token.type === expectedTokenType) {
2803
- // This keyword token is available as an identifier in this context
2804
- return this.advance().lexeme;
2805
- }
2806
- }
2807
-
2808
- throw this.error(this.peek(), errorMessage);
2809
- }
2810
-
2811
- /**
2812
- * @internal Helper to check if current token is an identifier or available contextual keyword
2813
- */
2814
- private checkIdentifierLike(availableKeywords: string[] = []): boolean {
2815
- if (this.check(TokenType.IDENTIFIER)) {
2816
- return true;
2817
- }
2818
-
2819
- return this.isContextualKeywordAvailable(availableKeywords);
2820
- }
2821
-
2822
- /**
2823
- * @internal Helper to check if token at offset is an identifier or available contextual keyword
2824
- */
2825
- private checkIdentifierLikeAt(offset: number, availableKeywords: string[] = []): boolean {
2826
- if (this.checkNext(offset, TokenType.IDENTIFIER)) {
2827
- return true;
2828
- }
2829
-
2830
- if (this.current + offset >= this.tokens.length) return false;
2831
- const token = this.tokens[this.current + offset];
2832
-
2833
- for (const keyword of availableKeywords) {
2834
- const keywordUpper = keyword.toUpperCase();
2835
- const expectedTokenType = TokenType[keywordUpper as keyof typeof TokenType];
2836
-
2837
- if (expectedTokenType && token.type === expectedTokenType) {
2838
- return true;
2839
- }
2840
- }
2841
-
2842
- return false;
2843
- }
2844
-
2845
- /**
2846
- * @internal Helper to check if any of the specified contextual keywords are available at current position
2847
- */
2848
- private isContextualKeywordAvailable(availableKeywords: string[]): boolean {
2849
- const token = this.peek();
2850
-
2851
- for (const keyword of availableKeywords) {
2852
- const keywordUpper = keyword.toUpperCase();
2853
- const expectedTokenType = TokenType[keywordUpper as keyof typeof TokenType];
2854
-
2855
- if (expectedTokenType && token.type === expectedTokenType) {
2856
- return true;
2857
- }
2858
- }
2859
-
2860
- return false;
2861
- }
2862
-
2863
- // --- Stubs for required helpers (implement fully for CREATE TABLE) ---
2864
-
2865
- /** @internal Parses a column definition */
2866
- private columnDefinition(): AST.ColumnDef {
2867
- const name = this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'], "Expected column name.");
2868
-
2869
- let dataType: string | undefined;
2870
- if (this.check(TokenType.IDENTIFIER)) {
2871
- dataType = this.advance().lexeme;
2872
- if (this.match(TokenType.LPAREN)) {
2873
- dataType += '(';
2874
- let parenLevel = 1;
2875
- while (parenLevel > 0 && !this.isAtEnd()) {
2876
- const token = this.peek();
2877
- if (token.type === TokenType.LPAREN) parenLevel++;
2878
- if (token.type === TokenType.RPAREN) parenLevel--;
2879
- if (parenLevel > 0) {
2880
- dataType += this.advance().lexeme;
2881
- }
2882
- }
2883
- dataType += ')';
2884
- this.consume(TokenType.RPAREN, "Expected ')' after type parameters.");
2885
- }
2886
- }
2887
-
2888
- const constraints = this.columnConstraintList();
2889
-
2890
- return { name, dataType, constraints };
2891
- }
2892
-
2893
- /** @internal Parses mutation context variable definitions: WITH CONTEXT (var type [NULL], ...) */
2894
- private parseMutationContextDefinitions(): AST.MutationContextVar[] {
2895
- this.consume(TokenType.LPAREN, "Expected '(' after WITH CONTEXT.");
2896
-
2897
- const contextVars: AST.MutationContextVar[] = [];
2898
-
2899
- do {
2900
- const name = this.consumeIdentifier("Expected context variable name.");
2901
-
2902
- let dataType: string | undefined;
2903
- if (this.check(TokenType.IDENTIFIER)) {
2904
- dataType = this.advance().lexeme;
2905
- if (this.match(TokenType.LPAREN)) {
2906
- dataType += '(';
2907
- let parenLevel = 1;
2908
- while (parenLevel > 0 && !this.isAtEnd()) {
2909
- const token = this.peek();
2910
- if (token.type === TokenType.LPAREN) parenLevel++;
2911
- if (token.type === TokenType.RPAREN) parenLevel--;
2912
- if (parenLevel > 0) {
2913
- dataType += this.advance().lexeme;
2914
- }
2915
- }
2916
- dataType += ')';
2917
- this.consume(TokenType.RPAREN, "Expected ')' after type parameters.");
2918
- }
2919
- }
2920
-
2921
- // Check for NULL keyword (explicit nullable marker)
2922
- const notNull = !this.match(TokenType.NULL);
2923
-
2924
- contextVars.push({ name, dataType, notNull });
2925
-
2926
- } while (this.match(TokenType.COMMA));
2927
-
2928
- this.consume(TokenType.RPAREN, "Expected ')' after mutation context definitions.");
2929
-
2930
- return contextVars;
2931
- }
2932
-
2933
- /** @internal Parses mutation context assignments: WITH CONTEXT var = expr, ... */
2934
- private parseContextAssignments(): AST.ContextAssignment[] {
2935
- const assignments: AST.ContextAssignment[] = [];
2936
-
2937
- do {
2938
- const name = this.consumeIdentifier("Expected context variable name.");
2939
- this.consume(TokenType.EQUAL, `Expected '=' after context variable '${name}'.`);
2940
- const value = this.expression();
2941
-
2942
- assignments.push({ name, value });
2943
-
2944
- } while (this.match(TokenType.COMMA));
2945
-
2946
- return assignments;
2947
- }
2948
-
2949
- /** @internal Parses column constraints */
2950
- private columnConstraintList(): AST.ColumnConstraint[] {
2951
- const constraints: AST.ColumnConstraint[] = [];
2952
- while (this.isColumnConstraintStart()) {
2953
- constraints.push(this.columnConstraint());
2954
- }
2955
- return constraints;
2956
- }
2957
-
2958
- /** @internal Checks if the current token can start a column constraint */
2959
- private isColumnConstraintStart(): boolean {
2960
- return this.check(TokenType.CONSTRAINT) ||
2961
- this.check(TokenType.PRIMARY) ||
2962
- this.check(TokenType.NOT) ||
2963
- this.check(TokenType.NULL) ||
2964
- this.check(TokenType.UNIQUE) ||
2965
- this.check(TokenType.CHECK) ||
2966
- this.check(TokenType.DEFAULT) ||
2967
- this.check(TokenType.COLLATE) ||
2968
- this.check(TokenType.REFERENCES) ||
2969
- this.check(TokenType.GENERATED);
2970
- }
2971
-
2972
- /** @internal Parses a single column constraint */
2973
- private columnConstraint(): AST.ColumnConstraint {
2974
- let name: string | undefined;
2975
- const startToken = this.peek(); // Capture start token
2976
- let endToken = startToken; // Initialize end token
2977
-
2978
- if (this.match(TokenType.CONSTRAINT)) {
2979
- name = this.consumeIdentifier("Expected constraint name after CONSTRAINT.");
2980
- endToken = this.previous();
2981
- }
2982
-
2983
- if (this.match(TokenType.PRIMARY)) {
2984
- this.consume(TokenType.KEY, "Expected KEY after PRIMARY.");
2985
- const direction = this.match(TokenType.ASC) ? 'asc' : this.match(TokenType.DESC) ? 'desc' : undefined;
2986
- if (direction) endToken = this.previous();
2987
- const onConflict = this.parseConflictClause();
2988
- if (onConflict) endToken = this.previous(); // Update endToken if conflict clause was parsed
2989
- const autoincrement = this.match(TokenType.AUTOINCREMENT);
2990
- if (autoincrement) endToken = this.previous();
2991
- return { type: 'primaryKey', name, onConflict, autoincrement, direction, loc: _createLoc(startToken, endToken) };
2992
- } else if (this.match(TokenType.NOT)) {
2993
- this.consume(TokenType.NULL, "Expected NULL after NOT.");
2994
- endToken = this.previous();
2995
- const onConflict = this.parseConflictClause();
2996
- if (onConflict) endToken = this.previous(); // Update endToken if conflict clause was parsed
2997
- return { type: 'notNull', name, onConflict, loc: _createLoc(startToken, endToken) };
2998
- } else if (this.match(TokenType.NULL)) {
2999
- endToken = this.previous();
3000
- const onConflict = this.parseConflictClause();
3001
- if (onConflict) endToken = this.previous(); // Update endToken if conflict clause was parsed
3002
- return { type: 'null', name, onConflict, loc: _createLoc(startToken, endToken) };
3003
- } else if (this.match(TokenType.UNIQUE)) {
3004
- endToken = this.previous();
3005
- const onConflict = this.parseConflictClause();
3006
- if (onConflict) endToken = this.previous(); // Update endToken if conflict clause was parsed
3007
- return { type: 'unique', name, onConflict, loc: _createLoc(startToken, endToken) };
3008
- } else if (this.match(TokenType.CHECK)) {
3009
- // --- Parse optional ON clause before parentheses --- //
3010
- let operations: RowOp[] | undefined;
3011
- if (this.matchKeyword('ON')) {
3012
- operations = this.parseRowOpList();
3013
- }
3014
- // --- End Parse ON clause --- //
3015
- this.consume(TokenType.LPAREN, "Expected '(' after CHECK.");
3016
- const expr = this.expression();
3017
- endToken = this.consume(TokenType.RPAREN, "Expected ')' after CHECK expression.");
3018
- // No DEFERRABLE syntax supported; deferral is auto-detected by the planner
3019
- return {
3020
- type: 'check',
3021
- name,
3022
- expr,
3023
- operations,
3024
- loc: _createLoc(startToken, endToken)
3025
- };
3026
- } else if (this.match(TokenType.DEFAULT)) {
3027
- const expr = this.expression();
3028
- endToken = this.previous();
3029
- return { type: 'default', name, expr, loc: _createLoc(startToken, endToken) };
3030
- } else if (this.match(TokenType.COLLATE)) {
3031
- if (!this.check(TokenType.IDENTIFIER)) {
3032
- throw this.error(this.peek(), "Expected collation name after COLLATE.");
3033
- }
3034
- const collation = this.getIdentifierValue(this.advance());
3035
- endToken = this.previous();
3036
- return { type: 'collate', name, collation, loc: _createLoc(startToken, endToken) };
3037
- } else if (this.match(TokenType.REFERENCES)) {
3038
- const fkClause = this.foreignKeyClause();
3039
- endToken = this.previous(); // End token is end of FK clause
3040
- return { type: 'foreignKey', name, foreignKey: fkClause, loc: _createLoc(startToken, endToken) };
3041
- } else if (this.match(TokenType.GENERATED)) {
3042
- this.consume(TokenType.ALWAYS, "Expected ALWAYS after GENERATED.");
3043
- this.consume(TokenType.AS, "Expected AS after GENERATED ALWAYS.");
3044
- this.consume(TokenType.LPAREN, "Expected '(' after AS.");
3045
- const expr = this.expression();
3046
- this.consume(TokenType.RPAREN, "Expected ')' after generated expression.");
3047
- endToken = this.previous();
3048
- let stored = false;
3049
- if (this.match(TokenType.STORED)) {
3050
- stored = true;
3051
- endToken = this.previous();
3052
- } else if (this.match(TokenType.VIRTUAL)) {
3053
- endToken = this.previous();
3054
- }
3055
- return { type: 'generated', name, generated: { expr, stored }, loc: _createLoc(startToken, endToken) };
3056
- }
3057
-
3058
- throw this.error(this.peek(), "Expected column constraint type (PRIMARY KEY, NOT NULL, UNIQUE, CHECK, DEFAULT, COLLATE, REFERENCES, GENERATED).");
3059
- }
3060
-
3061
- /** @internal Parses a table constraint */
3062
- private tableConstraint(): AST.TableConstraint {
3063
- let name: string | undefined;
3064
- const startToken = this.peek(); // Capture start token
3065
- let endToken = startToken; // Initialize end token
3066
-
3067
- if (this.match(TokenType.CONSTRAINT)) {
3068
- name = this.consumeIdentifier("Expected constraint name after CONSTRAINT.");
3069
- endToken = this.previous();
3070
- }
3071
-
3072
- if (this.match(TokenType.PRIMARY)) {
3073
- this.consume(TokenType.KEY, "Expected KEY after PRIMARY.");
3074
- this.consume(TokenType.LPAREN, "Expected '(' before PRIMARY KEY columns.");
3075
-
3076
- // Handle empty PRIMARY KEY () for singleton tables (Third Manifesto feature)
3077
- let columns: { name: string; direction?: 'asc' | 'desc' }[] = [];
3078
- if (!this.check(TokenType.RPAREN)) {
3079
- columns = this.identifierListWithDirection();
3080
- }
3081
-
3082
- endToken = this.consume(TokenType.RPAREN, "Expected ')' after PRIMARY KEY columns.");
3083
- const onConflict = this.parseConflictClause();
3084
- if (onConflict) endToken = this.previous();
3085
- return { type: 'primaryKey', name, columns, onConflict, loc: _createLoc(startToken, endToken) };
3086
- } else if (this.match(TokenType.UNIQUE)) {
3087
- this.consume(TokenType.LPAREN, "Expected '(' before UNIQUE columns.");
3088
- const columnsSimple = this.identifierList();
3089
- const columns = columnsSimple.map(name => ({ name }));
3090
- endToken = this.consume(TokenType.RPAREN, "Expected ')' after UNIQUE columns.");
3091
- const onConflict = this.parseConflictClause();
3092
- if (onConflict) endToken = this.previous();
3093
- return { type: 'unique', name, columns, onConflict, loc: _createLoc(startToken, endToken) };
3094
- } else if (this.match(TokenType.CHECK)) {
3095
- // --- Parse optional ON clause before parentheses --- //
3096
- let operations: RowOp[] | undefined;
3097
- if (this.matchKeyword('ON')) {
3098
- operations = this.parseRowOpList();
3099
- }
3100
- // --- End Parse ON clause --- //
3101
- this.consume(TokenType.LPAREN, "Expected '(' after CHECK.");
3102
- const expr = this.expression();
3103
- endToken = this.consume(TokenType.RPAREN, "Expected ')' after CHECK expression.");
3104
- // No DEFERRABLE syntax supported; deferral is auto-detected by the planner
3105
- return {
3106
- type: 'check',
3107
- name,
3108
- expr,
3109
- operations,
3110
- loc: _createLoc(startToken, endToken)
3111
- };
3112
- } else if (this.match(TokenType.FOREIGN)) {
3113
- this.consume(TokenType.KEY, "Expected KEY after FOREIGN.");
3114
- this.consume(TokenType.LPAREN, "Expected '(' before FOREIGN KEY columns.");
3115
- const columns = this.identifierList().map(name => ({ name }));
3116
- this.consume(TokenType.RPAREN, "Expected ')' after FOREIGN KEY columns.");
3117
- const fkClause = this.foreignKeyClause();
3118
- endToken = this.previous(); // End token is end of FK clause
3119
- return { type: 'foreignKey', name, columns, foreignKey: fkClause, loc: _createLoc(startToken, endToken) };
3120
- }
3121
-
3122
- throw this.error(this.peek(), "Expected table constraint type (PRIMARY KEY, UNIQUE, CHECK, FOREIGN KEY).");
3123
- }
3124
-
3125
- /** @internal Parses a foreign key clause */
3126
- private foreignKeyClause(): AST.ForeignKeyClause {
3127
- this.consume(TokenType.REFERENCES, "Expected REFERENCES for foreign key.");
3128
- const table = this.consumeIdentifier("Expected foreign table name.");
3129
- let columns: string[] | undefined;
3130
- if (this.match(TokenType.LPAREN)) {
3131
- columns = this.identifierList();
3132
- this.consume(TokenType.RPAREN, "Expected ')' after foreign columns.");
3133
- }
3134
-
3135
- let onDelete: AST.ForeignKeyAction | undefined;
3136
- let onUpdate: AST.ForeignKeyAction | undefined;
3137
- let deferrable: boolean | undefined;
3138
- let initiallyDeferred: boolean | undefined;
3139
-
3140
- while (this.check(TokenType.ON) || this.check(TokenType.DEFERRABLE) || this.check(TokenType.NOT)) {
3141
- if (this.match(TokenType.ON)) {
3142
- if (this.match(TokenType.DELETE)) {
3143
- onDelete = this.parseForeignKeyAction();
3144
- } else if (this.match(TokenType.UPDATE)) {
3145
- onUpdate = this.parseForeignKeyAction();
3146
- } else {
3147
- throw this.error(this.peek(), "Expected DELETE or UPDATE after ON.");
3148
- }
3149
- } else if (this.match(TokenType.DEFERRABLE)) {
3150
- deferrable = true;
3151
- if (this.match(TokenType.INITIALLY)) {
3152
- if (this.match(TokenType.DEFERRED)) {
3153
- initiallyDeferred = true;
3154
- } else if (this.match(TokenType.IMMEDIATE)) {
3155
- initiallyDeferred = false;
3156
- } else {
3157
- throw this.error(this.peek(), "Expected DEFERRED or IMMEDIATE after INITIALLY.");
3158
- }
3159
- }
3160
- } else if (this.match(TokenType.NOT)) {
3161
- this.consume(TokenType.DEFERRABLE, "Expected DEFERRABLE after NOT.");
3162
- deferrable = false;
3163
- if (this.match(TokenType.INITIALLY)) {
3164
- if (this.match(TokenType.DEFERRED)) {
3165
- initiallyDeferred = true;
3166
- } else if (this.match(TokenType.IMMEDIATE)) {
3167
- initiallyDeferred = false;
3168
- } else {
3169
- throw this.error(this.peek(), "Expected DEFERRED or IMMEDIATE after INITIALLY.");
3170
- }
3171
- }
3172
- } else {
3173
- break;
3174
- }
3175
- }
3176
-
3177
- return { table, columns, onDelete, onUpdate, deferrable, initiallyDeferred };
3178
- }
3179
-
3180
- /** @internal Parses the ON CONFLICT clause */
3181
- private parseConflictClause(): ConflictResolution | undefined {
3182
- if (this.match(TokenType.ON)) {
3183
- this.consume(TokenType.CONFLICT, "Expected CONFLICT after ON.");
3184
- if (this.match(TokenType.ROLLBACK)) return ConflictResolution.ROLLBACK;
3185
- if (this.match(TokenType.ABORT)) return ConflictResolution.ABORT;
3186
- if (this.match(TokenType.FAIL)) return ConflictResolution.FAIL;
3187
- if (this.match(TokenType.IGNORE)) return ConflictResolution.IGNORE;
3188
- if (this.match(TokenType.REPLACE)) return ConflictResolution.REPLACE;
3189
- throw this.error(this.peek(), "Expected conflict resolution algorithm (ROLLBACK, ABORT, FAIL, IGNORE, REPLACE).");
3190
- }
3191
- return undefined;
3192
- }
3193
-
3194
- /** @internal Parses the foreign key action */
3195
- private parseForeignKeyAction(): AST.ForeignKeyAction {
3196
- if (this.match(TokenType.SET)) {
3197
- if (this.match(TokenType.NULL)) return 'setNull';
3198
- if (this.match(TokenType.DEFAULT)) return 'setDefault';
3199
- throw this.error(this.peek(), "Expected NULL or DEFAULT after SET.");
3200
- } else if (this.match(TokenType.CASCADE)) {
3201
- return 'cascade';
3202
- } else if (this.match(TokenType.RESTRICT)) {
3203
- return 'restrict';
3204
- } else if (this.match(TokenType.NO)) {
3205
- this.consume(TokenType.ACTION, "Expected ACTION after NO.");
3206
- return 'noAction';
3207
- }
3208
- throw this.error(this.peek(), "Expected foreign key action (SET NULL, SET DEFAULT, CASCADE, RESTRICT, NO ACTION).");
3209
- }
3210
-
3211
- /** @internal Parses a comma-separated list of identifiers, optionally with ASC/DESC */
3212
- private identifierList(): string[] {
3213
- const identifiers: string[] = [];
3214
- do {
3215
- identifiers.push(this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'], "Expected identifier in list."));
3216
- } while (this.match(TokenType.COMMA));
3217
- return identifiers;
3218
- }
3219
-
3220
- /** @internal Parses a comma-separated list of identifiers, optionally with ASC/DESC */
3221
- private identifierListWithDirection(): { name: string; direction?: 'asc' | 'desc' }[] {
3222
- const identifiers: { name: string; direction?: 'asc' | 'desc' }[] = [];
3223
- do {
3224
- const name = this.consumeIdentifier(['key', 'action', 'set', 'default', 'check', 'unique', 'references', 'on', 'cascade', 'restrict', 'like'], "Expected identifier in list.");
3225
- const direction = this.match(TokenType.ASC) ? 'asc' : this.match(TokenType.DESC) ? 'desc' : undefined;
3226
- identifiers.push({ name, direction });
3227
- } while (this.match(TokenType.COMMA));
3228
- return identifiers;
3229
- }
3230
-
3231
- // --- Helper method to peek keywords case-insensitively ---
3232
- private peekKeyword(keyword: string): boolean {
3233
- if (this.isAtEnd()) return false;
3234
- const token = this.peek();
3235
-
3236
- // The keyword lookup string should be uppercase to match TokenType enum keys (e.g., TokenType.SELECT)
3237
- const keywordKey = keyword.toUpperCase();
3238
- const expectedTokenType = TokenType[keywordKey as keyof typeof TokenType];
3239
-
3240
- // Check if the current token's type is the expected specific keyword TokenType.
3241
- // This assumes the lexer has already correctly typed true keywords.
3242
- if (expectedTokenType !== undefined && token.type === expectedTokenType) {
3243
- return true;
3244
- }
3245
-
3246
- // Fallback: if the token is a generic IDENTIFIER, check if its lexeme matches the keyword.
3247
- // This handles contextual keywords like FIRST, LAST that aren't reserved keywords.
3248
- if (token.type === TokenType.IDENTIFIER && token.lexeme.toUpperCase() === keywordKey) {
3249
- return true;
3250
- }
3251
-
3252
- return false;
3253
- }
3254
-
3255
- // --- Helper method to match keywords case-insensitively ---
3256
- private matchKeyword(keyword: string): boolean {
3257
- if (this.isAtEnd()) return false;
3258
- if (this.peekKeyword(keyword)) {
3259
- this.advance();
3260
- return true;
3261
- }
3262
- return false;
3263
- }
3264
-
3265
- // --- Helper method to consume keywords case-insensitively ---
3266
- private consumeKeyword(keyword: string, message: string): Token {
3267
- if (this.peekKeyword(keyword)) {
3268
- return this.advance();
3269
- }
3270
- throw this.error(this.peek(), message);
3271
- }
3272
-
3273
- /** Parses the list of operations for CHECK ON */
3274
- private parseRowOpList(): RowOp[] {
3275
- const operations: RowOp[] = [];
3276
-
3277
- // Parse operations in a comma-separated list
3278
- do {
3279
- if (this.match(TokenType.INSERT)) {
3280
- operations.push('insert');
3281
- } else if (this.match(TokenType.UPDATE)) {
3282
- operations.push('update');
3283
- } else if (this.match(TokenType.DELETE)) {
3284
- operations.push('delete');
3285
- } else {
3286
- throw this.error(this.peek(), "Expected INSERT, UPDATE, or DELETE after ON.");
3287
- }
3288
- } while (this.match(TokenType.COMMA));
3289
-
3290
- // Optional: Check for duplicates? The design allows them but ignores them.
3291
- return operations;
3292
- }
3293
-
3294
- /**
3295
- * Parses a CASE expression
3296
- * CASE [base_expr] WHEN cond THEN result ... [ELSE else_result] END
3297
- * CASE WHEN cond THEN result ... [ELSE else_result] END
3298
- */
3299
- private parseCaseExpression(startToken: Token): AST.CaseExpr {
3300
- let baseExpr: AST.Expression | undefined;
3301
- const whenThenClauses: AST.CaseExprWhenThenClause[] = [];
3302
- let elseExpr: AST.Expression | undefined;
3303
- let endToken = startToken; // Initialize with CASE token
3304
-
3305
- // Check if it's CASE expr WHEN ... or CASE WHEN ...
3306
- if (!this.peekKeyword('WHEN')) { // Changed from checkKeyword
3307
- baseExpr = this.expression();
3308
- }
3309
-
3310
- while (this.matchKeyword('WHEN')) {
3311
- const whenCondition = this.expression();
3312
- this.consumeKeyword('THEN', "Expected 'THEN' after WHEN condition in CASE expression.");
3313
- const thenResult = this.expression();
3314
- whenThenClauses.push({ when: whenCondition, then: thenResult });
3315
- endToken = this.previous(); // Update endToken to the end of the THEN expression
3316
- }
3317
-
3318
- if (whenThenClauses.length === 0) {
3319
- throw this.error(this.peek(), "CASE expression must have at least one WHEN clause.");
3320
- }
3321
-
3322
- if (this.matchKeyword('ELSE')) {
3323
- elseExpr = this.expression();
3324
- endToken = this.previous(); // Update endToken to the end of the ELSE expression
3325
- }
3326
-
3327
- endToken = this.consumeKeyword('END', "Expected 'END' to terminate CASE expression.");
3328
-
3329
- return {
3330
- type: 'case',
3331
- baseExpr,
3332
- whenThenClauses,
3333
- elseExpr,
3334
- loc: _createLoc(startToken, endToken),
3335
- };
3336
- }
3337
-
3338
- // Helper to check if a token lexeme is a common type name keyword for CAST
3339
- private isTypeNameKeyword(lexeme: string): boolean {
3340
- const typeKeywords = ['TEXT', 'INTEGER', 'REAL', 'BLOB', 'NUMERIC', 'VARCHAR', 'CHAR', 'DATE', 'DATETIME', 'BOOLEAN', 'INT'];
3341
- return typeKeywords.includes(lexeme.toUpperCase());
3342
- }
3343
-
3344
- private statementSupportsWithClause(statement: AST.AstNode): boolean {
3345
- return statement.type === 'select' ||
3346
- statement.type === 'insert' ||
3347
- statement.type === 'update' ||
3348
- statement.type === 'delete';
3349
- }
3350
-
3351
- // DEFERRABLE syntax not supported for CHECK constraints in Quereus.
3352
- }