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,236 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module RubyDB
|
|
6
|
+
module WAL
|
|
7
|
+
# Writer - Writes records to the WAL
|
|
8
|
+
class Writer
|
|
9
|
+
attr_reader :current_segment, :current_lsn, :stats
|
|
10
|
+
|
|
11
|
+
def initialize(wal_dir, config = {})
|
|
12
|
+
@wal_dir = wal_dir
|
|
13
|
+
@config = config
|
|
14
|
+
@segment_max_size = config[:segment_size] || Segment::DEFAULT_MAX_SIZE
|
|
15
|
+
@current_segment = nil
|
|
16
|
+
@current_lsn = LSN.new(1, 0)
|
|
17
|
+
@buffer = []
|
|
18
|
+
@buffer_size = config[:buffer_size] || 1024 * 1024 # 1MB
|
|
19
|
+
@buffer_current_size = 0
|
|
20
|
+
@sync_on_write = config.key?(:sync) ? config[:sync] : true
|
|
21
|
+
@stats = {
|
|
22
|
+
records_written: 0,
|
|
23
|
+
bytes_written: 0,
|
|
24
|
+
segments_created: 0,
|
|
25
|
+
segments_rotated: 0,
|
|
26
|
+
buffer_flushes: 0,
|
|
27
|
+
syncs: 0
|
|
28
|
+
}
|
|
29
|
+
@lock = Mutex.new
|
|
30
|
+
@write_thread = nil
|
|
31
|
+
@running = false
|
|
32
|
+
@shutdown = false
|
|
33
|
+
@background_error = nil
|
|
34
|
+
|
|
35
|
+
# Create WAL directory
|
|
36
|
+
FileUtils.mkdir_p(@wal_dir)
|
|
37
|
+
|
|
38
|
+
# Create first segment
|
|
39
|
+
create_new_segment
|
|
40
|
+
|
|
41
|
+
# Start background writer if async
|
|
42
|
+
if config[:async] != false
|
|
43
|
+
start_background_writer
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def write_record(record)
|
|
48
|
+
@lock.synchronize do
|
|
49
|
+
raise @background_error if @background_error
|
|
50
|
+
# Assign LSN
|
|
51
|
+
lsn = next_lsn
|
|
52
|
+
record.instance_variable_set(:@lsn, lsn)
|
|
53
|
+
|
|
54
|
+
# Serialize record
|
|
55
|
+
serialized = record.serialize
|
|
56
|
+
record_data = serialized[:checksum] + serialized[:data]
|
|
57
|
+
|
|
58
|
+
# Add to buffer
|
|
59
|
+
@buffer << { record: record, data: record_data, lsn: lsn }
|
|
60
|
+
@buffer_current_size += record_data.bytesize
|
|
61
|
+
|
|
62
|
+
@stats[:records_written] += 1
|
|
63
|
+
|
|
64
|
+
# Flush if buffer is full OR if sync is enabled (for durability)
|
|
65
|
+
if @buffer_current_size >= @buffer_size || @sync_on_write
|
|
66
|
+
flush_buffer
|
|
67
|
+
_sync if @sync_on_write
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
lsn
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def write_batch(records)
|
|
75
|
+
@lock.synchronize do
|
|
76
|
+
start_lsn = nil
|
|
77
|
+
last_lsn = nil
|
|
78
|
+
|
|
79
|
+
records.each do |record|
|
|
80
|
+
lsn = write_record_locked(record)
|
|
81
|
+
start_lsn ||= lsn
|
|
82
|
+
last_lsn = lsn
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
flush_buffer if @buffer.any?
|
|
86
|
+
|
|
87
|
+
{ start_lsn: start_lsn, end_lsn: last_lsn, count: records.size }
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def flush
|
|
92
|
+
@lock.synchronize do
|
|
93
|
+
raise @background_error if @background_error
|
|
94
|
+
flush_buffer
|
|
95
|
+
_sync if @sync_on_write
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def sync
|
|
100
|
+
@lock.synchronize do
|
|
101
|
+
_sync
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private def _sync
|
|
106
|
+
if @current_segment && @current_segment.open?
|
|
107
|
+
@current_segment.instance_variable_get(:@file).fsync
|
|
108
|
+
@stats[:syncs] += 1
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def shutdown(wait = true)
|
|
113
|
+
write_thread = nil
|
|
114
|
+
@lock.synchronize do
|
|
115
|
+
return if @shutdown
|
|
116
|
+
|
|
117
|
+
@shutdown = true
|
|
118
|
+
@running = false
|
|
119
|
+
@write_thread&.kill if !wait
|
|
120
|
+
flush_buffer
|
|
121
|
+
_sync if @sync_on_write
|
|
122
|
+
@current_segment.close if @current_segment&.open?
|
|
123
|
+
write_thread = @write_thread
|
|
124
|
+
@write_thread = nil
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# The async writer can be asleep or flushing while shutdown begins.
|
|
128
|
+
# Join it before the caller removes the WAL directory; otherwise a
|
|
129
|
+
# late background flush can race segment close and leave Windows file
|
|
130
|
+
# handles open during temporary-directory cleanup.
|
|
131
|
+
write_thread&.join if wait && write_thread != Thread.current
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def stats
|
|
135
|
+
@lock.synchronize do
|
|
136
|
+
@stats.merge({
|
|
137
|
+
buffer_size: @buffer.size,
|
|
138
|
+
buffer_bytes: @buffer_current_size,
|
|
139
|
+
current_segment: @current_segment&.segment_id,
|
|
140
|
+
current_lsn: @current_lsn.to_s,
|
|
141
|
+
async: @running,
|
|
142
|
+
last_background_error: @background_error&.message
|
|
143
|
+
})
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
def write_record_locked(record)
|
|
150
|
+
lsn = next_lsn
|
|
151
|
+
record.instance_variable_set(:@lsn, lsn)
|
|
152
|
+
|
|
153
|
+
serialized = record.serialize
|
|
154
|
+
record_data = serialized[:checksum] + serialized[:data]
|
|
155
|
+
@buffer << { record: record, data: record_data, lsn: lsn }
|
|
156
|
+
@buffer_current_size += record_data.bytesize
|
|
157
|
+
@stats[:records_written] += 1
|
|
158
|
+
|
|
159
|
+
if @buffer_current_size >= @buffer_size || @sync_on_write
|
|
160
|
+
flush_buffer
|
|
161
|
+
_sync if @sync_on_write
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
lsn
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def create_new_segment
|
|
168
|
+
segment_id = @current_segment ? @current_segment.segment_id + 1 : 1
|
|
169
|
+
segment = Segment.new(segment_id, @wal_dir, @segment_max_size)
|
|
170
|
+
segment.open
|
|
171
|
+
@current_segment = segment
|
|
172
|
+
@stats[:segments_created] += 1
|
|
173
|
+
segment
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def next_lsn
|
|
177
|
+
@current_lsn = LSN.new(
|
|
178
|
+
@current_segment.segment_id,
|
|
179
|
+
@current_segment.size + @buffer.sum { |entry| entry[:data].bytesize + Segment::FRAME_HEADER_SIZE }
|
|
180
|
+
)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def flush_buffer
|
|
184
|
+
return if @buffer.empty?
|
|
185
|
+
|
|
186
|
+
@stats[:buffer_flushes] += 1
|
|
187
|
+
|
|
188
|
+
@buffer.each do |entry|
|
|
189
|
+
# Check if current segment is full
|
|
190
|
+
if @current_segment && @current_segment.full?
|
|
191
|
+
rotate_segment
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Write to segment
|
|
195
|
+
result = @current_segment.append(entry[:data], entry[:lsn])
|
|
196
|
+
@stats[:bytes_written] += entry[:data].bytesize
|
|
197
|
+
|
|
198
|
+
if result == :full
|
|
199
|
+
rotate_segment
|
|
200
|
+
# Re-write to new segment
|
|
201
|
+
@current_segment.append(entry[:data], entry[:lsn])
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
@buffer.clear
|
|
206
|
+
@buffer_current_size = 0
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def rotate_segment
|
|
210
|
+
@current_segment.close
|
|
211
|
+
@stats[:segments_rotated] += 1
|
|
212
|
+
create_new_segment
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def start_background_writer
|
|
216
|
+
@running = true
|
|
217
|
+
@write_thread = Thread.new do
|
|
218
|
+
while !@shutdown
|
|
219
|
+
sleep(1)
|
|
220
|
+
begin
|
|
221
|
+
if @buffer.any?
|
|
222
|
+
flush
|
|
223
|
+
end
|
|
224
|
+
rescue => e
|
|
225
|
+
@lock.synchronize do
|
|
226
|
+
@background_error ||= e
|
|
227
|
+
@running = false
|
|
228
|
+
end
|
|
229
|
+
break
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
data/lib/rubydb.rb
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Top-level entry point for the RubyDB library.
|
|
4
|
+
# The project stores the implementation under lib/rubydb/rubydb.rb, so this wrapper
|
|
5
|
+
# keeps the public require path compatible with typical Ruby package usage.
|
|
6
|
+
|
|
7
|
+
require_relative "rubydb/rubydb"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1
|
|
2
|
+
|
|
3
|
+
FROM ruby:3.3-slim
|
|
4
|
+
|
|
5
|
+
ENV APP_HOME=/opt/rubydb \
|
|
6
|
+
RUBYDB_DATA_DIR=/var/lib/rubydb/data \
|
|
7
|
+
RUBYDB_LOG_DIR=/var/log/rubydb \
|
|
8
|
+
RUBYDB_PID_FILE=/run/rubydb.pid \
|
|
9
|
+
RUBYDB_HOST=0.0.0.0 \
|
|
10
|
+
RUBYDB_PORT=7432
|
|
11
|
+
|
|
12
|
+
WORKDIR ${APP_HOME}
|
|
13
|
+
|
|
14
|
+
COPY Gemfile Gemfile.lock rubydb.gemspec ./
|
|
15
|
+
COPY lib ./lib
|
|
16
|
+
COPY exe ./exe
|
|
17
|
+
COPY bin ./bin
|
|
18
|
+
|
|
19
|
+
RUN bundle config set --without development:test \
|
|
20
|
+
&& bundle install --jobs 4 --retry 3 \
|
|
21
|
+
&& gem build rubydb.gemspec \
|
|
22
|
+
&& gem install --no-document rubydb-*.gem \
|
|
23
|
+
&& rm -f rubydb-*.gem \
|
|
24
|
+
&& useradd --system --home-dir ${APP_HOME} --shell /usr/sbin/nologin rubydb \
|
|
25
|
+
&& install -d -o rubydb -g rubydb ${RUBYDB_DATA_DIR} ${RUBYDB_LOG_DIR} /run/rubydb
|
|
26
|
+
|
|
27
|
+
COPY packaging/docker/entrypoint.sh /usr/local/bin/rubydb-entrypoint
|
|
28
|
+
RUN chmod 0755 /usr/local/bin/rubydb-entrypoint
|
|
29
|
+
|
|
30
|
+
USER rubydb
|
|
31
|
+
EXPOSE 7432
|
|
32
|
+
VOLUME ["/var/lib/rubydb"]
|
|
33
|
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
34
|
+
CMD rubydb status --json >/dev/null || exit 1
|
|
35
|
+
|
|
36
|
+
ENTRYPOINT ["/usr/local/bin/rubydb-entrypoint"]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
services:
|
|
2
|
+
primary:
|
|
3
|
+
build:
|
|
4
|
+
context: ../..
|
|
5
|
+
dockerfile: packaging/docker/Dockerfile
|
|
6
|
+
hostname: rubydb-primary
|
|
7
|
+
environment:
|
|
8
|
+
RUBYDB_DATA_DIR: /var/lib/rubydb/data
|
|
9
|
+
RUBYDB_HOST: 0.0.0.0
|
|
10
|
+
RUBYDB_PORT: 7432
|
|
11
|
+
volumes:
|
|
12
|
+
- primary-data:/var/lib/rubydb
|
|
13
|
+
networks: [rubydb-cluster]
|
|
14
|
+
|
|
15
|
+
replica:
|
|
16
|
+
build:
|
|
17
|
+
context: ../..
|
|
18
|
+
dockerfile: packaging/docker/Dockerfile
|
|
19
|
+
hostname: rubydb-replica
|
|
20
|
+
environment:
|
|
21
|
+
RUBYDB_DATA_DIR: /var/lib/rubydb/data
|
|
22
|
+
RUBYDB_HOST: 0.0.0.0
|
|
23
|
+
RUBYDB_PORT: 7432
|
|
24
|
+
volumes:
|
|
25
|
+
- replica-data:/var/lib/rubydb
|
|
26
|
+
networks: [rubydb-cluster]
|
|
27
|
+
|
|
28
|
+
observer:
|
|
29
|
+
image: ruby:3.3-slim
|
|
30
|
+
working_dir: /workspace
|
|
31
|
+
volumes:
|
|
32
|
+
- ../..:/workspace:ro
|
|
33
|
+
command: ["ruby", "scripts/replication_network_failover_drill"]
|
|
34
|
+
networks: [rubydb-cluster]
|
|
35
|
+
profiles: [drill]
|
|
36
|
+
|
|
37
|
+
networks:
|
|
38
|
+
rubydb-cluster:
|
|
39
|
+
driver: bridge
|
|
40
|
+
|
|
41
|
+
volumes:
|
|
42
|
+
primary-data:
|
|
43
|
+
replica-data:
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
: "${RUBYDB_DATA_DIR:=/var/lib/rubydb/data}"
|
|
5
|
+
: "${RUBYDB_LOG_DIR:=/var/log/rubydb}"
|
|
6
|
+
: "${RUBYDB_PID_FILE:=/run/rubydb.pid}"
|
|
7
|
+
: "${RUBYDB_HOST:=0.0.0.0}"
|
|
8
|
+
: "${RUBYDB_PORT:=7432}"
|
|
9
|
+
|
|
10
|
+
exec rubydb start \
|
|
11
|
+
--host "$RUBYDB_HOST" \
|
|
12
|
+
--port "$RUBYDB_PORT" \
|
|
13
|
+
--data-dir "$RUBYDB_DATA_DIR" \
|
|
14
|
+
--log-dir "$RUBYDB_LOG_DIR" \
|
|
15
|
+
--pid-file "$RUBYDB_PID_FILE"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class Rubydb < Formula
|
|
2
|
+
desc "Developer-first relational database for Ruby"
|
|
3
|
+
homepage "https://github.com/aldanedev-create/rubydb"
|
|
4
|
+
url "https://github.com/aldanedev-create/rubydb/archive/refs/tags/v0.1.0.tar.gz"
|
|
5
|
+
version "0.1.0"
|
|
6
|
+
license "MIT"
|
|
7
|
+
|
|
8
|
+
depends_on "ruby@3.3"
|
|
9
|
+
|
|
10
|
+
def install
|
|
11
|
+
system "gem", "build", "rubydb.gemspec"
|
|
12
|
+
system "gem", "install", "--no-document", *Dir["rubydb-*.gem"]
|
|
13
|
+
bin.install "exe/rubydb"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
test do
|
|
17
|
+
assert_match version.to_s, shell_output("#{bin}/rubydb --version")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=RubyDB database server
|
|
3
|
+
After=network-online.target
|
|
4
|
+
Wants=network-online.target
|
|
5
|
+
|
|
6
|
+
[Service]
|
|
7
|
+
Type=simple
|
|
8
|
+
User=rubydb
|
|
9
|
+
Group=rubydb
|
|
10
|
+
ExecStart=/usr/local/bin/rubydb start --host 127.0.0.1 --port 7432 --data-dir /var/lib/rubydb/data --log-dir /var/log/rubydb --pid-file /run/rubydb.pid
|
|
11
|
+
Restart=on-failure
|
|
12
|
+
RestartSec=5s
|
|
13
|
+
TimeoutStopSec=30s
|
|
14
|
+
UMask=0077
|
|
15
|
+
NoNewPrivileges=true
|
|
16
|
+
PrivateTmp=true
|
|
17
|
+
ProtectSystem=strict
|
|
18
|
+
ProtectHome=true
|
|
19
|
+
ReadWritePaths=/var/lib/rubydb /var/log/rubydb /run
|
|
20
|
+
|
|
21
|
+
[Install]
|
|
22
|
+
WantedBy=multi-user.target
|
data/rubydb.gemspec
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/rubydb/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'rubydb'
|
|
7
|
+
spec.version = RubyDB::VERSION
|
|
8
|
+
spec.authors = ['Aldane Hutchinson']
|
|
9
|
+
spec.email = ['aldanehutchinson5@gmail.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'A developer-first relational database for Ruby'
|
|
12
|
+
spec.description = <<~DESC
|
|
13
|
+
RubyDB is a developer-first relational database foundation written in Ruby.
|
|
14
|
+
It combines SQLite-like simplicity with a growing SQL, WAL, MVCC, server,
|
|
15
|
+
replication, and Rails integration surface. Production deployment is
|
|
16
|
+
limited to the capabilities and validation documented by the project.
|
|
17
|
+
DESC
|
|
18
|
+
spec.homepage = 'https://github.com/aldanedev-create/rubydb'
|
|
19
|
+
spec.license = 'MIT'
|
|
20
|
+
# The implementation is currently compatible with the workspace Ruby runtime used for
|
|
21
|
+
# local development and CI. This is a practical compatibility target until the
|
|
22
|
+
# project adds a narrower support matrix and explicit Ruby-version policy.
|
|
23
|
+
spec.required_ruby_version = '>= 3.3.0'
|
|
24
|
+
|
|
25
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
26
|
+
spec.metadata['source_code_uri'] = 'https://github.com/aldanedev-create/rubydb/tree/main'
|
|
27
|
+
spec.metadata['changelog_uri'] = 'https://github.com/aldanedev-create/rubydb/blob/main/CHANGELOG.md'
|
|
28
|
+
|
|
29
|
+
# RubyGems signing is opt-in for release builders. The private key and
|
|
30
|
+
# certificate are supplied through protected CI paths and never committed.
|
|
31
|
+
signing_key = ENV['RUBYDB_GEM_SIGNING_KEY']
|
|
32
|
+
certificate = ENV['RUBYDB_GEM_CERT']
|
|
33
|
+
if signing_key || certificate
|
|
34
|
+
raise 'RUBYDB_GEM_SIGNING_KEY and RUBYDB_GEM_CERT must be provided together' unless signing_key && certificate
|
|
35
|
+
raise "RubyGems signing key not found: #{signing_key}" unless File.file?(signing_key)
|
|
36
|
+
raise "RubyGems certificate not found: #{certificate}" unless File.file?(certificate)
|
|
37
|
+
|
|
38
|
+
spec.signing_key = signing_key
|
|
39
|
+
spec.cert_chain = [File.read(certificate)]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Specify which files should be added to the gem when it is released.
|
|
43
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
44
|
+
spec.files = Dir.chdir(__dir__) do
|
|
45
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
46
|
+
f.match(%r{^(test|spec|features|benchmarks|fuzz|chaos|examples)/})
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
spec.bindir = 'exe'
|
|
50
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
51
|
+
spec.require_paths = ['lib']
|
|
52
|
+
|
|
53
|
+
spec.add_dependency 'digest', '~> 3.1'
|
|
54
|
+
spec.add_dependency 'json', '~> 2.7'
|
|
55
|
+
spec.add_dependency 'date', '~> 3.3'
|
|
56
|
+
spec.add_dependency 'time', '~> 0.4'
|
|
57
|
+
end
|
data/scripts/benchmark
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Runs the real crash, filesystem-fault, corruption, compaction, and restore
|
|
5
|
+
# paths as one release gate. Physical quota filling is opt-in because filling a
|
|
6
|
+
# developer's filesystem without an explicit safety flag would be dangerous.
|
|
7
|
+
|
|
8
|
+
require "json"
|
|
9
|
+
require "rbconfig"
|
|
10
|
+
|
|
11
|
+
root = File.expand_path("..", __dir__)
|
|
12
|
+
Dir.chdir(root)
|
|
13
|
+
specs = %w[
|
|
14
|
+
spec/filesystem_fault_injection_spec.rb
|
|
15
|
+
spec/transaction_crash_recovery_spec.rb
|
|
16
|
+
spec/crash_recovery_spec.rb
|
|
17
|
+
spec/crash_recovery_wal_failure_spec.rb
|
|
18
|
+
spec/corruption_repair_spec.rb
|
|
19
|
+
spec/compaction_persistence_spec.rb
|
|
20
|
+
spec/backup_restore_spec.rb
|
|
21
|
+
spec/incremental_backup_spec.rb
|
|
22
|
+
spec/delta_restore_spec.rb
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
command = [RbConfig.ruby, "-Ilib", "-S", "rspec", *specs]
|
|
26
|
+
started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
27
|
+
success = system(*command)
|
|
28
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
|
|
29
|
+
|
|
30
|
+
result = {
|
|
31
|
+
success: success,
|
|
32
|
+
checks: %w[filesystem_faults crash_recovery corruption_detection compaction backup_restore],
|
|
33
|
+
physical_quota: ENV["RUBYDB_DURABILITY_QUOTA_DIR"] ? "operator_configured" : "not_run",
|
|
34
|
+
elapsed_seconds: elapsed.round(3)
|
|
35
|
+
}
|
|
36
|
+
puts JSON.generate(result)
|
|
37
|
+
exit(success ? 0 : 1)
|
data/scripts/format
ADDED
|
File without changes
|
data/scripts/fuzz
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Run the repository's fuzzers as a bounded CI or local safety gate.
|
|
5
|
+
# Invalid SQL and intentionally injected WAL corruption are expected inputs;
|
|
6
|
+
# uncaught errors and reported crashes are not.
|
|
7
|
+
|
|
8
|
+
require "tmpdir"
|
|
9
|
+
|
|
10
|
+
root = File.expand_path("..", __dir__)
|
|
11
|
+
$LOAD_PATH.unshift(File.join(root, "lib"))
|
|
12
|
+
require "rubydb"
|
|
13
|
+
require_relative "../fuzz/sql_parser"
|
|
14
|
+
require_relative "../fuzz/wal"
|
|
15
|
+
require_relative "../fuzz/storage"
|
|
16
|
+
require_relative "../fuzz/transactions"
|
|
17
|
+
require_relative "../fuzz/query_engine"
|
|
18
|
+
|
|
19
|
+
iterations = Integer(ENV.fetch("RUBYDB_FUZZ_ITERATIONS", "100"), 10)
|
|
20
|
+
abort "RUBYDB_FUZZ_ITERATIONS must be positive" unless iterations.positive?
|
|
21
|
+
seed = Integer(ENV.fetch("RUBYDB_FUZZ_SEED", "20260909"), 10)
|
|
22
|
+
|
|
23
|
+
def run_fuzzer(name)
|
|
24
|
+
stats = yield
|
|
25
|
+
crashes = stats[:crashes].to_i
|
|
26
|
+
errors = stats[:errors].to_i
|
|
27
|
+
abort "#{name} found #{crashes} crash(es) and #{errors} error(s)" if crashes.positive? || errors.positive?
|
|
28
|
+
puts "#{name}: #{stats.inspect}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
Dir.mktmpdir("rubydb-fuzz-") do |directory|
|
|
32
|
+
run_fuzzer("SQL parser") do
|
|
33
|
+
RubyDB::Fuzz::SQLParserFuzzer.new(max_iterations: iterations, seed: seed).fuzz
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
run_fuzzer("WAL") do
|
|
37
|
+
RubyDB::Fuzz::WALFuzzer.new(
|
|
38
|
+
max_iterations: iterations,
|
|
39
|
+
seed: seed,
|
|
40
|
+
test_dir: File.join(directory, "wal")
|
|
41
|
+
).fuzz
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
run_fuzzer("storage") do
|
|
45
|
+
RubyDB::Fuzz::StorageFuzzer.new(
|
|
46
|
+
max_iterations: iterations,
|
|
47
|
+
seed: seed,
|
|
48
|
+
test_dir: File.join(directory, "storage")
|
|
49
|
+
).fuzz
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
engine = RubyDB::Storage::Engine.new(File.join(directory, "engine.rdb"))
|
|
53
|
+
begin
|
|
54
|
+
run_fuzzer("transactions") do
|
|
55
|
+
RubyDB::Fuzz::TransactionsFuzzer.new(engine, max_iterations: iterations, seed: seed).fuzz
|
|
56
|
+
end
|
|
57
|
+
run_fuzzer("query engine") do
|
|
58
|
+
RubyDB::Fuzz::QueryEngineFuzzer.new(engine, max_iterations: iterations, seed: seed).fuzz
|
|
59
|
+
end
|
|
60
|
+
ensure
|
|
61
|
+
engine.close
|
|
62
|
+
end
|
|
63
|
+
end
|
data/scripts/lint
ADDED
|
File without changes
|
data/scripts/release
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "digest"
|
|
5
|
+
require "rbconfig"
|
|
6
|
+
require "rubygems"
|
|
7
|
+
|
|
8
|
+
root = File.expand_path("..", __dir__)
|
|
9
|
+
Dir.chdir(root)
|
|
10
|
+
|
|
11
|
+
abort "release metadata preflight failed" unless system(RbConfig.ruby, File.join(root, "scripts", "release_check"))
|
|
12
|
+
|
|
13
|
+
release_descriptors = [
|
|
14
|
+
"rubydb.gemspec",
|
|
15
|
+
"adapters/activerecord/rubydb-activerecord.gemspec",
|
|
16
|
+
"packaging/homebrew/rubydb.rb"
|
|
17
|
+
]
|
|
18
|
+
placeholder_pattern = /yourusername|your name|you@example\.com|your\.email@example\.com/i
|
|
19
|
+
placeholder_files = release_descriptors.select do |path|
|
|
20
|
+
File.exist?(path) && File.read(path).match?(placeholder_pattern)
|
|
21
|
+
end
|
|
22
|
+
unless placeholder_files.empty?
|
|
23
|
+
abort "Release metadata contains placeholders in: #{placeholder_files.join(', ')}. Set the real owner, contact, homepage, source, and changelog URLs before publishing."
|
|
24
|
+
end
|
|
25
|
+
abort "rake build:checksum failed" unless system("rake", "build:checksum")
|
|
26
|
+
|
|
27
|
+
if ENV["RUBYDB_GEM_SIGNING_KEY"] || ENV["RUBYDB_GEM_CERT"]
|
|
28
|
+
abort "Both RUBYDB_GEM_SIGNING_KEY and RUBYDB_GEM_CERT are required for gem signing" unless ENV["RUBYDB_GEM_SIGNING_KEY"] && ENV["RUBYDB_GEM_CERT"]
|
|
29
|
+
abort "RubyGems signing key does not exist" unless File.file?(ENV["RUBYDB_GEM_SIGNING_KEY"])
|
|
30
|
+
abort "RubyGems signing certificate does not exist" unless File.file?(ENV["RUBYDB_GEM_CERT"])
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
gem_path = Dir["pkg/rubydb-*.gem"].max_by { |path| File.mtime(path) }
|
|
34
|
+
abort "No built gem found" unless gem_path
|
|
35
|
+
checksum_path = File.join("checksums", "#{File.basename(gem_path)}.sha512")
|
|
36
|
+
expected = File.read(checksum_path).split.first
|
|
37
|
+
actual = Digest::SHA512.file(gem_path).hexdigest
|
|
38
|
+
abort "Checksum verification failed" unless expected == actual
|
|
39
|
+
|
|
40
|
+
if ENV["RUBYDB_PUBLISH"] == "1"
|
|
41
|
+
api_key = ENV.fetch("GEM_HOST_API_KEY", "")
|
|
42
|
+
credentials_path = Gem.configuration.credentials_path
|
|
43
|
+
credentials_available = File.file?(credentials_path) && !File.zero?(credentials_path)
|
|
44
|
+
abort "Set GEM_HOST_API_KEY or configure RubyGems credentials at #{credentials_path}" if api_key.empty? && !credentials_available
|
|
45
|
+
abort "gem push failed" unless system("gem", "push", gem_path)
|
|
46
|
+
puts "Published #{gem_path}"
|
|
47
|
+
else
|
|
48
|
+
puts "Release artifact verified: #{gem_path}"
|
|
49
|
+
puts "To publish: RUBYDB_PUBLISH=1 GEM_HOST_API_KEY=<RubyGems API key> ruby scripts/release"
|
|
50
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Fail-closed release metadata validation. This stays separate from gem
|
|
5
|
+
# building so CI can prove that a tag has a reviewed changelog entry before
|
|
6
|
+
# any artifact is published.
|
|
7
|
+
|
|
8
|
+
require "pathname"
|
|
9
|
+
|
|
10
|
+
root = Pathname.new(__dir__).parent
|
|
11
|
+
$LOAD_PATH.unshift(root.join("lib").to_s)
|
|
12
|
+
require "rubydb/version"
|
|
13
|
+
|
|
14
|
+
version = RubyDB::VERSION
|
|
15
|
+
github_ref = ENV.fetch("GITHUB_REF_NAME", "").strip
|
|
16
|
+
requested_version = ENV.fetch("RUBYDB_RELEASE_VERSION", "").strip
|
|
17
|
+
|
|
18
|
+
tag_version = if github_ref.start_with?("v")
|
|
19
|
+
github_ref.delete_prefix("v")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
if !requested_version.empty? && tag_version && requested_version != tag_version
|
|
23
|
+
abort "RUBYDB_RELEASE_VERSION #{requested_version} does not match GITHUB_REF_NAME #{github_ref}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
release_version = requested_version.empty? ? tag_version : requested_version
|
|
27
|
+
if release_version.nil? || release_version.empty?
|
|
28
|
+
if ENV["RUBYDB_PUBLISH"] == "1"
|
|
29
|
+
abort "Publishing requires GITHUB_REF_NAME=v#{version} or RUBYDB_RELEASE_VERSION=#{version}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
puts "Release preflight skipped: no release tag or explicit release version"
|
|
33
|
+
exit 0
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
abort "Release version #{release_version} does not match RubyDB::VERSION #{version}" unless release_version == version
|
|
37
|
+
|
|
38
|
+
changelog_path = root.join("CHANGELOG.md")
|
|
39
|
+
changelog = changelog_path.read
|
|
40
|
+
header = /^##\s+#{Regexp.escape(release_version)}(?:\s|$)/
|
|
41
|
+
abort "CHANGELOG.md has no entry for version #{release_version}" unless changelog.match?(header)
|
|
42
|
+
|
|
43
|
+
puts "Release preflight passed for RubyDB #{release_version}"
|