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,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+
5
+ module RubyDB
6
+ module WAL
7
+ # Checkpoint - Manages checkpoints for recovery
8
+ class Checkpoint
9
+ attr_reader :last_checkpoint_lsn, :stats
10
+
11
+ def initialize(wal_writer, config = {})
12
+ @wal_writer = wal_writer
13
+ @config = config
14
+ @checkpoint_interval = config[:interval] || 300 # 5 minutes
15
+ @min_checkpoint_age = config[:min_age] || 60 # 1 minute
16
+ @last_checkpoint_time = Time.now
17
+ @last_checkpoint_lsn = nil
18
+ @last_checkpoint_data = nil
19
+ @stats = {
20
+ checkpoints_created: 0,
21
+ checkpoints_restored: 0,
22
+ checkpoint_failures: 0,
23
+ last_error: nil,
24
+ total_checkpoint_time_ms: 0,
25
+ avg_checkpoint_time_ms: 0,
26
+ last_checkpoint_size: 0
27
+ }
28
+ @lock = Mutex.new
29
+ @running = false
30
+ @checkpoint_thread = nil
31
+ end
32
+
33
+ def start
34
+ @lock.synchronize do
35
+ return if @running
36
+
37
+ @running = true
38
+ @checkpoint_thread = Thread.new do
39
+ checkpoint_loop
40
+ end
41
+ end
42
+ end
43
+
44
+ def stop
45
+ @lock.synchronize do
46
+ @running = false
47
+ @checkpoint_thread&.kill
48
+ @checkpoint_thread = nil
49
+ end
50
+ end
51
+
52
+ def create_checkpoint(force = false)
53
+ @lock.synchronize do
54
+ start_time = Time.now
55
+
56
+ # Check if enough time has passed
57
+ if !force && (Time.now - @last_checkpoint_time) < @min_checkpoint_age
58
+ return false
59
+ end
60
+
61
+ begin
62
+ # Flush WAL
63
+ @wal_writer.flush
64
+
65
+ # Get current LSN
66
+ current_lsn = @wal_writer.current_lsn
67
+
68
+ # Create checkpoint record
69
+ checkpoint_data = {
70
+ lsn: current_lsn.to_s,
71
+ timestamp: Time.now.iso8601,
72
+ wal_segment: @wal_writer.current_segment.segment_id,
73
+ wal_offset: current_lsn.offset
74
+ }
75
+
76
+ # Write checkpoint record to WAL
77
+ record = Record.new(:checkpoint, checkpoint_data)
78
+ @wal_writer.write_record(record)
79
+ # A checkpoint is a durability boundary even when ordinary WAL
80
+ # writes use asynchronous buffering. Do not publish its state or
81
+ # return success until the checkpoint frame is flushed and synced.
82
+ @wal_writer.flush
83
+ @wal_writer.sync
84
+
85
+ # Update checkpoint info
86
+ @last_checkpoint_lsn = current_lsn
87
+ @last_checkpoint_data = checkpoint_data
88
+ @last_checkpoint_time = Time.now
89
+
90
+ elapsed_ms = (Time.now - start_time) * 1000
91
+ @stats[:checkpoints_created] += 1
92
+ @stats[:total_checkpoint_time_ms] += elapsed_ms
93
+ @stats[:avg_checkpoint_time_ms] = @stats[:total_checkpoint_time_ms] / @stats[:checkpoints_created]
94
+ @stats[:last_checkpoint_size] = estimate_checkpoint_size
95
+
96
+ true
97
+
98
+ rescue => e
99
+ @stats[:checkpoint_failures] += 1
100
+ @stats[:last_error] = "#{e.class}: #{e.message}"
101
+ raise
102
+ end
103
+ end
104
+ end
105
+
106
+ def restore_checkpoint(reader)
107
+ @lock.synchronize do
108
+ start_time = Time.now
109
+
110
+ begin
111
+ # Find the latest checkpoint in the WAL
112
+ all_records = reader.read_all
113
+ checkpoint_records = all_records.select { |r| r.type == :checkpoint }
114
+
115
+ if checkpoint_records.empty?
116
+ return nil
117
+ end
118
+
119
+ # Get the latest checkpoint
120
+ latest = checkpoint_records.last
121
+ # `latest.data[:lsn]` is the last record covered by the
122
+ # checkpoint and is an inclusive replay boundary. Return the
123
+ # marker's own LSN so recovery starts after the covered records.
124
+ @last_checkpoint_lsn = latest.lsn
125
+ @last_checkpoint_data = latest.data
126
+ @last_checkpoint_time = Time.parse(latest.data[:timestamp])
127
+
128
+ @stats[:checkpoints_restored] += 1
129
+ @last_checkpoint_lsn
130
+
131
+ rescue => e
132
+ @stats[:checkpoint_failures] += 1
133
+ raise
134
+ end
135
+ end
136
+ end
137
+
138
+ def checkpoint_exists?
139
+ !@last_checkpoint_lsn.nil?
140
+ end
141
+
142
+ def checkpoint_age
143
+ return nil unless @last_checkpoint_lsn
144
+ Time.now - @last_checkpoint_time
145
+ end
146
+
147
+ def stats
148
+ @lock.synchronize do
149
+ @stats.merge({
150
+ running: @running,
151
+ last_checkpoint: @last_checkpoint_time.iso8601,
152
+ last_checkpoint_lsn: @last_checkpoint_lsn&.to_s,
153
+ checkpoint_age: checkpoint_age,
154
+ interval: @checkpoint_interval,
155
+ min_age: @min_checkpoint_age
156
+ })
157
+ end
158
+ end
159
+
160
+ private
161
+
162
+ def checkpoint_loop
163
+ while @running
164
+ sleep(@checkpoint_interval)
165
+ begin
166
+ create_checkpoint
167
+ rescue => e
168
+ # Log error but continue
169
+ end
170
+ end
171
+ end
172
+
173
+ def estimate_checkpoint_size
174
+ segment = @wal_writer.current_segment
175
+ return 0 unless segment
176
+
177
+ # The checkpoint covers the flushed WAL through the current segment.
178
+ # Report the actual segment bytes instead of a fixed placeholder.
179
+ segment.size.to_i + @wal_writer.stats.fetch(:buffer_bytes, 0).to_i
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+
5
+ module RubyDB
6
+ module WAL
7
+ # LSN - Log Sequence Number for identifying WAL records
8
+ class LSN
9
+ attr_reader :segment_id, :offset, :timestamp
10
+
11
+ def initialize(segment_id = 0, offset = 0, timestamp = nil)
12
+ @segment_id = segment_id
13
+ @offset = offset
14
+ @timestamp = timestamp || Time.now
15
+ end
16
+
17
+ def to_i
18
+ (@segment_id << 32) | @offset
19
+ end
20
+
21
+ def to_s
22
+ "LSN(#{@segment_id}, #{@offset})"
23
+ end
24
+
25
+ def inspect
26
+ to_s
27
+ end
28
+
29
+ def ==(other)
30
+ other.is_a?(LSN) && @segment_id == other.segment_id && @offset == other.offset
31
+ end
32
+
33
+ def <(other)
34
+ return false unless other.is_a?(LSN)
35
+ @segment_id < other.segment_id || (@segment_id == other.segment_id && @offset < other.offset)
36
+ end
37
+
38
+ def <=(other)
39
+ self < other || self == other
40
+ end
41
+
42
+ def >(other)
43
+ return false unless other.is_a?(LSN)
44
+ @segment_id > other.segment_id || (@segment_id == other.segment_id && @offset > other.offset)
45
+ end
46
+
47
+ def >=(other)
48
+ self > other || self == other
49
+ end
50
+
51
+ def to_i64
52
+ [@segment_id, @offset].pack("Q>Q>")
53
+ end
54
+
55
+ def self.from_i64(data)
56
+ segment_id, offset = data.unpack("Q>Q>")
57
+ new(segment_id, offset)
58
+ end
59
+
60
+ def self.from_i(value)
61
+ segment_id = value >> 32
62
+ offset = value & 0xFFFFFFFF
63
+ new(segment_id, offset)
64
+ end
65
+
66
+ def self.from_s(value)
67
+ match = value.to_s.match(/\ALSN\((\d+),\s*(\d+)\)\z/)
68
+ raise ArgumentError, "Invalid LSN: #{value.inspect}" unless match
69
+
70
+ new(match[1].to_i, match[2].to_i)
71
+ end
72
+
73
+ def self.null
74
+ new(0, 0)
75
+ end
76
+
77
+ def null?
78
+ @segment_id == 0 && @offset == 0
79
+ end
80
+
81
+ def serialize
82
+ {
83
+ segment_id: @segment_id,
84
+ offset: @offset,
85
+ timestamp: @timestamp.iso8601
86
+ }
87
+ end
88
+
89
+ def self.deserialize(data)
90
+ new(data[:segment_id], data[:offset], Time.parse(data[:timestamp]))
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,260 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module WAL
5
+ # Reader - Reads records from the WAL
6
+ class Reader
7
+ attr_reader :stats
8
+
9
+ def initialize(wal_dir, config = {})
10
+ @wal_dir = wal_dir
11
+ @config = config
12
+ @segments = []
13
+ @current_segment_index = 0
14
+ @stats = {
15
+ records_read: 0,
16
+ bytes_read: 0,
17
+ segments_read: 0,
18
+ segment_skips: 0,
19
+ corrupted_records: 0
20
+ }
21
+ @lock = Mutex.new
22
+ @batch_size = config[:batch_size] || 1000
23
+
24
+ load_segments
25
+ end
26
+
27
+ def read_all
28
+ @lock.synchronize do
29
+ records = []
30
+ @segments.each do |segment|
31
+ records.concat(_read_segment(segment))
32
+ end
33
+ records
34
+ end
35
+ end
36
+
37
+ def read_from_lsn(start_lsn)
38
+ @lock.synchronize do
39
+ records = []
40
+ found_start = false
41
+
42
+ @segments.each do |segment|
43
+ if segment.segment_id < start_lsn.segment_id
44
+ next
45
+ end
46
+
47
+ if segment.segment_id == start_lsn.segment_id
48
+ # Read from offset
49
+ records.concat(_read_segment_from_offset(segment, start_lsn.offset))
50
+ else
51
+ records.concat(_read_segment(segment))
52
+ end
53
+
54
+ break if @config[:limit] && records.size >= @config[:limit]
55
+ end
56
+
57
+ records
58
+ end
59
+ end
60
+
61
+ def read_to_lsn(end_lsn)
62
+ @lock.synchronize do
63
+ records = []
64
+
65
+ @segments.each do |segment|
66
+ break if segment.segment_id > end_lsn.segment_id
67
+
68
+ if segment.segment_id == end_lsn.segment_id
69
+ records.concat(_read_segment_to_offset(segment, end_lsn.offset))
70
+ else
71
+ records.concat(_read_segment(segment))
72
+ end
73
+ end
74
+
75
+ records
76
+ end
77
+ end
78
+
79
+ def read_range(start_lsn, end_lsn)
80
+ @lock.synchronize do
81
+ records = []
82
+
83
+ @segments.each do |segment|
84
+ break if segment.segment_id > end_lsn.segment_id
85
+ next if segment.segment_id < start_lsn.segment_id
86
+
87
+ if segment.segment_id == start_lsn.segment_id
88
+ records.concat(_read_segment_range(segment, start_lsn.offset, end_lsn.offset))
89
+ elsif segment.segment_id == end_lsn.segment_id
90
+ records.concat(_read_segment_to_offset(segment, end_lsn.offset))
91
+ else
92
+ records.concat(_read_segment(segment))
93
+ end
94
+ end
95
+
96
+ records
97
+ end
98
+ end
99
+
100
+ def read_segment(segment)
101
+ @lock.synchronize do
102
+ _read_segment(segment)
103
+ end
104
+ end
105
+
106
+ private def _read_segment(segment)
107
+ records = []
108
+ offset = 0
109
+
110
+ puts "WAL DEBUG: _read_segment starting for segment #{segment.segment_id}" if ENV['DEBUG_RECOVERY']
111
+ while true
112
+ record_data = segment.read_record_at(offset)
113
+ break if record_data.nil? || record_data.empty?
114
+ puts "WAL DEBUG: read record at offset #{offset}, size=#{record_data.bytesize}" if ENV['DEBUG_RECOVERY']
115
+
116
+ begin
117
+ lsn = LSN.new(segment.segment_id, offset)
118
+ record = Record.deserialize(record_data, lsn)
119
+ records << record
120
+ @stats[:records_read] += 1
121
+ @stats[:bytes_read] += record_data.bytesize
122
+ offset += Segment::FRAME_HEADER_SIZE + record_data.bytesize
123
+ puts "WAL DEBUG: deserialized record #{record.type}" if ENV['DEBUG_RECOVERY']
124
+ rescue => e
125
+ @stats[:corrupted_records] += 1
126
+ puts "WAL DEBUG: failed to deserialize at offset #{offset}: #{e.message}" if ENV['DEBUG_RECOVERY']
127
+ break
128
+ end
129
+ end
130
+
131
+ @stats[:segments_read] += 1
132
+ puts "WAL DEBUG: finished reading segment #{segment.segment_id}, found #{records.count} records" if ENV['DEBUG_RECOVERY']
133
+ records
134
+ end
135
+
136
+ private def _read_segment_from_offset(segment, offset)
137
+ records = []
138
+ current_offset = offset
139
+
140
+ while true
141
+ record_data = segment.read_record_at(current_offset)
142
+ break if record_data.nil? || record_data.empty?
143
+
144
+ begin
145
+ lsn = LSN.new(segment.segment_id, current_offset)
146
+ record = Record.deserialize(record_data, lsn)
147
+ records << record
148
+ @stats[:records_read] += 1
149
+ @stats[:bytes_read] += record_data.bytesize
150
+ current_offset += Segment::FRAME_HEADER_SIZE + record_data.bytesize
151
+ rescue => e
152
+ @stats[:corrupted_records] += 1
153
+ break
154
+ end
155
+ end
156
+
157
+ records
158
+ end
159
+
160
+ private def _read_segment_to_offset(segment, offset)
161
+ records = []
162
+ current_offset = 0
163
+
164
+ while current_offset < offset
165
+ record_data = segment.read_record_at(current_offset)
166
+ break if record_data.nil? || record_data.empty?
167
+
168
+ begin
169
+ lsn = LSN.new(segment.segment_id, current_offset)
170
+ record = Record.deserialize(record_data, lsn)
171
+ records << record
172
+ @stats[:records_read] += 1
173
+ @stats[:bytes_read] += record_data.bytesize
174
+ current_offset += Segment::FRAME_HEADER_SIZE + record_data.bytesize
175
+ rescue => e
176
+ @stats[:corrupted_records] += 1
177
+ break
178
+ end
179
+ end
180
+
181
+ records
182
+ end
183
+
184
+ private def _read_segment_range(segment, start_offset, end_offset)
185
+ records = []
186
+ current_offset = start_offset
187
+
188
+ while current_offset < end_offset
189
+ record_data = segment.read_record_at(current_offset)
190
+ break if record_data.nil? || record_data.empty?
191
+
192
+ begin
193
+ lsn = LSN.new(segment.segment_id, current_offset)
194
+ record = Record.deserialize(record_data, lsn)
195
+ records << record
196
+ @stats[:records_read] += 1
197
+ @stats[:bytes_read] += record_data.bytesize
198
+ current_offset += Segment::FRAME_HEADER_SIZE + record_data.bytesize
199
+ rescue => e
200
+ @stats[:corrupted_records] += 1
201
+ break
202
+ end
203
+ end
204
+
205
+ records
206
+ end
207
+
208
+ def reload
209
+ @lock.synchronize do
210
+ @segments.each(&:close)
211
+ @segments.clear
212
+ @current_segment_index = 0
213
+ load_segments
214
+ true
215
+ end
216
+ end
217
+
218
+ def close
219
+ @lock.synchronize do
220
+ @segments.each(&:close)
221
+ @segments.clear
222
+ true
223
+ end
224
+ end
225
+
226
+ def stats
227
+ @lock.synchronize do
228
+ @stats.merge({
229
+ segments_loaded: @segments.size,
230
+ current_segment: @current_segment_index,
231
+ batch_size: @batch_size
232
+ })
233
+ end
234
+ end
235
+
236
+ private
237
+
238
+ def load_segments
239
+ @segments = []
240
+
241
+ # Find all WAL segment files
242
+ wal_files = Dir.glob(File.join(@wal_dir, "wal_*.log"))
243
+ puts "WAL DEBUG: found #{wal_files.count} WAL files: #{wal_files.map { |f| File.basename(f) }.inspect}" if ENV['DEBUG_RECOVERY']
244
+ wal_files.sort.each do |file_path|
245
+ # Extract segment ID from filename
246
+ if file_path =~ /wal_(\d+)\.log$/
247
+ segment_id = $1.to_i
248
+ segment = Segment.new(segment_id, @wal_dir)
249
+ if segment.exists?
250
+ segment.open
251
+ @segments << segment
252
+ puts "WAL DEBUG: loaded segment #{segment_id}" if ENV['DEBUG_RECOVERY']
253
+ end
254
+ end
255
+ end
256
+ puts "WAL DEBUG: total segments loaded: #{@segments.count}" if ENV['DEBUG_RECOVERY']
257
+ end
258
+ end
259
+ end
260
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "digest"
5
+ require "time"
6
+ require_relative "../errors/corruption_error"
7
+
8
+ module RubyDB
9
+ module WAL
10
+ # Record - A single WAL record
11
+ class Record
12
+ # Record types
13
+ TYPE_BEGIN = :begin
14
+ TYPE_COMMIT = :commit
15
+ TYPE_ROLLBACK = :rollback
16
+ TYPE_INSERT = :insert
17
+ TYPE_UPDATE = :update
18
+ TYPE_DELETE = :delete
19
+ TYPE_CREATE_TABLE = :create_table
20
+ TYPE_DROP_TABLE = :drop_table
21
+ TYPE_CREATE_INDEX = :create_index
22
+ TYPE_DROP_INDEX = :drop_index
23
+ TYPE_CHECKPOINT = :checkpoint
24
+ TYPE_SCHEMA_CHANGE = :schema_change
25
+ TYPE_PREPARE = :prepare
26
+ TYPE_SAVEPOINT = :savepoint
27
+
28
+ attr_reader :lsn, :type, :data, :transaction_id, :timestamp
29
+ attr_reader :checksum, :prev_lsn, :size
30
+
31
+ def initialize(type, data = {}, transaction_id: nil, prev_lsn: nil)
32
+ @type = type
33
+ @data = data
34
+ @transaction_id = transaction_id
35
+ @prev_lsn = prev_lsn
36
+ @timestamp = Time.now
37
+ @lsn = nil
38
+ @checksum = nil
39
+ @size = 0
40
+ end
41
+
42
+ def serialize
43
+ # Build record data
44
+ record_data = {
45
+ type: @type,
46
+ transaction_id: @transaction_id,
47
+ prev_lsn: @prev_lsn&.to_s,
48
+ timestamp: @timestamp.iso8601,
49
+ data: @data
50
+ }
51
+
52
+ json_data = JSON.generate(record_data)
53
+ # The serialized payload is checksum (16 ASCII bytes) followed by JSON.
54
+ # Segment framing is owned by Segment, so this is the payload size.
55
+ @size = json_data.bytesize + 16
56
+
57
+ # Calculate checksum
58
+ @checksum = Digest::SHA256.hexdigest(json_data)[0...16]
59
+
60
+ # Return serialized record
61
+ {
62
+ checksum: @checksum,
63
+ data: json_data,
64
+ size: @size
65
+ }
66
+ end
67
+
68
+ def self.deserialize(raw_data, lsn)
69
+ checksum = raw_data[0...16]
70
+ json_data = raw_data[16..-1]
71
+
72
+ # Verify checksum
73
+ calculated = Digest::SHA256.hexdigest(json_data)[0...16]
74
+ raise CorruptionError, "WAL record checksum mismatch" if calculated != checksum
75
+
76
+ parsed = JSON.parse(json_data, symbolize_names: true)
77
+
78
+ record = new(
79
+ parsed[:type].to_sym,
80
+ parsed[:data] || {},
81
+ transaction_id: parsed[:transaction_id],
82
+ prev_lsn: parsed[:prev_lsn] ? LSN.from_s(parsed[:prev_lsn]) : nil
83
+ )
84
+ record.instance_variable_set(:@timestamp, Time.parse(parsed[:timestamp]))
85
+ record.instance_variable_set(:@lsn, lsn)
86
+ record.instance_variable_set(:@checksum, checksum)
87
+ record.instance_variable_set(:@size, raw_data.bytesize)
88
+
89
+ record
90
+ end
91
+
92
+ def to_s
93
+ "WALRecord(lsn=#{@lsn}, type=#{@type}, txn=#{@transaction_id})"
94
+ end
95
+
96
+ def inspect
97
+ to_s
98
+ end
99
+
100
+ def size
101
+ @size || 0
102
+ end
103
+ end
104
+ end
105
+ end