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,435 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "set"
|
|
4
|
+
require "concurrent"
|
|
5
|
+
|
|
6
|
+
# Import all transaction components
|
|
7
|
+
require_relative "transaction"
|
|
8
|
+
require_relative "transaction_id"
|
|
9
|
+
require_relative "lock_manager"
|
|
10
|
+
require_relative "commit_manager"
|
|
11
|
+
require_relative "transaction_log"
|
|
12
|
+
require_relative "isolation"
|
|
13
|
+
require_relative "savepoint"
|
|
14
|
+
|
|
15
|
+
# Import engine for rollback operations
|
|
16
|
+
require_relative "../storage/engine"
|
|
17
|
+
|
|
18
|
+
module RubyDB
|
|
19
|
+
module Transactions
|
|
20
|
+
# TransactionManager - Manages all database transactions
|
|
21
|
+
class TransactionManager
|
|
22
|
+
attr_reader :active_transactions, :committed_transactions, :aborted_transactions
|
|
23
|
+
attr_reader :lock_manager, :commit_manager, :stats
|
|
24
|
+
|
|
25
|
+
def initialize(engine, config = {})
|
|
26
|
+
@engine = engine
|
|
27
|
+
@config = config
|
|
28
|
+
@active_transactions = {}
|
|
29
|
+
@committed_transactions = []
|
|
30
|
+
@aborted_transactions = []
|
|
31
|
+
@transaction_log = TransactionLog.new(config[:log_path])
|
|
32
|
+
@lock_manager = LockManager.new(config.merge(transaction_manager: self))
|
|
33
|
+
@commit_manager = CommitManager.new(self, config)
|
|
34
|
+
@stats = {
|
|
35
|
+
transactions_started: 0,
|
|
36
|
+
transactions_committed: 0,
|
|
37
|
+
transactions_aborted: 0,
|
|
38
|
+
transactions_prepared: 0,
|
|
39
|
+
transactions_in_doubt: 0,
|
|
40
|
+
deadlocks_detected: 0,
|
|
41
|
+
deadlocks_resolved: 0,
|
|
42
|
+
lock_timeouts: 0,
|
|
43
|
+
total_commit_time_ms: 0,
|
|
44
|
+
total_rollback_time_ms: 0
|
|
45
|
+
}
|
|
46
|
+
@next_txn_id = 1
|
|
47
|
+
@lock = Mutex.new
|
|
48
|
+
@cleanup_thread = nil
|
|
49
|
+
|
|
50
|
+
# Start cleanup thread
|
|
51
|
+
start_cleanup_thread if config[:auto_cleanup] != false
|
|
52
|
+
|
|
53
|
+
# Recover transactions on startup
|
|
54
|
+
recover if config[:recovery] != false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def begin_transaction(options = {})
|
|
58
|
+
@lock.synchronize do
|
|
59
|
+
txn = Transaction.new({
|
|
60
|
+
id: generate_id,
|
|
61
|
+
isolation_level: options[:isolation_level] || :read_committed,
|
|
62
|
+
read_only: options[:read_only] || false,
|
|
63
|
+
timeout: options[:timeout] || 30,
|
|
64
|
+
priority: options[:priority] || 0,
|
|
65
|
+
name: options[:name]
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
@active_transactions[txn.id] = txn
|
|
69
|
+
@stats[:transactions_started] += 1
|
|
70
|
+
|
|
71
|
+
# Log transaction start
|
|
72
|
+
@transaction_log.log_start(txn)
|
|
73
|
+
|
|
74
|
+
txn
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def commit_transaction(transaction)
|
|
79
|
+
@lock.synchronize do
|
|
80
|
+
return false unless transaction && transaction.active?
|
|
81
|
+
|
|
82
|
+
start_time = Time.now
|
|
83
|
+
|
|
84
|
+
# Check for conflicts
|
|
85
|
+
if detect_conflicts(transaction)
|
|
86
|
+
@stats[:deadlocks_detected] += 1
|
|
87
|
+
return false
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Prepare transaction
|
|
91
|
+
transaction.prepare
|
|
92
|
+
@stats[:transactions_prepared] += 1
|
|
93
|
+
|
|
94
|
+
# Log prepare
|
|
95
|
+
@transaction_log.log_prepare(transaction)
|
|
96
|
+
|
|
97
|
+
# Commit
|
|
98
|
+
@commit_manager.commit(transaction)
|
|
99
|
+
|
|
100
|
+
# Update stats
|
|
101
|
+
elapsed_ms = ((Time.now - start_time) * 1000).round(2)
|
|
102
|
+
@stats[:transactions_committed] += 1
|
|
103
|
+
@stats[:total_commit_time_ms] += elapsed_ms
|
|
104
|
+
|
|
105
|
+
# Log commit
|
|
106
|
+
@transaction_log.log_commit(transaction)
|
|
107
|
+
|
|
108
|
+
# Move to committed
|
|
109
|
+
@active_transactions.delete(transaction.id)
|
|
110
|
+
@committed_transactions << transaction
|
|
111
|
+
|
|
112
|
+
# Clean old committed transactions
|
|
113
|
+
cleanup_committed_transactions if @committed_transactions.size > 1000
|
|
114
|
+
|
|
115
|
+
true
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def rollback_transaction(transaction)
|
|
120
|
+
@lock.synchronize do
|
|
121
|
+
return false unless transaction && transaction.active?
|
|
122
|
+
|
|
123
|
+
start_time = Time.now
|
|
124
|
+
|
|
125
|
+
# Rollback changes
|
|
126
|
+
rollback_changes(transaction)
|
|
127
|
+
|
|
128
|
+
# Abort transaction
|
|
129
|
+
transaction.abort
|
|
130
|
+
@stats[:transactions_aborted] += 1
|
|
131
|
+
|
|
132
|
+
elapsed_ms = ((Time.now - start_time) * 1000).round(2)
|
|
133
|
+
@stats[:total_rollback_time_ms] += elapsed_ms
|
|
134
|
+
|
|
135
|
+
# Log rollback
|
|
136
|
+
@transaction_log.log_rollback(transaction)
|
|
137
|
+
|
|
138
|
+
# Move to aborted
|
|
139
|
+
@active_transactions.delete(transaction.id)
|
|
140
|
+
@aborted_transactions << transaction
|
|
141
|
+
|
|
142
|
+
# Clean old aborted transactions
|
|
143
|
+
cleanup_aborted_transactions if @aborted_transactions.size > 1000
|
|
144
|
+
|
|
145
|
+
true
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def get_transaction(id)
|
|
150
|
+
@lock.synchronize do
|
|
151
|
+
@active_transactions[id] ||
|
|
152
|
+
@committed_transactions.find { |t| t.id == id } ||
|
|
153
|
+
@aborted_transactions.find { |t| t.id == id }
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def active_transaction_count
|
|
158
|
+
@active_transactions.size
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def find_latest_transaction
|
|
162
|
+
@lock.synchronize do
|
|
163
|
+
all = @committed_transactions + @aborted_transactions
|
|
164
|
+
all.max_by { |t| t.start_time }
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def detect_deadlock
|
|
169
|
+
cycles = detect_cycles(build_wait_for_graph)
|
|
170
|
+
return false if cycles.empty?
|
|
171
|
+
|
|
172
|
+
@lock.synchronize { @stats[:deadlocks_detected] += cycles.size }
|
|
173
|
+
cycles.each { |cycle| resolve_deadlock(cycle) }
|
|
174
|
+
true
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def resolve_deadlock(cycle)
|
|
178
|
+
transactions = Array(cycle).filter_map do |entry|
|
|
179
|
+
if entry.respond_to?(:priority)
|
|
180
|
+
entry
|
|
181
|
+
else
|
|
182
|
+
@lock.synchronize { @active_transactions[entry] }
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
victim = @lock.synchronize do
|
|
186
|
+
transactions.min_by { |transaction| transaction.priority.to_i }
|
|
187
|
+
end
|
|
188
|
+
return nil unless victim
|
|
189
|
+
|
|
190
|
+
rollback_transaction(victim)
|
|
191
|
+
@lock.synchronize { @stats[:deadlocks_resolved] += 1 }
|
|
192
|
+
victim
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def acquire_lock(transaction, table_name, row_id, lock_type)
|
|
196
|
+
valid = @lock.synchronize do
|
|
197
|
+
return false unless transaction.active?
|
|
198
|
+
|
|
199
|
+
# Check for timeout
|
|
200
|
+
if transaction.expired?
|
|
201
|
+
@stats[:lock_timeouts] += 1
|
|
202
|
+
return false
|
|
203
|
+
end
|
|
204
|
+
true
|
|
205
|
+
end
|
|
206
|
+
return false unless valid
|
|
207
|
+
|
|
208
|
+
# Do not hold the transaction-manager mutex while a lock wait blocks;
|
|
209
|
+
# deadlock resolution must be able to return through this method.
|
|
210
|
+
result = @lock_manager.acquire_lock(
|
|
211
|
+
transaction,
|
|
212
|
+
table_name,
|
|
213
|
+
row_id,
|
|
214
|
+
lock_type,
|
|
215
|
+
transaction.timeout
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
if result
|
|
219
|
+
transaction.add_locked_row(table_name, row_id, lock_type)
|
|
220
|
+
elsif @lock_manager.deadlock_victim?(transaction.id)
|
|
221
|
+
@lock.synchronize do
|
|
222
|
+
@stats[:deadlocks_detected] += 1
|
|
223
|
+
end
|
|
224
|
+
rollback_transaction(transaction)
|
|
225
|
+
@lock_manager.clear_deadlock_victim(transaction.id)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
result
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def release_locks(transaction)
|
|
232
|
+
@lock.synchronize do
|
|
233
|
+
@lock_manager.release_locks(transaction)
|
|
234
|
+
transaction.locked_rows.clear
|
|
235
|
+
true
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def release_all_locks
|
|
240
|
+
@lock.synchronize do
|
|
241
|
+
@lock_manager.release_all_locks
|
|
242
|
+
@active_transactions.each do |_, txn|
|
|
243
|
+
txn.locked_rows.clear
|
|
244
|
+
end
|
|
245
|
+
true
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def recover
|
|
250
|
+
@lock.synchronize do
|
|
251
|
+
# Recover from transaction log
|
|
252
|
+
recovery = @transaction_log.recover
|
|
253
|
+
entries = recovery[:entries] || []
|
|
254
|
+
|
|
255
|
+
entries.each do |entry|
|
|
256
|
+
case entry[:type]
|
|
257
|
+
when :prepare
|
|
258
|
+
# Transaction was prepared but not committed
|
|
259
|
+
txn = get_transaction(entry[:transaction_id])
|
|
260
|
+
if txn && txn.prepared?
|
|
261
|
+
# Commit if we can
|
|
262
|
+
if can_commit?(txn)
|
|
263
|
+
commit_transaction(txn)
|
|
264
|
+
else
|
|
265
|
+
txn.mark_in_doubt
|
|
266
|
+
@stats[:transactions_in_doubt] += 1
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
when :commit
|
|
270
|
+
# Already committed
|
|
271
|
+
when :rollback
|
|
272
|
+
# Already rolled back
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def stats
|
|
279
|
+
@lock.synchronize do
|
|
280
|
+
@stats.merge({
|
|
281
|
+
active_count: @active_transactions.size,
|
|
282
|
+
committed_count: @committed_transactions.size,
|
|
283
|
+
aborted_count: @aborted_transactions.size,
|
|
284
|
+
in_doubt_count: @active_transactions.count { |_, t| t.in_doubt? },
|
|
285
|
+
avg_commit_time: average_commit_time,
|
|
286
|
+
avg_rollback_time: average_rollback_time,
|
|
287
|
+
locks_held: @lock_manager.total_locks,
|
|
288
|
+
lock_waiting: @lock_manager.waiting_transactions
|
|
289
|
+
})
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
private
|
|
294
|
+
|
|
295
|
+
def generate_id
|
|
296
|
+
"txn_#{Time.now.to_i}_#{@next_txn_id}"
|
|
297
|
+
ensure
|
|
298
|
+
@next_txn_id += 1
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def rollback_changes(transaction)
|
|
302
|
+
# Rollback in reverse order
|
|
303
|
+
transaction.modified_rows.each do |row_id, info|
|
|
304
|
+
@engine.update_row(info[:table], row_id, info[:old])
|
|
305
|
+
end
|
|
306
|
+
transaction.changes.reverse.each do |change|
|
|
307
|
+
undo_change(change)
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def undo_change(change)
|
|
312
|
+
case change[:type]
|
|
313
|
+
when :insert
|
|
314
|
+
# Delete inserted row
|
|
315
|
+
@engine.delete_row(change[:table], change[:row_id])
|
|
316
|
+
when :update
|
|
317
|
+
# Restore old values
|
|
318
|
+
@engine.update_row(change[:table], change[:row_id], change[:old_values])
|
|
319
|
+
when :delete
|
|
320
|
+
# Reinsert deleted row
|
|
321
|
+
@engine.insert_row(change[:table], change[:columns], change[:row_data])
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def detect_conflicts(transaction)
|
|
326
|
+
# Check for write-write conflicts
|
|
327
|
+
transaction.modified_rows.each do |row_id, info|
|
|
328
|
+
@active_transactions.each do |_, other_txn|
|
|
329
|
+
next if other_txn.id == transaction.id
|
|
330
|
+
|
|
331
|
+
if other_txn.modified_rows.key?(row_id)
|
|
332
|
+
return true
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
false
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def build_wait_for_graph
|
|
341
|
+
@lock_manager.wait_for_graph
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def detect_cycles(graph)
|
|
345
|
+
cycles = []
|
|
346
|
+
visited = Set.new
|
|
347
|
+
active = Set.new
|
|
348
|
+
path = []
|
|
349
|
+
|
|
350
|
+
graph.each_key do |node|
|
|
351
|
+
detect_cycle_dfs(node, graph, visited, active, path, cycles) unless visited.include?(node)
|
|
352
|
+
end
|
|
353
|
+
cycles
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def detect_cycle_dfs(node, graph, visited, active, path, cycles)
|
|
357
|
+
if active.include?(node)
|
|
358
|
+
start = path.index(node)
|
|
359
|
+
cycle = path[start..] + [node]
|
|
360
|
+
cycles << cycle unless cycles.include?(cycle)
|
|
361
|
+
return
|
|
362
|
+
end
|
|
363
|
+
return if visited.include?(node)
|
|
364
|
+
|
|
365
|
+
visited.add(node)
|
|
366
|
+
active.add(node)
|
|
367
|
+
path << node
|
|
368
|
+
|
|
369
|
+
graph[node]&.each { |neighbor| detect_cycle_dfs(neighbor, graph, visited, active, path, cycles) }
|
|
370
|
+
|
|
371
|
+
path.pop
|
|
372
|
+
active.delete(node)
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def can_commit?(transaction)
|
|
376
|
+
# Check if all prerequisites are met
|
|
377
|
+
transaction.modified_rows.each do |_, info|
|
|
378
|
+
# Check if no other active transaction modified the same row
|
|
379
|
+
@active_transactions.each do |_, other|
|
|
380
|
+
next if other.id == transaction.id
|
|
381
|
+
if other.modified_rows.key?(info[:row_id])
|
|
382
|
+
return false
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
true
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def cleanup_committed_transactions
|
|
391
|
+
if @committed_transactions.size > 1000
|
|
392
|
+
@committed_transactions = @committed_transactions.last(100)
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def cleanup_aborted_transactions
|
|
397
|
+
if @aborted_transactions.size > 1000
|
|
398
|
+
@aborted_transactions = @aborted_transactions.last(100)
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def average_commit_time
|
|
403
|
+
return 0 if @stats[:transactions_committed] == 0
|
|
404
|
+
@stats[:total_commit_time_ms] / @stats[:transactions_committed]
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def average_rollback_time
|
|
408
|
+
return 0 if @stats[:transactions_aborted] == 0
|
|
409
|
+
@stats[:total_rollback_time_ms] / @stats[:transactions_aborted]
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def start_cleanup_thread
|
|
413
|
+
@cleanup_thread = Thread.new do
|
|
414
|
+
loop do
|
|
415
|
+
sleep(60) # Run every minute
|
|
416
|
+
|
|
417
|
+
begin
|
|
418
|
+
# Cleanup expired transactions
|
|
419
|
+
@active_transactions.each do |id, txn|
|
|
420
|
+
if txn.expired?
|
|
421
|
+
rollback_transaction(txn)
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
# Detect deadlocks
|
|
426
|
+
detect_deadlock
|
|
427
|
+
rescue => e
|
|
428
|
+
# Log error but continue
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyDB
|
|
4
|
+
module Types
|
|
5
|
+
# 64-bit signed integer
|
|
6
|
+
class BigInt < Type
|
|
7
|
+
RANGE = (-9_223_372_036_854_775_808..9_223_372_036_854_775_807).freeze
|
|
8
|
+
SIZE = 8
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
super(:bigint, size: SIZE)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def serialize(value)
|
|
15
|
+
validate(value)
|
|
16
|
+
[value.to_i].pack("q>") # Big-endian 64-bit signed
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def deserialize(bytes)
|
|
20
|
+
return nil if bytes.nil? || bytes.empty?
|
|
21
|
+
bytes.unpack("q>").first
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def validate(value)
|
|
25
|
+
return true if value.nil?
|
|
26
|
+
raise ConstraintError, "Invalid bigint: #{value}" unless value.is_a?(::Integer)
|
|
27
|
+
raise ConstraintError, "BigInt out of range: #{value}" unless RANGE.include?(value)
|
|
28
|
+
true
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def default
|
|
32
|
+
0
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyDB
|
|
4
|
+
module Types
|
|
5
|
+
# Binary large object (arbitrary binary data)
|
|
6
|
+
class Blob < Type
|
|
7
|
+
def initialize
|
|
8
|
+
super(:blob)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def serialize(value)
|
|
12
|
+
validate(value)
|
|
13
|
+
return "".b if value.nil?
|
|
14
|
+
value.is_a?(String) ? value.b : value.to_s.b
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def deserialize(bytes)
|
|
18
|
+
return nil if bytes.nil? || bytes.empty?
|
|
19
|
+
bytes.b
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def validate(value)
|
|
23
|
+
return true if value.nil?
|
|
24
|
+
raise ConstraintError, "Invalid blob: #{value}" unless value.respond_to?(:to_s)
|
|
25
|
+
true
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def default
|
|
29
|
+
"".b
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def storage_size
|
|
33
|
+
nil # Variable length
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyDB
|
|
4
|
+
module Types
|
|
5
|
+
# Boolean type (true/false)
|
|
6
|
+
class Boolean < Type
|
|
7
|
+
SIZE = 1
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
super(:boolean, size: SIZE)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def serialize(value)
|
|
14
|
+
validate(value)
|
|
15
|
+
value ? "\x01".b : "\x00".b
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def deserialize(bytes)
|
|
19
|
+
return nil if bytes.nil? || bytes.empty?
|
|
20
|
+
bytes.unpack("C").first == 1
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def validate(value)
|
|
24
|
+
return true if value.nil?
|
|
25
|
+
raise ConstraintError, "Invalid boolean: #{value}" unless [true, false].include?(value)
|
|
26
|
+
true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def default
|
|
30
|
+
false
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
|
|
5
|
+
module RubyDB
|
|
6
|
+
module Types
|
|
7
|
+
# Date type (year, month, day)
|
|
8
|
+
class Date < Type
|
|
9
|
+
SIZE = 4 # Days since epoch (stored as integer)
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
super(:date, size: SIZE)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def serialize(value)
|
|
16
|
+
validate(value)
|
|
17
|
+
return "\x00" * SIZE if value.nil?
|
|
18
|
+
days = value - Date.new(1970, 1, 1)
|
|
19
|
+
[days.to_i].pack("l>")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def deserialize(bytes)
|
|
23
|
+
return nil if bytes.nil? || bytes.empty?
|
|
24
|
+
days = bytes.unpack("l>").first
|
|
25
|
+
Date.new(1970, 1, 1) + days
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def validate(value)
|
|
29
|
+
return true if value.nil?
|
|
30
|
+
raise ConstraintError, "Invalid date: #{value}" unless value.is_a?(::Date) || value.is_a?(::DateTime)
|
|
31
|
+
true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def default
|
|
35
|
+
Date.today
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bigdecimal"
|
|
4
|
+
|
|
5
|
+
module RubyDB
|
|
6
|
+
module Types
|
|
7
|
+
# Arbitrary precision decimal
|
|
8
|
+
class Decimal < Type
|
|
9
|
+
attr_reader :precision, :scale
|
|
10
|
+
|
|
11
|
+
def initialize(precision: 10, scale: 2)
|
|
12
|
+
super(:decimal, precision: precision, scale: scale)
|
|
13
|
+
@precision = precision
|
|
14
|
+
@scale = scale
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def serialize(value)
|
|
18
|
+
validate(value)
|
|
19
|
+
bd = value.is_a?(BigDecimal) ? value : BigDecimal(value.to_s)
|
|
20
|
+
bd.to_s("F").encode("UTF-8")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def deserialize(bytes)
|
|
24
|
+
return nil if bytes.nil? || bytes.empty?
|
|
25
|
+
str = bytes.force_encoding("UTF-8")
|
|
26
|
+
BigDecimal(str)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def validate(value)
|
|
30
|
+
return true if value.nil?
|
|
31
|
+
bd = value.is_a?(BigDecimal) ? value : BigDecimal(value.to_s)
|
|
32
|
+
raise ConstraintError, "Invalid decimal: #{value}" unless bd
|
|
33
|
+
true
|
|
34
|
+
rescue ArgumentError, TypeError
|
|
35
|
+
raise ConstraintError, "Invalid decimal: #{value}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def default
|
|
39
|
+
BigDecimal("0")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def storage_size
|
|
43
|
+
# Variable length, stored as string
|
|
44
|
+
nil
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyDB
|
|
4
|
+
module Types
|
|
5
|
+
# 64-bit floating point (Double precision)
|
|
6
|
+
class Float < Type
|
|
7
|
+
SIZE = 8
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
super(:float, size: SIZE)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def serialize(value)
|
|
14
|
+
validate(value)
|
|
15
|
+
[value.to_f].pack("E") # Big-endian double
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def deserialize(bytes)
|
|
19
|
+
return nil if bytes.nil? || bytes.empty?
|
|
20
|
+
bytes.unpack("E").first
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def validate(value)
|
|
24
|
+
return true if value.nil?
|
|
25
|
+
raise ConstraintError, "Invalid float: #{value}" unless value.is_a?(::Numeric)
|
|
26
|
+
true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def default
|
|
30
|
+
0.0
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyDB
|
|
4
|
+
module Types
|
|
5
|
+
# 32-bit signed integer
|
|
6
|
+
class Integer < Type
|
|
7
|
+
RANGE = (-2_147_483_648..2_147_483_647).freeze
|
|
8
|
+
SIZE = 4
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
super(:integer, size: SIZE)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def serialize(value)
|
|
15
|
+
validate(value)
|
|
16
|
+
[value.to_i].pack("l>") # Big-endian 32-bit signed
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def deserialize(bytes)
|
|
20
|
+
return nil if bytes.nil? || bytes.empty?
|
|
21
|
+
bytes.unpack("l>").first
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def validate(value)
|
|
25
|
+
return true if value.nil?
|
|
26
|
+
raise ConstraintError, "Invalid integer: #{value}" unless value.is_a?(::Integer)
|
|
27
|
+
raise ConstraintError, "Integer out of range: #{value}" unless RANGE.include?(value)
|
|
28
|
+
true
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def default
|
|
32
|
+
0
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|