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,236 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module RubyDB
6
+ module WAL
7
+ # Writer - Writes records to the WAL
8
+ class Writer
9
+ attr_reader :current_segment, :current_lsn, :stats
10
+
11
+ def initialize(wal_dir, config = {})
12
+ @wal_dir = wal_dir
13
+ @config = config
14
+ @segment_max_size = config[:segment_size] || Segment::DEFAULT_MAX_SIZE
15
+ @current_segment = nil
16
+ @current_lsn = LSN.new(1, 0)
17
+ @buffer = []
18
+ @buffer_size = config[:buffer_size] || 1024 * 1024 # 1MB
19
+ @buffer_current_size = 0
20
+ @sync_on_write = config.key?(:sync) ? config[:sync] : true
21
+ @stats = {
22
+ records_written: 0,
23
+ bytes_written: 0,
24
+ segments_created: 0,
25
+ segments_rotated: 0,
26
+ buffer_flushes: 0,
27
+ syncs: 0
28
+ }
29
+ @lock = Mutex.new
30
+ @write_thread = nil
31
+ @running = false
32
+ @shutdown = false
33
+ @background_error = nil
34
+
35
+ # Create WAL directory
36
+ FileUtils.mkdir_p(@wal_dir)
37
+
38
+ # Create first segment
39
+ create_new_segment
40
+
41
+ # Start background writer if async
42
+ if config[:async] != false
43
+ start_background_writer
44
+ end
45
+ end
46
+
47
+ def write_record(record)
48
+ @lock.synchronize do
49
+ raise @background_error if @background_error
50
+ # Assign LSN
51
+ lsn = next_lsn
52
+ record.instance_variable_set(:@lsn, lsn)
53
+
54
+ # Serialize record
55
+ serialized = record.serialize
56
+ record_data = serialized[:checksum] + serialized[:data]
57
+
58
+ # Add to buffer
59
+ @buffer << { record: record, data: record_data, lsn: lsn }
60
+ @buffer_current_size += record_data.bytesize
61
+
62
+ @stats[:records_written] += 1
63
+
64
+ # Flush if buffer is full OR if sync is enabled (for durability)
65
+ if @buffer_current_size >= @buffer_size || @sync_on_write
66
+ flush_buffer
67
+ _sync if @sync_on_write
68
+ end
69
+
70
+ lsn
71
+ end
72
+ end
73
+
74
+ def write_batch(records)
75
+ @lock.synchronize do
76
+ start_lsn = nil
77
+ last_lsn = nil
78
+
79
+ records.each do |record|
80
+ lsn = write_record_locked(record)
81
+ start_lsn ||= lsn
82
+ last_lsn = lsn
83
+ end
84
+
85
+ flush_buffer if @buffer.any?
86
+
87
+ { start_lsn: start_lsn, end_lsn: last_lsn, count: records.size }
88
+ end
89
+ end
90
+
91
+ def flush
92
+ @lock.synchronize do
93
+ raise @background_error if @background_error
94
+ flush_buffer
95
+ _sync if @sync_on_write
96
+ end
97
+ end
98
+
99
+ def sync
100
+ @lock.synchronize do
101
+ _sync
102
+ end
103
+ end
104
+
105
+ private def _sync
106
+ if @current_segment && @current_segment.open?
107
+ @current_segment.instance_variable_get(:@file).fsync
108
+ @stats[:syncs] += 1
109
+ end
110
+ end
111
+
112
+ def shutdown(wait = true)
113
+ write_thread = nil
114
+ @lock.synchronize do
115
+ return if @shutdown
116
+
117
+ @shutdown = true
118
+ @running = false
119
+ @write_thread&.kill if !wait
120
+ flush_buffer
121
+ _sync if @sync_on_write
122
+ @current_segment.close if @current_segment&.open?
123
+ write_thread = @write_thread
124
+ @write_thread = nil
125
+ end
126
+
127
+ # The async writer can be asleep or flushing while shutdown begins.
128
+ # Join it before the caller removes the WAL directory; otherwise a
129
+ # late background flush can race segment close and leave Windows file
130
+ # handles open during temporary-directory cleanup.
131
+ write_thread&.join if wait && write_thread != Thread.current
132
+ end
133
+
134
+ def stats
135
+ @lock.synchronize do
136
+ @stats.merge({
137
+ buffer_size: @buffer.size,
138
+ buffer_bytes: @buffer_current_size,
139
+ current_segment: @current_segment&.segment_id,
140
+ current_lsn: @current_lsn.to_s,
141
+ async: @running,
142
+ last_background_error: @background_error&.message
143
+ })
144
+ end
145
+ end
146
+
147
+ private
148
+
149
+ def write_record_locked(record)
150
+ lsn = next_lsn
151
+ record.instance_variable_set(:@lsn, lsn)
152
+
153
+ serialized = record.serialize
154
+ record_data = serialized[:checksum] + serialized[:data]
155
+ @buffer << { record: record, data: record_data, lsn: lsn }
156
+ @buffer_current_size += record_data.bytesize
157
+ @stats[:records_written] += 1
158
+
159
+ if @buffer_current_size >= @buffer_size || @sync_on_write
160
+ flush_buffer
161
+ _sync if @sync_on_write
162
+ end
163
+
164
+ lsn
165
+ end
166
+
167
+ def create_new_segment
168
+ segment_id = @current_segment ? @current_segment.segment_id + 1 : 1
169
+ segment = Segment.new(segment_id, @wal_dir, @segment_max_size)
170
+ segment.open
171
+ @current_segment = segment
172
+ @stats[:segments_created] += 1
173
+ segment
174
+ end
175
+
176
+ def next_lsn
177
+ @current_lsn = LSN.new(
178
+ @current_segment.segment_id,
179
+ @current_segment.size + @buffer.sum { |entry| entry[:data].bytesize + Segment::FRAME_HEADER_SIZE }
180
+ )
181
+ end
182
+
183
+ def flush_buffer
184
+ return if @buffer.empty?
185
+
186
+ @stats[:buffer_flushes] += 1
187
+
188
+ @buffer.each do |entry|
189
+ # Check if current segment is full
190
+ if @current_segment && @current_segment.full?
191
+ rotate_segment
192
+ end
193
+
194
+ # Write to segment
195
+ result = @current_segment.append(entry[:data], entry[:lsn])
196
+ @stats[:bytes_written] += entry[:data].bytesize
197
+
198
+ if result == :full
199
+ rotate_segment
200
+ # Re-write to new segment
201
+ @current_segment.append(entry[:data], entry[:lsn])
202
+ end
203
+ end
204
+
205
+ @buffer.clear
206
+ @buffer_current_size = 0
207
+ end
208
+
209
+ def rotate_segment
210
+ @current_segment.close
211
+ @stats[:segments_rotated] += 1
212
+ create_new_segment
213
+ end
214
+
215
+ def start_background_writer
216
+ @running = true
217
+ @write_thread = Thread.new do
218
+ while !@shutdown
219
+ sleep(1)
220
+ begin
221
+ if @buffer.any?
222
+ flush
223
+ end
224
+ rescue => e
225
+ @lock.synchronize do
226
+ @background_error ||= e
227
+ @running = false
228
+ end
229
+ break
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end
236
+ end
data/lib/rubydb.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Top-level entry point for the RubyDB library.
4
+ # The project stores the implementation under lib/rubydb/rubydb.rb, so this wrapper
5
+ # keeps the public require path compatible with typical Ruby package usage.
6
+
7
+ require_relative "rubydb/rubydb"
@@ -0,0 +1,36 @@
1
+ # syntax=docker/dockerfile:1
2
+
3
+ FROM ruby:3.3-slim
4
+
5
+ ENV APP_HOME=/opt/rubydb \
6
+ RUBYDB_DATA_DIR=/var/lib/rubydb/data \
7
+ RUBYDB_LOG_DIR=/var/log/rubydb \
8
+ RUBYDB_PID_FILE=/run/rubydb.pid \
9
+ RUBYDB_HOST=0.0.0.0 \
10
+ RUBYDB_PORT=7432
11
+
12
+ WORKDIR ${APP_HOME}
13
+
14
+ COPY Gemfile Gemfile.lock rubydb.gemspec ./
15
+ COPY lib ./lib
16
+ COPY exe ./exe
17
+ COPY bin ./bin
18
+
19
+ RUN bundle config set --without development:test \
20
+ && bundle install --jobs 4 --retry 3 \
21
+ && gem build rubydb.gemspec \
22
+ && gem install --no-document rubydb-*.gem \
23
+ && rm -f rubydb-*.gem \
24
+ && useradd --system --home-dir ${APP_HOME} --shell /usr/sbin/nologin rubydb \
25
+ && install -d -o rubydb -g rubydb ${RUBYDB_DATA_DIR} ${RUBYDB_LOG_DIR} /run/rubydb
26
+
27
+ COPY packaging/docker/entrypoint.sh /usr/local/bin/rubydb-entrypoint
28
+ RUN chmod 0755 /usr/local/bin/rubydb-entrypoint
29
+
30
+ USER rubydb
31
+ EXPOSE 7432
32
+ VOLUME ["/var/lib/rubydb"]
33
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
34
+ CMD rubydb status --json >/dev/null || exit 1
35
+
36
+ ENTRYPOINT ["/usr/local/bin/rubydb-entrypoint"]
@@ -0,0 +1,43 @@
1
+ services:
2
+ primary:
3
+ build:
4
+ context: ../..
5
+ dockerfile: packaging/docker/Dockerfile
6
+ hostname: rubydb-primary
7
+ environment:
8
+ RUBYDB_DATA_DIR: /var/lib/rubydb/data
9
+ RUBYDB_HOST: 0.0.0.0
10
+ RUBYDB_PORT: 7432
11
+ volumes:
12
+ - primary-data:/var/lib/rubydb
13
+ networks: [rubydb-cluster]
14
+
15
+ replica:
16
+ build:
17
+ context: ../..
18
+ dockerfile: packaging/docker/Dockerfile
19
+ hostname: rubydb-replica
20
+ environment:
21
+ RUBYDB_DATA_DIR: /var/lib/rubydb/data
22
+ RUBYDB_HOST: 0.0.0.0
23
+ RUBYDB_PORT: 7432
24
+ volumes:
25
+ - replica-data:/var/lib/rubydb
26
+ networks: [rubydb-cluster]
27
+
28
+ observer:
29
+ image: ruby:3.3-slim
30
+ working_dir: /workspace
31
+ volumes:
32
+ - ../..:/workspace:ro
33
+ command: ["ruby", "scripts/replication_network_failover_drill"]
34
+ networks: [rubydb-cluster]
35
+ profiles: [drill]
36
+
37
+ networks:
38
+ rubydb-cluster:
39
+ driver: bridge
40
+
41
+ volumes:
42
+ primary-data:
43
+ replica-data:
@@ -0,0 +1,15 @@
1
+ #!/bin/sh
2
+ set -eu
3
+
4
+ : "${RUBYDB_DATA_DIR:=/var/lib/rubydb/data}"
5
+ : "${RUBYDB_LOG_DIR:=/var/log/rubydb}"
6
+ : "${RUBYDB_PID_FILE:=/run/rubydb.pid}"
7
+ : "${RUBYDB_HOST:=0.0.0.0}"
8
+ : "${RUBYDB_PORT:=7432}"
9
+
10
+ exec rubydb start \
11
+ --host "$RUBYDB_HOST" \
12
+ --port "$RUBYDB_PORT" \
13
+ --data-dir "$RUBYDB_DATA_DIR" \
14
+ --log-dir "$RUBYDB_LOG_DIR" \
15
+ --pid-file "$RUBYDB_PID_FILE"
@@ -0,0 +1,19 @@
1
+ class Rubydb < Formula
2
+ desc "Developer-first relational database for Ruby"
3
+ homepage "https://github.com/aldanedev-create/rubydb"
4
+ url "https://github.com/aldanedev-create/rubydb/archive/refs/tags/v0.1.0.tar.gz"
5
+ version "0.1.0"
6
+ license "MIT"
7
+
8
+ depends_on "ruby@3.3"
9
+
10
+ def install
11
+ system "gem", "build", "rubydb.gemspec"
12
+ system "gem", "install", "--no-document", *Dir["rubydb-*.gem"]
13
+ bin.install "exe/rubydb"
14
+ end
15
+
16
+ test do
17
+ assert_match version.to_s, shell_output("#{bin}/rubydb --version")
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ [Unit]
2
+ Description=RubyDB database server
3
+ After=network-online.target
4
+ Wants=network-online.target
5
+
6
+ [Service]
7
+ Type=simple
8
+ User=rubydb
9
+ Group=rubydb
10
+ ExecStart=/usr/local/bin/rubydb start --host 127.0.0.1 --port 7432 --data-dir /var/lib/rubydb/data --log-dir /var/log/rubydb --pid-file /run/rubydb.pid
11
+ Restart=on-failure
12
+ RestartSec=5s
13
+ TimeoutStopSec=30s
14
+ UMask=0077
15
+ NoNewPrivileges=true
16
+ PrivateTmp=true
17
+ ProtectSystem=strict
18
+ ProtectHome=true
19
+ ReadWritePaths=/var/lib/rubydb /var/log/rubydb /run
20
+
21
+ [Install]
22
+ WantedBy=multi-user.target
data/rubydb.gemspec ADDED
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/rubydb/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'rubydb'
7
+ spec.version = RubyDB::VERSION
8
+ spec.authors = ['Aldane Hutchinson']
9
+ spec.email = ['aldanehutchinson5@gmail.com']
10
+
11
+ spec.summary = 'A developer-first relational database for Ruby'
12
+ spec.description = <<~DESC
13
+ RubyDB is a developer-first relational database foundation written in Ruby.
14
+ It combines SQLite-like simplicity with a growing SQL, WAL, MVCC, server,
15
+ replication, and Rails integration surface. Production deployment is
16
+ limited to the capabilities and validation documented by the project.
17
+ DESC
18
+ spec.homepage = 'https://github.com/aldanedev-create/rubydb'
19
+ spec.license = 'MIT'
20
+ # The implementation is currently compatible with the workspace Ruby runtime used for
21
+ # local development and CI. This is a practical compatibility target until the
22
+ # project adds a narrower support matrix and explicit Ruby-version policy.
23
+ spec.required_ruby_version = '>= 3.3.0'
24
+
25
+ spec.metadata['homepage_uri'] = spec.homepage
26
+ spec.metadata['source_code_uri'] = 'https://github.com/aldanedev-create/rubydb/tree/main'
27
+ spec.metadata['changelog_uri'] = 'https://github.com/aldanedev-create/rubydb/blob/main/CHANGELOG.md'
28
+
29
+ # RubyGems signing is opt-in for release builders. The private key and
30
+ # certificate are supplied through protected CI paths and never committed.
31
+ signing_key = ENV['RUBYDB_GEM_SIGNING_KEY']
32
+ certificate = ENV['RUBYDB_GEM_CERT']
33
+ if signing_key || certificate
34
+ raise 'RUBYDB_GEM_SIGNING_KEY and RUBYDB_GEM_CERT must be provided together' unless signing_key && certificate
35
+ raise "RubyGems signing key not found: #{signing_key}" unless File.file?(signing_key)
36
+ raise "RubyGems certificate not found: #{certificate}" unless File.file?(certificate)
37
+
38
+ spec.signing_key = signing_key
39
+ spec.cert_chain = [File.read(certificate)]
40
+ end
41
+
42
+ # Specify which files should be added to the gem when it is released.
43
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
44
+ spec.files = Dir.chdir(__dir__) do
45
+ `git ls-files -z`.split("\x0").reject do |f|
46
+ f.match(%r{^(test|spec|features|benchmarks|fuzz|chaos|examples)/})
47
+ end
48
+ end
49
+ spec.bindir = 'exe'
50
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
51
+ spec.require_paths = ['lib']
52
+
53
+ spec.add_dependency 'digest', '~> 3.1'
54
+ spec.add_dependency 'json', '~> 2.7'
55
+ spec.add_dependency 'date', '~> 3.3'
56
+ spec.add_dependency 'time', '~> 0.4'
57
+ end
data/scripts/benchmark ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "rbconfig"
5
+
6
+ root = File.expand_path("..", __dir__)
7
+ exec RbConfig.ruby, File.join(root, "benchmarks", "concurrent_workload.rb"), *ARGV
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Runs the real crash, filesystem-fault, corruption, compaction, and restore
5
+ # paths as one release gate. Physical quota filling is opt-in because filling a
6
+ # developer's filesystem without an explicit safety flag would be dangerous.
7
+
8
+ require "json"
9
+ require "rbconfig"
10
+
11
+ root = File.expand_path("..", __dir__)
12
+ Dir.chdir(root)
13
+ specs = %w[
14
+ spec/filesystem_fault_injection_spec.rb
15
+ spec/transaction_crash_recovery_spec.rb
16
+ spec/crash_recovery_spec.rb
17
+ spec/crash_recovery_wal_failure_spec.rb
18
+ spec/corruption_repair_spec.rb
19
+ spec/compaction_persistence_spec.rb
20
+ spec/backup_restore_spec.rb
21
+ spec/incremental_backup_spec.rb
22
+ spec/delta_restore_spec.rb
23
+ ]
24
+
25
+ command = [RbConfig.ruby, "-Ilib", "-S", "rspec", *specs]
26
+ started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
27
+ success = system(*command)
28
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
29
+
30
+ result = {
31
+ success: success,
32
+ checks: %w[filesystem_faults crash_recovery corruption_detection compaction backup_restore],
33
+ physical_quota: ENV["RUBYDB_DURABILITY_QUOTA_DIR"] ? "operator_configured" : "not_run",
34
+ elapsed_seconds: elapsed.round(3)
35
+ }
36
+ puts JSON.generate(result)
37
+ exit(success ? 0 : 1)
data/scripts/format ADDED
File without changes
data/scripts/fuzz ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Run the repository's fuzzers as a bounded CI or local safety gate.
5
+ # Invalid SQL and intentionally injected WAL corruption are expected inputs;
6
+ # uncaught errors and reported crashes are not.
7
+
8
+ require "tmpdir"
9
+
10
+ root = File.expand_path("..", __dir__)
11
+ $LOAD_PATH.unshift(File.join(root, "lib"))
12
+ require "rubydb"
13
+ require_relative "../fuzz/sql_parser"
14
+ require_relative "../fuzz/wal"
15
+ require_relative "../fuzz/storage"
16
+ require_relative "../fuzz/transactions"
17
+ require_relative "../fuzz/query_engine"
18
+
19
+ iterations = Integer(ENV.fetch("RUBYDB_FUZZ_ITERATIONS", "100"), 10)
20
+ abort "RUBYDB_FUZZ_ITERATIONS must be positive" unless iterations.positive?
21
+ seed = Integer(ENV.fetch("RUBYDB_FUZZ_SEED", "20260909"), 10)
22
+
23
+ def run_fuzzer(name)
24
+ stats = yield
25
+ crashes = stats[:crashes].to_i
26
+ errors = stats[:errors].to_i
27
+ abort "#{name} found #{crashes} crash(es) and #{errors} error(s)" if crashes.positive? || errors.positive?
28
+ puts "#{name}: #{stats.inspect}"
29
+ end
30
+
31
+ Dir.mktmpdir("rubydb-fuzz-") do |directory|
32
+ run_fuzzer("SQL parser") do
33
+ RubyDB::Fuzz::SQLParserFuzzer.new(max_iterations: iterations, seed: seed).fuzz
34
+ end
35
+
36
+ run_fuzzer("WAL") do
37
+ RubyDB::Fuzz::WALFuzzer.new(
38
+ max_iterations: iterations,
39
+ seed: seed,
40
+ test_dir: File.join(directory, "wal")
41
+ ).fuzz
42
+ end
43
+
44
+ run_fuzzer("storage") do
45
+ RubyDB::Fuzz::StorageFuzzer.new(
46
+ max_iterations: iterations,
47
+ seed: seed,
48
+ test_dir: File.join(directory, "storage")
49
+ ).fuzz
50
+ end
51
+
52
+ engine = RubyDB::Storage::Engine.new(File.join(directory, "engine.rdb"))
53
+ begin
54
+ run_fuzzer("transactions") do
55
+ RubyDB::Fuzz::TransactionsFuzzer.new(engine, max_iterations: iterations, seed: seed).fuzz
56
+ end
57
+ run_fuzzer("query engine") do
58
+ RubyDB::Fuzz::QueryEngineFuzzer.new(engine, max_iterations: iterations, seed: seed).fuzz
59
+ end
60
+ ensure
61
+ engine.close
62
+ end
63
+ end
data/scripts/lint ADDED
File without changes
data/scripts/release ADDED
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "digest"
5
+ require "rbconfig"
6
+ require "rubygems"
7
+
8
+ root = File.expand_path("..", __dir__)
9
+ Dir.chdir(root)
10
+
11
+ abort "release metadata preflight failed" unless system(RbConfig.ruby, File.join(root, "scripts", "release_check"))
12
+
13
+ release_descriptors = [
14
+ "rubydb.gemspec",
15
+ "adapters/activerecord/rubydb-activerecord.gemspec",
16
+ "packaging/homebrew/rubydb.rb"
17
+ ]
18
+ placeholder_pattern = /yourusername|your name|you@example\.com|your\.email@example\.com/i
19
+ placeholder_files = release_descriptors.select do |path|
20
+ File.exist?(path) && File.read(path).match?(placeholder_pattern)
21
+ end
22
+ unless placeholder_files.empty?
23
+ abort "Release metadata contains placeholders in: #{placeholder_files.join(', ')}. Set the real owner, contact, homepage, source, and changelog URLs before publishing."
24
+ end
25
+ abort "rake build:checksum failed" unless system("rake", "build:checksum")
26
+
27
+ if ENV["RUBYDB_GEM_SIGNING_KEY"] || ENV["RUBYDB_GEM_CERT"]
28
+ abort "Both RUBYDB_GEM_SIGNING_KEY and RUBYDB_GEM_CERT are required for gem signing" unless ENV["RUBYDB_GEM_SIGNING_KEY"] && ENV["RUBYDB_GEM_CERT"]
29
+ abort "RubyGems signing key does not exist" unless File.file?(ENV["RUBYDB_GEM_SIGNING_KEY"])
30
+ abort "RubyGems signing certificate does not exist" unless File.file?(ENV["RUBYDB_GEM_CERT"])
31
+ end
32
+
33
+ gem_path = Dir["pkg/rubydb-*.gem"].max_by { |path| File.mtime(path) }
34
+ abort "No built gem found" unless gem_path
35
+ checksum_path = File.join("checksums", "#{File.basename(gem_path)}.sha512")
36
+ expected = File.read(checksum_path).split.first
37
+ actual = Digest::SHA512.file(gem_path).hexdigest
38
+ abort "Checksum verification failed" unless expected == actual
39
+
40
+ if ENV["RUBYDB_PUBLISH"] == "1"
41
+ api_key = ENV.fetch("GEM_HOST_API_KEY", "")
42
+ credentials_path = Gem.configuration.credentials_path
43
+ credentials_available = File.file?(credentials_path) && !File.zero?(credentials_path)
44
+ abort "Set GEM_HOST_API_KEY or configure RubyGems credentials at #{credentials_path}" if api_key.empty? && !credentials_available
45
+ abort "gem push failed" unless system("gem", "push", gem_path)
46
+ puts "Published #{gem_path}"
47
+ else
48
+ puts "Release artifact verified: #{gem_path}"
49
+ puts "To publish: RUBYDB_PUBLISH=1 GEM_HOST_API_KEY=<RubyGems API key> ruby scripts/release"
50
+ end
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Fail-closed release metadata validation. This stays separate from gem
5
+ # building so CI can prove that a tag has a reviewed changelog entry before
6
+ # any artifact is published.
7
+
8
+ require "pathname"
9
+
10
+ root = Pathname.new(__dir__).parent
11
+ $LOAD_PATH.unshift(root.join("lib").to_s)
12
+ require "rubydb/version"
13
+
14
+ version = RubyDB::VERSION
15
+ github_ref = ENV.fetch("GITHUB_REF_NAME", "").strip
16
+ requested_version = ENV.fetch("RUBYDB_RELEASE_VERSION", "").strip
17
+
18
+ tag_version = if github_ref.start_with?("v")
19
+ github_ref.delete_prefix("v")
20
+ end
21
+
22
+ if !requested_version.empty? && tag_version && requested_version != tag_version
23
+ abort "RUBYDB_RELEASE_VERSION #{requested_version} does not match GITHUB_REF_NAME #{github_ref}"
24
+ end
25
+
26
+ release_version = requested_version.empty? ? tag_version : requested_version
27
+ if release_version.nil? || release_version.empty?
28
+ if ENV["RUBYDB_PUBLISH"] == "1"
29
+ abort "Publishing requires GITHUB_REF_NAME=v#{version} or RUBYDB_RELEASE_VERSION=#{version}"
30
+ end
31
+
32
+ puts "Release preflight skipped: no release tag or explicit release version"
33
+ exit 0
34
+ end
35
+
36
+ abort "Release version #{release_version} does not match RubyDB::VERSION #{version}" unless release_version == version
37
+
38
+ changelog_path = root.join("CHANGELOG.md")
39
+ changelog = changelog_path.read
40
+ header = /^##\s+#{Regexp.escape(release_version)}(?:\s|$)/
41
+ abort "CHANGELOG.md has no entry for version #{release_version}" unless changelog.match?(header)
42
+
43
+ puts "Release preflight passed for RubyDB #{release_version}"