Declaring views
A view is a Rust struct deriving mabat::View. It names its table, and each field is a column unless an attribute
makes it an embedded value, a reference or a collection (MPA-CORE-1).
use mabat::View;use uuid::Uuid;
#[derive(View)]#[view(table = "task")] // key = "id" by defaultstruct TaskView { id: Uuid, name: String, description: Option<String>, // nullable: NULL is None #[view(column = "created")] created_at: chrono::DateTime<chrono::Utc>, #[view(json)] metadata: Option<Metadata>, // any serde type, from a JSON or JSONB column #[view(embed(prefix = "addr_"))] address: Address, // columns addr_street, addr_city #[view(to_one(fk = "assignee_id"))] assignee: Option<PersonView>, // a reference to another view #[view(child(fk = "parent_id", order_by = "position, name desc"))] children: Vec<SubtaskView>, // a collection of another view}
#[derive(View)]#[view(embedded)]struct Address { street: String, city: String,}The view
Section titled “The view”| Attribute | Meaning | Rule |
|---|---|---|
table = "t" | The table the view is loaded from | MPA-VIEW-1 |
key = "c" | The key column, id by default | MPA-VIEW-2 |
embedded | A struct stored in the columns of the view that contains it | MPA-VIEW-3 |
databases = "postgres, mysql" | Limit the databases the view is generated for | MPA-DB-2 |
The same table can have as many views as the use cases that read it: a list view with three columns and a detail view with every relationship.
An embedded struct may be generic over types, for values that recur with different types: each use is a shape of its own (MPA-VIEW-4).
#[derive(View)]#[view(embedded)]struct Range<T> { start: T, end: T,}
#[derive(View)]#[view(table = "booking")]struct Booking { id: i64, #[view(embed(prefix = "stay_"))] stay: Range<NaiveDate>, // stay_start, stay_end #[view(embed(prefix = "guests_"))] guests: Range<i32>, // guests_start, guests_end}A field typed by a parameter is a column or another embedded struct, and its type implements
mabat::GenericColumn: decoded and encoded by the database, Serialize for JSON (chrono needs its serde feature),
Clone and PartialEq. Views themselves are not generic: a table has fixed column types.
Fields
Section titled “Fields”- Columns are named like the field, unless
columnnames them.Option<T>is nullable; a NULL in a non-Optionfield is a decode error that names the path (MPA-VIEW-5). jsondecodes a JSON column withserde(MPA-VIEW-6).embedholds an embedded struct or an enum, whose columns carry an optional prefix; embedded values nest and prefixes concatenate (MPA-VIEW-7).to_one(fk = "c")references another view through a foreign key of this table, held asT,Box<T>,Arc<T>orRef<T>.Option<T>makes it optional; a required reference to a missing row is an error (MPA-VIEW-8). A reference to its own view, such as a parent, is recursive.child(fk = "c")is a to-many collection, described in Collections and recursion.versionmarks an integer column for optimistic locking.
Invalid combinations are compile errors that name the attribute: a child without fk, index on a map,
version on a collection (MPA-VIEW-11). The complete list is on the
attributes reference.
How fields are read
Section titled “How fields are read”Every query aliases its columns with the field’s path — name, address.city, children.notes — and rows are
decoded by alias, never by position (MPA-PLAN-2,
MPA-PLAN-3). That is what lets an override select columns in any
order, from any table, as long as it keeps the aliases.