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,435 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+ require "concurrent"
5
+
6
+ # Import all transaction components
7
+ require_relative "transaction"
8
+ require_relative "transaction_id"
9
+ require_relative "lock_manager"
10
+ require_relative "commit_manager"
11
+ require_relative "transaction_log"
12
+ require_relative "isolation"
13
+ require_relative "savepoint"
14
+
15
+ # Import engine for rollback operations
16
+ require_relative "../storage/engine"
17
+
18
+ module RubyDB
19
+ module Transactions
20
+ # TransactionManager - Manages all database transactions
21
+ class TransactionManager
22
+ attr_reader :active_transactions, :committed_transactions, :aborted_transactions
23
+ attr_reader :lock_manager, :commit_manager, :stats
24
+
25
+ def initialize(engine, config = {})
26
+ @engine = engine
27
+ @config = config
28
+ @active_transactions = {}
29
+ @committed_transactions = []
30
+ @aborted_transactions = []
31
+ @transaction_log = TransactionLog.new(config[:log_path])
32
+ @lock_manager = LockManager.new(config.merge(transaction_manager: self))
33
+ @commit_manager = CommitManager.new(self, config)
34
+ @stats = {
35
+ transactions_started: 0,
36
+ transactions_committed: 0,
37
+ transactions_aborted: 0,
38
+ transactions_prepared: 0,
39
+ transactions_in_doubt: 0,
40
+ deadlocks_detected: 0,
41
+ deadlocks_resolved: 0,
42
+ lock_timeouts: 0,
43
+ total_commit_time_ms: 0,
44
+ total_rollback_time_ms: 0
45
+ }
46
+ @next_txn_id = 1
47
+ @lock = Mutex.new
48
+ @cleanup_thread = nil
49
+
50
+ # Start cleanup thread
51
+ start_cleanup_thread if config[:auto_cleanup] != false
52
+
53
+ # Recover transactions on startup
54
+ recover if config[:recovery] != false
55
+ end
56
+
57
+ def begin_transaction(options = {})
58
+ @lock.synchronize do
59
+ txn = Transaction.new({
60
+ id: generate_id,
61
+ isolation_level: options[:isolation_level] || :read_committed,
62
+ read_only: options[:read_only] || false,
63
+ timeout: options[:timeout] || 30,
64
+ priority: options[:priority] || 0,
65
+ name: options[:name]
66
+ })
67
+
68
+ @active_transactions[txn.id] = txn
69
+ @stats[:transactions_started] += 1
70
+
71
+ # Log transaction start
72
+ @transaction_log.log_start(txn)
73
+
74
+ txn
75
+ end
76
+ end
77
+
78
+ def commit_transaction(transaction)
79
+ @lock.synchronize do
80
+ return false unless transaction && transaction.active?
81
+
82
+ start_time = Time.now
83
+
84
+ # Check for conflicts
85
+ if detect_conflicts(transaction)
86
+ @stats[:deadlocks_detected] += 1
87
+ return false
88
+ end
89
+
90
+ # Prepare transaction
91
+ transaction.prepare
92
+ @stats[:transactions_prepared] += 1
93
+
94
+ # Log prepare
95
+ @transaction_log.log_prepare(transaction)
96
+
97
+ # Commit
98
+ @commit_manager.commit(transaction)
99
+
100
+ # Update stats
101
+ elapsed_ms = ((Time.now - start_time) * 1000).round(2)
102
+ @stats[:transactions_committed] += 1
103
+ @stats[:total_commit_time_ms] += elapsed_ms
104
+
105
+ # Log commit
106
+ @transaction_log.log_commit(transaction)
107
+
108
+ # Move to committed
109
+ @active_transactions.delete(transaction.id)
110
+ @committed_transactions << transaction
111
+
112
+ # Clean old committed transactions
113
+ cleanup_committed_transactions if @committed_transactions.size > 1000
114
+
115
+ true
116
+ end
117
+ end
118
+
119
+ def rollback_transaction(transaction)
120
+ @lock.synchronize do
121
+ return false unless transaction && transaction.active?
122
+
123
+ start_time = Time.now
124
+
125
+ # Rollback changes
126
+ rollback_changes(transaction)
127
+
128
+ # Abort transaction
129
+ transaction.abort
130
+ @stats[:transactions_aborted] += 1
131
+
132
+ elapsed_ms = ((Time.now - start_time) * 1000).round(2)
133
+ @stats[:total_rollback_time_ms] += elapsed_ms
134
+
135
+ # Log rollback
136
+ @transaction_log.log_rollback(transaction)
137
+
138
+ # Move to aborted
139
+ @active_transactions.delete(transaction.id)
140
+ @aborted_transactions << transaction
141
+
142
+ # Clean old aborted transactions
143
+ cleanup_aborted_transactions if @aborted_transactions.size > 1000
144
+
145
+ true
146
+ end
147
+ end
148
+
149
+ def get_transaction(id)
150
+ @lock.synchronize do
151
+ @active_transactions[id] ||
152
+ @committed_transactions.find { |t| t.id == id } ||
153
+ @aborted_transactions.find { |t| t.id == id }
154
+ end
155
+ end
156
+
157
+ def active_transaction_count
158
+ @active_transactions.size
159
+ end
160
+
161
+ def find_latest_transaction
162
+ @lock.synchronize do
163
+ all = @committed_transactions + @aborted_transactions
164
+ all.max_by { |t| t.start_time }
165
+ end
166
+ end
167
+
168
+ def detect_deadlock
169
+ cycles = detect_cycles(build_wait_for_graph)
170
+ return false if cycles.empty?
171
+
172
+ @lock.synchronize { @stats[:deadlocks_detected] += cycles.size }
173
+ cycles.each { |cycle| resolve_deadlock(cycle) }
174
+ true
175
+ end
176
+
177
+ def resolve_deadlock(cycle)
178
+ transactions = Array(cycle).filter_map do |entry|
179
+ if entry.respond_to?(:priority)
180
+ entry
181
+ else
182
+ @lock.synchronize { @active_transactions[entry] }
183
+ end
184
+ end
185
+ victim = @lock.synchronize do
186
+ transactions.min_by { |transaction| transaction.priority.to_i }
187
+ end
188
+ return nil unless victim
189
+
190
+ rollback_transaction(victim)
191
+ @lock.synchronize { @stats[:deadlocks_resolved] += 1 }
192
+ victim
193
+ end
194
+
195
+ def acquire_lock(transaction, table_name, row_id, lock_type)
196
+ valid = @lock.synchronize do
197
+ return false unless transaction.active?
198
+
199
+ # Check for timeout
200
+ if transaction.expired?
201
+ @stats[:lock_timeouts] += 1
202
+ return false
203
+ end
204
+ true
205
+ end
206
+ return false unless valid
207
+
208
+ # Do not hold the transaction-manager mutex while a lock wait blocks;
209
+ # deadlock resolution must be able to return through this method.
210
+ result = @lock_manager.acquire_lock(
211
+ transaction,
212
+ table_name,
213
+ row_id,
214
+ lock_type,
215
+ transaction.timeout
216
+ )
217
+
218
+ if result
219
+ transaction.add_locked_row(table_name, row_id, lock_type)
220
+ elsif @lock_manager.deadlock_victim?(transaction.id)
221
+ @lock.synchronize do
222
+ @stats[:deadlocks_detected] += 1
223
+ end
224
+ rollback_transaction(transaction)
225
+ @lock_manager.clear_deadlock_victim(transaction.id)
226
+ end
227
+
228
+ result
229
+ end
230
+
231
+ def release_locks(transaction)
232
+ @lock.synchronize do
233
+ @lock_manager.release_locks(transaction)
234
+ transaction.locked_rows.clear
235
+ true
236
+ end
237
+ end
238
+
239
+ def release_all_locks
240
+ @lock.synchronize do
241
+ @lock_manager.release_all_locks
242
+ @active_transactions.each do |_, txn|
243
+ txn.locked_rows.clear
244
+ end
245
+ true
246
+ end
247
+ end
248
+
249
+ def recover
250
+ @lock.synchronize do
251
+ # Recover from transaction log
252
+ recovery = @transaction_log.recover
253
+ entries = recovery[:entries] || []
254
+
255
+ entries.each do |entry|
256
+ case entry[:type]
257
+ when :prepare
258
+ # Transaction was prepared but not committed
259
+ txn = get_transaction(entry[:transaction_id])
260
+ if txn && txn.prepared?
261
+ # Commit if we can
262
+ if can_commit?(txn)
263
+ commit_transaction(txn)
264
+ else
265
+ txn.mark_in_doubt
266
+ @stats[:transactions_in_doubt] += 1
267
+ end
268
+ end
269
+ when :commit
270
+ # Already committed
271
+ when :rollback
272
+ # Already rolled back
273
+ end
274
+ end
275
+ end
276
+ end
277
+
278
+ def stats
279
+ @lock.synchronize do
280
+ @stats.merge({
281
+ active_count: @active_transactions.size,
282
+ committed_count: @committed_transactions.size,
283
+ aborted_count: @aborted_transactions.size,
284
+ in_doubt_count: @active_transactions.count { |_, t| t.in_doubt? },
285
+ avg_commit_time: average_commit_time,
286
+ avg_rollback_time: average_rollback_time,
287
+ locks_held: @lock_manager.total_locks,
288
+ lock_waiting: @lock_manager.waiting_transactions
289
+ })
290
+ end
291
+ end
292
+
293
+ private
294
+
295
+ def generate_id
296
+ "txn_#{Time.now.to_i}_#{@next_txn_id}"
297
+ ensure
298
+ @next_txn_id += 1
299
+ end
300
+
301
+ def rollback_changes(transaction)
302
+ # Rollback in reverse order
303
+ transaction.modified_rows.each do |row_id, info|
304
+ @engine.update_row(info[:table], row_id, info[:old])
305
+ end
306
+ transaction.changes.reverse.each do |change|
307
+ undo_change(change)
308
+ end
309
+ end
310
+
311
+ def undo_change(change)
312
+ case change[:type]
313
+ when :insert
314
+ # Delete inserted row
315
+ @engine.delete_row(change[:table], change[:row_id])
316
+ when :update
317
+ # Restore old values
318
+ @engine.update_row(change[:table], change[:row_id], change[:old_values])
319
+ when :delete
320
+ # Reinsert deleted row
321
+ @engine.insert_row(change[:table], change[:columns], change[:row_data])
322
+ end
323
+ end
324
+
325
+ def detect_conflicts(transaction)
326
+ # Check for write-write conflicts
327
+ transaction.modified_rows.each do |row_id, info|
328
+ @active_transactions.each do |_, other_txn|
329
+ next if other_txn.id == transaction.id
330
+
331
+ if other_txn.modified_rows.key?(row_id)
332
+ return true
333
+ end
334
+ end
335
+ end
336
+
337
+ false
338
+ end
339
+
340
+ def build_wait_for_graph
341
+ @lock_manager.wait_for_graph
342
+ end
343
+
344
+ def detect_cycles(graph)
345
+ cycles = []
346
+ visited = Set.new
347
+ active = Set.new
348
+ path = []
349
+
350
+ graph.each_key do |node|
351
+ detect_cycle_dfs(node, graph, visited, active, path, cycles) unless visited.include?(node)
352
+ end
353
+ cycles
354
+ end
355
+
356
+ def detect_cycle_dfs(node, graph, visited, active, path, cycles)
357
+ if active.include?(node)
358
+ start = path.index(node)
359
+ cycle = path[start..] + [node]
360
+ cycles << cycle unless cycles.include?(cycle)
361
+ return
362
+ end
363
+ return if visited.include?(node)
364
+
365
+ visited.add(node)
366
+ active.add(node)
367
+ path << node
368
+
369
+ graph[node]&.each { |neighbor| detect_cycle_dfs(neighbor, graph, visited, active, path, cycles) }
370
+
371
+ path.pop
372
+ active.delete(node)
373
+ end
374
+
375
+ def can_commit?(transaction)
376
+ # Check if all prerequisites are met
377
+ transaction.modified_rows.each do |_, info|
378
+ # Check if no other active transaction modified the same row
379
+ @active_transactions.each do |_, other|
380
+ next if other.id == transaction.id
381
+ if other.modified_rows.key?(info[:row_id])
382
+ return false
383
+ end
384
+ end
385
+ end
386
+
387
+ true
388
+ end
389
+
390
+ def cleanup_committed_transactions
391
+ if @committed_transactions.size > 1000
392
+ @committed_transactions = @committed_transactions.last(100)
393
+ end
394
+ end
395
+
396
+ def cleanup_aborted_transactions
397
+ if @aborted_transactions.size > 1000
398
+ @aborted_transactions = @aborted_transactions.last(100)
399
+ end
400
+ end
401
+
402
+ def average_commit_time
403
+ return 0 if @stats[:transactions_committed] == 0
404
+ @stats[:total_commit_time_ms] / @stats[:transactions_committed]
405
+ end
406
+
407
+ def average_rollback_time
408
+ return 0 if @stats[:transactions_aborted] == 0
409
+ @stats[:total_rollback_time_ms] / @stats[:transactions_aborted]
410
+ end
411
+
412
+ def start_cleanup_thread
413
+ @cleanup_thread = Thread.new do
414
+ loop do
415
+ sleep(60) # Run every minute
416
+
417
+ begin
418
+ # Cleanup expired transactions
419
+ @active_transactions.each do |id, txn|
420
+ if txn.expired?
421
+ rollback_transaction(txn)
422
+ end
423
+ end
424
+
425
+ # Detect deadlocks
426
+ detect_deadlock
427
+ rescue => e
428
+ # Log error but continue
429
+ end
430
+ end
431
+ end
432
+ end
433
+ end
434
+ end
435
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module Types
5
+ # 64-bit signed integer
6
+ class BigInt < Type
7
+ RANGE = (-9_223_372_036_854_775_808..9_223_372_036_854_775_807).freeze
8
+ SIZE = 8
9
+
10
+ def initialize
11
+ super(:bigint, size: SIZE)
12
+ end
13
+
14
+ def serialize(value)
15
+ validate(value)
16
+ [value.to_i].pack("q>") # Big-endian 64-bit signed
17
+ end
18
+
19
+ def deserialize(bytes)
20
+ return nil if bytes.nil? || bytes.empty?
21
+ bytes.unpack("q>").first
22
+ end
23
+
24
+ def validate(value)
25
+ return true if value.nil?
26
+ raise ConstraintError, "Invalid bigint: #{value}" unless value.is_a?(::Integer)
27
+ raise ConstraintError, "BigInt out of range: #{value}" unless RANGE.include?(value)
28
+ true
29
+ end
30
+
31
+ def default
32
+ 0
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module Types
5
+ # Binary large object (arbitrary binary data)
6
+ class Blob < Type
7
+ def initialize
8
+ super(:blob)
9
+ end
10
+
11
+ def serialize(value)
12
+ validate(value)
13
+ return "".b if value.nil?
14
+ value.is_a?(String) ? value.b : value.to_s.b
15
+ end
16
+
17
+ def deserialize(bytes)
18
+ return nil if bytes.nil? || bytes.empty?
19
+ bytes.b
20
+ end
21
+
22
+ def validate(value)
23
+ return true if value.nil?
24
+ raise ConstraintError, "Invalid blob: #{value}" unless value.respond_to?(:to_s)
25
+ true
26
+ end
27
+
28
+ def default
29
+ "".b
30
+ end
31
+
32
+ def storage_size
33
+ nil # Variable length
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module Types
5
+ # Boolean type (true/false)
6
+ class Boolean < Type
7
+ SIZE = 1
8
+
9
+ def initialize
10
+ super(:boolean, size: SIZE)
11
+ end
12
+
13
+ def serialize(value)
14
+ validate(value)
15
+ value ? "\x01".b : "\x00".b
16
+ end
17
+
18
+ def deserialize(bytes)
19
+ return nil if bytes.nil? || bytes.empty?
20
+ bytes.unpack("C").first == 1
21
+ end
22
+
23
+ def validate(value)
24
+ return true if value.nil?
25
+ raise ConstraintError, "Invalid boolean: #{value}" unless [true, false].include?(value)
26
+ true
27
+ end
28
+
29
+ def default
30
+ false
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+
5
+ module RubyDB
6
+ module Types
7
+ # Date type (year, month, day)
8
+ class Date < Type
9
+ SIZE = 4 # Days since epoch (stored as integer)
10
+
11
+ def initialize
12
+ super(:date, size: SIZE)
13
+ end
14
+
15
+ def serialize(value)
16
+ validate(value)
17
+ return "\x00" * SIZE if value.nil?
18
+ days = value - Date.new(1970, 1, 1)
19
+ [days.to_i].pack("l>")
20
+ end
21
+
22
+ def deserialize(bytes)
23
+ return nil if bytes.nil? || bytes.empty?
24
+ days = bytes.unpack("l>").first
25
+ Date.new(1970, 1, 1) + days
26
+ end
27
+
28
+ def validate(value)
29
+ return true if value.nil?
30
+ raise ConstraintError, "Invalid date: #{value}" unless value.is_a?(::Date) || value.is_a?(::DateTime)
31
+ true
32
+ end
33
+
34
+ def default
35
+ Date.today
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bigdecimal"
4
+
5
+ module RubyDB
6
+ module Types
7
+ # Arbitrary precision decimal
8
+ class Decimal < Type
9
+ attr_reader :precision, :scale
10
+
11
+ def initialize(precision: 10, scale: 2)
12
+ super(:decimal, precision: precision, scale: scale)
13
+ @precision = precision
14
+ @scale = scale
15
+ end
16
+
17
+ def serialize(value)
18
+ validate(value)
19
+ bd = value.is_a?(BigDecimal) ? value : BigDecimal(value.to_s)
20
+ bd.to_s("F").encode("UTF-8")
21
+ end
22
+
23
+ def deserialize(bytes)
24
+ return nil if bytes.nil? || bytes.empty?
25
+ str = bytes.force_encoding("UTF-8")
26
+ BigDecimal(str)
27
+ end
28
+
29
+ def validate(value)
30
+ return true if value.nil?
31
+ bd = value.is_a?(BigDecimal) ? value : BigDecimal(value.to_s)
32
+ raise ConstraintError, "Invalid decimal: #{value}" unless bd
33
+ true
34
+ rescue ArgumentError, TypeError
35
+ raise ConstraintError, "Invalid decimal: #{value}"
36
+ end
37
+
38
+ def default
39
+ BigDecimal("0")
40
+ end
41
+
42
+ def storage_size
43
+ # Variable length, stored as string
44
+ nil
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module Types
5
+ # 64-bit floating point (Double precision)
6
+ class Float < Type
7
+ SIZE = 8
8
+
9
+ def initialize
10
+ super(:float, size: SIZE)
11
+ end
12
+
13
+ def serialize(value)
14
+ validate(value)
15
+ [value.to_f].pack("E") # Big-endian double
16
+ end
17
+
18
+ def deserialize(bytes)
19
+ return nil if bytes.nil? || bytes.empty?
20
+ bytes.unpack("E").first
21
+ end
22
+
23
+ def validate(value)
24
+ return true if value.nil?
25
+ raise ConstraintError, "Invalid float: #{value}" unless value.is_a?(::Numeric)
26
+ true
27
+ end
28
+
29
+ def default
30
+ 0.0
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module Types
5
+ # 32-bit signed integer
6
+ class Integer < Type
7
+ RANGE = (-2_147_483_648..2_147_483_647).freeze
8
+ SIZE = 4
9
+
10
+ def initialize
11
+ super(:integer, size: SIZE)
12
+ end
13
+
14
+ def serialize(value)
15
+ validate(value)
16
+ [value.to_i].pack("l>") # Big-endian 32-bit signed
17
+ end
18
+
19
+ def deserialize(bytes)
20
+ return nil if bytes.nil? || bytes.empty?
21
+ bytes.unpack("l>").first
22
+ end
23
+
24
+ def validate(value)
25
+ return true if value.nil?
26
+ raise ConstraintError, "Invalid integer: #{value}" unless value.is_a?(::Integer)
27
+ raise ConstraintError, "Integer out of range: #{value}" unless RANGE.include?(value)
28
+ true
29
+ end
30
+
31
+ def default
32
+ 0
33
+ end
34
+ end
35
+ end
36
+ end