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,362 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module RubyDB
6
+ module Transactions
7
+
8
+ # Import lock and transaction classes
9
+ require_relative "lock"
10
+ require_relative "transaction"
11
+
12
+ # LockManager - Manages locks for transactions
13
+ class LockManager
14
+ attr_reader :locks, :waiting, :stats
15
+
16
+ def initialize(config = {})
17
+ @locks = {}
18
+ @waiting = {}
19
+ @lock_timeout = config[:lock_timeout] || 30
20
+ @deadlock_detection = config[:deadlock_detection] != false
21
+ @transaction_manager = config[:transaction_manager]
22
+ @stats = {
23
+ locks_acquired: 0,
24
+ locks_released: 0,
25
+ lock_waits: 0,
26
+ lock_timeouts: 0,
27
+ deadlocks_detected: 0,
28
+ deadlocks_resolved: 0
29
+ }
30
+ @lock = Mutex.new
31
+ @condition = ConditionVariable.new
32
+ @transactions = {}
33
+ @deadlock_victims = {}
34
+ end
35
+
36
+ def acquire_lock(transaction, table_name, row_id, lock_type, timeout = @lock_timeout)
37
+ @lock.synchronize do
38
+ @transactions[transaction.id] = transaction
39
+ return false unless transaction.active?
40
+
41
+ key = lock_key(table_name, row_id)
42
+
43
+ # Check if transaction already has lock
44
+ if has_lock?(transaction, key)
45
+ return upgrade_lock(transaction, key, lock_type)
46
+ end
47
+
48
+ # Check if lock is available
49
+ current_lock = @locks[key]
50
+
51
+ if current_lock.nil?
52
+ # No lock exists - acquire
53
+ @locks[key] = Lock.new(key, lock_type, transaction)
54
+ @stats[:locks_acquired] += 1
55
+ return true
56
+ end
57
+
58
+ # Check compatibility
59
+ if compatible?(current_lock, lock_type, transaction)
60
+ # Add transaction to lock
61
+ current_lock.add_holder(transaction, lock_type)
62
+ @stats[:locks_acquired] += 1
63
+ return true
64
+ end
65
+
66
+ # Lock conflict - wait
67
+ @stats[:lock_waits] += 1
68
+ result = wait_for_lock(transaction, key, lock_type, timeout)
69
+
70
+ result
71
+ end
72
+ end
73
+
74
+ def release_locks(transaction)
75
+ @lock.synchronize do
76
+ @locks.each do |key, lock|
77
+ if lock.holders.key?(transaction.id)
78
+ lock.remove_holder(transaction)
79
+ @stats[:locks_released] += 1
80
+
81
+ # Remove empty lock
82
+ if lock.holders.empty?
83
+ @locks.delete(key)
84
+
85
+ # Wake up waiting transactions
86
+ wake_waiting_transactions(key)
87
+ @condition.broadcast
88
+ end
89
+ end
90
+ end
91
+ @transactions.delete(transaction.id)
92
+ end
93
+ end
94
+
95
+ def release_all_locks
96
+ @lock.synchronize do
97
+ @locks.clear
98
+ @waiting.clear
99
+ @transactions.clear
100
+ @deadlock_victims.clear
101
+ @stats[:locks_released] += 1
102
+ end
103
+ end
104
+
105
+ def has_lock?(transaction, key)
106
+ lock = @locks[key]
107
+ return false unless lock
108
+ lock.holders.key?(transaction.id)
109
+ end
110
+
111
+ def lock_type(transaction, key)
112
+ lock = @locks[key]
113
+ return nil unless lock
114
+ lock.holders[transaction.id] if lock.holders.key?(transaction.id)
115
+ end
116
+
117
+ def waiting_transactions
118
+ @waiting.size
119
+ end
120
+
121
+ def deadlock_victim?(transaction_id)
122
+ @lock.synchronize { @deadlock_victims.key?(transaction_id) }
123
+ end
124
+
125
+ def clear_deadlock_victim(transaction_id)
126
+ @lock.synchronize { @deadlock_victims.delete(transaction_id) }
127
+ end
128
+
129
+ def total_locks
130
+ @locks.size
131
+ end
132
+
133
+ def lock_info
134
+ @locks.transform_values do |lock|
135
+ {
136
+ type: lock.type,
137
+ holders: lock.holders.keys,
138
+ waiters: lock.waiters.keys
139
+ }
140
+ end
141
+ end
142
+
143
+ # Return the current wait-for graph as transaction ids. This is a
144
+ # snapshot intended for monitoring and transaction-manager deadlock
145
+ # resolution; callers never receive mutable lock-manager state.
146
+ def wait_for_graph
147
+ @lock.synchronize { build_wait_for_graph }
148
+ end
149
+
150
+ private
151
+
152
+ def build_wait_for_graph
153
+ graph = {}
154
+ @waiting.each do |transaction_id, waits|
155
+ graph[transaction_id] = Set.new
156
+ waits.each_key do |key|
157
+ @locks[key]&.holders&.each_key do |holder_id|
158
+ graph[holder_id] ||= Set.new
159
+ graph[transaction_id] << holder_id
160
+ end
161
+ end
162
+ end
163
+ graph
164
+ end
165
+
166
+ def lock_key(table_name, row_id)
167
+ "#{table_name}:#{row_id}"
168
+ end
169
+
170
+ def compatible?(lock, lock_type, transaction)
171
+ # Check if any holder has incompatible lock
172
+ lock.holders.each do |holder_id, holder_type|
173
+ next if holder_id == transaction.id
174
+
175
+ if !compatible_lock_types(holder_type, lock_type)
176
+ return false
177
+ end
178
+ end
179
+
180
+ true
181
+ end
182
+
183
+ def compatible_lock_types(type1, type2)
184
+ # Shared locks are compatible with shared locks
185
+ if type1 == :shared && type2 == :shared
186
+ return true
187
+ end
188
+
189
+ # Exclusive locks are incompatible with any other
190
+ if type1 == :exclusive || type2 == :exclusive
191
+ return false
192
+ end
193
+
194
+ true
195
+ end
196
+
197
+ def upgrade_lock(transaction, key, new_type)
198
+ lock = @locks[key]
199
+ current_type = lock.holders[transaction.id]
200
+
201
+ # Check if upgrade is needed
202
+ return true if current_type == new_type
203
+
204
+ # Check if upgrade is possible
205
+ if new_type == :exclusive && current_type == :shared
206
+ # Need to check if other transactions hold shared locks
207
+ if lock.holders.size == 1 && lock.holders.key?(transaction.id)
208
+ # Only this transaction holds the lock - upgrade
209
+ lock.holders[transaction.id] = :exclusive
210
+ @stats[:locks_acquired] += 1
211
+ return true
212
+ end
213
+ end
214
+
215
+ false
216
+ end
217
+
218
+ def wait_for_lock(transaction, key, lock_type, timeout)
219
+ start_time = Time.now
220
+
221
+ # Add to waiters
222
+ @waiting[transaction.id] ||= {}
223
+ @waiting[transaction.id][key] = {
224
+ lock_type: lock_type,
225
+ start_time: start_time
226
+ }
227
+
228
+ # Wait loop
229
+ while (remaining = timeout - (Time.now - start_time)) > 0
230
+ if !transaction.active? || @deadlock_victims.key?(transaction.id)
231
+ waits = @waiting[transaction.id]
232
+ waits&.delete(key)
233
+ @waiting.delete(transaction.id) if waits&.empty?
234
+ return false
235
+ end
236
+
237
+ # Check if lock is available
238
+ if @locks[key].nil? || compatible?(@locks[key], lock_type, transaction)
239
+ # Remove from waiting
240
+ @waiting[transaction.id].delete(key)
241
+ @waiting.delete(transaction.id) if @waiting[transaction.id].empty?
242
+
243
+ # Acquire lock
244
+ @locks[key] ||= Lock.new(key, lock_type)
245
+ @locks[key].add_holder(transaction, lock_type)
246
+ @stats[:locks_acquired] += 1
247
+ return true
248
+ end
249
+
250
+ # ConditionVariable releases @lock while waiting, allowing the
251
+ # current holder to release its lock and wake this waiter.
252
+ @condition.wait(@lock, remaining)
253
+ end
254
+
255
+ # Detect while the timed-out waiter is still in the wait graph. The
256
+ # previous ordering removed it first, making a two-transaction cycle
257
+ # impossible to observe. Resolution is performed by the unlocked
258
+ # helper because this method already owns @lock.
259
+ detect_deadlock if @deadlock_detection
260
+
261
+ # Timeout or deadlock victim
262
+ @stats[:lock_timeouts] += 1
263
+ waits = @waiting[transaction.id]
264
+ waits&.delete(key)
265
+ @waiting.delete(transaction.id) if waits&.empty?
266
+ false
267
+ end
268
+
269
+ def wake_waiting_transactions(key)
270
+ # Find waiting transactions for this key
271
+ @waiting.each do |txn_id, waits|
272
+ if waits.key?(key)
273
+ # Transaction is waiting - it will be woken in wait loop
274
+ end
275
+ end
276
+ end
277
+
278
+ def detect_deadlock
279
+ graph = build_wait_for_graph
280
+
281
+ # Detect cycles
282
+ cycles = detect_cycles(graph)
283
+
284
+ if cycles.any?
285
+ @stats[:deadlocks_detected] += 1
286
+ # Resolve by aborting the lowest-priority transaction. The victim is
287
+ # marked before locks are released so its waiter cannot reacquire a
288
+ # lock after the condition variable wakes it.
289
+ cycles.each do |cycle|
290
+ victim = cycle.compact.min_by { |id| [@transactions[id]&.priority.to_i, id.to_s] }
291
+ abort_transaction_unlocked(victim) if victim
292
+ end
293
+ end
294
+ end
295
+
296
+ def detect_cycles(graph)
297
+ cycles = []
298
+ visited = Set.new
299
+ active = Set.new
300
+ path = []
301
+
302
+ graph.keys.each do |node|
303
+ detect_cycle_dfs(node, graph, visited, active, path, cycles) unless visited.include?(node)
304
+ end
305
+
306
+ cycles
307
+ end
308
+
309
+ def detect_cycle_dfs(node, graph, visited, active, path, cycles)
310
+ if active.include?(node)
311
+ start = path.index(node)
312
+ cycle = path[start..] + [node]
313
+ cycles << cycle unless cycles.any? { |existing| existing == cycle }
314
+ return
315
+ end
316
+ return if visited.include?(node)
317
+
318
+ visited.add(node)
319
+ active.add(node)
320
+ path << node
321
+
322
+ graph[node]&.each do |neighbor|
323
+ detect_cycle_dfs(neighbor, graph, visited, active, path, cycles)
324
+ end
325
+
326
+ path.pop
327
+ active.delete(node)
328
+ end
329
+
330
+ def abort_transaction(transaction_id)
331
+ transaction = if @transaction_manager.respond_to?(:get_transaction)
332
+ @transaction_manager.get_transaction(transaction_id)
333
+ end
334
+ if transaction && @transaction_manager.respond_to?(:rollback_transaction)
335
+ @transaction_manager.rollback_transaction(transaction)
336
+ end
337
+ @lock.synchronize { abort_transaction_unlocked(transaction_id) }
338
+ end
339
+
340
+ # Called only while @lock is held. It must never call back into the
341
+ # transaction manager, whose own mutex may be held by the caller.
342
+ def abort_transaction_unlocked(transaction_id)
343
+ transaction = @transactions[transaction_id]
344
+ if @transaction_manager
345
+ @deadlock_victims[transaction_id] = transaction if transaction
346
+ else
347
+ transaction&.abort
348
+ end
349
+
350
+ @waiting.delete(transaction_id)
351
+ @locks.each do |key, lock|
352
+ next unless lock.holders.key?(transaction_id)
353
+
354
+ lock.remove_holder(Transaction.new(id: transaction_id))
355
+ @locks.delete(key) if lock.holders.empty?
356
+ end
357
+ @stats[:deadlocks_resolved] += 1
358
+ @condition.broadcast
359
+ end
360
+ end
361
+ end
362
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module Transactions
5
+
6
+ # Import transaction dependency
7
+ require_relative "transaction"
8
+ # Savepoint - Represents a savepoint within a transaction
9
+ class Savepoint
10
+ attr_reader :name, :transaction, :created_at, :position
11
+ attr_reader :modified_rows, :locked_rows, :changes
12
+
13
+ def initialize(name, transaction)
14
+ @name = name
15
+ @transaction = transaction
16
+ @created_at = Time.now
17
+ @position = transaction.savepoints.size
18
+ @modified_rows = {}
19
+ @locked_rows = {}
20
+ @changes = []
21
+ @state = :active
22
+ @lock = Mutex.new
23
+
24
+ # Save current state
25
+ save_state
26
+ end
27
+
28
+ def active?
29
+ @state == :active
30
+ end
31
+
32
+ def released?
33
+ @state == :released
34
+ end
35
+
36
+ def rollbacked?
37
+ @state == :rollbacked
38
+ end
39
+
40
+ def rollback
41
+ @lock.synchronize do
42
+ return false unless active?
43
+
44
+ # Rollback changes in reverse order
45
+ @changes.reverse.each do |change|
46
+ undo_change(change)
47
+ end
48
+
49
+ @state = :rollbacked
50
+ true
51
+ end
52
+ end
53
+
54
+ def release
55
+ @lock.synchronize do
56
+ return false unless active?
57
+ @state = :released
58
+ true
59
+ end
60
+ end
61
+
62
+ def record_change(change)
63
+ @lock.synchronize do
64
+ @changes << change
65
+ end
66
+ end
67
+
68
+ def record_modified_row(table_name, row_id, old_value, new_value)
69
+ @lock.synchronize do
70
+ @modified_rows[row_id] = {
71
+ table: table_name,
72
+ old: old_value,
73
+ new: new_value
74
+ }
75
+ end
76
+ end
77
+
78
+ def record_locked_row(table_name, row_id, lock_type)
79
+ @lock.synchronize do
80
+ key = "#{table_name}:#{row_id}"
81
+ @locked_rows[key] = {
82
+ table: table_name,
83
+ row_id: row_id,
84
+ lock_type: lock_type
85
+ }
86
+ end
87
+ end
88
+
89
+ def to_hash
90
+ {
91
+ name: @name,
92
+ position: @position,
93
+ created_at: @created_at.iso8601,
94
+ state: @state,
95
+ changes: @changes.size,
96
+ modified_rows: @modified_rows.size,
97
+ locked_rows: @locked_rows.size
98
+ }
99
+ end
100
+
101
+ def inspect
102
+ "#<Savepoint name=#{@name} position=#{@position} state=#{@state}>"
103
+ end
104
+
105
+ private
106
+
107
+ def save_state
108
+ # Save current transaction state
109
+ @transaction.modified_rows.each do |row_id, info|
110
+ @modified_rows[row_id] = info.dup
111
+ end
112
+ @transaction.locked_rows.each do |key, info|
113
+ @locked_rows[key] = info.dup
114
+ end
115
+ end
116
+
117
+ def undo_change(change)
118
+ case change[:type]
119
+ when :insert
120
+ # Delete inserted row
121
+ @transaction.instance_variable_get(:@engine).delete_row(
122
+ change[:table],
123
+ change[:row_id]
124
+ )
125
+ when :update
126
+ # Restore old values
127
+ @transaction.instance_variable_get(:@engine).update_row(
128
+ change[:table],
129
+ change[:row_id],
130
+ change[:old_values]
131
+ )
132
+ when :delete
133
+ # Reinsert deleted row
134
+ @transaction.instance_variable_get(:@engine).insert_row(
135
+ change[:table],
136
+ change[:columns],
137
+ change[:row_data]
138
+ )
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,215 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require "time"
5
+
6
+
7
+ # Import dependencies
8
+ require_relative "savepoint" # For Savepoint class
9
+ require_relative "transaction_id"
10
+
11
+ module RubyDB
12
+ module Transactions
13
+ # Transaction - Represents a database transaction
14
+ class Transaction
15
+ attr_reader :id, :start_time, :status, :isolation_level, :read_only, :priority
16
+ attr_reader :timeout
17
+ attr_reader :savepoints, :locked_rows, :modified_rows, :accessed_tables
18
+ attr_reader :changes
19
+ attr_accessor :parent_transaction
20
+
21
+ # Transaction statuses
22
+ STATUS_ACTIVE = :active
23
+ STATUS_COMMITTED = :committed
24
+ STATUS_ABORTED = :aborted
25
+ STATUS_PREPARED = :prepared
26
+ STATUS_IN_DOUBT = :in_doubt
27
+
28
+ def initialize(options = {})
29
+ @id = options[:id] || generate_id
30
+ @start_time = Time.now
31
+ @status = STATUS_ACTIVE
32
+ @isolation_level = options[:isolation_level] || :read_committed
33
+ @read_only = options[:read_only] || false
34
+ @parent_transaction = options[:parent]
35
+ @savepoints = []
36
+ @locked_rows = {}
37
+ @modified_rows = {}
38
+ @accessed_tables = Set.new
39
+ @changes = []
40
+ @undo_log = []
41
+ @timeout = options[:timeout] || 30 # 30 seconds default
42
+ @deadline = @start_time + @timeout
43
+ @lock = Mutex.new
44
+ @name = options[:name]
45
+ @priority = options[:priority] || 0
46
+ end
47
+
48
+ def active?
49
+ @status == STATUS_ACTIVE
50
+ end
51
+
52
+ def committed?
53
+ @status == STATUS_COMMITTED
54
+ end
55
+
56
+ def aborted?
57
+ @status == STATUS_ABORTED
58
+ end
59
+
60
+ def prepared?
61
+ @status == STATUS_PREPARED
62
+ end
63
+
64
+ def in_doubt?
65
+ @status == STATUS_IN_DOUBT
66
+ end
67
+
68
+ def expired?
69
+ Time.now > @deadline
70
+ end
71
+
72
+ def add_change(change)
73
+ @lock.synchronize do
74
+ @changes << change
75
+ @undo_log << create_undo_entry(change)
76
+ end
77
+ end
78
+
79
+ def add_modified_row(table_name, row_id, old_value, new_value)
80
+ @lock.synchronize do
81
+ @modified_rows[row_id] ||= {
82
+ table: table_name,
83
+ old: old_value,
84
+ new: new_value,
85
+ timestamp: Time.now
86
+ }
87
+ end
88
+ end
89
+
90
+ def add_locked_row(table_name, row_id, lock_type)
91
+ @lock.synchronize do
92
+ key = "#{table_name}:#{row_id}"
93
+ @locked_rows[key] = {
94
+ table: table_name,
95
+ row_id: row_id,
96
+ lock_type: lock_type,
97
+ timestamp: Time.now
98
+ }
99
+ end
100
+ end
101
+
102
+ def add_accessed_table(table_name)
103
+ @lock.synchronize do
104
+ @accessed_tables.add(table_name)
105
+ end
106
+ end
107
+
108
+ def create_savepoint(name = nil)
109
+ @lock.synchronize do
110
+ savepoint = Savepoint.new(name || "savepoint_#{@savepoints.size + 1}", self)
111
+ @savepoints << savepoint
112
+ savepoint
113
+ end
114
+ end
115
+
116
+ def rollback_to_savepoint(name)
117
+ @lock.synchronize do
118
+ index = @savepoints.index { |sp| sp.name == name }
119
+ return false unless index
120
+
121
+ # Rollback changes after this savepoint
122
+ @savepoints[index + 1..-1].each do |sp|
123
+ sp.rollback
124
+ end
125
+ @savepoints = @savepoints[0..index]
126
+
127
+ true
128
+ end
129
+ end
130
+
131
+ def release_savepoint(name)
132
+ @lock.synchronize do
133
+ index = @savepoints.index { |sp| sp.name == name }
134
+ return false unless index
135
+
136
+ @savepoints.delete_at(index)
137
+ true
138
+ end
139
+ end
140
+
141
+ def commit
142
+ @lock.synchronize do
143
+ return false unless active?
144
+ @status = STATUS_COMMITTED
145
+ @commit_time = Time.now
146
+ true
147
+ end
148
+ end
149
+
150
+ def abort
151
+ @lock.synchronize do
152
+ return false unless active?
153
+ @status = STATUS_ABORTED
154
+ @abort_time = Time.now
155
+ true
156
+ end
157
+ end
158
+
159
+ def prepare
160
+ @lock.synchronize do
161
+ return false unless active?
162
+ @status = STATUS_PREPARED
163
+ @prepare_time = Time.now
164
+ true
165
+ end
166
+ end
167
+
168
+ def mark_in_doubt
169
+ @lock.synchronize do
170
+ @status = STATUS_IN_DOUBT
171
+ @in_doubt_time = Time.now
172
+ end
173
+ end
174
+
175
+ def to_hash
176
+ {
177
+ id: @id,
178
+ start_time: @start_time.iso8601,
179
+ status: @status,
180
+ isolation_level: @isolation_level,
181
+ read_only: @read_only,
182
+ changes: @changes.size,
183
+ locked_rows: @locked_rows.size,
184
+ modified_rows: @modified_rows.size,
185
+ accessed_tables: @accessed_tables.to_a,
186
+ savepoints: @savepoints.map(&:name),
187
+ timeout: @timeout,
188
+ expired: expired?
189
+ }
190
+ end
191
+
192
+ def inspect
193
+ "#<Transaction id=#{@id} status=#{@status} isolation=#{@isolation_level}>"
194
+ end
195
+
196
+ def to_s
197
+ inspect
198
+ end
199
+
200
+ private
201
+
202
+ def generate_id
203
+ "txn_#{Time.now.to_i}_#{SecureRandom.hex(8)}"
204
+ end
205
+
206
+ def create_undo_entry(change)
207
+ {
208
+ change: change,
209
+ timestamp: Time.now,
210
+ type: change[:type]
211
+ }
212
+ end
213
+ end
214
+ end
215
+ end