The short answer
1 / 11 · at a glance in the full document · PDF
A view is a Rust struct that declares the shape of the data to load. The derive turns it into a static shape at compile time; the planner turns the shape into a tree of queries, one per relationship; the executor runs them level by level, each batched by the keys of the rows above, and decodes every row by its alias. A DBA can replace the SQL of any query, checked against the type when the application starts. Writes use the same shape the other way: an encoder builds a tree of rows, and the statements run when they are called.
Declared, not discovered. A view says everything a load will fetch: its columns, embedded values, to-one references and to-many collections MPA-CORE-1. The values it loads form an aggregate: the row, its embedded values, the rows of its owned collections and its variant tables MPA-CORE-2. There is no lazy loading MPA-NOT-4: a load never reaches the database again because code touched a field. The same table may have many views, each the shape one use of the data needs.
Generated at compile time. #[derive(View)] generates the shape as static data, and for each enabled database a decoder and an encoder; nothing is computed by reflection at run time MPA-CORE-3. Views whose column types cannot be written, or serialized as JSON, still compile and fail only when used that way MPA-WRITE-11 MPA-JSON-2. mabat-core, which plans and renders SQL, has no database dependency, so planning is unit-tested without one.
Against an ORM. An ORM's unit of a read is an entity, and what else loads depends on what the code touches; its queries are decided at run time, and its state is a session. Mabat's queries are fixed by the shape and can be printed before they run MPA-PLAN-5; its values are plain structs; and its writes run when called, in your transaction MPA-WRITE-1. Page 9 compares the two on change tracking, point by point.