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,467 +1,457 @@
1
- # frozen_string_literal: true
2
-
3
- require "digest"
4
-
5
- module RubyDB
6
- module Recovery
7
- # CorruptionDetector - Detects database corruption
8
- class CorruptionDetector
9
- attr_reader :stats
10
-
11
- def initialize(engine, config = {})
12
- @engine = engine
13
- @config = config
14
- @checksums = {}
15
- @checksum_cache = {}
16
- @stats = {
17
- detections: 0,
18
- false_positives: 0,
19
- total_checks: 0,
20
- corrupted_pages: 0,
21
- corrupted_records: 0,
22
- corrupted_indexes: 0,
23
- last_detection_time: nil,
24
- last_detection_result: nil
25
- }
26
- @lock = Mutex.new
27
- @page_checksums = {}
28
- end
29
-
30
- def detect_corruption(scan_all = false)
31
- @lock.synchronize do
32
- @stats[:total_checks] += 1
33
- results = {
34
- corrupted: false,
35
- issues: [],
36
- pages: [],
37
- records: [],
38
- indexes: [],
39
- recommendations: []
40
- }
41
-
42
- # Check page corruption
43
- page_results = check_pages(scan_all)
44
- results[:pages] = page_results[:corrupted]
45
- results[:issues].concat(page_results[:issues])
46
- results[:corrupted] = true if page_results[:corrupted].any?
47
-
48
- # Check record corruption
49
- record_results = check_records(scan_all)
50
- results[:records] = record_results[:corrupted]
51
- results[:issues].concat(record_results[:issues])
52
- results[:corrupted] = true if record_results[:corrupted].any?
53
-
54
- # Check index corruption
55
- index_results = check_indexes(scan_all)
56
- results[:indexes] = index_results[:corrupted]
57
- results[:issues].concat(index_results[:issues])
58
- results[:corrupted] = true if index_results[:corrupted].any?
59
-
60
- # Generate recommendations
61
- results[:recommendations] = generate_recommendations(results)
62
-
63
- # Update stats
64
- if results[:corrupted]
65
- @stats[:detections] += 1
66
- @stats[:corrupted_pages] = results[:pages].size
67
- @stats[:corrupted_records] = results[:records].size
68
- @stats[:corrupted_indexes] = results[:indexes].size
69
- @stats[:last_detection_time] = Time.now
70
- @stats[:last_detection_result] = results
71
- end
72
-
73
- results
74
- end
75
- end
76
-
77
- def check_page(page_number)
78
- @lock.synchronize do
79
- result = { corrupted: false, issues: [] }
80
-
81
- begin
82
- page = @engine.read_page(page_number)
83
- return result unless page
84
-
85
- # Check page header
86
- header = page.header
87
- if header.nil?
88
- result[:corrupted] = true
89
- result[:issues] << { type: "page_header", page: page_number, message: "Missing header" }
90
- return result
91
- end
92
-
93
- # Check page size
94
- if header.page_size != page.size
95
- result[:corrupted] = true
96
- result[:issues] << { type: "page_size", page: page_number, message: "Invalid page size" }
97
- end
98
-
99
- # Check page checksum
100
- if checksum_mismatch?(page)
101
- result[:corrupted] = true
102
- result[:issues] << { type: "checksum", page: page_number, message: "Checksum mismatch" }
103
- end
104
-
105
- # Check page data
106
- if page.data.nil? || page.data.bytesize != page.size
107
- result[:corrupted] = true
108
- result[:issues] << { type: "page_data", page: page_number, message: "Invalid page data" }
109
- end
110
-
111
- rescue => e
112
- result[:corrupted] = true
113
- result[:issues] << { type: "page_read", page: page_number, message: e.message }
114
- end
115
-
116
- result
117
- end
118
- end
119
-
120
- def check_record(table_name, row_id)
121
- @lock.synchronize do
122
- result = { corrupted: false, issues: [] }
123
-
124
- begin
125
- columns = @engine.table_columns(table_name)
126
- row = @engine.select_row(table_name, row_id, columns)
127
- return result unless row
128
-
129
- # Validate each column value
130
- columns.each do |col|
131
- value = row[col.name]
132
- next if value.nil? && col.nullable?
133
-
134
- # Check type validity
135
- unless valid_value_type?(value, col.type_class)
136
- result[:corrupted] = true
137
- result[:issues] << {
138
- type: "record_value",
139
- table: table_name,
140
- row: row_id,
141
- column: col.name,
142
- message: "Invalid value type"
143
- }
144
- end
145
- end
146
-
147
- rescue => e
148
- result[:corrupted] = true
149
- result[:issues] << {
150
- type: "record_read",
151
- table: table_name,
152
- row: row_id,
153
- message: e.message
154
- }
155
- end
156
-
157
- result
158
- end
159
- end
160
-
161
- def check_index(index_name)
162
- @lock.synchronize do
163
- result = { corrupted: false, issues: [] }
164
-
165
- begin
166
- if @engine.respond_to?(:index_manager)
167
- index = @engine.index_manager.get_index(index_name)
168
- return result unless index
169
-
170
- # Validate index
171
- if index.respond_to?(:validate)
172
- index.validate
173
- end
174
-
175
- # Check index entries
176
- entries = index.entries_count
177
- if entries < 0
178
- result[:corrupted] = true
179
- result[:issues] << {
180
- type: "index_entries",
181
- index: index_name,
182
- message: "Invalid entry count"
183
- }
184
- end
185
- end
186
-
187
- rescue => e
188
- result[:corrupted] = true
189
- result[:issues] << {
190
- type: "index_check",
191
- index: index_name,
192
- message: e.message
193
- }
194
- end
195
-
196
- result
197
- end
198
- end
199
-
200
- def repair_corruption(corruption_info)
201
- @lock.synchronize do
202
- results = { repaired: false, actions: [] }
203
-
204
- issues = Array(corruption_info[:issues])
205
- issues.each do |issue|
206
- case issue[:type]
207
- when "page_header"
208
- repaired = repair_page_header(issue[:page])
209
- results[:actions] << { type: "repair_page_header", page: issue[:page], success: repaired }
210
- when "checksum"
211
- repaired = repair_checksum(issue[:page])
212
- results[:actions] << { type: "repair_checksum", page: issue[:page], success: repaired }
213
- when "record_value"
214
- repaired = repair_record(issue[:table], issue[:row], issue[:column])
215
- results[:actions] << {
216
- type: "repair_record",
217
- table: issue[:table],
218
- row: issue[:row],
219
- column: issue[:column],
220
- success: repaired
221
- }
222
- when "index_entries"
223
- repaired = repair_index(issue[:index])
224
- results[:actions] << { type: "repair_index", index: issue[:index], success: repaired }
225
- else
226
- results[:actions] << {
227
- type: "unsupported_repair",
228
- issue: issue[:type],
229
- success: false
230
- }
231
- end
232
- end
233
-
234
- results[:repaired] = results[:actions].any? && results[:actions].all? { |a| a[:success] }
235
- results
236
- end
237
- end
238
-
239
- def stats
240
- @lock.synchronize do
241
- @stats.merge({
242
- checksum_cache_size: @checksum_cache.size,
243
- page_checksums: @page_checksums.size,
244
- last_detection: @stats[:last_detection_time]&.iso8601
245
- })
246
- end
247
- end
248
-
249
- private
250
-
251
- def check_pages(scan_all)
252
- result = { corrupted: [], issues: [] }
253
-
254
- begin
255
- page_manager = @engine.page_manager
256
- return result unless page_manager
257
-
258
- pages_to_check = scan_all ? (0...page_manager.total_pages) : [page_manager.current_page]
259
-
260
- pages_to_check.each do |page_number|
261
- page_result = check_page(page_number)
262
- if page_result[:corrupted]
263
- result[:corrupted] << page_number
264
- result[:issues].concat(page_result[:issues])
265
- end
266
- end
267
-
268
- rescue => e
269
- result[:issues] << { type: "page_scan", message: e.message }
270
- end
271
-
272
- result
273
- end
274
-
275
- def check_records(scan_all)
276
- result = { corrupted: [], issues: [] }
277
-
278
- begin
279
- tables = @engine.list_tables
280
- return result if tables.empty?
281
-
282
- tables.each do |table_name|
283
- columns = @engine.table_columns(table_name)
284
- rows = @engine.select_rows(table_name, columns)
285
-
286
- sample_size = scan_all ? rows.size : [rows.size, 100].min
287
- rows.first(sample_size).each do |row|
288
- row_id = row[:_row_id] || row["_row_id"]
289
- record_result = check_record(table_name, row_id)
290
- if record_result[:corrupted]
291
- result[:corrupted] << { table: table_name, row: row_id }
292
- result[:issues].concat(record_result[:issues])
293
- end
294
- end
295
- end
296
-
297
- rescue => e
298
- result[:issues] << { type: "record_scan", message: e.message }
299
- end
300
-
301
- result
302
- end
303
-
304
- def check_indexes(scan_all)
305
- result = { corrupted: [], issues: [] }
306
-
307
- begin
308
- if @engine.respond_to?(:index_manager)
309
- index_manager = @engine.index_manager
310
- indexes = index_manager.indexes
311
-
312
- indexes.each do |name, _|
313
- index_result = check_index(name)
314
- if index_result[:corrupted]
315
- result[:corrupted] << name
316
- result[:issues].concat(index_result[:issues])
317
- end
318
- end
319
- end
320
-
321
- rescue => e
322
- result[:issues] << { type: "index_scan", message: e.message }
323
- end
324
-
325
- result
326
- end
327
-
328
- def checksum_mismatch?(page)
329
- # Calculate page checksum
330
- current_checksum = calculate_checksum(page.data)
331
- stored_checksum = page.header.checksum rescue 0
332
-
333
- current_checksum != stored_checksum
334
- end
335
-
336
- def calculate_checksum(data)
337
- Digest::SHA256.hexdigest(data)[0...16]
338
- end
339
-
340
- def valid_value_type?(value, type)
341
- return true if value.nil?
342
-
343
- case type
344
- when :integer, :bigint, :smallint
345
- value.is_a?(Integer)
346
- when :float, :decimal
347
- value.is_a?(Numeric)
348
- when :boolean
349
- [true, false].include?(value)
350
- when :text, :varchar, :char
351
- value.is_a?(String)
352
- when :date, :time, :timestamp
353
- value.is_a?(Time) || value.is_a?(Date) || value.is_a?(DateTime)
354
- when :blob
355
- value.is_a?(String)
356
- when :json
357
- value.is_a?(Hash) || value.is_a?(Array)
358
- else
359
- true
360
- end
361
- end
362
-
363
- def repair_page_header(page_number)
364
- begin
365
- page = @engine.read_page(page_number)
366
- return false unless page
367
-
368
- # Rebuild header
369
- page.header.page_number = page_number
370
- page.header.page_size = page.size
371
- page.header.header_size = 64
372
- page.header.data_end = 64
373
- page.header.flags = 0
374
- page.header.checksum = calculate_checksum(page.data)
375
- page.header.version = 1
376
- page.header.page_type = 0
377
-
378
- page.write_header
379
- @engine.write_page(page)
380
- true
381
- rescue
382
- false
383
- end
384
- end
385
-
386
- def repair_checksum(page_number)
387
- begin
388
- page = @engine.read_page(page_number)
389
- return false unless page
390
-
391
- page.header.checksum = calculate_checksum(page.data)
392
- page.write_header
393
- @engine.write_page(page)
394
- true
395
- rescue
396
- false
397
- end
398
- end
399
-
400
- def repair_record(table, row_id, column)
401
- begin
402
- columns = @engine.table_columns(table)
403
- row = @engine.select_row(table, row_id, columns)
404
- return false unless row
405
-
406
- col_def = columns.find { |c| c.name == column }
407
- return false unless col_def && col_def.has_default?
408
-
409
- row[column] = col_def.default
410
-
411
- !!@engine.update_row(table, row_id, row)
412
- rescue
413
- false
414
- end
415
- end
416
-
417
- def repair_index(index_name)
418
- begin
419
- if @engine.respond_to?(:index_manager)
420
- !!@engine.index_manager.rebuild_index(index_name)
421
- else
422
- false
423
- end
424
- rescue
425
- false
426
- end
427
- end
428
-
429
- def generate_recommendations(results)
430
- recommendations = []
431
-
432
- if results[:pages].any?
433
- recommendations << {
434
- action: "repair_pages",
435
- pages: results[:pages],
436
- description: "Repair corrupted pages"
437
- }
438
- end
439
-
440
- if results[:records].any?
441
- recommendations << {
442
- action: "repair_records",
443
- records: results[:records],
444
- description: "Repair corrupted records"
445
- }
446
- end
447
-
448
- if results[:indexes].any?
449
- recommendations << {
450
- action: "rebuild_indexes",
451
- indexes: results[:indexes],
452
- description: "Rebuild corrupted indexes"
453
- }
454
- end
455
-
456
- if results[:corrupted]
457
- recommendations << {
458
- action: "create_backup",
459
- description: "Create backup before repairs"
460
- }
461
- end
462
-
463
- recommendations
464
- end
465
- end
466
- end
467
- end
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ module RubyDB
6
+ module Recovery
7
+ # CorruptionDetector - Detects database corruption
8
+ class CorruptionDetector
9
+ attr_reader :stats
10
+
11
+ def initialize(engine, config = {})
12
+ @engine = engine
13
+ @config = config
14
+ @checksums = {}
15
+ @checksum_cache = {}
16
+ @stats = {
17
+ detections: 0,
18
+ false_positives: 0,
19
+ total_checks: 0,
20
+ corrupted_pages: 0,
21
+ corrupted_records: 0,
22
+ corrupted_indexes: 0,
23
+ last_detection_time: nil,
24
+ last_detection_result: nil
25
+ }
26
+ @lock = Mutex.new
27
+ @page_checksums = {}
28
+ end
29
+
30
+ def detect_corruption(scan_all = false)
31
+ @lock.synchronize do
32
+ @stats[:total_checks] += 1
33
+ results = {
34
+ corrupted: false,
35
+ issues: [],
36
+ pages: [],
37
+ records: [],
38
+ indexes: [],
39
+ recommendations: []
40
+ }
41
+
42
+ # Check page corruption
43
+ page_results = check_pages(scan_all)
44
+ results[:pages] = page_results[:corrupted]
45
+ results[:issues].concat(page_results[:issues])
46
+ results[:corrupted] = true if page_results[:corrupted].any?
47
+
48
+ # Check record corruption
49
+ record_results = check_records(scan_all)
50
+ results[:records] = record_results[:corrupted]
51
+ results[:issues].concat(record_results[:issues])
52
+ results[:corrupted] = true if record_results[:corrupted].any?
53
+
54
+ # Check index corruption
55
+ index_results = check_indexes(scan_all)
56
+ results[:indexes] = index_results[:corrupted]
57
+ results[:issues].concat(index_results[:issues])
58
+ results[:corrupted] = true if index_results[:corrupted].any?
59
+
60
+ # Generate recommendations
61
+ results[:recommendations] = generate_recommendations(results)
62
+
63
+ # Update stats
64
+ if results[:corrupted]
65
+ @stats[:detections] += 1
66
+ @stats[:corrupted_pages] = results[:pages].size
67
+ @stats[:corrupted_records] = results[:records].size
68
+ @stats[:corrupted_indexes] = results[:indexes].size
69
+ @stats[:last_detection_time] = Time.now
70
+ @stats[:last_detection_result] = results
71
+ end
72
+
73
+ results
74
+ end
75
+ end
76
+
77
+ def check_page(page_number)
78
+ @lock.synchronize do
79
+ result = {corrupted: false, issues: []}
80
+
81
+ begin
82
+ page = @engine.read_page(page_number)
83
+ return result unless page
84
+
85
+ # Check page header
86
+ header = page.header
87
+ if header.nil?
88
+ result[:corrupted] = true
89
+ result[:issues] << {type: "page_header", page: page_number, message: "Missing header"}
90
+ return result
91
+ end
92
+
93
+ # Check page size
94
+ if header.page_size != page.size
95
+ result[:corrupted] = true
96
+ result[:issues] << {type: "page_size", page: page_number, message: "Invalid page size"}
97
+ end
98
+
99
+ # Check page checksum
100
+ if checksum_mismatch?(page)
101
+ result[:corrupted] = true
102
+ result[:issues] << {type: "checksum", page: page_number, message: "Checksum mismatch"}
103
+ end
104
+
105
+ # Check page data
106
+ if page.data.nil? || page.data.bytesize != page.size
107
+ result[:corrupted] = true
108
+ result[:issues] << {type: "page_data", page: page_number, message: "Invalid page data"}
109
+ end
110
+ rescue => e
111
+ result[:corrupted] = true
112
+ result[:issues] << {type: "page_read", page: page_number, message: e.message}
113
+ end
114
+
115
+ result
116
+ end
117
+ end
118
+
119
+ def check_record(table_name, row_id)
120
+ @lock.synchronize do
121
+ result = {corrupted: false, issues: []}
122
+
123
+ begin
124
+ columns = @engine.table_columns(table_name)
125
+ row = @engine.select_row(table_name, row_id, columns)
126
+ return result unless row
127
+
128
+ # Validate each column value
129
+ columns.each do |col|
130
+ value = row[col.name]
131
+ next if value.nil? && col.nullable?
132
+
133
+ # Check type validity
134
+ unless valid_value_type?(value, col.type_class)
135
+ result[:corrupted] = true
136
+ result[:issues] << {
137
+ type: "record_value",
138
+ table: table_name,
139
+ row: row_id,
140
+ column: col.name,
141
+ message: "Invalid value type"
142
+ }
143
+ end
144
+ end
145
+ rescue => e
146
+ result[:corrupted] = true
147
+ result[:issues] << {
148
+ type: "record_read",
149
+ table: table_name,
150
+ row: row_id,
151
+ message: e.message
152
+ }
153
+ end
154
+
155
+ result
156
+ end
157
+ end
158
+
159
+ def check_index(index_name)
160
+ @lock.synchronize do
161
+ result = {corrupted: false, issues: []}
162
+
163
+ begin
164
+ if @engine.respond_to?(:index_manager)
165
+ index = @engine.index_manager.get_index(index_name)
166
+ return result unless index
167
+
168
+ # Validate index
169
+ if index.respond_to?(:validate)
170
+ index.validate
171
+ end
172
+
173
+ # Check index entries
174
+ entries = index.entries_count
175
+ if entries < 0
176
+ result[:corrupted] = true
177
+ result[:issues] << {
178
+ type: "index_entries",
179
+ index: index_name,
180
+ message: "Invalid entry count"
181
+ }
182
+ end
183
+ end
184
+ rescue => e
185
+ result[:corrupted] = true
186
+ result[:issues] << {
187
+ type: "index_check",
188
+ index: index_name,
189
+ message: e.message
190
+ }
191
+ end
192
+
193
+ result
194
+ end
195
+ end
196
+
197
+ def repair_corruption(corruption_info)
198
+ @lock.synchronize do
199
+ results = {repaired: false, actions: []}
200
+
201
+ issues = Array(corruption_info[:issues])
202
+ issues.each do |issue|
203
+ case issue[:type]
204
+ when "page_header"
205
+ repaired = repair_page_header(issue[:page])
206
+ results[:actions] << {type: "repair_page_header", page: issue[:page], success: repaired}
207
+ when "checksum"
208
+ repaired = repair_checksum(issue[:page])
209
+ results[:actions] << {type: "repair_checksum", page: issue[:page], success: repaired}
210
+ when "record_value"
211
+ repaired = repair_record(issue[:table], issue[:row], issue[:column])
212
+ results[:actions] << {
213
+ type: "repair_record",
214
+ table: issue[:table],
215
+ row: issue[:row],
216
+ column: issue[:column],
217
+ success: repaired
218
+ }
219
+ when "index_entries"
220
+ repaired = repair_index(issue[:index])
221
+ results[:actions] << {type: "repair_index", index: issue[:index], success: repaired}
222
+ else
223
+ results[:actions] << {
224
+ type: "unsupported_repair",
225
+ issue: issue[:type],
226
+ success: false
227
+ }
228
+ end
229
+ end
230
+
231
+ results[:repaired] = results[:actions].any? && results[:actions].all? { |a| a[:success] }
232
+ results
233
+ end
234
+ end
235
+
236
+ def stats
237
+ @lock.synchronize do
238
+ @stats.merge({
239
+ checksum_cache_size: @checksum_cache.size,
240
+ page_checksums: @page_checksums.size,
241
+ last_detection: @stats[:last_detection_time]&.iso8601
242
+ })
243
+ end
244
+ end
245
+
246
+ private
247
+
248
+ def check_pages(scan_all)
249
+ result = {corrupted: [], issues: []}
250
+
251
+ begin
252
+ page_manager = @engine.page_manager
253
+ return result unless page_manager
254
+
255
+ pages_to_check = scan_all ? (0...page_manager.total_pages) : [page_manager.current_page]
256
+
257
+ pages_to_check.each do |page_number|
258
+ page_result = check_page(page_number)
259
+ if page_result[:corrupted]
260
+ result[:corrupted] << page_number
261
+ result[:issues].concat(page_result[:issues])
262
+ end
263
+ end
264
+ rescue => e
265
+ result[:issues] << {type: "page_scan", message: e.message}
266
+ end
267
+
268
+ result
269
+ end
270
+
271
+ def check_records(scan_all)
272
+ result = {corrupted: [], issues: []}
273
+
274
+ begin
275
+ tables = @engine.list_tables
276
+ return result if tables.empty?
277
+
278
+ tables.each do |table_name|
279
+ columns = @engine.table_columns(table_name)
280
+ rows = @engine.select_rows(table_name, columns)
281
+
282
+ sample_size = scan_all ? rows.size : [rows.size, 100].min
283
+ rows.first(sample_size).each do |row|
284
+ row_id = row[:_row_id] || row["_row_id"]
285
+ record_result = check_record(table_name, row_id)
286
+ if record_result[:corrupted]
287
+ result[:corrupted] << {table: table_name, row: row_id}
288
+ result[:issues].concat(record_result[:issues])
289
+ end
290
+ end
291
+ end
292
+ rescue => e
293
+ result[:issues] << {type: "record_scan", message: e.message}
294
+ end
295
+
296
+ result
297
+ end
298
+
299
+ def check_indexes(scan_all)
300
+ result = {corrupted: [], issues: []}
301
+
302
+ begin
303
+ if @engine.respond_to?(:index_manager)
304
+ index_manager = @engine.index_manager
305
+ indexes = index_manager.indexes
306
+
307
+ indexes.each do |name, _|
308
+ index_result = check_index(name)
309
+ if index_result[:corrupted]
310
+ result[:corrupted] << name
311
+ result[:issues].concat(index_result[:issues])
312
+ end
313
+ end
314
+ end
315
+ rescue => e
316
+ result[:issues] << {type: "index_scan", message: e.message}
317
+ end
318
+
319
+ result
320
+ end
321
+
322
+ def checksum_mismatch?(page)
323
+ # Calculate page checksum
324
+ current_checksum = calculate_checksum(page.data)
325
+ stored_checksum = begin
326
+ page.header.checksum
327
+ rescue
328
+ 0
329
+ end
330
+
331
+ current_checksum != stored_checksum
332
+ end
333
+
334
+ def calculate_checksum(data)
335
+ Digest::SHA256.hexdigest(data)[0...16]
336
+ end
337
+
338
+ def valid_value_type?(value, type)
339
+ return true if value.nil?
340
+
341
+ case type
342
+ when :integer, :bigint, :smallint
343
+ value.is_a?(Integer)
344
+ when :float, :decimal
345
+ value.is_a?(Numeric)
346
+ when :boolean
347
+ [true, false].include?(value)
348
+ when :text, :varchar, :char
349
+ value.is_a?(String)
350
+ when :date, :time, :timestamp
351
+ value.is_a?(Time) || value.is_a?(Date) || value.is_a?(DateTime)
352
+ when :blob
353
+ value.is_a?(String)
354
+ when :json
355
+ value.is_a?(Hash) || value.is_a?(Array)
356
+ else
357
+ true
358
+ end
359
+ end
360
+
361
+ def repair_page_header(page_number)
362
+ page = @engine.read_page(page_number)
363
+ return false unless page
364
+
365
+ # Rebuild header
366
+ page.header.page_number = page_number
367
+ page.header.page_size = page.size
368
+ page.header.header_size = 64
369
+ page.header.data_end = 64
370
+ page.header.flags = 0
371
+ page.header.checksum = calculate_checksum(page.data)
372
+ page.header.version = 1
373
+ page.header.page_type = 0
374
+
375
+ page.write_header
376
+ @engine.write_page(page)
377
+ true
378
+ rescue
379
+ false
380
+ end
381
+
382
+ def repair_checksum(page_number)
383
+ page = @engine.read_page(page_number)
384
+ return false unless page
385
+
386
+ page.header.checksum = calculate_checksum(page.data)
387
+ page.write_header
388
+ @engine.write_page(page)
389
+ true
390
+ rescue
391
+ false
392
+ end
393
+
394
+ def repair_record(table, row_id, column)
395
+ columns = @engine.table_columns(table)
396
+ row = @engine.select_row(table, row_id, columns)
397
+ return false unless row
398
+
399
+ col_def = columns.find { |c| c.name == column }
400
+ return false unless col_def&.has_default?
401
+
402
+ row[column] = col_def.default
403
+
404
+ !!@engine.update_row(table, row_id, row)
405
+ rescue
406
+ false
407
+ end
408
+
409
+ def repair_index(index_name)
410
+ if @engine.respond_to?(:index_manager)
411
+ !!@engine.index_manager.rebuild_index(index_name)
412
+ else
413
+ false
414
+ end
415
+ rescue
416
+ false
417
+ end
418
+
419
+ def generate_recommendations(results)
420
+ recommendations = []
421
+
422
+ if results[:pages].any?
423
+ recommendations << {
424
+ action: "repair_pages",
425
+ pages: results[:pages],
426
+ description: "Repair corrupted pages"
427
+ }
428
+ end
429
+
430
+ if results[:records].any?
431
+ recommendations << {
432
+ action: "repair_records",
433
+ records: results[:records],
434
+ description: "Repair corrupted records"
435
+ }
436
+ end
437
+
438
+ if results[:indexes].any?
439
+ recommendations << {
440
+ action: "rebuild_indexes",
441
+ indexes: results[:indexes],
442
+ description: "Rebuild corrupted indexes"
443
+ }
444
+ end
445
+
446
+ if results[:corrupted]
447
+ recommendations << {
448
+ action: "create_backup",
449
+ description: "Create backup before repairs"
450
+ }
451
+ end
452
+
453
+ recommendations
454
+ end
455
+ end
456
+ end
457
+ end