rubydb 0.1.4 → 0.1.6

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 (469) hide show
  1. checksums.yaml +4 -4
  2. data/.github/PULL_REQUEST_TEMPLATE.md +15 -15
  3. data/.github/workflows/benchmark.yml +26 -26
  4. data/.github/workflows/compatibility.yml +63 -63
  5. data/.github/workflows/fuzz.yml +33 -33
  6. data/.github/workflows/lint.yml +21 -21
  7. data/.github/workflows/operations.yml +24 -24
  8. data/.github/workflows/production-validation.yml +111 -111
  9. data/.github/workflows/release.yml +77 -77
  10. data/.github/workflows/security.yml +39 -37
  11. data/.github/workflows/test.yml +26 -26
  12. data/.github/workflows/workload.yml +58 -58
  13. data/.gitignore +16 -5
  14. data/.rubocop.yml +50 -44
  15. data/.standard.yml +9 -14
  16. data/ARCHITECTURE.md +21 -21
  17. data/CHANGELOG.md +57 -27
  18. data/CODE_OF_CONDUCT.md +13 -13
  19. data/CONTRIBUTING.md +29 -29
  20. data/GOVERNANCE.md +16 -16
  21. data/Gemfile +18 -17
  22. data/Gemfile.lock +125 -71
  23. data/README.md +168 -12
  24. data/ROADMAP.md +27 -27
  25. data/Rakefile +76 -71
  26. data/SECURITY.md +54 -54
  27. data/SUPPORT.md +14 -14
  28. data/accelerator/bin/SHA256SUMS +6 -0
  29. data/accelerator/bin/rubydb-accelerator-darwin-amd64 +0 -0
  30. data/accelerator/bin/rubydb-accelerator-darwin-arm64 +0 -0
  31. data/accelerator/bin/rubydb-accelerator-linux-amd64 +0 -0
  32. data/accelerator/bin/rubydb-accelerator-linux-arm64 +0 -0
  33. data/accelerator/bin/rubydb-accelerator-windows-amd64.exe +0 -0
  34. data/accelerator/bin/rubydb-accelerator-windows-arm64.exe +0 -0
  35. data/accelerator/cmd/rubydb-accelerator/main.go +11 -0
  36. data/accelerator/go.mod +3 -0
  37. data/accelerator/internal/execution/aggregate.go +94 -0
  38. data/accelerator/internal/execution/distinct.go +22 -0
  39. data/accelerator/internal/execution/filter.go +73 -0
  40. data/accelerator/internal/execution/join.go +79 -0
  41. data/accelerator/internal/execution/operators.go +167 -0
  42. data/accelerator/internal/execution/scan.go +20 -0
  43. data/accelerator/internal/execution/sort.go +62 -0
  44. data/accelerator/internal/execution/types.go +136 -0
  45. data/accelerator/internal/execution/value.go +67 -0
  46. data/accelerator/internal/memory/arena.go +47 -0
  47. data/accelerator/internal/memory/reuse.go +22 -0
  48. data/accelerator/internal/metrics/registry.go +67 -0
  49. data/accelerator/internal/parallel/bounded_queue.go +56 -0
  50. data/accelerator/internal/parallel/scheduler.go +47 -0
  51. data/accelerator/internal/parallel/worker_pool.go +53 -0
  52. data/accelerator/internal/protocol/cancellation.go +48 -0
  53. data/accelerator/internal/protocol/columnar.go +263 -0
  54. data/accelerator/internal/protocol/frame.go +187 -0
  55. data/accelerator/internal/runtime/worker.go +521 -0
  56. data/accelerator/internal/storage/page_reader.go +81 -0
  57. data/accelerator/internal/storage/snapshot_scan.go +539 -0
  58. data/accelerator/internal/wal/checksum.go +13 -0
  59. data/accelerator/internal/wal/compression.go +41 -0
  60. data/accelerator/internal/wal/group_commit.go +24 -0
  61. data/accelerator/internal/wal/record_encoder.go +40 -0
  62. data/adapters/activerecord/Gemfile +11 -11
  63. data/adapters/activerecord/README.md +8 -3
  64. data/adapters/activerecord/lib/active_record/connection_adapters/rubydb_adapter.rb +881 -879
  65. data/adapters/activerecord/rubydb-activerecord.gemspec +21 -21
  66. data/adapters/activerecord/spec/rubydb_adapter_integration_spec.rb +143 -143
  67. data/adapters/ruby/README.md +18 -18
  68. data/adapters/sequel/README.md +11 -11
  69. data/config/monitoring/prometheus-alerts.yml +39 -39
  70. data/config/production.yml +36 -36
  71. data/docs/README.md +77 -71
  72. data/docs/architecture/concurrency.md +14 -14
  73. data/docs/architecture/current-state.md +125 -125
  74. data/docs/architecture/execution-engine.md +25 -25
  75. data/docs/architecture/go-accelerator.md +179 -0
  76. data/docs/architecture/indexes.md +19 -19
  77. data/docs/architecture/mvcc.md +19 -19
  78. data/docs/architecture/overview.md +13 -13
  79. data/docs/architecture/pages.md +11 -11
  80. data/docs/architecture/production-roadmap.md +82 -82
  81. data/docs/architecture/query-planner.md +20 -20
  82. data/docs/architecture/recovery.md +18 -18
  83. data/docs/architecture/sql-engine.md +12 -12
  84. data/docs/architecture/storage-engine.md +14 -14
  85. data/docs/architecture/transactions.md +10 -10
  86. data/docs/architecture/wal.md +28 -28
  87. data/docs/cli-cheatsheet.md +98 -98
  88. data/docs/cli.md +299 -275
  89. data/docs/contributing/architecture.md +9 -9
  90. data/docs/contributing/benchmarking.md +30 -14
  91. data/docs/contributing/development.md +16 -16
  92. data/docs/contributing/release-process.md +49 -49
  93. data/docs/contributing/testing.md +16 -16
  94. data/docs/debugging.md +229 -229
  95. data/docs/developer/branching.md +10 -10
  96. data/docs/developer/database-diff.md +10 -10
  97. data/docs/developer/local-development.md +49 -17
  98. data/docs/developer/snapshots.md +9 -9
  99. data/docs/developer/temporal-data.md +10 -10
  100. data/docs/developer-guide.md +297 -297
  101. data/docs/getting-started/first-database.md +16 -16
  102. data/docs/getting-started/first-query.md +13 -13
  103. data/docs/getting-started/installation.md +19 -19
  104. data/docs/getting-started/local-to-production.md +300 -300
  105. data/docs/getting-started/quickstart.md +17 -17
  106. data/docs/getting-started/rails.md +16 -16
  107. data/docs/hardening_backlog.md +93 -93
  108. data/docs/lessons-learned.md +112 -112
  109. data/docs/operations/backups.md +33 -33
  110. data/docs/operations/disaster-recovery.md +31 -31
  111. data/docs/operations/failover.md +30 -30
  112. data/docs/operations/monitoring.md +25 -25
  113. data/docs/operations/production-guide.md +295 -295
  114. data/docs/operations/production-runbook.md +45 -45
  115. data/docs/operations/replication.md +33 -33
  116. data/docs/operations/restore.md +6 -6
  117. data/docs/operations/runbook.md +34 -34
  118. data/docs/operations/upgrades.md +14 -14
  119. data/docs/operations/workload-testing.md +17 -17
  120. data/docs/production-readiness.md +118 -118
  121. data/docs/production_validation.md +150 -150
  122. data/docs/rails/active-record.md +11 -11
  123. data/docs/rails/compatibility-guide.md +90 -90
  124. data/docs/rails/database-yml.md +92 -92
  125. data/docs/rails/installation.md +17 -17
  126. data/docs/rails/migrations.md +17 -17
  127. data/docs/rails/production.md +82 -82
  128. data/docs/rails/troubleshooting.md +18 -18
  129. data/docs/release.md +25 -25
  130. data/docs/server/architecture.md +10 -10
  131. data/docs/server/authentication.md +10 -10
  132. data/docs/server/configuration.md +16 -16
  133. data/docs/server/connection-pooling.md +10 -10
  134. data/docs/server/deployment.md +10 -10
  135. data/docs/server/protocol.md +12 -12
  136. data/docs/sql/compatibility-guide.md +82 -82
  137. data/docs/sql/compatibility.md +39 -39
  138. data/docs/sql/data-types.md +10 -10
  139. data/docs/sql/functions.md +9 -9
  140. data/docs/sql/joins.md +9 -9
  141. data/docs/sql/operators.md +9 -9
  142. data/docs/sql/sqlite-compatibility.md +21 -21
  143. data/docs/sql/syntax.md +10 -10
  144. data/docs/sql/transactions.md +10 -10
  145. data/docs/troubleshooting.md +244 -244
  146. data/lessons/01-foundations.md +73 -0
  147. data/lessons/02-local-development.md +121 -0
  148. data/lessons/03-embedded-rubydb.md +99 -0
  149. data/lessons/04-rails-complex-apps.md +138 -0
  150. data/lessons/05-rubydb-production-server.md +237 -0
  151. data/lessons/06-postgresql-massive-apps.md +96 -0
  152. data/lessons/07-hybrid-microservices.md +179 -0
  153. data/lessons/08-migrations-backups-recovery.md +86 -0
  154. data/lessons/09-observability-security-scale.md +87 -0
  155. data/lessons/10-release-readiness.md +192 -0
  156. data/lessons/11-community-adapter.md +323 -0
  157. data/lessons/12-rails-ecommerce-pressure.md +263 -0
  158. data/lib/rubydb/accelerator/client.rb +451 -0
  159. data/lib/rubydb/accelerator/error.rb +22 -0
  160. data/lib/rubydb/accelerator/manager.rb +606 -0
  161. data/lib/rubydb/accelerator.rb +13 -0
  162. data/lib/rubydb/backup/archive.rb +332 -334
  163. data/lib/rubydb/backup/backup.rb +400 -401
  164. data/lib/rubydb/backup/incremental.rb +349 -353
  165. data/lib/rubydb/backup/restore.rb +289 -290
  166. data/lib/rubydb/backup/snapshot.rb +265 -267
  167. data/lib/rubydb/backup/verification.rb +276 -279
  168. data/lib/rubydb/branching/branch.rb +181 -181
  169. data/lib/rubydb/branching/branch_manager.rb +307 -311
  170. data/lib/rubydb/branching/branch_metadata.rb +140 -140
  171. data/lib/rubydb/branching/checkout.rb +165 -166
  172. data/lib/rubydb/branching/copy_on_write.rb +272 -272
  173. data/lib/rubydb/branching/diff.rb +137 -138
  174. data/lib/rubydb/branching/merge.rb +282 -285
  175. data/lib/rubydb/build_info.rb +15 -15
  176. data/lib/rubydb/catalog/catalog.rb +391 -391
  177. data/lib/rubydb/catalog/column.rb +112 -112
  178. data/lib/rubydb/catalog/constraint.rb +180 -180
  179. data/lib/rubydb/catalog/database.rb +184 -184
  180. data/lib/rubydb/catalog/index.rb +97 -97
  181. data/lib/rubydb/catalog/schema.rb +103 -103
  182. data/lib/rubydb/catalog/sequence.rb +90 -90
  183. data/lib/rubydb/catalog/system_catalog.rb +698 -698
  184. data/lib/rubydb/catalog/table.rb +178 -178
  185. data/lib/rubydb/catalog/trigger.rb +102 -102
  186. data/lib/rubydb/catalog/view.rb +66 -66
  187. data/lib/rubydb/cli/application.rb +168 -163
  188. data/lib/rubydb/cli/commands/accelerator.rb +72 -0
  189. data/lib/rubydb/cli/commands/backup.rb +80 -81
  190. data/lib/rubydb/cli/commands/branch.rb +72 -72
  191. data/lib/rubydb/cli/commands/checkout.rb +54 -54
  192. data/lib/rubydb/cli/commands/create.rb +58 -58
  193. data/lib/rubydb/cli/commands/diff.rb +76 -77
  194. data/lib/rubydb/cli/commands/doctor.rb +77 -74
  195. data/lib/rubydb/cli/commands/drop.rb +57 -57
  196. data/lib/rubydb/cli/commands/init.rb +101 -102
  197. data/lib/rubydb/cli/commands/inspect.rb +95 -95
  198. data/lib/rubydb/cli/commands/merge.rb +63 -63
  199. data/lib/rubydb/cli/commands/migrate.rb +62 -62
  200. data/lib/rubydb/cli/commands/restart.rb +42 -39
  201. data/lib/rubydb/cli/commands/restore.rb +121 -121
  202. data/lib/rubydb/cli/commands/shell.rb +365 -365
  203. data/lib/rubydb/cli/commands/snapshot.rb +79 -79
  204. data/lib/rubydb/cli/commands/start.rb +88 -82
  205. data/lib/rubydb/cli/commands/status.rb +96 -92
  206. data/lib/rubydb/cli/commands/stop.rb +47 -47
  207. data/lib/rubydb/cli/commands/vacuum.rb +58 -58
  208. data/lib/rubydb/cli/formatter.rb +221 -221
  209. data/lib/rubydb/cli/output.rb +168 -168
  210. data/lib/rubydb/client/client.rb +309 -304
  211. data/lib/rubydb/client/connection.rb +429 -415
  212. data/lib/rubydb/client/connection_pool.rb +168 -168
  213. data/lib/rubydb/client/connection_url.rb +96 -96
  214. data/lib/rubydb/client/prepared_statement.rb +60 -60
  215. data/lib/rubydb/client/result.rb +127 -123
  216. data/lib/rubydb/client/statement.rb +52 -52
  217. data/lib/rubydb/client/transaction.rb +130 -130
  218. data/lib/rubydb/concurrency/concurrency.rb +19 -19
  219. data/lib/rubydb/concurrency/deadlock_detector.rb +148 -150
  220. data/lib/rubydb/concurrency/latch.rb +101 -101
  221. data/lib/rubydb/concurrency/lock_graph.rb +163 -165
  222. data/lib/rubydb/concurrency/mutex.rb +181 -183
  223. data/lib/rubydb/concurrency/rw_lock.rb +180 -180
  224. data/lib/rubydb/concurrency/scheduler.rb +248 -250
  225. data/lib/rubydb/concurrency/worker_pool.rb +145 -143
  226. data/lib/rubydb/configuration/config.rb +170 -170
  227. data/lib/rubydb/configuration/defaults.rb +191 -179
  228. data/lib/rubydb/configuration/environment.rb +152 -152
  229. data/lib/rubydb/configuration/parser.rb +185 -185
  230. data/lib/rubydb/configuration/validation.rb +228 -221
  231. data/lib/rubydb/constants.rb +74 -74
  232. data/lib/rubydb/constraints/check.rb +181 -181
  233. data/lib/rubydb/constraints/constraint.rb +101 -101
  234. data/lib/rubydb/constraints/foreign_key.rb +130 -130
  235. data/lib/rubydb/constraints/not_null.rb +64 -64
  236. data/lib/rubydb/constraints/primary_key.rb +99 -99
  237. data/lib/rubydb/constraints/unique.rb +106 -108
  238. data/lib/rubydb/constraints/validator.rb +349 -350
  239. data/lib/rubydb/errors/authentication_error.rb +10 -10
  240. data/lib/rubydb/errors/authorization_error.rb +23 -23
  241. data/lib/rubydb/errors/client_error.rb +10 -10
  242. data/lib/rubydb/errors/configuration_error.rb +10 -10
  243. data/lib/rubydb/errors/connection_error.rb +10 -10
  244. data/lib/rubydb/errors/constraint_error.rb +23 -23
  245. data/lib/rubydb/errors/corruption_error.rb +10 -10
  246. data/lib/rubydb/errors/database_error.rb +10 -10
  247. data/lib/rubydb/errors/error.rb +20 -20
  248. data/lib/rubydb/errors/execution_error.rb +10 -10
  249. data/lib/rubydb/errors/parser_error.rb +10 -10
  250. data/lib/rubydb/errors/recovery_error.rb +10 -10
  251. data/lib/rubydb/errors/replication_error.rb +10 -10
  252. data/lib/rubydb/errors/server_error.rb +6 -6
  253. data/lib/rubydb/errors/storage_error.rb +10 -10
  254. data/lib/rubydb/errors/transaction_error.rb +10 -10
  255. data/lib/rubydb/execution/accelerator_dispatch.rb +30 -0
  256. data/lib/rubydb/execution/aggregate_executor.rb +134 -138
  257. data/lib/rubydb/execution/cost_model.rb +72 -0
  258. data/lib/rubydb/execution/delete_executor.rb +110 -112
  259. data/lib/rubydb/execution/distinct_executor.rb +131 -135
  260. data/lib/rubydb/execution/executor.rb +1544 -1188
  261. data/lib/rubydb/execution/expression.rb +191 -193
  262. data/lib/rubydb/execution/index_scan.rb +142 -142
  263. data/lib/rubydb/execution/insert_executor.rb +215 -217
  264. data/lib/rubydb/execution/join_executor.rb +243 -249
  265. data/lib/rubydb/execution/limit_executor.rb +83 -85
  266. data/lib/rubydb/execution/operator_selection.rb +57 -0
  267. data/lib/rubydb/execution/optimizer.rb +227 -215
  268. data/lib/rubydb/execution/physical_plan.rb +47 -0
  269. data/lib/rubydb/execution/plan.rb +355 -353
  270. data/lib/rubydb/execution/planner.rb +508 -536
  271. data/lib/rubydb/execution/predicate.rb +235 -235
  272. data/lib/rubydb/execution/scan.rb +49 -49
  273. data/lib/rubydb/execution/sequential_scan.rb +63 -63
  274. data/lib/rubydb/execution/sort_executor.rb +194 -185
  275. data/lib/rubydb/execution/update_executor.rb +160 -162
  276. data/lib/rubydb/functions/aggregate.rb +70 -70
  277. data/lib/rubydb/functions/date_functions.rb +274 -278
  278. data/lib/rubydb/functions/function.rb +85 -85
  279. data/lib/rubydb/functions/json_functions.rb +231 -215
  280. data/lib/rubydb/functions/numeric_functions.rb +346 -346
  281. data/lib/rubydb/functions/scalar.rb +52 -52
  282. data/lib/rubydb/functions/string_functions.rb +383 -383
  283. data/lib/rubydb/functions/system_functions.rb +258 -246
  284. data/lib/rubydb/history/as_of.rb +238 -238
  285. data/lib/rubydb/history/change.rb +105 -105
  286. data/lib/rubydb/history/history.rb +131 -131
  287. data/lib/rubydb/history/history_manager.rb +228 -229
  288. data/lib/rubydb/history/temporal_query.rb +202 -202
  289. data/lib/rubydb/history/timeline.rb +144 -144
  290. data/lib/rubydb/indexes/btree.rb +215 -186
  291. data/lib/rubydb/indexes/btree_cursor.rb +258 -258
  292. data/lib/rubydb/indexes/btree_node.rb +384 -385
  293. data/lib/rubydb/indexes/hash_index.rb +150 -150
  294. data/lib/rubydb/indexes/index.rb +71 -71
  295. data/lib/rubydb/indexes/index_manager.rb +408 -406
  296. data/lib/rubydb/indexes/index_scan.rb +466 -470
  297. data/lib/rubydb/migrations/migration.rb +253 -254
  298. data/lib/rubydb/migrations/migration_lock.rb +146 -146
  299. data/lib/rubydb/migrations/migration_manager.rb +187 -176
  300. data/lib/rubydb/migrations/migration_version.rb +71 -71
  301. data/lib/rubydb/migrations/schema_diff.rb +211 -211
  302. data/lib/rubydb/migrations/schema_version.rb +64 -64
  303. data/lib/rubydb/monitoring/events.rb +155 -160
  304. data/lib/rubydb/monitoring/health.rb +216 -222
  305. data/lib/rubydb/monitoring/logger.rb +188 -193
  306. data/lib/rubydb/monitoring/metrics.rb +363 -359
  307. data/lib/rubydb/monitoring/performance.rb +176 -176
  308. data/lib/rubydb/monitoring/statistics.rb +168 -170
  309. data/lib/rubydb/mvcc/garbage_collector.rb +199 -199
  310. data/lib/rubydb/mvcc/mvcc.rb +16 -16
  311. data/lib/rubydb/mvcc/snapshot.rb +146 -147
  312. data/lib/rubydb/mvcc/vacuum.rb +180 -180
  313. data/lib/rubydb/mvcc/version.rb +106 -106
  314. data/lib/rubydb/mvcc/version_store.rb +396 -398
  315. data/lib/rubydb/mvcc/visibility.rb +107 -109
  316. data/lib/rubydb/protocol/capabilities.rb +125 -125
  317. data/lib/rubydb/protocol/decoder.rb +142 -145
  318. data/lib/rubydb/protocol/encoder.rb +131 -136
  319. data/lib/rubydb/protocol/handshake.rb +306 -305
  320. data/lib/rubydb/protocol/message.rb +121 -121
  321. data/lib/rubydb/protocol/parameter_binder.rb +101 -0
  322. data/lib/rubydb/protocol/protocol.rb +276 -277
  323. data/lib/rubydb/protocol/version.rb +54 -54
  324. data/lib/rubydb/rails/adapter.rb +245 -239
  325. data/lib/rubydb/rails/connection.rb +312 -314
  326. data/lib/rubydb/rails/database_statements.rb +122 -122
  327. data/lib/rubydb/rails/migration.rb +131 -131
  328. data/lib/rubydb/rails/quoting.rb +109 -109
  329. data/lib/rubydb/rails/result.rb +117 -117
  330. data/lib/rubydb/rails/schema_statements.rb +339 -339
  331. data/lib/rubydb/rails/transaction.rb +105 -105
  332. data/lib/rubydb/rails/type.rb +126 -126
  333. data/lib/rubydb/recovery/checkpoint.rb +261 -257
  334. data/lib/rubydb/recovery/consistency.rb +457 -467
  335. data/lib/rubydb/recovery/corruption_detector.rb +5 -5
  336. data/lib/rubydb/recovery/crash_recovery.rb +381 -387
  337. data/lib/rubydb/recovery/recovery_manager.rb +204 -206
  338. data/lib/rubydb/recovery/redo.rb +235 -237
  339. data/lib/rubydb/recovery/undo.rb +204 -206
  340. data/lib/rubydb/replication/failover.rb +5 -5
  341. data/lib/rubydb/replication/fencing.rb +63 -63
  342. data/lib/rubydb/replication/primary.rb +461 -450
  343. data/lib/rubydb/replication/replica.rb +382 -384
  344. data/lib/rubydb/replication/replication_log.rb +194 -200
  345. data/lib/rubydb/replication/replication_manager.rb +307 -308
  346. data/lib/rubydb/replication/replication_slot.rb +293 -295
  347. data/lib/rubydb/replication/replication_stream.rb +198 -201
  348. data/lib/rubydb/rubydb.rb +570 -560
  349. data/lib/rubydb/security/access_control.rb +252 -254
  350. data/lib/rubydb/security/audit_log.rb +209 -213
  351. data/lib/rubydb/security/authentication.rb +302 -302
  352. data/lib/rubydb/security/authorization.rb +282 -282
  353. data/lib/rubydb/security/credentials.rb +192 -196
  354. data/lib/rubydb/security/password.rb +205 -215
  355. data/lib/rubydb/security/permissions.rb +74 -74
  356. data/lib/rubydb/security/role.rb +99 -101
  357. data/lib/rubydb/security/user.rb +86 -86
  358. data/lib/rubydb/server/connection.rb +383 -366
  359. data/lib/rubydb/server/connection_pool.rb +193 -193
  360. data/lib/rubydb/server/lifecycle.rb +227 -228
  361. data/lib/rubydb/server/listener.rb +139 -136
  362. data/lib/rubydb/server/request_handler.rb +277 -276
  363. data/lib/rubydb/server/server.rb +363 -364
  364. data/lib/rubydb/server/session.rb +416 -369
  365. data/lib/rubydb/server/worker.rb +206 -210
  366. data/lib/rubydb/server/worker_pool.rb +168 -168
  367. data/lib/rubydb/sql/ast/alter_table.rb +169 -169
  368. data/lib/rubydb/sql/ast/begin_transaction.rb +47 -47
  369. data/lib/rubydb/sql/ast/commit.rb +37 -37
  370. data/lib/rubydb/sql/ast/constraint.rb +92 -83
  371. data/lib/rubydb/sql/ast/create_database.rb +41 -41
  372. data/lib/rubydb/sql/ast/create_index.rb +61 -61
  373. data/lib/rubydb/sql/ast/create_schema.rb +52 -52
  374. data/lib/rubydb/sql/ast/create_table.rb +187 -187
  375. data/lib/rubydb/sql/ast/delete.rb +54 -54
  376. data/lib/rubydb/sql/ast/drop_database.rb +41 -41
  377. data/lib/rubydb/sql/ast/drop_index.rb +41 -41
  378. data/lib/rubydb/sql/ast/drop_schema.rb +49 -49
  379. data/lib/rubydb/sql/ast/drop_table.rb +49 -49
  380. data/lib/rubydb/sql/ast/explain.rb +64 -64
  381. data/lib/rubydb/sql/ast/expression.rb +617 -604
  382. data/lib/rubydb/sql/ast/insert.rb +66 -66
  383. data/lib/rubydb/sql/ast/node.rb +42 -42
  384. data/lib/rubydb/sql/ast/rollback.rb +63 -63
  385. data/lib/rubydb/sql/ast/savepoint.rb +59 -59
  386. data/lib/rubydb/sql/ast/select.rb +88 -88
  387. data/lib/rubydb/sql/ast/set_operation.rb +22 -20
  388. data/lib/rubydb/sql/ast/trigger.rb +35 -29
  389. data/lib/rubydb/sql/ast/update.rb +88 -88
  390. data/lib/rubydb/sql/ast/vacuum.rb +19 -19
  391. data/lib/rubydb/sql/ast/view.rb +38 -32
  392. data/lib/rubydb/sql/ast/with.rb +32 -32
  393. data/lib/rubydb/sql/grammar.rb +86 -86
  394. data/lib/rubydb/sql/keywords.rb +156 -156
  395. data/lib/rubydb/sql/lexer.rb +209 -214
  396. data/lib/rubydb/sql/operators.rb +100 -100
  397. data/lib/rubydb/sql/parser.rb +1167 -1170
  398. data/lib/rubydb/sql/planner/analyzer.rb +283 -302
  399. data/lib/rubydb/sql/planner/binder.rb +537 -550
  400. data/lib/rubydb/sql/planner/type_checker.rb +427 -431
  401. data/lib/rubydb/sql/token.rb +210 -210
  402. data/lib/rubydb/storage/buffer_frame.rb +44 -44
  403. data/lib/rubydb/storage/buffer_pool.rb +155 -155
  404. data/lib/rubydb/storage/database_lock.rb +74 -74
  405. data/lib/rubydb/storage/deserializer.rb +332 -342
  406. data/lib/rubydb/storage/engine.rb +2409 -2330
  407. data/lib/rubydb/storage/file_manager.rb +191 -187
  408. data/lib/rubydb/storage/free_space_map.rb +79 -81
  409. data/lib/rubydb/storage/page.rb +92 -94
  410. data/lib/rubydb/storage/page_allocator.rb +852 -855
  411. data/lib/rubydb/storage/page_header.rb +63 -67
  412. data/lib/rubydb/storage/page_manager.rb +127 -131
  413. data/lib/rubydb/storage/record.rb +58 -58
  414. data/lib/rubydb/storage/row.rb +78 -78
  415. data/lib/rubydb/storage/serializer.rb +51 -51
  416. data/lib/rubydb/storage/snapshot_reader.rb +167 -0
  417. data/lib/rubydb/storage/storage_layout.rb +151 -151
  418. data/lib/rubydb/storage/storage_manager.rb +114 -114
  419. data/lib/rubydb/storage/tuple.rb +458 -461
  420. data/lib/rubydb/storage/visibility_map.rb +964 -973
  421. data/lib/rubydb/transactions/commit_manager.rb +219 -220
  422. data/lib/rubydb/transactions/isolation.rb +98 -98
  423. data/lib/rubydb/transactions/lock.rb +76 -76
  424. data/lib/rubydb/transactions/lock_manager.rb +359 -362
  425. data/lib/rubydb/transactions/savepoint.rb +142 -143
  426. data/lib/rubydb/transactions/transaction.rb +214 -215
  427. data/lib/rubydb/transactions/transaction_id.rb +84 -84
  428. data/lib/rubydb/transactions/transaction_log.rb +256 -257
  429. data/lib/rubydb/transactions/transaction_manager.rb +434 -435
  430. data/lib/rubydb/types/bigint.rb +36 -36
  431. data/lib/rubydb/types/blob.rb +37 -37
  432. data/lib/rubydb/types/boolean.rb +34 -34
  433. data/lib/rubydb/types/date.rb +39 -39
  434. data/lib/rubydb/types/decimal.rb +48 -48
  435. data/lib/rubydb/types/float.rb +34 -34
  436. data/lib/rubydb/types/integer.rb +36 -36
  437. data/lib/rubydb/types/json.rb +41 -41
  438. data/lib/rubydb/types/null.rb +34 -34
  439. data/lib/rubydb/types/smallint.rb +36 -36
  440. data/lib/rubydb/types/text.rb +37 -37
  441. data/lib/rubydb/types/time.rb +46 -46
  442. data/lib/rubydb/types/timestamp.rb +39 -39
  443. data/lib/rubydb/types/type.rb +119 -119
  444. data/lib/rubydb/types/uuid.rb +47 -47
  445. data/lib/rubydb/types/varchar.rb +37 -37
  446. data/lib/rubydb/version.rb +32 -32
  447. data/lib/rubydb/wal/archive.rb +207 -193
  448. data/lib/rubydb/wal/checkpoint.rb +181 -183
  449. data/lib/rubydb/wal/lsn.rb +94 -94
  450. data/lib/rubydb/wal/reader.rb +259 -260
  451. data/lib/rubydb/wal/record.rb +105 -105
  452. data/lib/rubydb/wal/segment.rb +193 -193
  453. data/lib/rubydb/wal/wal.rb +481 -452
  454. data/lib/rubydb/wal/writer.rb +236 -236
  455. data/lib/rubydb.rb +7 -7
  456. data/packaging/docker/docker-compose.failover.yml +43 -43
  457. data/packaging/homebrew/rubydb.rb +19 -19
  458. data/rubydb.gemspec +70 -57
  459. data/scripts/benchmark +7 -7
  460. data/scripts/build_accelerator +49 -0
  461. data/scripts/durability_drill +37 -37
  462. data/scripts/fuzz +63 -63
  463. data/scripts/release +77 -42
  464. data/scripts/release_check +43 -43
  465. data/scripts/replication_failover_drill +268 -250
  466. data/scripts/replication_network_failover_drill +287 -255
  467. data/scripts/restore_drill +45 -45
  468. data/scripts/security +45 -0
  469. metadata +102 -1
@@ -1,470 +1,466 @@
1
- # frozen_string_literal: true
2
-
3
- require "set"
4
-
5
- module RubyDB
6
- module Indexes
7
- # IndexScan - Executes index scans for queries with full production features
8
- class IndexScan
9
- attr_reader :index, :scan_type, :conditions, :stats
10
-
11
- SCAN_TYPE_EQUAL = :equal
12
- SCAN_TYPE_RANGE = :range
13
- SCAN_TYPE_PREFIX = :prefix
14
- SCAN_TYPE_FULL = :full
15
- SCAN_TYPE_MULTI_KEY = :multi_key
16
-
17
- def initialize(index, scan_type = SCAN_TYPE_FULL, conditions = {})
18
- @index = index
19
- @scan_type = scan_type
20
- @conditions = conditions
21
- @cursor = nil
22
- @results = nil
23
- @position = 0
24
- @lock = Mutex.new
25
- @limit = conditions[:limit]
26
- @offset = conditions[:offset] || 0
27
- @stats = {
28
- rows_scanned: 0,
29
- keys_checked: 0,
30
- matches_found: 0,
31
- scan_time_ms: 0,
32
- index_pages_accessed: 0
33
- }
34
- @executed = false
35
- @filter = conditions[:filter]
36
- @order = conditions[:order] || :asc
37
- @distinct = conditions[:distinct] || false
38
- end
39
-
40
- def execute
41
- @lock.synchronize do
42
- start_time = Time.now
43
- @results = []
44
-
45
- case @scan_type
46
- when SCAN_TYPE_EQUAL
47
- execute_equal_scan
48
- when SCAN_TYPE_RANGE
49
- execute_range_scan
50
- when SCAN_TYPE_PREFIX
51
- execute_prefix_scan
52
- when SCAN_TYPE_MULTI_KEY
53
- execute_multi_key_scan
54
- else
55
- execute_full_scan
56
- end
57
-
58
- # Apply limit and offset
59
- if @limit
60
- @results = @results[@offset, @limit]
61
- elsif @offset > 0
62
- @results = @results[@offset..-1] || []
63
- end
64
-
65
- # Apply distinct if needed
66
- if @distinct
67
- @results = distinct_results(@results)
68
- end
69
-
70
- @executed = true
71
- @stats[:scan_time_ms] = ((Time.now - start_time) * 1000).round(2)
72
- @stats[:matches_found] = @results.size
73
- @results
74
- end
75
- end
76
-
77
- def next
78
- @lock.synchronize do
79
- return nil if @results.nil?
80
- return nil if @position >= @results.size
81
-
82
- result = @results[@position]
83
- @position += 1
84
- result
85
- end
86
- end
87
-
88
- def next_batch(batch_size = 100)
89
- @lock.synchronize do
90
- return [] if @results.nil? || @position >= @results.size
91
-
92
- batch = @results[@position, batch_size] || []
93
- @position += batch.size
94
- batch
95
- end
96
- end
97
-
98
- def reset
99
- @lock.synchronize do
100
- @position = 0
101
- @results = nil
102
- @executed = false
103
- @cursor = nil if @cursor
104
- end
105
- end
106
-
107
- def row_ids
108
- @lock.synchronize do
109
- @results ? @results.map { |r| r[:row_id] } : []
110
- end
111
- end
112
-
113
- def keys
114
- @lock.synchronize do
115
- @results ? @results.map { |r| r[:key] } : []
116
- end
117
- end
118
-
119
- def count
120
- @results ? @results.size : 0
121
- end
122
-
123
- def estimate_cost
124
- case @scan_type
125
- when SCAN_TYPE_EQUAL
126
- 1
127
- when SCAN_TYPE_PREFIX
128
- @index.entries_count / 10
129
- when SCAN_TYPE_RANGE
130
- @index.entries_count / 2
131
- when SCAN_TYPE_MULTI_KEY
132
- @index.entries_count * 0.75
133
- else
134
- @index.entries_count
135
- end
136
- end
137
-
138
- def estimate_rows
139
- case @scan_type
140
- when SCAN_TYPE_EQUAL
141
- 1
142
- when SCAN_TYPE_PREFIX
143
- @index.entries_count / 20
144
- when SCAN_TYPE_RANGE
145
- @index.entries_count / 3
146
- else
147
- @index.entries_count
148
- end
149
- end
150
-
151
- def index_used?
152
- @executed && @results && @results.any?
153
- end
154
-
155
- def scan_stats
156
- @stats.merge({
157
- scan_type: @scan_type,
158
- index_type: @index.type,
159
- index_name: @index.name,
160
- table_name: @index.table_name,
161
- columns: @index.columns,
162
- entries_count: @index.entries_count,
163
- limit: @limit,
164
- offset: @offset
165
- })
166
- end
167
-
168
- private
169
-
170
- def execute_equal_scan
171
- key = @conditions[:key]
172
- return [] unless key
173
-
174
- @stats[:keys_checked] += 1
175
-
176
- if @index.type == :btree || @index.type == :hash
177
- row_ids = @index.search(key)
178
- @stats[:rows_scanned] = row_ids.size
179
- @stats[:index_pages_accessed] = 1
180
-
181
- @results = row_ids.map do |row_id|
182
- { row_id: row_id, key: key }
183
- end
184
- else
185
- @results = []
186
- end
187
-
188
- # Apply filter if present
189
- apply_filter if @filter
190
-
191
- @results
192
- end
193
-
194
- def execute_range_scan
195
- start_key = @conditions[:start_key]
196
- end_key = @conditions[:end_key]
197
- inclusive_start = @conditions[:inclusive_start] != false
198
- inclusive_end = @conditions[:inclusive_end] != false
199
-
200
- if @index.type == :btree
201
- results = @index.range_search(start_key, end_key)
202
- @stats[:rows_scanned] = results.size
203
- @stats[:index_pages_accessed] = (results.size / 10.0).ceil + 1
204
-
205
- @results = results.map do |r|
206
- { row_id: r[:value], key: r[:key] }
207
- end
208
-
209
- # Filter by inclusivity
210
- if !inclusive_start && start_key
211
- @results.reject! { |r| r[:key] == start_key }
212
- end
213
- if !inclusive_end && end_key
214
- @results.reject! { |r| r[:key] == end_key }
215
- end
216
- else
217
- # For non-BTree indexes, fall back to full scan with filtering
218
- execute_full_scan_with_range_filter(start_key, end_key, inclusive_start, inclusive_end)
219
- end
220
-
221
- apply_filter if @filter
222
- @results
223
- end
224
-
225
- def execute_prefix_scan
226
- prefix = @conditions[:prefix]
227
- return [] unless prefix
228
-
229
- if @index.type == :btree
230
- # For prefix scan, find keys starting with prefix
231
- start_key = prefix
232
- end_key = prefix + "\xFF\xFF\xFF" # Max possible suffix
233
-
234
- results = @index.range_search(start_key, end_key)
235
- @stats[:rows_scanned] = results.size
236
- @stats[:index_pages_accessed] = (results.size / 20.0).ceil + 1
237
-
238
- @results = results.map do |r|
239
- { row_id: r[:value], key: r[:key] }
240
- end
241
- else
242
- # For hash indexes, full scan with prefix filter
243
- execute_full_scan_with_prefix_filter(prefix)
244
- end
245
-
246
- apply_filter if @filter
247
- @results
248
- end
249
-
250
- def execute_multi_key_scan
251
- keys = @conditions[:keys]
252
- return [] unless keys.is_a?(Array) && keys.any?
253
-
254
- @stats[:keys_checked] = keys.size
255
-
256
- all_row_ids = []
257
- keys.each do |key|
258
- if @index.type == :btree || @index.type == :hash
259
- row_ids = @index.search(key)
260
- all_row_ids.concat(row_ids.map { |rid| { row_id: rid, key: key } })
261
- end
262
- end
263
-
264
- # Remove duplicates if needed
265
- if @conditions[:distinct_keys] != false
266
- seen = Set.new
267
- all_row_ids = all_row_ids.select do |entry|
268
- key = entry[:key]
269
- if seen.include?(key)
270
- false
271
- else
272
- seen.add(key)
273
- true
274
- end
275
- end
276
- end
277
-
278
- @stats[:rows_scanned] = all_row_ids.size
279
- @stats[:index_pages_accessed] = all_row_ids.size
280
- @results = all_row_ids
281
-
282
- apply_filter if @filter
283
- @results
284
- end
285
-
286
- def execute_full_scan
287
- @results = []
288
-
289
- if @index.type == :btree
290
- # Use cursor for B-Tree
291
- @cursor = BTreeCursor.new(@index)
292
- @cursor.first
293
-
294
- page_count = 0
295
- while (entry = @cursor.next)
296
- @stats[:rows_scanned] += 1
297
- @stats[:keys_checked] += 1
298
- page_count += 1 if page_count % 50 == 0
299
-
300
- # Apply filter early if possible
301
- if @filter
302
- next unless apply_filter_to_entry(entry)
303
- end
304
-
305
- @results << { row_id: entry[:value], key: entry[:key] }
306
- break if @limit && @results.size >= @limit + @offset
307
- end
308
- @stats[:index_pages_accessed] = (page_count / 50.0).ceil + 1
309
- else
310
- # For hash indexes, scan all buckets
311
- @results = scan_hash_index_full
312
- @stats[:rows_scanned] = @results.size
313
- @stats[:index_pages_accessed] = 1
314
- end
315
-
316
- apply_filter if @filter && !@results.empty?
317
- @results
318
- end
319
-
320
- def execute_full_scan_with_range_filter(start_key, end_key, inclusive_start, inclusive_end)
321
- @results = []
322
-
323
- if @index.type == :btree
324
- @cursor = BTreeCursor.new(@index)
325
- @cursor.seek(start_key) if start_key
326
- @cursor.first unless start_key
327
-
328
- while (entry = @cursor.next)
329
- break if end_key && entry[:key] > end_key
330
-
331
- @stats[:rows_scanned] += 1
332
- @stats[:keys_checked] += 1
333
-
334
- # Check inclusivity
335
- if start_key && entry[:key] == start_key && !inclusive_start
336
- next
337
- end
338
- if end_key && entry[:key] == end_key && !inclusive_end
339
- next
340
- end
341
-
342
- if @filter
343
- next unless apply_filter_to_entry(entry)
344
- end
345
-
346
- @results << { row_id: entry[:value], key: entry[:key] }
347
- break if @limit && @results.size >= @limit + @offset
348
- end
349
- @stats[:index_pages_accessed] = (results.size / 20.0).ceil + 1
350
- else
351
- # Hash index - scan all and filter
352
- scan_hash_index_full.each do |entry|
353
- next if start_key && entry[:key] < start_key
354
- next if end_key && entry[:key] > end_key
355
-
356
- if start_key && entry[:key] == start_key && !inclusive_start
357
- next
358
- end
359
- if end_key && entry[:key] == end_key && !inclusive_end
360
- next
361
- end
362
-
363
- @results << entry
364
- end
365
- end
366
-
367
- @results
368
- end
369
-
370
- def execute_full_scan_with_prefix_filter(prefix)
371
- @results = []
372
-
373
- if @index.type == :btree
374
- @cursor = BTreeCursor.new(@index)
375
- @cursor.first
376
-
377
- while (entry = @cursor.next)
378
- key_str = entry[:key].to_s
379
- next unless key_str.start_with?(prefix.to_s)
380
-
381
- @stats[:rows_scanned] += 1
382
- @stats[:keys_checked] += 1
383
-
384
- if @filter
385
- next unless apply_filter_to_entry(entry)
386
- end
387
-
388
- @results << { row_id: entry[:value], key: entry[:key] }
389
- break if @limit && @results.size >= @limit + @offset
390
- end
391
- else
392
- scan_hash_index_full.each do |entry|
393
- key_str = entry[:key].to_s
394
- next unless key_str.start_with?(prefix.to_s)
395
- @results << entry
396
- end
397
- end
398
-
399
- @results
400
- end
401
-
402
- def scan_hash_index_full
403
- # Access the internal hash table of the hash index
404
- # This is a production implementation that iterates through all buckets
405
- results = []
406
- hash_table = @index.instance_variable_get(:@hash_table)
407
-
408
- if hash_table
409
- hash_table.each do |_bucket_key, bucket|
410
- bucket.each do |entry|
411
- @stats[:rows_scanned] += 1
412
- @stats[:keys_checked] += 1
413
-
414
- if @filter
415
- next unless apply_filter_to_entry(entry)
416
- end
417
-
418
- results << { row_id: entry[:row_id], key: entry[:key] }
419
- break if @limit && results.size >= @limit + @offset
420
- end
421
- break if @limit && results.size >= @limit + @offset
422
- end
423
- end
424
-
425
- results
426
- end
427
-
428
- def apply_filter
429
- return unless @filter && @results.any?
430
-
431
- @results.select! do |result|
432
- apply_filter_to_entry(result)
433
- end
434
- end
435
-
436
- def apply_filter_to_entry(entry)
437
- return true unless @filter
438
-
439
- key = entry[:key] || entry[:row_id]
440
- value = entry[:row_id] || entry[:value]
441
-
442
- @filter.call(key, value)
443
- end
444
-
445
- def distinct_results(results)
446
- seen = Set.new
447
- results.select do |result|
448
- key = result[:key]
449
- if seen.include?(key)
450
- false
451
- else
452
- seen.add(key)
453
- true
454
- end
455
- end
456
- end
457
-
458
- def find_internal_hash_table
459
- # Try to get hash table from index
460
- if @index.respond_to?(:hash_table)
461
- @index.hash_table
462
- elsif @index.instance_variable_defined?(:@hash_table)
463
- @index.instance_variable_get(:@hash_table)
464
- else
465
- {}
466
- end
467
- end
468
- end
469
- end
470
- end
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module Indexes
5
+ # IndexScan - Executes index scans for queries with full production features
6
+ class IndexScan
7
+ attr_reader :index, :scan_type, :conditions, :stats
8
+
9
+ SCAN_TYPE_EQUAL = :equal
10
+ SCAN_TYPE_RANGE = :range
11
+ SCAN_TYPE_PREFIX = :prefix
12
+ SCAN_TYPE_FULL = :full
13
+ SCAN_TYPE_MULTI_KEY = :multi_key
14
+
15
+ def initialize(index, scan_type = SCAN_TYPE_FULL, conditions = {})
16
+ @index = index
17
+ @scan_type = scan_type
18
+ @conditions = conditions
19
+ @cursor = nil
20
+ @results = nil
21
+ @position = 0
22
+ @lock = Mutex.new
23
+ @limit = conditions[:limit]
24
+ @offset = conditions[:offset] || 0
25
+ @stats = {
26
+ rows_scanned: 0,
27
+ keys_checked: 0,
28
+ matches_found: 0,
29
+ scan_time_ms: 0,
30
+ index_pages_accessed: 0
31
+ }
32
+ @executed = false
33
+ @filter = conditions[:filter]
34
+ @order = conditions[:order] || :asc
35
+ @distinct = conditions[:distinct] || false
36
+ end
37
+
38
+ def execute
39
+ @lock.synchronize do
40
+ start_time = Time.now
41
+ @results = []
42
+
43
+ case @scan_type
44
+ when SCAN_TYPE_EQUAL
45
+ execute_equal_scan
46
+ when SCAN_TYPE_RANGE
47
+ execute_range_scan
48
+ when SCAN_TYPE_PREFIX
49
+ execute_prefix_scan
50
+ when SCAN_TYPE_MULTI_KEY
51
+ execute_multi_key_scan
52
+ else
53
+ execute_full_scan
54
+ end
55
+
56
+ # Apply limit and offset
57
+ if @limit
58
+ @results = @results[@offset, @limit]
59
+ elsif @offset > 0
60
+ @results = @results[@offset..] || []
61
+ end
62
+
63
+ # Apply distinct if needed
64
+ if @distinct
65
+ @results = distinct_results(@results)
66
+ end
67
+
68
+ @executed = true
69
+ @stats[:scan_time_ms] = ((Time.now - start_time) * 1000).round(2)
70
+ @stats[:matches_found] = @results.size
71
+ @results
72
+ end
73
+ end
74
+
75
+ def next
76
+ @lock.synchronize do
77
+ return nil if @results.nil?
78
+ return nil if @position >= @results.size
79
+
80
+ result = @results[@position]
81
+ @position += 1
82
+ result
83
+ end
84
+ end
85
+
86
+ def next_batch(batch_size = 100)
87
+ @lock.synchronize do
88
+ return [] if @results.nil? || @position >= @results.size
89
+
90
+ batch = @results[@position, batch_size] || []
91
+ @position += batch.size
92
+ batch
93
+ end
94
+ end
95
+
96
+ def reset
97
+ @lock.synchronize do
98
+ @position = 0
99
+ @results = nil
100
+ @executed = false
101
+ @cursor = nil if @cursor
102
+ end
103
+ end
104
+
105
+ def row_ids
106
+ @lock.synchronize do
107
+ @results ? @results.map { |r| r[:row_id] } : []
108
+ end
109
+ end
110
+
111
+ def keys
112
+ @lock.synchronize do
113
+ @results ? @results.map { |r| r[:key] } : []
114
+ end
115
+ end
116
+
117
+ def count
118
+ @results ? @results.size : 0
119
+ end
120
+
121
+ def estimate_cost
122
+ case @scan_type
123
+ when SCAN_TYPE_EQUAL
124
+ 1
125
+ when SCAN_TYPE_PREFIX
126
+ @index.entries_count / 10
127
+ when SCAN_TYPE_RANGE
128
+ @index.entries_count / 2
129
+ when SCAN_TYPE_MULTI_KEY
130
+ @index.entries_count * 0.75
131
+ else
132
+ @index.entries_count
133
+ end
134
+ end
135
+
136
+ def estimate_rows
137
+ case @scan_type
138
+ when SCAN_TYPE_EQUAL
139
+ 1
140
+ when SCAN_TYPE_PREFIX
141
+ @index.entries_count / 20
142
+ when SCAN_TYPE_RANGE
143
+ @index.entries_count / 3
144
+ else
145
+ @index.entries_count
146
+ end
147
+ end
148
+
149
+ def index_used?
150
+ @executed && @results && @results.any?
151
+ end
152
+
153
+ def scan_stats
154
+ @stats.merge({
155
+ scan_type: @scan_type,
156
+ index_type: @index.type,
157
+ index_name: @index.name,
158
+ table_name: @index.table_name,
159
+ columns: @index.columns,
160
+ entries_count: @index.entries_count,
161
+ limit: @limit,
162
+ offset: @offset
163
+ })
164
+ end
165
+
166
+ private
167
+
168
+ def execute_equal_scan
169
+ key = @conditions[:key]
170
+ return [] unless key
171
+
172
+ @stats[:keys_checked] += 1
173
+
174
+ if @index.type == :btree || @index.type == :hash
175
+ row_ids = @index.search(key)
176
+ @stats[:rows_scanned] = row_ids.size
177
+ @stats[:index_pages_accessed] = 1
178
+
179
+ @results = row_ids.map do |row_id|
180
+ {row_id: row_id, key: key}
181
+ end
182
+ else
183
+ @results = []
184
+ end
185
+
186
+ # Apply filter if present
187
+ apply_filter if @filter
188
+
189
+ @results
190
+ end
191
+
192
+ def execute_range_scan
193
+ start_key = @conditions[:start_key]
194
+ end_key = @conditions[:end_key]
195
+ inclusive_start = @conditions[:inclusive_start] != false
196
+ inclusive_end = @conditions[:inclusive_end] != false
197
+
198
+ if @index.type == :btree
199
+ results = @index.range_search(start_key, end_key)
200
+ @stats[:rows_scanned] = results.size
201
+ @stats[:index_pages_accessed] = (results.size / 10.0).ceil + 1
202
+
203
+ @results = results.map do |r|
204
+ {row_id: r[:value], key: r[:key]}
205
+ end
206
+
207
+ # Filter by inclusivity
208
+ if !inclusive_start && start_key
209
+ @results.reject! { |r| r[:key] == start_key }
210
+ end
211
+ if !inclusive_end && end_key
212
+ @results.reject! { |r| r[:key] == end_key }
213
+ end
214
+ else
215
+ # For non-BTree indexes, fall back to full scan with filtering
216
+ execute_full_scan_with_range_filter(start_key, end_key, inclusive_start, inclusive_end)
217
+ end
218
+
219
+ apply_filter if @filter
220
+ @results
221
+ end
222
+
223
+ def execute_prefix_scan
224
+ prefix = @conditions[:prefix]
225
+ return [] unless prefix
226
+
227
+ if @index.type == :btree
228
+ # For prefix scan, find keys starting with prefix
229
+ start_key = prefix
230
+ end_key = prefix + "\xFF\xFF\xFF" # Max possible suffix
231
+
232
+ results = @index.range_search(start_key, end_key)
233
+ @stats[:rows_scanned] = results.size
234
+ @stats[:index_pages_accessed] = (results.size / 20.0).ceil + 1
235
+
236
+ @results = results.map do |r|
237
+ {row_id: r[:value], key: r[:key]}
238
+ end
239
+ else
240
+ # For hash indexes, full scan with prefix filter
241
+ execute_full_scan_with_prefix_filter(prefix)
242
+ end
243
+
244
+ apply_filter if @filter
245
+ @results
246
+ end
247
+
248
+ def execute_multi_key_scan
249
+ keys = @conditions[:keys]
250
+ return [] unless keys.is_a?(Array) && keys.any?
251
+
252
+ @stats[:keys_checked] = keys.size
253
+
254
+ all_row_ids = []
255
+ keys.each do |key|
256
+ if @index.type == :btree || @index.type == :hash
257
+ row_ids = @index.search(key)
258
+ all_row_ids.concat(row_ids.map { |rid| {row_id: rid, key: key} })
259
+ end
260
+ end
261
+
262
+ # Remove duplicates if needed
263
+ if @conditions[:distinct_keys] != false
264
+ seen = Set.new
265
+ all_row_ids = all_row_ids.select do |entry|
266
+ key = entry[:key]
267
+ if seen.include?(key)
268
+ false
269
+ else
270
+ seen.add(key)
271
+ true
272
+ end
273
+ end
274
+ end
275
+
276
+ @stats[:rows_scanned] = all_row_ids.size
277
+ @stats[:index_pages_accessed] = all_row_ids.size
278
+ @results = all_row_ids
279
+
280
+ apply_filter if @filter
281
+ @results
282
+ end
283
+
284
+ def execute_full_scan
285
+ @results = []
286
+
287
+ if @index.type == :btree
288
+ # Use cursor for B-Tree
289
+ @cursor = BTreeCursor.new(@index)
290
+ @cursor.first
291
+
292
+ page_count = 0
293
+ while (entry = @cursor.next)
294
+ @stats[:rows_scanned] += 1
295
+ @stats[:keys_checked] += 1
296
+ page_count += 1 if page_count % 50 == 0
297
+
298
+ # Apply filter early if possible
299
+ if @filter
300
+ next unless apply_filter_to_entry(entry)
301
+ end
302
+
303
+ @results << {row_id: entry[:value], key: entry[:key]}
304
+ break if @limit && @results.size >= @limit + @offset
305
+ end
306
+ @stats[:index_pages_accessed] = (page_count / 50.0).ceil + 1
307
+ else
308
+ # For hash indexes, scan all buckets
309
+ @results = scan_hash_index_full
310
+ @stats[:rows_scanned] = @results.size
311
+ @stats[:index_pages_accessed] = 1
312
+ end
313
+
314
+ apply_filter if @filter && !@results.empty?
315
+ @results
316
+ end
317
+
318
+ def execute_full_scan_with_range_filter(start_key, end_key, inclusive_start, inclusive_end)
319
+ @results = []
320
+
321
+ if @index.type == :btree
322
+ @cursor = BTreeCursor.new(@index)
323
+ @cursor.seek(start_key) if start_key
324
+ @cursor.first unless start_key
325
+
326
+ while (entry = @cursor.next)
327
+ break if end_key && entry[:key] > end_key
328
+
329
+ @stats[:rows_scanned] += 1
330
+ @stats[:keys_checked] += 1
331
+
332
+ # Check inclusivity
333
+ if start_key && entry[:key] == start_key && !inclusive_start
334
+ next
335
+ end
336
+ if end_key && entry[:key] == end_key && !inclusive_end
337
+ next
338
+ end
339
+
340
+ if @filter
341
+ next unless apply_filter_to_entry(entry)
342
+ end
343
+
344
+ @results << {row_id: entry[:value], key: entry[:key]}
345
+ break if @limit && @results.size >= @limit + @offset
346
+ end
347
+ @stats[:index_pages_accessed] = (results.size / 20.0).ceil + 1
348
+ else
349
+ # Hash index - scan all and filter
350
+ scan_hash_index_full.each do |entry|
351
+ next if start_key && entry[:key] < start_key
352
+ next if end_key && entry[:key] > end_key
353
+
354
+ if start_key && entry[:key] == start_key && !inclusive_start
355
+ next
356
+ end
357
+ if end_key && entry[:key] == end_key && !inclusive_end
358
+ next
359
+ end
360
+
361
+ @results << entry
362
+ end
363
+ end
364
+
365
+ @results
366
+ end
367
+
368
+ def execute_full_scan_with_prefix_filter(prefix)
369
+ @results = []
370
+
371
+ if @index.type == :btree
372
+ @cursor = BTreeCursor.new(@index)
373
+ @cursor.first
374
+
375
+ while (entry = @cursor.next)
376
+ key_str = entry[:key].to_s
377
+ next unless key_str.start_with?(prefix.to_s)
378
+
379
+ @stats[:rows_scanned] += 1
380
+ @stats[:keys_checked] += 1
381
+
382
+ if @filter
383
+ next unless apply_filter_to_entry(entry)
384
+ end
385
+
386
+ @results << {row_id: entry[:value], key: entry[:key]}
387
+ break if @limit && @results.size >= @limit + @offset
388
+ end
389
+ else
390
+ scan_hash_index_full.each do |entry|
391
+ key_str = entry[:key].to_s
392
+ next unless key_str.start_with?(prefix.to_s)
393
+ @results << entry
394
+ end
395
+ end
396
+
397
+ @results
398
+ end
399
+
400
+ def scan_hash_index_full
401
+ # Access the internal hash table of the hash index
402
+ # This is a production implementation that iterates through all buckets
403
+ results = []
404
+ hash_table = @index.instance_variable_get(:@hash_table)
405
+
406
+ hash_table&.each do |_bucket_key, bucket|
407
+ bucket.each do |entry|
408
+ @stats[:rows_scanned] += 1
409
+ @stats[:keys_checked] += 1
410
+
411
+ if @filter
412
+ next unless apply_filter_to_entry(entry)
413
+ end
414
+
415
+ results << {row_id: entry[:row_id], key: entry[:key]}
416
+ break if @limit && results.size >= @limit + @offset
417
+ end
418
+ break if @limit && results.size >= @limit + @offset
419
+ end
420
+
421
+ results
422
+ end
423
+
424
+ def apply_filter
425
+ return unless @filter && @results.any?
426
+
427
+ @results.select! do |result|
428
+ apply_filter_to_entry(result)
429
+ end
430
+ end
431
+
432
+ def apply_filter_to_entry(entry)
433
+ return true unless @filter
434
+
435
+ key = entry[:key] || entry[:row_id]
436
+ value = entry[:row_id] || entry[:value]
437
+
438
+ @filter.call(key, value)
439
+ end
440
+
441
+ def distinct_results(results)
442
+ seen = Set.new
443
+ results.select do |result|
444
+ key = result[:key]
445
+ if seen.include?(key)
446
+ false
447
+ else
448
+ seen.add(key)
449
+ true
450
+ end
451
+ end
452
+ end
453
+
454
+ def find_internal_hash_table
455
+ # Try to get hash table from index
456
+ if @index.respond_to?(:hash_table)
457
+ @index.hash_table
458
+ elsif @index.instance_variable_defined?(:@hash_table)
459
+ @index.instance_variable_get(:@hash_table)
460
+ else
461
+ {}
462
+ end
463
+ end
464
+ end
465
+ end
466
+ end