rubydb 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (436) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +20 -0
  3. data/.github/ISSUE_TEMPLATE/bug.yml +0 -0
  4. data/.github/ISSUE_TEMPLATE/feature.yml +0 -0
  5. data/.github/ISSUE_TEMPLATE/performance.yml +0 -0
  6. data/.github/ISSUE_TEMPLATE/security.yml +0 -0
  7. data/.github/PULL_REQUEST_TEMPLATE.md +15 -0
  8. data/.github/dependabot.yml +0 -0
  9. data/.github/workflows/benchmark.yml +26 -0
  10. data/.github/workflows/compatibility.yml +63 -0
  11. data/.github/workflows/fuzz.yml +33 -0
  12. data/.github/workflows/lint.yml +21 -0
  13. data/.github/workflows/operations.yml +24 -0
  14. data/.github/workflows/production-validation.yml +111 -0
  15. data/.github/workflows/release.yml +77 -0
  16. data/.github/workflows/security.yml +37 -0
  17. data/.github/workflows/test.yml +26 -0
  18. data/.github/workflows/workload.yml +58 -0
  19. data/.gitignore +43 -0
  20. data/.rspec +4 -0
  21. data/.rubocop.yml +44 -0
  22. data/.ruby-version +1 -0
  23. data/.standard.yml +14 -0
  24. data/ARCHITECTURE.md +21 -0
  25. data/CHANGELOG.md +31 -0
  26. data/CODE_OF_CONDUCT.md +13 -0
  27. data/CONTRIBUTING.md +29 -0
  28. data/GOVERNANCE.md +16 -0
  29. data/Gemfile +17 -0
  30. data/Gemfile.lock +71 -0
  31. data/LICENSE +60 -0
  32. data/README.md +162 -0
  33. data/ROADMAP.md +27 -0
  34. data/Rakefile +71 -0
  35. data/SECURITY.md +54 -0
  36. data/SUPPORT.md +14 -0
  37. data/adapters/activerecord/Gemfile +11 -0
  38. data/adapters/activerecord/README.md +9 -0
  39. data/adapters/activerecord/lib/active_record/connection_adapters/rubydb_adapter.rb +887 -0
  40. data/adapters/activerecord/rubydb-activerecord.gemspec +27 -0
  41. data/adapters/activerecord/spec/rubydb_adapter_integration_spec.rb +151 -0
  42. data/adapters/ruby/README.md +18 -0
  43. data/adapters/sequel/README.md +11 -0
  44. data/bin/rubydb +19 -0
  45. data/bin/rubydb-server +126 -0
  46. data/config/development.yml +0 -0
  47. data/config/monitoring/prometheus-alerts.yml +39 -0
  48. data/config/production.yml +36 -0
  49. data/config/test.yml +0 -0
  50. data/docs/README.md +71 -0
  51. data/docs/architecture/concurrency.md +14 -0
  52. data/docs/architecture/current-state.md +125 -0
  53. data/docs/architecture/execution-engine.md +25 -0
  54. data/docs/architecture/indexes.md +19 -0
  55. data/docs/architecture/mvcc.md +19 -0
  56. data/docs/architecture/overview.md +13 -0
  57. data/docs/architecture/pages.md +11 -0
  58. data/docs/architecture/production-roadmap.md +82 -0
  59. data/docs/architecture/query-planner.md +20 -0
  60. data/docs/architecture/recovery.md +18 -0
  61. data/docs/architecture/sql-engine.md +12 -0
  62. data/docs/architecture/storage-engine.md +14 -0
  63. data/docs/architecture/transactions.md +10 -0
  64. data/docs/architecture/wal.md +28 -0
  65. data/docs/cli-cheatsheet.md +98 -0
  66. data/docs/cli.md +275 -0
  67. data/docs/contributing/architecture.md +9 -0
  68. data/docs/contributing/benchmarking.md +14 -0
  69. data/docs/contributing/development.md +16 -0
  70. data/docs/contributing/release-process.md +49 -0
  71. data/docs/contributing/testing.md +16 -0
  72. data/docs/debugging.md +229 -0
  73. data/docs/developer/branching.md +10 -0
  74. data/docs/developer/database-diff.md +10 -0
  75. data/docs/developer/local-development.md +17 -0
  76. data/docs/developer/snapshots.md +9 -0
  77. data/docs/developer/temporal-data.md +10 -0
  78. data/docs/developer-guide.md +297 -0
  79. data/docs/getting-started/first-database.md +16 -0
  80. data/docs/getting-started/first-query.md +13 -0
  81. data/docs/getting-started/installation.md +19 -0
  82. data/docs/getting-started/local-to-production.md +300 -0
  83. data/docs/getting-started/quickstart.md +17 -0
  84. data/docs/getting-started/rails.md +16 -0
  85. data/docs/hardening_backlog.md +93 -0
  86. data/docs/lessons-learned.md +112 -0
  87. data/docs/operations/backups.md +33 -0
  88. data/docs/operations/disaster-recovery.md +31 -0
  89. data/docs/operations/failover.md +30 -0
  90. data/docs/operations/monitoring.md +25 -0
  91. data/docs/operations/production-guide.md +295 -0
  92. data/docs/operations/production-runbook.md +45 -0
  93. data/docs/operations/replication.md +33 -0
  94. data/docs/operations/restore.md +6 -0
  95. data/docs/operations/runbook.md +34 -0
  96. data/docs/operations/upgrades.md +14 -0
  97. data/docs/operations/workload-testing.md +17 -0
  98. data/docs/production-readiness.md +118 -0
  99. data/docs/production_validation.md +150 -0
  100. data/docs/rails/active-record.md +11 -0
  101. data/docs/rails/compatibility-guide.md +90 -0
  102. data/docs/rails/database-yml.md +92 -0
  103. data/docs/rails/installation.md +17 -0
  104. data/docs/rails/migrations.md +17 -0
  105. data/docs/rails/production.md +82 -0
  106. data/docs/rails/troubleshooting.md +18 -0
  107. data/docs/release.md +59 -0
  108. data/docs/server/architecture.md +10 -0
  109. data/docs/server/authentication.md +10 -0
  110. data/docs/server/configuration.md +16 -0
  111. data/docs/server/connection-pooling.md +10 -0
  112. data/docs/server/deployment.md +10 -0
  113. data/docs/server/protocol.md +12 -0
  114. data/docs/sql/compatibility-guide.md +82 -0
  115. data/docs/sql/compatibility.md +39 -0
  116. data/docs/sql/data-types.md +10 -0
  117. data/docs/sql/functions.md +9 -0
  118. data/docs/sql/joins.md +9 -0
  119. data/docs/sql/operators.md +9 -0
  120. data/docs/sql/sqlite-compatibility.md +21 -0
  121. data/docs/sql/syntax.md +10 -0
  122. data/docs/sql/transactions.md +10 -0
  123. data/docs/troubleshooting.md +244 -0
  124. data/exe/rubydb +23 -0
  125. data/lib/rubydb/backup/archive.rb +334 -0
  126. data/lib/rubydb/backup/backup.rb +401 -0
  127. data/lib/rubydb/backup/incremental.rb +353 -0
  128. data/lib/rubydb/backup/restore.rb +290 -0
  129. data/lib/rubydb/backup/snapshot.rb +267 -0
  130. data/lib/rubydb/backup/verification.rb +279 -0
  131. data/lib/rubydb/branching/branch.rb +181 -0
  132. data/lib/rubydb/branching/branch_manager.rb +311 -0
  133. data/lib/rubydb/branching/branch_metadata.rb +140 -0
  134. data/lib/rubydb/branching/checkout.rb +166 -0
  135. data/lib/rubydb/branching/copy_on_write.rb +272 -0
  136. data/lib/rubydb/branching/diff.rb +138 -0
  137. data/lib/rubydb/branching/merge.rb +285 -0
  138. data/lib/rubydb/build_info.rb +15 -0
  139. data/lib/rubydb/catalog/catalog.rb +391 -0
  140. data/lib/rubydb/catalog/column.rb +112 -0
  141. data/lib/rubydb/catalog/constraint.rb +180 -0
  142. data/lib/rubydb/catalog/database.rb +184 -0
  143. data/lib/rubydb/catalog/index.rb +97 -0
  144. data/lib/rubydb/catalog/schema.rb +103 -0
  145. data/lib/rubydb/catalog/sequence.rb +90 -0
  146. data/lib/rubydb/catalog/system_catalog.rb +698 -0
  147. data/lib/rubydb/catalog/table.rb +178 -0
  148. data/lib/rubydb/catalog/trigger.rb +102 -0
  149. data/lib/rubydb/catalog/view.rb +66 -0
  150. data/lib/rubydb/cli/application.rb +163 -0
  151. data/lib/rubydb/cli/commands/backup.rb +81 -0
  152. data/lib/rubydb/cli/commands/branch.rb +72 -0
  153. data/lib/rubydb/cli/commands/checkout.rb +54 -0
  154. data/lib/rubydb/cli/commands/create.rb +58 -0
  155. data/lib/rubydb/cli/commands/diff.rb +77 -0
  156. data/lib/rubydb/cli/commands/doctor.rb +74 -0
  157. data/lib/rubydb/cli/commands/drop.rb +57 -0
  158. data/lib/rubydb/cli/commands/init.rb +102 -0
  159. data/lib/rubydb/cli/commands/inspect.rb +95 -0
  160. data/lib/rubydb/cli/commands/merge.rb +63 -0
  161. data/lib/rubydb/cli/commands/migrate.rb +62 -0
  162. data/lib/rubydb/cli/commands/restart.rb +39 -0
  163. data/lib/rubydb/cli/commands/restore.rb +121 -0
  164. data/lib/rubydb/cli/commands/shell.rb +365 -0
  165. data/lib/rubydb/cli/commands/snapshot.rb +79 -0
  166. data/lib/rubydb/cli/commands/start.rb +82 -0
  167. data/lib/rubydb/cli/commands/status.rb +92 -0
  168. data/lib/rubydb/cli/commands/stop.rb +47 -0
  169. data/lib/rubydb/cli/commands/vacuum.rb +58 -0
  170. data/lib/rubydb/cli/formatter.rb +221 -0
  171. data/lib/rubydb/cli/output.rb +168 -0
  172. data/lib/rubydb/client/client.rb +304 -0
  173. data/lib/rubydb/client/connection.rb +415 -0
  174. data/lib/rubydb/client/connection_pool.rb +168 -0
  175. data/lib/rubydb/client/connection_url.rb +96 -0
  176. data/lib/rubydb/client/prepared_statement.rb +60 -0
  177. data/lib/rubydb/client/result.rb +123 -0
  178. data/lib/rubydb/client/statement.rb +52 -0
  179. data/lib/rubydb/client/transaction.rb +130 -0
  180. data/lib/rubydb/concurrency/concurrency.rb +19 -0
  181. data/lib/rubydb/concurrency/deadlock_detector.rb +150 -0
  182. data/lib/rubydb/concurrency/latch.rb +101 -0
  183. data/lib/rubydb/concurrency/lock_graph.rb +165 -0
  184. data/lib/rubydb/concurrency/mutex.rb +183 -0
  185. data/lib/rubydb/concurrency/rw_lock.rb +180 -0
  186. data/lib/rubydb/concurrency/scheduler.rb +250 -0
  187. data/lib/rubydb/concurrency/worker_pool.rb +143 -0
  188. data/lib/rubydb/configuration/config.rb +170 -0
  189. data/lib/rubydb/configuration/defaults.rb +179 -0
  190. data/lib/rubydb/configuration/environment.rb +152 -0
  191. data/lib/rubydb/configuration/parser.rb +185 -0
  192. data/lib/rubydb/configuration/validation.rb +221 -0
  193. data/lib/rubydb/constants.rb +74 -0
  194. data/lib/rubydb/constraints/check.rb +181 -0
  195. data/lib/rubydb/constraints/constraint.rb +101 -0
  196. data/lib/rubydb/constraints/foreign_key.rb +130 -0
  197. data/lib/rubydb/constraints/not_null.rb +64 -0
  198. data/lib/rubydb/constraints/primary_key.rb +99 -0
  199. data/lib/rubydb/constraints/unique.rb +108 -0
  200. data/lib/rubydb/constraints/validator.rb +350 -0
  201. data/lib/rubydb/errors/authentication_error.rb +10 -0
  202. data/lib/rubydb/errors/authorization_error.rb +23 -0
  203. data/lib/rubydb/errors/client_error.rb +10 -0
  204. data/lib/rubydb/errors/configuration_error.rb +10 -0
  205. data/lib/rubydb/errors/connection_error.rb +10 -0
  206. data/lib/rubydb/errors/constraint_error.rb +23 -0
  207. data/lib/rubydb/errors/corruption_error.rb +10 -0
  208. data/lib/rubydb/errors/database_error.rb +10 -0
  209. data/lib/rubydb/errors/error.rb +20 -0
  210. data/lib/rubydb/errors/execution_error.rb +10 -0
  211. data/lib/rubydb/errors/parser_error.rb +10 -0
  212. data/lib/rubydb/errors/recovery_error.rb +10 -0
  213. data/lib/rubydb/errors/replication_error.rb +10 -0
  214. data/lib/rubydb/errors/server_error.rb +6 -0
  215. data/lib/rubydb/errors/storage_error.rb +10 -0
  216. data/lib/rubydb/errors/transaction_error.rb +10 -0
  217. data/lib/rubydb/execution/aggregate_executor.rb +138 -0
  218. data/lib/rubydb/execution/delete_executor.rb +112 -0
  219. data/lib/rubydb/execution/distinct_executor.rb +135 -0
  220. data/lib/rubydb/execution/executor.rb +1192 -0
  221. data/lib/rubydb/execution/expression.rb +193 -0
  222. data/lib/rubydb/execution/index_scan.rb +142 -0
  223. data/lib/rubydb/execution/insert_executor.rb +217 -0
  224. data/lib/rubydb/execution/join_executor.rb +249 -0
  225. data/lib/rubydb/execution/limit_executor.rb +85 -0
  226. data/lib/rubydb/execution/optimizer.rb +215 -0
  227. data/lib/rubydb/execution/plan.rb +361 -0
  228. data/lib/rubydb/execution/planner.rb +546 -0
  229. data/lib/rubydb/execution/predicate.rb +235 -0
  230. data/lib/rubydb/execution/scan.rb +49 -0
  231. data/lib/rubydb/execution/sequential_scan.rb +63 -0
  232. data/lib/rubydb/execution/sort_executor.rb +185 -0
  233. data/lib/rubydb/execution/update_executor.rb +162 -0
  234. data/lib/rubydb/functions/aggregate.rb +70 -0
  235. data/lib/rubydb/functions/date_functions.rb +278 -0
  236. data/lib/rubydb/functions/function.rb +85 -0
  237. data/lib/rubydb/functions/json_functions.rb +215 -0
  238. data/lib/rubydb/functions/numeric_functions.rb +346 -0
  239. data/lib/rubydb/functions/scalar.rb +52 -0
  240. data/lib/rubydb/functions/string_functions.rb +383 -0
  241. data/lib/rubydb/functions/system_functions.rb +246 -0
  242. data/lib/rubydb/history/as_of.rb +238 -0
  243. data/lib/rubydb/history/change.rb +105 -0
  244. data/lib/rubydb/history/history.rb +131 -0
  245. data/lib/rubydb/history/history_manager.rb +229 -0
  246. data/lib/rubydb/history/temporal_query.rb +202 -0
  247. data/lib/rubydb/history/timeline.rb +144 -0
  248. data/lib/rubydb/indexes/btree.rb +186 -0
  249. data/lib/rubydb/indexes/btree_cursor.rb +258 -0
  250. data/lib/rubydb/indexes/btree_node.rb +385 -0
  251. data/lib/rubydb/indexes/hash_index.rb +150 -0
  252. data/lib/rubydb/indexes/index.rb +71 -0
  253. data/lib/rubydb/indexes/index_manager.rb +406 -0
  254. data/lib/rubydb/indexes/index_scan.rb +470 -0
  255. data/lib/rubydb/migrations/migration.rb +254 -0
  256. data/lib/rubydb/migrations/migration_lock.rb +146 -0
  257. data/lib/rubydb/migrations/migration_manager.rb +176 -0
  258. data/lib/rubydb/migrations/migration_version.rb +71 -0
  259. data/lib/rubydb/migrations/schema_diff.rb +211 -0
  260. data/lib/rubydb/migrations/schema_version.rb +64 -0
  261. data/lib/rubydb/monitoring/events.rb +160 -0
  262. data/lib/rubydb/monitoring/health.rb +222 -0
  263. data/lib/rubydb/monitoring/logger.rb +193 -0
  264. data/lib/rubydb/monitoring/metrics.rb +359 -0
  265. data/lib/rubydb/monitoring/performance.rb +176 -0
  266. data/lib/rubydb/monitoring/statistics.rb +170 -0
  267. data/lib/rubydb/mvcc/garbage_collector.rb +199 -0
  268. data/lib/rubydb/mvcc/mvcc.rb +16 -0
  269. data/lib/rubydb/mvcc/snapshot.rb +147 -0
  270. data/lib/rubydb/mvcc/vacuum.rb +180 -0
  271. data/lib/rubydb/mvcc/version.rb +106 -0
  272. data/lib/rubydb/mvcc/version_store.rb +398 -0
  273. data/lib/rubydb/mvcc/visibility.rb +109 -0
  274. data/lib/rubydb/protocol/capabilities.rb +125 -0
  275. data/lib/rubydb/protocol/decoder.rb +145 -0
  276. data/lib/rubydb/protocol/encoder.rb +136 -0
  277. data/lib/rubydb/protocol/handshake.rb +305 -0
  278. data/lib/rubydb/protocol/message.rb +121 -0
  279. data/lib/rubydb/protocol/protocol.rb +277 -0
  280. data/lib/rubydb/protocol/version.rb +54 -0
  281. data/lib/rubydb/rails/adapter.rb +239 -0
  282. data/lib/rubydb/rails/connection.rb +314 -0
  283. data/lib/rubydb/rails/database_statements.rb +122 -0
  284. data/lib/rubydb/rails/migration.rb +131 -0
  285. data/lib/rubydb/rails/quoting.rb +109 -0
  286. data/lib/rubydb/rails/result.rb +120 -0
  287. data/lib/rubydb/rails/schema_statements.rb +339 -0
  288. data/lib/rubydb/rails/transaction.rb +105 -0
  289. data/lib/rubydb/rails/type.rb +126 -0
  290. data/lib/rubydb/recovery/checkpoint.rb +257 -0
  291. data/lib/rubydb/recovery/consistency.rb +467 -0
  292. data/lib/rubydb/recovery/corruption_detector.rb +5 -0
  293. data/lib/rubydb/recovery/crash_recovery.rb +387 -0
  294. data/lib/rubydb/recovery/recovery_manager.rb +206 -0
  295. data/lib/rubydb/recovery/redo.rb +237 -0
  296. data/lib/rubydb/recovery/undo.rb +206 -0
  297. data/lib/rubydb/replication/failover.rb +5 -0
  298. data/lib/rubydb/replication/fencing.rb +63 -0
  299. data/lib/rubydb/replication/primary.rb +450 -0
  300. data/lib/rubydb/replication/replica.rb +384 -0
  301. data/lib/rubydb/replication/replication_log.rb +200 -0
  302. data/lib/rubydb/replication/replication_manager.rb +308 -0
  303. data/lib/rubydb/replication/replication_slot.rb +295 -0
  304. data/lib/rubydb/replication/replication_stream.rb +201 -0
  305. data/lib/rubydb/rubydb.rb +560 -0
  306. data/lib/rubydb/security/access_control.rb +254 -0
  307. data/lib/rubydb/security/audit_log.rb +213 -0
  308. data/lib/rubydb/security/authentication.rb +302 -0
  309. data/lib/rubydb/security/authorization.rb +282 -0
  310. data/lib/rubydb/security/credentials.rb +196 -0
  311. data/lib/rubydb/security/password.rb +215 -0
  312. data/lib/rubydb/security/permissions.rb +74 -0
  313. data/lib/rubydb/security/role.rb +101 -0
  314. data/lib/rubydb/security/user.rb +86 -0
  315. data/lib/rubydb/server/connection.rb +366 -0
  316. data/lib/rubydb/server/connection_pool.rb +193 -0
  317. data/lib/rubydb/server/lifecycle.rb +228 -0
  318. data/lib/rubydb/server/listener.rb +136 -0
  319. data/lib/rubydb/server/request_handler.rb +276 -0
  320. data/lib/rubydb/server/server.rb +364 -0
  321. data/lib/rubydb/server/session.rb +369 -0
  322. data/lib/rubydb/server/worker.rb +210 -0
  323. data/lib/rubydb/server/worker_pool.rb +168 -0
  324. data/lib/rubydb/sql/ast/alter_table.rb +169 -0
  325. data/lib/rubydb/sql/ast/begin_transaction.rb +47 -0
  326. data/lib/rubydb/sql/ast/commit.rb +37 -0
  327. data/lib/rubydb/sql/ast/constraint.rb +83 -0
  328. data/lib/rubydb/sql/ast/create_database.rb +41 -0
  329. data/lib/rubydb/sql/ast/create_index.rb +61 -0
  330. data/lib/rubydb/sql/ast/create_schema.rb +52 -0
  331. data/lib/rubydb/sql/ast/create_table.rb +187 -0
  332. data/lib/rubydb/sql/ast/delete.rb +54 -0
  333. data/lib/rubydb/sql/ast/drop_database.rb +41 -0
  334. data/lib/rubydb/sql/ast/drop_index.rb +41 -0
  335. data/lib/rubydb/sql/ast/drop_schema.rb +49 -0
  336. data/lib/rubydb/sql/ast/drop_table.rb +49 -0
  337. data/lib/rubydb/sql/ast/explain.rb +64 -0
  338. data/lib/rubydb/sql/ast/expression.rb +604 -0
  339. data/lib/rubydb/sql/ast/insert.rb +82 -0
  340. data/lib/rubydb/sql/ast/node.rb +42 -0
  341. data/lib/rubydb/sql/ast/rollback.rb +63 -0
  342. data/lib/rubydb/sql/ast/savepoint.rb +59 -0
  343. data/lib/rubydb/sql/ast/select.rb +88 -0
  344. data/lib/rubydb/sql/ast/set_operation.rb +20 -0
  345. data/lib/rubydb/sql/ast/trigger.rb +29 -0
  346. data/lib/rubydb/sql/ast/update.rb +88 -0
  347. data/lib/rubydb/sql/ast/vacuum.rb +19 -0
  348. data/lib/rubydb/sql/ast/view.rb +32 -0
  349. data/lib/rubydb/sql/ast/with.rb +32 -0
  350. data/lib/rubydb/sql/grammar.rb +86 -0
  351. data/lib/rubydb/sql/keywords.rb +156 -0
  352. data/lib/rubydb/sql/lexer.rb +214 -0
  353. data/lib/rubydb/sql/operators.rb +100 -0
  354. data/lib/rubydb/sql/parser.rb +1185 -0
  355. data/lib/rubydb/sql/planner/analyzer.rb +302 -0
  356. data/lib/rubydb/sql/planner/binder.rb +550 -0
  357. data/lib/rubydb/sql/planner/type_checker.rb +431 -0
  358. data/lib/rubydb/sql/token.rb +210 -0
  359. data/lib/rubydb/storage/buffer_frame.rb +44 -0
  360. data/lib/rubydb/storage/buffer_pool.rb +155 -0
  361. data/lib/rubydb/storage/database_lock.rb +74 -0
  362. data/lib/rubydb/storage/deserializer.rb +342 -0
  363. data/lib/rubydb/storage/engine.rb +2338 -0
  364. data/lib/rubydb/storage/file_manager.rb +187 -0
  365. data/lib/rubydb/storage/free_space_map.rb +81 -0
  366. data/lib/rubydb/storage/page.rb +94 -0
  367. data/lib/rubydb/storage/page_allocator.rb +855 -0
  368. data/lib/rubydb/storage/page_header.rb +67 -0
  369. data/lib/rubydb/storage/page_manager.rb +131 -0
  370. data/lib/rubydb/storage/record.rb +58 -0
  371. data/lib/rubydb/storage/row.rb +78 -0
  372. data/lib/rubydb/storage/serializer.rb +51 -0
  373. data/lib/rubydb/storage/storage_layout.rb +151 -0
  374. data/lib/rubydb/storage/storage_manager.rb +114 -0
  375. data/lib/rubydb/storage/tuple.rb +461 -0
  376. data/lib/rubydb/storage/visibility_map.rb +984 -0
  377. data/lib/rubydb/transactions/commit_manager.rb +220 -0
  378. data/lib/rubydb/transactions/isolation.rb +98 -0
  379. data/lib/rubydb/transactions/lock.rb +76 -0
  380. data/lib/rubydb/transactions/lock_manager.rb +362 -0
  381. data/lib/rubydb/transactions/savepoint.rb +143 -0
  382. data/lib/rubydb/transactions/transaction.rb +215 -0
  383. data/lib/rubydb/transactions/transaction_id.rb +84 -0
  384. data/lib/rubydb/transactions/transaction_log.rb +257 -0
  385. data/lib/rubydb/transactions/transaction_manager.rb +435 -0
  386. data/lib/rubydb/types/bigint.rb +36 -0
  387. data/lib/rubydb/types/blob.rb +37 -0
  388. data/lib/rubydb/types/boolean.rb +34 -0
  389. data/lib/rubydb/types/date.rb +39 -0
  390. data/lib/rubydb/types/decimal.rb +48 -0
  391. data/lib/rubydb/types/float.rb +34 -0
  392. data/lib/rubydb/types/integer.rb +36 -0
  393. data/lib/rubydb/types/json.rb +41 -0
  394. data/lib/rubydb/types/null.rb +34 -0
  395. data/lib/rubydb/types/smallint.rb +36 -0
  396. data/lib/rubydb/types/text.rb +37 -0
  397. data/lib/rubydb/types/time.rb +46 -0
  398. data/lib/rubydb/types/timestamp.rb +39 -0
  399. data/lib/rubydb/types/type.rb +119 -0
  400. data/lib/rubydb/types/uuid.rb +47 -0
  401. data/lib/rubydb/types/varchar.rb +37 -0
  402. data/lib/rubydb/version.rb +33 -0
  403. data/lib/rubydb/wal/archive.rb +193 -0
  404. data/lib/rubydb/wal/checkpoint.rb +183 -0
  405. data/lib/rubydb/wal/lsn.rb +94 -0
  406. data/lib/rubydb/wal/reader.rb +260 -0
  407. data/lib/rubydb/wal/record.rb +105 -0
  408. data/lib/rubydb/wal/segment.rb +193 -0
  409. data/lib/rubydb/wal/wal.rb +452 -0
  410. data/lib/rubydb/wal/writer.rb +236 -0
  411. data/lib/rubydb.rb +7 -0
  412. data/packaging/docker/Dockerfile +36 -0
  413. data/packaging/docker/docker-compose.failover.yml +43 -0
  414. data/packaging/docker/entrypoint.sh +15 -0
  415. data/packaging/homebrew/rubydb.rb +19 -0
  416. data/packaging/systemd/rubydb.service +22 -0
  417. data/rubydb.gemspec +57 -0
  418. data/scripts/benchmark +7 -0
  419. data/scripts/durability_drill +37 -0
  420. data/scripts/format +0 -0
  421. data/scripts/fuzz +63 -0
  422. data/scripts/lint +0 -0
  423. data/scripts/release +50 -0
  424. data/scripts/release_check +43 -0
  425. data/scripts/replication_failover_drill +250 -0
  426. data/scripts/replication_network_failover_drill +255 -0
  427. data/scripts/restore_drill +45 -0
  428. data/scripts/security +0 -0
  429. data/scripts/setup +0 -0
  430. data/scripts/test +0 -0
  431. data/scripts/test-chaos +0 -0
  432. data/scripts/test-crash +0 -0
  433. data/scripts/test-integration +0 -0
  434. data/scripts/test-unit +0 -0
  435. data/visibility_map.json +1 -0
  436. metadata +538 -0
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module Catalog
5
+ # Table - contains columns, indexes, constraints
6
+ class Table
7
+ attr_reader :name, :columns, :indexes, :constraints, :database
8
+ attr_accessor :row_count, :storage_size
9
+
10
+ def initialize(name, database = nil)
11
+ @name = name
12
+ @database = database
13
+ @columns = []
14
+ @columns_by_name = {}
15
+ @indexes = {}
16
+ @constraints = []
17
+ @row_count = 0
18
+ @storage_size = 0
19
+ @created_at = Time.now
20
+ @modified_at = Time.now
21
+ @options = {}
22
+ end
23
+
24
+ # --- Column Methods ---
25
+
26
+ def add_column(column)
27
+ if @columns_by_name.key?(column.name)
28
+ raise DatabaseError, "Column '#{column.name}' already exists in table '#{@name}'"
29
+ end
30
+
31
+ @columns << column
32
+ @columns_by_name[column.name] = column
33
+ @modified_at = Time.now
34
+ column
35
+ end
36
+
37
+ def drop_column(name)
38
+ column = @columns_by_name.delete(name)
39
+ @columns.delete(column) if column
40
+ @modified_at = Time.now
41
+ column
42
+ end
43
+
44
+ def find_column(name)
45
+ @columns_by_name[name]
46
+ end
47
+
48
+ def column_exists?(name)
49
+ @columns_by_name.key?(name)
50
+ end
51
+
52
+ def columns
53
+ @columns.dup
54
+ end
55
+
56
+ # --- Index Methods ---
57
+
58
+ def add_index(index)
59
+ if @indexes.key?(index.name)
60
+ raise DatabaseError, "Index '#{index.name}' already exists in table '#{@name}'"
61
+ end
62
+
63
+ @indexes[index.name] = index
64
+ @modified_at = Time.now
65
+ index
66
+ end
67
+
68
+ def drop_index(name)
69
+ index = @indexes.delete(name)
70
+ @modified_at = Time.now
71
+ index
72
+ end
73
+
74
+ def find_index(name)
75
+ @indexes[name]
76
+ end
77
+
78
+ def indexes
79
+ @indexes.values
80
+ end
81
+
82
+ # --- Constraint Methods ---
83
+
84
+ def add_constraint(constraint)
85
+ @constraints << constraint
86
+ @modified_at = Time.now
87
+ constraint
88
+ end
89
+
90
+ def drop_constraint(name)
91
+ constraint = @constraints.find { |c| c.name == name }
92
+ @constraints.delete(constraint) if constraint
93
+ @modified_at = Time.now
94
+ constraint
95
+ end
96
+
97
+ def constraints
98
+ @constraints.dup
99
+ end
100
+
101
+ def primary_key
102
+ @constraints.find { |c| c.is_a?(PrimaryKeyConstraint) }
103
+ end
104
+
105
+ def foreign_keys
106
+ @constraints.select { |c| c.is_a?(ForeignKeyConstraint) }
107
+ end
108
+
109
+ def unique_constraints
110
+ @constraints.select { |c| c.is_a?(UniqueConstraint) }
111
+ end
112
+
113
+ # --- Helpers ---
114
+
115
+ def primary_key_column
116
+ pk = primary_key
117
+ return nil unless pk
118
+ find_column(pk.columns.first)
119
+ end
120
+
121
+ def column_names
122
+ @columns.map(&:name)
123
+ end
124
+
125
+ def column_types
126
+ @columns.map { |c| [c.name, c.type] }.to_h
127
+ end
128
+
129
+ def to_s
130
+ "#{@name}(#{@columns.map(&:name).join(", ")})"
131
+ end
132
+
133
+ # --- Serialization ---
134
+
135
+ def serialize
136
+ {
137
+ name: @name,
138
+ created_at: @created_at.iso8601,
139
+ modified_at: @modified_at.iso8601,
140
+ columns: @columns.map(&:serialize),
141
+ indexes: @indexes.transform_values(&:serialize),
142
+ constraints: @constraints.map(&:serialize),
143
+ row_count: @row_count,
144
+ storage_size: @storage_size,
145
+ options: @options
146
+ }
147
+ end
148
+
149
+ def self.deserialize(data)
150
+ table = new(data[:name])
151
+
152
+ data[:columns]&.each do |col_data|
153
+ table.add_column(Column.deserialize(col_data))
154
+ end
155
+
156
+ data[:indexes]&.each do |name, idx_data|
157
+ table.indexes[name] = Index.deserialize(idx_data)
158
+ end
159
+
160
+ data[:constraints]&.each do |con_data|
161
+ table.constraints << Constraint.deserialize(con_data)
162
+ end
163
+
164
+ table.row_count = data[:row_count] if data[:row_count]
165
+ table.storage_size = data[:storage_size] if data[:storage_size]
166
+ table.send(:options=, data[:options]) if data[:options]
167
+ table.send(:created_at=, Time.parse(data[:created_at])) if data[:created_at]
168
+ table.send(:modified_at=, Time.parse(data[:modified_at])) if data[:modified_at]
169
+
170
+ table
171
+ end
172
+
173
+ protected
174
+
175
+ attr_writer :created_at, :modified_at, :options
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module Catalog
5
+ # Trigger - executes a function on table events
6
+ class Trigger
7
+ attr_reader :name, :event, :table_name, :definition
8
+ attr_reader :timing, :condition, :function_name
9
+ attr_accessor :enabled
10
+
11
+ def initialize(name, event, table_name, definition, options = {})
12
+ @name = name
13
+ @event = event # :insert, :update, :delete, or array of these
14
+ @table_name = table_name
15
+ @definition = definition
16
+ @timing = options[:timing] || :after # :before, :after, :instead_of
17
+ @condition = options[:condition]
18
+ @function_name = options[:function_name]
19
+ @enabled = options.fetch(:enabled, true)
20
+ @created_at = Time.now
21
+ @modified_at = Time.now
22
+ end
23
+
24
+ def event_types
25
+ @event.is_a?(Array) ? @event : [@event]
26
+ end
27
+
28
+ def on_insert?
29
+ event_types.include?(:insert)
30
+ end
31
+
32
+ def on_update?
33
+ event_types.include?(:update)
34
+ end
35
+
36
+ def on_delete?
37
+ event_types.include?(:delete)
38
+ end
39
+
40
+ def before?
41
+ @timing == :before
42
+ end
43
+
44
+ def after?
45
+ @timing == :after
46
+ end
47
+
48
+ def instead_of?
49
+ @timing == :instead_of
50
+ end
51
+
52
+ def enabled?
53
+ @enabled
54
+ end
55
+
56
+ def to_s
57
+ "TRIGGER #{@name} #{@timing} #{@event} ON #{@table_name}"
58
+ end
59
+
60
+ def inspect
61
+ "#<Trigger name=#{@name} event=#{@event} table=#{@table_name} timing=#{@timing} enabled=#{@enabled}>"
62
+ end
63
+
64
+ # --- Serialization ---
65
+
66
+ def serialize
67
+ {
68
+ name: @name,
69
+ event: @event,
70
+ table_name: @table_name,
71
+ definition: @definition,
72
+ timing: @timing,
73
+ condition: @condition,
74
+ function_name: @function_name,
75
+ enabled: @enabled,
76
+ created_at: @created_at.iso8601,
77
+ modified_at: @modified_at.iso8601
78
+ }
79
+ end
80
+
81
+ def self.deserialize(data)
82
+ trigger = new(
83
+ data[:name],
84
+ data[:event],
85
+ data[:table_name],
86
+ data[:definition],
87
+ timing: data[:timing] || :after,
88
+ condition: data[:condition],
89
+ function_name: data[:function_name],
90
+ enabled: data[:enabled] != false
91
+ )
92
+ trigger.send(:created_at=, Time.parse(data[:created_at])) if data[:created_at]
93
+ trigger.send(:modified_at=, Time.parse(data[:modified_at])) if data[:modified_at]
94
+ trigger
95
+ end
96
+
97
+ protected
98
+
99
+ attr_writer :created_at, :modified_at
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module Catalog
5
+ # View - represents a database view (virtual table)
6
+ class View
7
+ attr_reader :name, :query, :columns
8
+ attr_accessor :materialized
9
+
10
+ def initialize(name, query, materialized: false)
11
+ @name = name
12
+ @query = query
13
+ @columns = []
14
+ @materialized = materialized
15
+ @created_at = Time.now
16
+ @modified_at = Time.now
17
+ end
18
+
19
+ def add_column(name, type)
20
+ @columns << { name: name, type: type }
21
+ @modified_at = Time.now
22
+ end
23
+
24
+ def column_names
25
+ @columns.map { |c| c[:name] }
26
+ end
27
+
28
+ def to_s
29
+ if @materialized
30
+ "MATERIALIZED VIEW #{@name}"
31
+ else
32
+ "VIEW #{@name}"
33
+ end
34
+ end
35
+
36
+ def inspect
37
+ "#<View name=#{@name} materialized=#{@materialized} columns=#{@columns.size}>"
38
+ end
39
+
40
+ # --- Serialization ---
41
+
42
+ def serialize
43
+ {
44
+ name: @name,
45
+ query: @query,
46
+ columns: @columns,
47
+ materialized: @materialized,
48
+ created_at: @created_at.iso8601,
49
+ modified_at: @modified_at.iso8601
50
+ }
51
+ end
52
+
53
+ def self.deserialize(data)
54
+ view = new(data[:name], data[:query], materialized: data[:materialized] || false)
55
+ view.send(:columns=, data[:columns]) if data[:columns]
56
+ view.send(:created_at=, Time.parse(data[:created_at])) if data[:created_at]
57
+ view.send(:modified_at=, Time.parse(data[:modified_at])) if data[:modified_at]
58
+ view
59
+ end
60
+
61
+ protected
62
+
63
+ attr_writer :created_at, :modified_at, :columns
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+
5
+ module RubyDB
6
+ module CLI
7
+ # Application - Main CLI application
8
+ class Application
9
+ def self.start(argv = ARGV)
10
+ new.run(argv)
11
+ end
12
+
13
+ def initialize
14
+ @output = Output.new
15
+ @formatter = Formatter.new(@output)
16
+ @commands = {}
17
+ @global_options = {
18
+ config: nil,
19
+ env: "development",
20
+ verbose: false,
21
+ quiet: false,
22
+ no_color: false
23
+ }
24
+
25
+ register_commands
26
+ end
27
+
28
+ def run(argv = ARGV)
29
+ parse_global_options(argv)
30
+
31
+ command_name = argv.shift
32
+
33
+ if command_name.nil? || command_name == "help"
34
+ print_help
35
+ return 0
36
+ end
37
+
38
+ command = @commands[command_name.to_sym]
39
+ if command.nil?
40
+ @output.error("Unknown command: #{command_name}")
41
+ print_help
42
+ return 1
43
+ end
44
+
45
+ begin
46
+ command.call(argv, @global_options)
47
+ 0
48
+ rescue => e
49
+ @output.error(e.message)
50
+ @output.debug(e.backtrace.join("\n"))
51
+ 1
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def register_commands
58
+ @commands[:init] = Commands::Init.new(@output, @formatter).method(:execute)
59
+ @commands[:create] = Commands::Create.new(@output, @formatter).method(:execute)
60
+ @commands[:drop] = Commands::Drop.new(@output, @formatter).method(:execute)
61
+ @commands[:start] = Commands::Start.new(@output, @formatter).method(:execute)
62
+ @commands[:stop] = Commands::Stop.new(@output, @formatter).method(:execute)
63
+ @commands[:restart] = Commands::Restart.new(@output, @formatter).method(:execute)
64
+ @commands[:status] = Commands::Status.new(@output, @formatter).method(:execute)
65
+ @commands[:shell] = Commands::Shell.new(@output, @formatter).method(:execute)
66
+ @commands[:migrate] = Commands::Migrate.new(@output, @formatter).method(:execute)
67
+ @commands[:backup] = Commands::Backup.new(@output, @formatter).method(:execute)
68
+ @commands[:restore] = Commands::Restore.new(@output, @formatter).method(:execute)
69
+ @commands[:snapshot] = Commands::Snapshot.new(@output, @formatter).method(:execute)
70
+ @commands[:branch] = Commands::Branch.new(@output, @formatter).method(:execute)
71
+ @commands[:checkout] = Commands::Checkout.new(@output, @formatter).method(:execute)
72
+ @commands[:merge] = Commands::Merge.new(@output, @formatter).method(:execute)
73
+ @commands[:diff] = Commands::Diff.new(@output, @formatter).method(:execute)
74
+ @commands[:inspect] = Commands::Inspect.new(@output, @formatter).method(:execute)
75
+ @commands[:vacuum] = Commands::Vacuum.new(@output, @formatter).method(:execute)
76
+ @commands[:doctor] = Commands::Doctor.new(@output, @formatter).method(:execute)
77
+ end
78
+
79
+ def parse_global_options(argv)
80
+ parser = OptionParser.new do |opts|
81
+ opts.banner = "Usage: rubydb [global_options] command [args]"
82
+
83
+ opts.on("-c", "--config FILE", "Configuration file") do |file|
84
+ @global_options[:config] = file
85
+ end
86
+
87
+ opts.on("-e", "--env ENV", "Environment (development, test, production)") do |env|
88
+ @global_options[:env] = env
89
+ end
90
+
91
+ opts.on("-v", "--verbose", "Verbose output") do
92
+ @global_options[:verbose] = true
93
+ end
94
+
95
+ opts.on("-q", "--quiet", "Quiet output") do
96
+ @global_options[:quiet] = true
97
+ end
98
+
99
+ opts.on("--no-color", "Disable colored output") do
100
+ @global_options[:no_color] = true
101
+ end
102
+
103
+ opts.on("-h", "--help", "Show help") do
104
+ print_help
105
+ exit(0)
106
+ end
107
+
108
+ opts.on("--version", "Show version") do
109
+ puts "RubyDB v#{RubyDB::VERSION}"
110
+ exit(0)
111
+ end
112
+ end
113
+
114
+ begin
115
+ parser.parse!(argv)
116
+ rescue OptionParser::ParseError => e
117
+ @output.error(e.message)
118
+ print_help
119
+ exit(1)
120
+ end
121
+ end
122
+
123
+ def print_help
124
+ @output.puts "RubyDB - Developer-first relational database", :bold
125
+ @output.puts "Version: #{RubyDB::VERSION}"
126
+ @output.puts
127
+ @output.puts "Usage: rubydb [global_options] <command> [args]"
128
+ @output.puts
129
+ @output.puts "Global Options:"
130
+ @output.puts " -c, --config FILE Configuration file"
131
+ @output.puts " -e, --env ENV Environment (development, test, production)"
132
+ @output.puts " -v, --verbose Verbose output"
133
+ @output.puts " -q, --quiet Quiet output"
134
+ @output.puts " --no-color Disable colored output"
135
+ @output.puts " -h, --help Show this help"
136
+ @output.puts " --version Show version"
137
+ @output.puts
138
+ @output.puts "Commands:"
139
+ @output.puts " init Initialize a new database"
140
+ @output.puts " create Create a database"
141
+ @output.puts " drop Drop a database"
142
+ @output.puts " start Start the database server"
143
+ @output.puts " stop Stop the database server"
144
+ @output.puts " restart Restart the database server"
145
+ @output.puts " status Show database status"
146
+ @output.puts " shell Open an interactive SQL shell"
147
+ @output.puts " migrate Run database migrations"
148
+ @output.puts " backup Create a database backup"
149
+ @output.puts " restore Restore a database backup"
150
+ @output.puts " snapshot Create a database snapshot"
151
+ @output.puts " branch Manage database branches"
152
+ @output.puts " checkout Switch to a branch"
153
+ @output.puts " merge Merge a branch"
154
+ @output.puts " diff Show differences between branches"
155
+ @output.puts " inspect Inspect database internals"
156
+ @output.puts " vacuum Vacuum the database"
157
+ @output.puts " doctor Run health checks"
158
+ @output.puts
159
+ @output.puts "Run 'rubydb <command> --help' for more information on a command."
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module CLI
5
+ module Commands
6
+ class Backup
7
+ def initialize(output, formatter)
8
+ @output = output
9
+ @formatter = formatter
10
+ end
11
+
12
+ def execute(args, options)
13
+ parser = OptionParser.new do |opts|
14
+ opts.banner = "Usage: rubydb backup [options]"
15
+ opts.on("-t", "--type TYPE", "Backup type (full, incremental, differential)") do |type|
16
+ options[:type] = type.to_sym
17
+ end
18
+ opts.on("-d", "--dir DIR", "Backup directory") do |dir|
19
+ options[:dir] = dir
20
+ end
21
+ opts.on("--compress", "Compress backup") do
22
+ options[:compress] = true
23
+ end
24
+ opts.on("--no-verify", "Skip verification") do
25
+ options[:no_verify] = true
26
+ end
27
+ opts.on("--database PATH", "Database path") { |path| options[:database] = path }
28
+ opts.on("-h", "--help", "Show help") do
29
+ @output.puts opts
30
+ exit(0)
31
+ end
32
+ end
33
+
34
+ parser.parse!(args)
35
+
36
+ # Get real config
37
+ config = RubyDB::Configuration::Config.instance
38
+ db_path = options[:database] || config.get("storage.data_dir") || "data/rubydb.rdb"
39
+ backup_dir = options[:dir] || config.get("backup.backup_dir") || "backups"
40
+
41
+ # Create backup instance
42
+ engine = RubyDB::Storage::Engine.new(db_path, {})
43
+ backup = RubyDB::Backup::Backup.new(engine,
44
+ backup_dir: backup_dir,
45
+ compress: options[:compress] != false,
46
+ verify_after_backup: !options[:no_verify]
47
+ )
48
+
49
+ @output.spinner("Creating #{options[:type] || 'full'} backup...") do
50
+ result = backup.create_backup(type: options[:type] || :full)
51
+
52
+ if result[:success]
53
+ @output.success("Backup created successfully")
54
+ @output.puts(" Name: #{result[:backup_name]}")
55
+ @output.puts(" Size: #{format_size(result[:size])}")
56
+ @output.puts(" Path: #{result[:backup_path]}")
57
+ @output.puts(" Time: #{result[:elapsed_ms].round(2)}ms")
58
+ else
59
+ @output.error("Backup failed: #{result[:error]}")
60
+ return 1
61
+ end
62
+ end
63
+
64
+ 0
65
+ ensure
66
+ engine&.close if engine&.open?
67
+ end
68
+
69
+ private
70
+
71
+ def format_size(bytes)
72
+ return "0 B" if bytes.to_i == 0
73
+ units = ["B", "KB", "MB", "GB", "TB"]
74
+ exp = (Math.log(bytes) / Math.log(1024)).floor
75
+ size = bytes / (1024.0 ** exp)
76
+ "#{size.round(2)} #{units[exp]}"
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+
5
+ module RubyDB
6
+ module CLI
7
+ module Commands
8
+ # Branch - Manage database branches
9
+ class Branch
10
+ def initialize(output, formatter)
11
+ @output = output
12
+ @formatter = formatter
13
+ end
14
+
15
+ def execute(args, options)
16
+ parser = OptionParser.new do |opts|
17
+ opts.banner = "Usage: rubydb branch [options]"
18
+ opts.on("-l", "--list", "List branches") do
19
+ options[:list] = true
20
+ end
21
+ opts.on("-c", "--create NAME", "Create branch") do |name|
22
+ options[:create] = name
23
+ end
24
+ opts.on("-d", "--delete NAME", "Delete branch") do |name|
25
+ options[:delete] = name
26
+ end
27
+ opts.on("--from BRANCH", "Create from branch") do |branch|
28
+ options[:from] = branch
29
+ end
30
+ opts.on("--database PATH", "Database path") { |path| options[:database] = path }
31
+ opts.on("--branch-dir DIR", "Branch metadata directory") { |path| options[:branch_dir] = path }
32
+ opts.on("-h", "--help", "Show help") do
33
+ @output.puts opts
34
+ exit(0)
35
+ end
36
+ end
37
+
38
+ parser.parse!(args)
39
+
40
+ database = RubyDB::Database.new(options[:database] || "data/rubydb.rdb", auto_connect: false).connect
41
+ manager = RubyDB::Branching::BranchManager.new(
42
+ database.engine,
43
+ branch_dir: options[:branch_dir] || "branches"
44
+ )
45
+
46
+ if options[:list] || (args.empty? && !options[:create] && !options[:delete])
47
+ @formatter.format_branches(manager.list_branches)
48
+ return 0
49
+ end
50
+
51
+ if options[:create]
52
+ from = options[:from] || "main"
53
+ result = manager.create_branch(options[:create], from: from)
54
+ raise result[:error] unless result[:success]
55
+ @output.success("Branch #{options[:create]} created")
56
+ return 0
57
+ end
58
+
59
+ if options[:delete]
60
+ result = manager.delete_branch(options[:delete])
61
+ raise result[:error] unless result[:success]
62
+ @output.success("Branch #{options[:delete]} deleted")
63
+ return 0
64
+ end
65
+ raise "Specify --list, --create, or --delete"
66
+ ensure
67
+ database&.close
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end