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,398 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+ require "json"
5
+ require "fileutils"
6
+ require "time"
7
+ require "monitor"
8
+ require_relative "version"
9
+
10
+ module RubyDB
11
+ module MVCC
12
+ # VersionStore - Stores and manages all versions of rows
13
+ class VersionStore
14
+ attr_reader :stats, :version_count, :row_count
15
+
16
+ def initialize(config = {})
17
+ @versions = {} # row_id => [versions]
18
+ @version_map = {} # version_id => version
19
+ @version_keys = {} # version_id => storage key
20
+ @active_versions = Set.new
21
+ @stats = {
22
+ versions_created: 0,
23
+ versions_committed: 0,
24
+ versions_aborted: 0,
25
+ versions_deleted: 0,
26
+ versions_pruned: 0,
27
+ cache_hits: 0,
28
+ cache_misses: 0
29
+ }
30
+ @version_count = 0
31
+ @row_count = 0
32
+ @max_versions_per_row = config[:max_versions_per_row] || 100
33
+ @lock = Monitor.new
34
+ @persistence_path = config[:persistence_path]
35
+ @dirty = false
36
+ load if @persistence_path && File.exist?(@persistence_path)
37
+ end
38
+
39
+ def create_version(row_id, data, transaction_id, key: row_id)
40
+ @lock.synchronize do
41
+ # Get current latest version
42
+ versions = @versions[key] || []
43
+ prev_version_id = versions.last&.version_id
44
+
45
+ # Create new version
46
+ version = Version.new(row_id, data, transaction_id, prev_version_id)
47
+
48
+ # Store version
49
+ @versions[key] ||= []
50
+ @versions[key] << version
51
+ @version_map[version.version_id] = version
52
+ @version_keys[version.version_id] = key
53
+
54
+ # Update active versions
55
+ @active_versions.add(version.version_id)
56
+
57
+ @version_count += 1
58
+ @row_count = @versions.size
59
+ @stats[:versions_created] += 1
60
+ @dirty = true
61
+
62
+ version
63
+ end
64
+ end
65
+
66
+ def commit_version(version, commit_id = nil)
67
+ @lock.synchronize do
68
+ version.commit(commit_id)
69
+ @active_versions.delete(version.version_id)
70
+ @stats[:versions_committed] += 1
71
+ @dirty = true
72
+ end
73
+ end
74
+
75
+ def abort_version(version)
76
+ @lock.synchronize do
77
+ version.abort
78
+ @active_versions.delete(version.version_id)
79
+
80
+ # Remove from version chain if it was the latest
81
+ key = @version_keys[version.version_id] || version.row_id
82
+ versions = @versions[key]
83
+ if versions && versions.last&.version_id == version.version_id
84
+ versions.pop
85
+ @version_map.delete(version.version_id)
86
+ @version_keys.delete(version.version_id)
87
+ end
88
+
89
+ @stats[:versions_aborted] += 1
90
+ @dirty = true
91
+ end
92
+ end
93
+
94
+ def get_latest_version(row_id, transaction_id = nil, snapshot = nil, key: row_id)
95
+ @lock.synchronize do
96
+ # Check cache for snapshot
97
+ cache_key = "#{row_id}:#{transaction_id}:#{snapshot&.id}"
98
+
99
+ versions = @versions[key]
100
+ return nil unless versions
101
+
102
+ # Get the latest committed version visible to transaction
103
+ versions.reverse_each do |version|
104
+ if snapshot
105
+ if version.visible_to?(transaction_id, snapshot)
106
+ return nil if version.is_deleted
107
+ @stats[:cache_hits] += 1
108
+ return version
109
+ end
110
+ elsif version.is_committed
111
+ # A nil transaction ID means "latest committed". Otherwise a
112
+ # version is visible only when its commit ID is not newer than
113
+ # the reader's transaction ID.
114
+ if transaction_id.nil? || version.commit_id.nil? || version.commit_id <= transaction_id
115
+ @stats[:cache_hits] += 1
116
+ return version.is_deleted ? nil : version
117
+ end
118
+ end
119
+ end
120
+
121
+ @stats[:cache_misses] += 1
122
+ nil
123
+ end
124
+ end
125
+
126
+ def get_version_at_time(row_id, time)
127
+ @lock.synchronize do
128
+ versions = @versions[row_id]
129
+ return nil unless versions
130
+
131
+ # Find version that was active at the given time
132
+ versions.reverse_each do |version|
133
+ if version.created_at <= time
134
+ return version
135
+ end
136
+ end
137
+
138
+ nil
139
+ end
140
+ end
141
+
142
+ def get_all_versions(row_id)
143
+ @lock.synchronize do
144
+ @versions[row_id] || []
145
+ end
146
+ end
147
+
148
+ def keys(prefix = nil)
149
+ @lock.synchronize do
150
+ values = @versions.keys
151
+ prefix ? values.select { |key| key.to_s.start_with?(prefix.to_s) } : values
152
+ end
153
+ end
154
+
155
+ def validate_serializable!(snapshot, read_keys, write_keys, read_predicates: [])
156
+ @lock.synchronize do
157
+ dependencies = Set.new(read_keys.to_a + write_keys.to_a)
158
+ @versions.each do |key, versions|
159
+ predicate_conflict = read_predicates.any? { |predicate| key.to_s.start_with?(predicate.to_s) }
160
+ next unless dependencies.include?(key) || predicate_conflict
161
+
162
+ versions.each do |version|
163
+ next unless version.is_committed
164
+ next unless version.commit_id && version.commit_id.to_i > snapshot.transaction_id.to_i
165
+ next if snapshot.committed_transactions.include?(version.commit_id)
166
+
167
+ raise RubyDB::DatabaseError,
168
+ "serialization failure: concurrent commit conflicts with #{key.inspect}"
169
+ end
170
+ end
171
+ true
172
+ end
173
+ end
174
+
175
+ def get_version(version_id)
176
+ @lock.synchronize do
177
+ @version_map[version_id]
178
+ end
179
+ end
180
+
181
+ def delete_version(version)
182
+ @lock.synchronize do
183
+ version.mark_deleted
184
+ @active_versions.delete(version.version_id)
185
+ @stats[:versions_deleted] += 1
186
+ @dirty = true
187
+ end
188
+ end
189
+
190
+ def prune_versions(row_id, keep_count = nil)
191
+ @lock.synchronize do
192
+ versions = @versions[row_id]
193
+ return 0 unless versions
194
+
195
+ keep_count ||= @max_versions_per_row
196
+ pruned = 0
197
+
198
+ # Remove old versions (keeping the most recent ones)
199
+ while versions.size > keep_count
200
+ old_version = versions.shift
201
+ next unless old_version
202
+
203
+ # Only prune committed versions that are not active
204
+ if old_version.is_committed && !@active_versions.include?(old_version.version_id)
205
+ @version_map.delete(old_version.version_id)
206
+ @version_keys.delete(old_version.version_id)
207
+ @stats[:versions_pruned] += 1
208
+ pruned += 1
209
+ else
210
+ # Can't prune active versions, move to end
211
+ versions << old_version
212
+ break
213
+ end
214
+ end
215
+
216
+ @dirty = true if pruned > 0
217
+ pruned
218
+ end
219
+ end
220
+
221
+ def active_version_count
222
+ @active_versions.size
223
+ end
224
+
225
+ def prune_all(max_versions = nil)
226
+ @lock.synchronize do
227
+ total_pruned = 0
228
+ @versions.keys.each do |row_id|
229
+ versions = @versions[row_id]
230
+ keep_count = max_versions || @max_versions_per_row
231
+ while versions && versions.size > keep_count
232
+ version = versions.first
233
+ break unless version.is_committed && !@active_versions.include?(version.version_id)
234
+
235
+ versions.shift
236
+ @version_map.delete(version.version_id)
237
+ @version_keys.delete(version.version_id)
238
+ @stats[:versions_pruned] += 1
239
+ total_pruned += 1
240
+ end
241
+ end
242
+ @dirty = true if total_pruned > 0
243
+ total_pruned
244
+ end
245
+ end
246
+
247
+ def vacuum(min_active_transaction_id: nil)
248
+ @lock.synchronize do
249
+ # Remove versions that are no longer needed
250
+ removed = 0
251
+ @versions.each do |row_id, versions|
252
+ # Keep the newest version as the base visible version. Older
253
+ # committed history may be removed only before the oldest active
254
+ # transaction; active and uncommitted versions are never removed.
255
+ newest = versions.last
256
+ protected_base = versions.reverse.find(&:is_committed)
257
+ versions.delete_if do |version|
258
+ removable = version.is_aborted ||
259
+ (version != newest && version != protected_base && version.is_committed &&
260
+ min_active_transaction_id && version.commit_id &&
261
+ version.commit_id < min_active_transaction_id)
262
+ if removable && !@active_versions.include?(version.version_id)
263
+ @version_map.delete(version.version_id)
264
+ removed += 1
265
+ true
266
+ else
267
+ false
268
+ end
269
+ end
270
+
271
+ # Remove empty arrays
272
+ if versions.empty?
273
+ @versions.delete(row_id)
274
+ end
275
+ end
276
+
277
+ @row_count = @versions.size
278
+ @stats[:versions_pruned] += removed
279
+ @dirty = true if removed > 0
280
+ removed
281
+ end
282
+ end
283
+
284
+ def serialize
285
+ @lock.synchronize do
286
+ data = {
287
+ versions: {},
288
+ version_count: @version_count,
289
+ row_count: @row_count,
290
+ active_versions: @active_versions.to_a,
291
+ timestamp: Time.now.iso8601
292
+ }
293
+
294
+ @versions.each do |row_key, versions|
295
+ data[:versions][row_key.to_s] = versions.map do |v|
296
+ {
297
+ row_id: v.row_id,
298
+ version_id: v.version_id,
299
+ created_at: v.created_at.iso8601,
300
+ transaction_id: v.transaction_id,
301
+ prev_version_id: v.prev_version_id,
302
+ is_committed: v.is_committed,
303
+ is_aborted: v.is_aborted,
304
+ is_deleted: v.is_deleted,
305
+ commit_id: v.commit_id,
306
+ visibility: v.visibility,
307
+ data: v.data
308
+ }
309
+ end
310
+ end
311
+
312
+ JSON.generate(data)
313
+ end
314
+ end
315
+
316
+ def deserialize(json_data)
317
+ @lock.synchronize do
318
+ data = JSON.parse(json_data, symbolize_names: true)
319
+
320
+ @versions.clear
321
+ @version_map.clear
322
+ @version_keys.clear
323
+ @active_versions.clear
324
+
325
+ data[:versions].each do |row_key, versions_data|
326
+ row_key = row_key.to_s
327
+ row_key = row_key.to_i if row_key.match?(/\A\d+\z/)
328
+ @versions[row_key] = versions_data.map do |v_data|
329
+ version = Version.new(
330
+ v_data[:row_id],
331
+ v_data[:data],
332
+ v_data[:transaction_id],
333
+ v_data[:prev_version_id]
334
+ )
335
+ version.instance_variable_set(:@version_id, v_data[:version_id])
336
+ version.instance_variable_set(:@created_at, Time.parse(v_data[:created_at]))
337
+ version.instance_variable_set(:@is_committed, v_data[:is_committed])
338
+ version.instance_variable_set(:@is_aborted, v_data[:is_aborted])
339
+ version.instance_variable_set(:@is_deleted, v_data[:is_deleted])
340
+ version.instance_variable_set(:@commit_id, v_data[:commit_id])
341
+ version.instance_variable_set(:@visibility, v_data[:visibility].to_sym)
342
+
343
+ @version_map[version.version_id] = version
344
+ @version_keys[version.version_id] = row_key
345
+ version
346
+ end
347
+ end
348
+
349
+ @version_count = data[:version_count] || 0
350
+ @row_count = data[:row_count] || 0
351
+ @active_versions = Set.new(data[:active_versions] || [])
352
+ @dirty = false
353
+ end
354
+ end
355
+
356
+ def persist
357
+ return unless @persistence_path && @dirty
358
+
359
+ @lock.synchronize do
360
+ FileUtils.mkdir_p(File.dirname(@persistence_path))
361
+ temp_path = "#{@persistence_path}.tmp"
362
+ File.open(temp_path, "w") do |file|
363
+ file.write(serialize)
364
+ file.flush
365
+ file.fsync
366
+ end
367
+ File.rename(temp_path, @persistence_path)
368
+ @dirty = false
369
+ end
370
+ end
371
+
372
+ def load
373
+ return unless @persistence_path && File.exist?(@persistence_path)
374
+
375
+ @lock.synchronize do
376
+ json_data = File.read(@persistence_path)
377
+ deserialize(json_data)
378
+ end
379
+ end
380
+
381
+ def stats
382
+ @lock.synchronize do
383
+ @stats.merge({
384
+ version_count: @version_count,
385
+ row_count: @row_count,
386
+ active_versions: @active_versions.size,
387
+ rows_with_versions: @versions.size,
388
+ dirty: @dirty
389
+ })
390
+ end
391
+ end
392
+
393
+ def inspect
394
+ "#<VersionStore versions=#{@version_count} rows=#{@row_count} active=#{@active_versions.size}>"
395
+ end
396
+ end
397
+ end
398
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module RubyDB
6
+ module MVCC
7
+ # Visibility - Determines visibility of versions to transactions
8
+ class Visibility
9
+ attr_reader :stats
10
+
11
+ def initialize
12
+ @visibility_cache = {}
13
+ @stats = {
14
+ cache_hits: 0,
15
+ cache_misses: 0,
16
+ visibility_checks: 0,
17
+ cache_evictions: 0
18
+ }
19
+ @cache_size = 10000
20
+ @lock = Mutex.new
21
+ end
22
+
23
+ def visible?(version, transaction_id, snapshot = nil)
24
+ @lock.synchronize do
25
+ @stats[:visibility_checks] += 1
26
+
27
+ # Check cache
28
+ cache_key = "#{version.version_id}:#{transaction_id}"
29
+ if @visibility_cache.key?(cache_key)
30
+ @stats[:cache_hits] += 1
31
+ return @visibility_cache[cache_key]
32
+ end
33
+
34
+ result = check_visibility(version, transaction_id, snapshot)
35
+
36
+ # Cache result
37
+ if @visibility_cache.size >= @cache_size
38
+ evict_oldest_cache_entry
39
+ end
40
+
41
+ @visibility_cache[cache_key] = result
42
+ @stats[:cache_misses] += 1
43
+
44
+ result
45
+ end
46
+ end
47
+
48
+ def check_visibility(version, transaction_id, snapshot = nil)
49
+ # Check if version is from the same transaction
50
+ return true if version.transaction_id == transaction_id
51
+
52
+ # Check snapshot
53
+ if snapshot
54
+ return snapshot.visible?(version, transaction_id)
55
+ end
56
+
57
+ # If not committed, invisible to others
58
+ return false unless version.is_committed
59
+
60
+ # Check if deleted
61
+ return false if version.is_deleted && version.visibility == :deleted
62
+
63
+ # Check commit visibility
64
+ if version.commit_id
65
+ return version.commit_id <= transaction_id
66
+ end
67
+
68
+ true
69
+ end
70
+
71
+ def clear_cache
72
+ @lock.synchronize do
73
+ @visibility_cache.clear
74
+ @stats[:cache_evictions] += 1
75
+ end
76
+ end
77
+
78
+ def cache_size
79
+ @visibility_cache.size
80
+ end
81
+
82
+ def stats
83
+ @lock.synchronize do
84
+ @stats.merge({
85
+ cache_size: @visibility_cache.size,
86
+ hit_rate: hit_rate
87
+ })
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def hit_rate
94
+ total = @stats[:cache_hits] + @stats[:cache_misses]
95
+ return 0.0 if total == 0
96
+ (@stats[:cache_hits].to_f / total * 100).round(2)
97
+ end
98
+
99
+ def evict_oldest_cache_entry
100
+ # Remove first entry (oldest)
101
+ if @visibility_cache.any?
102
+ key = @visibility_cache.keys.first
103
+ @visibility_cache.delete(key)
104
+ @stats[:cache_evictions] += 1
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module Protocol
5
+ class Capabilities
6
+ COMPRESSION = 0x0001
7
+ ENCRYPTION = 0x0002
8
+ PIPELINING = 0x0004
9
+ BATCHING = 0x0008
10
+ PREPARED_STATEMENTS = 0x0010
11
+ CURSORS = 0x0020
12
+ NOTIFICATIONS = 0x0040
13
+ LISTEN_NOTIFY = 0x0080
14
+ LARGE_OBJECTS = 0x0100
15
+ COPY = 0x0200
16
+ TRANSACTIONS = 0x0400
17
+ SAVEPOINTS = 0x0800
18
+ TWO_PHASE_COMMIT = 0x1000
19
+ SECURE_CONNECTION = 0x2000
20
+ LOAD_BALANCING = 0x4000
21
+ AUTO_RECONNECT = 0x8000
22
+
23
+ attr_reader :supported, :enabled, :negotiated, :features, :limits
24
+
25
+ def initialize
26
+ @supported = 0
27
+ @enabled = 0
28
+ @negotiated = 0
29
+ @limits = {}
30
+ @features = {}
31
+ end
32
+
33
+ def set_supported(flags)
34
+ @supported |= flags
35
+ end
36
+
37
+ def enable(flags)
38
+ return unless (@supported & flags) == flags
39
+ @enabled |= flags
40
+ end
41
+
42
+ def disable(flags)
43
+ @enabled &= ~flags
44
+ end
45
+
46
+ def has?(flags)
47
+ (@enabled & flags) == flags
48
+ end
49
+
50
+ def supported?(flags)
51
+ (@supported & flags) == flags
52
+ end
53
+
54
+ def negotiate(client_capabilities)
55
+ negotiated = client_capabilities & @supported
56
+ @negotiated = negotiated
57
+ @enabled = negotiated
58
+
59
+ @features[:compression] = has?(COMPRESSION)
60
+ @features[:encryption] = has?(ENCRYPTION)
61
+ @features[:pipelining] = has?(PIPELINING)
62
+ @features[:batching] = has?(BATCHING)
63
+ @features[:prepared_statements] = has?(PREPARED_STATEMENTS)
64
+ @features[:cursors] = has?(CURSORS)
65
+ @features[:notifications] = has?(NOTIFICATIONS)
66
+ @features[:listen_notify] = has?(LISTEN_NOTIFY)
67
+ @features[:large_objects] = has?(LARGE_OBJECTS)
68
+ @features[:copy] = has?(COPY)
69
+ @features[:transactions] = has?(TRANSACTIONS)
70
+ @features[:savepoints] = has?(SAVEPOINTS)
71
+ @features[:two_phase_commit] = has?(TWO_PHASE_COMMIT)
72
+ @features[:secure_connection] = has?(SECURE_CONNECTION)
73
+ @features[:load_balancing] = has?(LOAD_BALANCING)
74
+ @features[:auto_reconnect] = has?(AUTO_RECONNECT)
75
+
76
+ @negotiated
77
+ end
78
+
79
+ def set_limit(name, value)
80
+ @limits[name] = value
81
+ end
82
+
83
+ def get_limit(name)
84
+ @limits[name]
85
+ end
86
+
87
+ def to_hash
88
+ {
89
+ supported: @supported,
90
+ enabled: @enabled,
91
+ negotiated: @negotiated,
92
+ features: @features,
93
+ limits: @limits
94
+ }
95
+ end
96
+
97
+ def self.default_server
98
+ caps = new
99
+ caps.set_supported(
100
+ COMPRESSION | ENCRYPTION | PIPELINING | BATCHING |
101
+ PREPARED_STATEMENTS | CURSORS | NOTIFICATIONS |
102
+ LISTEN_NOTIFY | TRANSACTIONS | SAVEPOINTS |
103
+ TWO_PHASE_COMMIT | LOAD_BALANCING
104
+ )
105
+ caps.set_limit(:max_batch_size, 1000)
106
+ caps.set_limit(:max_rows, 100000)
107
+ caps.set_limit(:max_timeout, 300)
108
+ caps
109
+ end
110
+
111
+ def self.default_client
112
+ caps = new
113
+ caps.set_supported(
114
+ COMPRESSION | ENCRYPTION | PIPELINING | BATCHING |
115
+ PREPARED_STATEMENTS | CURSORS | TRANSACTIONS |
116
+ SAVEPOINTS | AUTO_RECONNECT
117
+ )
118
+ caps.set_limit(:batch_size, 100)
119
+ caps.set_limit(:max_rows, 10000)
120
+ caps.set_limit(:timeout, 30)
121
+ caps
122
+ end
123
+ end
124
+ end
125
+ end