rubydb 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (436) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +20 -0
  3. data/.github/ISSUE_TEMPLATE/bug.yml +0 -0
  4. data/.github/ISSUE_TEMPLATE/feature.yml +0 -0
  5. data/.github/ISSUE_TEMPLATE/performance.yml +0 -0
  6. data/.github/ISSUE_TEMPLATE/security.yml +0 -0
  7. data/.github/PULL_REQUEST_TEMPLATE.md +15 -0
  8. data/.github/dependabot.yml +0 -0
  9. data/.github/workflows/benchmark.yml +26 -0
  10. data/.github/workflows/compatibility.yml +63 -0
  11. data/.github/workflows/fuzz.yml +33 -0
  12. data/.github/workflows/lint.yml +21 -0
  13. data/.github/workflows/operations.yml +24 -0
  14. data/.github/workflows/production-validation.yml +111 -0
  15. data/.github/workflows/release.yml +77 -0
  16. data/.github/workflows/security.yml +37 -0
  17. data/.github/workflows/test.yml +26 -0
  18. data/.github/workflows/workload.yml +58 -0
  19. data/.gitignore +43 -0
  20. data/.rspec +4 -0
  21. data/.rubocop.yml +44 -0
  22. data/.ruby-version +1 -0
  23. data/.standard.yml +14 -0
  24. data/ARCHITECTURE.md +21 -0
  25. data/CHANGELOG.md +31 -0
  26. data/CODE_OF_CONDUCT.md +13 -0
  27. data/CONTRIBUTING.md +29 -0
  28. data/GOVERNANCE.md +16 -0
  29. data/Gemfile +17 -0
  30. data/Gemfile.lock +71 -0
  31. data/LICENSE +60 -0
  32. data/README.md +162 -0
  33. data/ROADMAP.md +27 -0
  34. data/Rakefile +71 -0
  35. data/SECURITY.md +54 -0
  36. data/SUPPORT.md +14 -0
  37. data/adapters/activerecord/Gemfile +11 -0
  38. data/adapters/activerecord/README.md +9 -0
  39. data/adapters/activerecord/lib/active_record/connection_adapters/rubydb_adapter.rb +887 -0
  40. data/adapters/activerecord/rubydb-activerecord.gemspec +27 -0
  41. data/adapters/activerecord/spec/rubydb_adapter_integration_spec.rb +151 -0
  42. data/adapters/ruby/README.md +18 -0
  43. data/adapters/sequel/README.md +11 -0
  44. data/bin/rubydb +19 -0
  45. data/bin/rubydb-server +126 -0
  46. data/config/development.yml +0 -0
  47. data/config/monitoring/prometheus-alerts.yml +39 -0
  48. data/config/production.yml +36 -0
  49. data/config/test.yml +0 -0
  50. data/docs/README.md +71 -0
  51. data/docs/architecture/concurrency.md +14 -0
  52. data/docs/architecture/current-state.md +125 -0
  53. data/docs/architecture/execution-engine.md +25 -0
  54. data/docs/architecture/indexes.md +19 -0
  55. data/docs/architecture/mvcc.md +19 -0
  56. data/docs/architecture/overview.md +13 -0
  57. data/docs/architecture/pages.md +11 -0
  58. data/docs/architecture/production-roadmap.md +82 -0
  59. data/docs/architecture/query-planner.md +20 -0
  60. data/docs/architecture/recovery.md +18 -0
  61. data/docs/architecture/sql-engine.md +12 -0
  62. data/docs/architecture/storage-engine.md +14 -0
  63. data/docs/architecture/transactions.md +10 -0
  64. data/docs/architecture/wal.md +28 -0
  65. data/docs/cli-cheatsheet.md +98 -0
  66. data/docs/cli.md +275 -0
  67. data/docs/contributing/architecture.md +9 -0
  68. data/docs/contributing/benchmarking.md +14 -0
  69. data/docs/contributing/development.md +16 -0
  70. data/docs/contributing/release-process.md +49 -0
  71. data/docs/contributing/testing.md +16 -0
  72. data/docs/debugging.md +229 -0
  73. data/docs/developer/branching.md +10 -0
  74. data/docs/developer/database-diff.md +10 -0
  75. data/docs/developer/local-development.md +17 -0
  76. data/docs/developer/snapshots.md +9 -0
  77. data/docs/developer/temporal-data.md +10 -0
  78. data/docs/developer-guide.md +297 -0
  79. data/docs/getting-started/first-database.md +16 -0
  80. data/docs/getting-started/first-query.md +13 -0
  81. data/docs/getting-started/installation.md +19 -0
  82. data/docs/getting-started/local-to-production.md +300 -0
  83. data/docs/getting-started/quickstart.md +17 -0
  84. data/docs/getting-started/rails.md +16 -0
  85. data/docs/hardening_backlog.md +93 -0
  86. data/docs/lessons-learned.md +112 -0
  87. data/docs/operations/backups.md +33 -0
  88. data/docs/operations/disaster-recovery.md +31 -0
  89. data/docs/operations/failover.md +30 -0
  90. data/docs/operations/monitoring.md +25 -0
  91. data/docs/operations/production-guide.md +295 -0
  92. data/docs/operations/production-runbook.md +45 -0
  93. data/docs/operations/replication.md +33 -0
  94. data/docs/operations/restore.md +6 -0
  95. data/docs/operations/runbook.md +34 -0
  96. data/docs/operations/upgrades.md +14 -0
  97. data/docs/operations/workload-testing.md +17 -0
  98. data/docs/production-readiness.md +118 -0
  99. data/docs/production_validation.md +150 -0
  100. data/docs/rails/active-record.md +11 -0
  101. data/docs/rails/compatibility-guide.md +90 -0
  102. data/docs/rails/database-yml.md +92 -0
  103. data/docs/rails/installation.md +17 -0
  104. data/docs/rails/migrations.md +17 -0
  105. data/docs/rails/production.md +82 -0
  106. data/docs/rails/troubleshooting.md +18 -0
  107. data/docs/release.md +59 -0
  108. data/docs/server/architecture.md +10 -0
  109. data/docs/server/authentication.md +10 -0
  110. data/docs/server/configuration.md +16 -0
  111. data/docs/server/connection-pooling.md +10 -0
  112. data/docs/server/deployment.md +10 -0
  113. data/docs/server/protocol.md +12 -0
  114. data/docs/sql/compatibility-guide.md +82 -0
  115. data/docs/sql/compatibility.md +39 -0
  116. data/docs/sql/data-types.md +10 -0
  117. data/docs/sql/functions.md +9 -0
  118. data/docs/sql/joins.md +9 -0
  119. data/docs/sql/operators.md +9 -0
  120. data/docs/sql/sqlite-compatibility.md +21 -0
  121. data/docs/sql/syntax.md +10 -0
  122. data/docs/sql/transactions.md +10 -0
  123. data/docs/troubleshooting.md +244 -0
  124. data/exe/rubydb +23 -0
  125. data/lib/rubydb/backup/archive.rb +334 -0
  126. data/lib/rubydb/backup/backup.rb +401 -0
  127. data/lib/rubydb/backup/incremental.rb +353 -0
  128. data/lib/rubydb/backup/restore.rb +290 -0
  129. data/lib/rubydb/backup/snapshot.rb +267 -0
  130. data/lib/rubydb/backup/verification.rb +279 -0
  131. data/lib/rubydb/branching/branch.rb +181 -0
  132. data/lib/rubydb/branching/branch_manager.rb +311 -0
  133. data/lib/rubydb/branching/branch_metadata.rb +140 -0
  134. data/lib/rubydb/branching/checkout.rb +166 -0
  135. data/lib/rubydb/branching/copy_on_write.rb +272 -0
  136. data/lib/rubydb/branching/diff.rb +138 -0
  137. data/lib/rubydb/branching/merge.rb +285 -0
  138. data/lib/rubydb/build_info.rb +15 -0
  139. data/lib/rubydb/catalog/catalog.rb +391 -0
  140. data/lib/rubydb/catalog/column.rb +112 -0
  141. data/lib/rubydb/catalog/constraint.rb +180 -0
  142. data/lib/rubydb/catalog/database.rb +184 -0
  143. data/lib/rubydb/catalog/index.rb +97 -0
  144. data/lib/rubydb/catalog/schema.rb +103 -0
  145. data/lib/rubydb/catalog/sequence.rb +90 -0
  146. data/lib/rubydb/catalog/system_catalog.rb +698 -0
  147. data/lib/rubydb/catalog/table.rb +178 -0
  148. data/lib/rubydb/catalog/trigger.rb +102 -0
  149. data/lib/rubydb/catalog/view.rb +66 -0
  150. data/lib/rubydb/cli/application.rb +163 -0
  151. data/lib/rubydb/cli/commands/backup.rb +81 -0
  152. data/lib/rubydb/cli/commands/branch.rb +72 -0
  153. data/lib/rubydb/cli/commands/checkout.rb +54 -0
  154. data/lib/rubydb/cli/commands/create.rb +58 -0
  155. data/lib/rubydb/cli/commands/diff.rb +77 -0
  156. data/lib/rubydb/cli/commands/doctor.rb +74 -0
  157. data/lib/rubydb/cli/commands/drop.rb +57 -0
  158. data/lib/rubydb/cli/commands/init.rb +102 -0
  159. data/lib/rubydb/cli/commands/inspect.rb +95 -0
  160. data/lib/rubydb/cli/commands/merge.rb +63 -0
  161. data/lib/rubydb/cli/commands/migrate.rb +62 -0
  162. data/lib/rubydb/cli/commands/restart.rb +39 -0
  163. data/lib/rubydb/cli/commands/restore.rb +121 -0
  164. data/lib/rubydb/cli/commands/shell.rb +365 -0
  165. data/lib/rubydb/cli/commands/snapshot.rb +79 -0
  166. data/lib/rubydb/cli/commands/start.rb +82 -0
  167. data/lib/rubydb/cli/commands/status.rb +92 -0
  168. data/lib/rubydb/cli/commands/stop.rb +47 -0
  169. data/lib/rubydb/cli/commands/vacuum.rb +58 -0
  170. data/lib/rubydb/cli/formatter.rb +221 -0
  171. data/lib/rubydb/cli/output.rb +168 -0
  172. data/lib/rubydb/client/client.rb +304 -0
  173. data/lib/rubydb/client/connection.rb +415 -0
  174. data/lib/rubydb/client/connection_pool.rb +168 -0
  175. data/lib/rubydb/client/connection_url.rb +96 -0
  176. data/lib/rubydb/client/prepared_statement.rb +60 -0
  177. data/lib/rubydb/client/result.rb +123 -0
  178. data/lib/rubydb/client/statement.rb +52 -0
  179. data/lib/rubydb/client/transaction.rb +130 -0
  180. data/lib/rubydb/concurrency/concurrency.rb +19 -0
  181. data/lib/rubydb/concurrency/deadlock_detector.rb +150 -0
  182. data/lib/rubydb/concurrency/latch.rb +101 -0
  183. data/lib/rubydb/concurrency/lock_graph.rb +165 -0
  184. data/lib/rubydb/concurrency/mutex.rb +183 -0
  185. data/lib/rubydb/concurrency/rw_lock.rb +180 -0
  186. data/lib/rubydb/concurrency/scheduler.rb +250 -0
  187. data/lib/rubydb/concurrency/worker_pool.rb +143 -0
  188. data/lib/rubydb/configuration/config.rb +170 -0
  189. data/lib/rubydb/configuration/defaults.rb +179 -0
  190. data/lib/rubydb/configuration/environment.rb +152 -0
  191. data/lib/rubydb/configuration/parser.rb +185 -0
  192. data/lib/rubydb/configuration/validation.rb +221 -0
  193. data/lib/rubydb/constants.rb +74 -0
  194. data/lib/rubydb/constraints/check.rb +181 -0
  195. data/lib/rubydb/constraints/constraint.rb +101 -0
  196. data/lib/rubydb/constraints/foreign_key.rb +130 -0
  197. data/lib/rubydb/constraints/not_null.rb +64 -0
  198. data/lib/rubydb/constraints/primary_key.rb +99 -0
  199. data/lib/rubydb/constraints/unique.rb +108 -0
  200. data/lib/rubydb/constraints/validator.rb +350 -0
  201. data/lib/rubydb/errors/authentication_error.rb +10 -0
  202. data/lib/rubydb/errors/authorization_error.rb +23 -0
  203. data/lib/rubydb/errors/client_error.rb +10 -0
  204. data/lib/rubydb/errors/configuration_error.rb +10 -0
  205. data/lib/rubydb/errors/connection_error.rb +10 -0
  206. data/lib/rubydb/errors/constraint_error.rb +23 -0
  207. data/lib/rubydb/errors/corruption_error.rb +10 -0
  208. data/lib/rubydb/errors/database_error.rb +10 -0
  209. data/lib/rubydb/errors/error.rb +20 -0
  210. data/lib/rubydb/errors/execution_error.rb +10 -0
  211. data/lib/rubydb/errors/parser_error.rb +10 -0
  212. data/lib/rubydb/errors/recovery_error.rb +10 -0
  213. data/lib/rubydb/errors/replication_error.rb +10 -0
  214. data/lib/rubydb/errors/server_error.rb +6 -0
  215. data/lib/rubydb/errors/storage_error.rb +10 -0
  216. data/lib/rubydb/errors/transaction_error.rb +10 -0
  217. data/lib/rubydb/execution/aggregate_executor.rb +138 -0
  218. data/lib/rubydb/execution/delete_executor.rb +112 -0
  219. data/lib/rubydb/execution/distinct_executor.rb +135 -0
  220. data/lib/rubydb/execution/executor.rb +1192 -0
  221. data/lib/rubydb/execution/expression.rb +193 -0
  222. data/lib/rubydb/execution/index_scan.rb +142 -0
  223. data/lib/rubydb/execution/insert_executor.rb +217 -0
  224. data/lib/rubydb/execution/join_executor.rb +249 -0
  225. data/lib/rubydb/execution/limit_executor.rb +85 -0
  226. data/lib/rubydb/execution/optimizer.rb +215 -0
  227. data/lib/rubydb/execution/plan.rb +361 -0
  228. data/lib/rubydb/execution/planner.rb +546 -0
  229. data/lib/rubydb/execution/predicate.rb +235 -0
  230. data/lib/rubydb/execution/scan.rb +49 -0
  231. data/lib/rubydb/execution/sequential_scan.rb +63 -0
  232. data/lib/rubydb/execution/sort_executor.rb +185 -0
  233. data/lib/rubydb/execution/update_executor.rb +162 -0
  234. data/lib/rubydb/functions/aggregate.rb +70 -0
  235. data/lib/rubydb/functions/date_functions.rb +278 -0
  236. data/lib/rubydb/functions/function.rb +85 -0
  237. data/lib/rubydb/functions/json_functions.rb +215 -0
  238. data/lib/rubydb/functions/numeric_functions.rb +346 -0
  239. data/lib/rubydb/functions/scalar.rb +52 -0
  240. data/lib/rubydb/functions/string_functions.rb +383 -0
  241. data/lib/rubydb/functions/system_functions.rb +246 -0
  242. data/lib/rubydb/history/as_of.rb +238 -0
  243. data/lib/rubydb/history/change.rb +105 -0
  244. data/lib/rubydb/history/history.rb +131 -0
  245. data/lib/rubydb/history/history_manager.rb +229 -0
  246. data/lib/rubydb/history/temporal_query.rb +202 -0
  247. data/lib/rubydb/history/timeline.rb +144 -0
  248. data/lib/rubydb/indexes/btree.rb +186 -0
  249. data/lib/rubydb/indexes/btree_cursor.rb +258 -0
  250. data/lib/rubydb/indexes/btree_node.rb +385 -0
  251. data/lib/rubydb/indexes/hash_index.rb +150 -0
  252. data/lib/rubydb/indexes/index.rb +71 -0
  253. data/lib/rubydb/indexes/index_manager.rb +406 -0
  254. data/lib/rubydb/indexes/index_scan.rb +470 -0
  255. data/lib/rubydb/migrations/migration.rb +254 -0
  256. data/lib/rubydb/migrations/migration_lock.rb +146 -0
  257. data/lib/rubydb/migrations/migration_manager.rb +176 -0
  258. data/lib/rubydb/migrations/migration_version.rb +71 -0
  259. data/lib/rubydb/migrations/schema_diff.rb +211 -0
  260. data/lib/rubydb/migrations/schema_version.rb +64 -0
  261. data/lib/rubydb/monitoring/events.rb +160 -0
  262. data/lib/rubydb/monitoring/health.rb +222 -0
  263. data/lib/rubydb/monitoring/logger.rb +193 -0
  264. data/lib/rubydb/monitoring/metrics.rb +359 -0
  265. data/lib/rubydb/monitoring/performance.rb +176 -0
  266. data/lib/rubydb/monitoring/statistics.rb +170 -0
  267. data/lib/rubydb/mvcc/garbage_collector.rb +199 -0
  268. data/lib/rubydb/mvcc/mvcc.rb +16 -0
  269. data/lib/rubydb/mvcc/snapshot.rb +147 -0
  270. data/lib/rubydb/mvcc/vacuum.rb +180 -0
  271. data/lib/rubydb/mvcc/version.rb +106 -0
  272. data/lib/rubydb/mvcc/version_store.rb +398 -0
  273. data/lib/rubydb/mvcc/visibility.rb +109 -0
  274. data/lib/rubydb/protocol/capabilities.rb +125 -0
  275. data/lib/rubydb/protocol/decoder.rb +145 -0
  276. data/lib/rubydb/protocol/encoder.rb +136 -0
  277. data/lib/rubydb/protocol/handshake.rb +305 -0
  278. data/lib/rubydb/protocol/message.rb +121 -0
  279. data/lib/rubydb/protocol/protocol.rb +277 -0
  280. data/lib/rubydb/protocol/version.rb +54 -0
  281. data/lib/rubydb/rails/adapter.rb +239 -0
  282. data/lib/rubydb/rails/connection.rb +314 -0
  283. data/lib/rubydb/rails/database_statements.rb +122 -0
  284. data/lib/rubydb/rails/migration.rb +131 -0
  285. data/lib/rubydb/rails/quoting.rb +109 -0
  286. data/lib/rubydb/rails/result.rb +120 -0
  287. data/lib/rubydb/rails/schema_statements.rb +339 -0
  288. data/lib/rubydb/rails/transaction.rb +105 -0
  289. data/lib/rubydb/rails/type.rb +126 -0
  290. data/lib/rubydb/recovery/checkpoint.rb +257 -0
  291. data/lib/rubydb/recovery/consistency.rb +467 -0
  292. data/lib/rubydb/recovery/corruption_detector.rb +5 -0
  293. data/lib/rubydb/recovery/crash_recovery.rb +387 -0
  294. data/lib/rubydb/recovery/recovery_manager.rb +206 -0
  295. data/lib/rubydb/recovery/redo.rb +237 -0
  296. data/lib/rubydb/recovery/undo.rb +206 -0
  297. data/lib/rubydb/replication/failover.rb +5 -0
  298. data/lib/rubydb/replication/fencing.rb +63 -0
  299. data/lib/rubydb/replication/primary.rb +450 -0
  300. data/lib/rubydb/replication/replica.rb +384 -0
  301. data/lib/rubydb/replication/replication_log.rb +200 -0
  302. data/lib/rubydb/replication/replication_manager.rb +308 -0
  303. data/lib/rubydb/replication/replication_slot.rb +295 -0
  304. data/lib/rubydb/replication/replication_stream.rb +201 -0
  305. data/lib/rubydb/rubydb.rb +560 -0
  306. data/lib/rubydb/security/access_control.rb +254 -0
  307. data/lib/rubydb/security/audit_log.rb +213 -0
  308. data/lib/rubydb/security/authentication.rb +302 -0
  309. data/lib/rubydb/security/authorization.rb +282 -0
  310. data/lib/rubydb/security/credentials.rb +196 -0
  311. data/lib/rubydb/security/password.rb +215 -0
  312. data/lib/rubydb/security/permissions.rb +74 -0
  313. data/lib/rubydb/security/role.rb +101 -0
  314. data/lib/rubydb/security/user.rb +86 -0
  315. data/lib/rubydb/server/connection.rb +366 -0
  316. data/lib/rubydb/server/connection_pool.rb +193 -0
  317. data/lib/rubydb/server/lifecycle.rb +228 -0
  318. data/lib/rubydb/server/listener.rb +136 -0
  319. data/lib/rubydb/server/request_handler.rb +276 -0
  320. data/lib/rubydb/server/server.rb +364 -0
  321. data/lib/rubydb/server/session.rb +369 -0
  322. data/lib/rubydb/server/worker.rb +210 -0
  323. data/lib/rubydb/server/worker_pool.rb +168 -0
  324. data/lib/rubydb/sql/ast/alter_table.rb +169 -0
  325. data/lib/rubydb/sql/ast/begin_transaction.rb +47 -0
  326. data/lib/rubydb/sql/ast/commit.rb +37 -0
  327. data/lib/rubydb/sql/ast/constraint.rb +83 -0
  328. data/lib/rubydb/sql/ast/create_database.rb +41 -0
  329. data/lib/rubydb/sql/ast/create_index.rb +61 -0
  330. data/lib/rubydb/sql/ast/create_schema.rb +52 -0
  331. data/lib/rubydb/sql/ast/create_table.rb +187 -0
  332. data/lib/rubydb/sql/ast/delete.rb +54 -0
  333. data/lib/rubydb/sql/ast/drop_database.rb +41 -0
  334. data/lib/rubydb/sql/ast/drop_index.rb +41 -0
  335. data/lib/rubydb/sql/ast/drop_schema.rb +49 -0
  336. data/lib/rubydb/sql/ast/drop_table.rb +49 -0
  337. data/lib/rubydb/sql/ast/explain.rb +64 -0
  338. data/lib/rubydb/sql/ast/expression.rb +604 -0
  339. data/lib/rubydb/sql/ast/insert.rb +82 -0
  340. data/lib/rubydb/sql/ast/node.rb +42 -0
  341. data/lib/rubydb/sql/ast/rollback.rb +63 -0
  342. data/lib/rubydb/sql/ast/savepoint.rb +59 -0
  343. data/lib/rubydb/sql/ast/select.rb +88 -0
  344. data/lib/rubydb/sql/ast/set_operation.rb +20 -0
  345. data/lib/rubydb/sql/ast/trigger.rb +29 -0
  346. data/lib/rubydb/sql/ast/update.rb +88 -0
  347. data/lib/rubydb/sql/ast/vacuum.rb +19 -0
  348. data/lib/rubydb/sql/ast/view.rb +32 -0
  349. data/lib/rubydb/sql/ast/with.rb +32 -0
  350. data/lib/rubydb/sql/grammar.rb +86 -0
  351. data/lib/rubydb/sql/keywords.rb +156 -0
  352. data/lib/rubydb/sql/lexer.rb +214 -0
  353. data/lib/rubydb/sql/operators.rb +100 -0
  354. data/lib/rubydb/sql/parser.rb +1185 -0
  355. data/lib/rubydb/sql/planner/analyzer.rb +302 -0
  356. data/lib/rubydb/sql/planner/binder.rb +550 -0
  357. data/lib/rubydb/sql/planner/type_checker.rb +431 -0
  358. data/lib/rubydb/sql/token.rb +210 -0
  359. data/lib/rubydb/storage/buffer_frame.rb +44 -0
  360. data/lib/rubydb/storage/buffer_pool.rb +155 -0
  361. data/lib/rubydb/storage/database_lock.rb +74 -0
  362. data/lib/rubydb/storage/deserializer.rb +342 -0
  363. data/lib/rubydb/storage/engine.rb +2338 -0
  364. data/lib/rubydb/storage/file_manager.rb +187 -0
  365. data/lib/rubydb/storage/free_space_map.rb +81 -0
  366. data/lib/rubydb/storage/page.rb +94 -0
  367. data/lib/rubydb/storage/page_allocator.rb +855 -0
  368. data/lib/rubydb/storage/page_header.rb +67 -0
  369. data/lib/rubydb/storage/page_manager.rb +131 -0
  370. data/lib/rubydb/storage/record.rb +58 -0
  371. data/lib/rubydb/storage/row.rb +78 -0
  372. data/lib/rubydb/storage/serializer.rb +51 -0
  373. data/lib/rubydb/storage/storage_layout.rb +151 -0
  374. data/lib/rubydb/storage/storage_manager.rb +114 -0
  375. data/lib/rubydb/storage/tuple.rb +461 -0
  376. data/lib/rubydb/storage/visibility_map.rb +984 -0
  377. data/lib/rubydb/transactions/commit_manager.rb +220 -0
  378. data/lib/rubydb/transactions/isolation.rb +98 -0
  379. data/lib/rubydb/transactions/lock.rb +76 -0
  380. data/lib/rubydb/transactions/lock_manager.rb +362 -0
  381. data/lib/rubydb/transactions/savepoint.rb +143 -0
  382. data/lib/rubydb/transactions/transaction.rb +215 -0
  383. data/lib/rubydb/transactions/transaction_id.rb +84 -0
  384. data/lib/rubydb/transactions/transaction_log.rb +257 -0
  385. data/lib/rubydb/transactions/transaction_manager.rb +435 -0
  386. data/lib/rubydb/types/bigint.rb +36 -0
  387. data/lib/rubydb/types/blob.rb +37 -0
  388. data/lib/rubydb/types/boolean.rb +34 -0
  389. data/lib/rubydb/types/date.rb +39 -0
  390. data/lib/rubydb/types/decimal.rb +48 -0
  391. data/lib/rubydb/types/float.rb +34 -0
  392. data/lib/rubydb/types/integer.rb +36 -0
  393. data/lib/rubydb/types/json.rb +41 -0
  394. data/lib/rubydb/types/null.rb +34 -0
  395. data/lib/rubydb/types/smallint.rb +36 -0
  396. data/lib/rubydb/types/text.rb +37 -0
  397. data/lib/rubydb/types/time.rb +46 -0
  398. data/lib/rubydb/types/timestamp.rb +39 -0
  399. data/lib/rubydb/types/type.rb +119 -0
  400. data/lib/rubydb/types/uuid.rb +47 -0
  401. data/lib/rubydb/types/varchar.rb +37 -0
  402. data/lib/rubydb/version.rb +33 -0
  403. data/lib/rubydb/wal/archive.rb +193 -0
  404. data/lib/rubydb/wal/checkpoint.rb +183 -0
  405. data/lib/rubydb/wal/lsn.rb +94 -0
  406. data/lib/rubydb/wal/reader.rb +260 -0
  407. data/lib/rubydb/wal/record.rb +105 -0
  408. data/lib/rubydb/wal/segment.rb +193 -0
  409. data/lib/rubydb/wal/wal.rb +452 -0
  410. data/lib/rubydb/wal/writer.rb +236 -0
  411. data/lib/rubydb.rb +7 -0
  412. data/packaging/docker/Dockerfile +36 -0
  413. data/packaging/docker/docker-compose.failover.yml +43 -0
  414. data/packaging/docker/entrypoint.sh +15 -0
  415. data/packaging/homebrew/rubydb.rb +19 -0
  416. data/packaging/systemd/rubydb.service +22 -0
  417. data/rubydb.gemspec +57 -0
  418. data/scripts/benchmark +7 -0
  419. data/scripts/durability_drill +37 -0
  420. data/scripts/format +0 -0
  421. data/scripts/fuzz +63 -0
  422. data/scripts/lint +0 -0
  423. data/scripts/release +50 -0
  424. data/scripts/release_check +43 -0
  425. data/scripts/replication_failover_drill +250 -0
  426. data/scripts/replication_network_failover_drill +255 -0
  427. data/scripts/restore_drill +45 -0
  428. data/scripts/security +0 -0
  429. data/scripts/setup +0 -0
  430. data/scripts/test +0 -0
  431. data/scripts/test-chaos +0 -0
  432. data/scripts/test-crash +0 -0
  433. data/scripts/test-integration +0 -0
  434. data/scripts/test-unit +0 -0
  435. data/visibility_map.json +1 -0
  436. metadata +538 -0
@@ -0,0 +1,1185 @@
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
+
542
+ expect(Token::Type::VALUES)
543
+ rows = []
544
+ loop do
545
+ expect(Token::Type::LPAREN)
546
+ values = []
547
+ while true
548
+ values << parse_expression
549
+ break unless current_token&.type == Token::Type::COMMA
550
+ advance
551
+ end
552
+ expect(Token::Type::RPAREN)
553
+ rows << values
554
+ break unless current_token&.type == Token::Type::COMMA
555
+ advance
556
+ end
557
+
558
+ on_conflict = nil
559
+ if current_token&.type == Token::Type::ON
560
+ advance
561
+ expect(Token::Type::CONFLICT)
562
+ target = []
563
+ if current_token&.type == Token::Type::LPAREN
564
+ advance
565
+ target << expect(Token::Type::IDENTIFIER).value
566
+ while current_token&.type == Token::Type::COMMA
567
+ advance
568
+ target << expect(Token::Type::IDENTIFIER).value
569
+ end
570
+ expect(Token::Type::RPAREN)
571
+ end
572
+ expect(Token::Type::DO)
573
+ if current_token&.type == Token::Type::NOTHING
574
+ advance
575
+ on_conflict = :nothing
576
+ elsif current_token&.type == Token::Type::UPDATE
577
+ advance
578
+ expect(Token::Type::SET)
579
+ assignments = []
580
+ loop do
581
+ column = expect(Token::Type::IDENTIFIER).value
582
+ expect(Token::Type::EQ)
583
+ assignments << AST::Assignment.new(column, parse_expression)
584
+ break unless current_token&.type == Token::Type::COMMA
585
+ advance
586
+ end
587
+ on_conflict = { action: :update, target: target, assignments: assignments }
588
+ else
589
+ raise ParserError, "Expected NOTHING or UPDATE after ON CONFLICT DO"
590
+ end
591
+ end
592
+ AST::Insert.new(table, columns, rows.first || [], rows: rows, on_conflict: on_conflict)
593
+ end
594
+
595
+ def parse_update
596
+ expect(Token::Type::UPDATE)
597
+ table = expect(Token::Type::IDENTIFIER).value
598
+ expect(Token::Type::SET)
599
+
600
+ assignments = []
601
+ while true
602
+ column = expect(Token::Type::IDENTIFIER).value
603
+ expect(Token::Type::EQ)
604
+ value = parse_expression
605
+ assignments << AST::Assignment.new(column, value)
606
+ break unless current_token&.type == Token::Type::COMMA
607
+ advance
608
+ end
609
+
610
+ where = nil
611
+ if current_token&.type == Token::Type::WHERE
612
+ advance
613
+ where = parse_expression
614
+ end
615
+
616
+ AST::Update.new(table, assignments, where)
617
+ end
618
+
619
+ def parse_delete
620
+ expect(Token::Type::DELETE)
621
+ expect(Token::Type::FROM)
622
+ table = expect(Token::Type::IDENTIFIER).value
623
+
624
+ where = nil
625
+ if current_token&.type == Token::Type::WHERE
626
+ advance
627
+ where = parse_expression
628
+ end
629
+
630
+ AST::Delete.new(table, where)
631
+ end
632
+
633
+ def parse_create
634
+ expect(Token::Type::CREATE)
635
+ case current_token&.type
636
+ when Token::Type::TABLE
637
+ parse_create_table
638
+ when Token::Type::INDEX
639
+ parse_create_index
640
+ when Token::Type::UNIQUE
641
+ advance
642
+ parse_create_index(unique: true)
643
+ when Token::Type::DATABASE
644
+ parse_create_database
645
+ when Token::Type::SCHEMA
646
+ parse_create_schema
647
+ when Token::Type::VIEW
648
+ parse_create_view
649
+ when Token::Type::TRIGGER
650
+ parse_create_trigger
651
+ else
652
+ raise ParserError, "Unexpected CREATE type: #{current_token}"
653
+ end
654
+ end
655
+
656
+ def parse_create_table
657
+ advance # TABLE
658
+ if_not_exists = false
659
+ if current_token&.type == Token::Type::IF
660
+ advance
661
+ expect(Token::Type::NOT)
662
+ expect(Token::Type::EXISTS)
663
+ if_not_exists = true
664
+ end
665
+ table_name = expect(Token::Type::IDENTIFIER).value
666
+ expect(Token::Type::LPAREN)
667
+
668
+ columns = []
669
+ constraints = []
670
+
671
+ while current_token&.type != Token::Type::RPAREN
672
+ if current_token&.type == Token::Type::CONSTRAINT
673
+ advance
674
+ constraint_name = expect(Token::Type::IDENTIFIER).value
675
+ constraint = parse_constraint(constraint_name)
676
+ constraints << constraint
677
+ elsif current_token&.type == Token::Type::PRIMARY ||
678
+ current_token&.type == Token::Type::FOREIGN ||
679
+ current_token&.type == Token::Type::UNIQUE ||
680
+ current_token&.type == Token::Type::CHECK
681
+ constraint = parse_constraint
682
+ constraints << constraint if constraint
683
+ else
684
+ # Column definition
685
+ column_name = expect(Token::Type::IDENTIFIER).value
686
+ column_type = expect_data_type
687
+ options = parse_column_options
688
+ columns << AST::ColumnDefinition.new(column_name, column_type, options)
689
+ end
690
+
691
+ break unless current_token&.type == Token::Type::COMMA
692
+ advance
693
+ end
694
+
695
+ expect(Token::Type::RPAREN)
696
+
697
+ AST::CreateTable.new(table_name, columns, constraints, if_not_exists: if_not_exists)
698
+ end
699
+
700
+ def parse_data_type
701
+ case current_token&.type
702
+ when Token::Type::INTEGER
703
+ advance
704
+ :integer
705
+ when Token::Type::BIGINT
706
+ advance
707
+ :bigint
708
+ when Token::Type::SMALLINT
709
+ advance
710
+ :smallint
711
+ when Token::Type::FLOAT
712
+ advance
713
+ :float
714
+ when Token::Type::DECIMAL
715
+ advance
716
+ if current_token&.type == Token::Type::LPAREN
717
+ advance
718
+ precision = expect(Token::Type::NUMBER).value
719
+ scale = 0
720
+ if current_token&.type == Token::Type::COMMA
721
+ advance
722
+ scale = expect(Token::Type::NUMBER).value
723
+ end
724
+ expect(Token::Type::RPAREN)
725
+ { type: :decimal, precision: precision, scale: scale }
726
+ else
727
+ :decimal
728
+ end
729
+ when Token::Type::BOOLEAN
730
+ advance
731
+ :boolean
732
+ when Token::Type::TEXT
733
+ advance
734
+ :text
735
+ when Token::Type::VARCHAR
736
+ advance
737
+ if current_token&.type == Token::Type::LPAREN
738
+ advance
739
+ limit = expect(Token::Type::NUMBER).value
740
+ expect(Token::Type::RPAREN)
741
+ { type: :varchar, limit: limit }
742
+ else
743
+ { type: :varchar, limit: 255 }
744
+ end
745
+ when Token::Type::BLOB
746
+ advance
747
+ :blob
748
+ when Token::Type::DATE
749
+ advance
750
+ :date
751
+ when Token::Type::TIME
752
+ advance
753
+ :time
754
+ when Token::Type::TIMESTAMP
755
+ advance
756
+ :timestamp
757
+ when Token::Type::JSON
758
+ advance
759
+ :json
760
+ when Token::Type::UUID
761
+ advance
762
+ :uuid
763
+ else
764
+ raise ParserError, "Expected data type, got: #{current_token}"
765
+ end
766
+ end
767
+
768
+ def parse_column_options
769
+ options = {}
770
+ while true
771
+ case current_token&.type
772
+ when Token::Type::PRIMARY
773
+ advance
774
+ expect(Token::Type::KEY)
775
+ options[:primary_key] = true
776
+ when Token::Type::UNIQUE
777
+ advance
778
+ options[:unique] = true
779
+ when Token::Type::NOT
780
+ advance
781
+ expect(Token::Type::NULL)
782
+ options[:null] = false
783
+ when Token::Type::NULL
784
+ advance
785
+ options[:null] = true
786
+ when Token::Type::DEFAULT
787
+ advance
788
+ options[:default] = parse_expression
789
+ when Token::Type::REFERENCES
790
+ advance
791
+ ref_table = expect(Token::Type::IDENTIFIER).value
792
+ expect(Token::Type::LPAREN)
793
+ ref_column = expect(Token::Type::IDENTIFIER).value
794
+ expect(Token::Type::RPAREN)
795
+ options[:references] = { table: ref_table, column: ref_column }
796
+ when Token::Type::IDENTIFIER
797
+ # SQLite spells an automatically allocated integer primary key
798
+ # `AUTOINCREMENT`. Keep it as column metadata; RubyDB's durable
799
+ # integer-primary-key allocator provides the actual behavior.
800
+ if current_token.value.to_s.upcase == "AUTOINCREMENT"
801
+ advance
802
+ options[:auto_increment] = true
803
+ else
804
+ break
805
+ end
806
+ else
807
+ break
808
+ end
809
+ end
810
+ options
811
+ end
812
+
813
+ def parse_referential_action(kind)
814
+ return nil unless current_token&.type == Token::Type::ON
815
+ advance
816
+ expected = kind == :delete ? Token::Type::DELETE : Token::Type::UPDATE
817
+ expect(expected)
818
+ action = case current_token&.type
819
+ when Token::Type::CASCADE
820
+ advance
821
+ :cascade
822
+ when Token::Type::RESTRICT
823
+ advance
824
+ :restrict
825
+ when Token::Type::SET
826
+ advance
827
+ expect(Token::Type::NULL) if current_token&.type == Token::Type::NULL
828
+ if current_token&.type == Token::Type::DEFAULT
829
+ advance
830
+ :set_default
831
+ else
832
+ :set_null
833
+ end
834
+ else
835
+ raise ParserError, "Expected referential action, got #{current_token}"
836
+ end
837
+ action
838
+ end
839
+
840
+ def parse_constraint(name = nil)
841
+ case current_token&.type
842
+ when Token::Type::PRIMARY
843
+ advance
844
+ expect(Token::Type::KEY)
845
+ expect(Token::Type::LPAREN)
846
+ columns = []
847
+ while true
848
+ columns << expect(Token::Type::IDENTIFIER).value
849
+ break unless current_token&.type == Token::Type::COMMA
850
+ advance
851
+ end
852
+ expect(Token::Type::RPAREN)
853
+ AST::PrimaryKeyConstraint.new(name, columns)
854
+ when Token::Type::FOREIGN
855
+ advance
856
+ expect(Token::Type::KEY)
857
+ expect(Token::Type::LPAREN)
858
+ columns = []
859
+ while true
860
+ columns << expect(Token::Type::IDENTIFIER).value
861
+ break unless current_token&.type == Token::Type::COMMA
862
+ advance
863
+ end
864
+ expect(Token::Type::RPAREN)
865
+ expect(Token::Type::REFERENCES)
866
+ ref_table = expect(Token::Type::IDENTIFIER).value
867
+ expect(Token::Type::LPAREN)
868
+ ref_columns = []
869
+ while true
870
+ ref_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
+ on_delete = parse_referential_action(:delete)
876
+ on_update = parse_referential_action(:update)
877
+ AST::ForeignKeyConstraint.new(name, columns, ref_table, ref_columns, on_delete: on_delete, on_update: on_update)
878
+ when Token::Type::UNIQUE
879
+ advance
880
+ expect(Token::Type::LPAREN)
881
+ columns = []
882
+ while true
883
+ columns << expect(Token::Type::IDENTIFIER).value
884
+ break unless current_token&.type == Token::Type::COMMA
885
+ advance
886
+ end
887
+ expect(Token::Type::RPAREN)
888
+ AST::UniqueConstraint.new(name, columns)
889
+ when Token::Type::CHECK
890
+ advance
891
+ expect(Token::Type::LPAREN)
892
+ condition = parse_expression
893
+ expect(Token::Type::RPAREN)
894
+ AST::CheckConstraint.new(name, condition)
895
+ else
896
+ nil
897
+ end
898
+ end
899
+
900
+ def parse_create_index(unique: false)
901
+ advance # INDEX
902
+ if_not_exists = false
903
+ if current_token&.type == Token::Type::IF
904
+ advance
905
+ expect(Token::Type::NOT)
906
+ expect(Token::Type::EXISTS)
907
+ if_not_exists = true
908
+ end
909
+ index_name = expect(Token::Type::IDENTIFIER).value
910
+ expect(Token::Type::ON)
911
+ table_name = expect(Token::Type::IDENTIFIER).value
912
+ expect(Token::Type::LPAREN)
913
+ columns = []
914
+ while true
915
+ columns << expect(Token::Type::IDENTIFIER).value
916
+ break unless current_token&.type == Token::Type::COMMA
917
+ advance
918
+ end
919
+ expect(Token::Type::RPAREN)
920
+
921
+ # Legacy grammar accepted UNIQUE after the column list; retain it.
922
+ if current_token&.type == Token::Type::UNIQUE
923
+ unique = true
924
+ advance
925
+ end
926
+
927
+ AST::CreateIndex.new(index_name, table_name, columns, unique: unique, if_not_exists: if_not_exists)
928
+ end
929
+
930
+ def parse_create_database
931
+ advance # DATABASE
932
+ if_not_exists = false
933
+ if current_token&.type == Token::Type::IF
934
+ advance
935
+ expect(Token::Type::NOT)
936
+ expect(Token::Type::EXISTS)
937
+ if_not_exists = true
938
+ end
939
+ db_name = expect(Token::Type::IDENTIFIER).value
940
+ AST::CreateDatabase.new(db_name, if_not_exists: if_not_exists)
941
+ end
942
+
943
+ def parse_create_schema
944
+ advance
945
+ if_not_exists = false
946
+ if current_token&.type == Token::Type::IF
947
+ advance
948
+ expect(Token::Type::NOT)
949
+ expect(Token::Type::EXISTS)
950
+ if_not_exists = true
951
+ end
952
+ name = expect(Token::Type::IDENTIFIER).value
953
+ AST::CreateSchema.new(name, if_not_exists: if_not_exists)
954
+ end
955
+
956
+ def parse_create_view
957
+ advance
958
+ if_not_exists = false
959
+ if current_token&.type == Token::Type::IF
960
+ advance
961
+ expect(Token::Type::NOT)
962
+ expect(Token::Type::EXISTS)
963
+ if_not_exists = true
964
+ end
965
+ name = expect(Token::Type::IDENTIFIER).value
966
+ expect(Token::Type::AS)
967
+ AST::CreateView.new(name, parse_select, if_not_exists: if_not_exists)
968
+ end
969
+
970
+ def parse_create_trigger
971
+ advance
972
+ name = expect(Token::Type::IDENTIFIER).value
973
+ timing = if current_token&.type == Token::Type::BEFORE
974
+ advance
975
+ :before
976
+ else
977
+ expect(Token::Type::AFTER)
978
+ :after
979
+ end
980
+ event_token = current_token
981
+ unless [Token::Type::INSERT, Token::Type::UPDATE, Token::Type::DELETE].include?(event_token&.type)
982
+ raise ParserError, "Expected trigger event, got: #{event_token}"
983
+ end
984
+ advance
985
+ expect(Token::Type::ON)
986
+ table_name = expect(Token::Type::IDENTIFIER).value
987
+ expect(Token::Type::EXECUTE)
988
+ expect(Token::Type::FUNCTION)
989
+ function_name = expect(Token::Type::IDENTIFIER).value
990
+ expect(Token::Type::LPAREN)
991
+ expect(Token::Type::RPAREN)
992
+ AST::CreateTrigger.new(name, timing, event_token.value.to_s.downcase.to_sym, table_name, function_name)
993
+ end
994
+
995
+ def parse_drop
996
+ expect(Token::Type::DROP)
997
+ case current_token&.type
998
+ when Token::Type::TABLE
999
+ advance
1000
+ if_exists = false
1001
+ if current_token&.type == Token::Type::IF
1002
+ advance
1003
+ expect(Token::Type::EXISTS)
1004
+ if_exists = true
1005
+ end
1006
+ table_name = expect(Token::Type::IDENTIFIER).value
1007
+ AST::DropTable.new(table_name, if_exists: if_exists)
1008
+ when Token::Type::INDEX
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
+ index_name = expect(Token::Type::IDENTIFIER).value
1017
+ AST::DropIndex.new(index_name, if_exists: if_exists)
1018
+ when Token::Type::DATABASE
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
+ db_name = expect(Token::Type::IDENTIFIER).value
1027
+ AST::DropDatabase.new(db_name, if_exists: if_exists)
1028
+ when Token::Type::SCHEMA
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
+ name = expect(Token::Type::IDENTIFIER).value
1037
+ cascade = false
1038
+ if current_token&.type == Token::Type::CASCADE
1039
+ advance
1040
+ cascade = true
1041
+ end
1042
+ AST::DropSchema.new(name, if_exists: if_exists, cascade: cascade)
1043
+ when Token::Type::VIEW
1044
+ advance
1045
+ if_exists = false
1046
+ if current_token&.type == Token::Type::IF
1047
+ advance
1048
+ expect(Token::Type::EXISTS)
1049
+ if_exists = true
1050
+ end
1051
+ AST::DropView.new(expect(Token::Type::IDENTIFIER).value, if_exists: if_exists)
1052
+ when Token::Type::TRIGGER
1053
+ advance
1054
+ if_exists = false
1055
+ if current_token&.type == Token::Type::IF
1056
+ advance
1057
+ expect(Token::Type::EXISTS)
1058
+ if_exists = true
1059
+ end
1060
+ AST::DropTrigger.new(expect(Token::Type::IDENTIFIER).value, if_exists: if_exists)
1061
+ else
1062
+ raise ParserError, "Unexpected DROP type: #{current_token}"
1063
+ end
1064
+ end
1065
+
1066
+ def parse_alter
1067
+ expect(Token::Type::ALTER)
1068
+ expect(Token::Type::TABLE)
1069
+ table_name = expect(Token::Type::IDENTIFIER).value
1070
+
1071
+ case current_token&.type
1072
+ when Token::Type::ADD
1073
+ advance
1074
+ skip_optional_column_keyword
1075
+ if current_token&.type == Token::Type::CONSTRAINT
1076
+ advance
1077
+ constraint_name = expect(Token::Type::IDENTIFIER).value
1078
+ constraint = parse_constraint(constraint_name)
1079
+ AST::AlterTableAddConstraint.new(table_name, constraint)
1080
+ else
1081
+ column_name = expect(Token::Type::IDENTIFIER).value
1082
+ column_type = parse_data_type
1083
+ options = parse_column_options
1084
+ AST::AlterTableAddColumn.new(table_name, column_name, column_type, options)
1085
+ end
1086
+ when Token::Type::DROP
1087
+ advance
1088
+ skip_optional_column_keyword
1089
+ if current_token&.type == Token::Type::CONSTRAINT
1090
+ advance
1091
+ constraint_name = expect(Token::Type::IDENTIFIER).value
1092
+ AST::AlterTableDropConstraint.new(table_name, constraint_name)
1093
+ else
1094
+ column_name = expect(Token::Type::IDENTIFIER).value
1095
+ AST::AlterTableDropColumn.new(table_name, column_name)
1096
+ end
1097
+ else
1098
+ raise ParserError, "Unexpected ALTER TABLE operation: #{current_token}"
1099
+ end
1100
+ end
1101
+
1102
+ def parse_begin
1103
+ expect(Token::Type::BEGIN_TRANSACTION)
1104
+ if current_token&.type == Token::Type::TRANSACTION
1105
+ advance
1106
+ end
1107
+ AST::BeginTransaction.new
1108
+ end
1109
+
1110
+ def skip_optional_column_keyword
1111
+ return unless current_token&.type == Token::Type::IDENTIFIER
1112
+ return unless current_token.value.to_s.casecmp("COLUMN").zero?
1113
+
1114
+ advance
1115
+ end
1116
+
1117
+ def parse_commit
1118
+ expect(Token::Type::COMMIT)
1119
+ if current_token&.type == Token::Type::TRANSACTION
1120
+ advance
1121
+ end
1122
+ AST::Commit.new
1123
+ end
1124
+
1125
+ def parse_rollback
1126
+ expect(Token::Type::ROLLBACK)
1127
+ if current_token&.type == Token::Type::TRANSACTION
1128
+ advance
1129
+ end
1130
+ if current_token&.type == Token::Type::TO
1131
+ advance
1132
+ advance if current_token&.type == Token::Type::SAVEPOINT
1133
+ savepoint = expect(Token::Type::IDENTIFIER).value
1134
+ return AST::RollbackToSavepoint.new(savepoint)
1135
+ end
1136
+ AST::Rollback.new
1137
+ end
1138
+
1139
+ def parse_savepoint
1140
+ expect(Token::Type::SAVEPOINT)
1141
+ AST::Savepoint.new(expect(Token::Type::IDENTIFIER).value)
1142
+ end
1143
+
1144
+ def parse_release_savepoint
1145
+ expect(Token::Type::RELEASE)
1146
+ expect(Token::Type::SAVEPOINT) if current_token&.type == Token::Type::SAVEPOINT
1147
+ AST::ReleaseSavepoint.new(expect(Token::Type::IDENTIFIER).value)
1148
+ end
1149
+
1150
+ def parse_explain
1151
+ expect(Token::Type::EXPLAIN)
1152
+ if current_token&.type == Token::Type::ANALYZE
1153
+ advance
1154
+ analyze = true
1155
+ else
1156
+ analyze = false
1157
+ end
1158
+ statement = parse_statement
1159
+ AST::Explain.new(statement, analyze: analyze)
1160
+ end
1161
+
1162
+ def expect(type)
1163
+ token = current_token
1164
+ if token.nil? || token.type != type
1165
+ raise ParserError, "Expected #{type}, got #{token}"
1166
+ end
1167
+ advance
1168
+ token
1169
+ end
1170
+
1171
+ def expect_data_type
1172
+ type = parse_data_type
1173
+ unless type
1174
+ raise ParserError, "Expected data type, got #{current_token}"
1175
+ end
1176
+ type
1177
+ end
1178
+
1179
+ def advance
1180
+ @position += 1
1181
+ @current_token = @tokens[@position]
1182
+ end
1183
+ end
1184
+ end
1185
+ end