rubydb 0.1.0
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 +7 -0
- data/.gitattributes +20 -0
- data/.github/ISSUE_TEMPLATE/bug.yml +0 -0
- data/.github/ISSUE_TEMPLATE/feature.yml +0 -0
- data/.github/ISSUE_TEMPLATE/performance.yml +0 -0
- data/.github/ISSUE_TEMPLATE/security.yml +0 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +15 -0
- data/.github/dependabot.yml +0 -0
- data/.github/workflows/benchmark.yml +26 -0
- data/.github/workflows/compatibility.yml +63 -0
- data/.github/workflows/fuzz.yml +33 -0
- data/.github/workflows/lint.yml +21 -0
- data/.github/workflows/operations.yml +24 -0
- data/.github/workflows/production-validation.yml +111 -0
- data/.github/workflows/release.yml +77 -0
- data/.github/workflows/security.yml +37 -0
- data/.github/workflows/test.yml +26 -0
- data/.github/workflows/workload.yml +58 -0
- data/.gitignore +43 -0
- data/.rspec +4 -0
- data/.rubocop.yml +44 -0
- data/.ruby-version +1 -0
- data/.standard.yml +14 -0
- data/ARCHITECTURE.md +21 -0
- data/CHANGELOG.md +31 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/CONTRIBUTING.md +29 -0
- data/GOVERNANCE.md +16 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +71 -0
- data/LICENSE +60 -0
- data/README.md +162 -0
- data/ROADMAP.md +27 -0
- data/Rakefile +71 -0
- data/SECURITY.md +54 -0
- data/SUPPORT.md +14 -0
- data/adapters/activerecord/Gemfile +11 -0
- data/adapters/activerecord/README.md +9 -0
- data/adapters/activerecord/lib/active_record/connection_adapters/rubydb_adapter.rb +887 -0
- data/adapters/activerecord/rubydb-activerecord.gemspec +27 -0
- data/adapters/activerecord/spec/rubydb_adapter_integration_spec.rb +151 -0
- data/adapters/ruby/README.md +18 -0
- data/adapters/sequel/README.md +11 -0
- data/bin/rubydb +19 -0
- data/bin/rubydb-server +126 -0
- data/config/development.yml +0 -0
- data/config/monitoring/prometheus-alerts.yml +39 -0
- data/config/production.yml +36 -0
- data/config/test.yml +0 -0
- data/docs/README.md +71 -0
- data/docs/architecture/concurrency.md +14 -0
- data/docs/architecture/current-state.md +125 -0
- data/docs/architecture/execution-engine.md +25 -0
- data/docs/architecture/indexes.md +19 -0
- data/docs/architecture/mvcc.md +19 -0
- data/docs/architecture/overview.md +13 -0
- data/docs/architecture/pages.md +11 -0
- data/docs/architecture/production-roadmap.md +82 -0
- data/docs/architecture/query-planner.md +20 -0
- data/docs/architecture/recovery.md +18 -0
- data/docs/architecture/sql-engine.md +12 -0
- data/docs/architecture/storage-engine.md +14 -0
- data/docs/architecture/transactions.md +10 -0
- data/docs/architecture/wal.md +28 -0
- data/docs/cli-cheatsheet.md +98 -0
- data/docs/cli.md +275 -0
- data/docs/contributing/architecture.md +9 -0
- data/docs/contributing/benchmarking.md +14 -0
- data/docs/contributing/development.md +16 -0
- data/docs/contributing/release-process.md +49 -0
- data/docs/contributing/testing.md +16 -0
- data/docs/debugging.md +229 -0
- data/docs/developer/branching.md +10 -0
- data/docs/developer/database-diff.md +10 -0
- data/docs/developer/local-development.md +17 -0
- data/docs/developer/snapshots.md +9 -0
- data/docs/developer/temporal-data.md +10 -0
- data/docs/developer-guide.md +297 -0
- data/docs/getting-started/first-database.md +16 -0
- data/docs/getting-started/first-query.md +13 -0
- data/docs/getting-started/installation.md +19 -0
- data/docs/getting-started/local-to-production.md +300 -0
- data/docs/getting-started/quickstart.md +17 -0
- data/docs/getting-started/rails.md +16 -0
- data/docs/hardening_backlog.md +93 -0
- data/docs/lessons-learned.md +112 -0
- data/docs/operations/backups.md +33 -0
- data/docs/operations/disaster-recovery.md +31 -0
- data/docs/operations/failover.md +30 -0
- data/docs/operations/monitoring.md +25 -0
- data/docs/operations/production-guide.md +295 -0
- data/docs/operations/production-runbook.md +45 -0
- data/docs/operations/replication.md +33 -0
- data/docs/operations/restore.md +6 -0
- data/docs/operations/runbook.md +34 -0
- data/docs/operations/upgrades.md +14 -0
- data/docs/operations/workload-testing.md +17 -0
- data/docs/production-readiness.md +118 -0
- data/docs/production_validation.md +150 -0
- data/docs/rails/active-record.md +11 -0
- data/docs/rails/compatibility-guide.md +90 -0
- data/docs/rails/database-yml.md +92 -0
- data/docs/rails/installation.md +17 -0
- data/docs/rails/migrations.md +17 -0
- data/docs/rails/production.md +82 -0
- data/docs/rails/troubleshooting.md +18 -0
- data/docs/release.md +59 -0
- data/docs/server/architecture.md +10 -0
- data/docs/server/authentication.md +10 -0
- data/docs/server/configuration.md +16 -0
- data/docs/server/connection-pooling.md +10 -0
- data/docs/server/deployment.md +10 -0
- data/docs/server/protocol.md +12 -0
- data/docs/sql/compatibility-guide.md +82 -0
- data/docs/sql/compatibility.md +39 -0
- data/docs/sql/data-types.md +10 -0
- data/docs/sql/functions.md +9 -0
- data/docs/sql/joins.md +9 -0
- data/docs/sql/operators.md +9 -0
- data/docs/sql/sqlite-compatibility.md +21 -0
- data/docs/sql/syntax.md +10 -0
- data/docs/sql/transactions.md +10 -0
- data/docs/troubleshooting.md +244 -0
- data/exe/rubydb +23 -0
- data/lib/rubydb/backup/archive.rb +334 -0
- data/lib/rubydb/backup/backup.rb +401 -0
- data/lib/rubydb/backup/incremental.rb +353 -0
- data/lib/rubydb/backup/restore.rb +290 -0
- data/lib/rubydb/backup/snapshot.rb +267 -0
- data/lib/rubydb/backup/verification.rb +279 -0
- data/lib/rubydb/branching/branch.rb +181 -0
- data/lib/rubydb/branching/branch_manager.rb +311 -0
- data/lib/rubydb/branching/branch_metadata.rb +140 -0
- data/lib/rubydb/branching/checkout.rb +166 -0
- data/lib/rubydb/branching/copy_on_write.rb +272 -0
- data/lib/rubydb/branching/diff.rb +138 -0
- data/lib/rubydb/branching/merge.rb +285 -0
- data/lib/rubydb/build_info.rb +15 -0
- data/lib/rubydb/catalog/catalog.rb +391 -0
- data/lib/rubydb/catalog/column.rb +112 -0
- data/lib/rubydb/catalog/constraint.rb +180 -0
- data/lib/rubydb/catalog/database.rb +184 -0
- data/lib/rubydb/catalog/index.rb +97 -0
- data/lib/rubydb/catalog/schema.rb +103 -0
- data/lib/rubydb/catalog/sequence.rb +90 -0
- data/lib/rubydb/catalog/system_catalog.rb +698 -0
- data/lib/rubydb/catalog/table.rb +178 -0
- data/lib/rubydb/catalog/trigger.rb +102 -0
- data/lib/rubydb/catalog/view.rb +66 -0
- data/lib/rubydb/cli/application.rb +163 -0
- data/lib/rubydb/cli/commands/backup.rb +81 -0
- data/lib/rubydb/cli/commands/branch.rb +72 -0
- data/lib/rubydb/cli/commands/checkout.rb +54 -0
- data/lib/rubydb/cli/commands/create.rb +58 -0
- data/lib/rubydb/cli/commands/diff.rb +77 -0
- data/lib/rubydb/cli/commands/doctor.rb +74 -0
- data/lib/rubydb/cli/commands/drop.rb +57 -0
- data/lib/rubydb/cli/commands/init.rb +102 -0
- data/lib/rubydb/cli/commands/inspect.rb +95 -0
- data/lib/rubydb/cli/commands/merge.rb +63 -0
- data/lib/rubydb/cli/commands/migrate.rb +62 -0
- data/lib/rubydb/cli/commands/restart.rb +39 -0
- data/lib/rubydb/cli/commands/restore.rb +121 -0
- data/lib/rubydb/cli/commands/shell.rb +365 -0
- data/lib/rubydb/cli/commands/snapshot.rb +79 -0
- data/lib/rubydb/cli/commands/start.rb +82 -0
- data/lib/rubydb/cli/commands/status.rb +92 -0
- data/lib/rubydb/cli/commands/stop.rb +47 -0
- data/lib/rubydb/cli/commands/vacuum.rb +58 -0
- data/lib/rubydb/cli/formatter.rb +221 -0
- data/lib/rubydb/cli/output.rb +168 -0
- data/lib/rubydb/client/client.rb +304 -0
- data/lib/rubydb/client/connection.rb +415 -0
- data/lib/rubydb/client/connection_pool.rb +168 -0
- data/lib/rubydb/client/connection_url.rb +96 -0
- data/lib/rubydb/client/prepared_statement.rb +60 -0
- data/lib/rubydb/client/result.rb +123 -0
- data/lib/rubydb/client/statement.rb +52 -0
- data/lib/rubydb/client/transaction.rb +130 -0
- data/lib/rubydb/concurrency/concurrency.rb +19 -0
- data/lib/rubydb/concurrency/deadlock_detector.rb +150 -0
- data/lib/rubydb/concurrency/latch.rb +101 -0
- data/lib/rubydb/concurrency/lock_graph.rb +165 -0
- data/lib/rubydb/concurrency/mutex.rb +183 -0
- data/lib/rubydb/concurrency/rw_lock.rb +180 -0
- data/lib/rubydb/concurrency/scheduler.rb +250 -0
- data/lib/rubydb/concurrency/worker_pool.rb +143 -0
- data/lib/rubydb/configuration/config.rb +170 -0
- data/lib/rubydb/configuration/defaults.rb +179 -0
- data/lib/rubydb/configuration/environment.rb +152 -0
- data/lib/rubydb/configuration/parser.rb +185 -0
- data/lib/rubydb/configuration/validation.rb +221 -0
- data/lib/rubydb/constants.rb +74 -0
- data/lib/rubydb/constraints/check.rb +181 -0
- data/lib/rubydb/constraints/constraint.rb +101 -0
- data/lib/rubydb/constraints/foreign_key.rb +130 -0
- data/lib/rubydb/constraints/not_null.rb +64 -0
- data/lib/rubydb/constraints/primary_key.rb +99 -0
- data/lib/rubydb/constraints/unique.rb +108 -0
- data/lib/rubydb/constraints/validator.rb +350 -0
- data/lib/rubydb/errors/authentication_error.rb +10 -0
- data/lib/rubydb/errors/authorization_error.rb +23 -0
- data/lib/rubydb/errors/client_error.rb +10 -0
- data/lib/rubydb/errors/configuration_error.rb +10 -0
- data/lib/rubydb/errors/connection_error.rb +10 -0
- data/lib/rubydb/errors/constraint_error.rb +23 -0
- data/lib/rubydb/errors/corruption_error.rb +10 -0
- data/lib/rubydb/errors/database_error.rb +10 -0
- data/lib/rubydb/errors/error.rb +20 -0
- data/lib/rubydb/errors/execution_error.rb +10 -0
- data/lib/rubydb/errors/parser_error.rb +10 -0
- data/lib/rubydb/errors/recovery_error.rb +10 -0
- data/lib/rubydb/errors/replication_error.rb +10 -0
- data/lib/rubydb/errors/server_error.rb +6 -0
- data/lib/rubydb/errors/storage_error.rb +10 -0
- data/lib/rubydb/errors/transaction_error.rb +10 -0
- data/lib/rubydb/execution/aggregate_executor.rb +138 -0
- data/lib/rubydb/execution/delete_executor.rb +112 -0
- data/lib/rubydb/execution/distinct_executor.rb +135 -0
- data/lib/rubydb/execution/executor.rb +1192 -0
- data/lib/rubydb/execution/expression.rb +193 -0
- data/lib/rubydb/execution/index_scan.rb +142 -0
- data/lib/rubydb/execution/insert_executor.rb +217 -0
- data/lib/rubydb/execution/join_executor.rb +249 -0
- data/lib/rubydb/execution/limit_executor.rb +85 -0
- data/lib/rubydb/execution/optimizer.rb +215 -0
- data/lib/rubydb/execution/plan.rb +361 -0
- data/lib/rubydb/execution/planner.rb +546 -0
- data/lib/rubydb/execution/predicate.rb +235 -0
- data/lib/rubydb/execution/scan.rb +49 -0
- data/lib/rubydb/execution/sequential_scan.rb +63 -0
- data/lib/rubydb/execution/sort_executor.rb +185 -0
- data/lib/rubydb/execution/update_executor.rb +162 -0
- data/lib/rubydb/functions/aggregate.rb +70 -0
- data/lib/rubydb/functions/date_functions.rb +278 -0
- data/lib/rubydb/functions/function.rb +85 -0
- data/lib/rubydb/functions/json_functions.rb +215 -0
- data/lib/rubydb/functions/numeric_functions.rb +346 -0
- data/lib/rubydb/functions/scalar.rb +52 -0
- data/lib/rubydb/functions/string_functions.rb +383 -0
- data/lib/rubydb/functions/system_functions.rb +246 -0
- data/lib/rubydb/history/as_of.rb +238 -0
- data/lib/rubydb/history/change.rb +105 -0
- data/lib/rubydb/history/history.rb +131 -0
- data/lib/rubydb/history/history_manager.rb +229 -0
- data/lib/rubydb/history/temporal_query.rb +202 -0
- data/lib/rubydb/history/timeline.rb +144 -0
- data/lib/rubydb/indexes/btree.rb +186 -0
- data/lib/rubydb/indexes/btree_cursor.rb +258 -0
- data/lib/rubydb/indexes/btree_node.rb +385 -0
- data/lib/rubydb/indexes/hash_index.rb +150 -0
- data/lib/rubydb/indexes/index.rb +71 -0
- data/lib/rubydb/indexes/index_manager.rb +406 -0
- data/lib/rubydb/indexes/index_scan.rb +470 -0
- data/lib/rubydb/migrations/migration.rb +254 -0
- data/lib/rubydb/migrations/migration_lock.rb +146 -0
- data/lib/rubydb/migrations/migration_manager.rb +176 -0
- data/lib/rubydb/migrations/migration_version.rb +71 -0
- data/lib/rubydb/migrations/schema_diff.rb +211 -0
- data/lib/rubydb/migrations/schema_version.rb +64 -0
- data/lib/rubydb/monitoring/events.rb +160 -0
- data/lib/rubydb/monitoring/health.rb +222 -0
- data/lib/rubydb/monitoring/logger.rb +193 -0
- data/lib/rubydb/monitoring/metrics.rb +359 -0
- data/lib/rubydb/monitoring/performance.rb +176 -0
- data/lib/rubydb/monitoring/statistics.rb +170 -0
- data/lib/rubydb/mvcc/garbage_collector.rb +199 -0
- data/lib/rubydb/mvcc/mvcc.rb +16 -0
- data/lib/rubydb/mvcc/snapshot.rb +147 -0
- data/lib/rubydb/mvcc/vacuum.rb +180 -0
- data/lib/rubydb/mvcc/version.rb +106 -0
- data/lib/rubydb/mvcc/version_store.rb +398 -0
- data/lib/rubydb/mvcc/visibility.rb +109 -0
- data/lib/rubydb/protocol/capabilities.rb +125 -0
- data/lib/rubydb/protocol/decoder.rb +145 -0
- data/lib/rubydb/protocol/encoder.rb +136 -0
- data/lib/rubydb/protocol/handshake.rb +305 -0
- data/lib/rubydb/protocol/message.rb +121 -0
- data/lib/rubydb/protocol/protocol.rb +277 -0
- data/lib/rubydb/protocol/version.rb +54 -0
- data/lib/rubydb/rails/adapter.rb +239 -0
- data/lib/rubydb/rails/connection.rb +314 -0
- data/lib/rubydb/rails/database_statements.rb +122 -0
- data/lib/rubydb/rails/migration.rb +131 -0
- data/lib/rubydb/rails/quoting.rb +109 -0
- data/lib/rubydb/rails/result.rb +120 -0
- data/lib/rubydb/rails/schema_statements.rb +339 -0
- data/lib/rubydb/rails/transaction.rb +105 -0
- data/lib/rubydb/rails/type.rb +126 -0
- data/lib/rubydb/recovery/checkpoint.rb +257 -0
- data/lib/rubydb/recovery/consistency.rb +467 -0
- data/lib/rubydb/recovery/corruption_detector.rb +5 -0
- data/lib/rubydb/recovery/crash_recovery.rb +387 -0
- data/lib/rubydb/recovery/recovery_manager.rb +206 -0
- data/lib/rubydb/recovery/redo.rb +237 -0
- data/lib/rubydb/recovery/undo.rb +206 -0
- data/lib/rubydb/replication/failover.rb +5 -0
- data/lib/rubydb/replication/fencing.rb +63 -0
- data/lib/rubydb/replication/primary.rb +450 -0
- data/lib/rubydb/replication/replica.rb +384 -0
- data/lib/rubydb/replication/replication_log.rb +200 -0
- data/lib/rubydb/replication/replication_manager.rb +308 -0
- data/lib/rubydb/replication/replication_slot.rb +295 -0
- data/lib/rubydb/replication/replication_stream.rb +201 -0
- data/lib/rubydb/rubydb.rb +560 -0
- data/lib/rubydb/security/access_control.rb +254 -0
- data/lib/rubydb/security/audit_log.rb +213 -0
- data/lib/rubydb/security/authentication.rb +302 -0
- data/lib/rubydb/security/authorization.rb +282 -0
- data/lib/rubydb/security/credentials.rb +196 -0
- data/lib/rubydb/security/password.rb +215 -0
- data/lib/rubydb/security/permissions.rb +74 -0
- data/lib/rubydb/security/role.rb +101 -0
- data/lib/rubydb/security/user.rb +86 -0
- data/lib/rubydb/server/connection.rb +366 -0
- data/lib/rubydb/server/connection_pool.rb +193 -0
- data/lib/rubydb/server/lifecycle.rb +228 -0
- data/lib/rubydb/server/listener.rb +136 -0
- data/lib/rubydb/server/request_handler.rb +276 -0
- data/lib/rubydb/server/server.rb +364 -0
- data/lib/rubydb/server/session.rb +369 -0
- data/lib/rubydb/server/worker.rb +210 -0
- data/lib/rubydb/server/worker_pool.rb +168 -0
- data/lib/rubydb/sql/ast/alter_table.rb +169 -0
- data/lib/rubydb/sql/ast/begin_transaction.rb +47 -0
- data/lib/rubydb/sql/ast/commit.rb +37 -0
- data/lib/rubydb/sql/ast/constraint.rb +83 -0
- data/lib/rubydb/sql/ast/create_database.rb +41 -0
- data/lib/rubydb/sql/ast/create_index.rb +61 -0
- data/lib/rubydb/sql/ast/create_schema.rb +52 -0
- data/lib/rubydb/sql/ast/create_table.rb +187 -0
- data/lib/rubydb/sql/ast/delete.rb +54 -0
- data/lib/rubydb/sql/ast/drop_database.rb +41 -0
- data/lib/rubydb/sql/ast/drop_index.rb +41 -0
- data/lib/rubydb/sql/ast/drop_schema.rb +49 -0
- data/lib/rubydb/sql/ast/drop_table.rb +49 -0
- data/lib/rubydb/sql/ast/explain.rb +64 -0
- data/lib/rubydb/sql/ast/expression.rb +604 -0
- data/lib/rubydb/sql/ast/insert.rb +82 -0
- data/lib/rubydb/sql/ast/node.rb +42 -0
- data/lib/rubydb/sql/ast/rollback.rb +63 -0
- data/lib/rubydb/sql/ast/savepoint.rb +59 -0
- data/lib/rubydb/sql/ast/select.rb +88 -0
- data/lib/rubydb/sql/ast/set_operation.rb +20 -0
- data/lib/rubydb/sql/ast/trigger.rb +29 -0
- data/lib/rubydb/sql/ast/update.rb +88 -0
- data/lib/rubydb/sql/ast/vacuum.rb +19 -0
- data/lib/rubydb/sql/ast/view.rb +32 -0
- data/lib/rubydb/sql/ast/with.rb +32 -0
- data/lib/rubydb/sql/grammar.rb +86 -0
- data/lib/rubydb/sql/keywords.rb +156 -0
- data/lib/rubydb/sql/lexer.rb +214 -0
- data/lib/rubydb/sql/operators.rb +100 -0
- data/lib/rubydb/sql/parser.rb +1185 -0
- data/lib/rubydb/sql/planner/analyzer.rb +302 -0
- data/lib/rubydb/sql/planner/binder.rb +550 -0
- data/lib/rubydb/sql/planner/type_checker.rb +431 -0
- data/lib/rubydb/sql/token.rb +210 -0
- data/lib/rubydb/storage/buffer_frame.rb +44 -0
- data/lib/rubydb/storage/buffer_pool.rb +155 -0
- data/lib/rubydb/storage/database_lock.rb +74 -0
- data/lib/rubydb/storage/deserializer.rb +342 -0
- data/lib/rubydb/storage/engine.rb +2338 -0
- data/lib/rubydb/storage/file_manager.rb +187 -0
- data/lib/rubydb/storage/free_space_map.rb +81 -0
- data/lib/rubydb/storage/page.rb +94 -0
- data/lib/rubydb/storage/page_allocator.rb +855 -0
- data/lib/rubydb/storage/page_header.rb +67 -0
- data/lib/rubydb/storage/page_manager.rb +131 -0
- data/lib/rubydb/storage/record.rb +58 -0
- data/lib/rubydb/storage/row.rb +78 -0
- data/lib/rubydb/storage/serializer.rb +51 -0
- data/lib/rubydb/storage/storage_layout.rb +151 -0
- data/lib/rubydb/storage/storage_manager.rb +114 -0
- data/lib/rubydb/storage/tuple.rb +461 -0
- data/lib/rubydb/storage/visibility_map.rb +984 -0
- data/lib/rubydb/transactions/commit_manager.rb +220 -0
- data/lib/rubydb/transactions/isolation.rb +98 -0
- data/lib/rubydb/transactions/lock.rb +76 -0
- data/lib/rubydb/transactions/lock_manager.rb +362 -0
- data/lib/rubydb/transactions/savepoint.rb +143 -0
- data/lib/rubydb/transactions/transaction.rb +215 -0
- data/lib/rubydb/transactions/transaction_id.rb +84 -0
- data/lib/rubydb/transactions/transaction_log.rb +257 -0
- data/lib/rubydb/transactions/transaction_manager.rb +435 -0
- data/lib/rubydb/types/bigint.rb +36 -0
- data/lib/rubydb/types/blob.rb +37 -0
- data/lib/rubydb/types/boolean.rb +34 -0
- data/lib/rubydb/types/date.rb +39 -0
- data/lib/rubydb/types/decimal.rb +48 -0
- data/lib/rubydb/types/float.rb +34 -0
- data/lib/rubydb/types/integer.rb +36 -0
- data/lib/rubydb/types/json.rb +41 -0
- data/lib/rubydb/types/null.rb +34 -0
- data/lib/rubydb/types/smallint.rb +36 -0
- data/lib/rubydb/types/text.rb +37 -0
- data/lib/rubydb/types/time.rb +46 -0
- data/lib/rubydb/types/timestamp.rb +39 -0
- data/lib/rubydb/types/type.rb +119 -0
- data/lib/rubydb/types/uuid.rb +47 -0
- data/lib/rubydb/types/varchar.rb +37 -0
- data/lib/rubydb/version.rb +33 -0
- data/lib/rubydb/wal/archive.rb +193 -0
- data/lib/rubydb/wal/checkpoint.rb +183 -0
- data/lib/rubydb/wal/lsn.rb +94 -0
- data/lib/rubydb/wal/reader.rb +260 -0
- data/lib/rubydb/wal/record.rb +105 -0
- data/lib/rubydb/wal/segment.rb +193 -0
- data/lib/rubydb/wal/wal.rb +452 -0
- data/lib/rubydb/wal/writer.rb +236 -0
- data/lib/rubydb.rb +7 -0
- data/packaging/docker/Dockerfile +36 -0
- data/packaging/docker/docker-compose.failover.yml +43 -0
- data/packaging/docker/entrypoint.sh +15 -0
- data/packaging/homebrew/rubydb.rb +19 -0
- data/packaging/systemd/rubydb.service +22 -0
- data/rubydb.gemspec +57 -0
- data/scripts/benchmark +7 -0
- data/scripts/durability_drill +37 -0
- data/scripts/format +0 -0
- data/scripts/fuzz +63 -0
- data/scripts/lint +0 -0
- data/scripts/release +50 -0
- data/scripts/release_check +43 -0
- data/scripts/replication_failover_drill +250 -0
- data/scripts/replication_network_failover_drill +255 -0
- data/scripts/restore_drill +45 -0
- data/scripts/security +0 -0
- data/scripts/setup +0 -0
- data/scripts/test +0 -0
- data/scripts/test-chaos +0 -0
- data/scripts/test-crash +0 -0
- data/scripts/test-integration +0 -0
- data/scripts/test-unit +0 -0
- data/visibility_map.json +1 -0
- metadata +538 -0
|
@@ -0,0 +1,550 @@
|
|
|
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
|