Skip to content

Schema Objects

The ISchemaObject interface is the foundation of Weasel's schema management. Every database object -- whether a table, sequence, function, view, stored procedure, or extension -- implements this interface, giving Weasel a uniform way to create, drop, inspect, and diff any object in any supported database.

The ISchemaObject Interface

Defined in Weasel.Core, the interface looks like this:

cs
public interface ISchemaObject_Sample
{
    DbObjectName Identifier { get; }

    void WriteCreateStatement(Migrator migrator, TextWriter writer);
    void WriteDropStatement(Migrator rules, TextWriter writer);
    void ConfigureQueryCommand(Weasel.Core.DbCommandBuilder builder);
    Task<ISchemaObjectDelta> CreateDeltaAsync(DbDataReader reader, CancellationToken ct = default);
    IEnumerable<DbObjectName> AllNames();
}

snippet source | anchor

MemberPurpose
IdentifierA DbObjectName that holds the schema and name of the object (e.g. public.users or dbo.orders).
WriteCreateStatement()Writes the DDL to create this object in a target database. The Migrator parameter supplies provider-specific formatting rules.
WriteDropStatement()Writes the DDL to drop this object.
ConfigureQueryCommand()Registers the SQL query that fetches the current state of this object from the live database.
CreateDeltaAsync()Reads the results of that query and returns an ISchemaObjectDelta describing how the expected configuration differs from the actual database state.
AllNames()Returns every DbObjectName this object produces. A table typically returns its own name plus the names of any indexes it creates.

ISchemaObjectDelta

When Weasel compares what you configured against what actually exists in the database, the result is an ISchemaObjectDelta:

cs
public interface ISchemaObjectDelta_Sample
{
    ISchemaObject SchemaObject { get; }
    SchemaPatchDifference Difference { get; }
    void WriteUpdate(Migrator rules, TextWriter writer);
    void WriteRollback(Migrator rules, TextWriter writer);
    void WriteRestorationOfPreviousState(Migrator rules, TextWriter writer);
}

snippet source | anchor

The Difference property tells you the outcome of the comparison using SchemaPatchDifference:

ValueMeaning
NoneThe database matches configuration. No action needed.
CreateThe object does not exist in the database and needs to be created.
UpdateThe object exists but differs from the configuration. An incremental update is possible.
InvalidThe object exists but cannot be updated incrementally. A full recreation may be required.

Object Hierarchy

The following diagram shows how concrete schema objects relate to ISchemaObject and the ITable sub-interface:

Each database provider supplies its own implementations. For example, PostgreSQL tables live in Weasel.Postgresql.Tables.Table, while SQL Server tables are Weasel.SqlServer.Tables.Table. Both implement ITable (which extends ISchemaObject), but each adds provider-specific features like PostgreSQL partitioning or SQL Server clustered indexes.

SchemaObjectDelta<T>

Weasel provides a generic base class SchemaObjectDelta<T> that simplifies building deltas for a specific schema object type:

cs
public abstract class SchemaObjectDelta_Sample<T> : ISchemaObjectDelta where T : ISchemaObject
{
    public T Expected { get; } = default!;
    public T? Actual { get; }
    public SchemaPatchDifference Difference { get; }

    public ISchemaObject SchemaObject => Expected;

    protected abstract SchemaPatchDifference compare(T expected, T? actual);
    public abstract void WriteUpdate(Migrator rules, TextWriter writer);
    public abstract void WriteRollback(Migrator rules, TextWriter writer);
    public abstract void WriteRestorationOfPreviousState(Migrator rules, TextWriter writer);
}

snippet source | anchor

The constructor calls compare() to determine the Difference between the expected and actual objects. If Actual is null, the object does not exist yet and the difference is Create.

Post-Processing with ISchemaObjectWithPostProcessing

Some schema objects need to examine other objects before they can finalize their own configuration. For example, PostgreSQL partitioned tables may need to adjust foreign key definitions based on the partition strategy of related tables.

cs
public interface ISchemaObjectWithPostProcessing_Sample : ISchemaObject
{
    void PostProcess(ISchemaObject[] allObjects);
}

snippet source | anchor

The migration infrastructure calls PostProcess() after all objects have been loaded, passing the full array of schema objects so the implementing object can make any cross-object adjustments.

DbObjectName

The DbObjectName record represents a qualified database object name with both a Schema and a Name component. Each provider has its own conventions:

ProviderDefault SchemaExample
PostgreSQLpublicpublic.users
SQL Serverdbodbo.orders
OracleWEASELWEASEL.CUSTOMERS
SQLitemainmain.sessions
MySQLpublicpublic.products

Released under the MIT License.