rubydb 0.1.3 → 0.1.5

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 (420) 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 +12 -5
  14. data/.rubocop.yml +50 -44
  15. data/.standard.yml +9 -14
  16. data/ARCHITECTURE.md +21 -21
  17. data/CHANGELOG.md +49 -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 +249 -162
  24. data/ROADMAP.md +27 -27
  25. data/Rakefile +71 -71
  26. data/SECURITY.md +54 -54
  27. data/SUPPORT.md +14 -14
  28. data/adapters/activerecord/Gemfile +11 -11
  29. data/adapters/activerecord/lib/active_record/connection_adapters/rubydb_adapter.rb +877 -880
  30. data/adapters/activerecord/rubydb-activerecord.gemspec +21 -21
  31. data/adapters/activerecord/spec/rubydb_adapter_integration_spec.rb +155 -143
  32. data/adapters/ruby/README.md +18 -18
  33. data/adapters/sequel/README.md +11 -11
  34. data/config/monitoring/prometheus-alerts.yml +39 -39
  35. data/config/production.yml +36 -36
  36. data/docs/README.md +75 -71
  37. data/docs/architecture/concurrency.md +14 -14
  38. data/docs/architecture/current-state.md +125 -125
  39. data/docs/architecture/execution-engine.md +25 -25
  40. data/docs/architecture/indexes.md +19 -19
  41. data/docs/architecture/mvcc.md +19 -19
  42. data/docs/architecture/overview.md +13 -13
  43. data/docs/architecture/pages.md +11 -11
  44. data/docs/architecture/production-roadmap.md +82 -82
  45. data/docs/architecture/query-planner.md +20 -20
  46. data/docs/architecture/recovery.md +18 -18
  47. data/docs/architecture/sql-engine.md +12 -12
  48. data/docs/architecture/storage-engine.md +14 -14
  49. data/docs/architecture/transactions.md +10 -10
  50. data/docs/architecture/wal.md +28 -28
  51. data/docs/cli-cheatsheet.md +98 -98
  52. data/docs/cli.md +275 -275
  53. data/docs/contributing/architecture.md +9 -9
  54. data/docs/contributing/benchmarking.md +14 -14
  55. data/docs/contributing/development.md +16 -16
  56. data/docs/contributing/release-process.md +49 -49
  57. data/docs/contributing/testing.md +16 -16
  58. data/docs/debugging.md +229 -229
  59. data/docs/developer/branching.md +10 -10
  60. data/docs/developer/database-diff.md +10 -10
  61. data/docs/developer/local-development.md +17 -17
  62. data/docs/developer/snapshots.md +9 -9
  63. data/docs/developer/temporal-data.md +10 -10
  64. data/docs/developer-guide.md +297 -297
  65. data/docs/getting-started/first-database.md +16 -16
  66. data/docs/getting-started/first-query.md +13 -13
  67. data/docs/getting-started/installation.md +19 -19
  68. data/docs/getting-started/local-to-production.md +300 -300
  69. data/docs/getting-started/quickstart.md +17 -17
  70. data/docs/getting-started/rails.md +16 -16
  71. data/docs/hardening_backlog.md +93 -93
  72. data/docs/lessons-learned.md +112 -112
  73. data/docs/operations/backups.md +33 -33
  74. data/docs/operations/disaster-recovery.md +31 -31
  75. data/docs/operations/failover.md +30 -30
  76. data/docs/operations/monitoring.md +25 -25
  77. data/docs/operations/production-guide.md +295 -295
  78. data/docs/operations/production-runbook.md +45 -45
  79. data/docs/operations/replication.md +33 -33
  80. data/docs/operations/restore.md +6 -6
  81. data/docs/operations/runbook.md +34 -34
  82. data/docs/operations/upgrades.md +14 -14
  83. data/docs/operations/workload-testing.md +17 -17
  84. data/docs/production-readiness.md +118 -118
  85. data/docs/production_validation.md +150 -150
  86. data/docs/rails/active-record.md +11 -11
  87. data/docs/rails/compatibility-guide.md +90 -90
  88. data/docs/rails/database-yml.md +92 -92
  89. data/docs/rails/installation.md +17 -17
  90. data/docs/rails/migrations.md +17 -17
  91. data/docs/rails/production.md +82 -82
  92. data/docs/rails/troubleshooting.md +18 -18
  93. data/docs/release.md +25 -25
  94. data/docs/server/architecture.md +10 -10
  95. data/docs/server/authentication.md +10 -10
  96. data/docs/server/configuration.md +16 -16
  97. data/docs/server/connection-pooling.md +10 -10
  98. data/docs/server/deployment.md +10 -10
  99. data/docs/server/protocol.md +12 -12
  100. data/docs/sql/compatibility-guide.md +82 -82
  101. data/docs/sql/compatibility.md +39 -39
  102. data/docs/sql/data-types.md +10 -10
  103. data/docs/sql/functions.md +9 -9
  104. data/docs/sql/joins.md +9 -9
  105. data/docs/sql/operators.md +9 -9
  106. data/docs/sql/sqlite-compatibility.md +21 -21
  107. data/docs/sql/syntax.md +10 -10
  108. data/docs/sql/transactions.md +10 -10
  109. data/docs/troubleshooting.md +244 -244
  110. data/lessons/01-foundations.md +73 -0
  111. data/lessons/02-local-development.md +121 -0
  112. data/lessons/03-embedded-rubydb.md +99 -0
  113. data/lessons/04-rails-complex-apps.md +138 -0
  114. data/lessons/05-rubydb-production-server.md +237 -0
  115. data/lessons/06-postgresql-massive-apps.md +96 -0
  116. data/lessons/07-hybrid-microservices.md +94 -0
  117. data/lessons/08-migrations-backups-recovery.md +86 -0
  118. data/lessons/09-observability-security-scale.md +87 -0
  119. data/lessons/10-release-readiness.md +125 -0
  120. data/lib/rubydb/backup/archive.rb +332 -334
  121. data/lib/rubydb/backup/backup.rb +400 -401
  122. data/lib/rubydb/backup/incremental.rb +349 -353
  123. data/lib/rubydb/backup/restore.rb +289 -290
  124. data/lib/rubydb/backup/snapshot.rb +265 -267
  125. data/lib/rubydb/backup/verification.rb +276 -279
  126. data/lib/rubydb/branching/branch.rb +181 -181
  127. data/lib/rubydb/branching/branch_manager.rb +307 -311
  128. data/lib/rubydb/branching/branch_metadata.rb +140 -140
  129. data/lib/rubydb/branching/checkout.rb +165 -166
  130. data/lib/rubydb/branching/copy_on_write.rb +272 -272
  131. data/lib/rubydb/branching/diff.rb +137 -138
  132. data/lib/rubydb/branching/merge.rb +282 -285
  133. data/lib/rubydb/build_info.rb +15 -15
  134. data/lib/rubydb/catalog/catalog.rb +391 -391
  135. data/lib/rubydb/catalog/column.rb +112 -112
  136. data/lib/rubydb/catalog/constraint.rb +180 -180
  137. data/lib/rubydb/catalog/database.rb +184 -184
  138. data/lib/rubydb/catalog/index.rb +97 -97
  139. data/lib/rubydb/catalog/schema.rb +103 -103
  140. data/lib/rubydb/catalog/sequence.rb +90 -90
  141. data/lib/rubydb/catalog/system_catalog.rb +698 -698
  142. data/lib/rubydb/catalog/table.rb +178 -178
  143. data/lib/rubydb/catalog/trigger.rb +102 -102
  144. data/lib/rubydb/catalog/view.rb +66 -66
  145. data/lib/rubydb/cli/application.rb +163 -163
  146. data/lib/rubydb/cli/commands/backup.rb +80 -81
  147. data/lib/rubydb/cli/commands/branch.rb +72 -72
  148. data/lib/rubydb/cli/commands/checkout.rb +54 -54
  149. data/lib/rubydb/cli/commands/create.rb +58 -58
  150. data/lib/rubydb/cli/commands/diff.rb +76 -77
  151. data/lib/rubydb/cli/commands/doctor.rb +74 -74
  152. data/lib/rubydb/cli/commands/drop.rb +57 -57
  153. data/lib/rubydb/cli/commands/init.rb +101 -102
  154. data/lib/rubydb/cli/commands/inspect.rb +95 -95
  155. data/lib/rubydb/cli/commands/merge.rb +63 -63
  156. data/lib/rubydb/cli/commands/migrate.rb +62 -62
  157. data/lib/rubydb/cli/commands/restart.rb +42 -39
  158. data/lib/rubydb/cli/commands/restore.rb +121 -121
  159. data/lib/rubydb/cli/commands/shell.rb +365 -365
  160. data/lib/rubydb/cli/commands/snapshot.rb +79 -79
  161. data/lib/rubydb/cli/commands/start.rb +88 -82
  162. data/lib/rubydb/cli/commands/status.rb +96 -92
  163. data/lib/rubydb/cli/commands/stop.rb +47 -47
  164. data/lib/rubydb/cli/commands/vacuum.rb +58 -58
  165. data/lib/rubydb/cli/formatter.rb +221 -221
  166. data/lib/rubydb/cli/output.rb +168 -168
  167. data/lib/rubydb/client/client.rb +302 -304
  168. data/lib/rubydb/client/connection.rb +414 -415
  169. data/lib/rubydb/client/connection_pool.rb +168 -168
  170. data/lib/rubydb/client/connection_url.rb +96 -96
  171. data/lib/rubydb/client/prepared_statement.rb +60 -60
  172. data/lib/rubydb/client/result.rb +123 -123
  173. data/lib/rubydb/client/statement.rb +52 -52
  174. data/lib/rubydb/client/transaction.rb +130 -130
  175. data/lib/rubydb/concurrency/concurrency.rb +19 -19
  176. data/lib/rubydb/concurrency/deadlock_detector.rb +148 -150
  177. data/lib/rubydb/concurrency/latch.rb +101 -101
  178. data/lib/rubydb/concurrency/lock_graph.rb +163 -165
  179. data/lib/rubydb/concurrency/mutex.rb +181 -183
  180. data/lib/rubydb/concurrency/rw_lock.rb +180 -180
  181. data/lib/rubydb/concurrency/scheduler.rb +248 -250
  182. data/lib/rubydb/concurrency/worker_pool.rb +145 -143
  183. data/lib/rubydb/configuration/config.rb +170 -170
  184. data/lib/rubydb/configuration/defaults.rb +179 -179
  185. data/lib/rubydb/configuration/environment.rb +152 -152
  186. data/lib/rubydb/configuration/parser.rb +185 -185
  187. data/lib/rubydb/configuration/validation.rb +221 -221
  188. data/lib/rubydb/constants.rb +74 -74
  189. data/lib/rubydb/constraints/check.rb +181 -181
  190. data/lib/rubydb/constraints/constraint.rb +101 -101
  191. data/lib/rubydb/constraints/foreign_key.rb +130 -130
  192. data/lib/rubydb/constraints/not_null.rb +64 -64
  193. data/lib/rubydb/constraints/primary_key.rb +99 -99
  194. data/lib/rubydb/constraints/unique.rb +106 -108
  195. data/lib/rubydb/constraints/validator.rb +349 -350
  196. data/lib/rubydb/errors/authentication_error.rb +10 -10
  197. data/lib/rubydb/errors/authorization_error.rb +23 -23
  198. data/lib/rubydb/errors/client_error.rb +10 -10
  199. data/lib/rubydb/errors/configuration_error.rb +10 -10
  200. data/lib/rubydb/errors/connection_error.rb +10 -10
  201. data/lib/rubydb/errors/constraint_error.rb +23 -23
  202. data/lib/rubydb/errors/corruption_error.rb +10 -10
  203. data/lib/rubydb/errors/database_error.rb +10 -10
  204. data/lib/rubydb/errors/error.rb +20 -20
  205. data/lib/rubydb/errors/execution_error.rb +10 -10
  206. data/lib/rubydb/errors/parser_error.rb +10 -10
  207. data/lib/rubydb/errors/recovery_error.rb +10 -10
  208. data/lib/rubydb/errors/replication_error.rb +10 -10
  209. data/lib/rubydb/errors/server_error.rb +6 -6
  210. data/lib/rubydb/errors/storage_error.rb +10 -10
  211. data/lib/rubydb/errors/transaction_error.rb +10 -10
  212. data/lib/rubydb/execution/aggregate_executor.rb +134 -138
  213. data/lib/rubydb/execution/delete_executor.rb +110 -112
  214. data/lib/rubydb/execution/distinct_executor.rb +131 -135
  215. data/lib/rubydb/execution/executor.rb +1195 -1192
  216. data/lib/rubydb/execution/expression.rb +191 -193
  217. data/lib/rubydb/execution/index_scan.rb +142 -142
  218. data/lib/rubydb/execution/insert_executor.rb +215 -217
  219. data/lib/rubydb/execution/join_executor.rb +243 -249
  220. data/lib/rubydb/execution/limit_executor.rb +83 -85
  221. data/lib/rubydb/execution/optimizer.rb +227 -215
  222. data/lib/rubydb/execution/plan.rb +355 -353
  223. data/lib/rubydb/execution/planner.rb +542 -536
  224. data/lib/rubydb/execution/predicate.rb +235 -235
  225. data/lib/rubydb/execution/scan.rb +49 -49
  226. data/lib/rubydb/execution/sequential_scan.rb +63 -63
  227. data/lib/rubydb/execution/sort_executor.rb +180 -185
  228. data/lib/rubydb/execution/update_executor.rb +160 -162
  229. data/lib/rubydb/functions/aggregate.rb +70 -70
  230. data/lib/rubydb/functions/date_functions.rb +274 -278
  231. data/lib/rubydb/functions/function.rb +85 -85
  232. data/lib/rubydb/functions/json_functions.rb +231 -215
  233. data/lib/rubydb/functions/numeric_functions.rb +346 -346
  234. data/lib/rubydb/functions/scalar.rb +52 -52
  235. data/lib/rubydb/functions/string_functions.rb +383 -383
  236. data/lib/rubydb/functions/system_functions.rb +258 -246
  237. data/lib/rubydb/history/as_of.rb +238 -238
  238. data/lib/rubydb/history/change.rb +105 -105
  239. data/lib/rubydb/history/history.rb +131 -131
  240. data/lib/rubydb/history/history_manager.rb +228 -229
  241. data/lib/rubydb/history/temporal_query.rb +202 -202
  242. data/lib/rubydb/history/timeline.rb +144 -144
  243. data/lib/rubydb/indexes/btree.rb +186 -186
  244. data/lib/rubydb/indexes/btree_cursor.rb +258 -258
  245. data/lib/rubydb/indexes/btree_node.rb +384 -385
  246. data/lib/rubydb/indexes/hash_index.rb +150 -150
  247. data/lib/rubydb/indexes/index.rb +71 -71
  248. data/lib/rubydb/indexes/index_manager.rb +408 -406
  249. data/lib/rubydb/indexes/index_scan.rb +466 -470
  250. data/lib/rubydb/migrations/migration.rb +253 -254
  251. data/lib/rubydb/migrations/migration_lock.rb +146 -146
  252. data/lib/rubydb/migrations/migration_manager.rb +187 -176
  253. data/lib/rubydb/migrations/migration_version.rb +71 -71
  254. data/lib/rubydb/migrations/schema_diff.rb +211 -211
  255. data/lib/rubydb/migrations/schema_version.rb +64 -64
  256. data/lib/rubydb/monitoring/events.rb +155 -160
  257. data/lib/rubydb/monitoring/health.rb +216 -222
  258. data/lib/rubydb/monitoring/logger.rb +188 -193
  259. data/lib/rubydb/monitoring/metrics.rb +363 -359
  260. data/lib/rubydb/monitoring/performance.rb +176 -176
  261. data/lib/rubydb/monitoring/statistics.rb +168 -170
  262. data/lib/rubydb/mvcc/garbage_collector.rb +199 -199
  263. data/lib/rubydb/mvcc/mvcc.rb +16 -16
  264. data/lib/rubydb/mvcc/snapshot.rb +146 -147
  265. data/lib/rubydb/mvcc/vacuum.rb +180 -180
  266. data/lib/rubydb/mvcc/version.rb +106 -106
  267. data/lib/rubydb/mvcc/version_store.rb +396 -398
  268. data/lib/rubydb/mvcc/visibility.rb +107 -109
  269. data/lib/rubydb/protocol/capabilities.rb +125 -125
  270. data/lib/rubydb/protocol/decoder.rb +142 -145
  271. data/lib/rubydb/protocol/encoder.rb +131 -136
  272. data/lib/rubydb/protocol/handshake.rb +306 -305
  273. data/lib/rubydb/protocol/message.rb +121 -121
  274. data/lib/rubydb/protocol/parameter_binder.rb +101 -0
  275. data/lib/rubydb/protocol/protocol.rb +276 -277
  276. data/lib/rubydb/protocol/version.rb +54 -54
  277. data/lib/rubydb/rails/adapter.rb +245 -239
  278. data/lib/rubydb/rails/connection.rb +312 -314
  279. data/lib/rubydb/rails/database_statements.rb +122 -122
  280. data/lib/rubydb/rails/migration.rb +131 -131
  281. data/lib/rubydb/rails/quoting.rb +109 -109
  282. data/lib/rubydb/rails/result.rb +123 -120
  283. data/lib/rubydb/rails/schema_statements.rb +339 -339
  284. data/lib/rubydb/rails/transaction.rb +105 -105
  285. data/lib/rubydb/rails/type.rb +126 -126
  286. data/lib/rubydb/recovery/checkpoint.rb +261 -257
  287. data/lib/rubydb/recovery/consistency.rb +457 -467
  288. data/lib/rubydb/recovery/corruption_detector.rb +5 -5
  289. data/lib/rubydb/recovery/crash_recovery.rb +381 -387
  290. data/lib/rubydb/recovery/recovery_manager.rb +204 -206
  291. data/lib/rubydb/recovery/redo.rb +235 -237
  292. data/lib/rubydb/recovery/undo.rb +204 -206
  293. data/lib/rubydb/replication/failover.rb +5 -5
  294. data/lib/rubydb/replication/fencing.rb +63 -63
  295. data/lib/rubydb/replication/primary.rb +461 -450
  296. data/lib/rubydb/replication/replica.rb +382 -384
  297. data/lib/rubydb/replication/replication_log.rb +194 -200
  298. data/lib/rubydb/replication/replication_manager.rb +307 -308
  299. data/lib/rubydb/replication/replication_slot.rb +293 -295
  300. data/lib/rubydb/replication/replication_stream.rb +198 -201
  301. data/lib/rubydb/rubydb.rb +564 -560
  302. data/lib/rubydb/security/access_control.rb +252 -254
  303. data/lib/rubydb/security/audit_log.rb +209 -213
  304. data/lib/rubydb/security/authentication.rb +302 -302
  305. data/lib/rubydb/security/authorization.rb +282 -282
  306. data/lib/rubydb/security/credentials.rb +192 -196
  307. data/lib/rubydb/security/password.rb +205 -215
  308. data/lib/rubydb/security/permissions.rb +74 -74
  309. data/lib/rubydb/security/role.rb +99 -101
  310. data/lib/rubydb/security/user.rb +86 -86
  311. data/lib/rubydb/server/connection.rb +383 -366
  312. data/lib/rubydb/server/connection_pool.rb +193 -193
  313. data/lib/rubydb/server/lifecycle.rb +227 -228
  314. data/lib/rubydb/server/listener.rb +139 -136
  315. data/lib/rubydb/server/request_handler.rb +277 -276
  316. data/lib/rubydb/server/server.rb +363 -364
  317. data/lib/rubydb/server/session.rb +371 -369
  318. data/lib/rubydb/server/worker.rb +206 -210
  319. data/lib/rubydb/server/worker_pool.rb +168 -168
  320. data/lib/rubydb/sql/ast/alter_table.rb +169 -169
  321. data/lib/rubydb/sql/ast/begin_transaction.rb +47 -47
  322. data/lib/rubydb/sql/ast/commit.rb +37 -37
  323. data/lib/rubydb/sql/ast/constraint.rb +92 -83
  324. data/lib/rubydb/sql/ast/create_database.rb +41 -41
  325. data/lib/rubydb/sql/ast/create_index.rb +61 -61
  326. data/lib/rubydb/sql/ast/create_schema.rb +52 -52
  327. data/lib/rubydb/sql/ast/create_table.rb +187 -187
  328. data/lib/rubydb/sql/ast/delete.rb +54 -54
  329. data/lib/rubydb/sql/ast/drop_database.rb +41 -41
  330. data/lib/rubydb/sql/ast/drop_index.rb +41 -41
  331. data/lib/rubydb/sql/ast/drop_schema.rb +49 -49
  332. data/lib/rubydb/sql/ast/drop_table.rb +49 -49
  333. data/lib/rubydb/sql/ast/explain.rb +64 -64
  334. data/lib/rubydb/sql/ast/expression.rb +617 -604
  335. data/lib/rubydb/sql/ast/insert.rb +66 -66
  336. data/lib/rubydb/sql/ast/node.rb +42 -42
  337. data/lib/rubydb/sql/ast/rollback.rb +63 -63
  338. data/lib/rubydb/sql/ast/savepoint.rb +59 -59
  339. data/lib/rubydb/sql/ast/select.rb +88 -88
  340. data/lib/rubydb/sql/ast/set_operation.rb +22 -20
  341. data/lib/rubydb/sql/ast/trigger.rb +35 -29
  342. data/lib/rubydb/sql/ast/update.rb +88 -88
  343. data/lib/rubydb/sql/ast/vacuum.rb +19 -19
  344. data/lib/rubydb/sql/ast/view.rb +38 -32
  345. data/lib/rubydb/sql/ast/with.rb +32 -32
  346. data/lib/rubydb/sql/grammar.rb +86 -86
  347. data/lib/rubydb/sql/keywords.rb +156 -156
  348. data/lib/rubydb/sql/lexer.rb +209 -214
  349. data/lib/rubydb/sql/operators.rb +100 -100
  350. data/lib/rubydb/sql/parser.rb +1167 -1170
  351. data/lib/rubydb/sql/planner/analyzer.rb +283 -302
  352. data/lib/rubydb/sql/planner/binder.rb +537 -550
  353. data/lib/rubydb/sql/planner/type_checker.rb +427 -431
  354. data/lib/rubydb/sql/token.rb +210 -210
  355. data/lib/rubydb/storage/buffer_frame.rb +44 -44
  356. data/lib/rubydb/storage/buffer_pool.rb +155 -155
  357. data/lib/rubydb/storage/database_lock.rb +74 -74
  358. data/lib/rubydb/storage/deserializer.rb +332 -342
  359. data/lib/rubydb/storage/engine.rb +2347 -2330
  360. data/lib/rubydb/storage/file_manager.rb +191 -187
  361. data/lib/rubydb/storage/free_space_map.rb +79 -81
  362. data/lib/rubydb/storage/page.rb +92 -94
  363. data/lib/rubydb/storage/page_allocator.rb +852 -855
  364. data/lib/rubydb/storage/page_header.rb +63 -67
  365. data/lib/rubydb/storage/page_manager.rb +127 -131
  366. data/lib/rubydb/storage/record.rb +58 -58
  367. data/lib/rubydb/storage/row.rb +78 -78
  368. data/lib/rubydb/storage/serializer.rb +51 -51
  369. data/lib/rubydb/storage/storage_layout.rb +151 -151
  370. data/lib/rubydb/storage/storage_manager.rb +114 -114
  371. data/lib/rubydb/storage/tuple.rb +458 -461
  372. data/lib/rubydb/storage/visibility_map.rb +964 -973
  373. data/lib/rubydb/transactions/commit_manager.rb +219 -220
  374. data/lib/rubydb/transactions/isolation.rb +98 -98
  375. data/lib/rubydb/transactions/lock.rb +76 -76
  376. data/lib/rubydb/transactions/lock_manager.rb +359 -362
  377. data/lib/rubydb/transactions/savepoint.rb +142 -143
  378. data/lib/rubydb/transactions/transaction.rb +214 -215
  379. data/lib/rubydb/transactions/transaction_id.rb +84 -84
  380. data/lib/rubydb/transactions/transaction_log.rb +256 -257
  381. data/lib/rubydb/transactions/transaction_manager.rb +434 -435
  382. data/lib/rubydb/types/bigint.rb +36 -36
  383. data/lib/rubydb/types/blob.rb +37 -37
  384. data/lib/rubydb/types/boolean.rb +34 -34
  385. data/lib/rubydb/types/date.rb +39 -39
  386. data/lib/rubydb/types/decimal.rb +48 -48
  387. data/lib/rubydb/types/float.rb +34 -34
  388. data/lib/rubydb/types/integer.rb +36 -36
  389. data/lib/rubydb/types/json.rb +41 -41
  390. data/lib/rubydb/types/null.rb +34 -34
  391. data/lib/rubydb/types/smallint.rb +36 -36
  392. data/lib/rubydb/types/text.rb +37 -37
  393. data/lib/rubydb/types/time.rb +46 -46
  394. data/lib/rubydb/types/timestamp.rb +39 -39
  395. data/lib/rubydb/types/type.rb +119 -119
  396. data/lib/rubydb/types/uuid.rb +47 -47
  397. data/lib/rubydb/types/varchar.rb +37 -37
  398. data/lib/rubydb/version.rb +32 -32
  399. data/lib/rubydb/wal/archive.rb +190 -193
  400. data/lib/rubydb/wal/checkpoint.rb +181 -183
  401. data/lib/rubydb/wal/lsn.rb +94 -94
  402. data/lib/rubydb/wal/reader.rb +259 -260
  403. data/lib/rubydb/wal/record.rb +105 -105
  404. data/lib/rubydb/wal/segment.rb +193 -193
  405. data/lib/rubydb/wal/wal.rb +480 -452
  406. data/lib/rubydb/wal/writer.rb +236 -236
  407. data/lib/rubydb.rb +7 -7
  408. data/packaging/docker/docker-compose.failover.yml +43 -43
  409. data/packaging/homebrew/rubydb.rb +19 -19
  410. data/rubydb.gemspec +60 -57
  411. data/scripts/benchmark +7 -7
  412. data/scripts/durability_drill +37 -37
  413. data/scripts/fuzz +63 -63
  414. data/scripts/release +45 -40
  415. data/scripts/release_check +43 -43
  416. data/scripts/replication_failover_drill +268 -250
  417. data/scripts/replication_network_failover_drill +287 -255
  418. data/scripts/restore_drill +45 -45
  419. data/scripts/security +45 -0
  420. metadata +54 -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