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,237 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module Recovery
5
+ # Redo - Handles REDO operations during recovery
6
+ class Redo
7
+ attr_reader :stats
8
+
9
+ def initialize(engine, wal, config = {})
10
+ @engine = engine
11
+ @wal = wal
12
+ @config = config
13
+ @stats = {
14
+ redo_count: 0,
15
+ redo_failures: 0,
16
+ total_redo_time_ms: 0,
17
+ avg_redo_time_ms: 0,
18
+ last_redo_lsn: nil,
19
+ last_redo_time: nil
20
+ }
21
+ @lock = Mutex.new
22
+ @batch_size = config[:batch_size] || 1000
23
+ end
24
+
25
+ def redo_records(records, from_lsn = nil)
26
+ @lock.synchronize do
27
+ start_time = Time.now
28
+
29
+ # Filter records to redo
30
+ redo_records = records
31
+ if from_lsn
32
+ redo_records = redo_records.select { |r| r.lsn && r.lsn >= from_lsn }
33
+ end
34
+
35
+ # Group by transaction
36
+ grouped = group_by_transaction(redo_records)
37
+
38
+ # Process each transaction
39
+ count = 0
40
+ grouped.each do |transaction_id, transaction_records|
41
+ begin
42
+ count += redo_transaction(transaction_records)
43
+ rescue => e
44
+ @stats[:redo_failures] += 1
45
+ raise if @config[:stop_on_error]
46
+ end
47
+ end
48
+
49
+ elapsed_ms = (Time.now - start_time) * 1000
50
+ @stats[:redo_count] += count
51
+ @stats[:total_redo_time_ms] += elapsed_ms
52
+ @stats[:avg_redo_time_ms] = @stats[:total_redo_time_ms] / @stats[:redo_count] if @stats[:redo_count] > 0
53
+
54
+ if redo_records.any?
55
+ @stats[:last_redo_lsn] = redo_records.last.lsn
56
+ @stats[:last_redo_time] = Time.now
57
+ end
58
+
59
+ count
60
+ end
61
+ end
62
+
63
+ def redo_transaction(records)
64
+ count = 0
65
+
66
+ records.each do |record|
67
+ # Skip if already applied
68
+ next if already_applied?(record)
69
+
70
+ case record.type
71
+ when :insert
72
+ redo_insert(record)
73
+ count += 1
74
+ when :update
75
+ redo_update(record)
76
+ count += 1
77
+ when :delete
78
+ redo_delete(record)
79
+ count += 1
80
+ when :create_table
81
+ redo_create_table(record)
82
+ count += 1
83
+ when :drop_table
84
+ redo_drop_table(record)
85
+ count += 1
86
+ when :schema_change
87
+ redo_schema_change(record)
88
+ count += 1
89
+ end
90
+ end
91
+
92
+ count
93
+ end
94
+
95
+ def redo_insert(record)
96
+ data = record.data
97
+ return unless data
98
+
99
+ # Re-insert the row
100
+ table = data[:table]
101
+ columns = data[:columns] || []
102
+ values = data[:values] || {}
103
+
104
+ # Check if row already exists
105
+ existing = @engine.select_row(table, data[:row_id], columns) if data[:row_id]
106
+ if existing
107
+ # Update instead of insert
108
+ @engine.update_row(table, data[:row_id], values)
109
+ else
110
+ @engine.insert_row(table, columns, values)
111
+ end
112
+ end
113
+
114
+ def redo_update(record)
115
+ data = record.data
116
+ return unless data
117
+
118
+ # Re-apply the update
119
+ table = data[:table]
120
+ row_id = data[:row_id]
121
+ values = data[:values] || {}
122
+
123
+ @engine.update_row(table, row_id, values)
124
+ end
125
+
126
+ def redo_delete(record)
127
+ data = record.data
128
+ return unless data
129
+
130
+ # Re-delete the row
131
+ table = data[:table]
132
+ row_id = data[:row_id]
133
+
134
+ @engine.delete_row(table, row_id)
135
+ end
136
+
137
+ def redo_create_table(record)
138
+ data = record.data
139
+ return unless data
140
+
141
+ # Re-create the table
142
+ table_name = data[:table_name]
143
+ columns = data[:columns] || []
144
+
145
+ unless @engine.table_exists?(table_name)
146
+ @engine.create_table(table_name, columns)
147
+ end
148
+ end
149
+
150
+ def redo_drop_table(record)
151
+ data = record.data
152
+ return unless data
153
+
154
+ # Re-drop the table
155
+ table_name = data[:table_name]
156
+
157
+ if @engine.table_exists?(table_name)
158
+ @engine.drop_table(table_name)
159
+ end
160
+ end
161
+
162
+ def redo_schema_change(record)
163
+ data = record.data
164
+ return unless data
165
+
166
+ case data[:operation]
167
+ when :add_column
168
+ table = @engine.find_table(data[:table_name])
169
+ table.add_column(data[:column_name], data[:column_type]) if table
170
+ when :drop_column
171
+ table = @engine.find_table(data[:table_name])
172
+ table.drop_column(data[:column_name]) if table
173
+ when :rename_table
174
+ @engine.rename_table(data[:old_name], data[:new_name])
175
+ end
176
+ end
177
+
178
+ def already_applied?(record)
179
+ data = record.data || {}
180
+ case record.type
181
+ when :insert
182
+ return false unless data[:row_id]
183
+ !@engine.select_row(data[:table], data[:row_id], data[:columns] || []).nil?
184
+ when :update
185
+ row = data[:row_id] && @engine.select_row(data[:table], data[:row_id], data[:columns] || [])
186
+ row && data.fetch(:values, {}).all? { |key, value| row[key] == value || row[key.to_s] == value }
187
+ when :delete
188
+ data[:row_id] && @engine.select_row(data[:table], data[:row_id], data[:columns] || []).nil?
189
+ when :create_table
190
+ @engine.table_exists?(data[:table_name])
191
+ when :drop_table
192
+ !@engine.table_exists?(data[:table_name])
193
+ when :schema_change
194
+ schema_change_applied?(data)
195
+ else
196
+ false
197
+ end
198
+ rescue StandardError
199
+ false
200
+ end
201
+
202
+ def schema_change_applied?(data)
203
+ case data[:operation]
204
+ when :add_column
205
+ table = @engine.find_table(data[:table_name])
206
+ table && table.columns.any? { |column| column.name.to_s == data[:column_name].to_s }
207
+ when :drop_column
208
+ table = @engine.find_table(data[:table_name])
209
+ table && !table.columns.any? { |column| column.name.to_s == data[:column_name].to_s }
210
+ when :rename_table
211
+ @engine.table_exists?(data[:new_name]) && !@engine.table_exists?(data[:old_name])
212
+ else
213
+ false
214
+ end
215
+ end
216
+
217
+ def group_by_transaction(records)
218
+ groups = {}
219
+ records.each do |record|
220
+ tx_id = record.transaction_id
221
+ groups[tx_id] ||= []
222
+ groups[tx_id] << record
223
+ end
224
+ groups
225
+ end
226
+
227
+ def stats
228
+ @lock.synchronize do
229
+ @stats.merge({
230
+ batch_size: @batch_size,
231
+ last_redo_lsn: @stats[:last_redo_lsn]&.to_s
232
+ })
233
+ end
234
+ end
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module Recovery
5
+ # Undo - Handles UNDO operations during recovery
6
+ class Undo
7
+ attr_reader :stats
8
+
9
+ def initialize(engine, wal, config = {})
10
+ @engine = engine
11
+ @wal = wal
12
+ @config = config
13
+ @stats = {
14
+ undo_count: 0,
15
+ undo_failures: 0,
16
+ total_undo_time_ms: 0,
17
+ avg_undo_time_ms: 0,
18
+ last_undo_lsn: nil,
19
+ last_undo_time: nil
20
+ }
21
+ @lock = Mutex.new
22
+ @batch_size = config[:batch_size] || 1000
23
+ end
24
+
25
+ def undo_records(records)
26
+ @lock.synchronize do
27
+ start_time = Time.now
28
+
29
+ # Group by transaction (undo in reverse order within transaction)
30
+ grouped = group_by_transaction(records)
31
+
32
+ # Process each transaction in reverse (LIFO)
33
+ count = 0
34
+ grouped.each do |transaction_id, transaction_records|
35
+ begin
36
+ # Undo in reverse order within transaction
37
+ count += undo_transaction(transaction_records.reverse)
38
+ rescue => e
39
+ @stats[:undo_failures] += 1
40
+ raise if @config[:stop_on_error]
41
+ end
42
+ end
43
+
44
+ elapsed_ms = (Time.now - start_time) * 1000
45
+ @stats[:undo_count] += count
46
+ @stats[:total_undo_time_ms] += elapsed_ms
47
+ @stats[:avg_undo_time_ms] = @stats[:total_undo_time_ms] / @stats[:undo_count] if @stats[:undo_count] > 0
48
+
49
+ if records.any?
50
+ @stats[:last_undo_lsn] = records.last.lsn
51
+ @stats[:last_undo_time] = Time.now
52
+ end
53
+
54
+ count
55
+ end
56
+ end
57
+
58
+ def undo_transaction(records)
59
+ count = 0
60
+
61
+ records.each do |record|
62
+ case record.type
63
+ when :insert
64
+ undo_insert(record)
65
+ count += 1
66
+ when :update
67
+ undo_update(record)
68
+ count += 1
69
+ when :delete
70
+ undo_delete(record)
71
+ count += 1
72
+ when :create_table
73
+ undo_create_table(record)
74
+ count += 1
75
+ when :drop_table
76
+ undo_drop_table(record)
77
+ count += 1
78
+ when :schema_change
79
+ undo_schema_change(record)
80
+ count += 1
81
+ end
82
+ end
83
+
84
+ count
85
+ end
86
+
87
+ def undo_insert(record)
88
+ data = record.data
89
+ return unless data
90
+
91
+ # Undo insert: delete the row
92
+ table = data[:table]
93
+ row_id = data[:row_id]
94
+
95
+ if row_id
96
+ # Get the row to verify it exists
97
+ columns = @engine.table_columns(table)
98
+ row = @engine.select_row(table, row_id, columns)
99
+ if row
100
+ @engine.delete_row(table, row_id)
101
+ end
102
+ end
103
+ end
104
+
105
+ def undo_update(record)
106
+ data = record.data
107
+ return unless data
108
+
109
+ # Undo update: restore old values
110
+ table = data[:table]
111
+ row_id = data[:row_id]
112
+ old_values = data[:old_values]
113
+
114
+ if row_id && old_values
115
+ # Verify the row still exists with the expected values
116
+ columns = @engine.table_columns(table)
117
+ current_row = @engine.select_row(table, row_id, columns)
118
+ if current_row
119
+ @engine.update_row(table, row_id, old_values)
120
+ end
121
+ end
122
+ end
123
+
124
+ def undo_delete(record)
125
+ data = record.data
126
+ return unless data
127
+
128
+ # Undo delete: reinsert the row
129
+ table = data[:table]
130
+ columns = data[:columns] || []
131
+ row_data = data[:row_data] || {}
132
+
133
+ if table && row_data.any?
134
+ # Check if row still exists
135
+ existing = @engine.select_row(table, data[:row_id], columns) if data[:row_id]
136
+ unless existing
137
+ @engine.insert_row(table, columns, row_data)
138
+ end
139
+ end
140
+ end
141
+
142
+ def undo_create_table(record)
143
+ data = record.data
144
+ return unless data
145
+
146
+ # Undo create table: drop the table
147
+ table_name = data[:table_name]
148
+
149
+ if @engine.table_exists?(table_name)
150
+ @engine.drop_table(table_name)
151
+ end
152
+ end
153
+
154
+ def undo_drop_table(record)
155
+ data = record.data
156
+ return unless data
157
+
158
+ # Undo drop table: re-create the table
159
+ table_name = data[:table_name]
160
+ columns = data[:columns] || []
161
+
162
+ unless @engine.table_exists?(table_name)
163
+ @engine.create_table(table_name, columns)
164
+ end
165
+ end
166
+
167
+ def undo_schema_change(record)
168
+ data = record.data
169
+ return unless data
170
+
171
+ case data[:operation]
172
+ when :add_column
173
+ # Undo add column: drop the column
174
+ table = @engine.find_table(data[:table_name])
175
+ table.drop_column(data[:column_name]) if table
176
+ when :drop_column
177
+ # Undo drop column: add the column back
178
+ table = @engine.find_table(data[:table_name])
179
+ table.add_column(data[:column_name], data[:column_type]) if table
180
+ when :rename_table
181
+ # Undo rename table: rename back
182
+ @engine.rename_table(data[:new_name], data[:old_name])
183
+ end
184
+ end
185
+
186
+ def group_by_transaction(records)
187
+ groups = {}
188
+ records.each do |record|
189
+ tx_id = record.transaction_id
190
+ groups[tx_id] ||= []
191
+ groups[tx_id] << record
192
+ end
193
+ groups
194
+ end
195
+
196
+ def stats
197
+ @lock.synchronize do
198
+ @stats.merge({
199
+ batch_size: @batch_size,
200
+ last_undo_lsn: @stats[:last_undo_lsn]&.to_s
201
+ })
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Failover is defined alongside the replication-slot catalog. This public
4
+ # require target keeps direct consumers independent of load order.
5
+ require_relative "replication_slot"
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "fileutils"
5
+
6
+ module RubyDB
7
+ module Replication
8
+ # File-backed fencing lease. The fence path must live on storage shared by
9
+ # all nodes that can write the same database. Epoch changes are atomic and
10
+ # stale writers fail closed when their lease no longer matches.
11
+ class FencingLease
12
+ attr_reader :path, :node_id, :epoch
13
+
14
+ def initialize(path, node_id)
15
+ @path = path
16
+ @node_id = node_id.to_s
17
+ @epoch = nil
18
+ end
19
+
20
+ def acquire!
21
+ FileUtils.mkdir_p(File.dirname(@path))
22
+ File.open("#{@path}.lock", "a+") do |lock|
23
+ lock.flock(File::LOCK_EX)
24
+ current = read_record
25
+ @epoch = current ? current.fetch("epoch").to_i + 1 : 1
26
+ temporary = "#{@path}.tmp-#{Process.pid}-#{Thread.current.object_id}"
27
+ File.open(temporary, "wb") do |file|
28
+ file.write(JSON.generate(node_id: @node_id, epoch: @epoch, updated_at: Time.now.utc.iso8601))
29
+ file.flush
30
+ file.fsync
31
+ end
32
+ File.rename(temporary, @path)
33
+ ensure
34
+ File.delete(temporary) if defined?(temporary) && File.file?(temporary)
35
+ lock.flock(File::LOCK_UN)
36
+ end
37
+ self
38
+ rescue KeyError, JSON::ParserError, SystemCallError => e
39
+ raise RubyDB::ReplicationError, "Unable to acquire fencing lease: #{e.message}"
40
+ end
41
+
42
+ def valid?
43
+ return false unless @epoch
44
+ record = read_record
45
+ record && record["node_id"].to_s == @node_id && record["epoch"].to_i == @epoch
46
+ rescue JSON::ParserError, SystemCallError
47
+ false
48
+ end
49
+
50
+ def assert_valid!
51
+ raise RubyDB::ReplicationError, "Fencing lease is missing or stale" unless valid?
52
+ true
53
+ end
54
+
55
+ private
56
+
57
+ def read_record
58
+ return nil unless File.file?(@path)
59
+ JSON.parse(File.read(@path))
60
+ end
61
+ end
62
+ end
63
+ end