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,369 +1,416 @@
1
- # frozen_string_literal: true
2
-
3
- require "securerandom"
4
- require "time"
5
-
6
- module RubyDB
7
- module Server
8
- # Session - Represents a client session
9
- class Session
10
- # A request-scoped cancellation token. Cancellation is deliberately
11
- # cooperative: executors observe it at bounded points and unwind the
12
- # request without killing a Ruby thread while it owns engine state.
13
- class CancellationToken
14
- def initialize
15
- @lock = Mutex.new
16
- @cancelled = false
17
- end
18
-
19
- def cancel!
20
- @lock.synchronize { @cancelled = true }
21
- end
22
-
23
- def cancelled?
24
- @lock.synchronize { @cancelled }
25
- end
26
- end
27
-
28
- attr_reader :id, :connection, :created_at, :last_activity
29
- attr_reader :username, :database, :transaction
30
-
31
- def initialize(connection, config = {})
32
- @id = generate_session_id
33
- @connection = connection
34
- @config = config
35
- @created_at = Time.now
36
- @last_activity = Time.now
37
- @username = nil
38
- @database = nil
39
- @permissions = nil
40
- @transaction = nil
41
- @prepared_statements = {}
42
- @cursors = {}
43
- @variables = {}
44
- @is_active = true
45
- @lock = Mutex.new
46
- @operations_lock = Mutex.new
47
- @operations = {}
48
- @pending_cancellations = {}
49
- end
50
-
51
- def authenticate(credentials)
52
- @lock.synchronize do
53
- @username = credentials[:username]
54
- @database = credentials[:database] || "rubydb"
55
- @permissions = resolve_permissions(@username)
56
- @last_activity = Time.now
57
- true
58
- end
59
- end
60
-
61
- def process(request)
62
- return process_cancel(request) if request[:type].to_s == "cancel"
63
-
64
- @lock.synchronize do
65
- @last_activity = Time.now
66
- if request[:deadline_at] && Time.now >= Time.parse(request[:deadline_at].to_s)
67
- return { success: false, error: "Request deadline exceeded before execution", code: "deadline_exceeded" }
68
- end
69
-
70
- request_id = request[:request_id].to_s
71
- request_id = "local_#{object_id}_#{Time.now.to_f}" if request_id.empty?
72
- cancellation = CancellationToken.new
73
- @operations_lock.synchronize do
74
- @operations[request_id] = cancellation
75
- cancellation.cancel! if @pending_cancellations.delete(request_id)
76
- end
77
-
78
- begin
79
- case request[:type]
80
- when "query"
81
- process_query(request[:sql], request[:params] || [], request[:deadline_at], cancellation)
82
- when "prepare"
83
- process_prepare(request[:sql])
84
- when "execute"
85
- process_execute(request[:statement_id], request[:params] || [], request[:deadline_at], cancellation)
86
- when "close"
87
- process_close(request[:statement_id])
88
- when "begin"
89
- process_begin
90
- when "commit"
91
- process_commit
92
- when "rollback"
93
- process_rollback
94
- when "ping"
95
- process_ping
96
- else
97
- { success: false, error: "Unknown request type: #{request[:type]}" }
98
- end
99
- rescue RubyDB::ExecutionError => error
100
- raise unless %w[deadline_exceeded cancelled].include?(error.code.to_s)
101
-
102
- { success: false, error: error.message, code: error.code.to_s }
103
- ensure
104
- @operations_lock.synchronize { @operations.delete(request_id) }
105
- end
106
- end
107
- end
108
-
109
- def cancel(request_id, allow_pending: false)
110
- token = @operations_lock.synchronize do
111
- key = request_id.to_s
112
- current = @operations[key]
113
- @pending_cancellations[key] = true if !current && allow_pending
114
- current
115
- end
116
-
117
- token&.cancel!
118
- true
119
- end
120
-
121
- def close
122
- @lock.synchronize do
123
- @is_active = false
124
-
125
- # Close all prepared statements
126
- @prepared_statements.each do |id, stmt|
127
- stmt[:close].call if stmt[:close]
128
- end
129
- @prepared_statements.clear
130
-
131
- # Close all cursors
132
- @cursors.each do |id, cursor|
133
- cursor[:close].call if cursor[:close]
134
- end
135
- @cursors.clear
136
-
137
- # Rollback transaction if active
138
- if @transaction && @transaction[:active]
139
- @transaction[:rollback].call if @transaction[:rollback]
140
- end
141
- @transaction = nil
142
-
143
- true
144
- end
145
- end
146
-
147
- def active?
148
- @is_active
149
- end
150
-
151
- def to_hash
152
- {
153
- id: @id,
154
- username: @username,
155
- database: @database,
156
- created_at: @created_at.iso8601,
157
- last_activity: @last_activity.iso8601,
158
- prepared_statements: @prepared_statements.size,
159
- cursors: @cursors.size,
160
- variables: @variables.size,
161
- in_transaction: @transaction && @transaction[:active],
162
- active: @is_active
163
- }
164
- end
165
-
166
- private
167
-
168
- def generate_session_id
169
- "sess_#{Time.now.to_i}_#{SecureRandom.hex(8)}"
170
- end
171
-
172
- def process_cancel(request)
173
- target = request[:target_request_id] || request[:request_id]
174
- cancelled = cancel(target, allow_pending: request[:allow_pending_cancellation] == true)
175
- {
176
- success: cancelled,
177
- type: "cancel_response",
178
- target_request_id: target,
179
- cancelled: cancelled,
180
- code: cancelled ? "cancel_requested" : "request_not_found",
181
- timestamp: Time.now.iso8601
182
- }
183
- end
184
-
185
- def process_query(sql, params, deadline_at = nil, cancellation = nil)
186
- permission_error = authorize_sql(sql)
187
- return permission_error if permission_error
188
-
189
- {
190
- success: true,
191
- type: "query_result",
192
- result: execute_sql(sql, params, deadline_at: deadline_at, cancellation: cancellation),
193
- timestamp: Time.now.iso8601
194
- }
195
- end
196
-
197
- def process_prepare(sql)
198
- permission_error = authorize_sql(sql)
199
- return permission_error if permission_error
200
-
201
- stmt_id = "stmt_#{Time.now.to_i}_#{SecureRandom.hex(4)}"
202
- @prepared_statements[stmt_id] = {
203
- sql: sql,
204
- created_at: Time.now,
205
- close: lambda { @prepared_statements.delete(stmt_id) }
206
- }
207
-
208
- {
209
- success: true,
210
- type: "prepare_result",
211
- statement_id: stmt_id,
212
- timestamp: Time.now.iso8601
213
- }
214
- end
215
-
216
- def process_execute(stmt_id, params, deadline_at = nil, cancellation = nil)
217
- stmt = @prepared_statements[stmt_id]
218
- unless stmt
219
- return {
220
- success: false,
221
- error: "Statement not found: #{stmt_id}"
222
- }
223
- end
224
-
225
- {
226
- success: true,
227
- type: "execute_result",
228
- result: execute_sql(stmt[:sql], params, deadline_at: deadline_at, cancellation: cancellation),
229
- timestamp: Time.now.iso8601
230
- }
231
- end
232
-
233
- def process_close(stmt_id)
234
- @prepared_statements.delete(stmt_id)
235
-
236
- {
237
- success: true,
238
- type: "close_result",
239
- timestamp: Time.now.iso8601
240
- }
241
- end
242
-
243
- def process_begin
244
- if @transaction && @transaction[:active]
245
- return {
246
- success: false,
247
- error: "Transaction already active"
248
- }
249
- end
250
-
251
- engine = @config[:engine]
252
- transaction_id = engine&.begin_transaction || "txn_#{Time.now.to_i}"
253
- @transaction = {
254
- id: transaction_id,
255
- started_at: Time.now,
256
- active: true,
257
- rollback: lambda do
258
- engine&.rollback_transaction
259
- @transaction = nil
260
- end
261
- }
262
-
263
- {
264
- success: true,
265
- type: "begin_result",
266
- transaction_id: @transaction[:id],
267
- timestamp: Time.now.iso8601
268
- }
269
- end
270
-
271
- def process_commit
272
- unless @transaction && @transaction[:active]
273
- return {
274
- success: false,
275
- error: "No active transaction"
276
- }
277
- end
278
-
279
- committed = @config[:engine]&.commit_transaction
280
- unless committed
281
- return {
282
- success: false,
283
- error: "Transaction commit failed",
284
- commit_ack: @config[:engine]&.last_commit_ack
285
- }
286
- end
287
- @transaction[:active] = false
288
- @transaction = nil
289
-
290
- {
291
- success: true,
292
- type: "commit_result",
293
- committed: true,
294
- durable: @config[:engine]&.last_commit_ack&.fetch(:status, nil) == :durable,
295
- commit_ack: @config[:engine]&.last_commit_ack,
296
- timestamp: Time.now.iso8601
297
- }
298
- end
299
-
300
- def process_rollback
301
- unless @transaction && @transaction[:active]
302
- return {
303
- success: false,
304
- error: "No active transaction"
305
- }
306
- end
307
-
308
- @transaction[:active] = false
309
- @config[:engine]&.rollback_transaction
310
- @transaction = nil
311
-
312
- {
313
- success: true,
314
- type: "rollback_result",
315
- timestamp: Time.now.iso8601
316
- }
317
- end
318
-
319
- def process_ping
320
- {
321
- success: true,
322
- type: "pong",
323
- timestamp: Time.now.iso8601,
324
- session_id: @id
325
- }
326
- end
327
-
328
- def execute_sql(sql, params, deadline_at: nil, cancellation: nil)
329
- engine = @config[:engine]
330
- raise RubyDB::ServerError, "Session has no database engine" unless engine
331
-
332
- tokens = RubyDB::SQL::Lexer.new(sql).tokenize
333
- statements = RubyDB::SQL::Parser.new(tokens).parse
334
- results = statements.map do |statement|
335
- plan = RubyDB::Execution::Planner.new(engine).plan(statement)
336
- RubyDB::Execution::Executor.new(
337
- engine,
338
- deadline_at: deadline_at,
339
- cancellation: cancellation
340
- ).execute(plan)
341
- end
342
- results.size == 1 ? results.first : results
343
- end
344
-
345
- def resolve_permissions(username)
346
- policy = @config[:authorization] || @config["authorization"]
347
- return %i[read write] unless policy
348
-
349
- users = policy[:users] || policy["users"] || {}
350
- symbol_username = username.respond_to?(:to_sym) ? username.to_sym : username
351
- definition = users[username] || users[username.to_s] || users[symbol_username]
352
- permissions = definition.is_a?(Hash) ? (definition[:permissions] || definition["permissions"]) : definition
353
- Array(permissions).map(&:to_sym)
354
- end
355
-
356
- def authorize_sql(sql)
357
- required = sql.to_s.strip.split(/\s+/, 2).first.to_s.downcase
358
- permission = %w[select show explain].include?(required) ? :read : :write
359
- return nil if @permissions&.include?(permission)
360
-
361
- {
362
- success: false,
363
- error: "Permission denied: #{permission} access is required",
364
- timestamp: Time.now.iso8601
365
- }
366
- end
367
- end
368
- end
369
- end
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require "time"
5
+ require_relative "../protocol/parameter_binder"
6
+
7
+ module RubyDB
8
+ module Server
9
+ # Session - Represents a client session
10
+ class Session
11
+ # A request-scoped cancellation token. Cancellation is deliberately
12
+ # cooperative: executors observe it at bounded points and unwind the
13
+ # request without killing a Ruby thread while it owns engine state.
14
+ class CancellationToken
15
+ def initialize
16
+ @lock = Mutex.new
17
+ @cancelled = false
18
+ end
19
+
20
+ def cancel!
21
+ @lock.synchronize { @cancelled = true }
22
+ end
23
+
24
+ def cancelled?
25
+ @lock.synchronize { @cancelled }
26
+ end
27
+ end
28
+
29
+ attr_reader :id, :connection, :created_at, :last_activity
30
+ attr_reader :username, :database, :transaction
31
+
32
+ def initialize(connection, config = {})
33
+ @id = generate_session_id
34
+ @connection = connection
35
+ @config = config
36
+ @created_at = Time.now
37
+ @last_activity = Time.now
38
+ @username = nil
39
+ @database = nil
40
+ @permissions = nil
41
+ @transaction = nil
42
+ @prepared_statements = {}
43
+ @cursors = {}
44
+ @variables = {}
45
+ @is_active = true
46
+ @lock = Mutex.new
47
+ @operations_lock = Mutex.new
48
+ @operations = {}
49
+ @pending_cancellations = {}
50
+ end
51
+
52
+ def authenticate(credentials)
53
+ @lock.synchronize do
54
+ @username = credentials[:username]
55
+ @database = credentials[:database] || "rubydb"
56
+ @permissions = resolve_permissions(@username)
57
+ @last_activity = Time.now
58
+ true
59
+ end
60
+ end
61
+
62
+ def process(request)
63
+ return process_cancel(request) if request[:type].to_s == "cancel"
64
+
65
+ @lock.synchronize do
66
+ @last_activity = Time.now
67
+ if request[:deadline_at] && Time.now >= Time.parse(request[:deadline_at].to_s)
68
+ return {success: false, error: "Request deadline exceeded before execution", code: "deadline_exceeded"}
69
+ end
70
+
71
+ request_id = request[:request_id].to_s
72
+ request_id = "local_#{object_id}_#{Time.now.to_f}" if request_id.empty?
73
+ cancellation = CancellationToken.new
74
+ @operations_lock.synchronize do
75
+ @operations[request_id] = cancellation
76
+ cancellation.cancel! if @pending_cancellations.delete(request_id)
77
+ end
78
+
79
+ begin
80
+ case request[:type]
81
+ when "query"
82
+ process_query(request[:sql], request[:params] || [], request[:deadline_at], cancellation)
83
+ when "metadata"
84
+ process_metadata(request[:table])
85
+ when "prepare"
86
+ process_prepare(request[:sql])
87
+ when "execute"
88
+ process_execute(request[:statement_id], request[:params] || [], request[:deadline_at], cancellation)
89
+ when "close"
90
+ process_close(request[:statement_id])
91
+ when "begin"
92
+ process_begin
93
+ when "commit"
94
+ process_commit
95
+ when "rollback"
96
+ process_rollback
97
+ when "ping"
98
+ process_ping
99
+ else
100
+ {success: false, error: "Unknown request type: #{request[:type]}"}
101
+ end
102
+ rescue RubyDB::ExecutionError => error
103
+ raise unless %w[deadline_exceeded cancelled].include?(error.code.to_s)
104
+
105
+ {success: false, error: error.message, code: error.code.to_s}
106
+ ensure
107
+ @operations_lock.synchronize { @operations.delete(request_id) }
108
+ end
109
+ end
110
+ end
111
+
112
+ def cancel(request_id, allow_pending: false)
113
+ token = @operations_lock.synchronize do
114
+ key = request_id.to_s
115
+ current = @operations[key]
116
+ @pending_cancellations[key] = true if !current && allow_pending
117
+ current
118
+ end
119
+
120
+ token&.cancel!
121
+ true
122
+ end
123
+
124
+ def close
125
+ @lock.synchronize do
126
+ @is_active = false
127
+
128
+ # Close all prepared statements
129
+ @prepared_statements.each do |id, stmt|
130
+ stmt[:close]&.call
131
+ end
132
+ @prepared_statements.clear
133
+
134
+ # Close all cursors
135
+ @cursors.each do |id, cursor|
136
+ cursor[:close]&.call
137
+ end
138
+ @cursors.clear
139
+
140
+ # Rollback transaction if active
141
+ if @transaction && @transaction[:active]
142
+ @transaction[:rollback]&.call
143
+ end
144
+ @transaction = nil
145
+
146
+ true
147
+ end
148
+ end
149
+
150
+ def active?
151
+ @is_active
152
+ end
153
+
154
+ def to_hash
155
+ {
156
+ id: @id,
157
+ username: @username,
158
+ database: @database,
159
+ created_at: @created_at.iso8601,
160
+ last_activity: @last_activity.iso8601,
161
+ prepared_statements: @prepared_statements.size,
162
+ cursors: @cursors.size,
163
+ variables: @variables.size,
164
+ in_transaction: @transaction && @transaction[:active],
165
+ active: @is_active
166
+ }
167
+ end
168
+
169
+ private
170
+
171
+ def generate_session_id
172
+ "sess_#{Time.now.to_i}_#{SecureRandom.hex(8)}"
173
+ end
174
+
175
+ def process_cancel(request)
176
+ target = request[:target_request_id] || request[:request_id]
177
+ cancelled = cancel(target, allow_pending: request[:allow_pending_cancellation] == true)
178
+ {
179
+ success: cancelled,
180
+ type: "cancel_response",
181
+ target_request_id: target,
182
+ cancelled: cancelled,
183
+ code: cancelled ? "cancel_requested" : "request_not_found",
184
+ timestamp: Time.now.iso8601
185
+ }
186
+ end
187
+
188
+ def process_query(sql, params, deadline_at = nil, cancellation = nil)
189
+ permission_error = authorize_sql(sql)
190
+ return permission_error if permission_error
191
+
192
+ {
193
+ success: true,
194
+ type: "query_result",
195
+ result: execute_sql(sql, params, deadline_at: deadline_at, cancellation: cancellation),
196
+ timestamp: Time.now.iso8601
197
+ }
198
+ end
199
+
200
+ def process_metadata(table_name = nil)
201
+ engine = @config[:engine]
202
+ raise RubyDB::ServerError, "Session has no database engine" unless engine
203
+
204
+ if table_name.nil? || table_name.to_s.empty?
205
+ return {
206
+ success: true,
207
+ type: "metadata_result",
208
+ metadata: {tables: engine.list_tables.map(&:to_s)},
209
+ timestamp: Time.now.iso8601
210
+ }
211
+ end
212
+
213
+ table_key = table_name.to_s
214
+ metadata = engine.table_metadata[table_key] || engine.table_metadata[table_key.to_sym]
215
+ raise RubyDB::DatabaseError, "Table '#{table_name}' does not exist" unless metadata
216
+
217
+ columns = engine.table_columns(table_key).map do |column|
218
+ {
219
+ name: column.name.to_s,
220
+ type: column.type.to_s,
221
+ nullable: column.nullable?,
222
+ primary_key: column.primary_key?,
223
+ default: column.has_default? ? column.default : nil
224
+ }
225
+ end
226
+ indexes = engine.index_manager.get_indexes_for_table(table_key).map do |index|
227
+ {name: index.name.to_s, unique: index.unique, columns: index.columns.map(&:to_s)}
228
+ end
229
+
230
+ {
231
+ success: true,
232
+ type: "metadata_result",
233
+ metadata: {
234
+ tables: [table_key],
235
+ columns: columns,
236
+ indexes: indexes,
237
+ constraints: metadata[:constraints] || []
238
+ },
239
+ timestamp: Time.now.iso8601
240
+ }
241
+ end
242
+
243
+ def process_prepare(sql)
244
+ permission_error = authorize_sql(sql)
245
+ return permission_error if permission_error
246
+
247
+ stmt_id = "stmt_#{Time.now.to_i}_#{SecureRandom.hex(4)}"
248
+ @prepared_statements[stmt_id] = {
249
+ sql: sql,
250
+ created_at: Time.now,
251
+ close: lambda { @prepared_statements.delete(stmt_id) }
252
+ }
253
+
254
+ {
255
+ success: true,
256
+ type: "prepare_result",
257
+ statement_id: stmt_id,
258
+ timestamp: Time.now.iso8601
259
+ }
260
+ end
261
+
262
+ def process_execute(stmt_id, params, deadline_at = nil, cancellation = nil)
263
+ stmt = @prepared_statements[stmt_id]
264
+ unless stmt
265
+ return {
266
+ success: false,
267
+ error: "Statement not found: #{stmt_id}"
268
+ }
269
+ end
270
+
271
+ {
272
+ success: true,
273
+ type: "execute_result",
274
+ result: execute_sql(stmt[:sql], params, deadline_at: deadline_at, cancellation: cancellation),
275
+ timestamp: Time.now.iso8601
276
+ }
277
+ end
278
+
279
+ def process_close(stmt_id)
280
+ @prepared_statements.delete(stmt_id)
281
+
282
+ {
283
+ success: true,
284
+ type: "close_result",
285
+ timestamp: Time.now.iso8601
286
+ }
287
+ end
288
+
289
+ def process_begin
290
+ if @transaction && @transaction[:active]
291
+ return {
292
+ success: false,
293
+ error: "Transaction already active"
294
+ }
295
+ end
296
+
297
+ engine = @config[:engine]
298
+ transaction_id = engine&.begin_transaction || "txn_#{Time.now.to_i}"
299
+ @transaction = {
300
+ id: transaction_id,
301
+ started_at: Time.now,
302
+ active: true,
303
+ rollback: lambda do
304
+ engine&.rollback_transaction
305
+ @transaction = nil
306
+ end
307
+ }
308
+
309
+ {
310
+ success: true,
311
+ type: "begin_result",
312
+ transaction_id: @transaction[:id],
313
+ timestamp: Time.now.iso8601
314
+ }
315
+ end
316
+
317
+ def process_commit
318
+ unless @transaction && @transaction[:active]
319
+ return {
320
+ success: false,
321
+ error: "No active transaction"
322
+ }
323
+ end
324
+
325
+ committed = @config[:engine]&.commit_transaction
326
+ unless committed
327
+ return {
328
+ success: false,
329
+ error: "Transaction commit failed",
330
+ commit_ack: @config[:engine]&.last_commit_ack
331
+ }
332
+ end
333
+ @transaction[:active] = false
334
+ @transaction = nil
335
+
336
+ {
337
+ success: true,
338
+ type: "commit_result",
339
+ committed: true,
340
+ durable: @config[:engine]&.last_commit_ack&.fetch(:status, nil) == :durable,
341
+ commit_ack: @config[:engine]&.last_commit_ack,
342
+ timestamp: Time.now.iso8601
343
+ }
344
+ end
345
+
346
+ def process_rollback
347
+ unless @transaction && @transaction[:active]
348
+ return {
349
+ success: false,
350
+ error: "No active transaction"
351
+ }
352
+ end
353
+
354
+ @transaction[:active] = false
355
+ @config[:engine]&.rollback_transaction
356
+ @transaction = nil
357
+
358
+ {
359
+ success: true,
360
+ type: "rollback_result",
361
+ timestamp: Time.now.iso8601
362
+ }
363
+ end
364
+
365
+ def process_ping
366
+ {
367
+ success: true,
368
+ type: "pong",
369
+ timestamp: Time.now.iso8601,
370
+ session_id: @id
371
+ }
372
+ end
373
+
374
+ def execute_sql(sql, params, deadline_at: nil, cancellation: nil)
375
+ engine = @config[:engine]
376
+ raise RubyDB::ServerError, "Session has no database engine" unless engine
377
+
378
+ sql = RubyDB::Protocol::ParameterBinder.bind(sql, params)
379
+ tokens = RubyDB::SQL::Lexer.new(sql).tokenize
380
+ statements = RubyDB::SQL::Parser.new(tokens).parse
381
+ results = statements.map do |statement|
382
+ plan = RubyDB::Execution::Planner.new(engine).plan(statement)
383
+ RubyDB::Execution::Executor.new(
384
+ engine,
385
+ deadline_at: deadline_at,
386
+ cancellation: cancellation
387
+ ).execute(plan)
388
+ end
389
+ (results.size == 1) ? results.first : results
390
+ end
391
+
392
+ def resolve_permissions(username)
393
+ policy = @config[:authorization] || @config["authorization"]
394
+ return %i[read write] unless policy
395
+
396
+ users = policy[:users] || policy["users"] || {}
397
+ symbol_username = username.respond_to?(:to_sym) ? username.to_sym : username
398
+ definition = users[username] || users[username.to_s] || users[symbol_username]
399
+ permissions = definition.is_a?(Hash) ? (definition[:permissions] || definition["permissions"]) : definition
400
+ Array(permissions).map(&:to_sym)
401
+ end
402
+
403
+ def authorize_sql(sql)
404
+ required = sql.to_s.strip.split(/\s+/, 2).first.to_s.downcase
405
+ permission = %w[select show explain].include?(required) ? :read : :write
406
+ return nil if @permissions&.include?(permission)
407
+
408
+ {
409
+ success: false,
410
+ error: "Permission denied: #{permission} access is required",
411
+ timestamp: Time.now.iso8601
412
+ }
413
+ end
414
+ end
415
+ end
416
+ end