Skip to content

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 default
struct 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,
}
AttributeMeaningRule
table = "t"The table the view is loaded fromMPA-VIEW-1
key = "c"The key column, id by defaultMPA-VIEW-2
embeddedA struct stored in the columns of the view that contains itMPA-VIEW-3
databases = "postgres, mysql"Limit the databases the view is generated forMPA-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.

  • Columns are named like the field, unless column names them. Option<T> is nullable; a NULL in a non-Option field is a decode error that names the path (MPA-VIEW-5).
  • json decodes a JSON column with serde (MPA-VIEW-6).
  • embed holds 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 as T, Box<T>, Arc<T> or Ref<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.
  • version marks 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.

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.