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,267 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "time"
5
+ require "json"
6
+ require "digest"
7
+ require "monitor"
8
+
9
+ module RubyDB
10
+ module Backup
11
+ class SnapshotError < StandardError; end unless const_defined?(:SnapshotError)
12
+ # Snapshot - Creates point-in-time snapshots
13
+ class Snapshot
14
+ attr_reader :stats
15
+
16
+ def initialize(engine, config = {})
17
+ @engine = engine
18
+ @config = config
19
+ @snapshot_dir = config[:snapshot_dir] || "snapshots"
20
+ @max_snapshots = config[:max_snapshots] || 10
21
+ @retention_days = config[:retention_days] || 30
22
+ @snapshots = {}
23
+ @stats = {
24
+ snapshots_created: 0,
25
+ snapshots_restored: 0,
26
+ snapshots_deleted: 0,
27
+ total_size_bytes: 0,
28
+ last_snapshot: nil
29
+ }
30
+ @lock = Monitor.new
31
+
32
+ FileUtils.mkdir_p(@snapshot_dir)
33
+ load_snapshots
34
+ end
35
+
36
+ def create_snapshot(name = nil)
37
+ @lock.synchronize do
38
+ start_time = Time.now
39
+ snapshot_name = name || "snapshot_#{Time.now.strftime('%Y%m%d_%H%M%S')}"
40
+ snapshot_path = snapshot_path_for_name(snapshot_name)
41
+ return { success: false, error: "Invalid snapshot name" } unless snapshot_path
42
+
43
+ if Dir.exist?(snapshot_path)
44
+ return { success: false, error: "Snapshot already exists" }
45
+ end
46
+
47
+ begin
48
+ # Create snapshot
49
+ FileUtils.mkdir_p(snapshot_path)
50
+
51
+ # Get current database state
52
+ state = capture_database_state
53
+
54
+ # Store snapshot metadata
55
+ metadata = {
56
+ name: snapshot_name,
57
+ created_at: Time.now.iso8601,
58
+ engine_version: RubyDB::VERSION,
59
+ database_name: @engine.current_database_name,
60
+ state: state,
61
+ size: 0
62
+ }
63
+
64
+ # Create copy-on-write snapshot
65
+ create_cow_snapshot(snapshot_path)
66
+
67
+ # Write metadata
68
+ File.write(File.join(snapshot_path, "snapshot.json"), JSON.generate(metadata))
69
+
70
+ # Calculate size
71
+ size = calculate_snapshot_size(snapshot_path)
72
+ metadata[:size] = size
73
+ metadata[:files] = snapshot_files(snapshot_path).to_h do |file|
74
+ [File.basename(file), Digest::SHA256.file(file).hexdigest]
75
+ end
76
+ File.write(File.join(snapshot_path, "snapshot.json"), JSON.generate(metadata))
77
+
78
+ # Update stats
79
+ @snapshots[snapshot_name] = metadata
80
+ @stats[:snapshots_created] += 1
81
+ @stats[:total_size_bytes] += size
82
+ @stats[:last_snapshot] = Time.now
83
+
84
+ # Clean old snapshots
85
+ clean_old_snapshots
86
+
87
+ { success: true, snapshot_name: snapshot_name, metadata: metadata }
88
+
89
+ rescue => e
90
+ FileUtils.rm_rf(snapshot_path) if Dir.exist?(snapshot_path)
91
+ { success: false, error: e.message }
92
+ end
93
+ end
94
+ end
95
+
96
+ def restore_snapshot(snapshot_name, options = {})
97
+ @lock.synchronize do
98
+ snapshot_path = snapshot_path_for_name(snapshot_name)
99
+ return { success: false, error: "Invalid snapshot name" } unless snapshot_path
100
+
101
+ unless Dir.exist?(snapshot_path)
102
+ return { success: false, error: "Snapshot does not exist" }
103
+ end
104
+
105
+ metadata_path = File.join(snapshot_path, "snapshot.json")
106
+ unless File.exist?(metadata_path)
107
+ return { success: false, error: "Snapshot metadata not found" }
108
+ end
109
+
110
+ begin
111
+ metadata = JSON.parse(File.read(metadata_path), symbolize_names: true)
112
+ verify_snapshot!(snapshot_path, metadata)
113
+
114
+ # Restore from snapshot
115
+ restore_cow_snapshot(snapshot_path)
116
+
117
+ @stats[:snapshots_restored] += 1
118
+
119
+ { success: true, snapshot_name: snapshot_name, metadata: metadata }
120
+
121
+ rescue => e
122
+ { success: false, error: e.message }
123
+ end
124
+ end
125
+ end
126
+
127
+ def list_snapshots
128
+ @lock.synchronize do
129
+ @snapshots.values.sort_by { |s| s[:created_at] }.reverse
130
+ end
131
+ end
132
+
133
+ def delete_snapshot(snapshot_name)
134
+ @lock.synchronize do
135
+ snapshot_path = snapshot_path_for_name(snapshot_name)
136
+ return { success: false, error: "Invalid snapshot name" } unless snapshot_path
137
+
138
+ unless Dir.exist?(snapshot_path)
139
+ return { success: false, error: "Snapshot does not exist" }
140
+ end
141
+
142
+ size = calculate_snapshot_size(snapshot_path)
143
+ FileUtils.rm_rf(snapshot_path)
144
+
145
+ @snapshots.delete(snapshot_name)
146
+ @stats[:snapshots_deleted] += 1
147
+ @stats[:total_size_bytes] -= size
148
+
149
+ { success: true }
150
+ end
151
+ end
152
+
153
+ def stats
154
+ @lock.synchronize do
155
+ @stats.merge({
156
+ snapshots: @snapshots.size,
157
+ snapshot_dir: @snapshot_dir,
158
+ max_snapshots: @max_snapshots
159
+ })
160
+ end
161
+ end
162
+
163
+ private
164
+
165
+ def snapshot_path_for_name(snapshot_name)
166
+ name = snapshot_name.to_s
167
+ return nil if name.empty? || name != File.basename(name)
168
+
169
+ root = File.expand_path(@snapshot_dir)
170
+ path = File.expand_path(File.join(root, name))
171
+ path == root || path.start_with?("#{root}#{File::SEPARATOR}") ? path : nil
172
+ end
173
+
174
+ def load_snapshots
175
+ Dir.glob(File.join(@snapshot_dir, "*")).each do |path|
176
+ next unless Dir.exist?(path)
177
+ metadata_path = File.join(path, "snapshot.json")
178
+ next unless File.exist?(metadata_path)
179
+
180
+ begin
181
+ metadata = JSON.parse(File.read(metadata_path), symbolize_names: true)
182
+ @snapshots[metadata[:name]] = metadata
183
+ rescue
184
+ raise SnapshotError, "Invalid snapshot metadata: #{metadata_path}"
185
+ end
186
+ end
187
+ end
188
+
189
+ def capture_database_state
190
+ {
191
+ tables: @engine.list_tables,
192
+ sequences: @engine.respond_to?(:list_sequences) ? @engine.list_sequences : [],
193
+ current_lsn: @engine.respond_to?(:current_lsn) ? @engine.current_lsn : nil,
194
+ wal_position: @engine.respond_to?(:wal_position) ? @engine.wal_position : nil,
195
+ active_transactions: @engine.respond_to?(:active_transactions) ? @engine.active_transactions : []
196
+ }
197
+ end
198
+
199
+ def create_cow_snapshot(snapshot_path)
200
+ data_files = @engine.respond_to?(:data_files) ? @engine.data_files : []
201
+ raise SnapshotError, "Engine returned no data files" if data_files.empty?
202
+ data_files.each do |file|
203
+ dest = File.join(snapshot_path, File.basename(file))
204
+ begin
205
+ File.link(file, dest)
206
+ rescue Errno::EEXIST
207
+ raise
208
+ rescue SystemCallError
209
+ FileUtils.cp(file, dest)
210
+ end
211
+ raise SnapshotError, "Snapshot file was not created: #{dest}" unless File.file?(dest)
212
+ end
213
+ end
214
+
215
+ def restore_cow_snapshot(snapshot_path)
216
+ metadata = JSON.parse(File.read(File.join(snapshot_path, "snapshot.json")), symbolize_names: true)
217
+ verify_snapshot!(snapshot_path, metadata)
218
+ data_files = snapshot_files(snapshot_path)
219
+ data_files.each do |file|
220
+ dest = File.join(@engine.data_dir, File.basename(file))
221
+ FileUtils.cp(file, dest)
222
+ raise SnapshotError, "Snapshot restore failed: #{dest}" unless FileUtils.compare_file(file, dest)
223
+ end
224
+ @engine.reload_from_disk if @engine.respond_to?(:reload_from_disk)
225
+ end
226
+
227
+ def snapshot_files(snapshot_path)
228
+ Dir.glob(File.join(snapshot_path, "*")).select { |file| File.file?(file) && File.basename(file) != "snapshot.json" }
229
+ end
230
+
231
+ def verify_snapshot!(snapshot_path, metadata)
232
+ files = metadata[:files]
233
+ raise SnapshotError, "Snapshot has no file manifest" unless files.is_a?(Hash) && files.any?
234
+ files.each do |name, checksum|
235
+ file = File.join(snapshot_path, name.to_s)
236
+ unless File.file?(file) && Digest::SHA256.file(file).hexdigest == checksum
237
+ raise SnapshotError, "Snapshot checksum mismatch: #{name}"
238
+ end
239
+ end
240
+ end
241
+
242
+ def calculate_snapshot_size(snapshot_path)
243
+ Dir.glob(File.join(snapshot_path, "**/*")).sum do |f|
244
+ File.size(f) if File.file?(f)
245
+ end || 0
246
+ end
247
+
248
+ def clean_old_snapshots
249
+ snapshots = list_snapshots
250
+
251
+ # Remove by retention days
252
+ cutoff = Time.now - @retention_days * 24 * 60 * 60
253
+ snapshots.select { |s| Time.parse(s[:created_at]) < cutoff }.each do |s|
254
+ delete_snapshot(s[:name])
255
+ end
256
+
257
+ # Remove by count
258
+ snapshots = list_snapshots
259
+ if snapshots.size > @max_snapshots
260
+ snapshots.last(snapshots.size - @max_snapshots).each do |s|
261
+ delete_snapshot(s[:name])
262
+ end
263
+ end
264
+ end
265
+ end
266
+ end
267
+ end
@@ -0,0 +1,279 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "time"
5
+ require "json"
6
+ require "digest"
7
+ require "monitor"
8
+ require "tmpdir"
9
+ require_relative "restore"
10
+ require_relative "../storage/engine"
11
+
12
+ module RubyDB
13
+ module Backup
14
+ # Verification - Verifies backup integrity
15
+ class Verification
16
+ attr_reader :stats
17
+
18
+ def initialize(config = {})
19
+ @config = config
20
+ @verification_dir = config[:verification_dir] || "verifications"
21
+ @stats = {
22
+ verifications: 0,
23
+ successful: 0,
24
+ failed: 0,
25
+ total_time_ms: 0,
26
+ avg_time_ms: 0,
27
+ last_verification: nil,
28
+ errors: 0
29
+ }
30
+ @lock = Monitor.new
31
+ @cache = {}
32
+
33
+ FileUtils.mkdir_p(@verification_dir)
34
+ end
35
+
36
+ def verify_backup(backup_path, options = {})
37
+ @lock.synchronize do
38
+ start_time = Time.now
39
+ @stats[:verifications] += 1
40
+
41
+ unless Dir.exist?(backup_path)
42
+ return { success: false, error: "Backup path does not exist" }
43
+ end
44
+
45
+ manifest_path = File.join(backup_path, "manifest.json")
46
+ unless File.exist?(manifest_path)
47
+ return { success: false, error: "Manifest not found" }
48
+ end
49
+
50
+ begin
51
+ metadata = JSON.parse(File.read(manifest_path), symbolize_names: true)
52
+
53
+ results = {
54
+ manifest_valid: true,
55
+ files_valid: true,
56
+ checksums_valid: true,
57
+ database_valid: true,
58
+ details: {}
59
+ }
60
+
61
+ # Verify manifest
62
+ results[:details][:manifest] = verify_manifest(metadata)
63
+
64
+ # Verify files
65
+ files_result = verify_files(backup_path, metadata[:files] || [])
66
+ results[:files_valid] = files_result[:valid]
67
+ results[:details][:files] = files_result
68
+
69
+ # Verify checksums
70
+ if metadata[:checksum]
71
+ checksum_result = verify_checksums(backup_path, metadata[:checksum])
72
+ results[:checksums_valid] = checksum_result[:valid]
73
+ results[:details][:checksums] = checksum_result
74
+ end
75
+
76
+ # Verify database consistency (restore to temp and verify)
77
+ if options[:verify_database] != false
78
+ db_result = verify_database(backup_path, metadata)
79
+ results[:database_valid] = db_result[:valid]
80
+ results[:details][:database] = db_result
81
+ end
82
+
83
+ success = results[:manifest_valid] && results[:files_valid] &&
84
+ results[:checksums_valid] && results[:database_valid]
85
+
86
+ # Save verification result
87
+ save_verification_result(backup_path, results)
88
+
89
+ elapsed_ms = (Time.now - start_time) * 1000
90
+ @stats[:total_time_ms] += elapsed_ms
91
+ @stats[:avg_time_ms] = @stats[:total_time_ms] / @stats[:verifications]
92
+
93
+ if success
94
+ @stats[:successful] += 1
95
+ else
96
+ @stats[:failed] += 1
97
+ end
98
+
99
+ @stats[:last_verification] = Time.now
100
+
101
+ { success: success, results: results, elapsed_ms: elapsed_ms }
102
+
103
+ rescue => e
104
+ @stats[:errors] += 1
105
+ @stats[:failed] += 1
106
+ { success: false, error: e.message }
107
+ end
108
+ end
109
+ end
110
+
111
+ def verify_restore(backup_path, options = {})
112
+ @lock.synchronize do
113
+ # Create temporary restore destination
114
+ temp_dir = File.join(@verification_dir, "temp_restore_#{Time.now.to_i}")
115
+ FileUtils.mkdir_p(temp_dir)
116
+
117
+ begin
118
+ manifest_path = File.join(backup_path, "manifest.json")
119
+ metadata = JSON.parse(File.read(manifest_path), symbolize_names: true)
120
+ # Restore to temporary location
121
+ restore = Restore.new(nil, @config)
122
+ restore_result = restore.restore(backup_path, destination: temp_dir)
123
+
124
+ unless restore_result[:success]
125
+ return { success: false, error: "Restore failed: #{restore_result[:error]}" }
126
+ end
127
+
128
+ database_path = Dir.glob(File.join(temp_dir, "*.rdb")).first
129
+ return { success: false, error: "Restored database file not found" } unless database_path
130
+
131
+ # Verify that the extracted files can be opened by the real engine,
132
+ # not merely that their checksums and names look correct.
133
+ engine = Storage::Engine.new(database_path, auto_cleanup: false, auto_vacuum: false)
134
+ actual_tables = engine.list_tables.map(&:to_s).sort
135
+ expected_tables = Array(metadata[:tables]).map(&:to_s).sort
136
+ unless actual_tables == expected_tables
137
+ return { success: false, error: "Restored catalog mismatch", expected_tables: expected_tables, actual_tables: actual_tables }
138
+ end
139
+ row_counts = actual_tables.to_h do |table|
140
+ [table, engine.table_row_count(table)]
141
+ end
142
+ engine.close
143
+ {
144
+ success: true,
145
+ restored_path: temp_dir,
146
+ tables: actual_tables,
147
+ row_counts: row_counts,
148
+ message: "Restore verification successful"
149
+ }
150
+
151
+ rescue => e
152
+ { success: false, error: e.message }
153
+
154
+ ensure
155
+ engine&.close if defined?(engine) && engine&.open?
156
+ # Clean up temp directory
157
+ FileUtils.rm_rf(temp_dir) if Dir.exist?(temp_dir)
158
+ end
159
+ end
160
+ end
161
+
162
+ def verify_chain(base_backup, incrementals, options = {})
163
+ @lock.synchronize do
164
+ start_time = Time.now
165
+ results = []
166
+
167
+ # Verify base backup
168
+ base_result = verify_backup(base_backup, options)
169
+ results << { backup: base_backup, result: base_result }
170
+
171
+ # Verify each incremental
172
+ incrementals.each do |inc|
173
+ inc_result = verify_backup(inc, options)
174
+ results << { backup: inc, result: inc_result }
175
+ end
176
+
177
+ # Verify chain integrity
178
+ chain_valid = results.all? { |r| r[:result][:success] }
179
+
180
+ {
181
+ success: chain_valid,
182
+ results: results,
183
+ elapsed_ms: (Time.now - start_time) * 1000
184
+ }
185
+ end
186
+ end
187
+
188
+ def stats
189
+ @lock.synchronize do
190
+ @stats.merge({
191
+ verification_dir: @verification_dir,
192
+ cache_size: @cache.size
193
+ })
194
+ end
195
+ end
196
+
197
+ private
198
+
199
+ def verify_manifest(metadata)
200
+ required_fields = [:name, :type, :created_at, :files]
201
+ missing = required_fields - metadata.keys
202
+
203
+ if missing.any?
204
+ { valid: false, error: "Missing required fields: #{missing.join(', ')}" }
205
+ else
206
+ { valid: true }
207
+ end
208
+ end
209
+
210
+ def verify_files(backup_path, files)
211
+ missing = []
212
+ corrupted = []
213
+
214
+ files.each do |file|
215
+ file_path = File.join(backup_path, file)
216
+ unless File.exist?(file_path)
217
+ missing << file
218
+ next
219
+ end
220
+
221
+ if File.size(file_path) == 0
222
+ corrupted << file
223
+ end
224
+ end
225
+
226
+ if missing.any?
227
+ { valid: false, missing: missing, message: "Missing files: #{missing.join(', ')}" }
228
+ elsif corrupted.any?
229
+ { valid: false, corrupted: corrupted, message: "Corrupted files: #{corrupted.join(', ')}" }
230
+ else
231
+ { valid: true, file_count: files.size }
232
+ end
233
+ end
234
+
235
+ def verify_checksums(backup_path, expected_checksum)
236
+ digest = Digest::SHA256.new
237
+ Dir.glob(File.join(backup_path, "**/*")).select { |path| File.file?(path) }.sort.each do |path|
238
+ relative = path.delete_prefix("#{backup_path}#{File::SEPARATOR}")
239
+ next if relative == "manifest.json"
240
+ digest.update(relative)
241
+ digest.update(File.binread(path))
242
+ end
243
+ actual = digest.hexdigest
244
+ { valid: actual == expected_checksum, expected: expected_checksum, actual: actual }
245
+ end
246
+
247
+ def verify_database(backup_path, metadata)
248
+ temp_dir = Dir.mktmpdir("rubydb_verify_")
249
+ begin
250
+ restore_result = Restore.new(nil, @config).restore(backup_path, destination: temp_dir)
251
+ return { valid: false, error: restore_result[:error] } unless restore_result[:success]
252
+
253
+ data_path = Dir.glob(File.join(temp_dir, "*.rdb")).first
254
+ return { valid: false, error: "Restored database file not found" } unless data_path
255
+
256
+ engine = Storage::Engine.new(data_path, auto_vacuum: false)
257
+ actual_tables = engine.list_tables.map(&:to_s).sort
258
+ expected_tables = Array(metadata[:tables]).map(&:to_s).sort
259
+ valid = actual_tables == expected_tables
260
+ { valid: valid, expected_tables: expected_tables, actual_tables: actual_tables }
261
+ rescue StandardError => e
262
+ { valid: false, error: e.message }
263
+ ensure
264
+ engine&.close
265
+ FileUtils.rm_rf(temp_dir) if Dir.exist?(temp_dir)
266
+ end
267
+ end
268
+
269
+ def save_verification_result(backup_path, results)
270
+ result_path = File.join(@verification_dir, "#{File.basename(backup_path)}_verify.json")
271
+ File.write(result_path, JSON.generate({
272
+ backup: File.basename(backup_path),
273
+ timestamp: Time.now.iso8601,
274
+ results: results
275
+ }))
276
+ end
277
+ end
278
+ end
279
+ end