computeSchemaDiff function
: compute the diff between two
snapshots. Returns a deterministic list of
SchemaDiffs. The caller decides what to do
with unsafe entries (typically: log them,
surface them via Db.pendingSchemaDiff(),
and DO NOT apply them).
Implementation
List<SchemaDiff> computeSchemaDiff(
SchemaSnapshot old,
SchemaSnapshot newSnap,
) {
// Sanity: the snapshot versions must be
// compatible. A 1.1.x runtime that somehow
// finds a 2.0.0 snapshot in the table should
// refuse, not silently corrupt.
if (old.version > newSnap.version) {
throw StateError(
'Schema snapshot downgrade detected: stored '
'snapshot is v${old.version}, runtime is '
'v${newSnap.version}. The database was likely '
'written by a newer d_rocket. Refusing to '
'migrate.',
);
}
final List<SchemaDiff> out = <SchemaDiff>[];
// Build lookup maps keyed by table name.
final Map<String, SchemaTable> oldTables = <String, SchemaTable>{
for (final SchemaTable t in old.tables) t.name: t,
};
final Map<String, SchemaTable> newTables = <String, SchemaTable>{
for (final SchemaTable t in newSnap.tables) t.name: t,
};
// 1. Tables that are in new but not in old:
// CREATE TABLE (safe).
for (final SchemaTable t in newSnap.tables) {
if (!oldTables.containsKey(t.name)) {
out.add(SchemaDiff(
severity: DiffSeverity.safe,
type: SchemaOperationType.createTable,
tableName: t.name,
sql: _createTableSql(t),
reason:
'New entity; CREATE TABLE IF NOT EXISTS is '
'idempotent and non-destructive.',
));
}
}
// 2. Tables that are in old but not in new:
// DROP TABLE (unsafe).
for (final SchemaTable t in old.tables) {
if (!newTables.containsKey(t.name)) {
out.add(SchemaDiff(
severity: DiffSeverity.unsafe,
type: SchemaOperationType.dropTable,
tableName: t.name,
sql: 'DROP TABLE ${t.name}',
reason:
'Entity removed from code; dropping the '
'table would lose all its data. Confirm '
'manually and write a hand-rolled '
'migration that does the drop explicitly.',
));
}
}
// 3. Tables that are in both: compare columns
// and indexes. Skip tables that are in
// neither (already handled above).
for (final SchemaTable newT in newSnap.tables) {
final SchemaTable? oldT = oldTables[newT.name];
if (oldT == null) continue;
_diffTable(out, oldT, newT);
}
// 4. Rename heuristic. After the column-level
// diff is complete, look for unsafe
// dropColumn + safe addColumn pairs that
// could be a RENAME. This is a best-effort
// suggestion only; the user is expected to
// confirm manually.
_appendRenameSuggestions(out, old, newSnap);
return out;
}