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,308 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thread"
4
+ require "time"
5
+ require "monitor"
6
+
7
+ module RubyDB
8
+ module Replication
9
+ # ReplicationManager - Manages replication setup and coordination
10
+ class ReplicationManager
11
+ attr_reader :config, :primary, :replica, :stats, :mode
12
+
13
+ # Replication modes
14
+ MODE_OFF = :off
15
+ MODE_PRIMARY = :primary
16
+ MODE_REPLICA = :replica
17
+ MODE_STANDBY = :standby
18
+
19
+ def initialize(engine, config = {})
20
+ @engine = engine
21
+ @config = config
22
+ @mode = config[:mode] || MODE_OFF
23
+ @cluster_name = config[:cluster_name] || "rubydb_cluster"
24
+ @node_id = config[:node_id] || generate_node_id
25
+ @primary_host = config[:primary_host]
26
+ @primary_port = config[:primary_port] || 7433
27
+ @replication_port = config[:replication_port] || 7434
28
+
29
+ @primary = nil
30
+ @replica = nil
31
+ @replication_slots = {}
32
+ @health_checks = []
33
+ @stats = {
34
+ mode: @mode,
35
+ node_id: @node_id,
36
+ cluster_name: @cluster_name,
37
+ healthy: true,
38
+ last_health_check: nil,
39
+ failovers: 0,
40
+ switches: 0,
41
+ errors: 0
42
+ }
43
+ @lock = Monitor.new
44
+ @running = false
45
+ @health_thread = nil
46
+ @health_check_interval = config[:health_check_interval] || 10
47
+
48
+ # Initialize based on mode
49
+ initialize_mode
50
+ end
51
+
52
+ def start
53
+ @lock.synchronize do
54
+ return if @running
55
+
56
+ @running = true
57
+
58
+ case @mode
59
+ when MODE_PRIMARY
60
+ @primary&.start
61
+ when MODE_REPLICA
62
+ @replica&.start
63
+ end
64
+
65
+ # Start health check
66
+ start_health_check
67
+
68
+ puts "Replication manager started in #{@mode} mode"
69
+ true
70
+ end
71
+ end
72
+
73
+ def stop
74
+ @lock.synchronize do
75
+ return unless @running
76
+
77
+ @running = false
78
+
79
+ @health_thread&.kill
80
+ @health_thread = nil
81
+
82
+ @primary&.stop
83
+ @replica&.stop
84
+
85
+ puts "Replication manager stopped"
86
+ true
87
+ end
88
+ end
89
+
90
+ def switch_mode(new_mode, config = {})
91
+ @lock.synchronize do
92
+ old_mode = @mode
93
+
94
+ # Stop current mode
95
+ stop
96
+
97
+ # Update config
98
+ @mode = new_mode
99
+ @config = @config.merge(config)
100
+
101
+ # Initialize new mode
102
+ initialize_mode
103
+
104
+ # Start new mode
105
+ start
106
+
107
+ @stats[:switches] += 1
108
+
109
+ { old_mode: old_mode, new_mode: new_mode }
110
+ end
111
+ end
112
+
113
+ def promote_to_primary(recovery_point: nil)
114
+ @lock.synchronize do
115
+ if @mode != MODE_REPLICA
116
+ return { success: false, error: "Not in replica mode" }
117
+ end
118
+
119
+ status = @replica&.replication_status
120
+ promotable_state = status && [
121
+ Replica::STATE_STREAMING,
122
+ Replica::STATE_SYNCED,
123
+ Replica::STATE_DISCONNECTED,
124
+ Replica::STATE_CONNECTING
125
+ ].include?(status[:state])
126
+ caught_up_after_disconnect = status &&
127
+ status[:last_replayed_lsn] &&
128
+ status[:last_received_lsn] == status[:last_replayed_lsn]
129
+ unless promotable_state && (status[:state] == Replica::STATE_STREAMING ||
130
+ status[:state] == Replica::STATE_SYNCED ||
131
+ caught_up_after_disconnect)
132
+ return { success: false, error: "Replica is not synchronized enough for manual promotion" }
133
+ end
134
+
135
+ if status[:last_received_lsn] != status[:last_replayed_lsn]
136
+ return {
137
+ success: false,
138
+ error: "Replica has unapplied replication data; promotion requires a caught-up recovery point"
139
+ }
140
+ end
141
+
142
+ if recovery_point && status[:last_replayed_lsn].to_i < recovery_point.to_i
143
+ return { success: false, error: "Replica has not reached recovery point #{recovery_point}" }
144
+ end
145
+
146
+ # Promote replica to primary. Automatic promotion is intentionally
147
+ # not attempted; callers must explicitly invoke this operation.
148
+ begin
149
+ @replica&.promote_to_primary(recovery_point: recovery_point)
150
+ rescue ReplicationError => e
151
+ return { success: false, error: e.message }
152
+ end
153
+
154
+ # Switch mode
155
+ @mode = MODE_PRIMARY
156
+
157
+ # Create new primary
158
+ @primary = Primary.new(@engine, @config)
159
+ @primary.start
160
+
161
+ @replica = nil
162
+ @stats[:failovers] += 1
163
+
164
+ { success: true, message: "Promoted to primary" }
165
+ end
166
+ end
167
+
168
+ def demote_to_replica(primary_host, primary_port = nil)
169
+ @lock.synchronize do
170
+ if @mode != MODE_PRIMARY
171
+ return { success: false, error: "Not in primary mode" }
172
+ end
173
+
174
+ # Stop primary
175
+ @primary&.stop
176
+
177
+ # Update config
178
+ @config[:primary_host] = primary_host
179
+ @config[:primary_port] = primary_port if primary_port
180
+ @mode = MODE_REPLICA
181
+
182
+ # Create new replica
183
+ @replica = Replica.new(@engine, @config)
184
+ @replica.start
185
+
186
+ @primary = nil
187
+ @stats[:switches] += 1
188
+
189
+ { success: true, message: "Demoted to replica" }
190
+ end
191
+ end
192
+
193
+ def health_check
194
+ @lock.synchronize do
195
+ @stats[:last_health_check] = Time.now
196
+
197
+ result = case @mode
198
+ when MODE_PRIMARY
199
+ check_primary_health
200
+ when MODE_REPLICA
201
+ check_replica_health
202
+ else
203
+ { healthy: true, message: "Not in active replication mode" }
204
+ end
205
+
206
+ @stats[:healthy] = result[:healthy]
207
+ @health_checks << result
208
+
209
+ # Trim health checks
210
+ if @health_checks.size > 100
211
+ @health_checks.shift
212
+ end
213
+
214
+ result
215
+ end
216
+ end
217
+
218
+ def get_replication_status
219
+ @lock.synchronize do
220
+ status = {
221
+ mode: @mode,
222
+ node_id: @node_id,
223
+ cluster_name: @cluster_name,
224
+ running: @running,
225
+ healthy: @stats[:healthy],
226
+ last_health_check: @stats[:last_health_check]&.iso8601
227
+ }
228
+
229
+ case @mode
230
+ when MODE_PRIMARY
231
+ status[:primary] = @primary&.get_replication_status
232
+ when MODE_REPLICA
233
+ status[:replica] = @replica&.replication_status
234
+ end
235
+
236
+ status
237
+ end
238
+ end
239
+
240
+ def stats
241
+ @lock.synchronize do
242
+ @stats.merge({
243
+ running: @running,
244
+ health_checks: @health_checks.size,
245
+ mode: @mode,
246
+ node_id: @node_id
247
+ })
248
+ end
249
+ end
250
+
251
+ private
252
+
253
+ def generate_node_id
254
+ "node_#{Time.now.to_i}_#{rand(10000)}"
255
+ end
256
+
257
+ def initialize_mode
258
+ case @mode
259
+ when MODE_PRIMARY
260
+ @primary = Primary.new(@engine, @config)
261
+ when MODE_REPLICA
262
+ unless @config[:primary_host]
263
+ raise ConfigError, "Primary host required for replica mode"
264
+ end
265
+ @replica = Replica.new(@engine, @config)
266
+ when MODE_OFF, MODE_STANDBY
267
+ # No replication
268
+ else
269
+ @mode = MODE_OFF
270
+ end
271
+ end
272
+
273
+ def start_health_check
274
+ @health_thread = Thread.new do
275
+ while @running
276
+ sleep(@health_check_interval)
277
+ begin
278
+ health_check
279
+ rescue => e
280
+ @stats[:errors] += 1
281
+ end
282
+ end
283
+ end
284
+ end
285
+
286
+ def check_primary_health
287
+ if @primary&.running?
288
+ { healthy: true, message: "Primary is running" }
289
+ else
290
+ { healthy: false, message: "Primary is not running" }
291
+ end
292
+ end
293
+
294
+ def check_replica_health
295
+ if @replica&.running?
296
+ status = @replica.replication_status
297
+ if status[:state] == :failed
298
+ { healthy: false, message: "Replica in failed state" }
299
+ else
300
+ { healthy: true, message: "Replica is running" }
301
+ end
302
+ else
303
+ { healthy: false, message: "Replica is not running" }
304
+ end
305
+ end
306
+ end
307
+ end
308
+ end
@@ -0,0 +1,295 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+ require "set"
5
+
6
+ module RubyDB
7
+ module Replication
8
+ # Durable slot state is represented by the primary while the replication
9
+ # catalog is still file-backed. A slot retains the last acknowledged LSN
10
+ # and prevents accidental reuse of a replica identity.
11
+ class ReplicationSlot
12
+ attr_reader :name, :primary
13
+ attr_accessor :confirmed_lsn
14
+
15
+ def initialize(name, primary)
16
+ @name = name.to_s
17
+ @primary = primary
18
+ @confirmed_lsn = 0
19
+ end
20
+
21
+ def advance(lsn)
22
+ @confirmed_lsn = [@confirmed_lsn, lsn.to_i].max
23
+ end
24
+ end
25
+
26
+ # Failover - Handles automatic failover
27
+ class Failover
28
+ attr_reader :stats
29
+
30
+ # Failover states
31
+ STATE_NORMAL = :normal
32
+ STATE_HEALTH_CHECK = :health_check
33
+ STATE_DETECTED = :detected
34
+ STATE_FAILING_OVER = :failing_over
35
+ STATE_PROMOTING = :promoting
36
+ STATE_VERIFYING = :verifying
37
+ STATE_COMPLETE = :complete
38
+ STATE_FAILED = :failed
39
+
40
+ def initialize(replication_manager, config = {})
41
+ @replication_manager = replication_manager
42
+ @config = config
43
+ @state = STATE_NORMAL
44
+ @failover_triggered = false
45
+ @failover_start_time = nil
46
+ @candidate_replicas = []
47
+ @chosen_candidate = nil
48
+ @health_check_history = []
49
+ @stats = {
50
+ failovers: 0,
51
+ successful_failovers: 0,
52
+ failed_failovers: 0,
53
+ total_failover_time_ms: 0,
54
+ avg_failover_time_ms: 0,
55
+ last_failover_time: nil,
56
+ last_failover_result: nil,
57
+ health_check_failures: 0,
58
+ consecutive_failures: 0
59
+ }
60
+ @lock = Mutex.new
61
+ @running = false
62
+ @failover_thread = nil
63
+ @failover_timeout = config[:timeout] || 30
64
+ @check_interval = config[:check_interval] || 5
65
+ @max_consecutive_failures = config[:max_consecutive_failures] || 3
66
+ @failover_trigger = config[:trigger] || :auto
67
+ end
68
+
69
+ def start
70
+ @lock.synchronize do
71
+ return if @running
72
+
73
+ @running = true
74
+ @state = STATE_NORMAL
75
+ @failover_thread = Thread.new { failover_loop }
76
+
77
+ puts "Failover handler started"
78
+ true
79
+ end
80
+ end
81
+
82
+ def stop
83
+ @lock.synchronize do
84
+ return unless @running
85
+
86
+ @running = false
87
+
88
+ @failover_thread&.kill
89
+ @failover_thread = nil
90
+
91
+ puts "Failover handler stopped"
92
+ true
93
+ end
94
+ end
95
+
96
+ def trigger_failover(reason = "manual trigger")
97
+ @lock.synchronize do
98
+ return false if @failover_triggered
99
+
100
+ @failover_triggered = true
101
+ @failover_start_time = Time.now
102
+ @stats[:failovers] += 1
103
+
104
+ Thread.new do
105
+ execute_failover(reason)
106
+ end
107
+
108
+ true
109
+ end
110
+ end
111
+
112
+ def health_check
113
+ trigger_reason = nil
114
+ result = @lock.synchronize do
115
+ result = @replication_manager.health_check
116
+
117
+ @health_check_history << {
118
+ timestamp: Time.now,
119
+ result: result
120
+ }
121
+
122
+ if @health_check_history.size > 100
123
+ @health_check_history.shift
124
+ end
125
+
126
+ if result[:healthy] == false
127
+ @stats[:health_check_failures] += 1
128
+ @stats[:consecutive_failures] += 1
129
+
130
+ if @stats[:consecutive_failures] >= @max_consecutive_failures &&
131
+ @failover_trigger == :auto &&
132
+ !@failover_triggered
133
+ trigger_reason = "health check failures"
134
+ end
135
+ else
136
+ @stats[:consecutive_failures] = 0
137
+ end
138
+
139
+ result
140
+ end
141
+ trigger_failover(trigger_reason) if trigger_reason
142
+ result
143
+ end
144
+
145
+ def stats
146
+ @lock.synchronize do
147
+ @stats.merge({
148
+ state: @state,
149
+ running: @running,
150
+ failover_triggered: @failover_triggered,
151
+ consecutive_failures: @stats[:consecutive_failures],
152
+ health_check_history_size: @health_check_history.size
153
+ })
154
+ end
155
+ end
156
+
157
+ private
158
+
159
+ def failover_loop
160
+ while @running
161
+ sleep(@check_interval)
162
+ begin
163
+ health_check
164
+ rescue => e
165
+ # Log error but continue
166
+ end
167
+ end
168
+ end
169
+
170
+ def execute_failover(reason)
171
+ @lock.synchronize do
172
+ @state = STATE_DETECTED
173
+ start_time = Time.now
174
+
175
+ begin
176
+ # Step 1: Find candidate replicas
177
+ @state = STATE_HEALTH_CHECK
178
+ candidates = find_candidate_replicas
179
+
180
+ if candidates.empty?
181
+ @state = STATE_FAILED
182
+ @stats[:failed_failovers] += 1
183
+ return failover_result(false, "No candidate replicas found")
184
+ end
185
+
186
+ # Step 2: Choose best candidate
187
+ @state = STATE_PROMOTING
188
+ @chosen_candidate = choose_best_candidate(candidates)
189
+
190
+ unless @chosen_candidate
191
+ @state = STATE_FAILED
192
+ @stats[:failed_failovers] += 1
193
+ return failover_result(false, "No suitable candidate found")
194
+ end
195
+
196
+ # Step 3: Promote candidate
197
+ @state = STATE_FAILING_OVER
198
+ promote_result = promote_candidate(@chosen_candidate)
199
+
200
+ unless promote_result[:success]
201
+ @state = STATE_FAILED
202
+ @stats[:failed_failovers] += 1
203
+ return failover_result(false, "Promotion failed: #{promote_result[:error]}")
204
+ end
205
+
206
+ # Step 4: Verify promotion
207
+ @state = STATE_VERIFYING
208
+ verify_result = verify_promotion(@chosen_candidate)
209
+
210
+ unless verify_result[:success]
211
+ @state = STATE_FAILED
212
+ @stats[:failed_failovers] += 1
213
+ return failover_result(false, "Verification failed: #{verify_result[:error]}")
214
+ end
215
+
216
+ # Step 5: Complete failover
217
+ @state = STATE_COMPLETE
218
+ @stats[:successful_failovers] += 1
219
+
220
+ elapsed_ms = (Time.now - start_time) * 1000
221
+ @stats[:total_failover_time_ms] += elapsed_ms
222
+ @stats[:avg_failover_time_ms] = @stats[:total_failover_time_ms] / @stats[:successful_failovers]
223
+ @stats[:last_failover_time] = Time.now
224
+
225
+ failover_result(true, "Failover completed successfully", {
226
+ candidate: @chosen_candidate[:node_id],
227
+ elapsed_ms: elapsed_ms,
228
+ reason: reason
229
+ })
230
+
231
+ rescue => e
232
+ @state = STATE_FAILED
233
+ @stats[:failed_failovers] += 1
234
+ failover_result(false, "Failover failed: #{e.message}")
235
+ ensure
236
+ @failover_triggered = false
237
+ @chosen_candidate = nil
238
+ end
239
+ end
240
+ end
241
+
242
+ def find_candidate_replicas
243
+ candidates = []
244
+
245
+ replica = @replication_manager.replica
246
+ status = replica&.replication_status
247
+ if replica&.running? && status && status[:state] == Replica::STATE_SYNCED
248
+ candidates << {
249
+ node_id: @replication_manager.replica.config[:node_id] || "replica_1",
250
+ host: replica.primary_host,
251
+ port: replica.primary_port,
252
+ status: status[:state].to_s,
253
+ lag_ms: status[:lag_ms].to_i,
254
+ replayed_lsn: status[:last_replayed_lsn].to_i
255
+ }
256
+ end
257
+
258
+ candidates
259
+ end
260
+
261
+ def choose_best_candidate(candidates)
262
+ candidates.min_by { |c| [c[:lag_ms] || 0, -(c[:replayed_lsn] || 0)] }
263
+ end
264
+
265
+ def promote_candidate(candidate)
266
+ result = @replication_manager.promote_to_primary
267
+ return { success: false, error: "Promotion manager returned no result" } unless result.is_a?(Hash)
268
+
269
+ result[:success] ? result : { success: false, error: result[:error] || "Promotion failed" }
270
+ end
271
+
272
+ def verify_promotion(candidate)
273
+ mode = @replication_manager.respond_to?(:mode) ? @replication_manager.mode : nil
274
+ return { success: false, error: "Promotion did not enter primary mode" } unless mode == ReplicationManager::MODE_PRIMARY
275
+
276
+ health = @replication_manager.health_check
277
+ health[:healthy] ? { success: true, health: health } : { success: false, error: "Promoted node is unhealthy" }
278
+ end
279
+
280
+ def failover_result(success, message, extra = {})
281
+ started_at = @failover_start_time || Time.now
282
+ result = {
283
+ success: success,
284
+ message: message,
285
+ timestamp: Time.now.iso8601,
286
+ elapsed_ms: (Time.now - started_at) * 1000,
287
+ state: @state
288
+ }.merge(extra)
289
+
290
+ @stats[:last_failover_result] = result
291
+ result
292
+ end
293
+ end
294
+ end
295
+ end