rubydb 0.1.4 → 0.1.6

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 (469) hide show
  1. checksums.yaml +4 -4
  2. data/.github/PULL_REQUEST_TEMPLATE.md +15 -15
  3. data/.github/workflows/benchmark.yml +26 -26
  4. data/.github/workflows/compatibility.yml +63 -63
  5. data/.github/workflows/fuzz.yml +33 -33
  6. data/.github/workflows/lint.yml +21 -21
  7. data/.github/workflows/operations.yml +24 -24
  8. data/.github/workflows/production-validation.yml +111 -111
  9. data/.github/workflows/release.yml +77 -77
  10. data/.github/workflows/security.yml +39 -37
  11. data/.github/workflows/test.yml +26 -26
  12. data/.github/workflows/workload.yml +58 -58
  13. data/.gitignore +16 -5
  14. data/.rubocop.yml +50 -44
  15. data/.standard.yml +9 -14
  16. data/ARCHITECTURE.md +21 -21
  17. data/CHANGELOG.md +57 -27
  18. data/CODE_OF_CONDUCT.md +13 -13
  19. data/CONTRIBUTING.md +29 -29
  20. data/GOVERNANCE.md +16 -16
  21. data/Gemfile +18 -17
  22. data/Gemfile.lock +125 -71
  23. data/README.md +168 -12
  24. data/ROADMAP.md +27 -27
  25. data/Rakefile +76 -71
  26. data/SECURITY.md +54 -54
  27. data/SUPPORT.md +14 -14
  28. data/accelerator/bin/SHA256SUMS +6 -0
  29. data/accelerator/bin/rubydb-accelerator-darwin-amd64 +0 -0
  30. data/accelerator/bin/rubydb-accelerator-darwin-arm64 +0 -0
  31. data/accelerator/bin/rubydb-accelerator-linux-amd64 +0 -0
  32. data/accelerator/bin/rubydb-accelerator-linux-arm64 +0 -0
  33. data/accelerator/bin/rubydb-accelerator-windows-amd64.exe +0 -0
  34. data/accelerator/bin/rubydb-accelerator-windows-arm64.exe +0 -0
  35. data/accelerator/cmd/rubydb-accelerator/main.go +11 -0
  36. data/accelerator/go.mod +3 -0
  37. data/accelerator/internal/execution/aggregate.go +94 -0
  38. data/accelerator/internal/execution/distinct.go +22 -0
  39. data/accelerator/internal/execution/filter.go +73 -0
  40. data/accelerator/internal/execution/join.go +79 -0
  41. data/accelerator/internal/execution/operators.go +167 -0
  42. data/accelerator/internal/execution/scan.go +20 -0
  43. data/accelerator/internal/execution/sort.go +62 -0
  44. data/accelerator/internal/execution/types.go +136 -0
  45. data/accelerator/internal/execution/value.go +67 -0
  46. data/accelerator/internal/memory/arena.go +47 -0
  47. data/accelerator/internal/memory/reuse.go +22 -0
  48. data/accelerator/internal/metrics/registry.go +67 -0
  49. data/accelerator/internal/parallel/bounded_queue.go +56 -0
  50. data/accelerator/internal/parallel/scheduler.go +47 -0
  51. data/accelerator/internal/parallel/worker_pool.go +53 -0
  52. data/accelerator/internal/protocol/cancellation.go +48 -0
  53. data/accelerator/internal/protocol/columnar.go +263 -0
  54. data/accelerator/internal/protocol/frame.go +187 -0
  55. data/accelerator/internal/runtime/worker.go +521 -0
  56. data/accelerator/internal/storage/page_reader.go +81 -0
  57. data/accelerator/internal/storage/snapshot_scan.go +539 -0
  58. data/accelerator/internal/wal/checksum.go +13 -0
  59. data/accelerator/internal/wal/compression.go +41 -0
  60. data/accelerator/internal/wal/group_commit.go +24 -0
  61. data/accelerator/internal/wal/record_encoder.go +40 -0
  62. data/adapters/activerecord/Gemfile +11 -11
  63. data/adapters/activerecord/README.md +8 -3
  64. data/adapters/activerecord/lib/active_record/connection_adapters/rubydb_adapter.rb +881 -879
  65. data/adapters/activerecord/rubydb-activerecord.gemspec +21 -21
  66. data/adapters/activerecord/spec/rubydb_adapter_integration_spec.rb +143 -143
  67. data/adapters/ruby/README.md +18 -18
  68. data/adapters/sequel/README.md +11 -11
  69. data/config/monitoring/prometheus-alerts.yml +39 -39
  70. data/config/production.yml +36 -36
  71. data/docs/README.md +77 -71
  72. data/docs/architecture/concurrency.md +14 -14
  73. data/docs/architecture/current-state.md +125 -125
  74. data/docs/architecture/execution-engine.md +25 -25
  75. data/docs/architecture/go-accelerator.md +179 -0
  76. data/docs/architecture/indexes.md +19 -19
  77. data/docs/architecture/mvcc.md +19 -19
  78. data/docs/architecture/overview.md +13 -13
  79. data/docs/architecture/pages.md +11 -11
  80. data/docs/architecture/production-roadmap.md +82 -82
  81. data/docs/architecture/query-planner.md +20 -20
  82. data/docs/architecture/recovery.md +18 -18
  83. data/docs/architecture/sql-engine.md +12 -12
  84. data/docs/architecture/storage-engine.md +14 -14
  85. data/docs/architecture/transactions.md +10 -10
  86. data/docs/architecture/wal.md +28 -28
  87. data/docs/cli-cheatsheet.md +98 -98
  88. data/docs/cli.md +299 -275
  89. data/docs/contributing/architecture.md +9 -9
  90. data/docs/contributing/benchmarking.md +30 -14
  91. data/docs/contributing/development.md +16 -16
  92. data/docs/contributing/release-process.md +49 -49
  93. data/docs/contributing/testing.md +16 -16
  94. data/docs/debugging.md +229 -229
  95. data/docs/developer/branching.md +10 -10
  96. data/docs/developer/database-diff.md +10 -10
  97. data/docs/developer/local-development.md +49 -17
  98. data/docs/developer/snapshots.md +9 -9
  99. data/docs/developer/temporal-data.md +10 -10
  100. data/docs/developer-guide.md +297 -297
  101. data/docs/getting-started/first-database.md +16 -16
  102. data/docs/getting-started/first-query.md +13 -13
  103. data/docs/getting-started/installation.md +19 -19
  104. data/docs/getting-started/local-to-production.md +300 -300
  105. data/docs/getting-started/quickstart.md +17 -17
  106. data/docs/getting-started/rails.md +16 -16
  107. data/docs/hardening_backlog.md +93 -93
  108. data/docs/lessons-learned.md +112 -112
  109. data/docs/operations/backups.md +33 -33
  110. data/docs/operations/disaster-recovery.md +31 -31
  111. data/docs/operations/failover.md +30 -30
  112. data/docs/operations/monitoring.md +25 -25
  113. data/docs/operations/production-guide.md +295 -295
  114. data/docs/operations/production-runbook.md +45 -45
  115. data/docs/operations/replication.md +33 -33
  116. data/docs/operations/restore.md +6 -6
  117. data/docs/operations/runbook.md +34 -34
  118. data/docs/operations/upgrades.md +14 -14
  119. data/docs/operations/workload-testing.md +17 -17
  120. data/docs/production-readiness.md +118 -118
  121. data/docs/production_validation.md +150 -150
  122. data/docs/rails/active-record.md +11 -11
  123. data/docs/rails/compatibility-guide.md +90 -90
  124. data/docs/rails/database-yml.md +92 -92
  125. data/docs/rails/installation.md +17 -17
  126. data/docs/rails/migrations.md +17 -17
  127. data/docs/rails/production.md +82 -82
  128. data/docs/rails/troubleshooting.md +18 -18
  129. data/docs/release.md +25 -25
  130. data/docs/server/architecture.md +10 -10
  131. data/docs/server/authentication.md +10 -10
  132. data/docs/server/configuration.md +16 -16
  133. data/docs/server/connection-pooling.md +10 -10
  134. data/docs/server/deployment.md +10 -10
  135. data/docs/server/protocol.md +12 -12
  136. data/docs/sql/compatibility-guide.md +82 -82
  137. data/docs/sql/compatibility.md +39 -39
  138. data/docs/sql/data-types.md +10 -10
  139. data/docs/sql/functions.md +9 -9
  140. data/docs/sql/joins.md +9 -9
  141. data/docs/sql/operators.md +9 -9
  142. data/docs/sql/sqlite-compatibility.md +21 -21
  143. data/docs/sql/syntax.md +10 -10
  144. data/docs/sql/transactions.md +10 -10
  145. data/docs/troubleshooting.md +244 -244
  146. data/lessons/01-foundations.md +73 -0
  147. data/lessons/02-local-development.md +121 -0
  148. data/lessons/03-embedded-rubydb.md +99 -0
  149. data/lessons/04-rails-complex-apps.md +138 -0
  150. data/lessons/05-rubydb-production-server.md +237 -0
  151. data/lessons/06-postgresql-massive-apps.md +96 -0
  152. data/lessons/07-hybrid-microservices.md +179 -0
  153. data/lessons/08-migrations-backups-recovery.md +86 -0
  154. data/lessons/09-observability-security-scale.md +87 -0
  155. data/lessons/10-release-readiness.md +192 -0
  156. data/lessons/11-community-adapter.md +323 -0
  157. data/lessons/12-rails-ecommerce-pressure.md +263 -0
  158. data/lib/rubydb/accelerator/client.rb +451 -0
  159. data/lib/rubydb/accelerator/error.rb +22 -0
  160. data/lib/rubydb/accelerator/manager.rb +606 -0
  161. data/lib/rubydb/accelerator.rb +13 -0
  162. data/lib/rubydb/backup/archive.rb +332 -334
  163. data/lib/rubydb/backup/backup.rb +400 -401
  164. data/lib/rubydb/backup/incremental.rb +349 -353
  165. data/lib/rubydb/backup/restore.rb +289 -290
  166. data/lib/rubydb/backup/snapshot.rb +265 -267
  167. data/lib/rubydb/backup/verification.rb +276 -279
  168. data/lib/rubydb/branching/branch.rb +181 -181
  169. data/lib/rubydb/branching/branch_manager.rb +307 -311
  170. data/lib/rubydb/branching/branch_metadata.rb +140 -140
  171. data/lib/rubydb/branching/checkout.rb +165 -166
  172. data/lib/rubydb/branching/copy_on_write.rb +272 -272
  173. data/lib/rubydb/branching/diff.rb +137 -138
  174. data/lib/rubydb/branching/merge.rb +282 -285
  175. data/lib/rubydb/build_info.rb +15 -15
  176. data/lib/rubydb/catalog/catalog.rb +391 -391
  177. data/lib/rubydb/catalog/column.rb +112 -112
  178. data/lib/rubydb/catalog/constraint.rb +180 -180
  179. data/lib/rubydb/catalog/database.rb +184 -184
  180. data/lib/rubydb/catalog/index.rb +97 -97
  181. data/lib/rubydb/catalog/schema.rb +103 -103
  182. data/lib/rubydb/catalog/sequence.rb +90 -90
  183. data/lib/rubydb/catalog/system_catalog.rb +698 -698
  184. data/lib/rubydb/catalog/table.rb +178 -178
  185. data/lib/rubydb/catalog/trigger.rb +102 -102
  186. data/lib/rubydb/catalog/view.rb +66 -66
  187. data/lib/rubydb/cli/application.rb +168 -163
  188. data/lib/rubydb/cli/commands/accelerator.rb +72 -0
  189. data/lib/rubydb/cli/commands/backup.rb +80 -81
  190. data/lib/rubydb/cli/commands/branch.rb +72 -72
  191. data/lib/rubydb/cli/commands/checkout.rb +54 -54
  192. data/lib/rubydb/cli/commands/create.rb +58 -58
  193. data/lib/rubydb/cli/commands/diff.rb +76 -77
  194. data/lib/rubydb/cli/commands/doctor.rb +77 -74
  195. data/lib/rubydb/cli/commands/drop.rb +57 -57
  196. data/lib/rubydb/cli/commands/init.rb +101 -102
  197. data/lib/rubydb/cli/commands/inspect.rb +95 -95
  198. data/lib/rubydb/cli/commands/merge.rb +63 -63
  199. data/lib/rubydb/cli/commands/migrate.rb +62 -62
  200. data/lib/rubydb/cli/commands/restart.rb +42 -39
  201. data/lib/rubydb/cli/commands/restore.rb +121 -121
  202. data/lib/rubydb/cli/commands/shell.rb +365 -365
  203. data/lib/rubydb/cli/commands/snapshot.rb +79 -79
  204. data/lib/rubydb/cli/commands/start.rb +88 -82
  205. data/lib/rubydb/cli/commands/status.rb +96 -92
  206. data/lib/rubydb/cli/commands/stop.rb +47 -47
  207. data/lib/rubydb/cli/commands/vacuum.rb +58 -58
  208. data/lib/rubydb/cli/formatter.rb +221 -221
  209. data/lib/rubydb/cli/output.rb +168 -168
  210. data/lib/rubydb/client/client.rb +309 -304
  211. data/lib/rubydb/client/connection.rb +429 -415
  212. data/lib/rubydb/client/connection_pool.rb +168 -168
  213. data/lib/rubydb/client/connection_url.rb +96 -96
  214. data/lib/rubydb/client/prepared_statement.rb +60 -60
  215. data/lib/rubydb/client/result.rb +127 -123
  216. data/lib/rubydb/client/statement.rb +52 -52
  217. data/lib/rubydb/client/transaction.rb +130 -130
  218. data/lib/rubydb/concurrency/concurrency.rb +19 -19
  219. data/lib/rubydb/concurrency/deadlock_detector.rb +148 -150
  220. data/lib/rubydb/concurrency/latch.rb +101 -101
  221. data/lib/rubydb/concurrency/lock_graph.rb +163 -165
  222. data/lib/rubydb/concurrency/mutex.rb +181 -183
  223. data/lib/rubydb/concurrency/rw_lock.rb +180 -180
  224. data/lib/rubydb/concurrency/scheduler.rb +248 -250
  225. data/lib/rubydb/concurrency/worker_pool.rb +145 -143
  226. data/lib/rubydb/configuration/config.rb +170 -170
  227. data/lib/rubydb/configuration/defaults.rb +191 -179
  228. data/lib/rubydb/configuration/environment.rb +152 -152
  229. data/lib/rubydb/configuration/parser.rb +185 -185
  230. data/lib/rubydb/configuration/validation.rb +228 -221
  231. data/lib/rubydb/constants.rb +74 -74
  232. data/lib/rubydb/constraints/check.rb +181 -181
  233. data/lib/rubydb/constraints/constraint.rb +101 -101
  234. data/lib/rubydb/constraints/foreign_key.rb +130 -130
  235. data/lib/rubydb/constraints/not_null.rb +64 -64
  236. data/lib/rubydb/constraints/primary_key.rb +99 -99
  237. data/lib/rubydb/constraints/unique.rb +106 -108
  238. data/lib/rubydb/constraints/validator.rb +349 -350
  239. data/lib/rubydb/errors/authentication_error.rb +10 -10
  240. data/lib/rubydb/errors/authorization_error.rb +23 -23
  241. data/lib/rubydb/errors/client_error.rb +10 -10
  242. data/lib/rubydb/errors/configuration_error.rb +10 -10
  243. data/lib/rubydb/errors/connection_error.rb +10 -10
  244. data/lib/rubydb/errors/constraint_error.rb +23 -23
  245. data/lib/rubydb/errors/corruption_error.rb +10 -10
  246. data/lib/rubydb/errors/database_error.rb +10 -10
  247. data/lib/rubydb/errors/error.rb +20 -20
  248. data/lib/rubydb/errors/execution_error.rb +10 -10
  249. data/lib/rubydb/errors/parser_error.rb +10 -10
  250. data/lib/rubydb/errors/recovery_error.rb +10 -10
  251. data/lib/rubydb/errors/replication_error.rb +10 -10
  252. data/lib/rubydb/errors/server_error.rb +6 -6
  253. data/lib/rubydb/errors/storage_error.rb +10 -10
  254. data/lib/rubydb/errors/transaction_error.rb +10 -10
  255. data/lib/rubydb/execution/accelerator_dispatch.rb +30 -0
  256. data/lib/rubydb/execution/aggregate_executor.rb +134 -138
  257. data/lib/rubydb/execution/cost_model.rb +72 -0
  258. data/lib/rubydb/execution/delete_executor.rb +110 -112
  259. data/lib/rubydb/execution/distinct_executor.rb +131 -135
  260. data/lib/rubydb/execution/executor.rb +1544 -1188
  261. data/lib/rubydb/execution/expression.rb +191 -193
  262. data/lib/rubydb/execution/index_scan.rb +142 -142
  263. data/lib/rubydb/execution/insert_executor.rb +215 -217
  264. data/lib/rubydb/execution/join_executor.rb +243 -249
  265. data/lib/rubydb/execution/limit_executor.rb +83 -85
  266. data/lib/rubydb/execution/operator_selection.rb +57 -0
  267. data/lib/rubydb/execution/optimizer.rb +227 -215
  268. data/lib/rubydb/execution/physical_plan.rb +47 -0
  269. data/lib/rubydb/execution/plan.rb +355 -353
  270. data/lib/rubydb/execution/planner.rb +508 -536
  271. data/lib/rubydb/execution/predicate.rb +235 -235
  272. data/lib/rubydb/execution/scan.rb +49 -49
  273. data/lib/rubydb/execution/sequential_scan.rb +63 -63
  274. data/lib/rubydb/execution/sort_executor.rb +194 -185
  275. data/lib/rubydb/execution/update_executor.rb +160 -162
  276. data/lib/rubydb/functions/aggregate.rb +70 -70
  277. data/lib/rubydb/functions/date_functions.rb +274 -278
  278. data/lib/rubydb/functions/function.rb +85 -85
  279. data/lib/rubydb/functions/json_functions.rb +231 -215
  280. data/lib/rubydb/functions/numeric_functions.rb +346 -346
  281. data/lib/rubydb/functions/scalar.rb +52 -52
  282. data/lib/rubydb/functions/string_functions.rb +383 -383
  283. data/lib/rubydb/functions/system_functions.rb +258 -246
  284. data/lib/rubydb/history/as_of.rb +238 -238
  285. data/lib/rubydb/history/change.rb +105 -105
  286. data/lib/rubydb/history/history.rb +131 -131
  287. data/lib/rubydb/history/history_manager.rb +228 -229
  288. data/lib/rubydb/history/temporal_query.rb +202 -202
  289. data/lib/rubydb/history/timeline.rb +144 -144
  290. data/lib/rubydb/indexes/btree.rb +215 -186
  291. data/lib/rubydb/indexes/btree_cursor.rb +258 -258
  292. data/lib/rubydb/indexes/btree_node.rb +384 -385
  293. data/lib/rubydb/indexes/hash_index.rb +150 -150
  294. data/lib/rubydb/indexes/index.rb +71 -71
  295. data/lib/rubydb/indexes/index_manager.rb +408 -406
  296. data/lib/rubydb/indexes/index_scan.rb +466 -470
  297. data/lib/rubydb/migrations/migration.rb +253 -254
  298. data/lib/rubydb/migrations/migration_lock.rb +146 -146
  299. data/lib/rubydb/migrations/migration_manager.rb +187 -176
  300. data/lib/rubydb/migrations/migration_version.rb +71 -71
  301. data/lib/rubydb/migrations/schema_diff.rb +211 -211
  302. data/lib/rubydb/migrations/schema_version.rb +64 -64
  303. data/lib/rubydb/monitoring/events.rb +155 -160
  304. data/lib/rubydb/monitoring/health.rb +216 -222
  305. data/lib/rubydb/monitoring/logger.rb +188 -193
  306. data/lib/rubydb/monitoring/metrics.rb +363 -359
  307. data/lib/rubydb/monitoring/performance.rb +176 -176
  308. data/lib/rubydb/monitoring/statistics.rb +168 -170
  309. data/lib/rubydb/mvcc/garbage_collector.rb +199 -199
  310. data/lib/rubydb/mvcc/mvcc.rb +16 -16
  311. data/lib/rubydb/mvcc/snapshot.rb +146 -147
  312. data/lib/rubydb/mvcc/vacuum.rb +180 -180
  313. data/lib/rubydb/mvcc/version.rb +106 -106
  314. data/lib/rubydb/mvcc/version_store.rb +396 -398
  315. data/lib/rubydb/mvcc/visibility.rb +107 -109
  316. data/lib/rubydb/protocol/capabilities.rb +125 -125
  317. data/lib/rubydb/protocol/decoder.rb +142 -145
  318. data/lib/rubydb/protocol/encoder.rb +131 -136
  319. data/lib/rubydb/protocol/handshake.rb +306 -305
  320. data/lib/rubydb/protocol/message.rb +121 -121
  321. data/lib/rubydb/protocol/parameter_binder.rb +101 -0
  322. data/lib/rubydb/protocol/protocol.rb +276 -277
  323. data/lib/rubydb/protocol/version.rb +54 -54
  324. data/lib/rubydb/rails/adapter.rb +245 -239
  325. data/lib/rubydb/rails/connection.rb +312 -314
  326. data/lib/rubydb/rails/database_statements.rb +122 -122
  327. data/lib/rubydb/rails/migration.rb +131 -131
  328. data/lib/rubydb/rails/quoting.rb +109 -109
  329. data/lib/rubydb/rails/result.rb +117 -117
  330. data/lib/rubydb/rails/schema_statements.rb +339 -339
  331. data/lib/rubydb/rails/transaction.rb +105 -105
  332. data/lib/rubydb/rails/type.rb +126 -126
  333. data/lib/rubydb/recovery/checkpoint.rb +261 -257
  334. data/lib/rubydb/recovery/consistency.rb +457 -467
  335. data/lib/rubydb/recovery/corruption_detector.rb +5 -5
  336. data/lib/rubydb/recovery/crash_recovery.rb +381 -387
  337. data/lib/rubydb/recovery/recovery_manager.rb +204 -206
  338. data/lib/rubydb/recovery/redo.rb +235 -237
  339. data/lib/rubydb/recovery/undo.rb +204 -206
  340. data/lib/rubydb/replication/failover.rb +5 -5
  341. data/lib/rubydb/replication/fencing.rb +63 -63
  342. data/lib/rubydb/replication/primary.rb +461 -450
  343. data/lib/rubydb/replication/replica.rb +382 -384
  344. data/lib/rubydb/replication/replication_log.rb +194 -200
  345. data/lib/rubydb/replication/replication_manager.rb +307 -308
  346. data/lib/rubydb/replication/replication_slot.rb +293 -295
  347. data/lib/rubydb/replication/replication_stream.rb +198 -201
  348. data/lib/rubydb/rubydb.rb +570 -560
  349. data/lib/rubydb/security/access_control.rb +252 -254
  350. data/lib/rubydb/security/audit_log.rb +209 -213
  351. data/lib/rubydb/security/authentication.rb +302 -302
  352. data/lib/rubydb/security/authorization.rb +282 -282
  353. data/lib/rubydb/security/credentials.rb +192 -196
  354. data/lib/rubydb/security/password.rb +205 -215
  355. data/lib/rubydb/security/permissions.rb +74 -74
  356. data/lib/rubydb/security/role.rb +99 -101
  357. data/lib/rubydb/security/user.rb +86 -86
  358. data/lib/rubydb/server/connection.rb +383 -366
  359. data/lib/rubydb/server/connection_pool.rb +193 -193
  360. data/lib/rubydb/server/lifecycle.rb +227 -228
  361. data/lib/rubydb/server/listener.rb +139 -136
  362. data/lib/rubydb/server/request_handler.rb +277 -276
  363. data/lib/rubydb/server/server.rb +363 -364
  364. data/lib/rubydb/server/session.rb +416 -369
  365. data/lib/rubydb/server/worker.rb +206 -210
  366. data/lib/rubydb/server/worker_pool.rb +168 -168
  367. data/lib/rubydb/sql/ast/alter_table.rb +169 -169
  368. data/lib/rubydb/sql/ast/begin_transaction.rb +47 -47
  369. data/lib/rubydb/sql/ast/commit.rb +37 -37
  370. data/lib/rubydb/sql/ast/constraint.rb +92 -83
  371. data/lib/rubydb/sql/ast/create_database.rb +41 -41
  372. data/lib/rubydb/sql/ast/create_index.rb +61 -61
  373. data/lib/rubydb/sql/ast/create_schema.rb +52 -52
  374. data/lib/rubydb/sql/ast/create_table.rb +187 -187
  375. data/lib/rubydb/sql/ast/delete.rb +54 -54
  376. data/lib/rubydb/sql/ast/drop_database.rb +41 -41
  377. data/lib/rubydb/sql/ast/drop_index.rb +41 -41
  378. data/lib/rubydb/sql/ast/drop_schema.rb +49 -49
  379. data/lib/rubydb/sql/ast/drop_table.rb +49 -49
  380. data/lib/rubydb/sql/ast/explain.rb +64 -64
  381. data/lib/rubydb/sql/ast/expression.rb +617 -604
  382. data/lib/rubydb/sql/ast/insert.rb +66 -66
  383. data/lib/rubydb/sql/ast/node.rb +42 -42
  384. data/lib/rubydb/sql/ast/rollback.rb +63 -63
  385. data/lib/rubydb/sql/ast/savepoint.rb +59 -59
  386. data/lib/rubydb/sql/ast/select.rb +88 -88
  387. data/lib/rubydb/sql/ast/set_operation.rb +22 -20
  388. data/lib/rubydb/sql/ast/trigger.rb +35 -29
  389. data/lib/rubydb/sql/ast/update.rb +88 -88
  390. data/lib/rubydb/sql/ast/vacuum.rb +19 -19
  391. data/lib/rubydb/sql/ast/view.rb +38 -32
  392. data/lib/rubydb/sql/ast/with.rb +32 -32
  393. data/lib/rubydb/sql/grammar.rb +86 -86
  394. data/lib/rubydb/sql/keywords.rb +156 -156
  395. data/lib/rubydb/sql/lexer.rb +209 -214
  396. data/lib/rubydb/sql/operators.rb +100 -100
  397. data/lib/rubydb/sql/parser.rb +1167 -1170
  398. data/lib/rubydb/sql/planner/analyzer.rb +283 -302
  399. data/lib/rubydb/sql/planner/binder.rb +537 -550
  400. data/lib/rubydb/sql/planner/type_checker.rb +427 -431
  401. data/lib/rubydb/sql/token.rb +210 -210
  402. data/lib/rubydb/storage/buffer_frame.rb +44 -44
  403. data/lib/rubydb/storage/buffer_pool.rb +155 -155
  404. data/lib/rubydb/storage/database_lock.rb +74 -74
  405. data/lib/rubydb/storage/deserializer.rb +332 -342
  406. data/lib/rubydb/storage/engine.rb +2409 -2330
  407. data/lib/rubydb/storage/file_manager.rb +191 -187
  408. data/lib/rubydb/storage/free_space_map.rb +79 -81
  409. data/lib/rubydb/storage/page.rb +92 -94
  410. data/lib/rubydb/storage/page_allocator.rb +852 -855
  411. data/lib/rubydb/storage/page_header.rb +63 -67
  412. data/lib/rubydb/storage/page_manager.rb +127 -131
  413. data/lib/rubydb/storage/record.rb +58 -58
  414. data/lib/rubydb/storage/row.rb +78 -78
  415. data/lib/rubydb/storage/serializer.rb +51 -51
  416. data/lib/rubydb/storage/snapshot_reader.rb +167 -0
  417. data/lib/rubydb/storage/storage_layout.rb +151 -151
  418. data/lib/rubydb/storage/storage_manager.rb +114 -114
  419. data/lib/rubydb/storage/tuple.rb +458 -461
  420. data/lib/rubydb/storage/visibility_map.rb +964 -973
  421. data/lib/rubydb/transactions/commit_manager.rb +219 -220
  422. data/lib/rubydb/transactions/isolation.rb +98 -98
  423. data/lib/rubydb/transactions/lock.rb +76 -76
  424. data/lib/rubydb/transactions/lock_manager.rb +359 -362
  425. data/lib/rubydb/transactions/savepoint.rb +142 -143
  426. data/lib/rubydb/transactions/transaction.rb +214 -215
  427. data/lib/rubydb/transactions/transaction_id.rb +84 -84
  428. data/lib/rubydb/transactions/transaction_log.rb +256 -257
  429. data/lib/rubydb/transactions/transaction_manager.rb +434 -435
  430. data/lib/rubydb/types/bigint.rb +36 -36
  431. data/lib/rubydb/types/blob.rb +37 -37
  432. data/lib/rubydb/types/boolean.rb +34 -34
  433. data/lib/rubydb/types/date.rb +39 -39
  434. data/lib/rubydb/types/decimal.rb +48 -48
  435. data/lib/rubydb/types/float.rb +34 -34
  436. data/lib/rubydb/types/integer.rb +36 -36
  437. data/lib/rubydb/types/json.rb +41 -41
  438. data/lib/rubydb/types/null.rb +34 -34
  439. data/lib/rubydb/types/smallint.rb +36 -36
  440. data/lib/rubydb/types/text.rb +37 -37
  441. data/lib/rubydb/types/time.rb +46 -46
  442. data/lib/rubydb/types/timestamp.rb +39 -39
  443. data/lib/rubydb/types/type.rb +119 -119
  444. data/lib/rubydb/types/uuid.rb +47 -47
  445. data/lib/rubydb/types/varchar.rb +37 -37
  446. data/lib/rubydb/version.rb +32 -32
  447. data/lib/rubydb/wal/archive.rb +207 -193
  448. data/lib/rubydb/wal/checkpoint.rb +181 -183
  449. data/lib/rubydb/wal/lsn.rb +94 -94
  450. data/lib/rubydb/wal/reader.rb +259 -260
  451. data/lib/rubydb/wal/record.rb +105 -105
  452. data/lib/rubydb/wal/segment.rb +193 -193
  453. data/lib/rubydb/wal/wal.rb +481 -452
  454. data/lib/rubydb/wal/writer.rb +236 -236
  455. data/lib/rubydb.rb +7 -7
  456. data/packaging/docker/docker-compose.failover.yml +43 -43
  457. data/packaging/homebrew/rubydb.rb +19 -19
  458. data/rubydb.gemspec +70 -57
  459. data/scripts/benchmark +7 -7
  460. data/scripts/build_accelerator +49 -0
  461. data/scripts/durability_drill +37 -37
  462. data/scripts/fuzz +63 -63
  463. data/scripts/release +77 -42
  464. data/scripts/release_check +43 -43
  465. data/scripts/replication_failover_drill +268 -250
  466. data/scripts/replication_network_failover_drill +287 -255
  467. data/scripts/restore_drill +45 -45
  468. data/scripts/security +45 -0
  469. metadata +102 -1
@@ -1,544 +1,544 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyDB
4
- module SQL
5
- # SQL Parser - converts tokens into AST
6
- class Parser
7
- attr_reader :tokens, :position, :current_token
8
-
9
- def initialize(tokens)
10
- @tokens = tokens
11
- @position = 0
12
- @current_token = @tokens[0]
13
- end
14
-
15
- def parse
16
- statements = []
17
- while current_token && current_token.type != Token::Type::EOF
18
- stmt = parse_statement
19
- statements << stmt if stmt
20
- # Skip semicolons between statements
21
- while current_token && current_token.type == Token::Type::SEMICOLON
22
- advance
23
- end
24
- end
25
- statements
26
- end
27
-
28
- private
29
-
30
- def parse_statement
31
- case current_token&.type
32
- when Token::Type::WITH
33
- parse_with
34
- when Token::Type::SELECT
35
- parse_select
36
- when Token::Type::INSERT
37
- parse_insert
38
- when Token::Type::UPDATE
39
- parse_update
40
- when Token::Type::DELETE
41
- parse_delete
42
- when Token::Type::CREATE
43
- parse_create
44
- when Token::Type::DROP
45
- parse_drop
46
- when Token::Type::ALTER
47
- parse_alter
48
- when Token::Type::BEGIN_TRANSACTION
49
- parse_begin
50
- when Token::Type::COMMIT
51
- parse_commit
52
- when Token::Type::ROLLBACK
53
- parse_rollback
54
- when Token::Type::SAVEPOINT
55
- parse_savepoint
56
- when Token::Type::RELEASE
57
- parse_release_savepoint
58
- when Token::Type::EXPLAIN
59
- parse_explain
60
- when Token::Type::VACUUM
61
- advance
62
- AST::Vacuum.new
63
- when nil, Token::Type::EOF
64
- nil
65
- else
66
- raise ParserError, "Unexpected token: #{current_token}"
67
- end
68
- end
69
-
70
- def parse_select
71
- expect(Token::Type::SELECT)
72
- distinct = false
73
- if current_token&.type == Token::Type::DISTINCT
74
- advance
75
- distinct = true
76
- end
77
-
78
- columns = parse_select_columns
79
- from = nil
80
- joins = []
81
- if current_token&.type == Token::Type::FROM
82
- advance
83
- from = parse_table_reference
84
- joins = parse_joins
85
- end
86
-
87
- where = nil
88
- if current_token&.type == Token::Type::WHERE
89
- advance
90
- where = parse_expression
91
- end
92
-
93
- group_by = []
94
- if current_token&.type == Token::Type::GROUP
95
- advance
96
- expect(Token::Type::BY)
97
- group_by = parse_expression_list
98
- end
99
-
100
- having = nil
101
- if current_token&.type == Token::Type::HAVING
102
- advance
103
- having = parse_expression
104
- end
105
-
106
- order_by = nil
107
- if current_token&.type == Token::Type::ORDER
108
- advance
109
- expect(Token::Type::BY)
110
- order_by = parse_order_by
111
- end
112
-
113
- limit = nil
114
- if current_token&.type == Token::Type::LIMIT
115
- advance
116
- limit = parse_expression
117
- end
118
-
119
- offset = nil
120
- if current_token&.type == Token::Type::OFFSET
121
- advance
122
- offset = parse_expression
123
- end
124
-
125
- select = AST::Select.new(columns, from, where, order_by, limit, offset, distinct,
126
- joins: joins, group_by: group_by, having: having)
127
- return select unless [Token::Type::UNION, Token::Type::INTERSECT, Token::Type::EXCEPT].include?(current_token&.type)
128
-
129
- operator = current_token.type.to_s.downcase.to_sym
130
- advance
131
- all = current_token&.type == Token::Type::ALL
132
- advance if all
133
- AST::SetOperation.new(select, parse_select, operator, all: all)
134
- end
135
-
136
- def parse_with
137
- expect(Token::Type::WITH)
138
- recursive = current_token&.type == Token::Type::RECURSIVE
139
- advance if recursive
140
-
141
- ctes = []
142
- loop do
143
- name = expect(Token::Type::IDENTIFIER).value
144
- expect(Token::Type::AS)
145
- expect(Token::Type::LPAREN)
146
- query = parse_select
147
- expect(Token::Type::RPAREN)
148
- ctes << [name, query]
149
- break unless current_token&.type == Token::Type::COMMA
150
- advance
151
- end
152
- AST::With.new(ctes, parse_select, recursive: recursive)
153
- end
154
-
155
- def parse_select_columns
156
- columns = []
157
- while true
158
- if qualified_star?
159
- table = expect(Token::Type::IDENTIFIER).value
160
- expect(Token::Type::DOT)
161
- expect(Token::Type::STAR)
162
- columns << AST::SelectColumn.new(AST::Star.new(table: table))
163
- elsif current_token&.type == Token::Type::STAR
164
- columns << AST::Star.new
165
- advance
166
- else
167
- expr = parse_expression
168
- alias_name = nil
169
- if current_token&.type == Token::Type::AS
170
- advance
171
- alias_name = expect(Token::Type::IDENTIFIER)
172
- alias_name = alias_name.value
173
- elsif current_token&.type == Token::Type::IDENTIFIER
174
- # Implicit alias
175
- alias_name = current_token.value
176
- advance
177
- end
178
- columns << AST::SelectColumn.new(expr, alias_name)
179
- end
180
-
181
- break unless current_token&.type == Token::Type::COMMA
182
- advance
183
- end
184
- columns
185
- end
186
-
187
- def qualified_star?
188
- current_token&.type == Token::Type::IDENTIFIER &&
189
- @tokens[@position + 1]&.type == Token::Type::DOT &&
190
- @tokens[@position + 2]&.type == Token::Type::STAR
191
- end
192
-
193
- def parse_table_reference
194
- table = expect(Token::Type::IDENTIFIER).value
195
- alias_name = nil
196
- if current_token&.type == Token::Type::AS
197
- advance
198
- alias_name = expect(Token::Type::IDENTIFIER).value
199
- elsif current_token&.type == Token::Type::IDENTIFIER
200
- alias_name = current_token.value
201
- advance
202
- end
203
- AST::TableRef.new(table, alias_name)
204
- end
205
-
206
- def parse_joins
207
- joins = []
208
- while join_start?
209
- join_type = parse_join_type
210
- table = parse_table_reference
211
- condition = nil
212
- if current_token&.type == Token::Type::ON
213
- advance
214
- condition = parse_expression
215
- elsif join_type != :cross
216
- raise ParserError, "Expected ON clause for #{join_type.to_s.upcase} JOIN"
217
- end
218
- joins << AST::Join.new(join_type, table, condition)
219
- end
220
- joins
221
- end
222
-
223
- def join_start?
224
- [Token::Type::JOIN, Token::Type::INNER, Token::Type::LEFT, Token::Type::RIGHT, Token::Type::FULL, Token::Type::CROSS].include?(current_token&.type)
225
- end
226
-
227
- def parse_join_type
228
- case current_token&.type
229
- when Token::Type::JOIN
230
- advance
231
- :inner
232
- when Token::Type::INNER
233
- advance
234
- expect(Token::Type::JOIN)
235
- :inner
236
- when Token::Type::LEFT
237
- advance
238
- advance if current_token&.type == Token::Type::OUTER
239
- expect(Token::Type::JOIN)
240
- :left
241
- when Token::Type::RIGHT
242
- advance
243
- advance if current_token&.type == Token::Type::OUTER
244
- expect(Token::Type::JOIN)
245
- :right
246
- when Token::Type::FULL
247
- advance
248
- advance if current_token&.type == Token::Type::OUTER
249
- expect(Token::Type::JOIN)
250
- :full
251
- when Token::Type::CROSS
252
- advance
253
- expect(Token::Type::JOIN)
254
- :cross
255
- end
256
- end
257
-
258
- def parse_expression
259
- parse_or
260
- end
261
-
262
- def parse_or
263
- expr = parse_and
264
- while current_token&.type == Token::Type::OR
265
- advance
266
- right = parse_and
267
- expr = AST::BinaryOp.new(:or, expr, right)
268
- end
269
- expr
270
- end
271
-
272
- def parse_and
273
- expr = parse_comparison
274
- while current_token&.type == Token::Type::AND
275
- advance
276
- right = parse_comparison
277
- expr = AST::BinaryOp.new(:and, expr, right)
278
- end
279
- expr
280
- end
281
-
282
- def parse_comparison
283
- expr = parse_additive
284
-
285
- # IS NULL / IS NOT NULL have a dedicated AST node and must be parsed
286
- # before the generic binary-comparison path. Treating IS as a normal
287
- # comparison loses the null predicate and can turn it into a
288
- # three-valued expression that is always truthy at the filter layer.
289
- if current_token && Operators.comparison?(current_token.type) &&
290
- ![Token::Type::IN, Token::Type::IS].include?(current_token.type)
291
- op = current_token.type
292
- advance
293
- right = parse_additive
294
- expr = AST::BinaryOp.new(op, expr, right)
295
- end
296
-
297
- if current_token&.type == Token::Type::IS
298
- advance
299
- is_not = current_token&.type == Token::Type::NOT
300
- advance if is_not
301
- expect(Token::Type::NULL)
302
- expr = AST::IsNull.new(expr, is_not)
303
- end
304
-
305
- if current_token&.type == Token::Type::BETWEEN
306
- advance
307
- low = parse_additive
308
- expect(Token::Type::AND)
309
- high = parse_additive
310
- expr = AST::Between.new(expr, low, high)
311
- end
312
-
313
- if current_token&.type == Token::Type::IN
314
- advance
315
- expect(Token::Type::LPAREN)
316
- values = []
317
- if current_token&.type == Token::Type::SELECT
318
- values << AST::Subquery.new(parse_select)
319
- else
320
- while true
321
- values << parse_expression
322
- break unless current_token&.type == Token::Type::COMMA
323
- advance
324
- end
325
- end
326
- expect(Token::Type::RPAREN)
327
- expr = AST::In.new(expr, values)
328
- end
329
-
330
- expr
331
- end
332
-
333
- def parse_additive
334
- expr = parse_multiplicative
335
- while current_token && [Token::Type::PLUS, Token::Type::MINUS].include?(current_token.type)
336
- op = current_token.type
337
- advance
338
- right = parse_multiplicative
339
- expr = AST::BinaryOp.new(op, expr, right)
340
- end
341
- expr
342
- end
343
-
344
- def parse_multiplicative
345
- expr = parse_unary
346
- while current_token && [Token::Type::STAR, Token::Type::SLASH, Token::Type::PERCENT].include?(current_token.type)
347
- op = current_token.type
348
- advance
349
- right = parse_unary
350
- expr = AST::BinaryOp.new(op, expr, right)
351
- end
352
- expr
353
- end
354
-
355
- def parse_unary
356
- if current_token && Operators.unary?(current_token.type)
357
- op = current_token.type
358
- advance
359
- expr = parse_primary
360
- AST::UnaryOp.new(op, expr)
361
- else
362
- parse_primary
363
- end
364
- end
365
-
366
- def parse_primary
367
- case current_token&.type
368
- when Token::Type::STAR
369
- advance
370
- AST::Star.new
371
- when Token::Type::EXISTS
372
- advance
373
- expect(Token::Type::LPAREN)
374
- query = parse_select
375
- expect(Token::Type::RPAREN)
376
- AST::Exists.new(query)
377
- when Token::Type::LPAREN
378
- advance
379
- expr = current_token&.type == Token::Type::SELECT ? AST::Subquery.new(parse_select) : parse_expression
380
- expect(Token::Type::RPAREN)
381
- expr
382
- when Token::Type::IDENTIFIER
383
- ident = current_token.value
384
- advance
385
- if current_token&.type == Token::Type::DOT
386
- advance
387
- column = expect(Token::Type::IDENTIFIER).value
388
- AST::Identifier.new(column, table: ident)
389
- elsif current_token&.type == Token::Type::LPAREN
390
- # Function call
391
- advance
392
- args = []
393
- distinct = false
394
- if current_token&.type == Token::Type::DISTINCT
395
- advance
396
- distinct = true
397
- end
398
- unless current_token&.type == Token::Type::RPAREN
399
- while true
400
- args << parse_expression
401
- break unless current_token&.type == Token::Type::COMMA
402
- advance
403
- end
404
- end
405
- expect(Token::Type::RPAREN)
406
- window = current_token&.type == Token::Type::OVER ? parse_window_spec : nil
407
- AST::FunctionCall.new(ident, args, distinct: distinct, window: window)
408
- else
409
- AST::Identifier.new(ident)
410
- end
411
- when Token::Type::STRING, Token::Type::NUMBER
412
- value = current_token.value
413
- type = current_token.type
414
- advance
415
- AST::Literal.new(value, type)
416
- when Token::Type::NULL
417
- advance
418
- AST::NullLiteral.new
419
- when Token::Type::TRUE
420
- advance
421
- AST::Literal.new(true, Token::Type::TRUE)
422
- when Token::Type::FALSE
423
- advance
424
- AST::Literal.new(false, Token::Type::FALSE)
425
- when Token::Type::PARAMETER
426
- value = current_token.value
427
- advance
428
- AST::Parameter.new(value)
429
- else
430
- raise ParserError, "Unexpected token in expression: #{current_token}"
431
- end
432
- end
433
-
434
- def parse_order_by
435
- order_items = []
436
- while true
437
- expr = parse_expression
438
- direction = :asc
439
- if [Token::Type::ASC, Token::Type::DESC].include?(current_token&.type)
440
- direction = :desc if current_token.type == Token::Type::DESC
441
- advance
442
- end
443
- order_items << AST::OrderItem.new(expr, direction)
444
- break unless current_token&.type == Token::Type::COMMA
445
- advance
446
- end
447
- order_items
448
- end
449
-
450
- def parse_window_spec
451
- expect(Token::Type::OVER)
452
- expect(Token::Type::LPAREN)
453
- partition_by = []
454
- if current_token&.type == Token::Type::PARTITION
455
- advance
456
- expect(Token::Type::BY)
457
- partition_by = parse_expression_list
458
- end
459
-
460
- order_by = []
461
- if current_token&.type == Token::Type::ORDER
462
- advance
463
- expect(Token::Type::BY)
464
- order_by = parse_order_by
465
- end
466
- frame = parse_window_frame if current_token&.type == Token::Type::ROWS
467
- expect(Token::Type::RPAREN)
468
- { partition_by: partition_by, order_by: order_by, frame: frame }
469
- end
470
-
471
- def parse_window_frame
472
- expect(Token::Type::ROWS)
473
- if current_token&.type == Token::Type::BETWEEN
474
- advance
475
- start = parse_frame_boundary
476
- expect(Token::Type::AND)
477
- finish = parse_frame_boundary
478
- { start: start, finish: finish }
479
- else
480
- { start: parse_frame_boundary, finish: { kind: :current_row } }
481
- end
482
- end
483
-
484
- def parse_frame_boundary
485
- case current_token&.type
486
- when Token::Type::UNBOUNDED
487
- advance
488
- direction = current_token&.type
489
- unless [Token::Type::PRECEDING, Token::Type::FOLLOWING].include?(direction)
490
- raise ParserError, "Expected PRECEDING or FOLLOWING after UNBOUNDED"
491
- end
492
- advance
493
- { kind: direction == Token::Type::PRECEDING ? :unbounded_preceding : :unbounded_following }
494
- when Token::Type::CURRENT
495
- advance
496
- row = expect(Token::Type::IDENTIFIER)
497
- raise ParserError, "Expected ROW after CURRENT" unless row.value.to_s.upcase == "ROW"
498
- { kind: :current_row }
499
- when Token::Type::NUMBER
500
- value = expect(Token::Type::NUMBER).value
501
- unless value.is_a?(Integer) && value >= 0
502
- raise ParserError, "Window frame offset must be a non-negative integer"
503
- end
504
- direction = current_token&.type
505
- unless [Token::Type::PRECEDING, Token::Type::FOLLOWING].include?(direction)
506
- raise ParserError, "Expected PRECEDING or FOLLOWING after frame offset"
507
- end
508
- advance
509
- { kind: direction == Token::Type::PRECEDING ? :preceding : :following, value: value }
510
- else
511
- raise ParserError, "Expected window frame boundary"
512
- end
513
- end
514
-
515
- def parse_expression_list
516
- expressions = []
517
- loop do
518
- expressions << parse_expression
519
- break unless current_token&.type == Token::Type::COMMA
520
-
521
- advance
522
- end
523
- expressions
524
- end
525
-
526
- def parse_insert
527
- expect(Token::Type::INSERT)
528
- expect(Token::Type::INTO)
529
- table = expect(Token::Type::IDENTIFIER).value
530
-
531
- columns = []
532
- if current_token&.type == Token::Type::LPAREN
533
- advance
534
- while true
535
- columns << expect(Token::Type::IDENTIFIER).value
536
- break unless current_token&.type == Token::Type::COMMA
537
- advance
538
- end
539
- expect(Token::Type::RPAREN)
540
- end
541
-
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module SQL
5
+ # SQL Parser - converts tokens into AST
6
+ class Parser
7
+ attr_reader :tokens, :position, :current_token
8
+
9
+ def initialize(tokens)
10
+ @tokens = tokens
11
+ @position = 0
12
+ @current_token = @tokens[0]
13
+ end
14
+
15
+ def parse
16
+ statements = []
17
+ while current_token && current_token.type != Token::Type::EOF
18
+ stmt = parse_statement
19
+ statements << stmt if stmt
20
+ # Skip semicolons between statements
21
+ while current_token && current_token.type == Token::Type::SEMICOLON
22
+ advance
23
+ end
24
+ end
25
+ statements
26
+ end
27
+
28
+ private
29
+
30
+ def parse_statement
31
+ case current_token&.type
32
+ when Token::Type::WITH
33
+ parse_with
34
+ when Token::Type::SELECT
35
+ parse_select
36
+ when Token::Type::INSERT
37
+ parse_insert
38
+ when Token::Type::UPDATE
39
+ parse_update
40
+ when Token::Type::DELETE
41
+ parse_delete
42
+ when Token::Type::CREATE
43
+ parse_create
44
+ when Token::Type::DROP
45
+ parse_drop
46
+ when Token::Type::ALTER
47
+ parse_alter
48
+ when Token::Type::BEGIN_TRANSACTION
49
+ parse_begin
50
+ when Token::Type::COMMIT
51
+ parse_commit
52
+ when Token::Type::ROLLBACK
53
+ parse_rollback
54
+ when Token::Type::SAVEPOINT
55
+ parse_savepoint
56
+ when Token::Type::RELEASE
57
+ parse_release_savepoint
58
+ when Token::Type::EXPLAIN
59
+ parse_explain
60
+ when Token::Type::VACUUM
61
+ advance
62
+ AST::Vacuum.new
63
+ when nil, Token::Type::EOF
64
+ nil
65
+ else
66
+ raise ParserError, "Unexpected token: #{current_token}"
67
+ end
68
+ end
69
+
70
+ def parse_select
71
+ expect(Token::Type::SELECT)
72
+ distinct = false
73
+ if current_token&.type == Token::Type::DISTINCT
74
+ advance
75
+ distinct = true
76
+ end
77
+
78
+ columns = parse_select_columns
79
+ from = nil
80
+ joins = []
81
+ if current_token&.type == Token::Type::FROM
82
+ advance
83
+ from = parse_table_reference
84
+ joins = parse_joins
85
+ end
86
+
87
+ where = nil
88
+ if current_token&.type == Token::Type::WHERE
89
+ advance
90
+ where = parse_expression
91
+ end
92
+
93
+ group_by = []
94
+ if current_token&.type == Token::Type::GROUP
95
+ advance
96
+ expect(Token::Type::BY)
97
+ group_by = parse_expression_list
98
+ end
99
+
100
+ having = nil
101
+ if current_token&.type == Token::Type::HAVING
102
+ advance
103
+ having = parse_expression
104
+ end
105
+
106
+ order_by = nil
107
+ if current_token&.type == Token::Type::ORDER
108
+ advance
109
+ expect(Token::Type::BY)
110
+ order_by = parse_order_by
111
+ end
112
+
113
+ limit = nil
114
+ if current_token&.type == Token::Type::LIMIT
115
+ advance
116
+ limit = parse_expression
117
+ end
118
+
119
+ offset = nil
120
+ if current_token&.type == Token::Type::OFFSET
121
+ advance
122
+ offset = parse_expression
123
+ end
124
+
125
+ select = AST::Select.new(columns, from, where, order_by, limit, offset, distinct,
126
+ joins: joins, group_by: group_by, having: having)
127
+ return select unless [Token::Type::UNION, Token::Type::INTERSECT, Token::Type::EXCEPT].include?(current_token&.type)
128
+
129
+ operator = current_token.type.to_s.downcase.to_sym
130
+ advance
131
+ all = current_token&.type == Token::Type::ALL
132
+ advance if all
133
+ AST::SetOperation.new(select, parse_select, operator, all: all)
134
+ end
135
+
136
+ def parse_with
137
+ expect(Token::Type::WITH)
138
+ recursive = current_token&.type == Token::Type::RECURSIVE
139
+ advance if recursive
140
+
141
+ ctes = []
142
+ loop do
143
+ name = expect(Token::Type::IDENTIFIER).value
144
+ expect(Token::Type::AS)
145
+ expect(Token::Type::LPAREN)
146
+ query = parse_select
147
+ expect(Token::Type::RPAREN)
148
+ ctes << [name, query]
149
+ break unless current_token&.type == Token::Type::COMMA
150
+ advance
151
+ end
152
+ AST::With.new(ctes, parse_select, recursive: recursive)
153
+ end
154
+
155
+ def parse_select_columns
156
+ columns = []
157
+ loop do
158
+ if qualified_star?
159
+ table = expect(Token::Type::IDENTIFIER).value
160
+ expect(Token::Type::DOT)
161
+ expect(Token::Type::STAR)
162
+ columns << AST::SelectColumn.new(AST::Star.new(table: table))
163
+ elsif current_token&.type == Token::Type::STAR
164
+ columns << AST::Star.new
165
+ advance
166
+ else
167
+ expr = parse_expression
168
+ alias_name = nil
169
+ if current_token&.type == Token::Type::AS
170
+ advance
171
+ alias_name = expect(Token::Type::IDENTIFIER)
172
+ alias_name = alias_name.value
173
+ elsif current_token&.type == Token::Type::IDENTIFIER
174
+ # Implicit alias
175
+ alias_name = current_token.value
176
+ advance
177
+ end
178
+ columns << AST::SelectColumn.new(expr, alias_name)
179
+ end
180
+
181
+ break unless current_token&.type == Token::Type::COMMA
182
+ advance
183
+ end
184
+ columns
185
+ end
186
+
187
+ def qualified_star?
188
+ current_token&.type == Token::Type::IDENTIFIER &&
189
+ @tokens[@position + 1]&.type == Token::Type::DOT &&
190
+ @tokens[@position + 2]&.type == Token::Type::STAR
191
+ end
192
+
193
+ def parse_table_reference
194
+ table = expect(Token::Type::IDENTIFIER).value
195
+ alias_name = nil
196
+ if current_token&.type == Token::Type::AS
197
+ advance
198
+ alias_name = expect(Token::Type::IDENTIFIER).value
199
+ elsif current_token&.type == Token::Type::IDENTIFIER
200
+ alias_name = current_token.value
201
+ advance
202
+ end
203
+ AST::TableRef.new(table, alias_name)
204
+ end
205
+
206
+ def parse_joins
207
+ joins = []
208
+ while join_start?
209
+ join_type = parse_join_type
210
+ table = parse_table_reference
211
+ condition = nil
212
+ if current_token&.type == Token::Type::ON
213
+ advance
214
+ condition = parse_expression
215
+ elsif join_type != :cross
216
+ raise ParserError, "Expected ON clause for #{join_type.to_s.upcase} JOIN"
217
+ end
218
+ joins << AST::Join.new(join_type, table, condition)
219
+ end
220
+ joins
221
+ end
222
+
223
+ def join_start?
224
+ [Token::Type::JOIN, Token::Type::INNER, Token::Type::LEFT, Token::Type::RIGHT, Token::Type::FULL, Token::Type::CROSS].include?(current_token&.type)
225
+ end
226
+
227
+ def parse_join_type
228
+ case current_token&.type
229
+ when Token::Type::JOIN
230
+ advance
231
+ :inner
232
+ when Token::Type::INNER
233
+ advance
234
+ expect(Token::Type::JOIN)
235
+ :inner
236
+ when Token::Type::LEFT
237
+ advance
238
+ advance if current_token&.type == Token::Type::OUTER
239
+ expect(Token::Type::JOIN)
240
+ :left
241
+ when Token::Type::RIGHT
242
+ advance
243
+ advance if current_token&.type == Token::Type::OUTER
244
+ expect(Token::Type::JOIN)
245
+ :right
246
+ when Token::Type::FULL
247
+ advance
248
+ advance if current_token&.type == Token::Type::OUTER
249
+ expect(Token::Type::JOIN)
250
+ :full
251
+ when Token::Type::CROSS
252
+ advance
253
+ expect(Token::Type::JOIN)
254
+ :cross
255
+ end
256
+ end
257
+
258
+ def parse_expression
259
+ parse_or
260
+ end
261
+
262
+ def parse_or
263
+ expr = parse_and
264
+ while current_token&.type == Token::Type::OR
265
+ advance
266
+ right = parse_and
267
+ expr = AST::BinaryOp.new(:or, expr, right)
268
+ end
269
+ expr
270
+ end
271
+
272
+ def parse_and
273
+ expr = parse_comparison
274
+ while current_token&.type == Token::Type::AND
275
+ advance
276
+ right = parse_comparison
277
+ expr = AST::BinaryOp.new(:and, expr, right)
278
+ end
279
+ expr
280
+ end
281
+
282
+ def parse_comparison
283
+ expr = parse_additive
284
+
285
+ # IS NULL / IS NOT NULL have a dedicated AST node and must be parsed
286
+ # before the generic binary-comparison path. Treating IS as a normal
287
+ # comparison loses the null predicate and can turn it into a
288
+ # three-valued expression that is always truthy at the filter layer.
289
+ if current_token && Operators.comparison?(current_token.type) &&
290
+ ![Token::Type::IN, Token::Type::IS].include?(current_token.type)
291
+ op = current_token.type
292
+ advance
293
+ right = parse_additive
294
+ expr = AST::BinaryOp.new(op, expr, right)
295
+ end
296
+
297
+ if current_token&.type == Token::Type::IS
298
+ advance
299
+ is_not = current_token&.type == Token::Type::NOT
300
+ advance if is_not
301
+ expect(Token::Type::NULL)
302
+ expr = AST::IsNull.new(expr, is_not)
303
+ end
304
+
305
+ if current_token&.type == Token::Type::BETWEEN
306
+ advance
307
+ low = parse_additive
308
+ expect(Token::Type::AND)
309
+ high = parse_additive
310
+ expr = AST::Between.new(expr, low, high)
311
+ end
312
+
313
+ if current_token&.type == Token::Type::IN
314
+ advance
315
+ expect(Token::Type::LPAREN)
316
+ values = []
317
+ if current_token&.type == Token::Type::SELECT
318
+ values << AST::Subquery.new(parse_select)
319
+ else
320
+ loop do
321
+ values << parse_expression
322
+ break unless current_token&.type == Token::Type::COMMA
323
+ advance
324
+ end
325
+ end
326
+ expect(Token::Type::RPAREN)
327
+ expr = AST::In.new(expr, values)
328
+ end
329
+
330
+ expr
331
+ end
332
+
333
+ def parse_additive
334
+ expr = parse_multiplicative
335
+ while current_token && [Token::Type::PLUS, Token::Type::MINUS].include?(current_token.type)
336
+ op = current_token.type
337
+ advance
338
+ right = parse_multiplicative
339
+ expr = AST::BinaryOp.new(op, expr, right)
340
+ end
341
+ expr
342
+ end
343
+
344
+ def parse_multiplicative
345
+ expr = parse_unary
346
+ while current_token && [Token::Type::STAR, Token::Type::SLASH, Token::Type::PERCENT].include?(current_token.type)
347
+ op = current_token.type
348
+ advance
349
+ right = parse_unary
350
+ expr = AST::BinaryOp.new(op, expr, right)
351
+ end
352
+ expr
353
+ end
354
+
355
+ def parse_unary
356
+ if current_token && Operators.unary?(current_token.type)
357
+ op = current_token.type
358
+ advance
359
+ expr = parse_primary
360
+ AST::UnaryOp.new(op, expr)
361
+ else
362
+ parse_primary
363
+ end
364
+ end
365
+
366
+ def parse_primary
367
+ case current_token&.type
368
+ when Token::Type::STAR
369
+ advance
370
+ AST::Star.new
371
+ when Token::Type::EXISTS
372
+ advance
373
+ expect(Token::Type::LPAREN)
374
+ query = parse_select
375
+ expect(Token::Type::RPAREN)
376
+ AST::Exists.new(query)
377
+ when Token::Type::LPAREN
378
+ advance
379
+ expr = (current_token&.type == Token::Type::SELECT) ? AST::Subquery.new(parse_select) : parse_expression
380
+ expect(Token::Type::RPAREN)
381
+ expr
382
+ when Token::Type::IDENTIFIER
383
+ ident = current_token.value
384
+ advance
385
+ if current_token&.type == Token::Type::DOT
386
+ advance
387
+ column = expect(Token::Type::IDENTIFIER).value
388
+ AST::Identifier.new(column, table: ident)
389
+ elsif current_token&.type == Token::Type::LPAREN
390
+ # Function call
391
+ advance
392
+ args = []
393
+ distinct = false
394
+ if current_token&.type == Token::Type::DISTINCT
395
+ advance
396
+ distinct = true
397
+ end
398
+ unless current_token&.type == Token::Type::RPAREN
399
+ loop do
400
+ args << parse_expression
401
+ break unless current_token&.type == Token::Type::COMMA
402
+ advance
403
+ end
404
+ end
405
+ expect(Token::Type::RPAREN)
406
+ window = (current_token&.type == Token::Type::OVER) ? parse_window_spec : nil
407
+ AST::FunctionCall.new(ident, args, distinct: distinct, window: window)
408
+ else
409
+ AST::Identifier.new(ident)
410
+ end
411
+ when Token::Type::STRING, Token::Type::NUMBER
412
+ value = current_token.value
413
+ type = current_token.type
414
+ advance
415
+ AST::Literal.new(value, type)
416
+ when Token::Type::NULL
417
+ advance
418
+ AST::NullLiteral.new
419
+ when Token::Type::TRUE
420
+ advance
421
+ AST::Literal.new(true, Token::Type::TRUE)
422
+ when Token::Type::FALSE
423
+ advance
424
+ AST::Literal.new(false, Token::Type::FALSE)
425
+ when Token::Type::PARAMETER
426
+ value = current_token.value
427
+ advance
428
+ AST::Parameter.new(value)
429
+ else
430
+ raise ParserError, "Unexpected token in expression: #{current_token}"
431
+ end
432
+ end
433
+
434
+ def parse_order_by
435
+ order_items = []
436
+ loop do
437
+ expr = parse_expression
438
+ direction = :asc
439
+ if [Token::Type::ASC, Token::Type::DESC].include?(current_token&.type)
440
+ direction = :desc if current_token.type == Token::Type::DESC
441
+ advance
442
+ end
443
+ order_items << AST::OrderItem.new(expr, direction)
444
+ break unless current_token&.type == Token::Type::COMMA
445
+ advance
446
+ end
447
+ order_items
448
+ end
449
+
450
+ def parse_window_spec
451
+ expect(Token::Type::OVER)
452
+ expect(Token::Type::LPAREN)
453
+ partition_by = []
454
+ if current_token&.type == Token::Type::PARTITION
455
+ advance
456
+ expect(Token::Type::BY)
457
+ partition_by = parse_expression_list
458
+ end
459
+
460
+ order_by = []
461
+ if current_token&.type == Token::Type::ORDER
462
+ advance
463
+ expect(Token::Type::BY)
464
+ order_by = parse_order_by
465
+ end
466
+ frame = parse_window_frame if current_token&.type == Token::Type::ROWS
467
+ expect(Token::Type::RPAREN)
468
+ {partition_by: partition_by, order_by: order_by, frame: frame}
469
+ end
470
+
471
+ def parse_window_frame
472
+ expect(Token::Type::ROWS)
473
+ if current_token&.type == Token::Type::BETWEEN
474
+ advance
475
+ start = parse_frame_boundary
476
+ expect(Token::Type::AND)
477
+ finish = parse_frame_boundary
478
+ {start: start, finish: finish}
479
+ else
480
+ {start: parse_frame_boundary, finish: {kind: :current_row}}
481
+ end
482
+ end
483
+
484
+ def parse_frame_boundary
485
+ case current_token&.type
486
+ when Token::Type::UNBOUNDED
487
+ advance
488
+ direction = current_token&.type
489
+ unless [Token::Type::PRECEDING, Token::Type::FOLLOWING].include?(direction)
490
+ raise ParserError, "Expected PRECEDING or FOLLOWING after UNBOUNDED"
491
+ end
492
+ advance
493
+ {kind: (direction == Token::Type::PRECEDING) ? :unbounded_preceding : :unbounded_following}
494
+ when Token::Type::CURRENT
495
+ advance
496
+ row = expect(Token::Type::IDENTIFIER)
497
+ raise ParserError, "Expected ROW after CURRENT" unless row.value.to_s.upcase == "ROW"
498
+ {kind: :current_row}
499
+ when Token::Type::NUMBER
500
+ value = expect(Token::Type::NUMBER).value
501
+ unless value.is_a?(Integer) && value >= 0
502
+ raise ParserError, "Window frame offset must be a non-negative integer"
503
+ end
504
+ direction = current_token&.type
505
+ unless [Token::Type::PRECEDING, Token::Type::FOLLOWING].include?(direction)
506
+ raise ParserError, "Expected PRECEDING or FOLLOWING after frame offset"
507
+ end
508
+ advance
509
+ {kind: (direction == Token::Type::PRECEDING) ? :preceding : :following, value: value}
510
+ else
511
+ raise ParserError, "Expected window frame boundary"
512
+ end
513
+ end
514
+
515
+ def parse_expression_list
516
+ expressions = []
517
+ loop do
518
+ expressions << parse_expression
519
+ break unless current_token&.type == Token::Type::COMMA
520
+
521
+ advance
522
+ end
523
+ expressions
524
+ end
525
+
526
+ def parse_insert
527
+ expect(Token::Type::INSERT)
528
+ expect(Token::Type::INTO)
529
+ table = expect(Token::Type::IDENTIFIER).value
530
+
531
+ columns = []
532
+ if current_token&.type == Token::Type::LPAREN
533
+ advance
534
+ loop do
535
+ columns << expect(Token::Type::IDENTIFIER).value
536
+ break unless current_token&.type == Token::Type::COMMA
537
+ advance
538
+ end
539
+ expect(Token::Type::RPAREN)
540
+ end
541
+
542
542
  default_values = false
543
543
  if current_token&.type == Token::Type::DEFAULT
544
544
  advance
@@ -552,7 +552,7 @@ module RubyDB
552
552
  loop do
553
553
  expect(Token::Type::LPAREN)
554
554
  values = []
555
- while true
555
+ loop do
556
556
  values << parse_expression
557
557
  break unless current_token&.type == Token::Type::COMMA
558
558
  advance
@@ -563,633 +563,630 @@ module RubyDB
563
563
  advance
564
564
  end
565
565
  end
566
-
567
- on_conflict = nil
568
- if current_token&.type == Token::Type::ON
569
- advance
570
- expect(Token::Type::CONFLICT)
571
- target = []
572
- if current_token&.type == Token::Type::LPAREN
573
- advance
574
- target << expect(Token::Type::IDENTIFIER).value
575
- while current_token&.type == Token::Type::COMMA
576
- advance
577
- target << expect(Token::Type::IDENTIFIER).value
578
- end
579
- expect(Token::Type::RPAREN)
580
- end
581
- expect(Token::Type::DO)
582
- if current_token&.type == Token::Type::NOTHING
583
- advance
584
- on_conflict = :nothing
585
- elsif current_token&.type == Token::Type::UPDATE
586
- advance
587
- expect(Token::Type::SET)
588
- assignments = []
589
- loop do
590
- column = expect(Token::Type::IDENTIFIER).value
591
- expect(Token::Type::EQ)
592
- assignments << AST::Assignment.new(column, parse_expression)
593
- break unless current_token&.type == Token::Type::COMMA
594
- advance
595
- end
596
- on_conflict = { action: :update, target: target, assignments: assignments }
597
- else
598
- raise ParserError, "Expected NOTHING or UPDATE after ON CONFLICT DO"
599
- end
600
- end
566
+
567
+ on_conflict = nil
568
+ if current_token&.type == Token::Type::ON
569
+ advance
570
+ expect(Token::Type::CONFLICT)
571
+ target = []
572
+ if current_token&.type == Token::Type::LPAREN
573
+ advance
574
+ target << expect(Token::Type::IDENTIFIER).value
575
+ while current_token&.type == Token::Type::COMMA
576
+ advance
577
+ target << expect(Token::Type::IDENTIFIER).value
578
+ end
579
+ expect(Token::Type::RPAREN)
580
+ end
581
+ expect(Token::Type::DO)
582
+ if current_token&.type == Token::Type::NOTHING
583
+ advance
584
+ on_conflict = :nothing
585
+ elsif current_token&.type == Token::Type::UPDATE
586
+ advance
587
+ expect(Token::Type::SET)
588
+ assignments = []
589
+ loop do
590
+ column = expect(Token::Type::IDENTIFIER).value
591
+ expect(Token::Type::EQ)
592
+ assignments << AST::Assignment.new(column, parse_expression)
593
+ break unless current_token&.type == Token::Type::COMMA
594
+ advance
595
+ end
596
+ on_conflict = {action: :update, target: target, assignments: assignments}
597
+ else
598
+ raise ParserError, "Expected NOTHING or UPDATE after ON CONFLICT DO"
599
+ end
600
+ end
601
601
  AST::Insert.new(table, columns, rows.first || [], rows: rows, on_conflict: on_conflict,
602
- default_values: default_values)
602
+ default_values: default_values)
603
+ end
604
+
605
+ def parse_update
606
+ expect(Token::Type::UPDATE)
607
+ table = expect(Token::Type::IDENTIFIER).value
608
+ expect(Token::Type::SET)
609
+
610
+ assignments = []
611
+ loop do
612
+ column = expect(Token::Type::IDENTIFIER).value
613
+ expect(Token::Type::EQ)
614
+ value = parse_expression
615
+ assignments << AST::Assignment.new(column, value)
616
+ break unless current_token&.type == Token::Type::COMMA
617
+ advance
618
+ end
619
+
620
+ where = nil
621
+ if current_token&.type == Token::Type::WHERE
622
+ advance
623
+ where = parse_expression
624
+ end
625
+
626
+ AST::Update.new(table, assignments, where)
627
+ end
628
+
629
+ def parse_delete
630
+ expect(Token::Type::DELETE)
631
+ expect(Token::Type::FROM)
632
+ table = expect(Token::Type::IDENTIFIER).value
633
+
634
+ where = nil
635
+ if current_token&.type == Token::Type::WHERE
636
+ advance
637
+ where = parse_expression
638
+ end
639
+
640
+ AST::Delete.new(table, where)
641
+ end
642
+
643
+ def parse_create
644
+ expect(Token::Type::CREATE)
645
+ case current_token&.type
646
+ when Token::Type::TABLE
647
+ parse_create_table
648
+ when Token::Type::INDEX
649
+ parse_create_index
650
+ when Token::Type::UNIQUE
651
+ advance
652
+ parse_create_index(unique: true)
653
+ when Token::Type::DATABASE
654
+ parse_create_database
655
+ when Token::Type::SCHEMA
656
+ parse_create_schema
657
+ when Token::Type::VIEW
658
+ parse_create_view
659
+ when Token::Type::TRIGGER
660
+ parse_create_trigger
661
+ else
662
+ raise ParserError, "Unexpected CREATE type: #{current_token}"
663
+ end
664
+ end
665
+
666
+ def parse_create_table
667
+ advance # TABLE
668
+ if_not_exists = false
669
+ if current_token&.type == Token::Type::IF
670
+ advance
671
+ expect(Token::Type::NOT)
672
+ expect(Token::Type::EXISTS)
673
+ if_not_exists = true
674
+ end
675
+ table_name = expect(Token::Type::IDENTIFIER).value
676
+ expect(Token::Type::LPAREN)
677
+
678
+ columns = []
679
+ constraints = []
680
+
681
+ while current_token&.type != Token::Type::RPAREN
682
+ if current_token&.type == Token::Type::CONSTRAINT
683
+ advance
684
+ constraint_name = expect(Token::Type::IDENTIFIER).value
685
+ constraint = parse_constraint(constraint_name)
686
+ constraints << constraint
687
+ elsif current_token&.type == Token::Type::PRIMARY ||
688
+ current_token&.type == Token::Type::FOREIGN ||
689
+ current_token&.type == Token::Type::UNIQUE ||
690
+ current_token&.type == Token::Type::CHECK
691
+ constraint = parse_constraint
692
+ constraints << constraint if constraint
693
+ else
694
+ # Column definition
695
+ column_name = expect(Token::Type::IDENTIFIER).value
696
+ column_type = expect_data_type
697
+ options = parse_column_options
698
+ columns << AST::ColumnDefinition.new(column_name, column_type, options)
699
+ end
700
+
701
+ break unless current_token&.type == Token::Type::COMMA
702
+ advance
703
+ end
704
+
705
+ expect(Token::Type::RPAREN)
706
+
707
+ AST::CreateTable.new(table_name, columns, constraints, if_not_exists: if_not_exists)
708
+ end
709
+
710
+ def parse_data_type
711
+ case current_token&.type
712
+ when Token::Type::INTEGER
713
+ advance
714
+ :integer
715
+ when Token::Type::BIGINT
716
+ advance
717
+ :bigint
718
+ when Token::Type::SMALLINT
719
+ advance
720
+ :smallint
721
+ when Token::Type::FLOAT
722
+ advance
723
+ :float
724
+ when Token::Type::DECIMAL
725
+ advance
726
+ if current_token&.type == Token::Type::LPAREN
727
+ advance
728
+ precision = expect(Token::Type::NUMBER).value
729
+ scale = 0
730
+ if current_token&.type == Token::Type::COMMA
731
+ advance
732
+ scale = expect(Token::Type::NUMBER).value
733
+ end
734
+ expect(Token::Type::RPAREN)
735
+ {type: :decimal, precision: precision, scale: scale}
736
+ else
737
+ :decimal
738
+ end
739
+ when Token::Type::BOOLEAN
740
+ advance
741
+ :boolean
742
+ when Token::Type::TEXT
743
+ advance
744
+ :text
745
+ when Token::Type::VARCHAR
746
+ advance
747
+ if current_token&.type == Token::Type::LPAREN
748
+ advance
749
+ limit = expect(Token::Type::NUMBER).value
750
+ expect(Token::Type::RPAREN)
751
+ {type: :varchar, limit: limit}
752
+ else
753
+ {type: :varchar, limit: 255}
754
+ end
755
+ when Token::Type::BLOB
756
+ advance
757
+ :blob
758
+ when Token::Type::DATE
759
+ advance
760
+ :date
761
+ when Token::Type::TIME
762
+ advance
763
+ :time
764
+ when Token::Type::TIMESTAMP
765
+ advance
766
+ :timestamp
767
+ when Token::Type::JSON
768
+ advance
769
+ :json
770
+ when Token::Type::UUID
771
+ advance
772
+ :uuid
773
+ else
774
+ raise ParserError, "Expected data type, got: #{current_token}"
775
+ end
776
+ end
777
+
778
+ def parse_column_options
779
+ options = {}
780
+ loop do
781
+ case current_token&.type
782
+ when Token::Type::PRIMARY
783
+ advance
784
+ expect(Token::Type::KEY)
785
+ options[:primary_key] = true
786
+ when Token::Type::UNIQUE
787
+ advance
788
+ options[:unique] = true
789
+ when Token::Type::NOT
790
+ advance
791
+ expect(Token::Type::NULL)
792
+ options[:null] = false
793
+ when Token::Type::NULL
794
+ advance
795
+ options[:null] = true
796
+ when Token::Type::DEFAULT
797
+ advance
798
+ options[:default] = parse_expression
799
+ when Token::Type::REFERENCES
800
+ advance
801
+ ref_table = expect(Token::Type::IDENTIFIER).value
802
+ expect(Token::Type::LPAREN)
803
+ ref_column = expect(Token::Type::IDENTIFIER).value
804
+ expect(Token::Type::RPAREN)
805
+ options[:references] = {table: ref_table, column: ref_column}
806
+ when Token::Type::IDENTIFIER
807
+ # SQLite spells an automatically allocated integer primary key
808
+ # `AUTOINCREMENT`. Keep it as column metadata; RubyDB's durable
809
+ # integer-primary-key allocator provides the actual behavior.
810
+ if current_token.value.to_s.upcase == "AUTOINCREMENT"
811
+ advance
812
+ options[:auto_increment] = true
813
+ else
814
+ break
815
+ end
816
+ else
817
+ break
818
+ end
819
+ end
820
+ options
821
+ end
822
+
823
+ def parse_referential_action(kind)
824
+ return nil unless current_token&.type == Token::Type::ON
825
+ advance
826
+ expected = (kind == :delete) ? Token::Type::DELETE : Token::Type::UPDATE
827
+ expect(expected)
828
+ case current_token&.type
829
+ when Token::Type::CASCADE
830
+ advance
831
+ :cascade
832
+ when Token::Type::RESTRICT
833
+ advance
834
+ :restrict
835
+ when Token::Type::SET
836
+ advance
837
+ expect(Token::Type::NULL) if current_token&.type == Token::Type::NULL
838
+ if current_token&.type == Token::Type::DEFAULT
839
+ advance
840
+ :set_default
841
+ else
842
+ :set_null
843
+ end
844
+ else
845
+ raise ParserError, "Expected referential action, got #{current_token}"
846
+ end
847
+ end
848
+
849
+ def parse_constraint(name = nil)
850
+ case current_token&.type
851
+ when Token::Type::PRIMARY
852
+ advance
853
+ expect(Token::Type::KEY)
854
+ expect(Token::Type::LPAREN)
855
+ columns = []
856
+ loop do
857
+ columns << expect(Token::Type::IDENTIFIER).value
858
+ break unless current_token&.type == Token::Type::COMMA
859
+ advance
860
+ end
861
+ expect(Token::Type::RPAREN)
862
+ AST::PrimaryKeyConstraint.new(name, columns)
863
+ when Token::Type::FOREIGN
864
+ advance
865
+ expect(Token::Type::KEY)
866
+ expect(Token::Type::LPAREN)
867
+ columns = []
868
+ loop do
869
+ columns << expect(Token::Type::IDENTIFIER).value
870
+ break unless current_token&.type == Token::Type::COMMA
871
+ advance
872
+ end
873
+ expect(Token::Type::RPAREN)
874
+ expect(Token::Type::REFERENCES)
875
+ ref_table = expect(Token::Type::IDENTIFIER).value
876
+ expect(Token::Type::LPAREN)
877
+ ref_columns = []
878
+ loop do
879
+ ref_columns << expect(Token::Type::IDENTIFIER).value
880
+ break unless current_token&.type == Token::Type::COMMA
881
+ advance
882
+ end
883
+ expect(Token::Type::RPAREN)
884
+ on_delete = parse_referential_action(:delete)
885
+ on_update = parse_referential_action(:update)
886
+ AST::ForeignKeyConstraint.new(name, columns, ref_table, ref_columns, on_delete: on_delete, on_update: on_update)
887
+ when Token::Type::UNIQUE
888
+ advance
889
+ expect(Token::Type::LPAREN)
890
+ columns = []
891
+ loop do
892
+ columns << expect(Token::Type::IDENTIFIER).value
893
+ break unless current_token&.type == Token::Type::COMMA
894
+ advance
895
+ end
896
+ expect(Token::Type::RPAREN)
897
+ AST::UniqueConstraint.new(name, columns)
898
+ when Token::Type::CHECK
899
+ advance
900
+ expect(Token::Type::LPAREN)
901
+ condition = parse_expression
902
+ expect(Token::Type::RPAREN)
903
+ AST::CheckConstraint.new(name, condition)
904
+ end
905
+ end
906
+
907
+ def parse_create_index(unique: false)
908
+ advance # INDEX
909
+ if_not_exists = false
910
+ if current_token&.type == Token::Type::IF
911
+ advance
912
+ expect(Token::Type::NOT)
913
+ expect(Token::Type::EXISTS)
914
+ if_not_exists = true
915
+ end
916
+ index_name = expect(Token::Type::IDENTIFIER).value
917
+ expect(Token::Type::ON)
918
+ table_name = expect(Token::Type::IDENTIFIER).value
919
+ expect(Token::Type::LPAREN)
920
+ columns = []
921
+ loop do
922
+ columns << expect(Token::Type::IDENTIFIER).value
923
+ break unless current_token&.type == Token::Type::COMMA
924
+ advance
925
+ end
926
+ expect(Token::Type::RPAREN)
927
+
928
+ # Legacy grammar accepted UNIQUE after the column list; retain it.
929
+ if current_token&.type == Token::Type::UNIQUE
930
+ unique = true
931
+ advance
932
+ end
933
+
934
+ AST::CreateIndex.new(index_name, table_name, columns, unique: unique, if_not_exists: if_not_exists)
935
+ end
936
+
937
+ def parse_create_database
938
+ advance # DATABASE
939
+ if_not_exists = false
940
+ if current_token&.type == Token::Type::IF
941
+ advance
942
+ expect(Token::Type::NOT)
943
+ expect(Token::Type::EXISTS)
944
+ if_not_exists = true
945
+ end
946
+ db_name = expect(Token::Type::IDENTIFIER).value
947
+ AST::CreateDatabase.new(db_name, if_not_exists: if_not_exists)
948
+ end
949
+
950
+ def parse_create_schema
951
+ advance
952
+ if_not_exists = false
953
+ if current_token&.type == Token::Type::IF
954
+ advance
955
+ expect(Token::Type::NOT)
956
+ expect(Token::Type::EXISTS)
957
+ if_not_exists = true
958
+ end
959
+ name = expect(Token::Type::IDENTIFIER).value
960
+ AST::CreateSchema.new(name, if_not_exists: if_not_exists)
961
+ end
962
+
963
+ def parse_create_view
964
+ advance
965
+ if_not_exists = false
966
+ if current_token&.type == Token::Type::IF
967
+ advance
968
+ expect(Token::Type::NOT)
969
+ expect(Token::Type::EXISTS)
970
+ if_not_exists = true
971
+ end
972
+ name = expect(Token::Type::IDENTIFIER).value
973
+ expect(Token::Type::AS)
974
+ AST::CreateView.new(name, parse_select, if_not_exists: if_not_exists)
975
+ end
976
+
977
+ def parse_create_trigger
978
+ advance
979
+ name = expect(Token::Type::IDENTIFIER).value
980
+ timing = if current_token&.type == Token::Type::BEFORE
981
+ advance
982
+ :before
983
+ else
984
+ expect(Token::Type::AFTER)
985
+ :after
986
+ end
987
+ event_token = current_token
988
+ unless [Token::Type::INSERT, Token::Type::UPDATE, Token::Type::DELETE].include?(event_token&.type)
989
+ raise ParserError, "Expected trigger event, got: #{event_token}"
990
+ end
991
+ advance
992
+ expect(Token::Type::ON)
993
+ table_name = expect(Token::Type::IDENTIFIER).value
994
+ expect(Token::Type::EXECUTE)
995
+ expect(Token::Type::FUNCTION)
996
+ function_name = expect(Token::Type::IDENTIFIER).value
997
+ expect(Token::Type::LPAREN)
998
+ expect(Token::Type::RPAREN)
999
+ AST::CreateTrigger.new(name, timing, event_token.value.to_s.downcase.to_sym, table_name, function_name)
1000
+ end
1001
+
1002
+ def parse_drop
1003
+ expect(Token::Type::DROP)
1004
+ case current_token&.type
1005
+ when Token::Type::TABLE
1006
+ advance
1007
+ if_exists = false
1008
+ if current_token&.type == Token::Type::IF
1009
+ advance
1010
+ expect(Token::Type::EXISTS)
1011
+ if_exists = true
1012
+ end
1013
+ table_name = expect(Token::Type::IDENTIFIER).value
1014
+ AST::DropTable.new(table_name, if_exists: if_exists)
1015
+ when Token::Type::INDEX
1016
+ advance
1017
+ if_exists = false
1018
+ if current_token&.type == Token::Type::IF
1019
+ advance
1020
+ expect(Token::Type::EXISTS)
1021
+ if_exists = true
1022
+ end
1023
+ index_name = expect(Token::Type::IDENTIFIER).value
1024
+ AST::DropIndex.new(index_name, if_exists: if_exists)
1025
+ when Token::Type::DATABASE
1026
+ advance
1027
+ if_exists = false
1028
+ if current_token&.type == Token::Type::IF
1029
+ advance
1030
+ expect(Token::Type::EXISTS)
1031
+ if_exists = true
1032
+ end
1033
+ db_name = expect(Token::Type::IDENTIFIER).value
1034
+ AST::DropDatabase.new(db_name, if_exists: if_exists)
1035
+ when Token::Type::SCHEMA
1036
+ advance
1037
+ if_exists = false
1038
+ if current_token&.type == Token::Type::IF
1039
+ advance
1040
+ expect(Token::Type::EXISTS)
1041
+ if_exists = true
1042
+ end
1043
+ name = expect(Token::Type::IDENTIFIER).value
1044
+ cascade = false
1045
+ if current_token&.type == Token::Type::CASCADE
1046
+ advance
1047
+ cascade = true
1048
+ end
1049
+ AST::DropSchema.new(name, if_exists: if_exists, cascade: cascade)
1050
+ when Token::Type::VIEW
1051
+ advance
1052
+ if_exists = false
1053
+ if current_token&.type == Token::Type::IF
1054
+ advance
1055
+ expect(Token::Type::EXISTS)
1056
+ if_exists = true
1057
+ end
1058
+ AST::DropView.new(expect(Token::Type::IDENTIFIER).value, if_exists: if_exists)
1059
+ when Token::Type::TRIGGER
1060
+ advance
1061
+ if_exists = false
1062
+ if current_token&.type == Token::Type::IF
1063
+ advance
1064
+ expect(Token::Type::EXISTS)
1065
+ if_exists = true
1066
+ end
1067
+ AST::DropTrigger.new(expect(Token::Type::IDENTIFIER).value, if_exists: if_exists)
1068
+ else
1069
+ raise ParserError, "Unexpected DROP type: #{current_token}"
1070
+ end
1071
+ end
1072
+
1073
+ def parse_alter
1074
+ expect(Token::Type::ALTER)
1075
+ expect(Token::Type::TABLE)
1076
+ table_name = expect(Token::Type::IDENTIFIER).value
1077
+
1078
+ case current_token&.type
1079
+ when Token::Type::ADD
1080
+ advance
1081
+ skip_optional_column_keyword
1082
+ if current_token&.type == Token::Type::CONSTRAINT
1083
+ advance
1084
+ constraint_name = expect(Token::Type::IDENTIFIER).value
1085
+ constraint = parse_constraint(constraint_name)
1086
+ AST::AlterTableAddConstraint.new(table_name, constraint)
1087
+ else
1088
+ column_name = expect(Token::Type::IDENTIFIER).value
1089
+ column_type = parse_data_type
1090
+ options = parse_column_options
1091
+ AST::AlterTableAddColumn.new(table_name, column_name, column_type, options)
1092
+ end
1093
+ when Token::Type::DROP
1094
+ advance
1095
+ skip_optional_column_keyword
1096
+ if current_token&.type == Token::Type::CONSTRAINT
1097
+ advance
1098
+ constraint_name = expect(Token::Type::IDENTIFIER).value
1099
+ AST::AlterTableDropConstraint.new(table_name, constraint_name)
1100
+ else
1101
+ column_name = expect(Token::Type::IDENTIFIER).value
1102
+ AST::AlterTableDropColumn.new(table_name, column_name)
1103
+ end
1104
+ else
1105
+ raise ParserError, "Unexpected ALTER TABLE operation: #{current_token}"
1106
+ end
1107
+ end
1108
+
1109
+ def parse_begin
1110
+ expect(Token::Type::BEGIN_TRANSACTION)
1111
+ if current_token&.type == Token::Type::TRANSACTION
1112
+ advance
1113
+ end
1114
+ AST::BeginTransaction.new
1115
+ end
1116
+
1117
+ def skip_optional_column_keyword
1118
+ return unless current_token&.type == Token::Type::IDENTIFIER
1119
+ return unless current_token.value.to_s.casecmp("COLUMN").zero?
1120
+
1121
+ advance
1122
+ end
1123
+
1124
+ def parse_commit
1125
+ expect(Token::Type::COMMIT)
1126
+ if current_token&.type == Token::Type::TRANSACTION
1127
+ advance
1128
+ end
1129
+ AST::Commit.new
1130
+ end
1131
+
1132
+ def parse_rollback
1133
+ expect(Token::Type::ROLLBACK)
1134
+ if current_token&.type == Token::Type::TRANSACTION
1135
+ advance
1136
+ end
1137
+ if current_token&.type == Token::Type::TO
1138
+ advance
1139
+ advance if current_token&.type == Token::Type::SAVEPOINT
1140
+ savepoint = expect(Token::Type::IDENTIFIER).value
1141
+ return AST::RollbackToSavepoint.new(savepoint)
1142
+ end
1143
+ AST::Rollback.new
1144
+ end
1145
+
1146
+ def parse_savepoint
1147
+ expect(Token::Type::SAVEPOINT)
1148
+ AST::Savepoint.new(expect(Token::Type::IDENTIFIER).value)
1149
+ end
1150
+
1151
+ def parse_release_savepoint
1152
+ expect(Token::Type::RELEASE)
1153
+ expect(Token::Type::SAVEPOINT) if current_token&.type == Token::Type::SAVEPOINT
1154
+ AST::ReleaseSavepoint.new(expect(Token::Type::IDENTIFIER).value)
1155
+ end
1156
+
1157
+ def parse_explain
1158
+ expect(Token::Type::EXPLAIN)
1159
+ if current_token&.type == Token::Type::ANALYZE
1160
+ advance
1161
+ analyze = true
1162
+ else
1163
+ analyze = false
1164
+ end
1165
+ statement = parse_statement
1166
+ AST::Explain.new(statement, analyze: analyze)
1167
+ end
1168
+
1169
+ def expect(type)
1170
+ token = current_token
1171
+ if token.nil? || token.type != type
1172
+ raise ParserError, "Expected #{type}, got #{token}"
1173
+ end
1174
+ advance
1175
+ token
1176
+ end
1177
+
1178
+ def expect_data_type
1179
+ type = parse_data_type
1180
+ unless type
1181
+ raise ParserError, "Expected data type, got #{current_token}"
1182
+ end
1183
+ type
1184
+ end
1185
+
1186
+ def advance
1187
+ @position += 1
1188
+ @current_token = @tokens[@position]
603
1189
  end
604
-
605
- def parse_update
606
- expect(Token::Type::UPDATE)
607
- table = expect(Token::Type::IDENTIFIER).value
608
- expect(Token::Type::SET)
609
-
610
- assignments = []
611
- while true
612
- column = expect(Token::Type::IDENTIFIER).value
613
- expect(Token::Type::EQ)
614
- value = parse_expression
615
- assignments << AST::Assignment.new(column, value)
616
- break unless current_token&.type == Token::Type::COMMA
617
- advance
618
- end
619
-
620
- where = nil
621
- if current_token&.type == Token::Type::WHERE
622
- advance
623
- where = parse_expression
624
- end
625
-
626
- AST::Update.new(table, assignments, where)
627
- end
628
-
629
- def parse_delete
630
- expect(Token::Type::DELETE)
631
- expect(Token::Type::FROM)
632
- table = expect(Token::Type::IDENTIFIER).value
633
-
634
- where = nil
635
- if current_token&.type == Token::Type::WHERE
636
- advance
637
- where = parse_expression
638
- end
639
-
640
- AST::Delete.new(table, where)
641
- end
642
-
643
- def parse_create
644
- expect(Token::Type::CREATE)
645
- case current_token&.type
646
- when Token::Type::TABLE
647
- parse_create_table
648
- when Token::Type::INDEX
649
- parse_create_index
650
- when Token::Type::UNIQUE
651
- advance
652
- parse_create_index(unique: true)
653
- when Token::Type::DATABASE
654
- parse_create_database
655
- when Token::Type::SCHEMA
656
- parse_create_schema
657
- when Token::Type::VIEW
658
- parse_create_view
659
- when Token::Type::TRIGGER
660
- parse_create_trigger
661
- else
662
- raise ParserError, "Unexpected CREATE type: #{current_token}"
663
- end
664
- end
665
-
666
- def parse_create_table
667
- advance # TABLE
668
- if_not_exists = false
669
- if current_token&.type == Token::Type::IF
670
- advance
671
- expect(Token::Type::NOT)
672
- expect(Token::Type::EXISTS)
673
- if_not_exists = true
674
- end
675
- table_name = expect(Token::Type::IDENTIFIER).value
676
- expect(Token::Type::LPAREN)
677
-
678
- columns = []
679
- constraints = []
680
-
681
- while current_token&.type != Token::Type::RPAREN
682
- if current_token&.type == Token::Type::CONSTRAINT
683
- advance
684
- constraint_name = expect(Token::Type::IDENTIFIER).value
685
- constraint = parse_constraint(constraint_name)
686
- constraints << constraint
687
- elsif current_token&.type == Token::Type::PRIMARY ||
688
- current_token&.type == Token::Type::FOREIGN ||
689
- current_token&.type == Token::Type::UNIQUE ||
690
- current_token&.type == Token::Type::CHECK
691
- constraint = parse_constraint
692
- constraints << constraint if constraint
693
- else
694
- # Column definition
695
- column_name = expect(Token::Type::IDENTIFIER).value
696
- column_type = expect_data_type
697
- options = parse_column_options
698
- columns << AST::ColumnDefinition.new(column_name, column_type, options)
699
- end
700
-
701
- break unless current_token&.type == Token::Type::COMMA
702
- advance
703
- end
704
-
705
- expect(Token::Type::RPAREN)
706
-
707
- AST::CreateTable.new(table_name, columns, constraints, if_not_exists: if_not_exists)
708
- end
709
-
710
- def parse_data_type
711
- case current_token&.type
712
- when Token::Type::INTEGER
713
- advance
714
- :integer
715
- when Token::Type::BIGINT
716
- advance
717
- :bigint
718
- when Token::Type::SMALLINT
719
- advance
720
- :smallint
721
- when Token::Type::FLOAT
722
- advance
723
- :float
724
- when Token::Type::DECIMAL
725
- advance
726
- if current_token&.type == Token::Type::LPAREN
727
- advance
728
- precision = expect(Token::Type::NUMBER).value
729
- scale = 0
730
- if current_token&.type == Token::Type::COMMA
731
- advance
732
- scale = expect(Token::Type::NUMBER).value
733
- end
734
- expect(Token::Type::RPAREN)
735
- { type: :decimal, precision: precision, scale: scale }
736
- else
737
- :decimal
738
- end
739
- when Token::Type::BOOLEAN
740
- advance
741
- :boolean
742
- when Token::Type::TEXT
743
- advance
744
- :text
745
- when Token::Type::VARCHAR
746
- advance
747
- if current_token&.type == Token::Type::LPAREN
748
- advance
749
- limit = expect(Token::Type::NUMBER).value
750
- expect(Token::Type::RPAREN)
751
- { type: :varchar, limit: limit }
752
- else
753
- { type: :varchar, limit: 255 }
754
- end
755
- when Token::Type::BLOB
756
- advance
757
- :blob
758
- when Token::Type::DATE
759
- advance
760
- :date
761
- when Token::Type::TIME
762
- advance
763
- :time
764
- when Token::Type::TIMESTAMP
765
- advance
766
- :timestamp
767
- when Token::Type::JSON
768
- advance
769
- :json
770
- when Token::Type::UUID
771
- advance
772
- :uuid
773
- else
774
- raise ParserError, "Expected data type, got: #{current_token}"
775
- end
776
- end
777
-
778
- def parse_column_options
779
- options = {}
780
- while true
781
- case current_token&.type
782
- when Token::Type::PRIMARY
783
- advance
784
- expect(Token::Type::KEY)
785
- options[:primary_key] = true
786
- when Token::Type::UNIQUE
787
- advance
788
- options[:unique] = true
789
- when Token::Type::NOT
790
- advance
791
- expect(Token::Type::NULL)
792
- options[:null] = false
793
- when Token::Type::NULL
794
- advance
795
- options[:null] = true
796
- when Token::Type::DEFAULT
797
- advance
798
- options[:default] = parse_expression
799
- when Token::Type::REFERENCES
800
- advance
801
- ref_table = expect(Token::Type::IDENTIFIER).value
802
- expect(Token::Type::LPAREN)
803
- ref_column = expect(Token::Type::IDENTIFIER).value
804
- expect(Token::Type::RPAREN)
805
- options[:references] = { table: ref_table, column: ref_column }
806
- when Token::Type::IDENTIFIER
807
- # SQLite spells an automatically allocated integer primary key
808
- # `AUTOINCREMENT`. Keep it as column metadata; RubyDB's durable
809
- # integer-primary-key allocator provides the actual behavior.
810
- if current_token.value.to_s.upcase == "AUTOINCREMENT"
811
- advance
812
- options[:auto_increment] = true
813
- else
814
- break
815
- end
816
- else
817
- break
818
- end
819
- end
820
- options
821
- end
822
-
823
- def parse_referential_action(kind)
824
- return nil unless current_token&.type == Token::Type::ON
825
- advance
826
- expected = kind == :delete ? Token::Type::DELETE : Token::Type::UPDATE
827
- expect(expected)
828
- action = case current_token&.type
829
- when Token::Type::CASCADE
830
- advance
831
- :cascade
832
- when Token::Type::RESTRICT
833
- advance
834
- :restrict
835
- when Token::Type::SET
836
- advance
837
- expect(Token::Type::NULL) if current_token&.type == Token::Type::NULL
838
- if current_token&.type == Token::Type::DEFAULT
839
- advance
840
- :set_default
841
- else
842
- :set_null
843
- end
844
- else
845
- raise ParserError, "Expected referential action, got #{current_token}"
846
- end
847
- action
848
- end
849
-
850
- def parse_constraint(name = nil)
851
- case current_token&.type
852
- when Token::Type::PRIMARY
853
- advance
854
- expect(Token::Type::KEY)
855
- expect(Token::Type::LPAREN)
856
- columns = []
857
- while true
858
- columns << expect(Token::Type::IDENTIFIER).value
859
- break unless current_token&.type == Token::Type::COMMA
860
- advance
861
- end
862
- expect(Token::Type::RPAREN)
863
- AST::PrimaryKeyConstraint.new(name, columns)
864
- when Token::Type::FOREIGN
865
- advance
866
- expect(Token::Type::KEY)
867
- expect(Token::Type::LPAREN)
868
- columns = []
869
- while true
870
- columns << expect(Token::Type::IDENTIFIER).value
871
- break unless current_token&.type == Token::Type::COMMA
872
- advance
873
- end
874
- expect(Token::Type::RPAREN)
875
- expect(Token::Type::REFERENCES)
876
- ref_table = expect(Token::Type::IDENTIFIER).value
877
- expect(Token::Type::LPAREN)
878
- ref_columns = []
879
- while true
880
- ref_columns << expect(Token::Type::IDENTIFIER).value
881
- break unless current_token&.type == Token::Type::COMMA
882
- advance
883
- end
884
- expect(Token::Type::RPAREN)
885
- on_delete = parse_referential_action(:delete)
886
- on_update = parse_referential_action(:update)
887
- AST::ForeignKeyConstraint.new(name, columns, ref_table, ref_columns, on_delete: on_delete, on_update: on_update)
888
- when Token::Type::UNIQUE
889
- advance
890
- expect(Token::Type::LPAREN)
891
- columns = []
892
- while true
893
- columns << expect(Token::Type::IDENTIFIER).value
894
- break unless current_token&.type == Token::Type::COMMA
895
- advance
896
- end
897
- expect(Token::Type::RPAREN)
898
- AST::UniqueConstraint.new(name, columns)
899
- when Token::Type::CHECK
900
- advance
901
- expect(Token::Type::LPAREN)
902
- condition = parse_expression
903
- expect(Token::Type::RPAREN)
904
- AST::CheckConstraint.new(name, condition)
905
- else
906
- nil
907
- end
908
- end
909
-
910
- def parse_create_index(unique: false)
911
- advance # INDEX
912
- if_not_exists = false
913
- if current_token&.type == Token::Type::IF
914
- advance
915
- expect(Token::Type::NOT)
916
- expect(Token::Type::EXISTS)
917
- if_not_exists = true
918
- end
919
- index_name = expect(Token::Type::IDENTIFIER).value
920
- expect(Token::Type::ON)
921
- table_name = expect(Token::Type::IDENTIFIER).value
922
- expect(Token::Type::LPAREN)
923
- columns = []
924
- while true
925
- columns << expect(Token::Type::IDENTIFIER).value
926
- break unless current_token&.type == Token::Type::COMMA
927
- advance
928
- end
929
- expect(Token::Type::RPAREN)
930
-
931
- # Legacy grammar accepted UNIQUE after the column list; retain it.
932
- if current_token&.type == Token::Type::UNIQUE
933
- unique = true
934
- advance
935
- end
936
-
937
- AST::CreateIndex.new(index_name, table_name, columns, unique: unique, if_not_exists: if_not_exists)
938
- end
939
-
940
- def parse_create_database
941
- advance # DATABASE
942
- if_not_exists = false
943
- if current_token&.type == Token::Type::IF
944
- advance
945
- expect(Token::Type::NOT)
946
- expect(Token::Type::EXISTS)
947
- if_not_exists = true
948
- end
949
- db_name = expect(Token::Type::IDENTIFIER).value
950
- AST::CreateDatabase.new(db_name, if_not_exists: if_not_exists)
951
- end
952
-
953
- def parse_create_schema
954
- advance
955
- if_not_exists = false
956
- if current_token&.type == Token::Type::IF
957
- advance
958
- expect(Token::Type::NOT)
959
- expect(Token::Type::EXISTS)
960
- if_not_exists = true
961
- end
962
- name = expect(Token::Type::IDENTIFIER).value
963
- AST::CreateSchema.new(name, if_not_exists: if_not_exists)
964
- end
965
-
966
- def parse_create_view
967
- advance
968
- if_not_exists = false
969
- if current_token&.type == Token::Type::IF
970
- advance
971
- expect(Token::Type::NOT)
972
- expect(Token::Type::EXISTS)
973
- if_not_exists = true
974
- end
975
- name = expect(Token::Type::IDENTIFIER).value
976
- expect(Token::Type::AS)
977
- AST::CreateView.new(name, parse_select, if_not_exists: if_not_exists)
978
- end
979
-
980
- def parse_create_trigger
981
- advance
982
- name = expect(Token::Type::IDENTIFIER).value
983
- timing = if current_token&.type == Token::Type::BEFORE
984
- advance
985
- :before
986
- else
987
- expect(Token::Type::AFTER)
988
- :after
989
- end
990
- event_token = current_token
991
- unless [Token::Type::INSERT, Token::Type::UPDATE, Token::Type::DELETE].include?(event_token&.type)
992
- raise ParserError, "Expected trigger event, got: #{event_token}"
993
- end
994
- advance
995
- expect(Token::Type::ON)
996
- table_name = expect(Token::Type::IDENTIFIER).value
997
- expect(Token::Type::EXECUTE)
998
- expect(Token::Type::FUNCTION)
999
- function_name = expect(Token::Type::IDENTIFIER).value
1000
- expect(Token::Type::LPAREN)
1001
- expect(Token::Type::RPAREN)
1002
- AST::CreateTrigger.new(name, timing, event_token.value.to_s.downcase.to_sym, table_name, function_name)
1003
- end
1004
-
1005
- def parse_drop
1006
- expect(Token::Type::DROP)
1007
- case current_token&.type
1008
- when Token::Type::TABLE
1009
- advance
1010
- if_exists = false
1011
- if current_token&.type == Token::Type::IF
1012
- advance
1013
- expect(Token::Type::EXISTS)
1014
- if_exists = true
1015
- end
1016
- table_name = expect(Token::Type::IDENTIFIER).value
1017
- AST::DropTable.new(table_name, if_exists: if_exists)
1018
- when Token::Type::INDEX
1019
- advance
1020
- if_exists = false
1021
- if current_token&.type == Token::Type::IF
1022
- advance
1023
- expect(Token::Type::EXISTS)
1024
- if_exists = true
1025
- end
1026
- index_name = expect(Token::Type::IDENTIFIER).value
1027
- AST::DropIndex.new(index_name, if_exists: if_exists)
1028
- when Token::Type::DATABASE
1029
- advance
1030
- if_exists = false
1031
- if current_token&.type == Token::Type::IF
1032
- advance
1033
- expect(Token::Type::EXISTS)
1034
- if_exists = true
1035
- end
1036
- db_name = expect(Token::Type::IDENTIFIER).value
1037
- AST::DropDatabase.new(db_name, if_exists: if_exists)
1038
- when Token::Type::SCHEMA
1039
- advance
1040
- if_exists = false
1041
- if current_token&.type == Token::Type::IF
1042
- advance
1043
- expect(Token::Type::EXISTS)
1044
- if_exists = true
1045
- end
1046
- name = expect(Token::Type::IDENTIFIER).value
1047
- cascade = false
1048
- if current_token&.type == Token::Type::CASCADE
1049
- advance
1050
- cascade = true
1051
- end
1052
- AST::DropSchema.new(name, if_exists: if_exists, cascade: cascade)
1053
- when Token::Type::VIEW
1054
- advance
1055
- if_exists = false
1056
- if current_token&.type == Token::Type::IF
1057
- advance
1058
- expect(Token::Type::EXISTS)
1059
- if_exists = true
1060
- end
1061
- AST::DropView.new(expect(Token::Type::IDENTIFIER).value, if_exists: if_exists)
1062
- when Token::Type::TRIGGER
1063
- advance
1064
- if_exists = false
1065
- if current_token&.type == Token::Type::IF
1066
- advance
1067
- expect(Token::Type::EXISTS)
1068
- if_exists = true
1069
- end
1070
- AST::DropTrigger.new(expect(Token::Type::IDENTIFIER).value, if_exists: if_exists)
1071
- else
1072
- raise ParserError, "Unexpected DROP type: #{current_token}"
1073
- end
1074
- end
1075
-
1076
- def parse_alter
1077
- expect(Token::Type::ALTER)
1078
- expect(Token::Type::TABLE)
1079
- table_name = expect(Token::Type::IDENTIFIER).value
1080
-
1081
- case current_token&.type
1082
- when Token::Type::ADD
1083
- advance
1084
- skip_optional_column_keyword
1085
- if current_token&.type == Token::Type::CONSTRAINT
1086
- advance
1087
- constraint_name = expect(Token::Type::IDENTIFIER).value
1088
- constraint = parse_constraint(constraint_name)
1089
- AST::AlterTableAddConstraint.new(table_name, constraint)
1090
- else
1091
- column_name = expect(Token::Type::IDENTIFIER).value
1092
- column_type = parse_data_type
1093
- options = parse_column_options
1094
- AST::AlterTableAddColumn.new(table_name, column_name, column_type, options)
1095
- end
1096
- when Token::Type::DROP
1097
- advance
1098
- skip_optional_column_keyword
1099
- if current_token&.type == Token::Type::CONSTRAINT
1100
- advance
1101
- constraint_name = expect(Token::Type::IDENTIFIER).value
1102
- AST::AlterTableDropConstraint.new(table_name, constraint_name)
1103
- else
1104
- column_name = expect(Token::Type::IDENTIFIER).value
1105
- AST::AlterTableDropColumn.new(table_name, column_name)
1106
- end
1107
- else
1108
- raise ParserError, "Unexpected ALTER TABLE operation: #{current_token}"
1109
- end
1110
- end
1111
-
1112
- def parse_begin
1113
- expect(Token::Type::BEGIN_TRANSACTION)
1114
- if current_token&.type == Token::Type::TRANSACTION
1115
- advance
1116
- end
1117
- AST::BeginTransaction.new
1118
- end
1119
-
1120
- def skip_optional_column_keyword
1121
- return unless current_token&.type == Token::Type::IDENTIFIER
1122
- return unless current_token.value.to_s.casecmp("COLUMN").zero?
1123
-
1124
- advance
1125
- end
1126
-
1127
- def parse_commit
1128
- expect(Token::Type::COMMIT)
1129
- if current_token&.type == Token::Type::TRANSACTION
1130
- advance
1131
- end
1132
- AST::Commit.new
1133
- end
1134
-
1135
- def parse_rollback
1136
- expect(Token::Type::ROLLBACK)
1137
- if current_token&.type == Token::Type::TRANSACTION
1138
- advance
1139
- end
1140
- if current_token&.type == Token::Type::TO
1141
- advance
1142
- advance if current_token&.type == Token::Type::SAVEPOINT
1143
- savepoint = expect(Token::Type::IDENTIFIER).value
1144
- return AST::RollbackToSavepoint.new(savepoint)
1145
- end
1146
- AST::Rollback.new
1147
- end
1148
-
1149
- def parse_savepoint
1150
- expect(Token::Type::SAVEPOINT)
1151
- AST::Savepoint.new(expect(Token::Type::IDENTIFIER).value)
1152
- end
1153
-
1154
- def parse_release_savepoint
1155
- expect(Token::Type::RELEASE)
1156
- expect(Token::Type::SAVEPOINT) if current_token&.type == Token::Type::SAVEPOINT
1157
- AST::ReleaseSavepoint.new(expect(Token::Type::IDENTIFIER).value)
1158
- end
1159
-
1160
- def parse_explain
1161
- expect(Token::Type::EXPLAIN)
1162
- if current_token&.type == Token::Type::ANALYZE
1163
- advance
1164
- analyze = true
1165
- else
1166
- analyze = false
1167
- end
1168
- statement = parse_statement
1169
- AST::Explain.new(statement, analyze: analyze)
1170
- end
1171
-
1172
- def expect(type)
1173
- token = current_token
1174
- if token.nil? || token.type != type
1175
- raise ParserError, "Expected #{type}, got #{token}"
1176
- end
1177
- advance
1178
- token
1179
- end
1180
-
1181
- def expect_data_type
1182
- type = parse_data_type
1183
- unless type
1184
- raise ParserError, "Expected data type, got #{current_token}"
1185
- end
1186
- type
1187
- end
1188
-
1189
- def advance
1190
- @position += 1
1191
- @current_token = @tokens[@position]
1192
- end
1193
- end
1194
- end
1195
- end
1190
+ end
1191
+ end
1192
+ end