Shared values and graphs
Shared values
Section titled “Shared values”A field of type Arc<T> — a reference or the elements of a collection — is decoded once per entity of a load and
shared: every task with the same assignee holds the same Arc (MPA-LOAD-13).
#[view(to_one(fk = "assignee_id"))]assignee: Option<Arc<PersonView>>,Graphs
Section titled “Graphs”Cyclic data — a manager and their reports, a team and its members — uses Ref<T> fields. A Ref<T> is a typed,
Copy index into a Graph, and the derive generates a method per reference to follow it
(MPA-LOAD-14).
#[derive(View)]#[view(table = "employee")]pub struct Employee { pub name: String, #[view(to_one(fk = "manager_id"))] pub manager: Option<Ref<Employee>>, #[view(child(fk = "manager_id", order_by = "name"))] pub reports: Vec<Ref<Employee>>, #[view(to_one(fk = "team_id"))] pub team: Ref<Team>,}
let graph = mabat::load::<Employee>().by_key(id).graph(&mut conn).await?;let me = graph.root().unwrap();for colleague in me.manager(&graph).unwrap().reports(&graph) { println!("{} in {}", colleague.name, colleague.team(&graph).name);}- A graph load fetches every entity reachable through the
Reffields, each once, with batched queries. Cycles end by themselves; graphs need nodepth. - Navigation borrows the
Graph: no runtime borrow checks. Changes go throughgraph.get_mut(r). GraphisSend + Sync.- A view with
Reffields is loaded withgraph, or as JSON with a selection, which unrolls it into a tree as deep as the selection asks, or withgraph_json, which keeps identity.
A graph as JSON
Section titled “A graph as JSON”graph_json writes the whole graph, each entity once, without a selection
(MPA-JSON-7). An entity is an object with an $id, its view and key, where it is
first reached from the roots; everywhere else it is a $ref to that id, so cycles and shared entities stay as they
are:
let json = mabat::load::<Employee>().by_key(3).graph_json(&mut conn).await?;[{ "$id": "Employee:3", "first_name": "Jane", "manager": { "$id": "Employee:2", "first_name": "Nancy", "manager": { "$id": "Employee:1", "...": "..." }, "reports": [{ "$ref": "Employee:3" }, { "$id": "Employee:4", "...": "..." }] }, "reports": [] }]This is the $id/$ref convention of Json.NET and of several JavaScript libraries that restore the references.
Saving a graph
Section titled “Saving a graph”mabat::save_graph saves every entity of a graph in one transaction, each as save saves a value, with its Ref
fields written as the keys of the entities they point to (MPA-WRITE-14). Build
new entities in a graph with Graph::insert, which returns a Ref to use in other entities:
let mut graph = Graph::<Employee>::new();let team = graph.insert(Team { id: None, name: "Core".into(), members: vec![] });let ada = graph.insert(Employee { id: None, name: "Ada".into(), team, manager: None, reports: vec![] });let grace = graph.insert(Employee { id: None, name: "Grace".into(), team, manager: Some(ada), reports: vec![] });graph.get_mut(ada).reports = vec![grace];graph.get_mut(team).members = vec![ada, grace];graph.add_root(ada);mabat::save_graph(&mut graph, &mut tx).await?; // the generated keys are written back- Order. An entity is saved after the entities it references, so their keys exist, generated or not, and
foreign keys hold as rows are inserted. In a cycle, the optional references are written NULL first and set once
the cycle is saved; required references in a cycle fail with
Error::Write(MPA-WRITE-15). - Collections by a foreign key.
reportsabove is the inverse ofmanager: the samemanager_idcolumn. The reference is what is written, and the collection must agree with it. A collection whose elements have no such reference writes their foreign key itself, and sets it to NULL for rows it no longer holds (MPA-WRITE-16). - Link tables. A collection through a link table replaces the entity’s links (MPA-WRITE-17).
- Everything is written.
save_graphwrites every entity of the graph, changed or not.saveandsave_changesrefuse a value withReffields.
Saving what changed
Section titled “Saving what changed”A graph records the entities added with insert and those handed out by get_mut, whether or not they were then
changed. mabat::save_graph_changes writes only those, in the same order and with the same checks as save_graph
(MPA-WRITE-20):
let mut graph = mabat::load::<Employee>().by_key(id).graph(&mut tx).await?;let ada = graph.root_refs()[0];graph.get_mut(ada).name = "Ada L.".into();assert!(graph.is_changed(ada));mabat::save_graph_changes(&mut graph, &mut tx).await?; // writes Ada's row onlyassert!(!graph.is_changed(ada));- Other entities’ rows are kept. Their keys are used as loaded. The exception is a changed entity’s collection by a foreign key, which writes that key to its elements’ rows.
- Collections of changed entities only. Link rows are replaced, and rows no longer in a collection unlinked, for the changed entities’ collections.
- The graph must still agree. A collection that is the inverse of a reference is checked against it across
the whole graph, so changing one side means changing the other too, as with
save_graph. - A successful save leaves no entity changed; a failed one rolls back and keeps them changed, to save again.