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,550 +1,537 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyDB
4
- module SQL
5
- module Planner
6
- # Binder - Resolves identifiers and binds AST nodes to catalog objects
7
- class Binder
8
- attr_reader :catalog, :errors, :warnings
9
-
10
- def initialize(catalog)
11
- @catalog = catalog
12
- @errors = []
13
- @warnings = []
14
- @current_scope = {}
15
- @current_table = nil
16
- @current_table_alias = nil
17
- @in_subquery = false
18
- end
19
-
20
- # Bind an AST statement to catalog objects
21
- def bind(statement)
22
- @errors = []
23
- @warnings = []
24
- statement.accept(self)
25
- [statement, @errors, @warnings]
26
- end
27
-
28
- # --- Visitor methods ---
29
-
30
- def visit_select(node)
31
- # Bind FROM clause first
32
- if node.from
33
- table_info = bind_table_reference(node.from)
34
- @current_table = table_info
35
- @current_table_alias = node.from.alias_name || node.from.name
36
-
37
- # Build scope from table columns
38
- @current_scope = build_scope_from_table(table_info)
39
- end
40
-
41
- # Bind WHERE clause
42
- if node.where
43
- node.where.accept(self)
44
- end
45
-
46
- # Bind ORDER BY
47
- node.order_by.each do |order_item|
48
- order_item.accept(self)
49
- end
50
-
51
- # Bind LIMIT and OFFSET
52
- node.limit&.accept(self)
53
- node.offset&.accept(self)
54
-
55
- # Bind columns
56
- node.columns.each do |col|
57
- col.accept(self)
58
- end
59
-
60
- # Clear scope after binding
61
- @current_scope = {}
62
- @current_table = nil
63
- @current_table_alias = nil
64
- end
65
-
66
- def visit_insert(node)
67
- table_info = @catalog.find_table(node.table)
68
- unless table_info
69
- @errors << "Table '#{node.table}' does not exist"
70
- return
71
- end
72
-
73
- @current_table = table_info
74
- @current_table_alias = node.table
75
- @current_scope = build_scope_from_table(table_info)
76
-
77
- # Validate columns
78
- if node.columns.any?
79
- node.columns.each do |col_name|
80
- unless table_info.columns.any? { |c| c.name == col_name }
81
- @errors << "Column '#{col_name}' does not exist in table '#{node.table}'"
82
- end
83
- end
84
- end
85
-
86
- # Bind values
87
- node.rows.each do |row|
88
- row.each do |value|
89
- value.accept(self)
90
- end
91
- end
92
-
93
- @current_scope = {}
94
- @current_table = nil
95
- @current_table_alias = nil
96
- end
97
-
98
- def visit_update(node)
99
- table_info = @catalog.find_table(node.table)
100
- unless table_info
101
- @errors << "Table '#{node.table}' does not exist"
102
- return
103
- end
104
-
105
- @current_table = table_info
106
- @current_table_alias = node.table
107
- @current_scope = build_scope_from_table(table_info)
108
-
109
- # Validate assignments
110
- node.assignments.each do |assignment|
111
- unless table_info.columns.any? { |c| c.name == assignment.column }
112
- @errors << "Column '#{assignment.column}' does not exist in table '#{node.table}'"
113
- end
114
- assignment.value.accept(self)
115
- end
116
-
117
- # Bind WHERE clause
118
- if node.where
119
- node.where.accept(self)
120
- end
121
-
122
- @current_scope = {}
123
- @current_table = nil
124
- @current_table_alias = nil
125
- end
126
-
127
- def visit_delete(node)
128
- table_info = @catalog.find_table(node.table)
129
- unless table_info
130
- @errors << "Table '#{node.table}' does not exist"
131
- return
132
- end
133
-
134
- @current_table = table_info
135
- @current_table_alias = node.table
136
- @current_scope = build_scope_from_table(table_info)
137
-
138
- # Bind WHERE clause
139
- if node.where
140
- node.where.accept(self)
141
- end
142
-
143
- @current_scope = {}
144
- @current_table = nil
145
- @current_table_alias = nil
146
- end
147
-
148
- def visit_create_table(node)
149
- # Check if table already exists
150
- if @catalog.find_table(node.name)
151
- unless node.if_not_exists
152
- @errors << "Table '#{node.name}' already exists"
153
- end
154
- return
155
- end
156
-
157
- # Validate column definitions
158
- node.columns.each do |col|
159
- # Check for duplicate column names
160
- if node.columns.count { |c| c.name == col.name } > 1
161
- @errors << "Duplicate column name '#{col.name}' in table '#{node.name}'"
162
- end
163
-
164
- # Validate type
165
- type_class = col.type_class
166
- unless valid_type?(type_class)
167
- @errors << "Invalid data type '#{type_class}' for column '#{col.name}'"
168
- end
169
- end
170
-
171
- # Validate constraints
172
- node.constraints.each do |constraint|
173
- constraint.accept(self)
174
- end
175
- end
176
-
177
- def visit_alter_table_add_column(node)
178
- table_info = @catalog.find_table(node.table_name)
179
- unless table_info
180
- @errors << "Table '#{node.table_name}' does not exist"
181
- return
182
- end
183
-
184
- # Check if column already exists
185
- if table_info.columns.any? { |c| c.name == node.column_name }
186
- @errors << "Column '#{node.column_name}' already exists in table '#{node.table_name}'"
187
- end
188
-
189
- # Validate type
190
- type_class = node.column_type.is_a?(Hash) ? node.column_type[:type] : node.column_type
191
- unless valid_type?(type_class)
192
- @errors << "Invalid data type '#{type_class}' for column '#{node.column_name}'"
193
- end
194
- end
195
-
196
- def visit_alter_table_drop_column(node)
197
- table_info = @catalog.find_table(node.table_name)
198
- unless table_info
199
- @errors << "Table '#{node.table_name}' does not exist"
200
- return
201
- end
202
-
203
- unless table_info.columns.any? { |c| c.name == node.column_name }
204
- @errors << "Column '#{node.column_name}' does not exist in table '#{node.table_name}'"
205
- end
206
- end
207
-
208
- def visit_alter_table_add_constraint(node)
209
- table_info = @catalog.find_table(node.table_name)
210
- unless table_info
211
- @errors << "Table '#{node.table_name}' does not exist"
212
- return
213
- end
214
- @current_table = table_info
215
- node.constraint.accept(self)
216
- @current_table = nil
217
- end
218
-
219
- def visit_alter_table_drop_constraint(node)
220
- table_info = @catalog.find_table(node.table_name)
221
- unless table_info
222
- @errors << "Table '#{node.table_name}' does not exist"
223
- return
224
- end
225
- # Constraint metadata is persisted by the storage engine and may not
226
- # yet be hydrated into the catalog after a restart. The executor is
227
- # authoritative for the existence check.
228
- end
229
-
230
- def visit_drop_table(node)
231
- unless @catalog.find_table(node.name)
232
- unless node.if_exists
233
- @errors << "Table '#{node.name}' does not exist"
234
- end
235
- end
236
- end
237
-
238
- def visit_create_index(node)
239
- table_info = @catalog.find_table(node.table_name)
240
- unless table_info
241
- @errors << "Table '#{node.table_name}' does not exist"
242
- return
243
- end
244
-
245
- # Validate columns
246
- node.columns.each do |col_name|
247
- unless table_info.columns.any? { |c| c.name == col_name }
248
- @errors << "Column '#{col_name}' does not exist in table '#{node.table_name}'"
249
- end
250
- end
251
-
252
- # Check if index already exists
253
- if table_info.indexes.any? { |idx| idx.name == node.name }
254
- unless node.if_not_exists
255
- @errors << "Index '#{node.name}' already exists"
256
- end
257
- end
258
- end
259
-
260
- def visit_drop_index(node)
261
- # Check if index exists (we need to search all tables)
262
- index_exists = false
263
- @catalog.tables.each do |table|
264
- if table.indexes.any? { |idx| idx.name == node.name }
265
- index_exists = true
266
- break
267
- end
268
- end
269
-
270
- unless index_exists
271
- unless node.if_exists
272
- @errors << "Index '#{node.name}' does not exist"
273
- end
274
- end
275
- end
276
-
277
- def visit_create_database(node)
278
- if @catalog.find_database(node.name)
279
- unless node.if_not_exists
280
- @errors << "Database '#{node.name}' already exists"
281
- end
282
- end
283
- end
284
-
285
- def visit_drop_database(node)
286
- unless @catalog.find_database(node.name)
287
- unless node.if_exists
288
- @errors << "Database '#{node.name}' does not exist"
289
- end
290
- end
291
- end
292
-
293
- def visit_create_schema(node)
294
- if @catalog.find_schema(node.name)
295
- @errors << "Schema '#{node.name}' already exists" unless node.if_not_exists
296
- end
297
- end
298
-
299
- def visit_drop_schema(node)
300
- unless @catalog.find_schema(node.name)
301
- @errors << "Schema '#{node.name}' does not exist" unless node.if_exists
302
- end
303
- end
304
-
305
- def visit_create_view(node)
306
- if @catalog.find_view(node.name)
307
- @errors << "View '#{node.name}' already exists" unless node.if_not_exists
308
- end
309
- node.query.accept(self)
310
- end
311
-
312
- def visit_drop_view(node)
313
- if !@catalog.find_view(node.name) && !node.if_exists
314
- @errors << "View '#{node.name}' does not exist"
315
- end
316
- end
317
-
318
- def visit_create_trigger(node)
319
- unless @catalog.find_table(node.table_name)
320
- @errors << "Table '#{node.table_name}' does not exist"
321
- end
322
- if @catalog.find_trigger(node.name)
323
- @errors << "Trigger '#{node.name}' already exists"
324
- end
325
- end
326
-
327
- def visit_drop_trigger(node)
328
- if !@catalog.find_trigger(node.name) && !node.if_exists
329
- @errors << "Trigger '#{node.name}' does not exist"
330
- end
331
- end
332
-
333
- # --- Expression visitors ---
334
-
335
- def visit_identifier(node)
336
- # Check if identifier is in current scope
337
- if @current_scope.key?(node.name)
338
- # Resolve to column
339
- node.table = @current_table_alias if node.table.nil?
340
- column_info = @current_scope[node.name]
341
- node.instance_variable_set(:@column_info, column_info)
342
- else
343
- @errors << "Column '#{node.name}' does not exist in current scope"
344
- end
345
- end
346
-
347
- def visit_binary_op(node)
348
- node.left.accept(self)
349
- node.right.accept(self)
350
-
351
- # Type compatibility check
352
- left_type = get_expression_type(node.left)
353
- right_type = get_expression_type(node.right)
354
-
355
- if left_type && right_type && !type_compatible?(left_type, right_type, node.operator)
356
- @warnings << "Type mismatch in binary operation: #{left_type} #{node.operator} #{right_type}"
357
- end
358
- end
359
-
360
- def visit_unary_op(node)
361
- node.operand.accept(self)
362
- end
363
-
364
- def visit_between(node)
365
- node.expression.accept(self)
366
- node.low.accept(self)
367
- node.high.accept(self)
368
- end
369
-
370
- def visit_in(node)
371
- node.expression.accept(self)
372
- node.values.each(&:accept)
373
- end
374
-
375
- def visit_is_null(node)
376
- node.expression.accept(self)
377
- end
378
-
379
- def visit_function_call(node)
380
- # Validate function exists
381
- unless function_exists?(node.name)
382
- @errors << "Function '#{node.name}' does not exist"
383
- end
384
-
385
- node.arguments.each(&:accept)
386
- end
387
-
388
- def visit_parameter(node)
389
- # Parameters are valid in any context
390
- end
391
-
392
- def visit_literal(node)
393
- # Literals are always valid
394
- end
395
-
396
- def visit_star(node)
397
- # Star is only valid in SELECT columns
398
- unless @in_select_columns
399
- @errors << "Invalid use of '*' outside SELECT column list"
400
- end
401
- end
402
-
403
- # --- Constraint visitors ---
404
-
405
- def visit_primary_key_constraint(node)
406
- table_info = @current_table
407
- if table_info
408
- node.columns.each do |col_name|
409
- unless table_info.columns.any? { |c| c.name == col_name }
410
- @errors << "Column '#{col_name}' does not exist for PRIMARY KEY constraint"
411
- end
412
- end
413
- end
414
- end
415
-
416
- def visit_foreign_key_constraint(node)
417
- table_info = @current_table
418
- if table_info
419
- node.columns.each do |col_name|
420
- unless table_info.columns.any? { |c| c.name == col_name }
421
- @errors << "Column '#{col_name}' does not exist for FOREIGN KEY constraint"
422
- end
423
- end
424
- end
425
-
426
- # Validate referenced table exists
427
- unless @catalog.find_table(node.reference_table)
428
- @errors << "Referenced table '#{node.reference_table}' does not exist"
429
- end
430
- end
431
-
432
- def visit_unique_constraint(node)
433
- table_info = @current_table
434
- if table_info
435
- node.columns.each do |col_name|
436
- unless table_info.columns.any? { |c| c.name == col_name }
437
- @errors << "Column '#{col_name}' does not exist for UNIQUE constraint"
438
- end
439
- end
440
- end
441
- end
442
-
443
- def visit_check_constraint(node)
444
- node.condition.accept(self)
445
- end
446
-
447
- # --- Helper methods ---
448
-
449
- private
450
-
451
- def bind_table_reference(table_ref)
452
- table_info = @catalog.find_table(table_ref.name)
453
- unless table_info
454
- @errors << "Table '#{table_ref.name}' does not exist"
455
- return nil
456
- end
457
- table_info
458
- end
459
-
460
- def build_scope_from_table(table_info)
461
- scope = {}
462
- if table_info
463
- table_info.columns.each do |col|
464
- scope[col.name] = col
465
- end
466
- end
467
- scope
468
- end
469
-
470
- def get_expression_type(expr)
471
- return nil unless expr.respond_to?(:type)
472
-
473
- if expr.type
474
- expr.type
475
- elsif expr.is_a?(AST::Identifier)
476
- # Try to resolve from scope
477
- @current_scope[expr.name]&.type
478
- elsif expr.is_a?(AST::Literal)
479
- map_literal_type(expr)
480
- else
481
- nil
482
- end
483
- end
484
-
485
- def map_literal_type(literal)
486
- case literal.token_type
487
- when Token::Type::STRING
488
- :text
489
- when Token::Type::NUMBER
490
- literal.value.is_a?(Integer) ? :integer : :float
491
- when Token::Type::TRUE, Token::Type::FALSE
492
- :boolean
493
- when Token::Type::NULL
494
- nil
495
- else
496
- nil
497
- end
498
- end
499
-
500
- def type_compatible?(type1, type2, operator)
501
- # Numeric types are compatible with each other
502
- numeric_types = [:integer, :bigint, :smallint, :float, :decimal]
503
- if numeric_types.include?(type1) && numeric_types.include?(type2)
504
- return true
505
- end
506
-
507
- # Text types are compatible with each other
508
- text_types = [:text, :varchar, :char]
509
- if text_types.include?(type1) && text_types.include?(type2)
510
- return true
511
- end
512
-
513
- # Same type is always compatible
514
- return true if type1 == type2
515
-
516
- # Comparison operators can handle most types
517
- if [Token::Type::EQ, Token::Type::NE, Token::Type::LT,
518
- Token::Type::LTE, Token::Type::GT, Token::Type::GTE].include?(operator)
519
- return true
520
- end
521
-
522
- false
523
- end
524
-
525
- def valid_type?(type)
526
- # Check if type is registered in type system
527
- begin
528
- Types::TypeRegistry.lookup(type)
529
- true
530
- rescue ConfigurationError
531
- false
532
- end
533
- end
534
-
535
- def function_exists?(name)
536
- # Check if function is registered
537
- # Built-in SQL functions are bound without a catalog entry.
538
- common_functions = %w[
539
- COUNT SUM AVG MAX MIN LOWER UPPER LENGTH
540
- SUBSTR CONCAT COALESCE NULLIF
541
- ROW_NUMBER RANK DENSE_RANK
542
- DATE TIME TIMESTAMP EXTRACT
543
- JSON_ARRAY JSON_OBJECT JSON_EXTRACT
544
- ]
545
- common_functions.include?(name.upcase)
546
- end
547
- end
548
- end
549
- end
550
- end
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module SQL
5
+ module Planner
6
+ # Binder - Resolves identifiers and binds AST nodes to catalog objects
7
+ class Binder
8
+ attr_reader :catalog, :errors, :warnings
9
+
10
+ def initialize(catalog)
11
+ @catalog = catalog
12
+ @errors = []
13
+ @warnings = []
14
+ @current_scope = {}
15
+ @current_table = nil
16
+ @current_table_alias = nil
17
+ @in_subquery = false
18
+ end
19
+
20
+ # Bind an AST statement to catalog objects
21
+ def bind(statement)
22
+ @errors = []
23
+ @warnings = []
24
+ statement.accept(self)
25
+ [statement, @errors, @warnings]
26
+ end
27
+
28
+ # --- Visitor methods ---
29
+
30
+ def visit_select(node)
31
+ # Bind FROM clause first
32
+ if node.from
33
+ table_info = bind_table_reference(node.from)
34
+ @current_table = table_info
35
+ @current_table_alias = node.from.alias_name || node.from.name
36
+
37
+ # Build scope from table columns
38
+ @current_scope = build_scope_from_table(table_info)
39
+ end
40
+
41
+ # Bind WHERE clause
42
+ node.where&.accept(self)
43
+
44
+ # Bind ORDER BY
45
+ node.order_by.each do |order_item|
46
+ order_item.accept(self)
47
+ end
48
+
49
+ # Bind LIMIT and OFFSET
50
+ node.limit&.accept(self)
51
+ node.offset&.accept(self)
52
+
53
+ # Bind columns
54
+ node.columns.each do |col|
55
+ col.accept(self)
56
+ end
57
+
58
+ # Clear scope after binding
59
+ @current_scope = {}
60
+ @current_table = nil
61
+ @current_table_alias = nil
62
+ end
63
+
64
+ def visit_insert(node)
65
+ table_info = @catalog.find_table(node.table)
66
+ unless table_info
67
+ @errors << "Table '#{node.table}' does not exist"
68
+ return
69
+ end
70
+
71
+ @current_table = table_info
72
+ @current_table_alias = node.table
73
+ @current_scope = build_scope_from_table(table_info)
74
+
75
+ # Validate columns
76
+ if node.columns.any?
77
+ node.columns.each do |col_name|
78
+ unless table_info.columns.any? { |c| c.name == col_name }
79
+ @errors << "Column '#{col_name}' does not exist in table '#{node.table}'"
80
+ end
81
+ end
82
+ end
83
+
84
+ # Bind values
85
+ node.rows.each do |row|
86
+ row.each do |value|
87
+ value.accept(self)
88
+ end
89
+ end
90
+
91
+ @current_scope = {}
92
+ @current_table = nil
93
+ @current_table_alias = nil
94
+ end
95
+
96
+ def visit_update(node)
97
+ table_info = @catalog.find_table(node.table)
98
+ unless table_info
99
+ @errors << "Table '#{node.table}' does not exist"
100
+ return
101
+ end
102
+
103
+ @current_table = table_info
104
+ @current_table_alias = node.table
105
+ @current_scope = build_scope_from_table(table_info)
106
+
107
+ # Validate assignments
108
+ node.assignments.each do |assignment|
109
+ unless table_info.columns.any? { |c| c.name == assignment.column }
110
+ @errors << "Column '#{assignment.column}' does not exist in table '#{node.table}'"
111
+ end
112
+ assignment.value.accept(self)
113
+ end
114
+
115
+ # Bind WHERE clause
116
+ node.where&.accept(self)
117
+
118
+ @current_scope = {}
119
+ @current_table = nil
120
+ @current_table_alias = nil
121
+ end
122
+
123
+ def visit_delete(node)
124
+ table_info = @catalog.find_table(node.table)
125
+ unless table_info
126
+ @errors << "Table '#{node.table}' does not exist"
127
+ return
128
+ end
129
+
130
+ @current_table = table_info
131
+ @current_table_alias = node.table
132
+ @current_scope = build_scope_from_table(table_info)
133
+
134
+ # Bind WHERE clause
135
+ node.where&.accept(self)
136
+
137
+ @current_scope = {}
138
+ @current_table = nil
139
+ @current_table_alias = nil
140
+ end
141
+
142
+ def visit_create_table(node)
143
+ # Check if table already exists
144
+ if @catalog.find_table(node.name)
145
+ unless node.if_not_exists
146
+ @errors << "Table '#{node.name}' already exists"
147
+ end
148
+ return
149
+ end
150
+
151
+ # Validate column definitions
152
+ node.columns.each do |col|
153
+ # Check for duplicate column names
154
+ if node.columns.count { |c| c.name == col.name } > 1
155
+ @errors << "Duplicate column name '#{col.name}' in table '#{node.name}'"
156
+ end
157
+
158
+ # Validate type
159
+ type_class = col.type_class
160
+ unless valid_type?(type_class)
161
+ @errors << "Invalid data type '#{type_class}' for column '#{col.name}'"
162
+ end
163
+ end
164
+
165
+ # Validate constraints
166
+ node.constraints.each do |constraint|
167
+ constraint.accept(self)
168
+ end
169
+ end
170
+
171
+ def visit_alter_table_add_column(node)
172
+ table_info = @catalog.find_table(node.table_name)
173
+ unless table_info
174
+ @errors << "Table '#{node.table_name}' does not exist"
175
+ return
176
+ end
177
+
178
+ # Check if column already exists
179
+ if table_info.columns.any? { |c| c.name == node.column_name }
180
+ @errors << "Column '#{node.column_name}' already exists in table '#{node.table_name}'"
181
+ end
182
+
183
+ # Validate type
184
+ type_class = node.column_type.is_a?(Hash) ? node.column_type[:type] : node.column_type
185
+ unless valid_type?(type_class)
186
+ @errors << "Invalid data type '#{type_class}' for column '#{node.column_name}'"
187
+ end
188
+ end
189
+
190
+ def visit_alter_table_drop_column(node)
191
+ table_info = @catalog.find_table(node.table_name)
192
+ unless table_info
193
+ @errors << "Table '#{node.table_name}' does not exist"
194
+ return
195
+ end
196
+
197
+ unless table_info.columns.any? { |c| c.name == node.column_name }
198
+ @errors << "Column '#{node.column_name}' does not exist in table '#{node.table_name}'"
199
+ end
200
+ end
201
+
202
+ def visit_alter_table_add_constraint(node)
203
+ table_info = @catalog.find_table(node.table_name)
204
+ unless table_info
205
+ @errors << "Table '#{node.table_name}' does not exist"
206
+ return
207
+ end
208
+ @current_table = table_info
209
+ node.constraint.accept(self)
210
+ @current_table = nil
211
+ end
212
+
213
+ def visit_alter_table_drop_constraint(node)
214
+ table_info = @catalog.find_table(node.table_name)
215
+ unless table_info
216
+ @errors << "Table '#{node.table_name}' does not exist"
217
+ nil
218
+ end
219
+ # Constraint metadata is persisted by the storage engine and may not
220
+ # yet be hydrated into the catalog after a restart. The executor is
221
+ # authoritative for the existence check.
222
+ end
223
+
224
+ def visit_drop_table(node)
225
+ unless @catalog.find_table(node.name)
226
+ unless node.if_exists
227
+ @errors << "Table '#{node.name}' does not exist"
228
+ end
229
+ end
230
+ end
231
+
232
+ def visit_create_index(node)
233
+ table_info = @catalog.find_table(node.table_name)
234
+ unless table_info
235
+ @errors << "Table '#{node.table_name}' does not exist"
236
+ return
237
+ end
238
+
239
+ # Validate columns
240
+ node.columns.each do |col_name|
241
+ unless table_info.columns.any? { |c| c.name == col_name }
242
+ @errors << "Column '#{col_name}' does not exist in table '#{node.table_name}'"
243
+ end
244
+ end
245
+
246
+ # Check if index already exists
247
+ if table_info.indexes.any? { |idx| idx.name == node.name }
248
+ unless node.if_not_exists
249
+ @errors << "Index '#{node.name}' already exists"
250
+ end
251
+ end
252
+ end
253
+
254
+ def visit_drop_index(node)
255
+ # Check if index exists (we need to search all tables)
256
+ index_exists = false
257
+ @catalog.tables.each do |table|
258
+ if table.indexes.any? { |idx| idx.name == node.name }
259
+ index_exists = true
260
+ break
261
+ end
262
+ end
263
+
264
+ unless index_exists
265
+ unless node.if_exists
266
+ @errors << "Index '#{node.name}' does not exist"
267
+ end
268
+ end
269
+ end
270
+
271
+ def visit_create_database(node)
272
+ if @catalog.find_database(node.name)
273
+ unless node.if_not_exists
274
+ @errors << "Database '#{node.name}' already exists"
275
+ end
276
+ end
277
+ end
278
+
279
+ def visit_drop_database(node)
280
+ unless @catalog.find_database(node.name)
281
+ unless node.if_exists
282
+ @errors << "Database '#{node.name}' does not exist"
283
+ end
284
+ end
285
+ end
286
+
287
+ def visit_create_schema(node)
288
+ if @catalog.find_schema(node.name)
289
+ @errors << "Schema '#{node.name}' already exists" unless node.if_not_exists
290
+ end
291
+ end
292
+
293
+ def visit_drop_schema(node)
294
+ unless @catalog.find_schema(node.name)
295
+ @errors << "Schema '#{node.name}' does not exist" unless node.if_exists
296
+ end
297
+ end
298
+
299
+ def visit_create_view(node)
300
+ if @catalog.find_view(node.name)
301
+ @errors << "View '#{node.name}' already exists" unless node.if_not_exists
302
+ end
303
+ node.query.accept(self)
304
+ end
305
+
306
+ def visit_drop_view(node)
307
+ if !@catalog.find_view(node.name) && !node.if_exists
308
+ @errors << "View '#{node.name}' does not exist"
309
+ end
310
+ end
311
+
312
+ def visit_create_trigger(node)
313
+ unless @catalog.find_table(node.table_name)
314
+ @errors << "Table '#{node.table_name}' does not exist"
315
+ end
316
+ if @catalog.find_trigger(node.name)
317
+ @errors << "Trigger '#{node.name}' already exists"
318
+ end
319
+ end
320
+
321
+ def visit_drop_trigger(node)
322
+ if !@catalog.find_trigger(node.name) && !node.if_exists
323
+ @errors << "Trigger '#{node.name}' does not exist"
324
+ end
325
+ end
326
+
327
+ # --- Expression visitors ---
328
+
329
+ def visit_identifier(node)
330
+ # Check if identifier is in current scope
331
+ if @current_scope.key?(node.name)
332
+ # Resolve to column
333
+ node.table = @current_table_alias if node.table.nil?
334
+ column_info = @current_scope[node.name]
335
+ node.instance_variable_set(:@column_info, column_info)
336
+ else
337
+ @errors << "Column '#{node.name}' does not exist in current scope"
338
+ end
339
+ end
340
+
341
+ def visit_binary_op(node)
342
+ node.left.accept(self)
343
+ node.right.accept(self)
344
+
345
+ # Type compatibility check
346
+ left_type = get_expression_type(node.left)
347
+ right_type = get_expression_type(node.right)
348
+
349
+ if left_type && right_type && !type_compatible?(left_type, right_type, node.operator)
350
+ @warnings << "Type mismatch in binary operation: #{left_type} #{node.operator} #{right_type}"
351
+ end
352
+ end
353
+
354
+ def visit_unary_op(node)
355
+ node.operand.accept(self)
356
+ end
357
+
358
+ def visit_between(node)
359
+ node.expression.accept(self)
360
+ node.low.accept(self)
361
+ node.high.accept(self)
362
+ end
363
+
364
+ def visit_in(node)
365
+ node.expression.accept(self)
366
+ node.values.each(&:accept)
367
+ end
368
+
369
+ def visit_is_null(node)
370
+ node.expression.accept(self)
371
+ end
372
+
373
+ def visit_function_call(node)
374
+ # Validate function exists
375
+ unless function_exists?(node.name)
376
+ @errors << "Function '#{node.name}' does not exist"
377
+ end
378
+
379
+ node.arguments.each(&:accept)
380
+ end
381
+
382
+ def visit_parameter(node)
383
+ # Parameters are valid in any context
384
+ end
385
+
386
+ def visit_literal(node)
387
+ # Literals are always valid
388
+ end
389
+
390
+ def visit_star(node)
391
+ # Star is only valid in SELECT columns
392
+ unless @in_select_columns
393
+ @errors << "Invalid use of '*' outside SELECT column list"
394
+ end
395
+ end
396
+
397
+ # --- Constraint visitors ---
398
+
399
+ def visit_primary_key_constraint(node)
400
+ table_info = @current_table
401
+ if table_info
402
+ node.columns.each do |col_name|
403
+ unless table_info.columns.any? { |c| c.name == col_name }
404
+ @errors << "Column '#{col_name}' does not exist for PRIMARY KEY constraint"
405
+ end
406
+ end
407
+ end
408
+ end
409
+
410
+ def visit_foreign_key_constraint(node)
411
+ table_info = @current_table
412
+ if table_info
413
+ node.columns.each do |col_name|
414
+ unless table_info.columns.any? { |c| c.name == col_name }
415
+ @errors << "Column '#{col_name}' does not exist for FOREIGN KEY constraint"
416
+ end
417
+ end
418
+ end
419
+
420
+ # Validate referenced table exists
421
+ unless @catalog.find_table(node.reference_table)
422
+ @errors << "Referenced table '#{node.reference_table}' does not exist"
423
+ end
424
+ end
425
+
426
+ def visit_unique_constraint(node)
427
+ table_info = @current_table
428
+ if table_info
429
+ node.columns.each do |col_name|
430
+ unless table_info.columns.any? { |c| c.name == col_name }
431
+ @errors << "Column '#{col_name}' does not exist for UNIQUE constraint"
432
+ end
433
+ end
434
+ end
435
+ end
436
+
437
+ def visit_check_constraint(node)
438
+ node.condition.accept(self)
439
+ end
440
+
441
+ # --- Helper methods ---
442
+
443
+ private
444
+
445
+ def bind_table_reference(table_ref)
446
+ table_info = @catalog.find_table(table_ref.name)
447
+ unless table_info
448
+ @errors << "Table '#{table_ref.name}' does not exist"
449
+ return nil
450
+ end
451
+ table_info
452
+ end
453
+
454
+ def build_scope_from_table(table_info)
455
+ scope = {}
456
+ table_info&.columns&.each do |col|
457
+ scope[col.name] = col
458
+ end
459
+ scope
460
+ end
461
+
462
+ def get_expression_type(expr)
463
+ return nil unless expr.respond_to?(:type)
464
+
465
+ if expr.type
466
+ expr.type
467
+ elsif expr.is_a?(AST::Identifier)
468
+ # Try to resolve from scope
469
+ @current_scope[expr.name]&.type
470
+ elsif expr.is_a?(AST::Literal)
471
+ map_literal_type(expr)
472
+ end
473
+ end
474
+
475
+ def map_literal_type(literal)
476
+ case literal.token_type
477
+ when Token::Type::STRING
478
+ :text
479
+ when Token::Type::NUMBER
480
+ literal.value.is_a?(Integer) ? :integer : :float
481
+ when Token::Type::TRUE, Token::Type::FALSE
482
+ :boolean
483
+ when Token::Type::NULL
484
+ nil
485
+ end
486
+ end
487
+
488
+ def type_compatible?(type1, type2, operator)
489
+ # Numeric types are compatible with each other
490
+ numeric_types = [:integer, :bigint, :smallint, :float, :decimal]
491
+ if numeric_types.include?(type1) && numeric_types.include?(type2)
492
+ return true
493
+ end
494
+
495
+ # Text types are compatible with each other
496
+ text_types = [:text, :varchar, :char]
497
+ if text_types.include?(type1) && text_types.include?(type2)
498
+ return true
499
+ end
500
+
501
+ # Same type is always compatible
502
+ return true if type1 == type2
503
+
504
+ # Comparison operators can handle most types
505
+ if [Token::Type::EQ, Token::Type::NE, Token::Type::LT,
506
+ Token::Type::LTE, Token::Type::GT, Token::Type::GTE].include?(operator)
507
+ return true
508
+ end
509
+
510
+ false
511
+ end
512
+
513
+ def valid_type?(type)
514
+ # Check if type is registered in type system
515
+
516
+ Types::TypeRegistry.lookup(type)
517
+ true
518
+ rescue ConfigurationError
519
+ false
520
+ end
521
+
522
+ def function_exists?(name)
523
+ # Check if function is registered
524
+ # Built-in SQL functions are bound without a catalog entry.
525
+ common_functions = %w[
526
+ COUNT SUM AVG MAX MIN LOWER UPPER LENGTH
527
+ SUBSTR CONCAT COALESCE NULLIF
528
+ ROW_NUMBER RANK DENSE_RANK
529
+ DATE TIME TIMESTAMP EXTRACT
530
+ JSON_ARRAY JSON_OBJECT JSON_EXTRACT
531
+ ]
532
+ common_functions.include?(name.upcase)
533
+ end
534
+ end
535
+ end
536
+ end
537
+ end