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.
- checksums.yaml +4 -4
- data/.github/PULL_REQUEST_TEMPLATE.md +15 -15
- data/.github/workflows/benchmark.yml +26 -26
- data/.github/workflows/compatibility.yml +63 -63
- data/.github/workflows/fuzz.yml +33 -33
- data/.github/workflows/lint.yml +21 -21
- data/.github/workflows/operations.yml +24 -24
- data/.github/workflows/production-validation.yml +111 -111
- data/.github/workflows/release.yml +77 -77
- data/.github/workflows/security.yml +39 -37
- data/.github/workflows/test.yml +26 -26
- data/.github/workflows/workload.yml +58 -58
- data/.gitignore +12 -5
- data/.rubocop.yml +50 -44
- data/.standard.yml +9 -14
- data/ARCHITECTURE.md +21 -21
- data/CHANGELOG.md +49 -27
- data/CODE_OF_CONDUCT.md +13 -13
- data/CONTRIBUTING.md +29 -29
- data/GOVERNANCE.md +16 -16
- data/Gemfile +18 -17
- data/Gemfile.lock +125 -71
- data/README.md +249 -162
- data/ROADMAP.md +27 -27
- data/Rakefile +71 -71
- data/SECURITY.md +54 -54
- data/SUPPORT.md +14 -14
- data/adapters/activerecord/Gemfile +11 -11
- data/adapters/activerecord/lib/active_record/connection_adapters/rubydb_adapter.rb +877 -880
- data/adapters/activerecord/rubydb-activerecord.gemspec +21 -21
- data/adapters/activerecord/spec/rubydb_adapter_integration_spec.rb +155 -143
- data/adapters/ruby/README.md +18 -18
- data/adapters/sequel/README.md +11 -11
- data/config/monitoring/prometheus-alerts.yml +39 -39
- data/config/production.yml +36 -36
- data/docs/README.md +75 -71
- data/docs/architecture/concurrency.md +14 -14
- data/docs/architecture/current-state.md +125 -125
- data/docs/architecture/execution-engine.md +25 -25
- data/docs/architecture/indexes.md +19 -19
- data/docs/architecture/mvcc.md +19 -19
- data/docs/architecture/overview.md +13 -13
- data/docs/architecture/pages.md +11 -11
- data/docs/architecture/production-roadmap.md +82 -82
- data/docs/architecture/query-planner.md +20 -20
- data/docs/architecture/recovery.md +18 -18
- data/docs/architecture/sql-engine.md +12 -12
- data/docs/architecture/storage-engine.md +14 -14
- data/docs/architecture/transactions.md +10 -10
- data/docs/architecture/wal.md +28 -28
- data/docs/cli-cheatsheet.md +98 -98
- data/docs/cli.md +275 -275
- data/docs/contributing/architecture.md +9 -9
- data/docs/contributing/benchmarking.md +14 -14
- data/docs/contributing/development.md +16 -16
- data/docs/contributing/release-process.md +49 -49
- data/docs/contributing/testing.md +16 -16
- data/docs/debugging.md +229 -229
- data/docs/developer/branching.md +10 -10
- data/docs/developer/database-diff.md +10 -10
- data/docs/developer/local-development.md +17 -17
- data/docs/developer/snapshots.md +9 -9
- data/docs/developer/temporal-data.md +10 -10
- data/docs/developer-guide.md +297 -297
- data/docs/getting-started/first-database.md +16 -16
- data/docs/getting-started/first-query.md +13 -13
- data/docs/getting-started/installation.md +19 -19
- data/docs/getting-started/local-to-production.md +300 -300
- data/docs/getting-started/quickstart.md +17 -17
- data/docs/getting-started/rails.md +16 -16
- data/docs/hardening_backlog.md +93 -93
- data/docs/lessons-learned.md +112 -112
- data/docs/operations/backups.md +33 -33
- data/docs/operations/disaster-recovery.md +31 -31
- data/docs/operations/failover.md +30 -30
- data/docs/operations/monitoring.md +25 -25
- data/docs/operations/production-guide.md +295 -295
- data/docs/operations/production-runbook.md +45 -45
- data/docs/operations/replication.md +33 -33
- data/docs/operations/restore.md +6 -6
- data/docs/operations/runbook.md +34 -34
- data/docs/operations/upgrades.md +14 -14
- data/docs/operations/workload-testing.md +17 -17
- data/docs/production-readiness.md +118 -118
- data/docs/production_validation.md +150 -150
- data/docs/rails/active-record.md +11 -11
- data/docs/rails/compatibility-guide.md +90 -90
- data/docs/rails/database-yml.md +92 -92
- data/docs/rails/installation.md +17 -17
- data/docs/rails/migrations.md +17 -17
- data/docs/rails/production.md +82 -82
- data/docs/rails/troubleshooting.md +18 -18
- data/docs/release.md +25 -25
- data/docs/server/architecture.md +10 -10
- data/docs/server/authentication.md +10 -10
- data/docs/server/configuration.md +16 -16
- data/docs/server/connection-pooling.md +10 -10
- data/docs/server/deployment.md +10 -10
- data/docs/server/protocol.md +12 -12
- data/docs/sql/compatibility-guide.md +82 -82
- data/docs/sql/compatibility.md +39 -39
- data/docs/sql/data-types.md +10 -10
- data/docs/sql/functions.md +9 -9
- data/docs/sql/joins.md +9 -9
- data/docs/sql/operators.md +9 -9
- data/docs/sql/sqlite-compatibility.md +21 -21
- data/docs/sql/syntax.md +10 -10
- data/docs/sql/transactions.md +10 -10
- data/docs/troubleshooting.md +244 -244
- data/lessons/01-foundations.md +73 -0
- data/lessons/02-local-development.md +121 -0
- data/lessons/03-embedded-rubydb.md +99 -0
- data/lessons/04-rails-complex-apps.md +138 -0
- data/lessons/05-rubydb-production-server.md +237 -0
- data/lessons/06-postgresql-massive-apps.md +96 -0
- data/lessons/07-hybrid-microservices.md +94 -0
- data/lessons/08-migrations-backups-recovery.md +86 -0
- data/lessons/09-observability-security-scale.md +87 -0
- data/lessons/10-release-readiness.md +125 -0
- data/lib/rubydb/backup/archive.rb +332 -334
- data/lib/rubydb/backup/backup.rb +400 -401
- data/lib/rubydb/backup/incremental.rb +349 -353
- data/lib/rubydb/backup/restore.rb +289 -290
- data/lib/rubydb/backup/snapshot.rb +265 -267
- data/lib/rubydb/backup/verification.rb +276 -279
- data/lib/rubydb/branching/branch.rb +181 -181
- data/lib/rubydb/branching/branch_manager.rb +307 -311
- data/lib/rubydb/branching/branch_metadata.rb +140 -140
- data/lib/rubydb/branching/checkout.rb +165 -166
- data/lib/rubydb/branching/copy_on_write.rb +272 -272
- data/lib/rubydb/branching/diff.rb +137 -138
- data/lib/rubydb/branching/merge.rb +282 -285
- data/lib/rubydb/build_info.rb +15 -15
- data/lib/rubydb/catalog/catalog.rb +391 -391
- data/lib/rubydb/catalog/column.rb +112 -112
- data/lib/rubydb/catalog/constraint.rb +180 -180
- data/lib/rubydb/catalog/database.rb +184 -184
- data/lib/rubydb/catalog/index.rb +97 -97
- data/lib/rubydb/catalog/schema.rb +103 -103
- data/lib/rubydb/catalog/sequence.rb +90 -90
- data/lib/rubydb/catalog/system_catalog.rb +698 -698
- data/lib/rubydb/catalog/table.rb +178 -178
- data/lib/rubydb/catalog/trigger.rb +102 -102
- data/lib/rubydb/catalog/view.rb +66 -66
- data/lib/rubydb/cli/application.rb +163 -163
- data/lib/rubydb/cli/commands/backup.rb +80 -81
- data/lib/rubydb/cli/commands/branch.rb +72 -72
- data/lib/rubydb/cli/commands/checkout.rb +54 -54
- data/lib/rubydb/cli/commands/create.rb +58 -58
- data/lib/rubydb/cli/commands/diff.rb +76 -77
- data/lib/rubydb/cli/commands/doctor.rb +74 -74
- data/lib/rubydb/cli/commands/drop.rb +57 -57
- data/lib/rubydb/cli/commands/init.rb +101 -102
- data/lib/rubydb/cli/commands/inspect.rb +95 -95
- data/lib/rubydb/cli/commands/merge.rb +63 -63
- data/lib/rubydb/cli/commands/migrate.rb +62 -62
- data/lib/rubydb/cli/commands/restart.rb +42 -39
- data/lib/rubydb/cli/commands/restore.rb +121 -121
- data/lib/rubydb/cli/commands/shell.rb +365 -365
- data/lib/rubydb/cli/commands/snapshot.rb +79 -79
- data/lib/rubydb/cli/commands/start.rb +88 -82
- data/lib/rubydb/cli/commands/status.rb +96 -92
- data/lib/rubydb/cli/commands/stop.rb +47 -47
- data/lib/rubydb/cli/commands/vacuum.rb +58 -58
- data/lib/rubydb/cli/formatter.rb +221 -221
- data/lib/rubydb/cli/output.rb +168 -168
- data/lib/rubydb/client/client.rb +302 -304
- data/lib/rubydb/client/connection.rb +414 -415
- data/lib/rubydb/client/connection_pool.rb +168 -168
- data/lib/rubydb/client/connection_url.rb +96 -96
- data/lib/rubydb/client/prepared_statement.rb +60 -60
- data/lib/rubydb/client/result.rb +123 -123
- data/lib/rubydb/client/statement.rb +52 -52
- data/lib/rubydb/client/transaction.rb +130 -130
- data/lib/rubydb/concurrency/concurrency.rb +19 -19
- data/lib/rubydb/concurrency/deadlock_detector.rb +148 -150
- data/lib/rubydb/concurrency/latch.rb +101 -101
- data/lib/rubydb/concurrency/lock_graph.rb +163 -165
- data/lib/rubydb/concurrency/mutex.rb +181 -183
- data/lib/rubydb/concurrency/rw_lock.rb +180 -180
- data/lib/rubydb/concurrency/scheduler.rb +248 -250
- data/lib/rubydb/concurrency/worker_pool.rb +145 -143
- data/lib/rubydb/configuration/config.rb +170 -170
- data/lib/rubydb/configuration/defaults.rb +179 -179
- data/lib/rubydb/configuration/environment.rb +152 -152
- data/lib/rubydb/configuration/parser.rb +185 -185
- data/lib/rubydb/configuration/validation.rb +221 -221
- data/lib/rubydb/constants.rb +74 -74
- data/lib/rubydb/constraints/check.rb +181 -181
- data/lib/rubydb/constraints/constraint.rb +101 -101
- data/lib/rubydb/constraints/foreign_key.rb +130 -130
- data/lib/rubydb/constraints/not_null.rb +64 -64
- data/lib/rubydb/constraints/primary_key.rb +99 -99
- data/lib/rubydb/constraints/unique.rb +106 -108
- data/lib/rubydb/constraints/validator.rb +349 -350
- data/lib/rubydb/errors/authentication_error.rb +10 -10
- data/lib/rubydb/errors/authorization_error.rb +23 -23
- data/lib/rubydb/errors/client_error.rb +10 -10
- data/lib/rubydb/errors/configuration_error.rb +10 -10
- data/lib/rubydb/errors/connection_error.rb +10 -10
- data/lib/rubydb/errors/constraint_error.rb +23 -23
- data/lib/rubydb/errors/corruption_error.rb +10 -10
- data/lib/rubydb/errors/database_error.rb +10 -10
- data/lib/rubydb/errors/error.rb +20 -20
- data/lib/rubydb/errors/execution_error.rb +10 -10
- data/lib/rubydb/errors/parser_error.rb +10 -10
- data/lib/rubydb/errors/recovery_error.rb +10 -10
- data/lib/rubydb/errors/replication_error.rb +10 -10
- data/lib/rubydb/errors/server_error.rb +6 -6
- data/lib/rubydb/errors/storage_error.rb +10 -10
- data/lib/rubydb/errors/transaction_error.rb +10 -10
- data/lib/rubydb/execution/aggregate_executor.rb +134 -138
- data/lib/rubydb/execution/delete_executor.rb +110 -112
- data/lib/rubydb/execution/distinct_executor.rb +131 -135
- data/lib/rubydb/execution/executor.rb +1195 -1192
- data/lib/rubydb/execution/expression.rb +191 -193
- data/lib/rubydb/execution/index_scan.rb +142 -142
- data/lib/rubydb/execution/insert_executor.rb +215 -217
- data/lib/rubydb/execution/join_executor.rb +243 -249
- data/lib/rubydb/execution/limit_executor.rb +83 -85
- data/lib/rubydb/execution/optimizer.rb +227 -215
- data/lib/rubydb/execution/plan.rb +355 -353
- data/lib/rubydb/execution/planner.rb +542 -536
- data/lib/rubydb/execution/predicate.rb +235 -235
- data/lib/rubydb/execution/scan.rb +49 -49
- data/lib/rubydb/execution/sequential_scan.rb +63 -63
- data/lib/rubydb/execution/sort_executor.rb +180 -185
- data/lib/rubydb/execution/update_executor.rb +160 -162
- data/lib/rubydb/functions/aggregate.rb +70 -70
- data/lib/rubydb/functions/date_functions.rb +274 -278
- data/lib/rubydb/functions/function.rb +85 -85
- data/lib/rubydb/functions/json_functions.rb +231 -215
- data/lib/rubydb/functions/numeric_functions.rb +346 -346
- data/lib/rubydb/functions/scalar.rb +52 -52
- data/lib/rubydb/functions/string_functions.rb +383 -383
- data/lib/rubydb/functions/system_functions.rb +258 -246
- data/lib/rubydb/history/as_of.rb +238 -238
- data/lib/rubydb/history/change.rb +105 -105
- data/lib/rubydb/history/history.rb +131 -131
- data/lib/rubydb/history/history_manager.rb +228 -229
- data/lib/rubydb/history/temporal_query.rb +202 -202
- data/lib/rubydb/history/timeline.rb +144 -144
- data/lib/rubydb/indexes/btree.rb +186 -186
- data/lib/rubydb/indexes/btree_cursor.rb +258 -258
- data/lib/rubydb/indexes/btree_node.rb +384 -385
- data/lib/rubydb/indexes/hash_index.rb +150 -150
- data/lib/rubydb/indexes/index.rb +71 -71
- data/lib/rubydb/indexes/index_manager.rb +408 -406
- data/lib/rubydb/indexes/index_scan.rb +466 -470
- data/lib/rubydb/migrations/migration.rb +253 -254
- data/lib/rubydb/migrations/migration_lock.rb +146 -146
- data/lib/rubydb/migrations/migration_manager.rb +187 -176
- data/lib/rubydb/migrations/migration_version.rb +71 -71
- data/lib/rubydb/migrations/schema_diff.rb +211 -211
- data/lib/rubydb/migrations/schema_version.rb +64 -64
- data/lib/rubydb/monitoring/events.rb +155 -160
- data/lib/rubydb/monitoring/health.rb +216 -222
- data/lib/rubydb/monitoring/logger.rb +188 -193
- data/lib/rubydb/monitoring/metrics.rb +363 -359
- data/lib/rubydb/monitoring/performance.rb +176 -176
- data/lib/rubydb/monitoring/statistics.rb +168 -170
- data/lib/rubydb/mvcc/garbage_collector.rb +199 -199
- data/lib/rubydb/mvcc/mvcc.rb +16 -16
- data/lib/rubydb/mvcc/snapshot.rb +146 -147
- data/lib/rubydb/mvcc/vacuum.rb +180 -180
- data/lib/rubydb/mvcc/version.rb +106 -106
- data/lib/rubydb/mvcc/version_store.rb +396 -398
- data/lib/rubydb/mvcc/visibility.rb +107 -109
- data/lib/rubydb/protocol/capabilities.rb +125 -125
- data/lib/rubydb/protocol/decoder.rb +142 -145
- data/lib/rubydb/protocol/encoder.rb +131 -136
- data/lib/rubydb/protocol/handshake.rb +306 -305
- data/lib/rubydb/protocol/message.rb +121 -121
- data/lib/rubydb/protocol/parameter_binder.rb +101 -0
- data/lib/rubydb/protocol/protocol.rb +276 -277
- data/lib/rubydb/protocol/version.rb +54 -54
- data/lib/rubydb/rails/adapter.rb +245 -239
- data/lib/rubydb/rails/connection.rb +312 -314
- data/lib/rubydb/rails/database_statements.rb +122 -122
- data/lib/rubydb/rails/migration.rb +131 -131
- data/lib/rubydb/rails/quoting.rb +109 -109
- data/lib/rubydb/rails/result.rb +123 -120
- data/lib/rubydb/rails/schema_statements.rb +339 -339
- data/lib/rubydb/rails/transaction.rb +105 -105
- data/lib/rubydb/rails/type.rb +126 -126
- data/lib/rubydb/recovery/checkpoint.rb +261 -257
- data/lib/rubydb/recovery/consistency.rb +457 -467
- data/lib/rubydb/recovery/corruption_detector.rb +5 -5
- data/lib/rubydb/recovery/crash_recovery.rb +381 -387
- data/lib/rubydb/recovery/recovery_manager.rb +204 -206
- data/lib/rubydb/recovery/redo.rb +235 -237
- data/lib/rubydb/recovery/undo.rb +204 -206
- data/lib/rubydb/replication/failover.rb +5 -5
- data/lib/rubydb/replication/fencing.rb +63 -63
- data/lib/rubydb/replication/primary.rb +461 -450
- data/lib/rubydb/replication/replica.rb +382 -384
- data/lib/rubydb/replication/replication_log.rb +194 -200
- data/lib/rubydb/replication/replication_manager.rb +307 -308
- data/lib/rubydb/replication/replication_slot.rb +293 -295
- data/lib/rubydb/replication/replication_stream.rb +198 -201
- data/lib/rubydb/rubydb.rb +564 -560
- data/lib/rubydb/security/access_control.rb +252 -254
- data/lib/rubydb/security/audit_log.rb +209 -213
- data/lib/rubydb/security/authentication.rb +302 -302
- data/lib/rubydb/security/authorization.rb +282 -282
- data/lib/rubydb/security/credentials.rb +192 -196
- data/lib/rubydb/security/password.rb +205 -215
- data/lib/rubydb/security/permissions.rb +74 -74
- data/lib/rubydb/security/role.rb +99 -101
- data/lib/rubydb/security/user.rb +86 -86
- data/lib/rubydb/server/connection.rb +383 -366
- data/lib/rubydb/server/connection_pool.rb +193 -193
- data/lib/rubydb/server/lifecycle.rb +227 -228
- data/lib/rubydb/server/listener.rb +139 -136
- data/lib/rubydb/server/request_handler.rb +277 -276
- data/lib/rubydb/server/server.rb +363 -364
- data/lib/rubydb/server/session.rb +371 -369
- data/lib/rubydb/server/worker.rb +206 -210
- data/lib/rubydb/server/worker_pool.rb +168 -168
- data/lib/rubydb/sql/ast/alter_table.rb +169 -169
- data/lib/rubydb/sql/ast/begin_transaction.rb +47 -47
- data/lib/rubydb/sql/ast/commit.rb +37 -37
- data/lib/rubydb/sql/ast/constraint.rb +92 -83
- data/lib/rubydb/sql/ast/create_database.rb +41 -41
- data/lib/rubydb/sql/ast/create_index.rb +61 -61
- data/lib/rubydb/sql/ast/create_schema.rb +52 -52
- data/lib/rubydb/sql/ast/create_table.rb +187 -187
- data/lib/rubydb/sql/ast/delete.rb +54 -54
- data/lib/rubydb/sql/ast/drop_database.rb +41 -41
- data/lib/rubydb/sql/ast/drop_index.rb +41 -41
- data/lib/rubydb/sql/ast/drop_schema.rb +49 -49
- data/lib/rubydb/sql/ast/drop_table.rb +49 -49
- data/lib/rubydb/sql/ast/explain.rb +64 -64
- data/lib/rubydb/sql/ast/expression.rb +617 -604
- data/lib/rubydb/sql/ast/insert.rb +66 -66
- data/lib/rubydb/sql/ast/node.rb +42 -42
- data/lib/rubydb/sql/ast/rollback.rb +63 -63
- data/lib/rubydb/sql/ast/savepoint.rb +59 -59
- data/lib/rubydb/sql/ast/select.rb +88 -88
- data/lib/rubydb/sql/ast/set_operation.rb +22 -20
- data/lib/rubydb/sql/ast/trigger.rb +35 -29
- data/lib/rubydb/sql/ast/update.rb +88 -88
- data/lib/rubydb/sql/ast/vacuum.rb +19 -19
- data/lib/rubydb/sql/ast/view.rb +38 -32
- data/lib/rubydb/sql/ast/with.rb +32 -32
- data/lib/rubydb/sql/grammar.rb +86 -86
- data/lib/rubydb/sql/keywords.rb +156 -156
- data/lib/rubydb/sql/lexer.rb +209 -214
- data/lib/rubydb/sql/operators.rb +100 -100
- data/lib/rubydb/sql/parser.rb +1167 -1170
- data/lib/rubydb/sql/planner/analyzer.rb +283 -302
- data/lib/rubydb/sql/planner/binder.rb +537 -550
- data/lib/rubydb/sql/planner/type_checker.rb +427 -431
- data/lib/rubydb/sql/token.rb +210 -210
- data/lib/rubydb/storage/buffer_frame.rb +44 -44
- data/lib/rubydb/storage/buffer_pool.rb +155 -155
- data/lib/rubydb/storage/database_lock.rb +74 -74
- data/lib/rubydb/storage/deserializer.rb +332 -342
- data/lib/rubydb/storage/engine.rb +2347 -2330
- data/lib/rubydb/storage/file_manager.rb +191 -187
- data/lib/rubydb/storage/free_space_map.rb +79 -81
- data/lib/rubydb/storage/page.rb +92 -94
- data/lib/rubydb/storage/page_allocator.rb +852 -855
- data/lib/rubydb/storage/page_header.rb +63 -67
- data/lib/rubydb/storage/page_manager.rb +127 -131
- data/lib/rubydb/storage/record.rb +58 -58
- data/lib/rubydb/storage/row.rb +78 -78
- data/lib/rubydb/storage/serializer.rb +51 -51
- data/lib/rubydb/storage/storage_layout.rb +151 -151
- data/lib/rubydb/storage/storage_manager.rb +114 -114
- data/lib/rubydb/storage/tuple.rb +458 -461
- data/lib/rubydb/storage/visibility_map.rb +964 -973
- data/lib/rubydb/transactions/commit_manager.rb +219 -220
- data/lib/rubydb/transactions/isolation.rb +98 -98
- data/lib/rubydb/transactions/lock.rb +76 -76
- data/lib/rubydb/transactions/lock_manager.rb +359 -362
- data/lib/rubydb/transactions/savepoint.rb +142 -143
- data/lib/rubydb/transactions/transaction.rb +214 -215
- data/lib/rubydb/transactions/transaction_id.rb +84 -84
- data/lib/rubydb/transactions/transaction_log.rb +256 -257
- data/lib/rubydb/transactions/transaction_manager.rb +434 -435
- data/lib/rubydb/types/bigint.rb +36 -36
- data/lib/rubydb/types/blob.rb +37 -37
- data/lib/rubydb/types/boolean.rb +34 -34
- data/lib/rubydb/types/date.rb +39 -39
- data/lib/rubydb/types/decimal.rb +48 -48
- data/lib/rubydb/types/float.rb +34 -34
- data/lib/rubydb/types/integer.rb +36 -36
- data/lib/rubydb/types/json.rb +41 -41
- data/lib/rubydb/types/null.rb +34 -34
- data/lib/rubydb/types/smallint.rb +36 -36
- data/lib/rubydb/types/text.rb +37 -37
- data/lib/rubydb/types/time.rb +46 -46
- data/lib/rubydb/types/timestamp.rb +39 -39
- data/lib/rubydb/types/type.rb +119 -119
- data/lib/rubydb/types/uuid.rb +47 -47
- data/lib/rubydb/types/varchar.rb +37 -37
- data/lib/rubydb/version.rb +32 -32
- data/lib/rubydb/wal/archive.rb +190 -193
- data/lib/rubydb/wal/checkpoint.rb +181 -183
- data/lib/rubydb/wal/lsn.rb +94 -94
- data/lib/rubydb/wal/reader.rb +259 -260
- data/lib/rubydb/wal/record.rb +105 -105
- data/lib/rubydb/wal/segment.rb +193 -193
- data/lib/rubydb/wal/wal.rb +480 -452
- data/lib/rubydb/wal/writer.rb +236 -236
- data/lib/rubydb.rb +7 -7
- data/packaging/docker/docker-compose.failover.yml +43 -43
- data/packaging/homebrew/rubydb.rb +19 -19
- data/rubydb.gemspec +60 -57
- data/scripts/benchmark +7 -7
- data/scripts/durability_drill +37 -37
- data/scripts/fuzz +63 -63
- data/scripts/release +45 -40
- data/scripts/release_check +43 -43
- data/scripts/replication_failover_drill +268 -250
- data/scripts/replication_network_failover_drill +287 -255
- data/scripts/restore_drill +45 -45
- data/scripts/security +45 -0
- metadata +54 -1
|
@@ -1,1192 +1,1195 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
module
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@
|
|
14
|
-
@
|
|
15
|
-
@
|
|
16
|
-
@
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@
|
|
28
|
-
@
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
@
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
@stats[:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
when :
|
|
123
|
-
when :
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
iterations
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
next_rows =
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
@stats[:
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
#
|
|
181
|
-
#
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
#
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
rows =
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
# Apply
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
end
|
|
407
|
-
|
|
408
|
-
def
|
|
409
|
-
@engine.catalog.
|
|
410
|
-
{
|
|
411
|
-
end
|
|
412
|
-
|
|
413
|
-
def
|
|
414
|
-
@engine.catalog.
|
|
415
|
-
{
|
|
416
|
-
end
|
|
417
|
-
|
|
418
|
-
def
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
{
|
|
443
|
-
end
|
|
444
|
-
|
|
445
|
-
def
|
|
446
|
-
@engine.
|
|
447
|
-
{
|
|
448
|
-
end
|
|
449
|
-
|
|
450
|
-
def
|
|
451
|
-
@engine.
|
|
452
|
-
{
|
|
453
|
-
end
|
|
454
|
-
|
|
455
|
-
def
|
|
456
|
-
@engine.
|
|
457
|
-
{
|
|
458
|
-
end
|
|
459
|
-
|
|
460
|
-
def
|
|
461
|
-
@engine.
|
|
462
|
-
{
|
|
463
|
-
end
|
|
464
|
-
|
|
465
|
-
def
|
|
466
|
-
|
|
467
|
-
table_name
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
{
|
|
500
|
-
row_count: 0,
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
@engine.
|
|
508
|
-
|
|
509
|
-
{
|
|
510
|
-
row_count: 0,
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
end
|
|
533
|
-
|
|
534
|
-
def
|
|
535
|
-
@engine.
|
|
536
|
-
{
|
|
537
|
-
end
|
|
538
|
-
|
|
539
|
-
def
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
result << merge_join_rows(
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
when Predicate::
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
predicate.
|
|
663
|
-
when Predicate::
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
when Expression::
|
|
721
|
-
expr.
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
when SQL::Token::Type::
|
|
779
|
-
when SQL::Token::Type::
|
|
780
|
-
when SQL::Token::Type::
|
|
781
|
-
when SQL::Token::Type::
|
|
782
|
-
when SQL::Token::Type::
|
|
783
|
-
when SQL::Token::Type::
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
return
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
when "
|
|
854
|
-
args.
|
|
855
|
-
when "
|
|
856
|
-
args.
|
|
857
|
-
when "
|
|
858
|
-
args.
|
|
859
|
-
when "
|
|
860
|
-
|
|
861
|
-
when "
|
|
862
|
-
|
|
863
|
-
when "
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
result_row[
|
|
943
|
-
end
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
end
|
|
1000
|
-
|
|
1001
|
-
def
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
left
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
return
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
when "
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
when :
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
when "
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
when "
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
return
|
|
1154
|
-
|
|
1155
|
-
expression.to_s
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
|
|
5
|
+
module RubyDB
|
|
6
|
+
module Execution
|
|
7
|
+
# Executor - Executes query plans and returns results
|
|
8
|
+
class Executor
|
|
9
|
+
attr_reader :engine, :stats
|
|
10
|
+
|
|
11
|
+
def initialize(engine, cte_rows: {}, deadline_at: nil, cancellation: nil)
|
|
12
|
+
@engine = engine
|
|
13
|
+
@cte_rows = cte_rows
|
|
14
|
+
@deadline_at = deadline_at && (deadline_at.is_a?(Time) ? deadline_at : Time.parse(deadline_at.to_s))
|
|
15
|
+
@cancellation = cancellation
|
|
16
|
+
@stats = {
|
|
17
|
+
queries_executed: 0,
|
|
18
|
+
rows_returned: 0,
|
|
19
|
+
total_time_ms: 0,
|
|
20
|
+
sequential_scans: 0,
|
|
21
|
+
index_scans: 0,
|
|
22
|
+
joins: 0,
|
|
23
|
+
aggregations: 0,
|
|
24
|
+
sorts: 0
|
|
25
|
+
}
|
|
26
|
+
@lock = Mutex.new
|
|
27
|
+
@current_transaction = nil
|
|
28
|
+
@explain_mode = false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def execute(plan, transaction_id = nil)
|
|
32
|
+
@lock.synchronize do
|
|
33
|
+
check_deadline!
|
|
34
|
+
start_time = Time.now
|
|
35
|
+
@stats[:queries_executed] += 1
|
|
36
|
+
@current_transaction = transaction_id
|
|
37
|
+
|
|
38
|
+
result = case plan
|
|
39
|
+
when Plan::With
|
|
40
|
+
execute_with(plan)
|
|
41
|
+
when Plan::Select
|
|
42
|
+
execute_select(plan)
|
|
43
|
+
when Plan::SetOperation
|
|
44
|
+
execute_set_operation(plan)
|
|
45
|
+
when Plan::Insert
|
|
46
|
+
execute_insert(plan)
|
|
47
|
+
when Plan::Update
|
|
48
|
+
execute_update(plan)
|
|
49
|
+
when Plan::Delete
|
|
50
|
+
execute_delete(plan)
|
|
51
|
+
when Plan::CreateTable
|
|
52
|
+
execute_create_table(plan)
|
|
53
|
+
when Plan::DropTable
|
|
54
|
+
execute_drop_table(plan)
|
|
55
|
+
when Plan::CreateDatabase
|
|
56
|
+
execute_create_database(plan)
|
|
57
|
+
when Plan::DropDatabase
|
|
58
|
+
execute_drop_database(plan)
|
|
59
|
+
when Plan::CreateSchema
|
|
60
|
+
execute_create_schema(plan)
|
|
61
|
+
when Plan::DropSchema
|
|
62
|
+
execute_drop_schema(plan)
|
|
63
|
+
when Plan::CreateView
|
|
64
|
+
execute_create_view(plan)
|
|
65
|
+
when Plan::DropView
|
|
66
|
+
execute_drop_view(plan)
|
|
67
|
+
when Plan::CreateTrigger
|
|
68
|
+
execute_create_trigger(plan)
|
|
69
|
+
when Plan::DropTrigger
|
|
70
|
+
execute_drop_trigger(plan)
|
|
71
|
+
when Plan::Vacuum
|
|
72
|
+
execute_vacuum(plan)
|
|
73
|
+
when Plan::AlterTableAddColumn
|
|
74
|
+
execute_alter_table_add_column(plan)
|
|
75
|
+
when Plan::AlterTableDropColumn
|
|
76
|
+
execute_alter_table_drop_column(plan)
|
|
77
|
+
when Plan::AlterTableAddConstraint
|
|
78
|
+
execute_alter_table_add_constraint(plan)
|
|
79
|
+
when Plan::AlterTableDropConstraint
|
|
80
|
+
execute_alter_table_drop_constraint(plan)
|
|
81
|
+
when Plan::CreateIndex
|
|
82
|
+
execute_create_index(plan)
|
|
83
|
+
when Plan::DropIndex
|
|
84
|
+
execute_drop_index(plan)
|
|
85
|
+
when Plan::BeginTransaction
|
|
86
|
+
execute_begin_transaction(plan)
|
|
87
|
+
when Plan::Commit
|
|
88
|
+
execute_commit(plan)
|
|
89
|
+
when Plan::Rollback
|
|
90
|
+
execute_rollback(plan)
|
|
91
|
+
when Plan::Savepoint
|
|
92
|
+
execute_savepoint(plan)
|
|
93
|
+
when Plan::RollbackToSavepoint
|
|
94
|
+
execute_rollback_to_savepoint(plan)
|
|
95
|
+
when Plan::ReleaseSavepoint
|
|
96
|
+
execute_release_savepoint(plan)
|
|
97
|
+
when Plan::Explain
|
|
98
|
+
execute_explain(plan)
|
|
99
|
+
else
|
|
100
|
+
raise ExecutionError, "Unknown plan type: #{plan.class}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
elapsed_ms = ((Time.now - start_time) * 1000).round(2)
|
|
104
|
+
@stats[:total_time_ms] += elapsed_ms
|
|
105
|
+
@stats[:rows_returned] += result[:row_count] if result && result[:row_count]
|
|
106
|
+
|
|
107
|
+
result
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def execute_set_operation(plan)
|
|
112
|
+
left = child_executor(@cte_rows).execute(plan.left_plan, @current_transaction)[:rows]
|
|
113
|
+
right = child_executor(@cte_rows).execute(plan.right_plan, @current_transaction)[:rows]
|
|
114
|
+
left_width = left.first&.size || plan.left_plan.columns.size
|
|
115
|
+
right_width = right.first&.size || plan.right_plan.columns.size
|
|
116
|
+
if left_width != right_width
|
|
117
|
+
raise ExecutionError, "Set operation requires the same number of columns on both sides"
|
|
118
|
+
end
|
|
119
|
+
key = ->(row) { row.to_a }
|
|
120
|
+
rows = case plan.operator
|
|
121
|
+
when :union then plan.all ? left + right : (left + right).uniq { |row| key.call(row) }
|
|
122
|
+
when :intersect then left.select { |row| right.any? { |other| key.call(other) == key.call(row) } }.uniq { |row| key.call(row) }
|
|
123
|
+
when :except then left.reject { |row| right.any? { |other| key.call(other) == key.call(row) } }.uniq { |row| key.call(row) }
|
|
124
|
+
else raise ExecutionError, "Unsupported set operation: #{plan.operator}"
|
|
125
|
+
end
|
|
126
|
+
{rows: rows, row_count: rows.size, column_names: rows.first&.keys || []}
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def execute_with(plan)
|
|
130
|
+
available_ctes = @cte_rows.dup
|
|
131
|
+
plan.ctes.each do |name, cte_plan|
|
|
132
|
+
available_ctes[name.to_s] = if plan.recursive && cte_plan.is_a?(Plan::SetOperation) && cte_plan.operator == :union
|
|
133
|
+
execute_recursive_cte(name.to_s, cte_plan, available_ctes)
|
|
134
|
+
else
|
|
135
|
+
child_executor(available_ctes).execute(cte_plan, @current_transaction)[:rows]
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
child_executor(available_ctes).execute(plan.query_plan, @current_transaction)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def execute_recursive_cte(name, cte_plan, available_ctes)
|
|
142
|
+
unless cte_plan.is_a?(Plan::SetOperation) && cte_plan.operator == :union
|
|
143
|
+
raise ExecutionError, "Recursive CTE requires UNION or UNION ALL"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
anchor = child_executor(available_ctes).execute(cte_plan.left_plan, @current_transaction)[:rows]
|
|
147
|
+
accumulated = anchor.dup
|
|
148
|
+
working = anchor
|
|
149
|
+
output_columns = anchor.first&.keys || cte_plan.left_plan.columns.map(&:to_s)
|
|
150
|
+
max_iterations = Integer(@engine.instance_variable_get(:@config)[:max_recursive_iterations] || 10_000)
|
|
151
|
+
iterations = 0
|
|
152
|
+
|
|
153
|
+
while working.any?
|
|
154
|
+
iterations += 1
|
|
155
|
+
raise ExecutionError, "Recursive CTE exceeded #{max_iterations} iterations" if iterations > max_iterations
|
|
156
|
+
|
|
157
|
+
recursive_scope = available_ctes.merge(name => working)
|
|
158
|
+
next_rows = child_executor(recursive_scope).execute(cte_plan.right_plan, @current_transaction)[:rows]
|
|
159
|
+
next_rows = next_rows.map do |row|
|
|
160
|
+
output_columns.zip(row.values).to_h
|
|
161
|
+
end
|
|
162
|
+
if cte_plan.all
|
|
163
|
+
accumulated.concat(next_rows)
|
|
164
|
+
working = next_rows
|
|
165
|
+
else
|
|
166
|
+
existing = accumulated.map { |row| row.to_a }
|
|
167
|
+
working = next_rows.reject { |row| existing.include?(row.to_a) }
|
|
168
|
+
accumulated.concat(working)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
accumulated
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def execute_select(plan)
|
|
176
|
+
@stats[:sequential_scans] += 1 if plan.scan_type == :sequential
|
|
177
|
+
@stats[:index_scans] += 1 if plan.scan_type == :index
|
|
178
|
+
|
|
179
|
+
# Qualify source rows before evaluating predicates. This preserves
|
|
180
|
+
# SQL's table/alias namespace while retaining unqualified keys for
|
|
181
|
+
# existing single-table callers.
|
|
182
|
+
rows = qualify_rows(scan_table(plan), plan.source_reference || plan.table_name)
|
|
183
|
+
plan.joins.each do |join|
|
|
184
|
+
check_deadline!
|
|
185
|
+
right_rows = qualify_rows(scan_table(Plan::Select.new(join[:table].name, [])), join[:table])
|
|
186
|
+
rows = execute_join(rows, right_rows, join)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Apply filters (WHERE clause)
|
|
190
|
+
if plan.predicate
|
|
191
|
+
rows = rows.each_with_index.filter_map do |row, index|
|
|
192
|
+
check_deadline! if (index & 255).zero?
|
|
193
|
+
row if evaluate_predicate(plan.predicate, row)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
apply_window_functions!(rows, plan.projections) if plan.projections
|
|
198
|
+
|
|
199
|
+
# ORDER BY is evaluated against the source rows, so a column used
|
|
200
|
+
# solely for ordering remains available even when it is not selected.
|
|
201
|
+
aggregating = plan.aggregates && !plan.aggregates.empty?
|
|
202
|
+
if plan.order_by && !plan.order_by.empty? && !aggregating
|
|
203
|
+
rows = sort_rows(rows, plan.order_by)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
if aggregating
|
|
207
|
+
rows = aggregate_rows(rows, plan.group_by || [], plan.projections)
|
|
208
|
+
rows = rows.select { |row| evaluate_predicate(plan.having, row) } if plan.having
|
|
209
|
+
elsif plan.projections
|
|
210
|
+
# Apply projections (SELECT columns)
|
|
211
|
+
rows = rows.each_with_index.map do |row, index|
|
|
212
|
+
check_deadline! if (index & 255).zero?
|
|
213
|
+
project_row(row, plan.projections)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Apply DISTINCT
|
|
218
|
+
if plan.distinct
|
|
219
|
+
rows = distinct_rows(rows)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Apply GROUP BY
|
|
223
|
+
# Apply ORDER BY
|
|
224
|
+
if plan.order_by && !plan.order_by.empty? && aggregating
|
|
225
|
+
rows = sort_rows(rows, plan.order_by)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Apply LIMIT and OFFSET
|
|
229
|
+
if plan.limit
|
|
230
|
+
offset = plan.offset || 0
|
|
231
|
+
rows = rows[offset, plan.limit] || []
|
|
232
|
+
elsif plan.offset
|
|
233
|
+
rows = rows[plan.offset..] || []
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
{
|
|
237
|
+
rows: rows,
|
|
238
|
+
row_count: rows.size,
|
|
239
|
+
column_names: get_column_names(plan)
|
|
240
|
+
}
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def execute_insert(plan)
|
|
244
|
+
table_name = plan.table_name
|
|
245
|
+
columns = plan.columns
|
|
246
|
+
rows = plan.rows || [plan.values]
|
|
247
|
+
implicit_transaction = rows.size > 1 && !@engine.in_transaction?
|
|
248
|
+
@engine.begin_transaction if implicit_transaction
|
|
249
|
+
|
|
250
|
+
begin
|
|
251
|
+
inserted = rows.map { |values| execute_single_insert(plan, table_name, columns, values) }
|
|
252
|
+
unless !implicit_transaction || @engine.commit_transaction
|
|
253
|
+
raise ExecutionError, "Implicit multi-row INSERT transaction could not commit"
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
{
|
|
257
|
+
row_count: inserted.sum { |result| result[:row_count] },
|
|
258
|
+
affected_rows: inserted.sum { |result| result[:affected_rows] },
|
|
259
|
+
row_ids: inserted.filter_map { |result| result[:row_id] },
|
|
260
|
+
row_id: inserted.reverse_each.map { |result| result[:row_id] }.compact.first,
|
|
261
|
+
inserted_ids: inserted.filter_map { |result| result[:inserted_id] },
|
|
262
|
+
inserted_id: inserted.reverse_each.map { |result| result[:inserted_id] }.compact.first,
|
|
263
|
+
message: "INSERT #{inserted.sum { |result| result[:affected_rows] }}"
|
|
264
|
+
}
|
|
265
|
+
# Roll back even when the request is interrupted, then re-raise it.
|
|
266
|
+
# rubocop:disable Lint/RescueException
|
|
267
|
+
rescue Exception
|
|
268
|
+
@engine.rollback_transaction if implicit_transaction && @engine.in_transaction?
|
|
269
|
+
raise
|
|
270
|
+
# rubocop:enable Lint/RescueException
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def execute_single_insert(plan, table_name, columns, values)
|
|
275
|
+
# Build row data
|
|
276
|
+
row_data = {}
|
|
277
|
+
columns.each_with_index do |col, idx|
|
|
278
|
+
row_data[col] = evaluate_expression(values[idx])
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Insert into engine
|
|
282
|
+
table_columns = @engine.table_columns(table_name)
|
|
283
|
+
row_id = @engine.insert_row(table_name, table_columns, row_data)
|
|
284
|
+
primary_key = table_columns.find(&:primary_key?)
|
|
285
|
+
inserted_row = @engine.select_row(table_name, row_id, table_columns)
|
|
286
|
+
inserted_id = if primary_key && inserted_row
|
|
287
|
+
inserted_row[primary_key.name] || inserted_row[primary_key.name.to_sym]
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
{
|
|
291
|
+
row_count: 1,
|
|
292
|
+
affected_rows: 1,
|
|
293
|
+
row_id: row_id,
|
|
294
|
+
inserted_id: inserted_id,
|
|
295
|
+
message: "INSERT 1"
|
|
296
|
+
}
|
|
297
|
+
rescue DatabaseError => error
|
|
298
|
+
raise unless error.message.match?(/duplicate|unique|primary key/i)
|
|
299
|
+
return {row_count: 0, affected_rows: 0, message: "INSERT 0 (conflict ignored)"} if plan.on_conflict == :nothing
|
|
300
|
+
raise unless plan.on_conflict.is_a?(Hash) && plan.on_conflict[:action] == :update
|
|
301
|
+
|
|
302
|
+
target = plan.on_conflict[:target]
|
|
303
|
+
target = @engine.table_columns(table_name).select(&:primary_key?).map(&:name) if target.empty?
|
|
304
|
+
if target.empty?
|
|
305
|
+
target = @engine.table_columns(table_name).select(&:unique?).map(&:name)
|
|
306
|
+
end
|
|
307
|
+
if target.empty? && @engine.respond_to?(:index_manager)
|
|
308
|
+
target = @engine.index_manager.get_indexes_for_table(table_name)
|
|
309
|
+
.select(&:unique?).min_by { |index| index.columns.size }&.columns || []
|
|
310
|
+
end
|
|
311
|
+
raise ExecutionError, "ON CONFLICT DO UPDATE requires a conflict target or primary key" if target.empty?
|
|
312
|
+
existing = @engine.select_rows(table_name, @engine.table_columns(table_name)).find do |row|
|
|
313
|
+
target.all? do |column|
|
|
314
|
+
row_value(row, column, column.to_s) == row_value(row_data, column, column.to_s)
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
raise error unless existing
|
|
318
|
+
|
|
319
|
+
context = existing.merge(row_data.transform_keys(&:to_s).transform_keys { |key| "excluded.#{key}" })
|
|
320
|
+
values = plan.on_conflict[:assignments].each_with_object({}) do |assignment, updates|
|
|
321
|
+
updates[assignment.column] = evaluate_expression(assignment.value, context)
|
|
322
|
+
end
|
|
323
|
+
row_id = existing[:_row_id] || existing["_row_id"]
|
|
324
|
+
@engine.update_row(table_name, row_id, values)
|
|
325
|
+
inserted_id = target.map { |column| existing[column] || existing[column.to_sym] }.first
|
|
326
|
+
{row_count: 1, affected_rows: 1, row_id: row_id, inserted_id: inserted_id, message: "INSERT 0 UPDATE 1"}
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def execute_update(plan)
|
|
330
|
+
table_name = plan.table_name
|
|
331
|
+
assignments = plan.assignments
|
|
332
|
+
predicate = plan.predicate
|
|
333
|
+
|
|
334
|
+
# Get rows to update
|
|
335
|
+
rows = scan_table(Plan::Select.new(table_name, []))
|
|
336
|
+
rows = rows.select { |row| evaluate_predicate(predicate, row) } if predicate
|
|
337
|
+
|
|
338
|
+
updated_count = 0
|
|
339
|
+
rows.each do |row|
|
|
340
|
+
# Apply updates
|
|
341
|
+
assignments.each do |assignment|
|
|
342
|
+
column = assignment.is_a?(Hash) ? (assignment[:column] || assignment["column"]) : assignment.column
|
|
343
|
+
value = assignment.is_a?(Hash) ? (assignment[:value] || assignment["value"]) : assignment.value
|
|
344
|
+
row[column] = evaluate_expression(value, row)
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Update in engine
|
|
348
|
+
row_id = row[:_row_id] || row["_row_id"] || row[:id] || row["id"]
|
|
349
|
+
@engine.update_row(table_name, row_id, row)
|
|
350
|
+
|
|
351
|
+
updated_count += 1
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
{
|
|
355
|
+
row_count: updated_count,
|
|
356
|
+
affected_rows: updated_count,
|
|
357
|
+
message: "UPDATE #{updated_count}"
|
|
358
|
+
}
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def execute_delete(plan)
|
|
362
|
+
table_name = plan.table_name
|
|
363
|
+
predicate = plan.predicate
|
|
364
|
+
|
|
365
|
+
# Get rows to delete
|
|
366
|
+
rows = scan_table(Plan::Select.new(table_name, []))
|
|
367
|
+
rows = rows.select { |row| evaluate_predicate(predicate, row) } if predicate
|
|
368
|
+
|
|
369
|
+
deleted_count = 0
|
|
370
|
+
rows.each do |row|
|
|
371
|
+
row_id = row[:_row_id] || row["_row_id"] || row[:id] || row["id"]
|
|
372
|
+
@engine.delete_row(table_name, row_id)
|
|
373
|
+
deleted_count += 1
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
{
|
|
377
|
+
row_count: deleted_count,
|
|
378
|
+
affected_rows: deleted_count,
|
|
379
|
+
message: "DELETE #{deleted_count}"
|
|
380
|
+
}
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def execute_create_table(plan)
|
|
384
|
+
table_name = plan.table_name
|
|
385
|
+
columns = plan.columns
|
|
386
|
+
options = plan.options || {}
|
|
387
|
+
|
|
388
|
+
@engine.create_table(table_name, columns, options)
|
|
389
|
+
|
|
390
|
+
{
|
|
391
|
+
row_count: 0,
|
|
392
|
+
message: "CREATE TABLE #{table_name}"
|
|
393
|
+
}
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def execute_drop_table(plan)
|
|
397
|
+
table_name = plan.table_name
|
|
398
|
+
options = plan.options || {}
|
|
399
|
+
|
|
400
|
+
@engine.drop_table(table_name, options)
|
|
401
|
+
|
|
402
|
+
{
|
|
403
|
+
row_count: 0,
|
|
404
|
+
message: "DROP TABLE #{table_name}"
|
|
405
|
+
}
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
def execute_create_database(plan)
|
|
409
|
+
@engine.catalog.create_database(plan.database_name, **(plan.options || {}))
|
|
410
|
+
{row_count: 0, message: "CREATE DATABASE #{plan.database_name}"}
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
def execute_drop_database(plan)
|
|
414
|
+
@engine.catalog.drop_database(plan.database_name, **(plan.options || {}))
|
|
415
|
+
{row_count: 0, message: "DROP DATABASE #{plan.database_name}"}
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def execute_create_schema(plan)
|
|
419
|
+
@engine.catalog.create_schema(plan.schema_name, **(plan.options || {}))
|
|
420
|
+
{row_count: 0, message: "CREATE SCHEMA #{plan.schema_name}"}
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def execute_drop_schema(plan)
|
|
424
|
+
@engine.catalog.drop_schema(plan.schema_name, **(plan.options || {}))
|
|
425
|
+
{row_count: 0, message: "DROP SCHEMA #{plan.schema_name}"}
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def execute_create_view(plan)
|
|
429
|
+
query = plan.query.respond_to?(:to_sql) ? plan.query.to_sql : plan.query
|
|
430
|
+
@engine.catalog.create_view(plan.view_name, query, **(plan.options || {}))
|
|
431
|
+
{row_count: 0, message: "CREATE VIEW #{plan.view_name}"}
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def execute_drop_view(plan)
|
|
435
|
+
@engine.catalog.drop_view(plan.view_name, **(plan.options || {}))
|
|
436
|
+
{row_count: 0, message: "DROP VIEW #{plan.view_name}"}
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
def execute_create_trigger(plan)
|
|
440
|
+
definition = "EXECUTE FUNCTION #{plan.function_name}()"
|
|
441
|
+
@engine.catalog.create_trigger(plan.trigger_name, plan.event, plan.target_table, definition, timing: plan.timing, function_name: plan.function_name)
|
|
442
|
+
{row_count: 0, message: "CREATE TRIGGER #{plan.trigger_name}"}
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
def execute_drop_trigger(plan)
|
|
446
|
+
@engine.catalog.drop_trigger(plan.trigger_name, **(plan.options || {}))
|
|
447
|
+
{row_count: 0, message: "DROP TRIGGER #{plan.trigger_name}"}
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
def execute_vacuum(_plan)
|
|
451
|
+
result = @engine.vacuum
|
|
452
|
+
{row_count: 0, vacuum: result, message: "VACUUM"}
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
def execute_alter_table_add_column(plan)
|
|
456
|
+
@engine.add_column(plan.table_name, plan.column_name, plan.column_type, plan.options)
|
|
457
|
+
{row_count: 0, message: "ALTER TABLE #{plan.table_name} ADD COLUMN #{plan.column_name}"}
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
def execute_alter_table_drop_column(plan)
|
|
461
|
+
@engine.drop_column(plan.table_name, plan.column_name)
|
|
462
|
+
{row_count: 0, message: "ALTER TABLE #{plan.table_name} DROP COLUMN #{plan.column_name}"}
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
def execute_alter_table_add_constraint(plan)
|
|
466
|
+
@engine.add_constraint(plan.table_name, plan.constraint)
|
|
467
|
+
{row_count: 0, message: "ALTER TABLE #{plan.table_name} ADD CONSTRAINT #{plan.constraint.name}"}
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
def execute_alter_table_drop_constraint(plan)
|
|
471
|
+
@engine.drop_constraint(plan.table_name, plan.constraint_name)
|
|
472
|
+
{row_count: 0, message: "ALTER TABLE #{plan.table_name} DROP CONSTRAINT #{plan.constraint_name}"}
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
def execute_create_index(plan)
|
|
476
|
+
index_name = plan.index_name
|
|
477
|
+
table_name = plan.table_name
|
|
478
|
+
columns = plan.columns
|
|
479
|
+
options = plan.options || {}
|
|
480
|
+
|
|
481
|
+
if @engine.respond_to?(:index_manager)
|
|
482
|
+
@engine.index_manager.create_index(index_name, table_name, columns, options)
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
{
|
|
486
|
+
row_count: 0,
|
|
487
|
+
message: "CREATE INDEX #{index_name}"
|
|
488
|
+
}
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
def execute_drop_index(plan)
|
|
492
|
+
index_name = plan.index_name
|
|
493
|
+
options = plan.options || {}
|
|
494
|
+
|
|
495
|
+
if @engine.respond_to?(:index_manager)
|
|
496
|
+
@engine.index_manager.drop_index(index_name, options)
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
{
|
|
500
|
+
row_count: 0,
|
|
501
|
+
message: "DROP INDEX #{index_name}"
|
|
502
|
+
}
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
def execute_begin_transaction(plan)
|
|
506
|
+
isolation_level = plan.isolation_level || :read_committed
|
|
507
|
+
transaction_id = @engine.begin_transaction(isolation_level)
|
|
508
|
+
|
|
509
|
+
{
|
|
510
|
+
row_count: 0,
|
|
511
|
+
transaction_id: transaction_id,
|
|
512
|
+
message: "BEGIN"
|
|
513
|
+
}
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
def execute_commit(plan)
|
|
517
|
+
@engine.commit_transaction
|
|
518
|
+
|
|
519
|
+
{
|
|
520
|
+
row_count: 0,
|
|
521
|
+
message: "COMMIT"
|
|
522
|
+
}
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
def execute_rollback(plan)
|
|
526
|
+
@engine.rollback_transaction
|
|
527
|
+
|
|
528
|
+
{
|
|
529
|
+
row_count: 0,
|
|
530
|
+
message: "ROLLBACK"
|
|
531
|
+
}
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
def execute_savepoint(plan)
|
|
535
|
+
@engine.create_savepoint(plan.name)
|
|
536
|
+
{row_count: 0, message: "SAVEPOINT #{plan.name}"}
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
def execute_rollback_to_savepoint(plan)
|
|
540
|
+
@engine.rollback_to_savepoint(plan.name)
|
|
541
|
+
{row_count: 0, message: "ROLLBACK TO SAVEPOINT #{plan.name}"}
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
def execute_release_savepoint(plan)
|
|
545
|
+
@engine.release_savepoint(plan.name)
|
|
546
|
+
{row_count: 0, message: "RELEASE SAVEPOINT #{plan.name}"}
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
def execute_explain(plan)
|
|
550
|
+
statement = plan.statement
|
|
551
|
+
analyze = plan.analyze || false
|
|
552
|
+
|
|
553
|
+
# Generate execution plan for the statement
|
|
554
|
+
planner = Planner.new(@engine)
|
|
555
|
+
analyzed_plan = planner.plan(statement)
|
|
556
|
+
|
|
557
|
+
# Format the plan
|
|
558
|
+
plan_text = format_plan(analyzed_plan)
|
|
559
|
+
|
|
560
|
+
if analyze
|
|
561
|
+
# Execute and get actual stats
|
|
562
|
+
start_time = Time.now
|
|
563
|
+
result = execute(analyzed_plan)
|
|
564
|
+
elapsed_ms = ((Time.now - start_time) * 1000).round(2)
|
|
565
|
+
|
|
566
|
+
plan_text = "EXPLAIN ANALYZE:\n#{plan_text}\n" \
|
|
567
|
+
"Execution Time: #{elapsed_ms}ms\n" \
|
|
568
|
+
"Rows: #{result[:row_count]}"
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
{
|
|
572
|
+
rows: [{"QUERY PLAN" => plan_text}],
|
|
573
|
+
row_count: 1,
|
|
574
|
+
column_names: ["QUERY PLAN"]
|
|
575
|
+
}
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
# Scan operations
|
|
579
|
+
def scan_table(plan)
|
|
580
|
+
table_name = plan.table_name
|
|
581
|
+
return [{}] unless table_name
|
|
582
|
+
return @cte_rows.fetch(table_name.to_s) if @cte_rows.key?(table_name.to_s)
|
|
583
|
+
if @engine.catalog.respond_to?(:find_view) && (view = @engine.catalog.find_view(table_name))
|
|
584
|
+
statement = RubyDB::SQL::Parser.new(RubyDB::SQL::Lexer.new(view.query).tokenize).parse.first
|
|
585
|
+
return scan_table(Planner.new(@engine).plan(statement))
|
|
586
|
+
end
|
|
587
|
+
columns = @engine.table_columns(table_name)
|
|
588
|
+
|
|
589
|
+
if plan.scan_type == :index && plan.index
|
|
590
|
+
# Use index scan
|
|
591
|
+
scan = Indexes::IndexScan.new(plan.index, :full)
|
|
592
|
+
results = scan.execute
|
|
593
|
+
row_ids = results.map { |r| r[:row_id] }
|
|
594
|
+
|
|
595
|
+
# Fetch rows by row_id
|
|
596
|
+
rows = []
|
|
597
|
+
row_ids.each do |row_id|
|
|
598
|
+
row = @engine.select_row(table_name, row_id, columns)
|
|
599
|
+
rows << row if row
|
|
600
|
+
end
|
|
601
|
+
rows
|
|
602
|
+
else
|
|
603
|
+
# Sequential scan
|
|
604
|
+
@engine.select_rows(table_name, columns)
|
|
605
|
+
end
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
def qualify_rows(rows, table_reference)
|
|
609
|
+
table_name = table_reference.respond_to?(:name) ? table_reference.name : table_reference
|
|
610
|
+
alias_name = table_reference.respond_to?(:alias_name) ? table_reference.alias_name : nil
|
|
611
|
+
rows.map do |row|
|
|
612
|
+
row.each_with_object({}) do |(key, value), qualified|
|
|
613
|
+
key = key.to_s
|
|
614
|
+
qualified[key] = value
|
|
615
|
+
qualified["#{table_name}.#{key}"] = value
|
|
616
|
+
qualified["#{alias_name}.#{key}"] = value if alias_name
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
def execute_join(left_rows, right_rows, join)
|
|
622
|
+
return left_rows.flat_map { |left_row| right_rows.map { |right_row| merge_join_rows(left_row, right_row) } } if join[:type] == :cross
|
|
623
|
+
|
|
624
|
+
result = []
|
|
625
|
+
matched_right = Array.new(right_rows.length, false)
|
|
626
|
+
left_rows.each do |left_row|
|
|
627
|
+
check_deadline!
|
|
628
|
+
matches = right_rows.each_index.select do |index|
|
|
629
|
+
evaluate_predicate(join[:predicate], merge_join_rows(left_row, right_rows[index]))
|
|
630
|
+
end
|
|
631
|
+
if matches.empty?
|
|
632
|
+
result << merge_join_rows(left_row, nil) if %i[left full].include?(join[:type])
|
|
633
|
+
else
|
|
634
|
+
matches.each do |index|
|
|
635
|
+
matched_right[index] = true
|
|
636
|
+
result << merge_join_rows(left_row, right_rows[index])
|
|
637
|
+
end
|
|
638
|
+
end
|
|
639
|
+
end
|
|
640
|
+
if %i[right full].include?(join[:type])
|
|
641
|
+
right_rows.each_with_index do |right_row, index|
|
|
642
|
+
result << merge_join_rows(nil, right_row) unless matched_right[index]
|
|
643
|
+
end
|
|
644
|
+
end
|
|
645
|
+
result
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
def merge_join_rows(left_row, right_row)
|
|
649
|
+
(left_row || {}).merge(right_row || {}) { |_key, left_value, _right_value| left_value }
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
# Predicate evaluation
|
|
653
|
+
def evaluate_predicate(predicate, row)
|
|
654
|
+
case predicate
|
|
655
|
+
when Predicate::Comparison
|
|
656
|
+
left_val = evaluate_expression(predicate.left, row)
|
|
657
|
+
right_val = evaluate_expression(predicate.right, row)
|
|
658
|
+
compare_values(left_val, right_val, predicate.operator)
|
|
659
|
+
when Predicate::And
|
|
660
|
+
evaluate_predicate(predicate.left, row) && evaluate_predicate(predicate.right, row)
|
|
661
|
+
when Predicate::Or
|
|
662
|
+
evaluate_predicate(predicate.left, row) || evaluate_predicate(predicate.right, row)
|
|
663
|
+
when Predicate::Not
|
|
664
|
+
!evaluate_predicate(predicate.operand, row)
|
|
665
|
+
when Predicate::Between
|
|
666
|
+
val = evaluate_expression(predicate.expression, row)
|
|
667
|
+
low = evaluate_expression(predicate.low, row)
|
|
668
|
+
high = evaluate_expression(predicate.high, row)
|
|
669
|
+
val >= low && val <= high
|
|
670
|
+
when Predicate::In
|
|
671
|
+
val = evaluate_expression(predicate.expression, row)
|
|
672
|
+
predicate.values.any? { |v| val == evaluate_expression(v, row) }
|
|
673
|
+
when Predicate::IsNull
|
|
674
|
+
val = evaluate_expression(predicate.expression, row)
|
|
675
|
+
predicate.negated ? !val.nil? : val.nil?
|
|
676
|
+
when Predicate::Like
|
|
677
|
+
val = evaluate_expression(predicate.expression, row).to_s
|
|
678
|
+
pattern = evaluate_expression(predicate.pattern, row).to_s
|
|
679
|
+
like_match?(val, pattern, predicate.case_sensitive)
|
|
680
|
+
else
|
|
681
|
+
true
|
|
682
|
+
end
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
def evaluate_expression(expr, row = nil)
|
|
686
|
+
case expr
|
|
687
|
+
when SQL::AST::Literal
|
|
688
|
+
expr.value
|
|
689
|
+
when SQL::AST::Identifier
|
|
690
|
+
return nil unless row
|
|
691
|
+
|
|
692
|
+
qualified_name = expr.table && "#{expr.table}.#{expr.name}"
|
|
693
|
+
row_value(row, qualified_name, expr.name, expr.name.to_sym)
|
|
694
|
+
when SQL::AST::UnaryOp
|
|
695
|
+
apply_ast_unary_op(expr.operator, evaluate_expression(expr.operand, row))
|
|
696
|
+
when SQL::AST::BinaryOp
|
|
697
|
+
apply_ast_binary_op(
|
|
698
|
+
expr.operator,
|
|
699
|
+
evaluate_expression(expr.left, row),
|
|
700
|
+
evaluate_expression(expr.right, row)
|
|
701
|
+
)
|
|
702
|
+
when SQL::AST::FunctionCall
|
|
703
|
+
if expr.window
|
|
704
|
+
row && row[window_key(expr)]
|
|
705
|
+
else
|
|
706
|
+
apply_function(expr.name, expr.arguments.map { |argument| evaluate_expression(argument, row) })
|
|
707
|
+
end
|
|
708
|
+
when SQL::AST::Subquery
|
|
709
|
+
result = child_executor(@cte_rows).execute(Planner.new(@engine).plan(expr.query))
|
|
710
|
+
rows = result[:rows]
|
|
711
|
+
raise ExecutionError, "Scalar subquery returned more than one row" if rows.size > 1
|
|
712
|
+
rows.empty? ? nil : rows.first.values.first
|
|
713
|
+
when Expression::Literal
|
|
714
|
+
expr.value
|
|
715
|
+
when Expression::Column
|
|
716
|
+
return nil unless row
|
|
717
|
+
|
|
718
|
+
qualified_name = expr.table && "#{expr.table}.#{expr.name}"
|
|
719
|
+
row_value(row, qualified_name, expr.name, expr.name.to_sym)
|
|
720
|
+
when Expression::BinaryOp
|
|
721
|
+
left = evaluate_expression(expr.left, row)
|
|
722
|
+
right = evaluate_expression(expr.right, row)
|
|
723
|
+
apply_binary_op(left, right, expr.operator)
|
|
724
|
+
when Expression::UnaryOp
|
|
725
|
+
operand = evaluate_expression(expr.operand, row)
|
|
726
|
+
apply_unary_op(operand, expr.operator)
|
|
727
|
+
when Expression::Function
|
|
728
|
+
args = expr.arguments.map { |arg| evaluate_expression(arg, row) }
|
|
729
|
+
apply_function(expr.name, args)
|
|
730
|
+
when Expression::Parameter
|
|
731
|
+
expr.value
|
|
732
|
+
end
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
# Helper methods
|
|
736
|
+
def compare_values(left, right, operator)
|
|
737
|
+
return false if left.nil? || right.nil?
|
|
738
|
+
|
|
739
|
+
case operator
|
|
740
|
+
when :eq then left == right
|
|
741
|
+
when :ne then left != right
|
|
742
|
+
when :lt then left < right
|
|
743
|
+
when :lte then left <= right
|
|
744
|
+
when :gt then left > right
|
|
745
|
+
when :gte then left >= right
|
|
746
|
+
else false
|
|
747
|
+
end
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
def apply_binary_op(left, right, operator)
|
|
751
|
+
return nil if left.nil? || right.nil?
|
|
752
|
+
|
|
753
|
+
case operator
|
|
754
|
+
when :plus then left + right
|
|
755
|
+
when :minus then left - right
|
|
756
|
+
when :multiply then left * right
|
|
757
|
+
when :divide then left / right if right != 0
|
|
758
|
+
when :modulo then left % right if right != 0
|
|
759
|
+
when :concat then left.to_s + right.to_s
|
|
760
|
+
end
|
|
761
|
+
end
|
|
762
|
+
|
|
763
|
+
def apply_ast_unary_op(operator, value)
|
|
764
|
+
return nil if value.nil?
|
|
765
|
+
|
|
766
|
+
case operator
|
|
767
|
+
when SQL::Token::Type::PLUS then value
|
|
768
|
+
when SQL::Token::Type::MINUS then -value
|
|
769
|
+
when SQL::Token::Type::NOT then !value
|
|
770
|
+
else value
|
|
771
|
+
end
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
def apply_ast_binary_op(operator, left, right)
|
|
775
|
+
case operator
|
|
776
|
+
# SQL comparisons involving NULL evaluate to UNKNOWN, represented by
|
|
777
|
+
# nil here. WHERE filtering already treats UNKNOWN as non-matching.
|
|
778
|
+
when SQL::Token::Type::EQ then (left.nil? || right.nil?) ? nil : left == right
|
|
779
|
+
when SQL::Token::Type::NE then (left.nil? || right.nil?) ? nil : left != right
|
|
780
|
+
when SQL::Token::Type::LT then !left.nil? && !right.nil? && left < right
|
|
781
|
+
when SQL::Token::Type::LTE then !left.nil? && !right.nil? && left <= right
|
|
782
|
+
when SQL::Token::Type::GT then !left.nil? && !right.nil? && left > right
|
|
783
|
+
when SQL::Token::Type::GTE then !left.nil? && !right.nil? && left >= right
|
|
784
|
+
when SQL::Token::Type::AND then sql_and(left, right)
|
|
785
|
+
when SQL::Token::Type::OR then sql_or(left, right)
|
|
786
|
+
when SQL::Token::Type::PLUS then apply_binary_op(left, right, :plus)
|
|
787
|
+
when SQL::Token::Type::MINUS then apply_binary_op(left, right, :minus)
|
|
788
|
+
when SQL::Token::Type::STAR then apply_binary_op(left, right, :multiply)
|
|
789
|
+
when SQL::Token::Type::SLASH then apply_binary_op(left, right, :divide)
|
|
790
|
+
when SQL::Token::Type::PERCENT then apply_binary_op(left, right, :modulo)
|
|
791
|
+
end
|
|
792
|
+
end
|
|
793
|
+
|
|
794
|
+
def apply_unary_op(operand, operator)
|
|
795
|
+
return nil if operand.nil?
|
|
796
|
+
|
|
797
|
+
case operator
|
|
798
|
+
when :negate then -operand
|
|
799
|
+
when :not then !operand
|
|
800
|
+
else operand
|
|
801
|
+
end
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
def sql_and(left, right)
|
|
805
|
+
return false if left == false || right == false
|
|
806
|
+
return nil if left.nil? || right.nil?
|
|
807
|
+
|
|
808
|
+
!!left && !!right
|
|
809
|
+
end
|
|
810
|
+
|
|
811
|
+
def sql_or(left, right)
|
|
812
|
+
return true if left == true || right == true
|
|
813
|
+
return nil if left.nil? || right.nil?
|
|
814
|
+
|
|
815
|
+
!!left || !!right
|
|
816
|
+
end
|
|
817
|
+
|
|
818
|
+
def row_value(row, *keys)
|
|
819
|
+
keys.compact.each do |key|
|
|
820
|
+
return row[key] if row.respond_to?(:key?) && row.key?(key)
|
|
821
|
+
end
|
|
822
|
+
nil
|
|
823
|
+
end
|
|
824
|
+
|
|
825
|
+
def child_executor(cte_rows)
|
|
826
|
+
self.class.new(
|
|
827
|
+
@engine,
|
|
828
|
+
cte_rows: cte_rows,
|
|
829
|
+
deadline_at: @deadline_at,
|
|
830
|
+
cancellation: @cancellation
|
|
831
|
+
)
|
|
832
|
+
end
|
|
833
|
+
|
|
834
|
+
def check_deadline!
|
|
835
|
+
if @cancellation&.cancelled?
|
|
836
|
+
raise ExecutionError.new("Request cancelled by client", code: "cancelled")
|
|
837
|
+
end
|
|
838
|
+
|
|
839
|
+
return unless @deadline_at && Time.now >= @deadline_at
|
|
840
|
+
|
|
841
|
+
raise ExecutionError.new("Request deadline exceeded during execution", code: "deadline_exceeded")
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
def apply_function(name, args)
|
|
845
|
+
case name.to_s.upcase
|
|
846
|
+
when "COUNT"
|
|
847
|
+
args.compact.size
|
|
848
|
+
when "SUM"
|
|
849
|
+
args.compact.sum
|
|
850
|
+
when "AVG"
|
|
851
|
+
vals = args.compact
|
|
852
|
+
vals.empty? ? 0 : vals.sum / vals.size.to_f
|
|
853
|
+
when "MIN"
|
|
854
|
+
args.compact.min
|
|
855
|
+
when "MAX"
|
|
856
|
+
args.compact.max
|
|
857
|
+
when "LOWER"
|
|
858
|
+
args.first.to_s.downcase
|
|
859
|
+
when "UPPER"
|
|
860
|
+
args.first.to_s.upcase
|
|
861
|
+
when "LENGTH"
|
|
862
|
+
args.first.to_s.length
|
|
863
|
+
when "COALESCE"
|
|
864
|
+
args.find { |arg| !arg.nil? }
|
|
865
|
+
when "NOW"
|
|
866
|
+
Time.now
|
|
867
|
+
when "CURRENT_DATE"
|
|
868
|
+
Date.today
|
|
869
|
+
when "CURRENT_TIME"
|
|
870
|
+
Time.now
|
|
871
|
+
end
|
|
872
|
+
end
|
|
873
|
+
|
|
874
|
+
def like_match?(value, pattern, case_sensitive = true)
|
|
875
|
+
return false if value.nil? || pattern.nil?
|
|
876
|
+
|
|
877
|
+
str = case_sensitive ? value : value.downcase
|
|
878
|
+
pat = case_sensitive ? pattern : pattern.downcase
|
|
879
|
+
|
|
880
|
+
# Convert SQL LIKE pattern to regex
|
|
881
|
+
regex_str = Regexp.escape(pat)
|
|
882
|
+
.gsub("%", ".*")
|
|
883
|
+
.gsub("_", ".")
|
|
884
|
+
Regexp.new("^#{regex_str}$").match?(str)
|
|
885
|
+
end
|
|
886
|
+
|
|
887
|
+
def project_row(row, projections)
|
|
888
|
+
result = {}
|
|
889
|
+
projections.each do |proj|
|
|
890
|
+
expression = proj.respond_to?(:expression) ? proj.expression : proj
|
|
891
|
+
|
|
892
|
+
if expression.is_a?(SQL::AST::Star)
|
|
893
|
+
if expression.table
|
|
894
|
+
prefix = "#{expression.table}."
|
|
895
|
+
row.each do |key, value|
|
|
896
|
+
key = key.to_s
|
|
897
|
+
next unless key.start_with?(prefix)
|
|
898
|
+
|
|
899
|
+
column = key.delete_prefix(prefix)
|
|
900
|
+
result[column] = value unless column.start_with?("_")
|
|
901
|
+
end
|
|
902
|
+
else
|
|
903
|
+
row.each do |key, value|
|
|
904
|
+
key = key.to_s
|
|
905
|
+
result[key] = value unless key.include?(".") || key.start_with?("_")
|
|
906
|
+
end
|
|
907
|
+
end
|
|
908
|
+
elsif expression.is_a?(String) || expression.is_a?(Symbol)
|
|
909
|
+
result[expression.to_s] = row[expression.to_s]
|
|
910
|
+
else
|
|
911
|
+
result[projection_name(proj)] = evaluate_expression(expression, row)
|
|
912
|
+
end
|
|
913
|
+
end
|
|
914
|
+
result
|
|
915
|
+
end
|
|
916
|
+
|
|
917
|
+
def distinct_rows(rows)
|
|
918
|
+
seen = Set.new
|
|
919
|
+
rows.select do |row|
|
|
920
|
+
key = row.to_hash
|
|
921
|
+
if seen.include?(key)
|
|
922
|
+
false
|
|
923
|
+
else
|
|
924
|
+
seen.add(key)
|
|
925
|
+
true
|
|
926
|
+
end
|
|
927
|
+
end
|
|
928
|
+
end
|
|
929
|
+
|
|
930
|
+
def group_rows(rows, group_by, aggregates)
|
|
931
|
+
groups = {}
|
|
932
|
+
rows.each do |row|
|
|
933
|
+
key = group_by.map { |col| row[col.to_s] }
|
|
934
|
+
groups[key] ||= []
|
|
935
|
+
groups[key] << row
|
|
936
|
+
end
|
|
937
|
+
|
|
938
|
+
result = []
|
|
939
|
+
groups.each do |key, group_rows|
|
|
940
|
+
result_row = {}
|
|
941
|
+
group_by.each_with_index do |col, idx|
|
|
942
|
+
result_row[col.to_s] = key[idx]
|
|
943
|
+
end
|
|
944
|
+
|
|
945
|
+
aggregates&.each do |agg|
|
|
946
|
+
result_row[agg.alias || agg.name] = apply_aggregate(agg, group_rows)
|
|
947
|
+
end
|
|
948
|
+
|
|
949
|
+
result << result_row
|
|
950
|
+
end
|
|
951
|
+
result
|
|
952
|
+
end
|
|
953
|
+
|
|
954
|
+
def aggregate_rows(rows, group_by, projections)
|
|
955
|
+
groups = {}
|
|
956
|
+
if group_by.empty?
|
|
957
|
+
groups[[]] = rows
|
|
958
|
+
else
|
|
959
|
+
rows.each do |row|
|
|
960
|
+
key = group_by.map { |expression| evaluate_expression(expression, row) }
|
|
961
|
+
(groups[key] ||= []) << row
|
|
962
|
+
end
|
|
963
|
+
end
|
|
964
|
+
|
|
965
|
+
groups.map do |_key, group|
|
|
966
|
+
representative = group.first || {}
|
|
967
|
+
projections.each_with_object({}) do |projection, result|
|
|
968
|
+
expression = projection.respond_to?(:expression) ? projection.expression : projection
|
|
969
|
+
name = projection_name(projection)
|
|
970
|
+
result[name] = if aggregate_function?(expression)
|
|
971
|
+
apply_aggregate_function(expression, group)
|
|
972
|
+
else
|
|
973
|
+
evaluate_expression(expression, representative)
|
|
974
|
+
end
|
|
975
|
+
end
|
|
976
|
+
end
|
|
977
|
+
end
|
|
978
|
+
|
|
979
|
+
def aggregate_function?(expression)
|
|
980
|
+
expression.is_a?(SQL::AST::FunctionCall) && !expression.window &&
|
|
981
|
+
%w[COUNT SUM AVG MIN MAX].include?(expression.name.to_s.upcase)
|
|
982
|
+
end
|
|
983
|
+
|
|
984
|
+
def apply_window_functions!(rows, projections)
|
|
985
|
+
projections.each do |projection|
|
|
986
|
+
expression = projection.respond_to?(:expression) ? projection.expression : projection
|
|
987
|
+
next unless expression.is_a?(SQL::AST::FunctionCall) && expression.window
|
|
988
|
+
|
|
989
|
+
spec = expression.window
|
|
990
|
+
partitions = {}
|
|
991
|
+
rows.each { |row| (partitions[spec[:partition_by].map { |part| evaluate_expression(part, row) }] ||= []) << row }
|
|
992
|
+
partitions.each_value do |partition_rows|
|
|
993
|
+
ordered_rows = sort_window_rows(partition_rows, spec[:order_by])
|
|
994
|
+
ordered_rows.each_with_index do |row, index|
|
|
995
|
+
row[window_key(expression)] = window_value(expression, ordered_rows, index, spec[:order_by], spec[:frame])
|
|
996
|
+
end
|
|
997
|
+
end
|
|
998
|
+
end
|
|
999
|
+
end
|
|
1000
|
+
|
|
1001
|
+
def window_key(expression)
|
|
1002
|
+
"__rubydb_window_#{expression.object_id}"
|
|
1003
|
+
end
|
|
1004
|
+
|
|
1005
|
+
def sort_window_rows(rows, order_by)
|
|
1006
|
+
return rows.dup if order_by.empty?
|
|
1007
|
+
|
|
1008
|
+
rows.sort do |left, right|
|
|
1009
|
+
comparison = 0
|
|
1010
|
+
order_by.each do |item|
|
|
1011
|
+
left_value = evaluate_expression(item.expression, left)
|
|
1012
|
+
right_value = evaluate_expression(item.expression, right)
|
|
1013
|
+
comparison = compare_sort_values(left_value, right_value)
|
|
1014
|
+
comparison = -comparison if item.direction == :desc
|
|
1015
|
+
break unless comparison.zero?
|
|
1016
|
+
end
|
|
1017
|
+
comparison
|
|
1018
|
+
end
|
|
1019
|
+
end
|
|
1020
|
+
|
|
1021
|
+
def compare_sort_values(left, right)
|
|
1022
|
+
return 0 if left.nil? && right.nil?
|
|
1023
|
+
return -1 if left.nil?
|
|
1024
|
+
return 1 if right.nil?
|
|
1025
|
+
|
|
1026
|
+
left <=> right
|
|
1027
|
+
end
|
|
1028
|
+
|
|
1029
|
+
def window_value(expression, rows, index, order_by, frame = nil)
|
|
1030
|
+
rows = window_frame_rows(rows, index, frame) if frame
|
|
1031
|
+
name = expression.name.to_s.upcase
|
|
1032
|
+
if %w[ROW_NUMBER RANK DENSE_RANK].include?(name)
|
|
1033
|
+
return index + 1 if name == "ROW_NUMBER" || order_by.empty?
|
|
1034
|
+
|
|
1035
|
+
keys = rows.map { |row| order_by.map { |item| evaluate_expression(item.expression, row) } }
|
|
1036
|
+
current = keys[index]
|
|
1037
|
+
return keys.index(current) + 1 if name == "RANK"
|
|
1038
|
+
|
|
1039
|
+
return keys[0..index].uniq.index(current) + 1
|
|
1040
|
+
end
|
|
1041
|
+
|
|
1042
|
+
argument = expression.arguments.first
|
|
1043
|
+
values = if argument.is_a?(SQL::AST::Star)
|
|
1044
|
+
rows
|
|
1045
|
+
else
|
|
1046
|
+
rows.map { |row| evaluate_expression(argument, row) }.compact
|
|
1047
|
+
end
|
|
1048
|
+
values = values.uniq if expression.distinct
|
|
1049
|
+
case name
|
|
1050
|
+
when "COUNT" then values.size
|
|
1051
|
+
when "SUM" then values.empty? ? nil : values.sum
|
|
1052
|
+
when "AVG" then values.empty? ? nil : values.sum / values.size.to_f
|
|
1053
|
+
when "MIN" then values.min
|
|
1054
|
+
when "MAX" then values.max
|
|
1055
|
+
else
|
|
1056
|
+
raise ExecutionError, "Unsupported window function: #{expression.name}"
|
|
1057
|
+
end
|
|
1058
|
+
end
|
|
1059
|
+
|
|
1060
|
+
def window_frame_rows(rows, index, frame)
|
|
1061
|
+
start_index = window_frame_index(frame[:start], index, rows.size, start: true)
|
|
1062
|
+
finish = frame[:finish] || {kind: :current_row}
|
|
1063
|
+
end_index = window_frame_index(finish, index, rows.size, start: false)
|
|
1064
|
+
return [] if start_index > end_index
|
|
1065
|
+
|
|
1066
|
+
rows[start_index..end_index] || []
|
|
1067
|
+
end
|
|
1068
|
+
|
|
1069
|
+
def window_frame_index(boundary, index, size, start:)
|
|
1070
|
+
case boundary[:kind]
|
|
1071
|
+
when :unbounded_preceding then 0
|
|
1072
|
+
when :unbounded_following then size - 1
|
|
1073
|
+
when :current_row then index
|
|
1074
|
+
when :preceding then [index - boundary[:value], 0].max
|
|
1075
|
+
when :following then [index + boundary[:value], size - 1].min
|
|
1076
|
+
else raise ExecutionError, "Unsupported window frame boundary: #{boundary[:kind]}"
|
|
1077
|
+
end
|
|
1078
|
+
end
|
|
1079
|
+
|
|
1080
|
+
def apply_aggregate_function(expression, rows)
|
|
1081
|
+
argument = expression.arguments.first
|
|
1082
|
+
values = if argument.is_a?(SQL::AST::Star)
|
|
1083
|
+
rows
|
|
1084
|
+
else
|
|
1085
|
+
rows.map { |row| evaluate_expression(argument, row) }.compact
|
|
1086
|
+
end
|
|
1087
|
+
values = values.uniq if expression.distinct
|
|
1088
|
+
|
|
1089
|
+
case expression.name.to_s.upcase
|
|
1090
|
+
when "COUNT" then values.size
|
|
1091
|
+
when "SUM" then values.empty? ? nil : values.sum
|
|
1092
|
+
when "AVG" then values.empty? ? nil : values.sum / values.size.to_f
|
|
1093
|
+
when "MIN" then values.min
|
|
1094
|
+
when "MAX" then values.max
|
|
1095
|
+
end
|
|
1096
|
+
end
|
|
1097
|
+
|
|
1098
|
+
def apply_aggregate(agg, rows)
|
|
1099
|
+
values = rows.map { |row| row[agg.column.to_s] }.compact
|
|
1100
|
+
|
|
1101
|
+
case agg.name.to_s.upcase
|
|
1102
|
+
when "COUNT" then values.size
|
|
1103
|
+
when "SUM" then values.sum
|
|
1104
|
+
when "AVG" then values.empty? ? 0 : values.sum / values.size.to_f
|
|
1105
|
+
when "MIN" then values.min
|
|
1106
|
+
when "MAX" then values.max
|
|
1107
|
+
end
|
|
1108
|
+
end
|
|
1109
|
+
|
|
1110
|
+
def sort_rows(rows, order_by)
|
|
1111
|
+
rows.sort do |a, b|
|
|
1112
|
+
comparison = 0
|
|
1113
|
+
order_by.each do |order|
|
|
1114
|
+
col = (order.is_a?(Hash) ? order[:column] : order.column).to_s
|
|
1115
|
+
val_a = a[col]
|
|
1116
|
+
val_b = b[col]
|
|
1117
|
+
|
|
1118
|
+
comparison = if val_a.nil? && val_b.nil?
|
|
1119
|
+
0
|
|
1120
|
+
elsif val_a.nil?
|
|
1121
|
+
-1
|
|
1122
|
+
elsif val_b.nil?
|
|
1123
|
+
1
|
|
1124
|
+
else
|
|
1125
|
+
val_a <=> val_b
|
|
1126
|
+
end
|
|
1127
|
+
|
|
1128
|
+
direction = order.is_a?(Hash) ? order[:direction] : order.direction
|
|
1129
|
+
comparison = -comparison if direction == :desc
|
|
1130
|
+
break unless comparison == 0
|
|
1131
|
+
end
|
|
1132
|
+
comparison
|
|
1133
|
+
end
|
|
1134
|
+
end
|
|
1135
|
+
|
|
1136
|
+
def get_column_names(plan)
|
|
1137
|
+
if plan.projections
|
|
1138
|
+
plan.projections.map { |projection| projection_name(projection) }
|
|
1139
|
+
else
|
|
1140
|
+
@engine.table_columns(plan.table_name).map(&:name)
|
|
1141
|
+
end
|
|
1142
|
+
end
|
|
1143
|
+
|
|
1144
|
+
def projection_name(projection)
|
|
1145
|
+
return projection.to_s if projection.is_a?(String) || projection.is_a?(Symbol)
|
|
1146
|
+
|
|
1147
|
+
expression = projection.respond_to?(:expression) ? projection.expression : projection
|
|
1148
|
+
alias_name = if projection.respond_to?(:alias_name)
|
|
1149
|
+
projection.alias_name
|
|
1150
|
+
elsif projection.respond_to?(:alias)
|
|
1151
|
+
projection.alias
|
|
1152
|
+
end
|
|
1153
|
+
return alias_name.to_s if alias_name
|
|
1154
|
+
|
|
1155
|
+
return expression.name.to_s if expression.respond_to?(:name) && expression.name
|
|
1156
|
+
return expression.to_sql if expression.respond_to?(:to_sql)
|
|
1157
|
+
|
|
1158
|
+
expression.to_s
|
|
1159
|
+
end
|
|
1160
|
+
|
|
1161
|
+
def format_plan(plan)
|
|
1162
|
+
lines = []
|
|
1163
|
+
lines << "QUERY PLAN"
|
|
1164
|
+
lines << "=" * 40
|
|
1165
|
+
|
|
1166
|
+
lines << if plan.scan_type == :index
|
|
1167
|
+
"Index Scan on #{plan.table_name} using #{plan.index.name}"
|
|
1168
|
+
else
|
|
1169
|
+
"Seq Scan on #{plan.table_name}"
|
|
1170
|
+
end
|
|
1171
|
+
|
|
1172
|
+
if plan.predicate
|
|
1173
|
+
lines << " Filter: #{plan.predicate}"
|
|
1174
|
+
end
|
|
1175
|
+
|
|
1176
|
+
if plan.order_by&.any?
|
|
1177
|
+
order_str = plan.order_by.map { |o| "#{o.column} #{o.direction}" }.join(", ")
|
|
1178
|
+
lines << " Order By: #{order_str}"
|
|
1179
|
+
end
|
|
1180
|
+
|
|
1181
|
+
if plan.limit
|
|
1182
|
+
lines << " Limit: #{plan.limit}"
|
|
1183
|
+
end
|
|
1184
|
+
|
|
1185
|
+
if plan.offset
|
|
1186
|
+
lines << " Offset: #{plan.offset}"
|
|
1187
|
+
end
|
|
1188
|
+
|
|
1189
|
+
lines << " Estimated Cost: #{plan.estimated_cost}"
|
|
1190
|
+
lines << " Estimated Rows: #{plan.estimated_rows}"
|
|
1191
|
+
lines.join("\n")
|
|
1192
|
+
end
|
|
1193
|
+
end
|
|
1194
|
+
end
|
|
1195
|
+
end
|