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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The implementation lives with the consistency checker for backward
4
+ # compatibility. Keep this file as a valid standalone require target.
5
+ require_relative "consistency"
@@ -0,0 +1,387 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+ require "set"
5
+
6
+ module RubyDB
7
+ module Recovery
8
+ # CrashRecovery - Handles recovery from crashes
9
+ class CrashRecovery
10
+ attr_reader :stats, :last_recovery_time
11
+
12
+ def initialize(engine, wal, config = {})
13
+ @engine = engine
14
+ @wal = wal
15
+ @config = config
16
+ @last_recovery_time = nil
17
+ @recovery_log = []
18
+ @stats = {
19
+ recoveries: 0,
20
+ redo_records: 0,
21
+ undo_records: 0,
22
+ corrupted_records: 0,
23
+ skipped_records: 0,
24
+ recovery_time_ms: 0,
25
+ avg_recovery_time_ms: 0,
26
+ total_recovery_time_ms: 0
27
+ }
28
+ @lock = Mutex.new
29
+ @stop_on_error = config.fetch(:stop_on_error, true)
30
+ @max_errors = config.fetch(:max_errors, 10)
31
+ end
32
+
33
+ def recover
34
+ @lock.synchronize do
35
+ start_time = Time.now
36
+ @stats[:recoveries] += 1
37
+ @recovery_log.clear
38
+ errors = 0
39
+
40
+ begin
41
+ # Step 1: Find the latest checkpoint
42
+ checkpoint_lsn = find_latest_checkpoint
43
+ puts "RECOVERY DEBUG: checkpoint_lsn = #{checkpoint_lsn.inspect}" if ENV['DEBUG_RECOVERY']
44
+ @recovery_log << { step: "checkpoint", lsn: checkpoint_lsn }
45
+
46
+ # Step 2: Read records after checkpoint
47
+ records = if checkpoint_lsn
48
+ @wal.read_from(checkpoint_lsn)
49
+ else
50
+ @wal.read_all
51
+ end
52
+ puts "RECOVERY DEBUG: found #{records.count} WAL records" if ENV['DEBUG_RECOVERY']
53
+ records.each { |r| puts " - #{r.type}: #{r.data.inspect}" } if ENV['DEBUG_RECOVERY']
54
+ @recovery_log << { step: "read_records", count: records.size }
55
+
56
+ # Step 3: Analyze records for redo/undo
57
+ analysis = analyze_records(records)
58
+ puts "RECOVERY DEBUG: analysis redo=#{analysis[:redo].count}, undo=#{analysis[:undo].count}" if ENV['DEBUG_RECOVERY']
59
+ @recovery_log << { step: "analyze", redo: analysis[:redo].size, undo: analysis[:undo].size }
60
+
61
+ # Step 4: REDO committed transactions
62
+ redo_count = redo_records(analysis[:redo])
63
+ @stats[:redo_records] += redo_count
64
+ @recovery_log << { step: "redo", count: redo_count }
65
+
66
+ # Step 5: UNDO uncommitted transactions
67
+ undo_count = undo_records(analysis[:undo])
68
+ @stats[:undo_records] += undo_count
69
+ @recovery_log << { step: "undo", count: undo_count }
70
+
71
+ # Step 6: Verify consistency
72
+ consistency_check = verify_consistency
73
+ @recovery_log << { step: "consistency", passed: consistency_check }
74
+
75
+ # Step 7: Create new checkpoint
76
+ if consistency_check
77
+ create_checkpoint_after_recovery
78
+ end
79
+
80
+ @last_recovery_time = Time.now
81
+ recovery_time_ms = (Time.now - start_time) * 1000
82
+ @stats[:total_recovery_time_ms] += recovery_time_ms
83
+ @stats[:recovery_time_ms] = recovery_time_ms
84
+ @stats[:avg_recovery_time_ms] = @stats[:total_recovery_time_ms] / @stats[:recoveries]
85
+
86
+ {
87
+ success: true,
88
+ redo_count: redo_count,
89
+ undo_count: undo_count,
90
+ consistency_passed: consistency_check,
91
+ recovery_time_ms: recovery_time_ms,
92
+ records_processed: records.size,
93
+ log: @recovery_log
94
+ }
95
+
96
+ rescue => e
97
+ @stats[:corrupted_records] += 1
98
+ @recovery_log << { step: "error", error: e.message }
99
+
100
+ if @stop_on_error || errors >= @max_errors
101
+ raise
102
+ end
103
+
104
+ {
105
+ success: false,
106
+ error: e.message,
107
+ recovery_time_ms: (Time.now - start_time) * 1000,
108
+ log: @recovery_log
109
+ }
110
+ end
111
+ end
112
+ end
113
+
114
+ def find_latest_checkpoint
115
+ # Read all records and find the latest checkpoint
116
+ all_records = @wal.read_all
117
+ checkpoints = all_records.select { |r| r.type == :checkpoint }
118
+ return nil if checkpoints.empty?
119
+
120
+ latest = checkpoints.max_by { |r| r.timestamp }
121
+
122
+ # The checkpoint payload's `lsn` identifies the last WAL record that
123
+ # was flushed before the marker was written. It is therefore an
124
+ # inclusive boundary: starting recovery there replays that record a
125
+ # second time. Use the marker's own physical LSN instead. Reader
126
+ # ranges are inclusive, so the marker itself is harmless and all
127
+ # records after it are replayed exactly once.
128
+ latest.lsn
129
+ end
130
+
131
+ def analyze_records(records)
132
+ analysis = {
133
+ redo: [],
134
+ undo: [],
135
+ committed: Set.new,
136
+ prepared: Set.new,
137
+ active: Set.new
138
+ }
139
+
140
+ # First pass: collect transaction states
141
+ records.each do |record|
142
+ tx_id = record.transaction_id
143
+ case record.type
144
+ when :commit
145
+ analysis[:committed].add(tx_id)
146
+ when :prepare
147
+ analysis[:prepared].add(tx_id)
148
+ when :begin
149
+ analysis[:active].add(tx_id)
150
+ when :rollback
151
+ analysis[:active].delete(tx_id)
152
+ end
153
+ end
154
+
155
+ # Determine which transactions to redo and undo
156
+ records.each do |record|
157
+ tx_id = record.transaction_id
158
+
159
+ # Mutations without explicit transaction markers are auto-committed
160
+ if tx_id.nil? || tx_id == 0
161
+ if [:insert, :update, :delete, :create_table, :drop_table].include?(record.type)
162
+ analysis[:redo] << record
163
+ end
164
+ elsif analysis[:committed].include?(tx_id)
165
+ # Committed transactions need REDO
166
+ if [:insert, :update, :delete, :create_table, :drop_table].include?(record.type)
167
+ analysis[:redo] << record
168
+ end
169
+ elsif analysis[:prepared].include?(tx_id)
170
+ # Prepared but not committed - check if can commit
171
+ if can_commit_prepared?(record)
172
+ analysis[:redo] << record
173
+ else
174
+ analysis[:undo] << record
175
+ end
176
+ elsif analysis[:active].include?(tx_id)
177
+ # Active transactions need UNDO
178
+ if [:insert, :update, :delete].include?(record.type)
179
+ analysis[:undo] << record
180
+ end
181
+ end
182
+ end
183
+
184
+ analysis
185
+ end
186
+
187
+ def redo_records(records)
188
+ @engine.with_recovery do
189
+ count = 0
190
+ records.each do |record|
191
+ begin
192
+ redo_record(record)
193
+ count += 1
194
+ rescue => e
195
+ @stats[:corrupted_records] += 1
196
+ @recovery_log << { step: "redo_error", record: record.lsn.to_s, error: e.message }
197
+ raise if @stop_on_error
198
+ end
199
+ end
200
+ count
201
+ end
202
+ end
203
+
204
+ def undo_records(records)
205
+ @engine.with_recovery do
206
+ count = 0
207
+ # Undo in reverse order
208
+ records.reverse_each do |record|
209
+ begin
210
+ undo_record(record)
211
+ count += 1
212
+ rescue => e
213
+ @stats[:corrupted_records] += 1
214
+ @recovery_log << { step: "undo_error", record: record.lsn.to_s, error: e.message }
215
+ raise if @stop_on_error
216
+ end
217
+ end
218
+ count
219
+ end
220
+ end
221
+
222
+ def redo_record(record)
223
+ data = record.data
224
+ case record.type
225
+ when :insert
226
+ # Re-insert the row
227
+ table_name = data[:table_name] || data[:table]
228
+ table_name = table_name.to_sym if table_name.respond_to?(:to_sym)
229
+ columns = @engine.table_columns(table_name) || []
230
+ # JSON-backed WAL payloads may deserialize row_id as a string while
231
+ # physical row scans expose an integer. Compare the stable numeric
232
+ # identity so replay remains idempotent across process restarts.
233
+ existing = @engine.select_rows(table_name, columns).any? do |row|
234
+ row[:_row_id].to_i == data[:row_id].to_i
235
+ end
236
+ return if existing
237
+ begin
238
+ @engine.insert_row(table_name, columns, data[:values] || {})
239
+ rescue => e
240
+ warn "REDO INSERT failed: table=#{table_name}, error=#{e.message}"
241
+ raise
242
+ end
243
+ when :update
244
+ # Re-apply the update
245
+ table_name = data[:table_name] || data[:table]
246
+ table_name = table_name.to_sym if table_name.respond_to?(:to_sym)
247
+ begin
248
+ @engine.update_row(table_name, data[:row_id], data[:values] || {})
249
+ rescue => e
250
+ warn "REDO UPDATE failed: table=#{table_name}, row=#{data[:row_id]}, error=#{e.message}"
251
+ raise
252
+ end
253
+ when :delete
254
+ # Re-delete the row
255
+ table_name = data[:table_name] || data[:table]
256
+ table_name = table_name.to_sym if table_name.respond_to?(:to_sym)
257
+ begin
258
+ @engine.delete_row(table_name, data[:row_id])
259
+ rescue => e
260
+ warn "REDO DELETE failed: table=#{table_name}, row=#{data[:row_id]}, error=#{e.message}"
261
+ raise
262
+ end
263
+ when :create_table
264
+ # Re-create the table
265
+ begin
266
+ @engine.create_table(data[:table_name], data[:columns] || [])
267
+ rescue => e
268
+ warn "REDO CREATE_TABLE failed: table=#{data[:table_name]}, error=#{e.message}"
269
+ raise
270
+ end
271
+ when :drop_table
272
+ # Re-drop the table
273
+ begin
274
+ @engine.drop_table(data[:table_name])
275
+ rescue => e
276
+ warn "REDO DROP_TABLE failed: table=#{data[:table_name]}, error=#{e.message}"
277
+ raise
278
+ end
279
+ when :schema_change
280
+ # Re-apply schema change
281
+ apply_schema_change(data)
282
+ end
283
+ end
284
+
285
+ def undo_record(record)
286
+ data = record.data
287
+ case record.type
288
+ when :insert
289
+ # Undo insert: delete the row
290
+ table_name = data[:table_name] || data[:table]
291
+ table_name = table_name.to_sym if table_name.respond_to?(:to_sym)
292
+ @engine.delete_row(table_name, data[:row_id]) if table_name && data[:row_id]
293
+ when :update
294
+ # Undo update: restore old values
295
+ if data[:old_values]
296
+ table_name = data[:table_name] || data[:table]
297
+ table_name = table_name.to_sym if table_name.respond_to?(:to_sym)
298
+ @engine.update_row(table_name, data[:row_id], data[:old_values], visibility_check: false)
299
+ end
300
+ when :delete
301
+ # Undo delete by restoring the original physical row version. This
302
+ # preserves its row ID and avoids creating a duplicate row.
303
+ table_name = data[:table_name] || data[:table]
304
+ table_name = table_name.to_sym if table_name.respond_to?(:to_sym)
305
+ @engine.restore_deleted_row(table_name, data[:row_id]) if table_name && data[:row_id]
306
+ end
307
+ end
308
+
309
+ def can_commit_prepared?(record)
310
+ # Prepared transactions may commit only when their recorded resources
311
+ # remain available after recovery.
312
+ data = record.data
313
+ return true unless data && data[:resources]
314
+
315
+ # Check if all resources are still available
316
+ data[:resources].all? do |resource|
317
+ resource_available?(resource)
318
+ end
319
+ end
320
+
321
+ def resource_available?(resource)
322
+ case resource
323
+ when String
324
+ File.exist?(resource)
325
+ when Hash
326
+ path = resource[:path] || resource["path"] || resource[:file] || resource["file"]
327
+ path ? File.exist?(path) : false
328
+ else
329
+ resource.respond_to?(:available?) ? resource.available? : false
330
+ end
331
+ end
332
+
333
+ def apply_schema_change(data)
334
+ # Apply schema change
335
+ case data[:operation]
336
+ when :add_column
337
+ table = @engine.find_table(data[:table_name])
338
+ table.add_column(data[:column_name], data[:column_type])
339
+ when :drop_column
340
+ table = @engine.find_table(data[:table_name])
341
+ table.drop_column(data[:column_name])
342
+ when :rename_table
343
+ @engine.rename_table(data[:old_name], data[:new_name])
344
+ end
345
+ end
346
+
347
+ def verify_consistency
348
+ # Recovery must not report success when the catalog points at pages
349
+ # that cannot be read. This is intentionally structural: row-level
350
+ # validation belongs to the storage engine, while recovery needs a
351
+ # cheap post-redo/post-undo sanity check.
352
+ metadata = @engine.respond_to?(:table_metadata) ? @engine.table_metadata : {}
353
+ table_pages = @engine.instance_variable_get(:@table_pages) || {}
354
+
355
+ metadata.all? do |_table_name, table_data|
356
+ required_pages = [table_data[:metadata_page], table_data[:data_page]]
357
+ listed_pages = table_pages.values.flatten
358
+ (required_pages + listed_pages).compact.all? do |page_number|
359
+ page = @engine.read_page(page_number)
360
+ page && page.respond_to?(:header)
361
+ rescue StandardError
362
+ false
363
+ end
364
+ end
365
+ end
366
+
367
+ def create_checkpoint_after_recovery
368
+ # Create a checkpoint after successful recovery
369
+ @wal.create_checkpoint(true)
370
+ end
371
+
372
+ def stats
373
+ @lock.synchronize do
374
+ @stats.merge({
375
+ last_recovery: @last_recovery_time&.iso8601,
376
+ log_entries: @recovery_log.size,
377
+ stop_on_error: @stop_on_error
378
+ })
379
+ end
380
+ end
381
+
382
+ def recovery_log
383
+ @recovery_log.dup
384
+ end
385
+ end
386
+ end
387
+ end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module Recovery
5
+ # RecoveryManager - Main recovery coordination
6
+ class RecoveryManager
7
+ attr_reader :crash_recovery, :checkpoint, :redo, :undo
8
+ attr_reader :consistency_checker, :corruption_detector, :stats
9
+
10
+ def initialize(engine, wal, config = {})
11
+ @engine = engine
12
+ @wal = wal
13
+ @config = config
14
+
15
+ # Initialize components
16
+ @crash_recovery = CrashRecovery.new(engine, wal, config)
17
+ @checkpoint = Checkpoint.new(engine, wal, config)
18
+ @redo = Redo.new(engine, wal, config)
19
+ @undo = Undo.new(engine, wal, config)
20
+ @consistency_checker = ConsistencyChecker.new(engine, config)
21
+ @corruption_detector = CorruptionDetector.new(engine, config)
22
+
23
+ @stats = {
24
+ recoveries_performed: 0,
25
+ checkpoints_created: 0,
26
+ redos_performed: 0,
27
+ undos_performed: 0,
28
+ consistency_checks: 0,
29
+ corruption_detections: 0,
30
+ last_recovery_time: nil,
31
+ last_recovery_result: nil,
32
+ total_recovery_time_ms: 0
33
+ }
34
+ @lock = Mutex.new
35
+ @recovery_mode = config[:recovery_mode] || :auto
36
+ @recovery_states = []
37
+ @max_states = config[:max_states] || 100
38
+ end
39
+
40
+ def perform_recovery
41
+ @lock.synchronize do
42
+ start_time = Time.now
43
+ @stats[:recoveries_performed] += 1
44
+
45
+ result = {
46
+ success: false,
47
+ steps: [],
48
+ errors: [],
49
+ warnings: [],
50
+ details: {}
51
+ }
52
+
53
+ # Step 1: Detect corruption
54
+ corruption_result = @corruption_detector.detect_corruption
55
+ result[:steps] << { step: "corruption_detection", result: corruption_result }
56
+
57
+ if corruption_result[:corrupted]
58
+ result[:warnings] << "Corruption detected, attempting repair"
59
+
60
+ # Step 2: Repair corruption
61
+ repair_result = @corruption_detector.repair_corruption(corruption_result)
62
+ result[:steps] << { step: "corruption_repair", result: repair_result }
63
+
64
+ if repair_result[:repaired]
65
+ result[:details][:repaired] = true
66
+ else
67
+ result[:errors] << "Corruption repair failed"
68
+ @stats[:corruption_detections] += 1
69
+ end
70
+ end
71
+
72
+ # Step 3: Run crash recovery
73
+ recovery_result = @crash_recovery.recover
74
+ result[:steps] << { step: "crash_recovery", result: recovery_result }
75
+ @stats[:last_recovery_result] = recovery_result
76
+
77
+ if recovery_result[:success]
78
+ result[:details][:redo_count] = recovery_result[:redo_count]
79
+ result[:details][:undo_count] = recovery_result[:undo_count]
80
+ else
81
+ result[:errors] << "Crash recovery failed: #{recovery_result[:error]}"
82
+ end
83
+
84
+ # Step 4: Check consistency
85
+ consistency_result = @consistency_checker.check_all
86
+ result[:steps] << { step: "consistency_check", result: consistency_result }
87
+ @stats[:consistency_checks] += 1
88
+
89
+ if consistency_result[:passed]
90
+ result[:success] = true
91
+ else
92
+ result[:errors] << "Consistency check failed"
93
+ result[:success] = false
94
+ end
95
+
96
+ # Step 5: Create checkpoint if successful
97
+ if result[:success]
98
+ checkpoint_result = @checkpoint.create_checkpoint(true)
99
+ result[:steps] << { step: "checkpoint", result: checkpoint_result }
100
+ @stats[:checkpoints_created] += 1 if checkpoint_result
101
+ end
102
+
103
+ # Record state
104
+ record_state(result)
105
+
106
+ elapsed_ms = (Time.now - start_time) * 1000
107
+ @stats[:total_recovery_time_ms] += elapsed_ms
108
+ @stats[:last_recovery_time] = Time.now
109
+
110
+ result
111
+ end
112
+ end
113
+
114
+ def create_checkpoint(force = false)
115
+ @lock.synchronize do
116
+ @checkpoint.create_checkpoint(force)
117
+ @stats[:checkpoints_created] += 1
118
+ end
119
+ end
120
+
121
+ def redo_records(records, from_lsn = nil)
122
+ @lock.synchronize do
123
+ count = @redo.redo_records(records, from_lsn)
124
+ @stats[:redos_performed] += 1
125
+ count
126
+ end
127
+ end
128
+
129
+ def undo_records(records)
130
+ @lock.synchronize do
131
+ count = @undo.undo_records(records)
132
+ @stats[:undos_performed] += 1
133
+ count
134
+ end
135
+ end
136
+
137
+ def check_consistency
138
+ @lock.synchronize do
139
+ result = @consistency_checker.check_all
140
+ @stats[:consistency_checks] += 1
141
+ result
142
+ end
143
+ end
144
+
145
+ def detect_corruption(scan_all = false)
146
+ @lock.synchronize do
147
+ result = @corruption_detector.detect_corruption(scan_all)
148
+ @stats[:corruption_detections] += 1 if result[:corrupted]
149
+ result
150
+ end
151
+ end
152
+
153
+ def repair_corruption(corruption_info)
154
+ @lock.synchronize do
155
+ @corruption_detector.repair_corruption(corruption_info)
156
+ end
157
+ end
158
+
159
+ def recovery_mode
160
+ @recovery_mode
161
+ end
162
+
163
+ def recovery_mode=(mode)
164
+ @lock.synchronize do
165
+ @recovery_mode = mode
166
+ end
167
+ end
168
+
169
+ def recovery_history
170
+ @recovery_states.dup
171
+ end
172
+
173
+ def stats
174
+ @lock.synchronize do
175
+ @stats.merge({
176
+ recovery_mode: @recovery_mode,
177
+ history_size: @recovery_states.size,
178
+ max_states: @max_states,
179
+ crash_recovery: @crash_recovery.stats,
180
+ checkpoint: @checkpoint.stats,
181
+ redo: @redo.stats,
182
+ undo: @undo.stats,
183
+ consistency_checker: @consistency_checker.stats,
184
+ corruption_detector: @corruption_detector.stats
185
+ })
186
+ end
187
+ end
188
+
189
+ private
190
+
191
+ def record_state(result)
192
+ state = {
193
+ timestamp: Time.now.iso8601,
194
+ result: result,
195
+ stats: @stats.dup
196
+ }
197
+
198
+ @recovery_states << state
199
+
200
+ if @recovery_states.size > @max_states
201
+ @recovery_states.shift
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end