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,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module CLI
5
+ module Commands
6
+ # Checkout - Switch to a branch
7
+ class Checkout
8
+ def initialize(output, formatter)
9
+ @output = output
10
+ @formatter = formatter
11
+ end
12
+
13
+ def execute(args, options)
14
+ parser = OptionParser.new do |opts|
15
+ opts.banner = "Usage: rubydb checkout [options]"
16
+ opts.on("-b", "--create NAME", "Create and checkout branch") do |name|
17
+ options[:create] = name
18
+ end
19
+ opts.on("--database PATH", "Database path") { |path| options[:database] = path }
20
+ opts.on("--branch-dir DIR", "Branch metadata directory") { |path| options[:branch_dir] = path }
21
+ opts.on("-h", "--help", "Show help") do
22
+ @output.puts opts
23
+ exit(0)
24
+ end
25
+ end
26
+
27
+ parser.parse!(args)
28
+
29
+ branch = args.first
30
+ if branch.nil? && options[:create].nil?
31
+ @output.error("Branch name required")
32
+ @output.puts parser
33
+ return
34
+ end
35
+
36
+ database = RubyDB::Database.new(options[:database] || "data/rubydb.rdb", auto_connect: false).connect
37
+ manager = RubyDB::Branching::BranchManager.new(
38
+ database.engine,
39
+ branch_dir: options[:branch_dir] || "branches"
40
+ )
41
+ selected = options[:create] || branch
42
+ result = manager.create_branch(selected, from: manager.current_branch_name) if options[:create]
43
+ raise result[:error] if result && !result[:success]
44
+ result = manager.checkout(selected)
45
+ raise result[:error] unless result[:success]
46
+ @output.success("Switched to branch #{selected}")
47
+ 0
48
+ ensure
49
+ database&.close
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module CLI
5
+ module Commands
6
+ # Create - Create a database
7
+ class Create
8
+ def initialize(output, formatter)
9
+ @output = output
10
+ @formatter = formatter
11
+ end
12
+
13
+ def execute(args, options)
14
+ parser = OptionParser.new do |opts|
15
+ opts.banner = "Usage: rubydb create [options]"
16
+ opts.on("-n", "--name NAME", "Database name") do |name|
17
+ options[:name] = name
18
+ end
19
+ opts.on("-d", "--dir DIR", "Data directory") do |dir|
20
+ options[:dir] = dir
21
+ end
22
+ opts.on("-f", "--force", "Force create") do
23
+ options[:force] = true
24
+ end
25
+ opts.on("--database PATH", "Database path") { |path| options[:database] = path }
26
+ opts.on("-h", "--help", "Show help") do
27
+ @output.puts opts
28
+ exit(0)
29
+ end
30
+ end
31
+
32
+ parser.parse!(args)
33
+
34
+ name = options[:name] || "rubydb"
35
+ dir = options[:dir] || "data"
36
+ db_path = options[:database] || File.join(dir, "#{name}.rdb")
37
+ if File.exist?(db_path) && !options[:force]
38
+ @output.error("Database already exists at #{db_path}. Use --force to recreate it.")
39
+ return 1
40
+ end
41
+
42
+ @output.spinner("Creating database #{name}...") do
43
+ FileUtils.mkdir_p(File.dirname(db_path))
44
+ if options[:force]
45
+ [db_path, "#{db_path}.metadata", "#{db_path}.visibility", "#{db_path}.mvcc", "#{db_path}.wal"].each do |path|
46
+ FileUtils.rm_rf(path) if File.exist?(path)
47
+ end
48
+ end
49
+ engine = RubyDB::Storage::Engine.new(db_path, auto_vacuum: false)
50
+ engine.close
51
+ @output.success("Database #{name} created at #{db_path}")
52
+ end
53
+ 0
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module CLI
5
+ module Commands
6
+ # Diff - Show differences between branches
7
+ class Diff
8
+ def initialize(output, formatter)
9
+ @output = output
10
+ @formatter = formatter
11
+ end
12
+
13
+ def execute(args, options)
14
+ parser = OptionParser.new do |opts|
15
+ opts.banner = "Usage: rubydb diff [options]"
16
+ opts.on("--table TABLE", "Only show differences for a table") do |table|
17
+ options[:table] = table
18
+ end
19
+ opts.on("--summary", "Show summary only") do
20
+ options[:summary] = true
21
+ end
22
+ opts.on("--database PATH", "Database path") { |path| options[:database] = path }
23
+ opts.on("--branch-dir DIR", "Branch metadata directory") { |path| options[:branch_dir] = path }
24
+ opts.on("-h", "--help", "Show help") do
25
+ @output.puts opts
26
+ exit(0)
27
+ end
28
+ end
29
+
30
+ parser.parse!(args)
31
+
32
+ branch_a = args[0] || "main"
33
+ branch_b = args[1] || "current"
34
+
35
+ database = RubyDB::Database.new(options[:database] || "data/rubydb.rdb", auto_connect: false).connect
36
+ manager = RubyDB::Branching::BranchManager.new(
37
+ database.engine,
38
+ branch_dir: options[:branch_dir] || "branches"
39
+ )
40
+ branch_b = manager.current_branch_name || "main" if branch_b == "current"
41
+ result = RubyDB::Branching::Diff.new(database.engine, manager).diff(
42
+ branch_a, branch_b, type: RubyDB::Branching::Diff::DIFF_ALL,
43
+ tables: (options[:table] && [options[:table]])
44
+ )
45
+ raise result[:error] unless result[:success]
46
+
47
+ changes = result[:changes]
48
+ diff_data = {
49
+ added_tables: [], removed_tables: [], changed_tables: [],
50
+ added_columns: [], removed_columns: [], changed_columns: [],
51
+ changes: changes
52
+ }
53
+
54
+ if options[:summary]
55
+ @output.puts "Differences between #{branch_a} and #{branch_b}:"
56
+ @output.puts " Added tables: #{diff_data[:added_tables].size}"
57
+ @output.puts " Removed tables: #{diff_data[:removed_tables].size}"
58
+ @output.puts " Changed tables: #{diff_data[:changed_tables].size}"
59
+ @output.puts " Added columns: #{diff_data[:added_columns].size}"
60
+ @output.puts " Removed columns: #{diff_data[:removed_columns].size}"
61
+ @output.puts " Changed columns: #{diff_data[:changed_columns].size}"
62
+ else
63
+ @output.puts "Differences between #{branch_a} and #{branch_b}:"
64
+ @output.puts " Added changes: #{changes[:added].size}"
65
+ @output.puts " Removed changes: #{changes[:removed].size}"
66
+ changes.each do |kind, entries|
67
+ entries.each { |entry| @output.puts " #{kind}: #{entry.inspect}" }
68
+ end
69
+ end
70
+ 0
71
+ ensure
72
+ database&.close
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module CLI
5
+ module Commands
6
+ # Doctor - Run health checks
7
+ class Doctor
8
+ def initialize(output, formatter)
9
+ @output = output
10
+ @formatter = formatter
11
+ end
12
+
13
+ def execute(args, options)
14
+ parser = OptionParser.new do |opts|
15
+ opts.banner = "Usage: rubydb doctor [options]"
16
+ opts.on("--fix", "Attempt to fix issues") do
17
+ options[:fix] = true
18
+ end
19
+ opts.on("--quick", "Quick check only") do
20
+ options[:quick] = true
21
+ end
22
+ opts.on("--json", "Output as JSON") do
23
+ options[:json] = true
24
+ end
25
+ opts.on("-h", "--help", "Show help") do
26
+ @output.puts opts
27
+ exit(0)
28
+ end
29
+ end
30
+
31
+ parser.parse!(args)
32
+
33
+ config = RubyDB::Configuration::Config.instance
34
+ db_path = config.get("storage.data_dir") || "data/rubydb.rdb"
35
+ checks = []
36
+ engine = nil
37
+
38
+ begin
39
+ started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
40
+ engine = RubyDB::Storage::Engine.new(db_path, auto_vacuum: false)
41
+ latency_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at) * 1000).round(2)
42
+ checks << { name: "Connection", passed: true, details: { database: db_path, latency_ms: latency_ms } }
43
+ checks << { name: "Storage", passed: engine.storage_manager.open?, details: engine.storage_manager.stats }
44
+ checks << { name: "WAL", passed: !engine.wal.nil?, details: engine.wal.stats }
45
+ unless options[:quick]
46
+ checks << { name: "Indexes", passed: !engine.index_manager.nil?, details: engine.index_manager.stats }
47
+ end
48
+ rescue => e
49
+ checks << { name: "Connection", passed: false, errors: [e.message] }
50
+ ensure
51
+ engine&.close if engine&.open?
52
+ end
53
+
54
+ results = {
55
+ passed: checks.all? { |check| check[:passed] },
56
+ total_count: checks.size,
57
+ passed_count: checks.count { |check| check[:passed] },
58
+ checks: checks
59
+ }
60
+
61
+ if options[:json]
62
+ @output.json(results)
63
+ else
64
+ @formatter.format_doctor(results)
65
+
66
+ if options[:fix] && !results[:passed]
67
+ @output.puts "\nAutomatic repair is not available for the reported checks; no changes were made.", :yellow
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module CLI
5
+ module Commands
6
+ # Drop - Drop a database
7
+ class Drop
8
+ def initialize(output, formatter)
9
+ @output = output
10
+ @formatter = formatter
11
+ end
12
+
13
+ def execute(args, options)
14
+ parser = OptionParser.new do |opts|
15
+ opts.banner = "Usage: rubydb drop [options]"
16
+ opts.on("-n", "--name NAME", "Database name") do |name|
17
+ options[:name] = name
18
+ end
19
+ opts.on("-f", "--force", "Force drop") do
20
+ options[:force] = true
21
+ end
22
+ opts.on("--database PATH", "Database path") { |path| options[:database] = path }
23
+ opts.on("--dir DIR", "Database data directory") { |dir| options[:dir] = dir }
24
+ opts.on("-h", "--help", "Show help") do
25
+ @output.puts opts
26
+ exit(0)
27
+ end
28
+ end
29
+
30
+ parser.parse!(args)
31
+
32
+ name = options[:name] || "rubydb"
33
+ db_path = options[:database] || File.join(options[:dir] || "data", "#{name}.rdb")
34
+ unless File.file?(db_path)
35
+ @output.error("Database not found at #{db_path}")
36
+ return 1
37
+ end
38
+
39
+ unless options[:force]
40
+ @output.warn("This will permanently delete database '#{name}'")
41
+ @output.print("Are you sure? (yes/no): ", nil, false)
42
+ response = $stdin.gets.chomp
43
+ return unless response.downcase == "yes"
44
+ end
45
+
46
+ @output.spinner("Dropping database #{name}...") do
47
+ [db_path, "#{db_path}.metadata", "#{db_path}.visibility", "#{db_path}.mvcc", "#{db_path}.wal"].each do |path|
48
+ FileUtils.rm_rf(path) if File.exist?(path)
49
+ end
50
+ @output.success("Database #{name} dropped")
51
+ end
52
+ 0
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module RubyDB
6
+ module CLI
7
+ module Commands
8
+ class Init
9
+ def initialize(output, formatter)
10
+ @output = output
11
+ @formatter = formatter
12
+ end
13
+
14
+ def execute(args, options)
15
+ parser = OptionParser.new do |opts|
16
+ opts.banner = "Usage: rubydb init [options]"
17
+ opts.on("-n", "--name NAME", "Database name") do |name|
18
+ options[:name] = name
19
+ end
20
+ opts.on("-d", "--dir DIR", "Data directory") do |dir|
21
+ options[:dir] = dir
22
+ end
23
+ opts.on("-f", "--force", "Force overwrite") do
24
+ options[:force] = true
25
+ end
26
+ opts.on("-h", "--help", "Show help") do
27
+ @output.puts opts
28
+ exit(0)
29
+ end
30
+ end
31
+
32
+ parser.parse!(args)
33
+
34
+ name = options[:name] || "rubydb"
35
+ dir = options[:dir] || "data"
36
+ db_path = File.join(dir, "#{name}.rdb")
37
+
38
+ if File.exist?(db_path) && !options[:force]
39
+ @output.error("Database already exists at #{db_path}. Use --force to overwrite.")
40
+ return 1
41
+ end
42
+
43
+ @output.spinner("Initializing database #{name}...") do
44
+ # Create directory
45
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
46
+
47
+ # Initialize storage engine
48
+ engine = RubyDB::Storage::Engine.new(db_path, {})
49
+
50
+ # Create default tables
51
+ create_default_tables(engine)
52
+
53
+ # Create default user if not exists
54
+ create_default_user(engine) if options[:user]
55
+
56
+ @output.success("Database #{name} initialized at #{db_path}")
57
+ end
58
+
59
+ 0
60
+ end
61
+
62
+ private
63
+
64
+ def create_default_tables(engine)
65
+ # Create schema_migrations table
66
+ engine.create_table("schema_migrations") do |t|
67
+ t.column("version", :text, primary_key: true)
68
+ t.column("applied_at", :timestamp)
69
+ end
70
+
71
+ # Create users table
72
+ engine.create_table("users") do |t|
73
+ t.column("id", :integer, primary_key: true, auto_increment: true)
74
+ t.column("username", :text, unique: true, null: false)
75
+ t.column("email", :text, unique: true)
76
+ t.column("password_hash", :text)
77
+ t.column("salt", :text)
78
+ t.column("created_at", :timestamp)
79
+ t.column("updated_at", :timestamp)
80
+ end
81
+
82
+ # Create sessions table
83
+ engine.create_table("sessions") do |t|
84
+ t.column("id", :integer, primary_key: true, auto_increment: true)
85
+ t.column("user_id", :integer)
86
+ t.column("token", :text, unique: true)
87
+ t.column("created_at", :timestamp)
88
+ t.column("expires_at", :timestamp)
89
+ end
90
+ end
91
+
92
+ def create_default_user(engine)
93
+ # Create admin user
94
+ engine.insert_row("users",
95
+ ["username", "email", "created_at"],
96
+ ["admin", "admin@localhost", Time.now]
97
+ )
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module CLI
5
+ module Commands
6
+ # Inspect - Inspect database internals
7
+ class Inspect
8
+ def initialize(output, formatter)
9
+ @output = output
10
+ @formatter = formatter
11
+ end
12
+
13
+ def execute(args, options)
14
+ parser = OptionParser.new do |opts|
15
+ opts.banner = "Usage: rubydb inspect [options]"
16
+ opts.on("-t", "--table TABLE", "Inspect specific table") do |table|
17
+ options[:table] = table
18
+ end
19
+ opts.on("--pages", "Show page information") do
20
+ options[:pages] = true
21
+ end
22
+ opts.on("--indexes", "Show index information") do
23
+ options[:indexes] = true
24
+ end
25
+ opts.on("--stats", "Show statistics") do
26
+ options[:stats] = true
27
+ end
28
+ opts.on("--wal", "Show WAL information") do
29
+ options[:wal] = true
30
+ end
31
+ opts.on("--database PATH", "Database path") { |path| options[:database] = 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
+ engine = database.engine
42
+ stats = engine.stats
43
+
44
+ if options[:table]
45
+ table = options[:table]
46
+ raise "Table '#{table}' does not exist" unless engine.table_exists?(table)
47
+ columns = engine.table_columns(table)
48
+ @output.heading("Table: #{table}", 2)
49
+ @output.puts "Columns:"
50
+ columns.each { |column| @output.puts " #{column.name}: #{column.type_class}" }
51
+ @output.puts "\nRows: #{engine.select_rows(table, columns).size}"
52
+ end
53
+
54
+ if options[:pages]
55
+ @output.heading("Pages", 2)
56
+ @output.puts "Total pages: #{stats[:page_count]}"
57
+ @output.puts "Used pages: #{stats[:used_pages]}"
58
+ @output.puts "Free pages: #{stats[:free_pages]}"
59
+ end
60
+
61
+ if options[:indexes]
62
+ @output.heading("Indexes", 2)
63
+ @output.puts "Index statistics: #{engine.index_manager.stats.inspect}"
64
+ end
65
+
66
+ if options[:stats]
67
+ @output.heading("Statistics", 2)
68
+ stats.each { |key, value| @output.puts "#{key}: #{value}" }
69
+ end
70
+
71
+ if options[:wal]
72
+ @output.heading("WAL Information", 2)
73
+ wal_files = engine.wal_files
74
+ wal_size = wal_files.sum { |file| File.size(file) }
75
+ @output.puts "WAL enabled: #{!wal_files.empty?}"
76
+ @output.puts "WAL files: #{wal_files.size}"
77
+ @output.puts "WAL size: #{wal_size} bytes"
78
+ end
79
+
80
+ if !options[:table] && !options[:pages] && !options[:indexes] && !options[:stats] && !options[:wal]
81
+ @output.heading("Database Inspection", 1)
82
+ @output.puts "Version: #{RubyDB::VERSION}"
83
+ @output.puts "Tables: #{stats[:table_count]}"
84
+ @output.puts "Total pages: #{stats[:page_count]}"
85
+ @output.puts "Database size: #{File.size(engine.path)} bytes"
86
+ @output.puts "Cache hit rate: #{stats[:cache_hit_rate]}"
87
+ end
88
+ 0
89
+ ensure
90
+ database&.close
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module CLI
5
+ module Commands
6
+ # Merge - Merge a branch
7
+ class Merge
8
+ def initialize(output, formatter)
9
+ @output = output
10
+ @formatter = formatter
11
+ end
12
+
13
+ def execute(args, options)
14
+ parser = OptionParser.new do |opts|
15
+ opts.banner = "Usage: rubydb merge [options]"
16
+ opts.on("--into BRANCH", "Target branch") do |branch|
17
+ options[:into] = branch
18
+ end
19
+ opts.on("--strategy STRATEGY", "Merge strategy (fast-forward, recursive)") do |strategy|
20
+ options[:strategy] = strategy.to_sym
21
+ end
22
+ opts.on("--no-commit", "Don't commit after merge") do
23
+ options[:no_commit] = true
24
+ end
25
+ opts.on("--database PATH", "Database path") { |path| options[:database] = path }
26
+ opts.on("--branch-dir DIR", "Branch metadata directory") { |path| options[:branch_dir] = path }
27
+ opts.on("-h", "--help", "Show help") do
28
+ @output.puts opts
29
+ exit(0)
30
+ end
31
+ end
32
+
33
+ parser.parse!(args)
34
+
35
+ source = args.first
36
+ if source.nil?
37
+ @output.error("Source branch required")
38
+ @output.puts parser
39
+ return
40
+ end
41
+
42
+ target = options[:into] || "main"
43
+ database = RubyDB::Database.new(options[:database] || "data/rubydb.rdb", auto_connect: false).connect
44
+ manager = RubyDB::Branching::BranchManager.new(
45
+ database.engine,
46
+ branch_dir: options[:branch_dir] || "branches"
47
+ )
48
+ merger = RubyDB::Branching::Merge.new(database.engine, manager,
49
+ strategy: (options[:strategy] || :fast_forward))
50
+ result = nil
51
+ @output.spinner("Merging #{source} into #{target}...") do
52
+ result = merger.merge(source, target, abort_on_conflict: true)
53
+ end
54
+ raise result[:error] || result[:message] unless result[:success]
55
+ @output.success(result[:message] || "Merge completed successfully")
56
+ 0
57
+ ensure
58
+ database&.close
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+
5
+ module RubyDB
6
+ module CLI
7
+ module Commands
8
+ # Migrate - Run database migrations
9
+ class Migrate
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 migrate [options]"
18
+ opts.on("-v", "--version VERSION", "Migrate to version") do |version|
19
+ options[:version] = version
20
+ end
21
+ opts.on("-s", "--steps N", Integer, "Rollback N steps") do |steps|
22
+ options[:steps] = steps
23
+ end
24
+ opts.on("--down", "Migrate down") do
25
+ options[:down] = true
26
+ end
27
+ opts.on("--dry-run", "Show what would be run") do
28
+ options[:dry_run] = true
29
+ end
30
+ opts.on("--database PATH", "Database path") { |path| options[:database] = path }
31
+ opts.on("--path DIR", "Migration directory") { |path| options[:migrations_path] = 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::Migrations::MigrationManager.new(
42
+ database,
43
+ path: options[:migrations_path] || "db/migrate",
44
+ version: options[:version]
45
+ )
46
+ if options[:dry_run]
47
+ @formatter.format_migrations(manager.status)
48
+ elsif options[:down]
49
+ manager.rollback(options[:steps] || 1)
50
+ @output.success("Migration rollback completed")
51
+ else
52
+ manager.migrate
53
+ @output.success("Migrations completed successfully")
54
+ end
55
+ 0
56
+ ensure
57
+ database&.close
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+
5
+ module RubyDB
6
+ module CLI
7
+ module Commands
8
+ # Sends a restart signal to the process recorded by the PID file.
9
+ class Restart
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 restart [options]"
18
+ opts.on("--pid-file FILE", "PID file") { |file| options[:pid_file] = file }
19
+ opts.on("--force", "Send KILL instead of TERM") { options[:force] = true }
20
+ opts.on("-h", "--help", "Show help") { @output.puts opts; return 0 }
21
+ end
22
+ parser.parse!(args)
23
+
24
+ pid_file = options[:pid_file] || "rubydb.pid"
25
+ raise "PID file not found: #{pid_file}" unless File.file?(pid_file)
26
+ pid = Integer(File.read(pid_file).strip, 10)
27
+ Process.kill(0, pid)
28
+ Process.kill(options[:force] ? "KILL" : "TERM", pid)
29
+ @output.success("RubyDB restart signal sent to PID #{pid}")
30
+ 0
31
+ rescue Errno::ESRCH
32
+ raise "RubyDB process #{pid} is not running"
33
+ rescue ArgumentError
34
+ raise "Invalid PID in #{pid_file}"
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end