drift icon indicating copy to clipboard operation
drift copied to clipboard

Add `m.diffs` property, `m.applyDiffs()`, and `m.applySafeDiffs()` for auto-generated, ordered migration steps in step-by-step migrations

Open galacticgibbon opened this issue 2 months ago • 2 comments

Description

When generating migrations with make-migrations and using stepByStep, we currently have to manually inspect schema changes and then hand-write m.addColumn, m.createAll, etc. (although it's way better than without the generated step by step!)

This is error-prone - it’s easy to miss something, and there’s no single place that clearly shows what changed.

This proposal adds:

  • a generated property m.diffs, exposing every detected schema change;
  • m.applyDiffs() - a one-liner that applies all detected changes in the correct order;
  • m.applySafeDiffs() - an alternative that applies only non-destructive (safe) changes.

Both serve as documentation: we can click into applyDiffs or m.diffs to view the generated list of migration steps - giving an immediate record of what changed between schema versions.


Proposed API

If Drift can safely determine the correct ordering

I don't know if it's possible to compute a safe ordering of migrations, but if it is possible, it would be nice to have a method like this.

onUpgrade: stepByStep(
  from1To2: (m, s) async {
    await m.applyDiffs();      // applies all schema changes in order
    // or, if you prefer only additive operations:
    // await m.applySafeDiffs();
  },
);

applyDiffs() and applySafeDiffs() would both:

  • Run generated migration steps automatically in a safe, ordered way.
  • Generate a diffs object you can open in your IDE to see each specific change.
  • You could click into the function to see what changed in this migration, so it's self documenting.

When ordering cannot be automatically computed

If Drift can’t safely determine the sequence, m.diffs still provides a full, typed list of the individual migration steps - each one discoverable by autocomplete.

onUpgrade: stepByStep(
  from1To2: (m, s) async {
    await m.diffs.columnEntriesUtcDateTimeAdd();
    await m.diffs.columnEntriesDateUnitAdd();
    await m.diffs.columnEntriesFloatingDateTimeAdd();
    await m.diffs.columnEntriesFloatingTimeAdd();
    await m.diffs.columnDeviceEventsFloatingDateTimeAdd();
    await m.diffs.viewEnrichedEntriesRecreate();
    await m.diffs.createAllNewEntities();
  },
);

This way, it's very easy to generate migrations as you can easily see what changed, but it's still possible to create migrations manually.

galacticgibbon avatar Oct 29 '25 07:10 galacticgibbon

This is error-prone - it’s easy to miss something

That's what the tests are there for :)

In my experience, it's very hard to do an accurate and meaningful diff between schemas. E.g. what happens if you rename a table and also add a column in that table? A diff could pick it up as a rename + add column, but it could just as well be a table deleted + new table added.

So while I think this is something worth adding, I don't like diffs as an API, we need something that clearly describes the fact that the diff isn't always known. An approach I think could work may be to:

1 Start picking up simple differences (say an added column). 2. But instead of generating diffs, suggest the migration code as a snippet ("suggested migration: from1To2: (m, s) async { /* TODO: Suggested migration, which may be inaccurat. */ await m.addColumn(...); }")

That way, there's a clear comment that users need to verify and test the migration.

simolus3 avatar Oct 29 '25 22:10 simolus3

Well, I guess in the case of a table rename + add column, that would just be picked up as a drop table & create table. And then the dev would have to move the data manually if they chose to use that api, instead of applyDiffs().

Personally, I wouldn't be averse to that.

galacticgibbon avatar Oct 30 '25 04:10 galacticgibbon