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,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module Branching
5
+ # Checkout - Handles branch checkout operations
6
+ class Checkout
7
+ attr_reader :stats
8
+
9
+ def initialize(engine, branch_manager, config = {})
10
+ @engine = engine
11
+ @branch_manager = branch_manager
12
+ @config = config
13
+ @checkout_history = []
14
+ @stats = {
15
+ checkouts: 0,
16
+ successful_checkouts: 0,
17
+ failed_checkouts: 0,
18
+ checkout_time_ms: 0,
19
+ avg_checkout_time_ms: 0,
20
+ last_checkout: nil
21
+ }
22
+ @lock = Mutex.new
23
+ @max_history = config[:max_history] || 100
24
+ end
25
+
26
+ def checkout(branch_name, options = {})
27
+ @lock.synchronize do
28
+ start_time = Time.now
29
+ @stats[:checkouts] += 1
30
+
31
+ # Validate branch exists
32
+ branch = @branch_manager.get_branch(branch_name)
33
+ unless branch
34
+ @stats[:failed_checkouts] += 1
35
+ return { success: false, error: "Branch '#{branch_name}' not found" }
36
+ end
37
+
38
+ # Check if branch can be checked out
39
+ if branch.locked? && !options[:force]
40
+ @stats[:failed_checkouts] += 1
41
+ return { success: false, error: "Branch '#{branch_name}' is locked" }
42
+ end
43
+
44
+ begin
45
+ # Perform checkout
46
+ result = perform_checkout(branch, options)
47
+
48
+ if result[:success]
49
+ # Update branch manager
50
+ @branch_manager.checkout(branch_name)
51
+
52
+ # Record history
53
+ record_checkout(branch_name, options)
54
+
55
+ @stats[:successful_checkouts] += 1
56
+ @stats[:last_checkout] = Time.now
57
+
58
+ elapsed_ms = (Time.now - start_time) * 1000
59
+ @stats[:checkout_time_ms] += elapsed_ms
60
+ @stats[:avg_checkout_time_ms] = @stats[:checkout_time_ms] / @stats[:successful_checkouts]
61
+ else
62
+ @stats[:failed_checkouts] += 1
63
+ end
64
+
65
+ result.merge(elapsed_ms: elapsed_ms)
66
+
67
+ rescue => e
68
+ @stats[:failed_checkouts] += 1
69
+ { success: false, error: e.message }
70
+ end
71
+ end
72
+ end
73
+
74
+ def checkout_or_create(branch_name, options = {})
75
+ @lock.synchronize do
76
+ branch = @branch_manager.get_branch(branch_name)
77
+
78
+ if branch
79
+ checkout(branch_name, options)
80
+ else
81
+ # Create branch then checkout
82
+ result = @branch_manager.create_branch(branch_name, options)
83
+ if result[:success]
84
+ checkout(branch_name, options)
85
+ else
86
+ result
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ def checkout_latest(options = {})
93
+ @lock.synchronize do
94
+ # Find latest branch by creation time
95
+ branches = @branch_manager.list_branches
96
+ return { success: false, error: "No branches available" } if branches.empty?
97
+
98
+ latest = branches.max_by { |b| b[:created_at] }
99
+ checkout(latest[:name], options)
100
+ end
101
+ end
102
+
103
+ def checkout_history
104
+ @checkout_history.dup
105
+ end
106
+
107
+ def stats
108
+ @lock.synchronize do
109
+ @stats.merge({
110
+ history_size: @checkout_history.size,
111
+ max_history: @max_history
112
+ })
113
+ end
114
+ end
115
+
116
+ private
117
+
118
+ def perform_checkout(branch, options)
119
+ branch_name = branch.name
120
+
121
+ # Get branch data
122
+ branch_data = get_branch_data(branch_name)
123
+
124
+ # Apply branch state to engine
125
+ apply_branch_state(branch_data)
126
+
127
+ # Update engine to branch head
128
+ @engine.apply_branch_head(branch.head_lsn) if branch.head_lsn
129
+
130
+ { success: true, branch_name: branch_name, head_lsn: branch.head_lsn }
131
+ end
132
+
133
+ def get_branch_data(branch_name)
134
+ branch = @branch_manager.get_branch(branch_name)
135
+ raise ArgumentError, "Branch '#{branch_name}' not found" unless branch
136
+ snapshot = branch.respond_to?(:state_snapshot) ? branch.state_snapshot : nil
137
+ changes = branch.respond_to?(:logical_changes) ? branch.logical_changes : branch.changes
138
+ { base: snapshot, changes: changes }
139
+ end
140
+
141
+ def apply_branch_state(branch_data)
142
+ unless @engine.respond_to?(:apply_branch_state)
143
+ raise ArgumentError, "Branch checkout requires an engine state-application hook"
144
+ end
145
+
146
+ result = @engine.apply_branch_state(branch_data)
147
+ raise ArgumentError, "Engine rejected branch state" if result == false
148
+ result
149
+ end
150
+
151
+ def record_checkout(branch_name, options)
152
+ entry = {
153
+ branch_name: branch_name,
154
+ timestamp: Time.now.iso8601,
155
+ options: options
156
+ }
157
+
158
+ @checkout_history << entry
159
+
160
+ if @checkout_history.size > @max_history
161
+ @checkout_history.shift
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,272 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "json"
5
+ require "pathname"
6
+
7
+ module RubyDB
8
+ module Branching
9
+ # CopyOnWrite - Manages copy-on-write for branches
10
+ class CopyOnWrite
11
+ attr_reader :stats
12
+
13
+ def initialize(engine, config = {})
14
+ @engine = engine
15
+ @config = config
16
+ @cow_dir = config[:cow_dir] || "cow"
17
+ @branch_data = {}
18
+ @stats = {
19
+ cow_files_created: 0,
20
+ cow_files_accessed: 0,
21
+ cow_files_updated: 0,
22
+ cow_files_deleted: 0,
23
+ total_cow_size: 0
24
+ }
25
+ @lock = Mutex.new
26
+
27
+ FileUtils.mkdir_p(@cow_dir)
28
+ load_cow_data
29
+ end
30
+
31
+ def create_cow(branch_name, base_path)
32
+ @lock.synchronize do
33
+ cow_path = branch_path(branch_name)
34
+ return { success: false, error: "Invalid branch name" } unless cow_path
35
+ base_root = File.expand_path(base_path)
36
+ return { success: false, error: "Base path does not exist" } unless Dir.exist?(base_root)
37
+ FileUtils.mkdir_p(cow_path)
38
+
39
+ # Create COW for each file
40
+ files = Dir.glob(File.join(base_root, "**/*")).select { |file| File.file?(file) }
41
+ files.each do |file|
42
+ relative_path = Pathname.new(file).relative_path_from(Pathname.new(base_root)).to_s
43
+ cow_file = nested_path(cow_path, relative_path)
44
+ raise "Invalid source path for copy-on-write" unless cow_file
45
+ FileUtils.mkdir_p(File.dirname(cow_file))
46
+
47
+ # Create hard link or copy
48
+ begin
49
+ File.link(file, cow_file)
50
+ rescue
51
+ FileUtils.cp(file, cow_file)
52
+ end
53
+
54
+ @stats[:cow_files_created] += 1
55
+ @stats[:total_cow_size] += File.size(cow_file)
56
+ end
57
+
58
+ @branch_data[branch_name] = {
59
+ base_path: base_root,
60
+ cow_path: cow_path,
61
+ created_at: Time.now.iso8601,
62
+ files: files.map { |file| Pathname.new(file).relative_path_from(Pathname.new(base_root)).to_s }
63
+ }
64
+
65
+ save_cow_data
66
+
67
+ { success: true, cow_path: cow_path, file_count: files.size }
68
+ end
69
+ end
70
+
71
+ def read_file(branch_name, file_path)
72
+ @lock.synchronize do
73
+ cow_path = branch_path(branch_name)
74
+ cow_file = cow_path && nested_path(cow_path, file_path)
75
+ return nil unless cow_file
76
+
77
+ if File.exist?(cow_file)
78
+ @stats[:cow_files_accessed] += 1
79
+ return File.read(cow_file)
80
+ end
81
+
82
+ # Read from base
83
+ branch_data = @branch_data[branch_name]
84
+ return nil unless branch_data
85
+
86
+ base_file = nested_path(branch_data[:base_path], file_path)
87
+ return nil unless File.exist?(base_file)
88
+
89
+ File.read(base_file)
90
+ end
91
+ end
92
+
93
+ def write_file(branch_name, file_path, content)
94
+ @lock.synchronize do
95
+ cow_path = branch_path(branch_name)
96
+ cow_file = cow_path && nested_path(cow_path, file_path)
97
+ return { success: false, error: "Invalid branch name or file path" } unless cow_file
98
+ FileUtils.mkdir_p(File.dirname(cow_file))
99
+
100
+ File.write(cow_file, content)
101
+ @stats[:cow_files_updated] += 1
102
+
103
+ # Update branch data
104
+ @branch_data[branch_name] ||= { files: [] }
105
+ unless @branch_data[branch_name][:files].include?(file_path)
106
+ @branch_data[branch_name][:files] << file_path
107
+ end
108
+
109
+ save_cow_data
110
+
111
+ { success: true, path: cow_file }
112
+ end
113
+ end
114
+
115
+ def delete_file(branch_name, file_path)
116
+ @lock.synchronize do
117
+ cow_path = branch_path(branch_name)
118
+ cow_file = cow_path && nested_path(cow_path, file_path)
119
+ return { success: false, error: "Invalid branch name or file path" } unless cow_file
120
+
121
+ if File.exist?(cow_file)
122
+ File.delete(cow_file)
123
+ @stats[:cow_files_deleted] += 1
124
+
125
+ # Update branch data
126
+ if @branch_data[branch_name]
127
+ @branch_data[branch_name][:files].delete(file_path)
128
+ end
129
+
130
+ save_cow_data
131
+ return { success: true }
132
+ end
133
+
134
+ { success: false, error: "File not found in COW" }
135
+ end
136
+ end
137
+
138
+ def list_cow_files(branch_name)
139
+ @lock.synchronize do
140
+ cow_path = branch_path(branch_name)
141
+ return [] unless cow_path
142
+ return [] unless Dir.exist?(cow_path)
143
+
144
+ Dir.glob(File.join(cow_path, "**/*")).select do |f|
145
+ File.file?(f)
146
+ end.map { |f| f.sub(cow_path + "/", "") }
147
+ end
148
+ end
149
+
150
+ def delete_branch_cow(branch_name)
151
+ @lock.synchronize do
152
+ cow_path = branch_path(branch_name)
153
+ return { success: false, error: "Invalid branch name" } unless cow_path
154
+ if Dir.exist?(cow_path)
155
+ size = calculate_dir_size(cow_path)
156
+ FileUtils.rm_rf(cow_path)
157
+ @stats[:total_cow_size] -= size
158
+ @stats[:cow_files_deleted] += 1
159
+ end
160
+
161
+ @branch_data.delete(branch_name)
162
+ save_cow_data
163
+
164
+ { success: true }
165
+ end
166
+ end
167
+
168
+ def snapshot(branch_name, snapshot_name)
169
+ @lock.synchronize do
170
+ cow_path = branch_path(branch_name)
171
+ return { success: false, error: "Invalid branch name" } unless cow_path
172
+ return { success: false, error: "Branch COW not found" } unless Dir.exist?(cow_path)
173
+
174
+ snapshot_path = snapshot_path(snapshot_name)
175
+ return { success: false, error: "Invalid snapshot name" } unless snapshot_path
176
+ FileUtils.mkdir_p(File.dirname(snapshot_path))
177
+
178
+ # Copy all COW files
179
+ FileUtils.cp_r(cow_path, snapshot_path)
180
+
181
+ { success: true, snapshot_path: snapshot_path }
182
+ end
183
+ end
184
+
185
+ def restore_snapshot(snapshot_name, branch_name)
186
+ @lock.synchronize do
187
+ snapshot_path = snapshot_path(snapshot_name)
188
+ return { success: false, error: "Invalid snapshot name" } unless snapshot_path
189
+ return { success: false, error: "Snapshot not found" } unless Dir.exist?(snapshot_path)
190
+
191
+ cow_path = branch_path(branch_name)
192
+ return { success: false, error: "Invalid branch name" } unless cow_path
193
+ FileUtils.rm_rf(cow_path) if Dir.exist?(cow_path)
194
+ FileUtils.cp_r(snapshot_path, cow_path)
195
+
196
+ { success: true }
197
+ end
198
+ end
199
+
200
+ def stats
201
+ @lock.synchronize do
202
+ @stats.merge({
203
+ branches: @branch_data.size,
204
+ cow_dir: @cow_dir,
205
+ snapshots: Dir.glob(File.join(@cow_dir, "snapshots", "*")).size
206
+ })
207
+ end
208
+ end
209
+
210
+ private
211
+
212
+ def branch_path(branch_name)
213
+ name = branch_name.to_s
214
+ return nil unless safe_name?(name)
215
+
216
+ nested_path(@cow_dir, name)
217
+ end
218
+
219
+ def snapshot_path(snapshot_name)
220
+ name = snapshot_name.to_s
221
+ return nil unless safe_name?(name)
222
+
223
+ nested_path(File.join(@cow_dir, "snapshots"), name)
224
+ end
225
+
226
+ def safe_name?(name)
227
+ !name.empty? && name == File.basename(name) && !Pathname.new(name).absolute?
228
+ end
229
+
230
+ def nested_path(root_path, relative_path)
231
+ relative = relative_path.to_s
232
+ return nil if relative.empty? || Pathname.new(relative).absolute?
233
+
234
+ root = File.expand_path(root_path)
235
+ path = File.expand_path(File.join(root, relative))
236
+ path.start_with?("#{root}#{File::SEPARATOR}") ? path : nil
237
+ end
238
+
239
+ def load_cow_data
240
+ data_file = File.join(@cow_dir, "cow_data.json")
241
+ return unless File.exist?(data_file)
242
+
243
+ begin
244
+ data = JSON.parse(File.read(data_file), symbolize_names: true)
245
+ @branch_data = data[:branch_data] || {}
246
+ @stats = data[:stats] || @stats
247
+ rescue
248
+ @branch_data = {}
249
+ end
250
+ end
251
+
252
+ def save_cow_data
253
+ data = {
254
+ branch_data: @branch_data,
255
+ stats: @stats,
256
+ updated_at: Time.now.iso8601
257
+ }
258
+
259
+ data_file = File.join(@cow_dir, "cow_data.json")
260
+ temp_file = "#{data_file}.tmp"
261
+ File.write(temp_file, JSON.generate(data))
262
+ File.rename(temp_file, data_file)
263
+ end
264
+
265
+ def calculate_dir_size(path)
266
+ Dir.glob(File.join(path, "**/*")).sum do |f|
267
+ File.size(f) if File.file?(f)
268
+ end || 0
269
+ end
270
+ end
271
+ end
272
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module Branching
5
+ # Diff - Compares branches
6
+ class Diff
7
+ attr_reader :stats
8
+
9
+ # Diff types
10
+ DIFF_TABLE = :table
11
+ DIFF_SCHEMA = :schema
12
+ DIFF_DATA = :data
13
+ DIFF_ALL = :all
14
+
15
+ def initialize(engine, branch_manager, config = {})
16
+ @engine = engine
17
+ @branch_manager = branch_manager
18
+ @config = config
19
+ @stats = {
20
+ diffs: 0,
21
+ diff_time_ms: 0,
22
+ avg_diff_time_ms: 0,
23
+ tables_diffed: 0,
24
+ rows_diffed: 0
25
+ }
26
+ @lock = Mutex.new
27
+ @diff_cache = {}
28
+ end
29
+
30
+ def diff(branch_a, branch_b, options = {})
31
+ @lock.synchronize do
32
+ start_time = Time.now
33
+ @stats[:diffs] += 1
34
+
35
+ a = @branch_manager.get_branch(branch_a)
36
+ b = @branch_manager.get_branch(branch_b)
37
+
38
+ unless a
39
+ return { success: false, error: "Branch '#{branch_a}' not found" }
40
+ end
41
+
42
+ unless b
43
+ return { success: false, error: "Branch '#{branch_b}' not found" }
44
+ end
45
+
46
+ diff_type = options[:type] || DIFF_ALL
47
+ tables = options[:tables]
48
+
49
+ result = case diff_type
50
+ when DIFF_TABLE
51
+ diff_tables(a, b, tables)
52
+ when DIFF_SCHEMA
53
+ diff_schemas(a, b, tables)
54
+ when DIFF_DATA
55
+ diff_data(a, b, tables)
56
+ else
57
+ diff_all(a, b, tables)
58
+ end
59
+
60
+ elapsed_ms = (Time.now - start_time) * 1000
61
+ @stats[:diff_time_ms] += elapsed_ms
62
+ @stats[:avg_diff_time_ms] = @stats[:diff_time_ms] / @stats[:diffs]
63
+ @stats[:tables_diffed] += result[:tables]&.size || 0
64
+ @stats[:rows_diffed] += result[:rows]&.size || 0
65
+
66
+ result.merge(elapsed_ms: elapsed_ms)
67
+ end
68
+ end
69
+
70
+ def diff_tables(branch_a, branch_b, tables = nil)
71
+ compare_changes(branch_a, branch_b, tables, :tables)
72
+ end
73
+
74
+ def diff_schemas(branch_a, branch_b, tables = nil)
75
+ compare_changes(branch_a, branch_b, tables, :schemas)
76
+ end
77
+
78
+ def diff_data(branch_a, branch_b, tables = nil)
79
+ compare_changes(branch_a, branch_b, tables, :data)
80
+ end
81
+
82
+ def diff_all(branch_a, branch_b, tables = nil)
83
+ compare_changes(branch_a, branch_b, tables, :all)
84
+ end
85
+
86
+ def diff_table(branch_a, branch_b, table)
87
+ @lock.synchronize do
88
+ cache_key = "#{branch_a}_#{branch_b}_#{table}"
89
+ if @diff_cache.key?(cache_key)
90
+ return @diff_cache[cache_key]
91
+ end
92
+
93
+ result = perform_table_diff(branch_a, branch_b, table)
94
+ @diff_cache[cache_key] = result if result[:success]
95
+
96
+ result
97
+ end
98
+ end
99
+
100
+ def clear_cache
101
+ @lock.synchronize do
102
+ @diff_cache.clear
103
+ end
104
+ end
105
+
106
+ def stats
107
+ @lock.synchronize do
108
+ @stats.merge({
109
+ cache_size: @diff_cache.size
110
+ })
111
+ end
112
+ end
113
+
114
+ private
115
+
116
+ def perform_table_diff(branch_a, branch_b, table)
117
+ compare_changes(branch_a, branch_b, [table], :table).merge(table: table)
118
+ end
119
+
120
+ def compare_changes(branch_a, branch_b, tables, type)
121
+ a = branch_a.respond_to?(:logical_changes) ? branch_a : @branch_manager.get_branch(branch_a)
122
+ b = branch_b.respond_to?(:logical_changes) ? branch_b : @branch_manager.get_branch(branch_b)
123
+ return { success: false, error: "Branch not found: #{branch_a.inspect}, #{branch_b.inspect}" } unless a && b
124
+
125
+ filter = tables && Array(tables).map(&:to_s)
126
+ changes_a = a.respond_to?(:logical_changes) ? a.logical_changes : a.changes
127
+ changes_b = b.respond_to?(:logical_changes) ? b.logical_changes : b.changes
128
+ changes_a = changes_a.select { |change| filter.include?(change[:table].to_s) } if filter
129
+ changes_b = changes_b.select { |change| filter.include?(change[:table].to_s) } if filter
130
+ added = changes_b.reject { |change| changes_a.include?(change) }
131
+ removed = changes_a.reject { |change| changes_b.include?(change) }
132
+ { success: true, type: type, tables: (changes_a + changes_b).map { |c| c[:table] }.compact.uniq,
133
+ rows: { added: added, removed: removed }, changes: { added: added, removed: removed } }
134
+ end
135
+
136
+ end
137
+ end
138
+ end