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,366 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+ require "monitor"
5
+
6
+ module RubyDB
7
+ module Server
8
+ # Connection - Represents a client connection
9
+ class Connection
10
+ RequestTooLarge = Class.new(StandardError)
11
+ RequestTimeout = Class.new(StandardError)
12
+
13
+ attr_reader :id, :client, :remote_addr, :remote_port
14
+ attr_reader :created_at, :last_activity, :state, :session
15
+ attr_reader :bytes_received, :bytes_sent, :requests_processed
16
+
17
+ # Connection states
18
+ STATE_INIT = :init
19
+ STATE_HANDSHAKING = :handshaking
20
+ STATE_AUTHENTICATING = :authenticating
21
+ STATE_AUTHENTICATED = :authenticated
22
+ STATE_READY = :ready
23
+ STATE_BUSY = :busy
24
+ STATE_CLOSED = :closed
25
+
26
+ def initialize(client, id, config = {})
27
+ @id = id
28
+ @client = client
29
+ @remote_addr = client.peeraddr[2] rescue "unknown"
30
+ @remote_port = client.peeraddr[1] rescue 0
31
+ @config = config
32
+ @created_at = Time.now
33
+ @last_activity = Time.now
34
+ @state = STATE_INIT
35
+ @bytes_received = 0
36
+ @bytes_sent = 0
37
+ @requests_processed = 0
38
+ @session = Session.new(self, config) if config[:engine]
39
+ @session_id = nil
40
+ @authenticated = false
41
+ @username = nil
42
+ @read_timeout = config[:read_timeout] || 30
43
+ @write_timeout = config[:write_timeout] || 30
44
+ @idle_timeout = config[:idle_timeout] || 300
45
+ @buffer = ""
46
+ @lock = Monitor.new
47
+ @write_lock = Mutex.new
48
+ @active_request_lock = Mutex.new
49
+ @active_request_id = nil
50
+ @active_request_thread = nil
51
+ @closed = false
52
+ @pending_requests = []
53
+ end
54
+
55
+ def handshake(protocol)
56
+ @lock.synchronize do
57
+ return false if @closed
58
+
59
+ @state = STATE_HANDSHAKING
60
+
61
+ begin
62
+ # Read handshake request
63
+ data = read_data(protocol)
64
+
65
+ # Process handshake
66
+ response = protocol.handshake.start(data)
67
+
68
+ # Send handshake response
69
+ write_data(protocol, response)
70
+
71
+ @state = STATE_AUTHENTICATING
72
+ true
73
+
74
+ rescue => e
75
+ close
76
+ false
77
+ end
78
+ end
79
+ end
80
+
81
+ def authenticate(credentials)
82
+ @lock.synchronize do
83
+ return false if @closed || @state == STATE_CLOSED
84
+
85
+ @state = STATE_AUTHENTICATING
86
+
87
+ begin
88
+ # Authenticate
89
+ @authenticated = @session&.authenticate(credentials) || false
90
+
91
+ if @authenticated
92
+ @state = STATE_AUTHENTICATED
93
+ @username = credentials[:username]
94
+ true
95
+ else
96
+ @state = STATE_CLOSED
97
+ close
98
+ false
99
+ end
100
+
101
+ rescue => e
102
+ @state = STATE_CLOSED
103
+ close
104
+ false
105
+ end
106
+ end
107
+ end
108
+
109
+ def ready?
110
+ @state == STATE_READY
111
+ end
112
+
113
+ def busy?
114
+ @state == STATE_BUSY
115
+ end
116
+
117
+ def authenticated?
118
+ @authenticated
119
+ end
120
+
121
+ def closed?
122
+ @closed
123
+ end
124
+
125
+ def idle?
126
+ Time.now - @last_activity > @idle_timeout
127
+ end
128
+
129
+ def process_request(request, protocol)
130
+ @lock.synchronize do
131
+ return false if @closed
132
+
133
+ @state = STATE_BUSY
134
+ @last_activity = Time.now
135
+ @requests_processed += 1
136
+
137
+ begin
138
+ # Process request through session
139
+ response = @session&.process(request) || error_response("No session")
140
+
141
+ # Send response
142
+ write_data(protocol, response)
143
+
144
+ @state = STATE_READY
145
+ true
146
+
147
+ rescue => e
148
+ @state = STATE_READY
149
+ false
150
+ end
151
+ end
152
+ end
153
+
154
+ def start(protocol)
155
+ @thread = Thread.new { serve(protocol) }
156
+ true
157
+ end
158
+
159
+ def close
160
+ @lock.synchronize do
161
+ return if @closed
162
+
163
+ @state = STATE_CLOSED
164
+ @closed = true
165
+
166
+ if @client
167
+ @client.close rescue nil
168
+ @client = nil
169
+ end
170
+
171
+ if @session
172
+ @session.close
173
+ @session = nil
174
+ end
175
+
176
+ true
177
+ end
178
+ end
179
+
180
+ def to_hash
181
+ {
182
+ id: @id,
183
+ remote_addr: @remote_addr,
184
+ remote_port: @remote_port,
185
+ state: @state,
186
+ authenticated: @authenticated,
187
+ username: @username,
188
+ created_at: @created_at.iso8601,
189
+ last_activity: @last_activity.iso8601,
190
+ bytes_received: @bytes_received,
191
+ bytes_sent: @bytes_sent,
192
+ requests_processed: @requests_processed,
193
+ idle_time: (Time.now - @last_activity).round(2)
194
+ }
195
+ end
196
+
197
+ private
198
+
199
+ def serve(protocol)
200
+ first = protocol.decoder.decode(read_frame, protocol.encoder.format)
201
+ handshake = protocol.handshake.start(first.payload)
202
+ write_data(protocol, Protocol::Message.new(Protocol::Message::TYPE_HANDSHAKE_RESPONSE, handshake))
203
+ return close unless handshake[:success]
204
+
205
+ auth = protocol.decoder.decode(read_frame, protocol.encoder.format)
206
+ authenticated = protocol.handshake.authenticate(auth.payload)
207
+ @session&.authenticate(auth.payload)
208
+ write_data(protocol, Protocol::Message.new(Protocol::Message::TYPE_AUTHENTICATION, authenticated.merge(session_id: @session&.id)))
209
+ return close unless authenticated[:success]
210
+
211
+ sync = protocol.decoder.decode(read_frame, protocol.encoder.format)
212
+ negotiated = protocol.handshake.negotiate(sync.payload[:capabilities] || {})
213
+ @state = STATE_READY
214
+ write_data(protocol, Protocol::Message.new(Protocol::Message::TYPE_READY_FOR_QUERY, negotiated))
215
+
216
+ request_queue = Queue.new
217
+ request_worker = Thread.new do
218
+ while (entry = request_queue.pop)
219
+ begin
220
+ message, request = entry
221
+ response = begin
222
+ @session.process(request)
223
+ rescue StandardError => e
224
+ { success: false, error: e.message, timestamp: Time.now.iso8601 }
225
+ end
226
+ response[:request_id] ||= message.id
227
+ @config[:connection_pool]&.record_request(response[:success] != false)
228
+ response_type = response[:type] || "#{message.type}_response"
229
+ # The request is no longer executing once the session has
230
+ # produced its response. Clear this before writing so a fast
231
+ # client can issue the next request as soon as it receives the
232
+ # frame; keeping it set until after the write creates a
233
+ # connection-level false "request in flight" rejection.
234
+ @active_request_lock.synchronize do
235
+ @active_request_id = nil if message && @active_request_id == message.id
236
+ end
237
+ begin
238
+ write_data(protocol, Protocol::Message.new(response_type.to_sym, response))
239
+ rescue IOError, SystemCallError
240
+ break
241
+ end
242
+ ensure
243
+ @active_request_lock.synchronize do
244
+ @active_request_id = nil if message && @active_request_id == message.id
245
+ end
246
+ end
247
+ end
248
+ end
249
+ @active_request_lock.synchronize { @active_request_thread = request_worker }
250
+
251
+ while !@closed && (line = read_frame)
252
+ begin
253
+ message = protocol.decoder.decode(line, protocol.encoder.format)
254
+ rescue StandardError => e
255
+ write_data(protocol, Protocol::Message.new(
256
+ Protocol::Message::TYPE_ERROR,
257
+ { success: false, error: "Invalid protocol frame: #{e.message}" }
258
+ ))
259
+ next
260
+ end
261
+ break if message.type.to_s == "terminate"
262
+ request = message.payload.merge(type: message.type.to_s, request_id: message.id)
263
+
264
+ if message.type.to_s == "cancel"
265
+ active_target = @active_request_lock.synchronize { @active_request_id == request[:target_request_id] }
266
+ request[:allow_pending_cancellation] = active_target
267
+ response = begin
268
+ @session.process(request)
269
+ rescue StandardError => e
270
+ { success: false, error: e.message, timestamp: Time.now.iso8601 }
271
+ end
272
+ @config[:connection_pool]&.record_request(response[:success] != false)
273
+ write_data(protocol, Protocol::Message.new(:cancel_response, response))
274
+ next
275
+ end
276
+
277
+ already_busy = @active_request_lock.synchronize { !@active_request_id.nil? }
278
+ if already_busy
279
+ response = { success: false, error: "Connection has a request in flight", code: "busy", request_id: message.id, timestamp: Time.now.iso8601 }
280
+ write_data(protocol, Protocol::Message.new(:error, response))
281
+ next
282
+ end
283
+
284
+ @active_request_lock.synchronize { @active_request_id = message.id }
285
+ request_queue << [message, request]
286
+ end
287
+ request_queue << nil
288
+ request_worker.join(5)
289
+ @active_request_thread&.join(5)
290
+ rescue RequestTooLarge => e
291
+ write_data(protocol, Protocol::Message.new(
292
+ Protocol::Message::TYPE_ERROR,
293
+ { success: false, error: e.message }
294
+ )) rescue nil
295
+ close unless @closed
296
+ rescue RequestTimeout => e
297
+ write_data(protocol, Protocol::Message.new(
298
+ Protocol::Message::TYPE_ERROR,
299
+ { success: false, error: e.message }
300
+ )) rescue nil
301
+ close unless @closed
302
+ rescue StandardError => e
303
+ warn "RubyDB connection #{@id} failed: #{e.class}: #{e.message}" if ENV["RUBYDB_DEBUG"]
304
+ close unless @closed
305
+ end
306
+
307
+ def read_frame
308
+ limit = Integer(@config[:max_request_size] || 10 * 1024 * 1024)
309
+ timeout = @read_timeout
310
+ if timeout && !IO.select([@client], nil, nil, timeout)
311
+ raise RequestTimeout, "Request timed out after #{timeout} seconds"
312
+ end
313
+ # Passing the separator explicitly keeps the byte limit compatible
314
+ # with both plain sockets and OpenSSL::SSL::SSLSocket.
315
+ line = @client.gets("\n", limit + 1)
316
+ return nil unless line
317
+
318
+ if line.bytesize > limit || !line.end_with?("\n")
319
+ raise RequestTooLarge, "Request frame exceeds maximum size of #{limit} bytes"
320
+ end
321
+
322
+ @bytes_received += line.bytesize
323
+ line
324
+ end
325
+
326
+ def read_data(protocol)
327
+ data = ""
328
+
329
+ while true
330
+ chunk = @client.recv(4096)
331
+ break if chunk.empty?
332
+ data << chunk
333
+ @bytes_received += chunk.bytesize
334
+ break if data.bytesize >= @config[:max_request_size]
335
+ end
336
+
337
+ data
338
+ end
339
+
340
+ def write_data(protocol, data)
341
+ @write_lock.synchronize do
342
+ return false if @closed || @client.nil?
343
+
344
+ client = @client
345
+ if data.is_a?(String)
346
+ client.write(data)
347
+ @bytes_sent += data.bytesize
348
+ else
349
+ encoded = protocol.encoder.encode(data)
350
+ client.write(encoded)
351
+ @bytes_sent += encoded.bytesize
352
+ end
353
+ client.flush
354
+ end
355
+ end
356
+
357
+ def error_response(message)
358
+ {
359
+ success: false,
360
+ error: message,
361
+ timestamp: Time.now.iso8601
362
+ }
363
+ end
364
+ end
365
+ end
366
+ end
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "monitor"
4
+
5
+ module RubyDB
6
+ module Server
7
+ # ConnectionPool - Manages database connections
8
+ class ConnectionPool
9
+ attr_reader :stats
10
+
11
+ def initialize(config, protocol, engine = nil)
12
+ @config = config
13
+ @protocol = protocol
14
+ @engine = engine
15
+ @max_connections = config[:max_connections] || 100
16
+ @connections = {}
17
+ @connection_counter = 0
18
+ @stats = {
19
+ total_connections: 0,
20
+ active_connections: 0,
21
+ idle_connections: 0,
22
+ rejected_connections: 0,
23
+ connection_errors: 0,
24
+ requests_processed: 0,
25
+ requests_failed: 0,
26
+ avg_connection_time_ms: 0,
27
+ total_connection_time_ms: 0
28
+ }
29
+ @lock = Monitor.new
30
+ @running = false
31
+ @cleanup_thread = nil
32
+ @cleanup_interval = config[:cleanup_interval] || 60
33
+ @idle_timeout = config[:idle_timeout] || 300
34
+ end
35
+
36
+ def start
37
+ @lock.synchronize do
38
+ return if @running
39
+
40
+ @running = true
41
+ @cleanup_thread = Thread.new { cleanup_loop }
42
+ true
43
+ end
44
+ end
45
+
46
+ def stop
47
+ @lock.synchronize do
48
+ return unless @running
49
+
50
+ @running = false
51
+
52
+ if @cleanup_thread
53
+ @cleanup_thread.kill
54
+ @cleanup_thread = nil
55
+ end
56
+
57
+ close_all
58
+ true
59
+ end
60
+ end
61
+
62
+ def add_connection(client)
63
+ @lock.synchronize do
64
+ prune_closed_connections
65
+
66
+ if @connections.size >= @max_connections
67
+ client.close
68
+ @stats[:rejected_connections] += 1
69
+ return false
70
+ end
71
+
72
+ start_time = Time.now
73
+ conn_id = next_connection_id
74
+ connection = Connection.new(client, conn_id, @config.merge(engine: @engine, connection_pool: self))
75
+
76
+ @connections[conn_id] = connection
77
+ @stats[:total_connections] += 1
78
+ connection.start(@protocol)
79
+
80
+ elapsed_ms = (Time.now - start_time) * 1000
81
+ @stats[:total_connection_time_ms] += elapsed_ms
82
+ @stats[:avg_connection_time_ms] = @stats[:total_connection_time_ms] / @stats[:total_connections] if @stats[:total_connections] > 0
83
+
84
+ true
85
+ end
86
+ end
87
+
88
+ def get_connection(id)
89
+ @lock.synchronize do
90
+ @connections[id]
91
+ end
92
+ end
93
+
94
+ def remove_connection(id)
95
+ @lock.synchronize do
96
+ conn = @connections.delete(id)
97
+ conn&.close
98
+ conn
99
+ end
100
+ end
101
+
102
+ def close_all
103
+ @lock.synchronize do
104
+ @connections.each do |id, conn|
105
+ conn.close
106
+ end
107
+ @connections.clear
108
+ end
109
+ end
110
+
111
+ def can_accept?
112
+ @lock.synchronize do
113
+ prune_closed_connections
114
+ @connections.size < @max_connections
115
+ end
116
+ end
117
+
118
+ def active_count
119
+ @lock.synchronize do
120
+ @connections.values.count { |c| !c.closed? && !c.idle? }
121
+ end
122
+ end
123
+
124
+ def idle_count
125
+ @lock.synchronize do
126
+ @connections.values.count { |c| !c.closed? && c.idle? }
127
+ end
128
+ end
129
+
130
+ def total_connections
131
+ @connections.size
132
+ end
133
+
134
+ def running?
135
+ @running
136
+ end
137
+
138
+ # Report whether every tracked connection is still usable. This is
139
+ # intentionally based on connection state rather than pool cardinality;
140
+ # a non-empty pool can still contain only closed connections.
141
+ def healthy?
142
+ @lock.synchronize do
143
+ @running && @connections.values.all? { |connection| !connection.closed? }
144
+ end
145
+ end
146
+
147
+ def stats
148
+ @lock.synchronize do
149
+ @stats.merge({
150
+ current_connections: @connections.size,
151
+ active_connections: active_count,
152
+ idle_connections: idle_count,
153
+ max_connections: @max_connections,
154
+ running: @running
155
+ })
156
+ end
157
+ end
158
+
159
+ def record_request(success)
160
+ @lock.synchronize do
161
+ key = success ? :requests_processed : :requests_failed
162
+ @stats[key] += 1
163
+ end
164
+ end
165
+
166
+ private
167
+
168
+ def next_connection_id
169
+ @connection_counter += 1
170
+ end
171
+
172
+ def cleanup_loop
173
+ while @running
174
+ sleep(@cleanup_interval)
175
+
176
+ @lock.synchronize do
177
+ @connections.each do |id, conn|
178
+ # Remove idle connections
179
+ if conn.idle?
180
+ conn.close
181
+ @connections.delete(id)
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
187
+
188
+ def prune_closed_connections
189
+ @connections.delete_if { |_id, connection| connection.closed? }
190
+ end
191
+ end
192
+ end
193
+ end