Change tracking, against an ORM
9 / 11 · save_changes in the full document · PDF
An ORM tracks changes transparently: the objects you load belong to a session that keeps a snapshot of each, replaces your references and collections with proxies and wrappers that load and record on their own, and at flush compares every object with its snapshot to decide what to write. Mabat tracks changes explicitly: you keep the value as it was loaded, change a copy, and save_changes(&before, &mut after) compares the two with code the derive generated, writing only the rows that differ — now, in your transaction.
How an ORM does it. JPA providers such as Hibernate keep a persistence context: an identity map with one managed object per row, a snapshot of each object's state as loaded, and a queue of pending writes. Lazy references are proxies — generated subclasses that load on first touch — and collections are replaced by the provider's own, which record changes. At flush, before a query that might see a change or at commit, each managed object is compared with its snapshot (or, with bytecode enhancement, its setters will have marked it dirty), and the differences become SQL. It is convenient and invisible, and so is when and why SQL runs.
How Mabat does it. Values are plain structs with no session behind them, so there is nothing to intercept a change. The snapshot is before, a value you hold. save_changes compares it with after through a comparison the derive generated: columns and embedded values by PartialEq (a type without it counts as changed), to-one foreign keys, owned collection elements matched by key — changed ones updated, new ones saved whole, removed ones deleted with what they own, moved ones of an index list given their position — and link tables whose keys differ. Rows that did not change produce no statement MPA-WRITE-8.
What it buys, and costs. SQL runs exactly when you call it, in your transaction or a savepoint of it MPA-WRITE-1; there is no unit of work that flushes later MPA-NOT-3, and no lazy load after a session closed, because there is none MPA-NOT-4. before can be kept across requests, cached or reloaded; it must be what the database held, and a #[view(version)] column turns a stale one into Error::Conflict instead of a wrong write MPA-WRITE-9. The two values must have the same key. In Rust, which has no runtime proxies, the same explicitness is common: SeaORM's ActiveModel marks fields Set or Unchanged by hand.