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,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module CLI
5
+ module Commands
6
+ class Status
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 status [options]"
15
+ opts.on("--json", "Output as JSON") do
16
+ options[:json] = true
17
+ end
18
+ opts.on("-h", "--help", "Show help") do
19
+ @output.puts opts
20
+ exit(0)
21
+ end
22
+ end
23
+
24
+ parser.parse!(args)
25
+
26
+ # Get real status from engine
27
+ config = RubyDB::Configuration::Config.instance
28
+ db_path = config.get("storage.data_dir") || "data/rubydb.rdb"
29
+
30
+ status_data = {
31
+ database: File.basename(db_path, ".rdb"),
32
+ mode: "server",
33
+ status: "unavailable",
34
+ version: RubyDB::VERSION,
35
+ pid: Process.pid,
36
+ uptime: Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i,
37
+ connections: 0,
38
+ tables: 0,
39
+ storage_size: File.exist?(db_path) ? File.size(db_path) : 0,
40
+ memory_usage: get_memory_usage,
41
+ wal_enabled: true,
42
+ replication: { role: "unknown", status: "not_configured", lag_ms: nil },
43
+ branch: { current: "unknown", total: nil }
44
+ }
45
+
46
+ engine = nil
47
+ begin
48
+ engine = RubyDB::Storage::Engine.new(db_path, {})
49
+ status_data[:status] = "running"
50
+ status_data[:tables] = engine.list_tables.size
51
+ status_data[:connections] = engine.connection_count rescue 0
52
+ status_data[:wal_enabled] = !engine.wal.nil?
53
+ rescue => error
54
+ status_data[:error] = error.message
55
+ ensure
56
+ engine&.close if engine&.open?
57
+ end
58
+
59
+ if options[:json]
60
+ @output.json(status_data)
61
+ else
62
+ @formatter.format_status(status_data)
63
+ end
64
+
65
+ 0
66
+ end
67
+
68
+ private
69
+
70
+ def get_memory_usage
71
+ if File.exist?("/proc/self/status")
72
+ File.read("/proc/self/status").each_line do |line|
73
+ if line.start_with?("VmRSS:")
74
+ return line.split[1].to_i * 1024
75
+ end
76
+ end
77
+ end
78
+
79
+ # Fallback - run ps command
80
+ begin
81
+ output = `ps -o rss= -p #{Process.pid}`
82
+ return output.strip.to_i * 1024 if output && !output.empty?
83
+ rescue
84
+ # Ignore
85
+ end
86
+
87
+ 0
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+
5
+ module RubyDB
6
+ module CLI
7
+ module Commands
8
+ # Stop - Stop the database server
9
+ class Stop
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 stop [options]"
18
+ opts.on("-f", "--force", "Force stop") do
19
+ options[:force] = true
20
+ end
21
+ opts.on("--pid-file FILE", "PID file") do |file|
22
+ options[:pid_file] = file
23
+ end
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
+ pid_file = options[:pid_file] || "rubydb.pid"
33
+ raise "PID file not found: #{pid_file}" unless File.file?(pid_file)
34
+ pid = Integer(File.read(pid_file).strip, 10)
35
+ Process.kill(0, pid)
36
+ Process.kill(options[:force] ? "KILL" : "TERM", pid)
37
+ @output.success("RubyDB server stop signal sent to PID #{pid}")
38
+ 0
39
+ rescue Errno::ESRCH
40
+ raise "RubyDB process #{pid} is not running"
41
+ rescue ArgumentError
42
+ raise "Invalid PID in #{pid_file}"
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module CLI
5
+ module Commands
6
+ # Vacuum - Vacuum the database
7
+ class Vacuum
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 vacuum [options]"
16
+ opts.on("--full", "Full vacuum (reclaim space)") do
17
+ options[:full] = true
18
+ end
19
+ opts.on("--analyze", "Analyze after vacuum") do
20
+ options[:analyze] = true
21
+ end
22
+ opts.on("--table TABLE", "Vacuum specific table") do |table|
23
+ options[:table] = table
24
+ end
25
+ opts.on("--dry-run", "Show what would be vacuumed") do
26
+ options[:dry_run] = true
27
+ end
28
+ opts.on("--database PATH", "Database path") { |path| options[:database] = path }
29
+ opts.on("-h", "--help", "Show help") do
30
+ @output.puts opts
31
+ exit(0)
32
+ end
33
+ end
34
+
35
+ parser.parse!(args)
36
+
37
+ database = RubyDB::Database.new(options[:database] || "data/rubydb.rdb", auto_connect: false).connect
38
+ engine = database.engine
39
+
40
+ if options[:dry_run]
41
+ @output.info("Would vacuum:")
42
+ stats = engine.stats
43
+ @output.puts(" - Current rows: #{stats[:row_count]}")
44
+ @output.puts(" - Tables affected: #{stats[:table_count]}")
45
+ @output.puts(" - Cleanup will respect active transaction snapshots")
46
+ return 0
47
+ end
48
+
49
+ result = engine.vacuum
50
+ @output.success("Vacuum completed. Removed #{result[:removed]} rows; #{result[:total_rows]} rows remain.")
51
+ 0
52
+ ensure
53
+ database&.close
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+
5
+ module RubyDB
6
+ module CLI
7
+ # Formatter - Formats CLI output
8
+ class Formatter
9
+ def initialize(output)
10
+ @output = output
11
+ end
12
+
13
+ def format_status(status_data)
14
+ @output.heading("RubyDB Status", 1)
15
+
16
+ @output.puts "Database: #{status_data[:database] || 'N/A'}"
17
+ @output.puts "Mode: #{status_data[:mode] || 'N/A'}"
18
+ @output.puts "Status: #{status_data[:status] || 'N/A'}"
19
+ @output.puts "Version: #{status_data[:version] || RubyDB::VERSION}"
20
+ @output.puts "PID: #{status_data[:pid] || 'N/A'}"
21
+ @output.puts "Uptime: #{format_uptime(status_data[:uptime])}"
22
+ @output.puts "Connections: #{status_data[:connections] || 0}"
23
+ @output.puts "Tables: #{status_data[:tables] || 0}"
24
+ @output.puts "Storage: #{format_size(status_data[:storage_size])}"
25
+ @output.puts "Memory: #{format_size(status_data[:memory_usage])}"
26
+ @output.puts "WAL: #{status_data[:wal_enabled] ? 'Enabled' : 'Disabled'}"
27
+
28
+ if status_data[:replication]
29
+ @output.puts "\nReplication:"
30
+ @output.puts " Role: #{status_data[:replication][:role]}"
31
+ @output.puts " Status: #{status_data[:replication][:status]}"
32
+ @output.puts " Lag: #{status_data[:replication][:lag_ms]}ms"
33
+ end
34
+
35
+ if status_data[:branch]
36
+ @output.puts "\nBranch:"
37
+ @output.puts " Current: #{status_data[:branch][:current]}"
38
+ @output.puts " Total: #{status_data[:branch][:total]}"
39
+ end
40
+ end
41
+
42
+ def format_backups(backups)
43
+ return @output.puts("No backups found", :yellow) if backups.empty?
44
+
45
+ headers = ["Name", "Type", "Size", "Created At", "Age"]
46
+ rows = backups.map do |b|
47
+ [
48
+ b[:name],
49
+ b[:type],
50
+ format_size(b[:size]),
51
+ b[:created_at],
52
+ format_age(b[:age])
53
+ ]
54
+ end
55
+
56
+ @output.table(headers, rows, show_count: true)
57
+ end
58
+
59
+ def format_branches(branches)
60
+ return @output.puts("No branches found", :yellow) if branches.empty?
61
+
62
+ headers = ["Name", "State", "Commits", "Created At", "Parent"]
63
+ rows = branches.map do |b|
64
+ [
65
+ b[:name],
66
+ b[:state],
67
+ b[:commit_count],
68
+ b[:created_at],
69
+ b[:parent_branch] || "-"
70
+ ]
71
+ end
72
+
73
+ @output.table(headers, rows, show_count: true)
74
+ end
75
+
76
+ def format_tables(tables)
77
+ return @output.puts("No tables found", :yellow) if tables.empty?
78
+
79
+ headers = ["Table", "Rows", "Columns", "Size", "Created At"]
80
+ rows = tables.map do |t|
81
+ [
82
+ t[:name],
83
+ t[:row_count],
84
+ t[:column_count],
85
+ format_size(t[:size]),
86
+ t[:created_at]
87
+ ]
88
+ end
89
+
90
+ @output.table(headers, rows, show_count: true)
91
+ end
92
+
93
+ def format_migrations(migrations)
94
+ return @output.puts("No migrations found", :yellow) if migrations.empty?
95
+
96
+ migrations = migrations.map do |migration|
97
+ migration.merge(applied: migration[:applied] || migration[:state]&.to_sym == :applied)
98
+ end
99
+
100
+ headers = ["Version", "Name", "Status", "Applied At"]
101
+ rows = migrations.map do |m|
102
+ [
103
+ m[:version],
104
+ m[:name],
105
+ m[:applied] ? "✓ Applied" : "⏳ Pending",
106
+ m[:applied_at] || "-"
107
+ ]
108
+ end
109
+
110
+ @output.table(headers, rows, show_count: true)
111
+ end
112
+
113
+ def format_diff(diff_data)
114
+ @output.heading("Database Diff", 2)
115
+
116
+ if diff_data[:added_tables].any?
117
+ @output.puts "Added Tables:", :green
118
+ diff_data[:added_tables].each { |t| @output.puts " + #{t}" }
119
+ end
120
+
121
+ if diff_data[:removed_tables].any?
122
+ @output.puts "\nRemoved Tables:", :red
123
+ diff_data[:removed_tables].each { |t| @output.puts " - #{t}" }
124
+ end
125
+
126
+ if diff_data[:changed_tables].any?
127
+ @output.puts "\nChanged Tables:", :yellow
128
+ diff_data[:changed_tables].each do |t|
129
+ @output.puts " ~ #{t[:name]}"
130
+ t[:changes].each do |change|
131
+ @output.puts " - #{change}"
132
+ end
133
+ end
134
+ end
135
+
136
+ if diff_data[:added_columns].any?
137
+ @output.puts "\nAdded Columns:", :green
138
+ diff_data[:added_columns].each do |c|
139
+ @output.puts " + #{c[:table]}.#{c[:column]} (#{c[:type]})"
140
+ end
141
+ end
142
+
143
+ if diff_data[:removed_columns].any?
144
+ @output.puts "\nRemoved Columns:", :red
145
+ diff_data[:removed_columns].each do |c|
146
+ @output.puts " - #{c[:table]}.#{c[:column]}"
147
+ end
148
+ end
149
+
150
+ if diff_data[:changed_columns].any?
151
+ @output.puts "\nChanged Columns:", :yellow
152
+ diff_data[:changed_columns].each do |c|
153
+ @output.puts " ~ #{c[:table]}.#{c[:column]} (#{c[:old_type]} → #{c[:new_type]})"
154
+ end
155
+ end
156
+
157
+ if diff_data[:added_tables].empty? && diff_data[:removed_tables].empty? &&
158
+ diff_data[:changed_tables].empty? && diff_data[:added_columns].empty? &&
159
+ diff_data[:removed_columns].empty? && diff_data[:changed_columns].empty?
160
+ @output.puts "No differences found", :green
161
+ end
162
+ end
163
+
164
+ def format_doctor(results)
165
+ @output.heading("Database Health Check", 1)
166
+
167
+ results[:checks].each do |check|
168
+ status = check[:passed] ? "✓" : "✗"
169
+ color = check[:passed] ? :green : :red
170
+ @output.puts "#{status} #{check[:name]}", color
171
+
172
+ if check[:details]
173
+ @output.puts " Details:", :gray
174
+ check[:details].each do |key, value|
175
+ @output.puts " #{key}: #{value}", :gray
176
+ end
177
+ end
178
+
179
+ if check[:errors] && check[:errors].any?
180
+ @output.puts " Errors:", :red
181
+ check[:errors].each { |e| @output.puts " - #{e}", :red }
182
+ end
183
+ end
184
+
185
+ @output.puts "\nOverall: #{results[:passed] ? 'Healthy' : 'Issues Found'}", results[:passed] ? :green : :red
186
+ @output.puts "Checks Passed: #{results[:passed_count]}/#{results[:total_count]}"
187
+ end
188
+
189
+ private
190
+
191
+ def format_size(bytes)
192
+ return "0 B" if bytes.to_i == 0
193
+ units = ["B", "KB", "MB", "GB", "TB"]
194
+ exp = (Math.log(bytes) / Math.log(1024)).floor
195
+ size = bytes / (1024.0 ** exp)
196
+ "#{size.round(2)} #{units[exp]}"
197
+ end
198
+
199
+ def format_age(seconds)
200
+ return "N/A" unless seconds
201
+ return "#{seconds.round}s" if seconds < 60
202
+ return "#{(seconds / 60).round}m" if seconds < 3600
203
+ return "#{(seconds / 3600).round}h" if seconds < 86400
204
+ "#{(seconds / 86400).round}d"
205
+ end
206
+
207
+ def format_uptime(seconds)
208
+ return "N/A" unless seconds
209
+ days = (seconds / 86400).to_i
210
+ hours = ((seconds % 86400) / 3600).to_i
211
+ minutes = ((seconds % 3600) / 60).to_i
212
+
213
+ parts = []
214
+ parts << "#{days}d" if days > 0
215
+ parts << "#{hours}h" if hours > 0 || days > 0
216
+ parts << "#{minutes}m"
217
+ parts.join(" ")
218
+ end
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module CLI
5
+ # Output - Handles CLI output formatting
6
+ class Output
7
+ COLORS = {
8
+ reset: "\e[0m",
9
+ bold: "\e[1m",
10
+ red: "\e[31m",
11
+ green: "\e[32m",
12
+ yellow: "\e[33m",
13
+ blue: "\e[34m",
14
+ magenta: "\e[35m",
15
+ cyan: "\e[36m",
16
+ white: "\e[37m",
17
+ gray: "\e[90m",
18
+ bright_red: "\e[91m",
19
+ bright_green: "\e[92m",
20
+ bright_yellow: "\e[93m",
21
+ bright_blue: "\e[94m"
22
+ }
23
+
24
+ def initialize(no_color: false, quiet: false, format: :text)
25
+ @no_color = no_color
26
+ @quiet = quiet
27
+ @format = format
28
+ @buffer = []
29
+ end
30
+
31
+ def print(message, color = nil, newline = true)
32
+ return if @quiet
33
+
34
+ if color && !@no_color
35
+ message = "#{COLORS[color]}#{message}#{COLORS[:reset]}"
36
+ end
37
+
38
+ $stdout.print(message)
39
+ $stdout.print("\n") if newline
40
+ end
41
+
42
+ def puts(message = "", color = nil)
43
+ print(message, color, true)
44
+ end
45
+
46
+ def error(message)
47
+ print("Error: #{message}", :bright_red)
48
+ end
49
+
50
+ def warn(message)
51
+ print("Warning: #{message}", :yellow)
52
+ end
53
+
54
+ def success(message)
55
+ print("✓ #{message}", :green)
56
+ end
57
+
58
+ def info(message)
59
+ print("ℹ #{message}", :blue)
60
+ end
61
+
62
+ def debug(message)
63
+ print("DEBUG: #{message}", :gray) if ENV["RUBYDB_DEBUG"]
64
+ end
65
+
66
+ def table(headers, rows, options = {})
67
+ return if rows.empty? && !options[:show_empty]
68
+
69
+ # Calculate column widths
70
+ widths = headers.map.with_index do |h, i|
71
+ max = h.to_s.length
72
+ rows.each do |row|
73
+ val = row[i].to_s
74
+ max = val.length if val.length > max
75
+ end
76
+ max
77
+ end
78
+
79
+ # Print headers
80
+ header_line = headers.each_with_index.map { |h, i| h.to_s.ljust(widths[i]) }.join(" ")
81
+ puts header_line, :bold
82
+ puts "-" * header_line.length, :gray
83
+
84
+ # Print rows
85
+ rows.each do |row|
86
+ line = row.each_with_index.map { |val, i| val.to_s.ljust(widths[i]) }.join(" ")
87
+ puts line
88
+ end
89
+
90
+ puts "\nTotal: #{rows.size}" if options[:show_count]
91
+ end
92
+
93
+ def json(data)
94
+ require "json"
95
+ puts JSON.pretty_generate(data)
96
+ end
97
+
98
+ def yaml(data)
99
+ require "yaml"
100
+ puts data.to_yaml
101
+ end
102
+
103
+ def progress(current, total, message = nil)
104
+ percent = (current.to_f / total * 100).round(1)
105
+ bar_length = 40
106
+ filled = (percent / 100 * bar_length).round
107
+ bar = "[" + "=" * filled + " " * (bar_length - filled) + "]"
108
+
109
+ if message
110
+ print("\r#{message} #{bar} #{percent}%", nil, false)
111
+ else
112
+ print("\r#{bar} #{percent}%", nil, false)
113
+ end
114
+
115
+ print("\n") if current == total
116
+ end
117
+
118
+ def spinner(message = nil)
119
+ return unless block_given?
120
+
121
+ chars = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
122
+ index = 0
123
+ thread = Thread.new do
124
+ while true
125
+ print("\r#{chars[index % chars.length]} #{message}", nil, false)
126
+ index += 1
127
+ sleep(0.1)
128
+ end
129
+ end
130
+
131
+ result = yield
132
+ thread.kill
133
+ print("\r✓ #{message}", :green)
134
+ puts
135
+ result
136
+ end
137
+
138
+ def heading(text, level = 1)
139
+ case level
140
+ when 1
141
+ puts "#{'=' * text.length}", :bold
142
+ puts text, :bold
143
+ puts "#{'=' * text.length}", :bold
144
+ when 2
145
+ puts text, :bold
146
+ puts "-" * text.length, :gray
147
+ else
148
+ puts text, :bold
149
+ end
150
+ puts
151
+ end
152
+
153
+ def write(data)
154
+ @buffer << data
155
+ end
156
+
157
+ def flush
158
+ result = @buffer.join("\n")
159
+ @buffer.clear
160
+ result
161
+ end
162
+
163
+ def quiet?
164
+ @quiet
165
+ end
166
+ end
167
+ end
168
+ end