rubydb 0.1.3 → 0.1.5

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 (420) 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 +12 -5
  14. data/.rubocop.yml +50 -44
  15. data/.standard.yml +9 -14
  16. data/ARCHITECTURE.md +21 -21
  17. data/CHANGELOG.md +49 -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 +249 -162
  24. data/ROADMAP.md +27 -27
  25. data/Rakefile +71 -71
  26. data/SECURITY.md +54 -54
  27. data/SUPPORT.md +14 -14
  28. data/adapters/activerecord/Gemfile +11 -11
  29. data/adapters/activerecord/lib/active_record/connection_adapters/rubydb_adapter.rb +877 -880
  30. data/adapters/activerecord/rubydb-activerecord.gemspec +21 -21
  31. data/adapters/activerecord/spec/rubydb_adapter_integration_spec.rb +155 -143
  32. data/adapters/ruby/README.md +18 -18
  33. data/adapters/sequel/README.md +11 -11
  34. data/config/monitoring/prometheus-alerts.yml +39 -39
  35. data/config/production.yml +36 -36
  36. data/docs/README.md +75 -71
  37. data/docs/architecture/concurrency.md +14 -14
  38. data/docs/architecture/current-state.md +125 -125
  39. data/docs/architecture/execution-engine.md +25 -25
  40. data/docs/architecture/indexes.md +19 -19
  41. data/docs/architecture/mvcc.md +19 -19
  42. data/docs/architecture/overview.md +13 -13
  43. data/docs/architecture/pages.md +11 -11
  44. data/docs/architecture/production-roadmap.md +82 -82
  45. data/docs/architecture/query-planner.md +20 -20
  46. data/docs/architecture/recovery.md +18 -18
  47. data/docs/architecture/sql-engine.md +12 -12
  48. data/docs/architecture/storage-engine.md +14 -14
  49. data/docs/architecture/transactions.md +10 -10
  50. data/docs/architecture/wal.md +28 -28
  51. data/docs/cli-cheatsheet.md +98 -98
  52. data/docs/cli.md +275 -275
  53. data/docs/contributing/architecture.md +9 -9
  54. data/docs/contributing/benchmarking.md +14 -14
  55. data/docs/contributing/development.md +16 -16
  56. data/docs/contributing/release-process.md +49 -49
  57. data/docs/contributing/testing.md +16 -16
  58. data/docs/debugging.md +229 -229
  59. data/docs/developer/branching.md +10 -10
  60. data/docs/developer/database-diff.md +10 -10
  61. data/docs/developer/local-development.md +17 -17
  62. data/docs/developer/snapshots.md +9 -9
  63. data/docs/developer/temporal-data.md +10 -10
  64. data/docs/developer-guide.md +297 -297
  65. data/docs/getting-started/first-database.md +16 -16
  66. data/docs/getting-started/first-query.md +13 -13
  67. data/docs/getting-started/installation.md +19 -19
  68. data/docs/getting-started/local-to-production.md +300 -300
  69. data/docs/getting-started/quickstart.md +17 -17
  70. data/docs/getting-started/rails.md +16 -16
  71. data/docs/hardening_backlog.md +93 -93
  72. data/docs/lessons-learned.md +112 -112
  73. data/docs/operations/backups.md +33 -33
  74. data/docs/operations/disaster-recovery.md +31 -31
  75. data/docs/operations/failover.md +30 -30
  76. data/docs/operations/monitoring.md +25 -25
  77. data/docs/operations/production-guide.md +295 -295
  78. data/docs/operations/production-runbook.md +45 -45
  79. data/docs/operations/replication.md +33 -33
  80. data/docs/operations/restore.md +6 -6
  81. data/docs/operations/runbook.md +34 -34
  82. data/docs/operations/upgrades.md +14 -14
  83. data/docs/operations/workload-testing.md +17 -17
  84. data/docs/production-readiness.md +118 -118
  85. data/docs/production_validation.md +150 -150
  86. data/docs/rails/active-record.md +11 -11
  87. data/docs/rails/compatibility-guide.md +90 -90
  88. data/docs/rails/database-yml.md +92 -92
  89. data/docs/rails/installation.md +17 -17
  90. data/docs/rails/migrations.md +17 -17
  91. data/docs/rails/production.md +82 -82
  92. data/docs/rails/troubleshooting.md +18 -18
  93. data/docs/release.md +25 -25
  94. data/docs/server/architecture.md +10 -10
  95. data/docs/server/authentication.md +10 -10
  96. data/docs/server/configuration.md +16 -16
  97. data/docs/server/connection-pooling.md +10 -10
  98. data/docs/server/deployment.md +10 -10
  99. data/docs/server/protocol.md +12 -12
  100. data/docs/sql/compatibility-guide.md +82 -82
  101. data/docs/sql/compatibility.md +39 -39
  102. data/docs/sql/data-types.md +10 -10
  103. data/docs/sql/functions.md +9 -9
  104. data/docs/sql/joins.md +9 -9
  105. data/docs/sql/operators.md +9 -9
  106. data/docs/sql/sqlite-compatibility.md +21 -21
  107. data/docs/sql/syntax.md +10 -10
  108. data/docs/sql/transactions.md +10 -10
  109. data/docs/troubleshooting.md +244 -244
  110. data/lessons/01-foundations.md +73 -0
  111. data/lessons/02-local-development.md +121 -0
  112. data/lessons/03-embedded-rubydb.md +99 -0
  113. data/lessons/04-rails-complex-apps.md +138 -0
  114. data/lessons/05-rubydb-production-server.md +237 -0
  115. data/lessons/06-postgresql-massive-apps.md +96 -0
  116. data/lessons/07-hybrid-microservices.md +94 -0
  117. data/lessons/08-migrations-backups-recovery.md +86 -0
  118. data/lessons/09-observability-security-scale.md +87 -0
  119. data/lessons/10-release-readiness.md +125 -0
  120. data/lib/rubydb/backup/archive.rb +332 -334
  121. data/lib/rubydb/backup/backup.rb +400 -401
  122. data/lib/rubydb/backup/incremental.rb +349 -353
  123. data/lib/rubydb/backup/restore.rb +289 -290
  124. data/lib/rubydb/backup/snapshot.rb +265 -267
  125. data/lib/rubydb/backup/verification.rb +276 -279
  126. data/lib/rubydb/branching/branch.rb +181 -181
  127. data/lib/rubydb/branching/branch_manager.rb +307 -311
  128. data/lib/rubydb/branching/branch_metadata.rb +140 -140
  129. data/lib/rubydb/branching/checkout.rb +165 -166
  130. data/lib/rubydb/branching/copy_on_write.rb +272 -272
  131. data/lib/rubydb/branching/diff.rb +137 -138
  132. data/lib/rubydb/branching/merge.rb +282 -285
  133. data/lib/rubydb/build_info.rb +15 -15
  134. data/lib/rubydb/catalog/catalog.rb +391 -391
  135. data/lib/rubydb/catalog/column.rb +112 -112
  136. data/lib/rubydb/catalog/constraint.rb +180 -180
  137. data/lib/rubydb/catalog/database.rb +184 -184
  138. data/lib/rubydb/catalog/index.rb +97 -97
  139. data/lib/rubydb/catalog/schema.rb +103 -103
  140. data/lib/rubydb/catalog/sequence.rb +90 -90
  141. data/lib/rubydb/catalog/system_catalog.rb +698 -698
  142. data/lib/rubydb/catalog/table.rb +178 -178
  143. data/lib/rubydb/catalog/trigger.rb +102 -102
  144. data/lib/rubydb/catalog/view.rb +66 -66
  145. data/lib/rubydb/cli/application.rb +163 -163
  146. data/lib/rubydb/cli/commands/backup.rb +80 -81
  147. data/lib/rubydb/cli/commands/branch.rb +72 -72
  148. data/lib/rubydb/cli/commands/checkout.rb +54 -54
  149. data/lib/rubydb/cli/commands/create.rb +58 -58
  150. data/lib/rubydb/cli/commands/diff.rb +76 -77
  151. data/lib/rubydb/cli/commands/doctor.rb +74 -74
  152. data/lib/rubydb/cli/commands/drop.rb +57 -57
  153. data/lib/rubydb/cli/commands/init.rb +101 -102
  154. data/lib/rubydb/cli/commands/inspect.rb +95 -95
  155. data/lib/rubydb/cli/commands/merge.rb +63 -63
  156. data/lib/rubydb/cli/commands/migrate.rb +62 -62
  157. data/lib/rubydb/cli/commands/restart.rb +42 -39
  158. data/lib/rubydb/cli/commands/restore.rb +121 -121
  159. data/lib/rubydb/cli/commands/shell.rb +365 -365
  160. data/lib/rubydb/cli/commands/snapshot.rb +79 -79
  161. data/lib/rubydb/cli/commands/start.rb +88 -82
  162. data/lib/rubydb/cli/commands/status.rb +96 -92
  163. data/lib/rubydb/cli/commands/stop.rb +47 -47
  164. data/lib/rubydb/cli/commands/vacuum.rb +58 -58
  165. data/lib/rubydb/cli/formatter.rb +221 -221
  166. data/lib/rubydb/cli/output.rb +168 -168
  167. data/lib/rubydb/client/client.rb +302 -304
  168. data/lib/rubydb/client/connection.rb +414 -415
  169. data/lib/rubydb/client/connection_pool.rb +168 -168
  170. data/lib/rubydb/client/connection_url.rb +96 -96
  171. data/lib/rubydb/client/prepared_statement.rb +60 -60
  172. data/lib/rubydb/client/result.rb +123 -123
  173. data/lib/rubydb/client/statement.rb +52 -52
  174. data/lib/rubydb/client/transaction.rb +130 -130
  175. data/lib/rubydb/concurrency/concurrency.rb +19 -19
  176. data/lib/rubydb/concurrency/deadlock_detector.rb +148 -150
  177. data/lib/rubydb/concurrency/latch.rb +101 -101
  178. data/lib/rubydb/concurrency/lock_graph.rb +163 -165
  179. data/lib/rubydb/concurrency/mutex.rb +181 -183
  180. data/lib/rubydb/concurrency/rw_lock.rb +180 -180
  181. data/lib/rubydb/concurrency/scheduler.rb +248 -250
  182. data/lib/rubydb/concurrency/worker_pool.rb +145 -143
  183. data/lib/rubydb/configuration/config.rb +170 -170
  184. data/lib/rubydb/configuration/defaults.rb +179 -179
  185. data/lib/rubydb/configuration/environment.rb +152 -152
  186. data/lib/rubydb/configuration/parser.rb +185 -185
  187. data/lib/rubydb/configuration/validation.rb +221 -221
  188. data/lib/rubydb/constants.rb +74 -74
  189. data/lib/rubydb/constraints/check.rb +181 -181
  190. data/lib/rubydb/constraints/constraint.rb +101 -101
  191. data/lib/rubydb/constraints/foreign_key.rb +130 -130
  192. data/lib/rubydb/constraints/not_null.rb +64 -64
  193. data/lib/rubydb/constraints/primary_key.rb +99 -99
  194. data/lib/rubydb/constraints/unique.rb +106 -108
  195. data/lib/rubydb/constraints/validator.rb +349 -350
  196. data/lib/rubydb/errors/authentication_error.rb +10 -10
  197. data/lib/rubydb/errors/authorization_error.rb +23 -23
  198. data/lib/rubydb/errors/client_error.rb +10 -10
  199. data/lib/rubydb/errors/configuration_error.rb +10 -10
  200. data/lib/rubydb/errors/connection_error.rb +10 -10
  201. data/lib/rubydb/errors/constraint_error.rb +23 -23
  202. data/lib/rubydb/errors/corruption_error.rb +10 -10
  203. data/lib/rubydb/errors/database_error.rb +10 -10
  204. data/lib/rubydb/errors/error.rb +20 -20
  205. data/lib/rubydb/errors/execution_error.rb +10 -10
  206. data/lib/rubydb/errors/parser_error.rb +10 -10
  207. data/lib/rubydb/errors/recovery_error.rb +10 -10
  208. data/lib/rubydb/errors/replication_error.rb +10 -10
  209. data/lib/rubydb/errors/server_error.rb +6 -6
  210. data/lib/rubydb/errors/storage_error.rb +10 -10
  211. data/lib/rubydb/errors/transaction_error.rb +10 -10
  212. data/lib/rubydb/execution/aggregate_executor.rb +134 -138
  213. data/lib/rubydb/execution/delete_executor.rb +110 -112
  214. data/lib/rubydb/execution/distinct_executor.rb +131 -135
  215. data/lib/rubydb/execution/executor.rb +1195 -1192
  216. data/lib/rubydb/execution/expression.rb +191 -193
  217. data/lib/rubydb/execution/index_scan.rb +142 -142
  218. data/lib/rubydb/execution/insert_executor.rb +215 -217
  219. data/lib/rubydb/execution/join_executor.rb +243 -249
  220. data/lib/rubydb/execution/limit_executor.rb +83 -85
  221. data/lib/rubydb/execution/optimizer.rb +227 -215
  222. data/lib/rubydb/execution/plan.rb +355 -353
  223. data/lib/rubydb/execution/planner.rb +542 -536
  224. data/lib/rubydb/execution/predicate.rb +235 -235
  225. data/lib/rubydb/execution/scan.rb +49 -49
  226. data/lib/rubydb/execution/sequential_scan.rb +63 -63
  227. data/lib/rubydb/execution/sort_executor.rb +180 -185
  228. data/lib/rubydb/execution/update_executor.rb +160 -162
  229. data/lib/rubydb/functions/aggregate.rb +70 -70
  230. data/lib/rubydb/functions/date_functions.rb +274 -278
  231. data/lib/rubydb/functions/function.rb +85 -85
  232. data/lib/rubydb/functions/json_functions.rb +231 -215
  233. data/lib/rubydb/functions/numeric_functions.rb +346 -346
  234. data/lib/rubydb/functions/scalar.rb +52 -52
  235. data/lib/rubydb/functions/string_functions.rb +383 -383
  236. data/lib/rubydb/functions/system_functions.rb +258 -246
  237. data/lib/rubydb/history/as_of.rb +238 -238
  238. data/lib/rubydb/history/change.rb +105 -105
  239. data/lib/rubydb/history/history.rb +131 -131
  240. data/lib/rubydb/history/history_manager.rb +228 -229
  241. data/lib/rubydb/history/temporal_query.rb +202 -202
  242. data/lib/rubydb/history/timeline.rb +144 -144
  243. data/lib/rubydb/indexes/btree.rb +186 -186
  244. data/lib/rubydb/indexes/btree_cursor.rb +258 -258
  245. data/lib/rubydb/indexes/btree_node.rb +384 -385
  246. data/lib/rubydb/indexes/hash_index.rb +150 -150
  247. data/lib/rubydb/indexes/index.rb +71 -71
  248. data/lib/rubydb/indexes/index_manager.rb +408 -406
  249. data/lib/rubydb/indexes/index_scan.rb +466 -470
  250. data/lib/rubydb/migrations/migration.rb +253 -254
  251. data/lib/rubydb/migrations/migration_lock.rb +146 -146
  252. data/lib/rubydb/migrations/migration_manager.rb +187 -176
  253. data/lib/rubydb/migrations/migration_version.rb +71 -71
  254. data/lib/rubydb/migrations/schema_diff.rb +211 -211
  255. data/lib/rubydb/migrations/schema_version.rb +64 -64
  256. data/lib/rubydb/monitoring/events.rb +155 -160
  257. data/lib/rubydb/monitoring/health.rb +216 -222
  258. data/lib/rubydb/monitoring/logger.rb +188 -193
  259. data/lib/rubydb/monitoring/metrics.rb +363 -359
  260. data/lib/rubydb/monitoring/performance.rb +176 -176
  261. data/lib/rubydb/monitoring/statistics.rb +168 -170
  262. data/lib/rubydb/mvcc/garbage_collector.rb +199 -199
  263. data/lib/rubydb/mvcc/mvcc.rb +16 -16
  264. data/lib/rubydb/mvcc/snapshot.rb +146 -147
  265. data/lib/rubydb/mvcc/vacuum.rb +180 -180
  266. data/lib/rubydb/mvcc/version.rb +106 -106
  267. data/lib/rubydb/mvcc/version_store.rb +396 -398
  268. data/lib/rubydb/mvcc/visibility.rb +107 -109
  269. data/lib/rubydb/protocol/capabilities.rb +125 -125
  270. data/lib/rubydb/protocol/decoder.rb +142 -145
  271. data/lib/rubydb/protocol/encoder.rb +131 -136
  272. data/lib/rubydb/protocol/handshake.rb +306 -305
  273. data/lib/rubydb/protocol/message.rb +121 -121
  274. data/lib/rubydb/protocol/parameter_binder.rb +101 -0
  275. data/lib/rubydb/protocol/protocol.rb +276 -277
  276. data/lib/rubydb/protocol/version.rb +54 -54
  277. data/lib/rubydb/rails/adapter.rb +245 -239
  278. data/lib/rubydb/rails/connection.rb +312 -314
  279. data/lib/rubydb/rails/database_statements.rb +122 -122
  280. data/lib/rubydb/rails/migration.rb +131 -131
  281. data/lib/rubydb/rails/quoting.rb +109 -109
  282. data/lib/rubydb/rails/result.rb +123 -120
  283. data/lib/rubydb/rails/schema_statements.rb +339 -339
  284. data/lib/rubydb/rails/transaction.rb +105 -105
  285. data/lib/rubydb/rails/type.rb +126 -126
  286. data/lib/rubydb/recovery/checkpoint.rb +261 -257
  287. data/lib/rubydb/recovery/consistency.rb +457 -467
  288. data/lib/rubydb/recovery/corruption_detector.rb +5 -5
  289. data/lib/rubydb/recovery/crash_recovery.rb +381 -387
  290. data/lib/rubydb/recovery/recovery_manager.rb +204 -206
  291. data/lib/rubydb/recovery/redo.rb +235 -237
  292. data/lib/rubydb/recovery/undo.rb +204 -206
  293. data/lib/rubydb/replication/failover.rb +5 -5
  294. data/lib/rubydb/replication/fencing.rb +63 -63
  295. data/lib/rubydb/replication/primary.rb +461 -450
  296. data/lib/rubydb/replication/replica.rb +382 -384
  297. data/lib/rubydb/replication/replication_log.rb +194 -200
  298. data/lib/rubydb/replication/replication_manager.rb +307 -308
  299. data/lib/rubydb/replication/replication_slot.rb +293 -295
  300. data/lib/rubydb/replication/replication_stream.rb +198 -201
  301. data/lib/rubydb/rubydb.rb +564 -560
  302. data/lib/rubydb/security/access_control.rb +252 -254
  303. data/lib/rubydb/security/audit_log.rb +209 -213
  304. data/lib/rubydb/security/authentication.rb +302 -302
  305. data/lib/rubydb/security/authorization.rb +282 -282
  306. data/lib/rubydb/security/credentials.rb +192 -196
  307. data/lib/rubydb/security/password.rb +205 -215
  308. data/lib/rubydb/security/permissions.rb +74 -74
  309. data/lib/rubydb/security/role.rb +99 -101
  310. data/lib/rubydb/security/user.rb +86 -86
  311. data/lib/rubydb/server/connection.rb +383 -366
  312. data/lib/rubydb/server/connection_pool.rb +193 -193
  313. data/lib/rubydb/server/lifecycle.rb +227 -228
  314. data/lib/rubydb/server/listener.rb +139 -136
  315. data/lib/rubydb/server/request_handler.rb +277 -276
  316. data/lib/rubydb/server/server.rb +363 -364
  317. data/lib/rubydb/server/session.rb +371 -369
  318. data/lib/rubydb/server/worker.rb +206 -210
  319. data/lib/rubydb/server/worker_pool.rb +168 -168
  320. data/lib/rubydb/sql/ast/alter_table.rb +169 -169
  321. data/lib/rubydb/sql/ast/begin_transaction.rb +47 -47
  322. data/lib/rubydb/sql/ast/commit.rb +37 -37
  323. data/lib/rubydb/sql/ast/constraint.rb +92 -83
  324. data/lib/rubydb/sql/ast/create_database.rb +41 -41
  325. data/lib/rubydb/sql/ast/create_index.rb +61 -61
  326. data/lib/rubydb/sql/ast/create_schema.rb +52 -52
  327. data/lib/rubydb/sql/ast/create_table.rb +187 -187
  328. data/lib/rubydb/sql/ast/delete.rb +54 -54
  329. data/lib/rubydb/sql/ast/drop_database.rb +41 -41
  330. data/lib/rubydb/sql/ast/drop_index.rb +41 -41
  331. data/lib/rubydb/sql/ast/drop_schema.rb +49 -49
  332. data/lib/rubydb/sql/ast/drop_table.rb +49 -49
  333. data/lib/rubydb/sql/ast/explain.rb +64 -64
  334. data/lib/rubydb/sql/ast/expression.rb +617 -604
  335. data/lib/rubydb/sql/ast/insert.rb +66 -66
  336. data/lib/rubydb/sql/ast/node.rb +42 -42
  337. data/lib/rubydb/sql/ast/rollback.rb +63 -63
  338. data/lib/rubydb/sql/ast/savepoint.rb +59 -59
  339. data/lib/rubydb/sql/ast/select.rb +88 -88
  340. data/lib/rubydb/sql/ast/set_operation.rb +22 -20
  341. data/lib/rubydb/sql/ast/trigger.rb +35 -29
  342. data/lib/rubydb/sql/ast/update.rb +88 -88
  343. data/lib/rubydb/sql/ast/vacuum.rb +19 -19
  344. data/lib/rubydb/sql/ast/view.rb +38 -32
  345. data/lib/rubydb/sql/ast/with.rb +32 -32
  346. data/lib/rubydb/sql/grammar.rb +86 -86
  347. data/lib/rubydb/sql/keywords.rb +156 -156
  348. data/lib/rubydb/sql/lexer.rb +209 -214
  349. data/lib/rubydb/sql/operators.rb +100 -100
  350. data/lib/rubydb/sql/parser.rb +1167 -1170
  351. data/lib/rubydb/sql/planner/analyzer.rb +283 -302
  352. data/lib/rubydb/sql/planner/binder.rb +537 -550
  353. data/lib/rubydb/sql/planner/type_checker.rb +427 -431
  354. data/lib/rubydb/sql/token.rb +210 -210
  355. data/lib/rubydb/storage/buffer_frame.rb +44 -44
  356. data/lib/rubydb/storage/buffer_pool.rb +155 -155
  357. data/lib/rubydb/storage/database_lock.rb +74 -74
  358. data/lib/rubydb/storage/deserializer.rb +332 -342
  359. data/lib/rubydb/storage/engine.rb +2347 -2330
  360. data/lib/rubydb/storage/file_manager.rb +191 -187
  361. data/lib/rubydb/storage/free_space_map.rb +79 -81
  362. data/lib/rubydb/storage/page.rb +92 -94
  363. data/lib/rubydb/storage/page_allocator.rb +852 -855
  364. data/lib/rubydb/storage/page_header.rb +63 -67
  365. data/lib/rubydb/storage/page_manager.rb +127 -131
  366. data/lib/rubydb/storage/record.rb +58 -58
  367. data/lib/rubydb/storage/row.rb +78 -78
  368. data/lib/rubydb/storage/serializer.rb +51 -51
  369. data/lib/rubydb/storage/storage_layout.rb +151 -151
  370. data/lib/rubydb/storage/storage_manager.rb +114 -114
  371. data/lib/rubydb/storage/tuple.rb +458 -461
  372. data/lib/rubydb/storage/visibility_map.rb +964 -973
  373. data/lib/rubydb/transactions/commit_manager.rb +219 -220
  374. data/lib/rubydb/transactions/isolation.rb +98 -98
  375. data/lib/rubydb/transactions/lock.rb +76 -76
  376. data/lib/rubydb/transactions/lock_manager.rb +359 -362
  377. data/lib/rubydb/transactions/savepoint.rb +142 -143
  378. data/lib/rubydb/transactions/transaction.rb +214 -215
  379. data/lib/rubydb/transactions/transaction_id.rb +84 -84
  380. data/lib/rubydb/transactions/transaction_log.rb +256 -257
  381. data/lib/rubydb/transactions/transaction_manager.rb +434 -435
  382. data/lib/rubydb/types/bigint.rb +36 -36
  383. data/lib/rubydb/types/blob.rb +37 -37
  384. data/lib/rubydb/types/boolean.rb +34 -34
  385. data/lib/rubydb/types/date.rb +39 -39
  386. data/lib/rubydb/types/decimal.rb +48 -48
  387. data/lib/rubydb/types/float.rb +34 -34
  388. data/lib/rubydb/types/integer.rb +36 -36
  389. data/lib/rubydb/types/json.rb +41 -41
  390. data/lib/rubydb/types/null.rb +34 -34
  391. data/lib/rubydb/types/smallint.rb +36 -36
  392. data/lib/rubydb/types/text.rb +37 -37
  393. data/lib/rubydb/types/time.rb +46 -46
  394. data/lib/rubydb/types/timestamp.rb +39 -39
  395. data/lib/rubydb/types/type.rb +119 -119
  396. data/lib/rubydb/types/uuid.rb +47 -47
  397. data/lib/rubydb/types/varchar.rb +37 -37
  398. data/lib/rubydb/version.rb +32 -32
  399. data/lib/rubydb/wal/archive.rb +190 -193
  400. data/lib/rubydb/wal/checkpoint.rb +181 -183
  401. data/lib/rubydb/wal/lsn.rb +94 -94
  402. data/lib/rubydb/wal/reader.rb +259 -260
  403. data/lib/rubydb/wal/record.rb +105 -105
  404. data/lib/rubydb/wal/segment.rb +193 -193
  405. data/lib/rubydb/wal/wal.rb +480 -452
  406. data/lib/rubydb/wal/writer.rb +236 -236
  407. data/lib/rubydb.rb +7 -7
  408. data/packaging/docker/docker-compose.failover.yml +43 -43
  409. data/packaging/homebrew/rubydb.rb +19 -19
  410. data/rubydb.gemspec +60 -57
  411. data/scripts/benchmark +7 -7
  412. data/scripts/durability_drill +37 -37
  413. data/scripts/fuzz +63 -63
  414. data/scripts/release +45 -40
  415. data/scripts/release_check +43 -43
  416. data/scripts/replication_failover_drill +268 -250
  417. data/scripts/replication_network_failover_drill +287 -255
  418. data/scripts/restore_drill +45 -45
  419. data/scripts/security +45 -0
  420. metadata +54 -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