Migrations

Reflect deliberately separates validation, conservative migration, and destructive development reset.

Additive Sync

db.migrate<User>();

Additive sync:

  • creates the table if missing
  • adds missing model columns
  • creates expected indexes
  • validates the schema after applying the plan

It does not drop columns, rename columns, change types, rewrite constraints, or delete data.

Versioned Manual Migrations

db.apply_migrations({
    reflect::migration{
        .id = "001_add_search_index",
        .statements = {
            reflect::statement{
                .sql = "CREATE INDEX IF NOT EXISTS \"idx_users_name\" ON \"users\" (\"name\")",
            },
        },
    },
});

Applied migration IDs are stored in reflect_schema_migrations. Migrations are transactional by default.

reflect::migration{
    .id = "003_non_transactional_maintenance",
    .statements = {
        reflect::statement{.sql = "..."},
    },
    .transactional = false,
};

When apply_migrations is called inside client::transaction, Reflect disables the inner migration transaction and lets the outer transaction control commit or rollback.

Versioned Model Sync

db.migrate_versioned<User>("001_users");

This creates the same additive plan as migrate<User>(), records the migration ID if it ran, and validates the table afterward. If the migration ID is already recorded, it skips statements and still validates the table.

Destructive Development Reset

Use force mode only for development databases, tests, and demos:

db.migrate<User>({
    .force = true,
});

If the table exists and validation reports drift, Reflect drops and recreates the table.

You can also force a reset directly:

db.reset_schema<User>();

These APIs can destroy data. Do not use them against production databases unless data loss is intended.

How This Compares To Mature ORMs

Reflect’s current migration system is useful for:

  • first schema creation
  • additive local changes
  • hand-written versioned SQL migrations
  • startup drift checks
  • development resets

It is not yet equivalent to TypeORM, Sequelize, Diesel, SeaORM, or SQLx migration workflows. Missing pieces include generated migration files, down migrations, whole-database drift detection, column rename/drop/type-change planning, and CLI-driven migration application.

Migration Planning API

For tooling, you can build the generated plan without applying it:

auto model = reflect::describe_model<User>(reflect::dialect::sqlite);
auto plan = reflect::plan_migration(model, existing_column_names);

for(const auto& statement: plan.statements)
{
    // inspect statement.sql and statement.binds
}

plan_migration only knows existing column names. Full drift details come from schema validation.


This site uses Just the Docs, a documentation theme for Jekyll.