Offline Schema Inspection & Dry-Run Validation
Read a database's persisted schema, or simulate a code upgrade against it, without opening it for real.
Status: โ Implemented ยท Visibility: Public ยท Level: ๐ฃ Advanced ยท Category: Schema
๐ฏ What it solves
Two questions come up before you ever touch a production database file: "what schema does this database actually have?" and "will my new build's component structs and migrations succeed against it, or fail?". Answering either by opening the database with your application normally means accepting whatever side effects registration and auto-migration trigger โ including a hard failure that leaves you debugging a broken deployment. DatabaseSchema.Inspect and DatabaseSchema.ValidateEvolution answer both questions from outside the application, without registering any components for real and without writing a single byte to the file.
โ๏ธ How it works (in brief)
Both calls spin up a throwaway, isolated engine instance against the target file path, read what they need from the persisted system tables, and dispose it โ no shared state with any live DatabaseEngine your app may have open elsewhere. Inspect reads persisted component/field/index metadata and per-component entity counts directly, with no component registration at all. ValidateEvolution takes a configuration callback (the same shape as your real startup registration code) but captures the registrations into a recorder instead of applying them: it computes a SchemaDiff per component against the persisted layout, checks whether breaking changes have a registered migration chain, and reports the outcome โ all without allocating storage or migrating any entity.
๐ป Usage
// What's actually in this file?
var report = DatabaseSchema.Inspect("prod/game.typhon");
Console.WriteLine($"{report.DatabaseName} (format R{report.DatabaseFormatRevision})");
foreach (var component in report.Components)
{
Console.WriteLine($" {component.Name} R{component.Revision} โ {component.EntityCount} entities");
foreach (var field in component.Fields)
Console.WriteLine($" {field.Name} ({field.Type}) FieldId={field.FieldId} Offset={field.Offset}");
}
// Will my next release's schema open cleanly against it?
var result = DatabaseSchema.ValidateEvolution("prod/game.typhon", registrar =>
{
registrar.RegisterComponent<PlayerV2>();
registrar.RegisterMigration<PlayerV1, PlayerV2>(MigratePlayer);
});
if (!result.IsValid)
{
foreach (var error in result.Errors)
Console.WriteLine($"BLOCKED: {error}");
// fail the deploy / CI step here
}
โ ๏ธ Guarantees & limits
- Neither call mutates the database file โ
Inspectnever registers a component,ValidateEvolutionnever allocates segments, migrates entities, or writes a schema history entry. - Safe to run against a database another process has open; each call uses its own short-lived engine and service provider, fully disposed before returning.
ValidateEvolutiononly evaluates the components you register in the callback โ it does not flag persisted components your configuration omits.EvolutionValidationResult.IsValidisfalseonly when a component has breaking changes with no registered migration path; non-breaking (Compatible/CompatibleWidening) changes are reported asNeedsMigrationbut don't fail validation, matching what auto-migration would actually do on real registration.- A dry run is a prediction, not a guarantee โ it doesn't account for runtime conditions at real-registration time (disk space exhaustion mid-migration, a concurrently-open writer, etc.).
๐งช Tests
- OperationalToolingTests โ
Inspect_ReturnsComponentsAndFields(offline read of persisted components/fields),ValidateEvolution_CompatibleChange_IsValid(dry-run reportsNeedsMigration/HasMigrationPathwithout mutating)
๐ Related
- Related feature: Schema Validation on Reopen (the live-path equivalent these calls simulate)