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,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+ require "securerandom"
5
+
6
+ module RubyDB
7
+ module Branching
8
+ # Branch - Represents a database branch
9
+ class Branch
10
+ attr_reader :name, :id, :created_at, :updated_at, :metadata, :changes, :state_snapshot
11
+ attr_accessor :parent_branch, :head_lsn, :base_lsn, :state
12
+
13
+ # Branch states
14
+ STATE_ACTIVE = :active
15
+ STATE_MERGED = :merged
16
+ STATE_ABANDONED = :abandoned
17
+ STATE_ARCHIVED = :archived
18
+ STATE_LOCKED = :locked
19
+
20
+ def initialize(name, options = {})
21
+ @name = name
22
+ @id = options[:id] || generate_branch_id
23
+ @parent_branch = options[:parent_branch]
24
+ @base_lsn = options[:base_lsn] || 0
25
+ @head_lsn = @base_lsn
26
+ @created_at = Time.now
27
+ @updated_at = Time.now
28
+ @state = STATE_ACTIVE
29
+ @metadata = options[:metadata] || {}
30
+ @description = options[:description]
31
+ @owner = options[:owner]
32
+ @is_protected = options[:protected] || false
33
+ @is_default = options[:default] || false
34
+ @changes = []
35
+ @state_snapshot = options[:state_snapshot]
36
+ @commit_count = 0
37
+ @last_commit = nil
38
+ @lock = Mutex.new
39
+ end
40
+
41
+ def commit(change)
42
+ @lock.synchronize do
43
+ @changes << change
44
+ @commit_count += 1
45
+ @head_lsn = change[:lsn] if change[:lsn]
46
+ @last_commit = Time.now
47
+ @updated_at = Time.now
48
+ end
49
+ end
50
+
51
+ def changes
52
+ @lock.synchronize { @changes.map(&:dup) }
53
+ end
54
+
55
+ def logical_changes
56
+ @lock.synchronize do
57
+ @changes.map do |change|
58
+ payload = change[:data] || change["data"]
59
+ (payload.is_a?(Hash) ? payload : change).dup
60
+ end
61
+ end
62
+ end
63
+
64
+ def rollback(count = 1)
65
+ @lock.synchronize do
66
+ return false if @changes.empty?
67
+
68
+ removed = @changes.pop(count)
69
+ @commit_count -= removed.size
70
+ @head_lsn = @changes.last&.[](:lsn) || @base_lsn
71
+ @updated_at = Time.now
72
+
73
+ removed
74
+ end
75
+ end
76
+
77
+ def merge_changes(changes)
78
+ @lock.synchronize do
79
+ changes.each do |change|
80
+ @changes << change
81
+ @commit_count += 1
82
+ @head_lsn = change[:lsn] if change[:lsn]
83
+ end
84
+ @last_commit = Time.now
85
+ @updated_at = Time.now
86
+ end
87
+ end
88
+
89
+ def lock
90
+ @lock.synchronize do
91
+ @state = STATE_LOCKED
92
+ @updated_at = Time.now
93
+ end
94
+ end
95
+
96
+ def unlock
97
+ @lock.synchronize do
98
+ @state = STATE_ACTIVE if @state == STATE_LOCKED
99
+ @updated_at = Time.now
100
+ end
101
+ end
102
+
103
+ def archive
104
+ @lock.synchronize do
105
+ @state = STATE_ARCHIVED
106
+ @updated_at = Time.now
107
+ end
108
+ end
109
+
110
+ def merge
111
+ @lock.synchronize do
112
+ @state = STATE_MERGED
113
+ @updated_at = Time.now
114
+ end
115
+ end
116
+
117
+ def abandon
118
+ @lock.synchronize do
119
+ @state = STATE_ABANDONED
120
+ @updated_at = Time.now
121
+ end
122
+ end
123
+
124
+ def active?
125
+ @state == STATE_ACTIVE
126
+ end
127
+
128
+ def merged?
129
+ @state == STATE_MERGED
130
+ end
131
+
132
+ def locked?
133
+ @state == STATE_LOCKED
134
+ end
135
+
136
+ def archived?
137
+ @state == STATE_ARCHIVED
138
+ end
139
+
140
+ def abandoned?
141
+ @state == STATE_ABANDONED
142
+ end
143
+
144
+ def change_count
145
+ @changes.size
146
+ end
147
+
148
+ def to_hash
149
+ {
150
+ name: @name,
151
+ id: @id,
152
+ parent_branch: @parent_branch,
153
+ base_lsn: @base_lsn,
154
+ head_lsn: @head_lsn,
155
+ created_at: @created_at.iso8601,
156
+ updated_at: @updated_at.iso8601,
157
+ state: @state,
158
+ commit_count: @commit_count,
159
+ last_commit: @last_commit&.iso8601,
160
+ is_protected: @is_protected,
161
+ is_default: @is_default,
162
+ owner: @owner,
163
+ description: @description,
164
+ metadata: @metadata,
165
+ changes: @changes,
166
+ state_snapshot: @state_snapshot
167
+ }
168
+ end
169
+
170
+ def inspect
171
+ "#<Branch name=#{@name} state=#{@state} commits=#{@commit_count}>"
172
+ end
173
+
174
+ private
175
+
176
+ def generate_branch_id
177
+ "br_#{Time.now.to_i}_#{SecureRandom.hex(8)}"
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,311 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "json"
5
+ require "time"
6
+
7
+ module RubyDB
8
+ module Branching
9
+ class BranchingError < StandardError; end unless const_defined?(:BranchingError)
10
+
11
+ # BranchManager - Manages all database branches
12
+ class BranchManager
13
+ attr_reader :branches, :current_branch, :stats
14
+
15
+ def initialize(engine, config = {})
16
+ @engine = engine
17
+ @config = config
18
+ @branches = {}
19
+ @current_branch = nil
20
+ @branch_dir = config[:branch_dir] || "branches"
21
+ @stats = {
22
+ branches_created: 0,
23
+ branches_deleted: 0,
24
+ branches_merged: 0,
25
+ branches_abandoned: 0,
26
+ total_commits: 0,
27
+ active_branches: 0
28
+ }
29
+ @lock = Mutex.new
30
+
31
+ FileUtils.mkdir_p(@branch_dir)
32
+
33
+ # Load existing branches
34
+ load_branches
35
+
36
+ # Create default branch if none exists
37
+ create_default_branch if @branches.empty?
38
+ end
39
+
40
+ def create_branch(name, options = {})
41
+ @lock.synchronize do
42
+ return { success: false, error: "Invalid branch name" } unless valid_branch_name?(name)
43
+
44
+ if @branches.key?(name)
45
+ return { success: false, error: "Branch '#{name}' already exists" }
46
+ end
47
+
48
+ parent = options[:from] || @current_branch&.name
49
+ parent_branch = parent ? @branches[parent] : nil
50
+
51
+ unless parent_branch || options[:from_lsn]
52
+ return { success: false, error: "No parent branch or LSN specified" }
53
+ end
54
+
55
+ base_lsn = options[:from_lsn] || parent_branch.head_lsn
56
+
57
+ branch = Branch.new(name,
58
+ parent_branch: parent,
59
+ base_lsn: base_lsn,
60
+ description: options[:description],
61
+ owner: options[:owner],
62
+ protected: options[:protected] || false,
63
+ default: options[:default] || false,
64
+ metadata: options[:metadata] || {}
65
+ )
66
+ branch.instance_variable_set(:@state_snapshot, @engine.export_state) if @engine.respond_to?(:export_state)
67
+
68
+ @branches[name] = branch
69
+ @stats[:branches_created] += 1
70
+ @stats[:active_branches] = @branches.values.count(&:active?)
71
+
72
+ # Set as current if no current branch
73
+ @current_branch ||= branch
74
+
75
+ # Save branches
76
+ save_branches
77
+
78
+ { success: true, branch: branch }
79
+ end
80
+ end
81
+
82
+ def delete_branch(name, force = false)
83
+ @lock.synchronize do
84
+ return { success: false, error: "Invalid branch name" } unless valid_branch_name?(name)
85
+
86
+ branch = @branches[name]
87
+ return { success: false, error: "Branch '#{name}' not found" } unless branch
88
+
89
+ if branch.protected? && !force
90
+ return { success: false, error: "Branch '#{name}' is protected" }
91
+ end
92
+
93
+ if branch == @current_branch && !force
94
+ return { success: false, error: "Cannot delete current branch" }
95
+ end
96
+
97
+ # Delete branch data
98
+ delete_branch_data(name)
99
+
100
+ @branches.delete(name)
101
+ @stats[:branches_deleted] += 1
102
+ @stats[:active_branches] = @branches.values.count(&:active?)
103
+
104
+ save_branches
105
+
106
+ { success: true }
107
+ end
108
+ end
109
+
110
+ def checkout(name)
111
+ @lock.synchronize do
112
+ branch = @branches[name]
113
+ return { success: false, error: "Branch '#{name}' not found" } unless branch
114
+
115
+ if branch.locked?
116
+ return { success: false, error: "Branch '#{name}' is locked" }
117
+ end
118
+
119
+ unless @engine.respond_to?(:apply_branch_state)
120
+ return { success: false, error: "Branch checkout requires an engine state-application hook" }
121
+ end
122
+
123
+ # Apply the persisted state before publishing the branch switch. If
124
+ # state application fails, the manager and engine must continue to
125
+ # agree about the active branch.
126
+ applied = apply_branch_state(name)
127
+ return { success: false, error: "Unable to apply branch state" } if applied == false
128
+
129
+ @current_branch = branch
130
+
131
+ { success: true, branch: branch }
132
+ end
133
+ end
134
+
135
+ def commit(change_data)
136
+ @lock.synchronize do
137
+ return { success: false, error: "No current branch" } unless @current_branch
138
+
139
+ branch = @current_branch
140
+ return { success: false, error: "Branch is locked" } if branch.locked?
141
+
142
+ # Create commit
143
+ commit = {
144
+ id: generate_commit_id,
145
+ timestamp: Time.now.iso8601,
146
+ data: change_data,
147
+ lsn: (@engine.current_lsn if @engine.respond_to?(:current_lsn))
148
+ }
149
+
150
+ branch.commit(commit)
151
+ @stats[:total_commits] += 1
152
+
153
+ # Save branch state
154
+ save_branches
155
+
156
+ { success: true, commit: commit }
157
+ end
158
+ end
159
+
160
+ def rollback(count = 1)
161
+ @lock.synchronize do
162
+ return { success: false, error: "No current branch" } unless @current_branch
163
+
164
+ branch = @current_branch
165
+ return { success: false, error: "Branch is locked" } if branch.locked?
166
+
167
+ removed = branch.rollback(count)
168
+
169
+ if removed&.any?
170
+ save_branches
171
+ { success: true, removed: removed }
172
+ else
173
+ { success: false, error: "Nothing to rollback" }
174
+ end
175
+ end
176
+ end
177
+
178
+ def list_branches
179
+ @lock.synchronize do
180
+ @branches.values.map(&:to_hash)
181
+ end
182
+ end
183
+
184
+ def get_branch(name)
185
+ @branches[name]
186
+ end
187
+
188
+ def current_branch_name
189
+ @current_branch&.name
190
+ end
191
+
192
+ # Persist branch metadata after an external operation (such as merge)
193
+ # has changed a branch object through the public branching API.
194
+ def persist!
195
+ @lock.synchronize { save_branches }
196
+ true
197
+ end
198
+
199
+ def stats
200
+ @lock.synchronize do
201
+ @stats.merge({
202
+ total_branches: @branches.size,
203
+ active_branches: @branches.values.count(&:active?),
204
+ current_branch: @current_branch&.name,
205
+ branch_dir: @branch_dir
206
+ })
207
+ end
208
+ end
209
+
210
+ private
211
+
212
+ def valid_branch_name?(name)
213
+ value = name.to_s
214
+ !value.empty? && value == File.basename(value) && !value.include?(File::SEPARATOR) &&
215
+ (File::ALT_SEPARATOR.nil? || !value.include?(File::ALT_SEPARATOR))
216
+ end
217
+
218
+ def load_branches
219
+ branches_file = File.join(@branch_dir, "branches.json")
220
+ return unless File.exist?(branches_file)
221
+
222
+ begin
223
+ data = JSON.parse(File.read(branches_file), symbolize_names: true)
224
+
225
+ data[:branches].each do |branch_data|
226
+ branch = Branch.new(branch_data[:name],
227
+ id: branch_data[:id],
228
+ parent_branch: branch_data[:parent_branch],
229
+ base_lsn: branch_data[:base_lsn],
230
+ description: branch_data[:description],
231
+ owner: branch_data[:owner],
232
+ protected: branch_data[:is_protected] || false,
233
+ default: branch_data[:is_default] || false,
234
+ metadata: branch_data[:metadata] || {},
235
+ state_snapshot: branch_data[:state_snapshot]
236
+ )
237
+ branch.instance_variable_set(:@head_lsn, branch_data[:head_lsn])
238
+ branch.instance_variable_set(:@created_at, Time.parse(branch_data[:created_at]))
239
+ branch.instance_variable_set(:@updated_at, Time.parse(branch_data[:updated_at]))
240
+ branch.instance_variable_set(:@state, branch_data[:state].to_sym)
241
+ branch.instance_variable_set(:@commit_count, branch_data[:commit_count] || 0)
242
+ branch.instance_variable_set(:@changes, branch_data[:changes] || [])
243
+
244
+ @branches[branch_data[:name]] = branch
245
+ end
246
+
247
+ # Set current branch
248
+ current_name = data[:current_branch]
249
+ @current_branch = @branches[current_name] if current_name
250
+
251
+ @stats[:branches_created] = data[:stats][:branches_created] || 0
252
+ @stats[:total_commits] = data[:stats][:total_commits] || 0
253
+
254
+ rescue StandardError => error
255
+ raise BranchingError, "Invalid branch catalog #{branches_file}: #{error.message}"
256
+ end
257
+ end
258
+
259
+ def save_branches
260
+ data = {
261
+ branches: @branches.values.map(&:to_hash),
262
+ current_branch: @current_branch&.name,
263
+ stats: {
264
+ branches_created: @stats[:branches_created],
265
+ total_commits: @stats[:total_commits]
266
+ },
267
+ updated_at: Time.now.iso8601
268
+ }
269
+
270
+ branches_file = File.join(@branch_dir, "branches.json")
271
+ temp_file = "#{branches_file}.tmp"
272
+ File.write(temp_file, JSON.generate(data))
273
+ File.rename(temp_file, branches_file)
274
+ end
275
+
276
+ def delete_branch_data(name)
277
+ # Delete branch data files
278
+ branch_dir = File.join(@branch_dir, name)
279
+ FileUtils.rm_rf(branch_dir) if Dir.exist?(branch_dir)
280
+ end
281
+
282
+ def apply_branch_state(name)
283
+ branch = @branches.fetch(name)
284
+ return false unless @engine.respond_to?(:apply_branch_state)
285
+
286
+ snapshot = branch.respond_to?(:state_snapshot) ? branch.state_snapshot : nil
287
+ changes = branch.respond_to?(:logical_changes) ? branch.logical_changes : branch.changes
288
+ @engine.apply_branch_state(base: snapshot, changes: changes)
289
+ end
290
+
291
+ def create_default_branch
292
+ branch = Branch.new("main",
293
+ default: true,
294
+ description: "Default branch",
295
+ protected: true,
296
+ state_snapshot: (@engine.export_state if @engine.respond_to?(:export_state))
297
+ )
298
+ @branches["main"] = branch
299
+ @current_branch = branch
300
+ @stats[:branches_created] += 1
301
+ @stats[:active_branches] = 1
302
+
303
+ save_branches
304
+ end
305
+
306
+ def generate_commit_id
307
+ "commit_#{Time.now.to_i}_#{SecureRandom.hex(8)}"
308
+ end
309
+ end
310
+ end
311
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "time"
5
+
6
+ module RubyDB
7
+ module Branching
8
+ # BranchMetadata - Manages branch metadata
9
+ class BranchMetadata
10
+ attr_reader :stats
11
+
12
+ def initialize(config = {})
13
+ @config = config
14
+ @metadata_dir = config[:metadata_dir] || "branch_metadata"
15
+ @metadata = {}
16
+ @stats = {
17
+ metadata_entries: 0,
18
+ metadata_updated: 0,
19
+ metadata_deleted: 0
20
+ }
21
+ @lock = Mutex.new
22
+
23
+ FileUtils.mkdir_p(@metadata_dir)
24
+ load_metadata
25
+ end
26
+
27
+ def set_metadata(branch_name, key, value)
28
+ @lock.synchronize do
29
+ @metadata[branch_name] ||= {}
30
+ @metadata[branch_name][key] = {
31
+ value: value,
32
+ updated_at: Time.now.iso8601
33
+ }
34
+ @stats[:metadata_updated] += 1
35
+ save_metadata
36
+ end
37
+ end
38
+
39
+ def get_metadata(branch_name, key)
40
+ @lock.synchronize do
41
+ return nil unless @metadata[branch_name]
42
+ @metadata[branch_name][key]&.[](:value)
43
+ end
44
+ end
45
+
46
+ def get_all_metadata(branch_name)
47
+ @lock.synchronize do
48
+ return {} unless @metadata[branch_name]
49
+ @metadata[branch_name].transform_values { |v| v[:value] }
50
+ end
51
+ end
52
+
53
+ def delete_metadata(branch_name, key)
54
+ @lock.synchronize do
55
+ return false unless @metadata[branch_name]
56
+
57
+ if @metadata[branch_name].delete(key)
58
+ @stats[:metadata_deleted] += 1
59
+ save_metadata
60
+ true
61
+ else
62
+ false
63
+ end
64
+ end
65
+ end
66
+
67
+ def list_branches_with_metadata
68
+ @lock.synchronize do
69
+ @metadata.keys
70
+ end
71
+ end
72
+
73
+ def get_metadata_for_branch(branch_name)
74
+ @lock.synchronize do
75
+ @metadata[branch_name] || {}
76
+ end
77
+ end
78
+
79
+ def find_by_metadata(key, value)
80
+ @lock.synchronize do
81
+ results = []
82
+ @metadata.each do |branch_name, meta|
83
+ if meta[key] && meta[key][:value] == value
84
+ results << branch_name
85
+ end
86
+ end
87
+ results
88
+ end
89
+ end
90
+
91
+ def branch_metadata_count(branch_name)
92
+ @lock.synchronize do
93
+ @metadata[branch_name]&.size || 0
94
+ end
95
+ end
96
+
97
+ def stats
98
+ @lock.synchronize do
99
+ @stats.merge({
100
+ metadata_dir: @metadata_dir,
101
+ branches: @metadata.size,
102
+ total_entries: @metadata.values.sum { |m| m.size }
103
+ })
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ def load_metadata
110
+ metadata_file = File.join(@metadata_dir, "metadata.json")
111
+ return unless File.exist?(metadata_file)
112
+
113
+ begin
114
+ data = JSON.parse(File.read(metadata_file), symbolize_names: true)
115
+ @metadata = data[:metadata] || {}
116
+ @stats[:metadata_entries] = data[:stats][:metadata_entries] || 0
117
+ rescue
118
+ @metadata = {}
119
+ end
120
+ end
121
+
122
+ def save_metadata
123
+ data = {
124
+ metadata: @metadata,
125
+ stats: {
126
+ metadata_entries: @stats[:metadata_entries],
127
+ metadata_updated: @stats[:metadata_updated],
128
+ metadata_deleted: @stats[:metadata_deleted]
129
+ },
130
+ updated_at: Time.now.iso8601
131
+ }
132
+
133
+ metadata_file = File.join(@metadata_dir, "metadata.json")
134
+ temp_file = "#{metadata_file}.tmp"
135
+ File.write(temp_file, JSON.generate(data))
136
+ File.rename(temp_file, metadata_file)
137
+ end
138
+ end
139
+ end
140
+ end