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,546 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module Execution
5
+ # Planner - Converts AST to execution plan
6
+ class Planner
7
+ attr_reader :engine, :catalog
8
+
9
+ def initialize(engine)
10
+ @engine = engine
11
+ @catalog = engine.catalog if engine.respond_to?(:catalog)
12
+ @stats = {
13
+ plans_created: 0,
14
+ optimizations_applied: 0
15
+ }
16
+ end
17
+
18
+ def plan(statement)
19
+ @stats[:plans_created] += 1
20
+
21
+ plan = case statement
22
+ when SQL::AST::With
23
+ Plan::With.new(statement.ctes.map { |name, query| [name, plan(query)] }, plan(statement.query), recursive: statement.recursive)
24
+ when SQL::AST::Select
25
+ plan_select(statement)
26
+ when SQL::AST::SetOperation
27
+ Plan::SetOperation.new(plan(statement.left), plan(statement.right), statement.operator, all: statement.all)
28
+ when SQL::AST::Insert
29
+ plan_insert(statement)
30
+ when SQL::AST::Update
31
+ plan_update(statement)
32
+ when SQL::AST::Delete
33
+ plan_delete(statement)
34
+ when SQL::AST::CreateTable
35
+ plan_create_table(statement)
36
+ when SQL::AST::DropTable
37
+ plan_drop_table(statement)
38
+ when SQL::AST::CreateDatabase
39
+ Plan::CreateDatabase.new(statement.name, if_not_exists: statement.if_not_exists)
40
+ when SQL::AST::DropDatabase
41
+ Plan::DropDatabase.new(statement.name, if_exists: statement.if_exists)
42
+ when SQL::AST::CreateSchema
43
+ Plan::CreateSchema.new(statement.name, if_not_exists: statement.if_not_exists, authorization: statement.authorization)
44
+ when SQL::AST::DropSchema
45
+ Plan::DropSchema.new(statement.name, if_exists: statement.if_exists, cascade: statement.cascade)
46
+ when SQL::AST::CreateView
47
+ Plan::CreateView.new(statement.name, statement.query, if_not_exists: statement.if_not_exists)
48
+ when SQL::AST::DropView
49
+ Plan::DropView.new(statement.name, if_exists: statement.if_exists)
50
+ when SQL::AST::CreateTrigger
51
+ Plan::CreateTrigger.new(statement.name, statement.timing, statement.event, statement.table_name, statement.function_name)
52
+ when SQL::AST::DropTrigger
53
+ Plan::DropTrigger.new(statement.name, if_exists: statement.if_exists)
54
+ when SQL::AST::Vacuum
55
+ Plan::Vacuum.new
56
+ when SQL::AST::AlterTableAddColumn
57
+ options = statement.options.transform_values { |value| value.respond_to?(:value) ? value.value : value }
58
+ Plan::AlterTableAddColumn.new(statement.table_name, statement.column_name, statement.column_type, options)
59
+ when SQL::AST::AlterTableDropColumn
60
+ Plan::AlterTableDropColumn.new(statement.table_name, statement.column_name)
61
+ when SQL::AST::AlterTableAddConstraint
62
+ Plan::AlterTableAddConstraint.new(statement.table_name, statement.constraint)
63
+ when SQL::AST::AlterTableDropConstraint
64
+ Plan::AlterTableDropConstraint.new(statement.table_name, statement.constraint_name)
65
+ when SQL::AST::CreateIndex
66
+ plan_create_index(statement)
67
+ when SQL::AST::DropIndex
68
+ plan_drop_index(statement)
69
+ when SQL::AST::BeginTransaction
70
+ plan_begin_transaction(statement)
71
+ when SQL::AST::Commit
72
+ plan_commit(statement)
73
+ when SQL::AST::Rollback
74
+ plan_rollback(statement)
75
+ when SQL::AST::Savepoint
76
+ Plan::Savepoint.new(statement.name)
77
+ when SQL::AST::RollbackToSavepoint
78
+ Plan::RollbackToSavepoint.new(statement.savepoint_name)
79
+ when SQL::AST::ReleaseSavepoint
80
+ Plan::ReleaseSavepoint.new(statement.name)
81
+ when SQL::AST::Explain
82
+ plan_explain(statement)
83
+ else
84
+ raise ExecutionError, "Unknown statement type: #{statement.class}"
85
+ end
86
+
87
+ # Apply optimizations
88
+ plan = optimize(plan)
89
+
90
+ plan
91
+ end
92
+
93
+ def plan_select(statement)
94
+ table_name = statement.table_name
95
+ columns = statement.columns.map { |col| col.expression.name rescue col.to_s }
96
+
97
+ plan = Plan::Select.new(table_name, columns)
98
+ plan.set_source_reference(statement.from)
99
+
100
+ if statement.joins&.any?
101
+ plan.set_joins(statement.joins.map do |join|
102
+ { type: join.type, table: join.table, predicate: build_predicate(join.condition) }
103
+ end)
104
+ end
105
+
106
+ # Handle projections
107
+ if statement.columns.any?
108
+ plan.set_projections(statement.columns)
109
+ end
110
+
111
+ # Handle WHERE clause
112
+ if statement.where
113
+ predicate = build_predicate(statement.where)
114
+ plan.set_predicate(predicate)
115
+ end
116
+
117
+ # Handle ORDER BY
118
+ if statement.order_by&.any?
119
+ order_by = statement.order_by.map do |order|
120
+ column = order.expression.respond_to?(:name) ? order.expression.name : order.expression.to_s
121
+ table = order.expression.respond_to?(:table) ? order.expression.table : nil
122
+ { column: table ? "#{table}.#{column}" : column, direction: order.direction }
123
+ end
124
+ plan.set_order_by(order_by)
125
+ end
126
+
127
+ # Handle GROUP BY
128
+ aggregates = statement.columns.select do |column|
129
+ expression = column.respond_to?(:expression) ? column.expression : column
130
+ expression.is_a?(SQL::AST::FunctionCall) && !expression.window &&
131
+ %w[COUNT SUM AVG MIN MAX].include?(expression.name.to_s.upcase)
132
+ end
133
+ if statement.group_by&.any? || aggregates.any?
134
+ plan.set_group_by(statement.group_by || [], aggregates)
135
+ end
136
+ plan.set_having(build_predicate(statement.having)) if statement.having
137
+
138
+ # Handle LIMIT and OFFSET
139
+ if statement.limit
140
+ plan.set_limit(statement.limit.value, statement.offset&.value)
141
+ end
142
+
143
+ # Handle DISTINCT
144
+ plan.set_distinct(statement.distinct) if statement.distinct
145
+
146
+ # Choose scan method
147
+ choose_scan_method(plan)
148
+
149
+ # Estimate cost
150
+ estimate_cost(plan)
151
+
152
+ plan
153
+ end
154
+
155
+ def plan_insert(statement)
156
+ Plan::Insert.new(
157
+ statement.table,
158
+ statement.columns,
159
+ statement.values,
160
+ rows: statement.rows,
161
+ on_conflict: statement.on_conflict
162
+ )
163
+ end
164
+
165
+ def plan_update(statement)
166
+ assignments = statement.assignments.map do |ass|
167
+ { column: ass.column, value: ass.value }
168
+ end
169
+
170
+ plan = Plan::Update.new(statement.table, assignments)
171
+
172
+ if statement.where
173
+ plan.set_predicate(build_predicate(statement.where))
174
+ end
175
+
176
+ plan
177
+ end
178
+
179
+ def plan_delete(statement)
180
+ plan = Plan::Delete.new(statement.table)
181
+
182
+ if statement.where
183
+ plan.set_predicate(build_predicate(statement.where))
184
+ end
185
+
186
+ plan
187
+ end
188
+
189
+ def plan_create_table(statement)
190
+ columns = statement.columns.map do |col|
191
+ Catalog::Column.new(col.name, col.type_class, **col.options)
192
+ end
193
+
194
+ Plan::CreateTable.new(
195
+ statement.name,
196
+ columns,
197
+ if_not_exists: statement.if_not_exists,
198
+ constraints: statement.constraints
199
+ )
200
+ end
201
+
202
+ def plan_drop_table(statement)
203
+ Plan::DropTable.new(statement.name, if_exists: statement.if_exists)
204
+ end
205
+
206
+ def plan_create_index(statement)
207
+ Plan::CreateIndex.new(
208
+ statement.name,
209
+ statement.table_name,
210
+ statement.columns,
211
+ unique: statement.unique,
212
+ if_not_exists: statement.if_not_exists
213
+ )
214
+ end
215
+
216
+ def plan_drop_index(statement)
217
+ Plan::DropIndex.new(statement.name, if_exists: statement.if_exists)
218
+ end
219
+
220
+ def plan_begin_transaction(statement)
221
+ Plan::BeginTransaction.new(statement.isolation_level)
222
+ end
223
+
224
+ def plan_commit(_statement)
225
+ Plan::Commit.new
226
+ end
227
+
228
+ def plan_rollback(_statement)
229
+ Plan::Rollback.new
230
+ end
231
+
232
+ def plan_explain(statement)
233
+ Plan::Explain.new(statement.statement, statement.analyze)
234
+ end
235
+
236
+ # Predicate building
237
+ def build_predicate(ast)
238
+ case ast
239
+ when SQL::AST::BinaryOp
240
+ left = build_predicate(ast.left)
241
+ right = build_predicate(ast.right)
242
+
243
+ case ast.operator
244
+ when :AND, :and
245
+ Predicate::And.new(left, right)
246
+ when :OR, :or
247
+ Predicate::Or.new(left, right)
248
+ when :EQ, :NE, :LT, :LTE, :GT, :GTE
249
+ Predicate::Comparison.new(
250
+ build_expression(ast.left),
251
+ build_expression(ast.right),
252
+ { EQ: :eq, NE: :ne, LT: :lt, LTE: :lte, GT: :gt, GTE: :gte }.fetch(ast.operator)
253
+ )
254
+ when :LIKE, :ILIKE
255
+ Predicate::Like.new(
256
+ build_expression(ast.left),
257
+ build_expression(ast.right),
258
+ ast.operator == :LIKE
259
+ )
260
+ else
261
+ nil
262
+ end
263
+ when SQL::AST::UnaryOp
264
+ operand = build_predicate(ast.operand)
265
+ Predicate::Not.new(operand)
266
+ when SQL::AST::Between
267
+ Predicate::Between.new(
268
+ build_expression(ast.expression),
269
+ build_expression(ast.low),
270
+ build_expression(ast.high)
271
+ )
272
+ when SQL::AST::In
273
+ values = ast.values.flat_map do |value|
274
+ if value.is_a?(SQL::AST::Subquery)
275
+ Executor.new(@engine).execute(plan(value.query))[:rows].map { |row| Expression::Literal.new(row.values.first) }
276
+ else
277
+ [build_expression(value)]
278
+ end
279
+ end
280
+ Predicate::In.new(build_expression(ast.expression), values)
281
+ when SQL::AST::Exists
282
+ exists = Executor.new(@engine).execute(plan(ast.query))[:rows].any?
283
+ Predicate::Comparison.new(Expression::Literal.new(exists), Expression::Literal.new(true), :eq)
284
+ when SQL::AST::IsNull
285
+ Predicate::IsNull.new(build_expression(ast.expression), ast.negated)
286
+ else
287
+ Predicate::Comparison.new(
288
+ build_expression(ast),
289
+ build_expression(SQL::AST::Literal.new(true, :TRUE)),
290
+ :eq
291
+ )
292
+ end
293
+ end
294
+
295
+ def build_expression(ast)
296
+ case ast
297
+ when SQL::AST::Literal
298
+ Expression::Literal.new(ast.value)
299
+ when SQL::AST::Identifier
300
+ Expression::Column.new(ast.name, ast.table)
301
+ when SQL::AST::BinaryOp
302
+ left = build_expression(ast.left)
303
+ right = build_expression(ast.right)
304
+ operator = {
305
+ PLUS: :plus, MINUS: :minus, STAR: :multiply, SLASH: :divide,
306
+ PERCENT: :modulo, EQ: :eq
307
+ }[ast.operator] || ast.operator
308
+ Expression::BinaryOp.new(left, right, operator)
309
+ when SQL::AST::UnaryOp
310
+ operand = build_expression(ast.operand)
311
+ Expression::UnaryOp.new(operand, ast.operator)
312
+ when SQL::AST::FunctionCall
313
+ args = ast.arguments.map { |arg| build_expression(arg) }
314
+ Expression::Function.new(ast.name, args)
315
+ when SQL::AST::Parameter
316
+ Expression::Parameter.new(ast.index)
317
+ else
318
+ Expression::Literal.new(nil)
319
+ end
320
+ end
321
+
322
+ # Scan method selection
323
+ def choose_scan_method(plan)
324
+ return unless plan.type == :select
325
+
326
+ table_name = plan.table_name
327
+ return plan.set_scan_type(:sequential) unless table_name
328
+
329
+ # Check if there's an index that can be used
330
+ if @engine.respond_to?(:index_manager)
331
+ indexes = @engine.index_manager.get_indexes_for_table(table_name)
332
+
333
+ # Try to find a matching index for the predicate
334
+ if plan.predicate && indexes.any?
335
+ index = find_matching_index(plan.predicate, indexes)
336
+ if index
337
+ plan.set_scan_type(:index, index)
338
+ return
339
+ end
340
+ end
341
+
342
+ # A full index scan is not a substitute for a table scan: it can
343
+ # omit rows while an index is being maintained or rebuilt. Only use
344
+ # an index when the predicate specifically matches its columns.
345
+ end
346
+
347
+ # Default to sequential scan
348
+ plan.set_scan_type(:sequential)
349
+ end
350
+
351
+ def find_matching_index(predicate, indexes)
352
+ # Get columns used in predicate
353
+ columns = extract_predicate_columns(predicate)
354
+
355
+ indexes.each do |index|
356
+ # Check if predicate columns match index columns
357
+ if index.columns.any? { |col| columns.map(&:to_s).include?(col.to_s) }
358
+ return index
359
+ end
360
+ end
361
+
362
+ nil
363
+ end
364
+
365
+ def extract_predicate_columns(predicate)
366
+ return [] if predicate.nil?
367
+
368
+ case predicate
369
+ when Predicate::Comparison
370
+ left_name = predicate.left.respond_to?(:name) ? predicate.left.name : nil
371
+ [left_name].compact
372
+ when Predicate::And, Predicate::Or
373
+ extract_predicate_columns(predicate.left) + extract_predicate_columns(predicate.right)
374
+ when Predicate::Not
375
+ extract_predicate_columns(predicate.operand)
376
+ when Predicate::Between, Predicate::In, Predicate::IsNull, Predicate::Like
377
+ expr_name = predicate.expression.respond_to?(:name) ? predicate.expression.name : nil
378
+ [expr_name].compact
379
+ else
380
+ []
381
+ end
382
+ end
383
+
384
+ # Cost estimation
385
+ def estimate_cost(plan)
386
+ table_name = plan.table_name
387
+ row_count = @engine.table_row_count(table_name) rescue 0
388
+
389
+ case plan.scan_type
390
+ when :sequential
391
+ plan.set_cost(row_count, row_count)
392
+ when :index
393
+ # Index scan is cheaper for large tables with selective predicates
394
+ if plan.predicate
395
+ selectivity = estimate_selectivity(plan.predicate)
396
+ estimated_rows = (row_count * selectivity).ceil
397
+ plan.set_cost(estimated_rows * 0.1, estimated_rows)
398
+ else
399
+ plan.set_cost(row_count * 0.5, row_count)
400
+ end
401
+ else
402
+ plan.set_cost(row_count, row_count)
403
+ end
404
+ end
405
+
406
+ def estimate_selectivity(predicate)
407
+ case predicate
408
+ when Predicate::Comparison
409
+ case predicate.operator
410
+ when :EQ then 0.01
411
+ when :NE then 0.9
412
+ when :LT, :LTE, :GT, :GTE then 0.5
413
+ else 0.5
414
+ end
415
+ when Predicate::And
416
+ estimate_selectivity(predicate.left) * estimate_selectivity(predicate.right)
417
+ when Predicate::Or
418
+ estimate_selectivity(predicate.left) + estimate_selectivity(predicate.right) -
419
+ estimate_selectivity(predicate.left) * estimate_selectivity(predicate.right)
420
+ when Predicate::Not
421
+ 1.0 - estimate_selectivity(predicate.operand)
422
+ else
423
+ 0.5
424
+ end
425
+ end
426
+
427
+ # Optimization
428
+ def optimize(plan)
429
+ @stats[:optimizations_applied] += 1
430
+
431
+ # Push down predicates
432
+ plan = push_down_predicates(plan)
433
+
434
+ # Remove redundant conditions
435
+ plan = remove_redundant_conditions(plan)
436
+
437
+ # Reorder only a pure inner-join chain. Outer joins are order
438
+ # sensitive, and cross joins are an explicit user request, so those
439
+ # remain in source order. Predicate dependencies prevent a join from
440
+ # being moved before a table referenced by its ON clause.
441
+ reorder_inner_joins(plan) if plan.is_a?(Plan::Select)
442
+
443
+ plan
444
+ end
445
+
446
+ def push_down_predicates(plan)
447
+ # Push predicates down to scan level
448
+ # This is already done in the planner
449
+ plan
450
+ end
451
+
452
+ def reorder_inner_joins(plan)
453
+ joins = plan.joins
454
+ return plan if joins.empty? || joins.any? { |join| join[:type] != :inner }
455
+
456
+ available = [plan.source_reference&.name || plan.table_name].compact.map(&:to_s)
457
+ pending = joins.dup
458
+ ordered = []
459
+
460
+ until pending.empty?
461
+ eligible = pending.select do |join|
462
+ table = join[:table]
463
+ right_names = [table.name, table.alias_name].compact.map(&:to_s)
464
+ referenced = predicate_table_names(join[:predicate])
465
+ (referenced - (available + right_names)).empty?
466
+ end
467
+ eligible = [pending.first] if eligible.empty?
468
+
469
+ chosen = eligible.min_by do |join|
470
+ @engine.table_row_count(join[:table].name) rescue 1_000_000
471
+ end
472
+ pending.delete(chosen)
473
+ ordered << chosen
474
+ available.concat([chosen[:table].name, chosen[:table].alias_name].compact.map(&:to_s))
475
+ end
476
+
477
+ plan.set_joins(ordered)
478
+ plan
479
+ end
480
+
481
+ def predicate_table_names(predicate)
482
+ case predicate
483
+ when Predicate::Comparison
484
+ expression_table_names(predicate.left) + expression_table_names(predicate.right)
485
+ when Predicate::And, Predicate::Or
486
+ predicate_table_names(predicate.left) + predicate_table_names(predicate.right)
487
+ when Predicate::Not
488
+ predicate_table_names(predicate.operand)
489
+ when Predicate::Between
490
+ expression_table_names(predicate.expression) + expression_table_names(predicate.low) +
491
+ expression_table_names(predicate.high)
492
+ when Predicate::In, Predicate::IsNull, Predicate::Like
493
+ expression_table_names(predicate.expression)
494
+ else
495
+ []
496
+ end.uniq
497
+ end
498
+
499
+ def expression_table_names(expression)
500
+ case expression
501
+ when Expression::Column
502
+ expression.table ? [expression.table.to_s] : []
503
+ when Expression::BinaryOp
504
+ expression_table_names(expression.left) + expression_table_names(expression.right)
505
+ when Expression::UnaryOp
506
+ expression_table_names(expression.operand)
507
+ when Expression::Function
508
+ expression.arguments.flat_map { |argument| expression_table_names(argument) }
509
+ else
510
+ []
511
+ end
512
+ end
513
+
514
+ def remove_redundant_conditions(plan)
515
+ if plan.predicate.is_a?(Predicate::And)
516
+ # Simplify AND conditions
517
+ simplified = simplify_and(plan.predicate)
518
+ plan.set_predicate(simplified)
519
+ end
520
+ plan
521
+ end
522
+
523
+ def simplify_and(predicate)
524
+ left = predicate.left
525
+ right = predicate.right
526
+
527
+ # Remove redundant TRUE conditions
528
+ if left.is_a?(Predicate::Comparison) && left.left.is_a?(Expression::Literal) &&
529
+ left.left.value == true && left.operator == :EQ
530
+ return right
531
+ end
532
+
533
+ if right.is_a?(Predicate::Comparison) && right.left.is_a?(Expression::Literal) &&
534
+ right.left.value == true && right.operator == :EQ
535
+ return left
536
+ end
537
+
538
+ predicate
539
+ end
540
+
541
+ def stats
542
+ @stats
543
+ end
544
+ end
545
+ end
546
+ end