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,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module RubyDB
|
|
6
|
+
module Types
|
|
7
|
+
# JSON type (stores JSON data)
|
|
8
|
+
class Json < Type
|
|
9
|
+
def initialize
|
|
10
|
+
super(:json)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def serialize(value)
|
|
14
|
+
validate(value)
|
|
15
|
+
return "null".encode("UTF-8") if value.nil?
|
|
16
|
+
JSON.generate(value)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def deserialize(bytes)
|
|
20
|
+
return nil if bytes.nil? || bytes.empty?
|
|
21
|
+
JSON.parse(bytes.force_encoding("UTF-8"))
|
|
22
|
+
rescue JSON::ParserError
|
|
23
|
+
nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def validate(value)
|
|
27
|
+
return true if value.nil?
|
|
28
|
+
raise ConstraintError, "Invalid JSON: #{value}" unless value.is_a?(Hash) || value.is_a?(Array)
|
|
29
|
+
true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def default
|
|
33
|
+
{}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def storage_size
|
|
37
|
+
nil # Variable length
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyDB
|
|
4
|
+
module Types
|
|
5
|
+
# NULL type (represents absence of value)
|
|
6
|
+
class Null < Type
|
|
7
|
+
SIZE = 0
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
super(:null, size: SIZE)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def serialize(value)
|
|
14
|
+
""
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def deserialize(bytes)
|
|
18
|
+
nil
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def validate(value)
|
|
22
|
+
true # Always valid
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def default
|
|
26
|
+
nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def storage_size
|
|
30
|
+
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
|
+
# 16-bit signed integer
|
|
6
|
+
class SmallInt < Type
|
|
7
|
+
RANGE = (-32_768..32_767).freeze
|
|
8
|
+
SIZE = 2
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
super(:smallint, size: SIZE)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def serialize(value)
|
|
15
|
+
validate(value)
|
|
16
|
+
[value.to_i].pack("s>") # Big-endian 16-bit signed
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def deserialize(bytes)
|
|
20
|
+
return nil if bytes.nil? || bytes.empty?
|
|
21
|
+
bytes.unpack("s>").first
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def validate(value)
|
|
25
|
+
return true if value.nil?
|
|
26
|
+
raise ConstraintError, "Invalid smallint: #{value}" unless value.is_a?(::Integer)
|
|
27
|
+
raise ConstraintError, "SmallInt 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
|
+
# Variable-length text string (unlimited)
|
|
6
|
+
class Text < Type
|
|
7
|
+
def initialize
|
|
8
|
+
super(:text)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def serialize(value)
|
|
12
|
+
validate(value)
|
|
13
|
+
return "" if value.nil?
|
|
14
|
+
value.to_s.encode("UTF-8")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def deserialize(bytes)
|
|
18
|
+
return nil if bytes.nil? || bytes.empty?
|
|
19
|
+
bytes.force_encoding("UTF-8")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def validate(value)
|
|
23
|
+
return true if value.nil?
|
|
24
|
+
raise ConstraintError, "Invalid text: #{value}" unless value.respond_to?(:to_s)
|
|
25
|
+
true
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def default
|
|
29
|
+
""
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def storage_size
|
|
33
|
+
nil # Variable length
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
|
|
5
|
+
module RubyDB
|
|
6
|
+
module Types
|
|
7
|
+
# Time type (hour, minute, second)
|
|
8
|
+
class Time < Type
|
|
9
|
+
SIZE = 8 # Microseconds since midnight
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
super(:time, size: SIZE)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def serialize(value)
|
|
16
|
+
validate(value)
|
|
17
|
+
return "\x00" * SIZE if value.nil?
|
|
18
|
+
seconds = value.hour * 3600 + value.min * 60 + value.sec
|
|
19
|
+
microseconds = value.usec
|
|
20
|
+
total = seconds * 1_000_000 + microseconds
|
|
21
|
+
[total].pack("q>")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def deserialize(bytes)
|
|
25
|
+
return nil if bytes.nil? || bytes.empty?
|
|
26
|
+
total = bytes.unpack("q>").first
|
|
27
|
+
seconds = total / 1_000_000
|
|
28
|
+
microseconds = total % 1_000_000
|
|
29
|
+
hour = seconds / 3600
|
|
30
|
+
minute = (seconds % 3600) / 60
|
|
31
|
+
sec = seconds % 60
|
|
32
|
+
::Time.new(1970, 1, 1, hour, minute, sec, microseconds)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def validate(value)
|
|
36
|
+
return true if value.nil?
|
|
37
|
+
raise ConstraintError, "Invalid time: #{value}" unless value.is_a?(::Time) || value.is_a?(::DateTime)
|
|
38
|
+
true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def default
|
|
42
|
+
::Time.now
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
|
|
5
|
+
module RubyDB
|
|
6
|
+
module Types
|
|
7
|
+
# Timestamp (date + time with timezone)
|
|
8
|
+
class Timestamp < Type
|
|
9
|
+
SIZE = 8 # Seconds since epoch (Unix timestamp)
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
super(:timestamp, size: SIZE)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def serialize(value)
|
|
16
|
+
value = ::Time.parse(value) if value.is_a?(String)
|
|
17
|
+
validate(value)
|
|
18
|
+
return "\x00" * SIZE if value.nil?
|
|
19
|
+
[value.to_i].pack("q>")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def deserialize(bytes)
|
|
23
|
+
return nil if bytes.nil? || bytes.empty?
|
|
24
|
+
seconds = bytes.unpack("q>").first
|
|
25
|
+
::Time.at(seconds)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def validate(value)
|
|
29
|
+
return true if value.nil?
|
|
30
|
+
raise ConstraintError, "Invalid timestamp: #{value}" unless value.is_a?(::Time) || value.is_a?(::DateTime) || value.is_a?(::Date)
|
|
31
|
+
true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def default
|
|
35
|
+
::Time.now
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../errors/constraint_error"
|
|
4
|
+
require_relative "../errors/configuration_error"
|
|
5
|
+
|
|
6
|
+
module RubyDB
|
|
7
|
+
module Types
|
|
8
|
+
# Base class for all data types
|
|
9
|
+
class Type
|
|
10
|
+
attr_reader :name, :size, :precision, :scale
|
|
11
|
+
|
|
12
|
+
def initialize(name, size: nil, precision: nil, scale: nil)
|
|
13
|
+
@name = name
|
|
14
|
+
@size = size
|
|
15
|
+
@precision = precision
|
|
16
|
+
@scale = scale
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def serialize(value)
|
|
20
|
+
raise NotImplementedError, "#{self.class} must implement #serialize"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def deserialize(bytes)
|
|
24
|
+
raise NotImplementedError, "#{self.class} must implement #deserialize"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def validate(value)
|
|
28
|
+
raise NotImplementedError, "#{self.class} must implement #validate"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def compare(a, b)
|
|
32
|
+
return 0 if a == b
|
|
33
|
+
return -1 if a < b
|
|
34
|
+
1
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def default
|
|
38
|
+
nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def sql_type
|
|
42
|
+
@name.to_s.upcase
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def storage_size
|
|
46
|
+
@size
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def to_s
|
|
50
|
+
sql_type
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def inspect
|
|
54
|
+
"#<#{self.class} name=#{@name} size=#{@size}>"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Type registry for looking up types by name
|
|
59
|
+
class TypeRegistry
|
|
60
|
+
@types = {}
|
|
61
|
+
|
|
62
|
+
class << self
|
|
63
|
+
attr_reader :types
|
|
64
|
+
|
|
65
|
+
def register(name, type_class = nil)
|
|
66
|
+
klass = type_class || resolve(name)
|
|
67
|
+
return if klass.nil?
|
|
68
|
+
|
|
69
|
+
@types[name.to_sym] = klass
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def lookup(name, **kwargs)
|
|
73
|
+
type_class = @types[name.to_sym] || resolve(name)
|
|
74
|
+
if type_class
|
|
75
|
+
return type_class.new(kwargs.fetch(:limit, 255)) if type_class == Varchar
|
|
76
|
+
|
|
77
|
+
return type_class.new(**kwargs)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
raise ConfigurationError, "Unknown type: #{name}"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def reset
|
|
84
|
+
@types = {}
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def resolve(name)
|
|
90
|
+
name = name.to_sym
|
|
91
|
+
const_name = case name
|
|
92
|
+
when :bigint then "BigInt"
|
|
93
|
+
when :smallint then "SmallInt"
|
|
94
|
+
when :float then "Float"
|
|
95
|
+
when :decimal then "Decimal"
|
|
96
|
+
when :boolean then "Boolean"
|
|
97
|
+
when :text then "Text"
|
|
98
|
+
when :varchar then "Varchar"
|
|
99
|
+
when :blob then "Blob"
|
|
100
|
+
when :date then "Date"
|
|
101
|
+
when :time then "Time"
|
|
102
|
+
when :timestamp then "Timestamp"
|
|
103
|
+
when :json then "Json"
|
|
104
|
+
when :uuid then "UUID"
|
|
105
|
+
when :null then "Null"
|
|
106
|
+
when :integer then "Integer"
|
|
107
|
+
else name.to_s.split("_").map { |part| part.capitalize }.join
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
klass = RubyDB::Types.const_get(const_name)
|
|
111
|
+
@types[name] = klass
|
|
112
|
+
klass
|
|
113
|
+
rescue NameError
|
|
114
|
+
nil
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module RubyDB
|
|
6
|
+
module Types
|
|
7
|
+
# UUID type (Universally Unique Identifier)
|
|
8
|
+
class UUID < Type
|
|
9
|
+
SIZE = 16
|
|
10
|
+
UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
super(:uuid, size: SIZE)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def serialize(value)
|
|
17
|
+
validate(value)
|
|
18
|
+
return "\x00" * SIZE if value.nil?
|
|
19
|
+
# Remove hyphens and convert hex to bytes
|
|
20
|
+
hex = value.to_s.gsub("-", "")
|
|
21
|
+
[hex].pack("H*")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def deserialize(bytes)
|
|
25
|
+
return nil if bytes.nil? || bytes.empty?
|
|
26
|
+
hex = bytes.unpack("H*").first
|
|
27
|
+
# Add hyphens back
|
|
28
|
+
"#{hex[0...8]}-#{hex[8...12]}-#{hex[12...16]}-#{hex[16...20]}-#{hex[20...32]}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def validate(value)
|
|
32
|
+
return true if value.nil?
|
|
33
|
+
str = value.to_s
|
|
34
|
+
raise ConstraintError, "Invalid UUID: #{value}" unless str.match?(UUID_REGEX)
|
|
35
|
+
true
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def default
|
|
39
|
+
SecureRandom.uuid
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def generate
|
|
43
|
+
SecureRandom.uuid
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyDB
|
|
4
|
+
module Types
|
|
5
|
+
# Variable-length text string with maximum length
|
|
6
|
+
class Varchar < Text
|
|
7
|
+
attr_reader :limit
|
|
8
|
+
|
|
9
|
+
def initialize(limit)
|
|
10
|
+
super()
|
|
11
|
+
@name = :varchar
|
|
12
|
+
@limit = limit
|
|
13
|
+
@size = limit
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def serialize(value)
|
|
17
|
+
validate(value)
|
|
18
|
+
return "" if value.nil?
|
|
19
|
+
str = value.to_s.encode("UTF-8")
|
|
20
|
+
raise ConstraintError, "VARCHAR exceeds limit: #{str.length} > #{@limit}" if str.length > @limit
|
|
21
|
+
str
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def validate(value)
|
|
25
|
+
return true if value.nil?
|
|
26
|
+
raise ConstraintError, "Invalid varchar: #{value}" unless value.respond_to?(:to_s)
|
|
27
|
+
str = value.to_s
|
|
28
|
+
raise ConstraintError, "VARCHAR exceeds limit: #{str.length} > #{@limit}" if str.length > @limit
|
|
29
|
+
true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def to_s
|
|
33
|
+
"VARCHAR(#{@limit})"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# RubyDB version information
|
|
4
|
+
# Follows Semantic Versioning (SemVer) 2.0.0
|
|
5
|
+
# See: https://semver.org/
|
|
6
|
+
|
|
7
|
+
module RubyDB
|
|
8
|
+
# Current version of RubyDB
|
|
9
|
+
#
|
|
10
|
+
# Format: MAJOR.MINOR.PATCH
|
|
11
|
+
# - MAJOR: Incompatible API changes
|
|
12
|
+
# - MINOR: Backwards-compatible new functionality
|
|
13
|
+
# - PATCH: Backwards-compatible bug fixes
|
|
14
|
+
VERSION = "0.1.0"
|
|
15
|
+
|
|
16
|
+
# Version components for easy access
|
|
17
|
+
module Version
|
|
18
|
+
MAJOR = 0
|
|
19
|
+
MINOR = 1
|
|
20
|
+
PATCH = 0
|
|
21
|
+
PRE = nil # e.g., "alpha", "beta", "rc1"
|
|
22
|
+
|
|
23
|
+
def self.to_s
|
|
24
|
+
[MAJOR, MINOR, PATCH].join(".")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.full
|
|
28
|
+
version = to_s
|
|
29
|
+
version += "-#{PRE}" if PRE
|
|
30
|
+
version
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "zlib"
|
|
5
|
+
require "time"
|
|
6
|
+
|
|
7
|
+
module RubyDB
|
|
8
|
+
module WAL
|
|
9
|
+
# Archive - Manages WAL archiving and restoration
|
|
10
|
+
class Archive
|
|
11
|
+
attr_reader :stats
|
|
12
|
+
|
|
13
|
+
def initialize(wal_dir, archive_dir = nil, config = {})
|
|
14
|
+
@wal_dir = wal_dir
|
|
15
|
+
@archive_dir = archive_dir || File.join(wal_dir, "archive")
|
|
16
|
+
@config = config
|
|
17
|
+
@compression = config[:compression] || true
|
|
18
|
+
@max_archive_size = config[:max_size] || 10 * 1024 * 1024 * 1024 # 10GB
|
|
19
|
+
@retention_days = config[:retention_days] || 30
|
|
20
|
+
@stats = {
|
|
21
|
+
archives_created: 0,
|
|
22
|
+
archives_restored: 0,
|
|
23
|
+
archives_deleted: 0,
|
|
24
|
+
total_archive_size: 0,
|
|
25
|
+
compressed_bytes: 0,
|
|
26
|
+
uncompressed_bytes: 0,
|
|
27
|
+
archive_failures: 0
|
|
28
|
+
}
|
|
29
|
+
@lock = Mutex.new
|
|
30
|
+
|
|
31
|
+
# Create archive directory
|
|
32
|
+
FileUtils.mkdir_p(@archive_dir)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def archive_segment(segment)
|
|
36
|
+
@lock.synchronize do
|
|
37
|
+
start_time = Time.now
|
|
38
|
+
|
|
39
|
+
begin
|
|
40
|
+
# Check if segment exists
|
|
41
|
+
unless segment.exists?
|
|
42
|
+
return false
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Archive the segment
|
|
46
|
+
archive_path = segment.archive
|
|
47
|
+
archive_size = File.size(archive_path)
|
|
48
|
+
|
|
49
|
+
# Compress if enabled
|
|
50
|
+
if @compression && archive_size > 0
|
|
51
|
+
compressed_path = "#{archive_path}.gz"
|
|
52
|
+
compress_file(archive_path, compressed_path)
|
|
53
|
+
File.delete(archive_path)
|
|
54
|
+
archive_path = compressed_path
|
|
55
|
+
archive_size = File.size(archive_path)
|
|
56
|
+
@stats[:compressed_bytes] += archive_size
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
@stats[:archives_created] += 1
|
|
60
|
+
@stats[:total_archive_size] += archive_size
|
|
61
|
+
@stats[:uncompressed_bytes] += archive_size
|
|
62
|
+
|
|
63
|
+
# Clean old archives if needed
|
|
64
|
+
clean_old_archives
|
|
65
|
+
|
|
66
|
+
{ path: archive_path, size: archive_size }
|
|
67
|
+
|
|
68
|
+
rescue => e
|
|
69
|
+
@stats[:archive_failures] += 1
|
|
70
|
+
raise
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def restore_archive(segment_id)
|
|
76
|
+
@lock.synchronize do
|
|
77
|
+
# Find archive file
|
|
78
|
+
archive_path = File.join(@archive_dir, "wal_#{segment_id.to_s.rjust(8, '0')}.log")
|
|
79
|
+
|
|
80
|
+
unless File.exist?(archive_path)
|
|
81
|
+
# Try compressed
|
|
82
|
+
compressed_path = "#{archive_path}.gz"
|
|
83
|
+
if File.exist?(compressed_path)
|
|
84
|
+
archive_path = compressed_path
|
|
85
|
+
else
|
|
86
|
+
return false
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Decompress if needed
|
|
91
|
+
if archive_path.end_with?(".gz")
|
|
92
|
+
decompressed_path = archive_path.gsub(/\.gz$/, "")
|
|
93
|
+
decompress_file(archive_path, decompressed_path)
|
|
94
|
+
archive_path = decompressed_path
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Copy to WAL directory
|
|
98
|
+
wal_path = File.join(@wal_dir, "wal_#{segment_id.to_s.rjust(8, '0')}.log")
|
|
99
|
+
FileUtils.cp(archive_path, wal_path)
|
|
100
|
+
|
|
101
|
+
@stats[:archives_restored] += 1
|
|
102
|
+
wal_path
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def list_archives
|
|
107
|
+
@lock.synchronize do
|
|
108
|
+
archives = []
|
|
109
|
+
|
|
110
|
+
Dir.glob(File.join(@archive_dir, "wal_*.log*")).each do |path|
|
|
111
|
+
if path =~ /wal_(\d+)\.log(?:\.gz)?$/
|
|
112
|
+
segment_id = $1.to_i
|
|
113
|
+
size = File.size(path)
|
|
114
|
+
modified = File.mtime(path)
|
|
115
|
+
|
|
116
|
+
archives << {
|
|
117
|
+
segment_id: segment_id,
|
|
118
|
+
path: path,
|
|
119
|
+
size: size,
|
|
120
|
+
modified: modified,
|
|
121
|
+
compressed: path.end_with?(".gz")
|
|
122
|
+
}
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
archives.sort_by { |a| a[:segment_id] }
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def clean_old_archives
|
|
131
|
+
@lock.synchronize do
|
|
132
|
+
# Check total size
|
|
133
|
+
archives = list_archives
|
|
134
|
+
total_size = archives.sum { |a| a[:size] }
|
|
135
|
+
|
|
136
|
+
# If over limit, remove oldest
|
|
137
|
+
if total_size > @max_archive_size
|
|
138
|
+
archives.sort_by! { |a| a[:modified] }
|
|
139
|
+
archives.each do |archive|
|
|
140
|
+
break if total_size <= @max_archive_size
|
|
141
|
+
|
|
142
|
+
File.delete(archive[:path])
|
|
143
|
+
total_size -= archive[:size]
|
|
144
|
+
@stats[:archives_deleted] += 1
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Remove old archives
|
|
149
|
+
cutoff = Time.now - @retention_days * 24 * 60 * 60
|
|
150
|
+
archives.each do |archive|
|
|
151
|
+
if archive[:modified] < cutoff
|
|
152
|
+
File.delete(archive[:path])
|
|
153
|
+
@stats[:archives_deleted] += 1
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def stats
|
|
160
|
+
@lock.synchronize do
|
|
161
|
+
archives = list_archives
|
|
162
|
+
total_size = archives.sum { |a| a[:size] }
|
|
163
|
+
|
|
164
|
+
@stats.merge({
|
|
165
|
+
archive_count: archives.size,
|
|
166
|
+
total_archive_size: total_size,
|
|
167
|
+
retention_days: @retention_days,
|
|
168
|
+
max_archive_size: @max_archive_size,
|
|
169
|
+
compression: @compression
|
|
170
|
+
})
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
private
|
|
175
|
+
|
|
176
|
+
def compress_file(source, destination)
|
|
177
|
+
File.open(source, "rb") do |input|
|
|
178
|
+
Zlib::GzipWriter.open(destination) do |output|
|
|
179
|
+
output.write(input.read)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def decompress_file(source, destination)
|
|
185
|
+
Zlib::GzipReader.open(source) do |input|
|
|
186
|
+
File.open(destination, "wb") do |output|
|
|
187
|
+
output.write(input.read)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|