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,604 +1,617 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyDB
4
- module SQL
5
- module AST
6
- # Base class for all expression nodes
7
- class Expression < Node
8
- # Type of expression result (set by type checker)
9
- attr_accessor :type
10
-
11
- def initialize(location: nil)
12
- super(location: location)
13
- @type = nil
14
- end
15
-
16
- # Is this expression a literal value?
17
- def literal?
18
- false
19
- end
20
-
21
- # Is this expression a column reference?
22
- def column?
23
- false
24
- end
25
-
26
- # Is this expression NULL?
27
- def null?
28
- false
29
- end
30
-
31
- # Does this expression contain a subquery?
32
- def contains_subquery?
33
- false
34
- end
35
- end
36
-
37
- # Literal value
38
- class Literal < Expression
39
- attr_reader :value, :token_type
40
-
41
- def initialize(value, token_type, location: nil)
42
- super(location: location)
43
- @value = value
44
- @token_type = token_type
45
- end
46
-
47
- def literal?
48
- true
49
- end
50
-
51
- def null?
52
- @value.nil?
53
- end
54
-
55
- def accept(visitor)
56
- visitor.visit_literal(self)
57
- end
58
-
59
- def clone
60
- Literal.new(@value, @token_type, location: @location)
61
- end
62
-
63
- def to_sql
64
- case @token_type
65
- when Token::Type::STRING
66
- "'#{@value.to_s.gsub("'", "''")}'"
67
- when Token::Type::NUMBER
68
- @value.to_s
69
- when Token::Type::NULL
70
- "NULL"
71
- when Token::Type::TRUE
72
- "TRUE"
73
- when Token::Type::FALSE
74
- "FALSE"
75
- else
76
- @value.to_s
77
- end
78
- end
79
-
80
- def inspect
81
- "Literal(#{@value})"
82
- end
83
- end
84
-
85
- # NULL literal
86
- class NullLiteral < Literal
87
- def initialize(location: nil)
88
- super(nil, Token::Type::NULL, location: location)
89
- end
90
-
91
- def null?
92
- true
93
- end
94
-
95
- def to_sql
96
- "NULL"
97
- end
98
-
99
- def inspect
100
- "NULL"
101
- end
102
- end
103
-
104
- # Column reference
105
- class Identifier < Expression
106
- attr_reader :name, :table
107
-
108
- def initialize(name, table: nil, location: nil)
109
- super(location: location)
110
- @name = name
111
- @table = table
112
- end
113
-
114
- def column?
115
- true
116
- end
117
-
118
- def accept(visitor)
119
- visitor.visit_identifier(self)
120
- end
121
-
122
- def clone
123
- Identifier.new(@name, table: @table, location: @location)
124
- end
125
-
126
- def to_sql
127
- if @table
128
- "#{@table}.#{@name}"
129
- else
130
- @name
131
- end
132
- end
133
-
134
- def inspect
135
- if @table
136
- "Identifier(#{@table}.#{@name})"
137
- else
138
- "Identifier(#{@name})"
139
- end
140
- end
141
- end
142
-
143
- # Unary operator expression
144
- class UnaryOp < Expression
145
- attr_reader :operator, :operand
146
-
147
- def initialize(operator, operand, location: nil)
148
- super(location: location)
149
- @operator = operator
150
- @operand = operand
151
- end
152
-
153
- def accept(visitor)
154
- visitor.visit_unary_op(self)
155
- end
156
-
157
- def clone
158
- UnaryOp.new(@operator, @operand.clone, location: @location)
159
- end
160
-
161
- def to_sql
162
- "#{operator_string(@operator)}#{@operand.to_sql}"
163
- end
164
-
165
- def inspect
166
- "UnaryOp(#{@operator}, #{@operand.inspect})"
167
- end
168
-
169
- private
170
-
171
- def operator_string(op)
172
- case op
173
- when Token::Type::PLUS then "+"
174
- when Token::Type::MINUS then "-"
175
- when Token::Type::TILDE then "~"
176
- when Token::Type::NOT then "NOT "
177
- else op.to_s
178
- end
179
- end
180
- end
181
-
182
- # Binary operator expression
183
- class BinaryOp < Expression
184
- attr_reader :operator, :left, :right
185
-
186
- def initialize(operator, left, right, location: nil)
187
- super(location: location)
188
- @operator = operator
189
- @left = left
190
- @right = right
191
- end
192
-
193
- def accept(visitor)
194
- visitor.visit_binary_op(self)
195
- end
196
-
197
- def clone
198
- BinaryOp.new(@operator, @left.clone, @right.clone, location: @location)
199
- end
200
-
201
- def to_sql
202
- "#{@left.to_sql} #{operator_string(@operator)} #{@right.to_sql}"
203
- end
204
-
205
- def inspect
206
- "BinaryOp(#{@operator}, #{@left.inspect}, #{@right.inspect})"
207
- end
208
-
209
- private
210
-
211
- def operator_string(op)
212
- case op
213
- when Token::Type::EQ then "="
214
- when Token::Type::NE then "!="
215
- when Token::Type::LT then "<"
216
- when Token::Type::LTE then "<="
217
- when Token::Type::GT then ">"
218
- when Token::Type::GTE then ">="
219
- when Token::Type::PLUS then "+"
220
- when Token::Type::MINUS then "-"
221
- when Token::Type::STAR then "*"
222
- when Token::Type::SLASH then "/"
223
- when Token::Type::PERCENT then "%"
224
- when Token::Type::AND then "AND"
225
- when Token::Type::OR then "OR"
226
- when Token::Type::LIKE then "LIKE"
227
- when Token::Type::ILIKE then "ILIKE"
228
- else op.to_s
229
- end
230
- end
231
- end
232
-
233
- # BETWEEN expression
234
- class Between < Expression
235
- attr_reader :expression, :low, :high
236
-
237
- def initialize(expression, low, high, location: nil)
238
- super(location: location)
239
- @expression = expression
240
- @low = low
241
- @high = high
242
- end
243
-
244
- def accept(visitor)
245
- visitor.visit_between(self)
246
- end
247
-
248
- def clone
249
- Between.new(@expression.clone, @low.clone, @high.clone, location: @location)
250
- end
251
-
252
- def to_sql
253
- "#{@expression.to_sql} BETWEEN #{@low.to_sql} AND #{@high.to_sql}"
254
- end
255
-
256
- def inspect
257
- "Between(#{@expression.inspect}, #{@low.inspect}, #{@high.inspect})"
258
- end
259
- end
260
-
261
- # IN expression
262
- class In < Expression
263
- attr_reader :expression, :values
264
-
265
- def initialize(expression, values, location: nil)
266
- super(location: location)
267
- @expression = expression
268
- @values = values
269
- end
270
-
271
- def accept(visitor)
272
- visitor.visit_in(self)
273
- end
274
-
275
- def clone
276
- In.new(@expression.clone, @values.map(&:clone), location: @location)
277
- end
278
-
279
- def to_sql
280
- values_sql = @values.map(&:to_sql).join(", ")
281
- "#{@expression.to_sql} IN (#{values_sql})"
282
- end
283
-
284
- def inspect
285
- "In(#{@expression.inspect}, #{@values.map(&:inspect).join(", ")})"
286
- end
287
- end
288
-
289
- class Subquery < Expression
290
- attr_reader :query
291
- def initialize(query, location: nil) = (super(location: location); @query = query)
292
- def contains_subquery? = true
293
- def accept(visitor) = visitor.visit_subquery(self)
294
- def clone = Subquery.new(@query.clone, location: @location)
295
- def to_sql = "(#{@query.to_sql})"
296
- end
297
-
298
- class Exists < Expression
299
- attr_reader :query
300
- def initialize(query, location: nil) = (super(location: location); @query = query)
301
- def accept(visitor) = visitor.visit_exists(self)
302
- def clone = Exists.new(@query.clone, location: @location)
303
- def to_sql = "EXISTS (#{@query.to_sql})"
304
- end
305
-
306
- # IS NULL expression
307
- class IsNull < Expression
308
- attr_reader :expression, :negated
309
-
310
- def initialize(expression, negated = false, location: nil)
311
- super(location: location)
312
- @expression = expression
313
- @negated = negated
314
- end
315
-
316
- def accept(visitor)
317
- visitor.visit_is_null(self)
318
- end
319
-
320
- def clone
321
- IsNull.new(@expression.clone, @negated, location: @location)
322
- end
323
-
324
- def to_sql
325
- if @negated
326
- "#{@expression.to_sql} IS NOT NULL"
327
- else
328
- "#{@expression.to_sql} IS NULL"
329
- end
330
- end
331
-
332
- def inspect
333
- if @negated
334
- "IsNull(#{@expression.inspect}, negated: true)"
335
- else
336
- "IsNull(#{@expression.inspect})"
337
- end
338
- end
339
- end
340
-
341
- # Function call
342
- class FunctionCall < Expression
343
- attr_reader :name, :arguments, :distinct, :window
344
-
345
- def initialize(name, arguments = [], distinct: false, window: nil, location: nil)
346
- super(location: location)
347
- @name = name
348
- @arguments = arguments
349
- @distinct = distinct
350
- @window = window
351
- end
352
-
353
- def accept(visitor)
354
- visitor.visit_function_call(self)
355
- end
356
-
357
- def clone
358
- cloned_window = if @window
359
- {
360
- partition_by: @window[:partition_by].map(&:clone),
361
- order_by: @window[:order_by].map(&:clone),
362
- frame: @window[:frame]&.transform_values { |value| value.is_a?(Hash) ? value.dup : value }
363
- }
364
- end
365
- FunctionCall.new(@name, @arguments.map(&:clone), distinct: @distinct, window: cloned_window, location: @location)
366
- end
367
-
368
- def to_sql
369
- args = @arguments.map(&:to_sql).join(", ")
370
- args = "DISTINCT #{args}" if @distinct
371
- sql = "#{@name}(#{args})"
372
- if @window
373
- partition = @window[:partition_by]
374
- ordering = @window[:order_by]
375
- parts = []
376
- parts << "PARTITION BY #{partition.map(&:to_sql).join(', ')}" unless partition.empty?
377
- parts << "ORDER BY #{ordering.map(&:to_sql).join(', ')}" unless ordering.empty?
378
- parts << "ROWS #{window_frame_sql(@window[:frame])}" if @window[:frame]
379
- sql << " OVER (#{parts.join(' ')})"
380
- end
381
- sql
382
- end
383
-
384
- def window_frame_sql(frame)
385
- boundary = lambda do |value|
386
- case value[:kind]
387
- when :unbounded_preceding then "UNBOUNDED PRECEDING"
388
- when :unbounded_following then "UNBOUNDED FOLLOWING"
389
- when :current_row then "CURRENT ROW"
390
- when :preceding then "#{value[:value]} PRECEDING"
391
- when :following then "#{value[:value]} FOLLOWING"
392
- else raise ArgumentError, "Unknown window frame boundary: #{value[:kind]}"
393
- end
394
- end
395
- if frame[:start] && frame[:finish]
396
- "BETWEEN #{boundary.call(frame[:start])} AND #{boundary.call(frame[:finish])}"
397
- else
398
- boundary.call(frame[:start])
399
- end
400
- end
401
-
402
- def inspect
403
- "FunctionCall(#{@name}, #{@arguments.map(&:inspect).join(", ")})"
404
- end
405
- end
406
-
407
- # Parameter (placeholder) for prepared statements
408
- class Parameter < Expression
409
- attr_reader :index
410
-
411
- def initialize(index = nil, location: nil)
412
- super(location: location)
413
- @index = index
414
- end
415
-
416
- def accept(visitor)
417
- visitor.visit_parameter(self)
418
- end
419
-
420
- def clone
421
- Parameter.new(@index, location: @location)
422
- end
423
-
424
- def to_sql
425
- if @index
426
- "$#{@index}"
427
- else
428
- "?"
429
- end
430
- end
431
-
432
- def inspect
433
- if @index
434
- "Parameter($#{@index})"
435
- else
436
- "Parameter(?)"
437
- end
438
- end
439
- end
440
-
441
- # Star (*) in SELECT
442
- class Star < Expression
443
- attr_reader :table
444
-
445
- def initialize(table: nil, location: nil)
446
- super(location: location)
447
- @table = table
448
- end
449
-
450
- def accept(visitor)
451
- visitor.visit_star(self)
452
- end
453
-
454
- def clone
455
- Star.new(table: @table, location: @location)
456
- end
457
-
458
- def to_sql
459
- @table ? "#{@table}.*" : "*"
460
- end
461
-
462
- def inspect
463
- @table ? "Star(#{@table}.*)" : "Star"
464
- end
465
- end
466
-
467
- # SELECT column with optional alias
468
- class SelectColumn < Node
469
- attr_reader :expression, :alias_name
470
-
471
- def initialize(expression, alias_name = nil, location: nil)
472
- super(location: location)
473
- @expression = expression
474
- @alias_name = alias_name
475
- end
476
-
477
- def accept(visitor)
478
- visitor.visit_select_column(self)
479
- end
480
-
481
- def clone
482
- SelectColumn.new(@expression.clone, @alias_name, location: @location)
483
- end
484
-
485
- def to_sql
486
- if @alias_name
487
- "#{@expression.to_sql} AS #{@alias_name}"
488
- else
489
- @expression.to_sql
490
- end
491
- end
492
-
493
- def inspect
494
- if @alias_name
495
- "SelectColumn(#{@expression.inspect}, AS #{@alias_name})"
496
- else
497
- "SelectColumn(#{@expression.inspect})"
498
- end
499
- end
500
- end
501
-
502
- # Table reference with optional alias
503
- class TableRef < Node
504
- attr_reader :name, :alias_name
505
-
506
- def initialize(name, alias_name = nil, location: nil)
507
- super(location: location)
508
- @name = name
509
- @alias_name = alias_name
510
- end
511
-
512
- def accept(visitor)
513
- visitor.visit_table_ref(self)
514
- end
515
-
516
- def clone
517
- TableRef.new(@name, @alias_name, location: @location)
518
- end
519
-
520
- def to_sql
521
- if @alias_name
522
- "#{@name} AS #{@alias_name}"
523
- else
524
- @name
525
- end
526
- end
527
-
528
- def inspect
529
- if @alias_name
530
- "TableRef(#{@name} AS #{@alias_name})"
531
- else
532
- "TableRef(#{@name})"
533
- end
534
- end
535
- end
536
-
537
- # A joined table and its ON predicate. JOIN support is intentionally
538
- # represented in the regular SELECT AST so parser, planner and executor
539
- # all agree on table aliases and qualified identifiers.
540
- class Join < Node
541
- attr_reader :type, :table, :condition
542
-
543
- def initialize(type, table, condition = nil, location: nil)
544
- super(location: location)
545
- @type = type
546
- @table = table
547
- @condition = condition
548
- end
549
-
550
- def accept(visitor)
551
- visitor.visit_join(self)
552
- end
553
-
554
- def clone
555
- Join.new(@type, @table.clone, @condition&.clone, location: @location)
556
- end
557
-
558
- def to_sql
559
- keyword = {
560
- left: "LEFT JOIN",
561
- right: "RIGHT JOIN",
562
- full: "FULL JOIN",
563
- cross: "CROSS JOIN"
564
- }.fetch(@type, "INNER JOIN")
565
- sql = "#{keyword} #{@table.to_sql}"
566
- sql << " ON #{@condition.to_sql}" if @condition
567
- sql
568
- end
569
-
570
- def inspect
571
- "Join(#{@type}, #{@table.inspect}, #{@condition.inspect})"
572
- end
573
- end
574
-
575
- # ORDER BY item
576
- class OrderItem < Node
577
- attr_reader :expression, :direction
578
-
579
- def initialize(expression, direction = :asc, location: nil)
580
- super(location: location)
581
- @expression = expression
582
- @direction = direction
583
- end
584
-
585
- def accept(visitor)
586
- visitor.visit_order_item(self)
587
- end
588
-
589
- def clone
590
- OrderItem.new(@expression.clone, @direction, location: @location)
591
- end
592
-
593
- def to_sql
594
- dir = @direction == :asc ? "ASC" : "DESC"
595
- "#{@expression.to_sql} #{dir}"
596
- end
597
-
598
- def inspect
599
- "OrderItem(#{@expression.inspect}, #{@direction})"
600
- end
601
- end
602
- end
603
- end
604
- end
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module SQL
5
+ module AST
6
+ # Base class for all expression nodes
7
+ class Expression < Node
8
+ # Type of expression result (set by type checker)
9
+ attr_accessor :type
10
+
11
+ def initialize(location: nil)
12
+ super
13
+ @type = nil
14
+ end
15
+
16
+ # Is this expression a literal value?
17
+ def literal?
18
+ false
19
+ end
20
+
21
+ # Is this expression a column reference?
22
+ def column?
23
+ false
24
+ end
25
+
26
+ # Is this expression NULL?
27
+ def null?
28
+ false
29
+ end
30
+
31
+ # Does this expression contain a subquery?
32
+ def contains_subquery?
33
+ false
34
+ end
35
+ end
36
+
37
+ # Literal value
38
+ class Literal < Expression
39
+ attr_reader :value, :token_type
40
+
41
+ def initialize(value, token_type, location: nil)
42
+ super(location: location)
43
+ @value = value
44
+ @token_type = token_type
45
+ end
46
+
47
+ def literal?
48
+ true
49
+ end
50
+
51
+ def null?
52
+ @value.nil?
53
+ end
54
+
55
+ def accept(visitor)
56
+ visitor.visit_literal(self)
57
+ end
58
+
59
+ def clone
60
+ Literal.new(@value, @token_type, location: @location)
61
+ end
62
+
63
+ def to_sql
64
+ case @token_type
65
+ when Token::Type::STRING
66
+ "'#{@value.to_s.gsub("'", "''")}'"
67
+ when Token::Type::NUMBER
68
+ @value.to_s
69
+ when Token::Type::NULL
70
+ "NULL"
71
+ when Token::Type::TRUE
72
+ "TRUE"
73
+ when Token::Type::FALSE
74
+ "FALSE"
75
+ else
76
+ @value.to_s
77
+ end
78
+ end
79
+
80
+ def inspect
81
+ "Literal(#{@value})"
82
+ end
83
+ end
84
+
85
+ # NULL literal
86
+ class NullLiteral < Literal
87
+ def initialize(location: nil)
88
+ super(nil, Token::Type::NULL, location: location)
89
+ end
90
+
91
+ def null?
92
+ true
93
+ end
94
+
95
+ def to_sql
96
+ "NULL"
97
+ end
98
+
99
+ def inspect
100
+ "NULL"
101
+ end
102
+ end
103
+
104
+ # Column reference
105
+ class Identifier < Expression
106
+ attr_reader :name, :table
107
+
108
+ def initialize(name, table: nil, location: nil)
109
+ super(location: location)
110
+ @name = name
111
+ @table = table
112
+ end
113
+
114
+ def column?
115
+ true
116
+ end
117
+
118
+ def accept(visitor)
119
+ visitor.visit_identifier(self)
120
+ end
121
+
122
+ def clone
123
+ Identifier.new(@name, table: @table, location: @location)
124
+ end
125
+
126
+ def to_sql
127
+ if @table
128
+ "#{@table}.#{@name}"
129
+ else
130
+ @name
131
+ end
132
+ end
133
+
134
+ def inspect
135
+ if @table
136
+ "Identifier(#{@table}.#{@name})"
137
+ else
138
+ "Identifier(#{@name})"
139
+ end
140
+ end
141
+ end
142
+
143
+ # Unary operator expression
144
+ class UnaryOp < Expression
145
+ attr_reader :operator, :operand
146
+
147
+ def initialize(operator, operand, location: nil)
148
+ super(location: location)
149
+ @operator = operator
150
+ @operand = operand
151
+ end
152
+
153
+ def accept(visitor)
154
+ visitor.visit_unary_op(self)
155
+ end
156
+
157
+ def clone
158
+ UnaryOp.new(@operator, @operand.clone, location: @location)
159
+ end
160
+
161
+ def to_sql
162
+ "#{operator_string(@operator)}#{@operand.to_sql}"
163
+ end
164
+
165
+ def inspect
166
+ "UnaryOp(#{@operator}, #{@operand.inspect})"
167
+ end
168
+
169
+ private
170
+
171
+ def operator_string(op)
172
+ case op
173
+ when Token::Type::PLUS then "+"
174
+ when Token::Type::MINUS then "-"
175
+ when Token::Type::TILDE then "~"
176
+ when Token::Type::NOT then "NOT "
177
+ else op.to_s
178
+ end
179
+ end
180
+ end
181
+
182
+ # Binary operator expression
183
+ class BinaryOp < Expression
184
+ attr_reader :operator, :left, :right
185
+
186
+ def initialize(operator, left, right, location: nil)
187
+ super(location: location)
188
+ @operator = operator
189
+ @left = left
190
+ @right = right
191
+ end
192
+
193
+ def accept(visitor)
194
+ visitor.visit_binary_op(self)
195
+ end
196
+
197
+ def clone
198
+ BinaryOp.new(@operator, @left.clone, @right.clone, location: @location)
199
+ end
200
+
201
+ def to_sql
202
+ "#{@left.to_sql} #{operator_string(@operator)} #{@right.to_sql}"
203
+ end
204
+
205
+ def inspect
206
+ "BinaryOp(#{@operator}, #{@left.inspect}, #{@right.inspect})"
207
+ end
208
+
209
+ private
210
+
211
+ def operator_string(op)
212
+ case op
213
+ when Token::Type::EQ then "="
214
+ when Token::Type::NE then "!="
215
+ when Token::Type::LT then "<"
216
+ when Token::Type::LTE then "<="
217
+ when Token::Type::GT then ">"
218
+ when Token::Type::GTE then ">="
219
+ when Token::Type::PLUS then "+"
220
+ when Token::Type::MINUS then "-"
221
+ when Token::Type::STAR then "*"
222
+ when Token::Type::SLASH then "/"
223
+ when Token::Type::PERCENT then "%"
224
+ when Token::Type::AND then "AND"
225
+ when Token::Type::OR then "OR"
226
+ when Token::Type::LIKE then "LIKE"
227
+ when Token::Type::ILIKE then "ILIKE"
228
+ else op.to_s
229
+ end
230
+ end
231
+ end
232
+
233
+ # BETWEEN expression
234
+ class Between < Expression
235
+ attr_reader :expression, :low, :high
236
+
237
+ def initialize(expression, low, high, location: nil)
238
+ super(location: location)
239
+ @expression = expression
240
+ @low = low
241
+ @high = high
242
+ end
243
+
244
+ def accept(visitor)
245
+ visitor.visit_between(self)
246
+ end
247
+
248
+ def clone
249
+ Between.new(@expression.clone, @low.clone, @high.clone, location: @location)
250
+ end
251
+
252
+ def to_sql
253
+ "#{@expression.to_sql} BETWEEN #{@low.to_sql} AND #{@high.to_sql}"
254
+ end
255
+
256
+ def inspect
257
+ "Between(#{@expression.inspect}, #{@low.inspect}, #{@high.inspect})"
258
+ end
259
+ end
260
+
261
+ # IN expression
262
+ class In < Expression
263
+ attr_reader :expression, :values
264
+
265
+ def initialize(expression, values, location: nil)
266
+ super(location: location)
267
+ @expression = expression
268
+ @values = values
269
+ end
270
+
271
+ def accept(visitor)
272
+ visitor.visit_in(self)
273
+ end
274
+
275
+ def clone
276
+ In.new(@expression.clone, @values.map(&:clone), location: @location)
277
+ end
278
+
279
+ def to_sql
280
+ values_sql = @values.map(&:to_sql).join(", ")
281
+ "#{@expression.to_sql} IN (#{values_sql})"
282
+ end
283
+
284
+ def inspect
285
+ "In(#{@expression.inspect}, #{@values.map(&:inspect).join(", ")})"
286
+ end
287
+ end
288
+
289
+ class Subquery < Expression
290
+ attr_reader :query
291
+ def initialize(query, location: nil)
292
+ super(location: location)
293
+ @query = query
294
+ end
295
+
296
+ def contains_subquery? = true
297
+
298
+ def accept(visitor) = visitor.visit_subquery(self)
299
+
300
+ def clone = Subquery.new(@query.clone, location: @location)
301
+
302
+ def to_sql = "(#{@query.to_sql})"
303
+ end
304
+
305
+ class Exists < Expression
306
+ attr_reader :query
307
+ def initialize(query, location: nil)
308
+ super(location: location)
309
+ @query = query
310
+ end
311
+
312
+ def accept(visitor) = visitor.visit_exists(self)
313
+
314
+ def clone = Exists.new(@query.clone, location: @location)
315
+
316
+ def to_sql = "EXISTS (#{@query.to_sql})"
317
+ end
318
+
319
+ # IS NULL expression
320
+ class IsNull < Expression
321
+ attr_reader :expression, :negated
322
+
323
+ def initialize(expression, negated = false, location: nil)
324
+ super(location: location)
325
+ @expression = expression
326
+ @negated = negated
327
+ end
328
+
329
+ def accept(visitor)
330
+ visitor.visit_is_null(self)
331
+ end
332
+
333
+ def clone
334
+ IsNull.new(@expression.clone, @negated, location: @location)
335
+ end
336
+
337
+ def to_sql
338
+ if @negated
339
+ "#{@expression.to_sql} IS NOT NULL"
340
+ else
341
+ "#{@expression.to_sql} IS NULL"
342
+ end
343
+ end
344
+
345
+ def inspect
346
+ if @negated
347
+ "IsNull(#{@expression.inspect}, negated: true)"
348
+ else
349
+ "IsNull(#{@expression.inspect})"
350
+ end
351
+ end
352
+ end
353
+
354
+ # Function call
355
+ class FunctionCall < Expression
356
+ attr_reader :name, :arguments, :distinct, :window
357
+
358
+ def initialize(name, arguments = [], distinct: false, window: nil, location: nil)
359
+ super(location: location)
360
+ @name = name
361
+ @arguments = arguments
362
+ @distinct = distinct
363
+ @window = window
364
+ end
365
+
366
+ def accept(visitor)
367
+ visitor.visit_function_call(self)
368
+ end
369
+
370
+ def clone
371
+ cloned_window = if @window
372
+ {
373
+ partition_by: @window[:partition_by].map(&:clone),
374
+ order_by: @window[:order_by].map(&:clone),
375
+ frame: @window[:frame]&.transform_values { |value| value.is_a?(Hash) ? value.dup : value }
376
+ }
377
+ end
378
+ FunctionCall.new(@name, @arguments.map(&:clone), distinct: @distinct, window: cloned_window, location: @location)
379
+ end
380
+
381
+ def to_sql
382
+ args = @arguments.map(&:to_sql).join(", ")
383
+ args = "DISTINCT #{args}" if @distinct
384
+ sql = "#{@name}(#{args})"
385
+ if @window
386
+ partition = @window[:partition_by]
387
+ ordering = @window[:order_by]
388
+ parts = []
389
+ parts << "PARTITION BY #{partition.map(&:to_sql).join(", ")}" unless partition.empty?
390
+ parts << "ORDER BY #{ordering.map(&:to_sql).join(", ")}" unless ordering.empty?
391
+ parts << "ROWS #{window_frame_sql(@window[:frame])}" if @window[:frame]
392
+ sql << " OVER (#{parts.join(" ")})"
393
+ end
394
+ sql
395
+ end
396
+
397
+ def window_frame_sql(frame)
398
+ boundary = lambda do |value|
399
+ case value[:kind]
400
+ when :unbounded_preceding then "UNBOUNDED PRECEDING"
401
+ when :unbounded_following then "UNBOUNDED FOLLOWING"
402
+ when :current_row then "CURRENT ROW"
403
+ when :preceding then "#{value[:value]} PRECEDING"
404
+ when :following then "#{value[:value]} FOLLOWING"
405
+ else raise ArgumentError, "Unknown window frame boundary: #{value[:kind]}"
406
+ end
407
+ end
408
+ if frame[:start] && frame[:finish]
409
+ "BETWEEN #{boundary.call(frame[:start])} AND #{boundary.call(frame[:finish])}"
410
+ else
411
+ boundary.call(frame[:start])
412
+ end
413
+ end
414
+
415
+ def inspect
416
+ "FunctionCall(#{@name}, #{@arguments.map(&:inspect).join(", ")})"
417
+ end
418
+ end
419
+
420
+ # Parameter (placeholder) for prepared statements
421
+ class Parameter < Expression
422
+ attr_reader :index
423
+
424
+ def initialize(index = nil, location: nil)
425
+ super(location: location)
426
+ @index = index
427
+ end
428
+
429
+ def accept(visitor)
430
+ visitor.visit_parameter(self)
431
+ end
432
+
433
+ def clone
434
+ Parameter.new(@index, location: @location)
435
+ end
436
+
437
+ def to_sql
438
+ if @index
439
+ "$#{@index}"
440
+ else
441
+ "?"
442
+ end
443
+ end
444
+
445
+ def inspect
446
+ if @index
447
+ "Parameter($#{@index})"
448
+ else
449
+ "Parameter(?)"
450
+ end
451
+ end
452
+ end
453
+
454
+ # Star (*) in SELECT
455
+ class Star < Expression
456
+ attr_reader :table
457
+
458
+ def initialize(table: nil, location: nil)
459
+ super(location: location)
460
+ @table = table
461
+ end
462
+
463
+ def accept(visitor)
464
+ visitor.visit_star(self)
465
+ end
466
+
467
+ def clone
468
+ Star.new(table: @table, location: @location)
469
+ end
470
+
471
+ def to_sql
472
+ @table ? "#{@table}.*" : "*"
473
+ end
474
+
475
+ def inspect
476
+ @table ? "Star(#{@table}.*)" : "Star"
477
+ end
478
+ end
479
+
480
+ # SELECT column with optional alias
481
+ class SelectColumn < Node
482
+ attr_reader :expression, :alias_name
483
+
484
+ def initialize(expression, alias_name = nil, location: nil)
485
+ super(location: location)
486
+ @expression = expression
487
+ @alias_name = alias_name
488
+ end
489
+
490
+ def accept(visitor)
491
+ visitor.visit_select_column(self)
492
+ end
493
+
494
+ def clone
495
+ SelectColumn.new(@expression.clone, @alias_name, location: @location)
496
+ end
497
+
498
+ def to_sql
499
+ if @alias_name
500
+ "#{@expression.to_sql} AS #{@alias_name}"
501
+ else
502
+ @expression.to_sql
503
+ end
504
+ end
505
+
506
+ def inspect
507
+ if @alias_name
508
+ "SelectColumn(#{@expression.inspect}, AS #{@alias_name})"
509
+ else
510
+ "SelectColumn(#{@expression.inspect})"
511
+ end
512
+ end
513
+ end
514
+
515
+ # Table reference with optional alias
516
+ class TableRef < Node
517
+ attr_reader :name, :alias_name
518
+
519
+ def initialize(name, alias_name = nil, location: nil)
520
+ super(location: location)
521
+ @name = name
522
+ @alias_name = alias_name
523
+ end
524
+
525
+ def accept(visitor)
526
+ visitor.visit_table_ref(self)
527
+ end
528
+
529
+ def clone
530
+ TableRef.new(@name, @alias_name, location: @location)
531
+ end
532
+
533
+ def to_sql
534
+ if @alias_name
535
+ "#{@name} AS #{@alias_name}"
536
+ else
537
+ @name
538
+ end
539
+ end
540
+
541
+ def inspect
542
+ if @alias_name
543
+ "TableRef(#{@name} AS #{@alias_name})"
544
+ else
545
+ "TableRef(#{@name})"
546
+ end
547
+ end
548
+ end
549
+
550
+ # A joined table and its ON predicate. JOIN support is intentionally
551
+ # represented in the regular SELECT AST so parser, planner and executor
552
+ # all agree on table aliases and qualified identifiers.
553
+ class Join < Node
554
+ attr_reader :type, :table, :condition
555
+
556
+ def initialize(type, table, condition = nil, location: nil)
557
+ super(location: location)
558
+ @type = type
559
+ @table = table
560
+ @condition = condition
561
+ end
562
+
563
+ def accept(visitor)
564
+ visitor.visit_join(self)
565
+ end
566
+
567
+ def clone
568
+ Join.new(@type, @table.clone, @condition&.clone, location: @location)
569
+ end
570
+
571
+ def to_sql
572
+ keyword = {
573
+ left: "LEFT JOIN",
574
+ right: "RIGHT JOIN",
575
+ full: "FULL JOIN",
576
+ cross: "CROSS JOIN"
577
+ }.fetch(@type, "INNER JOIN")
578
+ sql = "#{keyword} #{@table.to_sql}"
579
+ sql << " ON #{@condition.to_sql}" if @condition
580
+ sql
581
+ end
582
+
583
+ def inspect
584
+ "Join(#{@type}, #{@table.inspect}, #{@condition.inspect})"
585
+ end
586
+ end
587
+
588
+ # ORDER BY item
589
+ class OrderItem < Node
590
+ attr_reader :expression, :direction
591
+
592
+ def initialize(expression, direction = :asc, location: nil)
593
+ super(location: location)
594
+ @expression = expression
595
+ @direction = direction
596
+ end
597
+
598
+ def accept(visitor)
599
+ visitor.visit_order_item(self)
600
+ end
601
+
602
+ def clone
603
+ OrderItem.new(@expression.clone, @direction, location: @location)
604
+ end
605
+
606
+ def to_sql
607
+ dir = (@direction == :asc) ? "ASC" : "DESC"
608
+ "#{@expression.to_sql} #{dir}"
609
+ end
610
+
611
+ def inspect
612
+ "OrderItem(#{@expression.inspect}, #{@direction})"
613
+ end
614
+ end
615
+ end
616
+ end
617
+ end