Locy Use Case: Supply Chain Provenance (Rust)¶
Trace multi-hop upstream supplier lineage for a finished component.
This notebook uses schema-first mode and mirrors the Python flow using the Rust API (uni_db).
How To Read This Notebook¶
- Define schema first, then load data.
- Keep Locy rules declarative and focused.
- Read output rows together with materialization stats.
1) Setup¶
Initialize an in-memory database and import DataType for schema definitions.
In [ ]:
Copied!
use uni_db::{DataType, Uni, Result};
let db = Uni::in_memory().build().await?;
use uni_db::{DataType, Uni, Result};
let db = Uni::in_memory().build().await?;
2) Define Schema (Recommended)¶
Define labels, typed properties, and edge types before inserting graph facts.
In [ ]:
Copied!
db.schema()
.label("Part")
.property("sku", DataType::String)
.property("kind", DataType::String)
.edge_type("SOURCED_FROM", &["Part"], &["Part"])
.apply()
.await?;
println!("Schema created");
db.schema()
.label("Part")
.property("sku", DataType::String)
.property("kind", DataType::String)
.edge_type("SOURCED_FROM", &["Part"], &["Part"])
.apply()
.await?;
println!("Schema created");
3) Seed Graph Data¶
Insert the minimal graph needed for the scenario.
In [ ]:
Copied!
db.execute("CREATE (:Part {sku: 'C1', kind: 'finished'})").await?;
db.execute("CREATE (:Part {sku: 'B1', kind: 'subassembly'})").await?;
db.execute("CREATE (:Part {sku: 'B2', kind: 'subassembly'})").await?;
db.execute("CREATE (:Part {sku: 'R1', kind: 'raw'})").await?;
db.execute("CREATE (:Part {sku: 'R2', kind: 'raw'})").await?;
db.execute("MATCH (c:Part {sku:'C1'}), (b1:Part {sku:'B1'}) CREATE (c)-[:SOURCED_FROM]->(b1)").await?;
db.execute("MATCH (c:Part {sku:'C1'}), (b2:Part {sku:'B2'}) CREATE (c)-[:SOURCED_FROM]->(b2)").await?;
db.execute("MATCH (b1:Part {sku:'B1'}), (r1:Part {sku:'R1'}) CREATE (b1)-[:SOURCED_FROM]->(r1)").await?;
db.execute("MATCH (b2:Part {sku:'B2'}), (r2:Part {sku:'R2'}) CREATE (b2)-[:SOURCED_FROM]->(r2)").await?;
println!("Seeded graph data");
db.execute("CREATE (:Part {sku: 'C1', kind: 'finished'})").await?;
db.execute("CREATE (:Part {sku: 'B1', kind: 'subassembly'})").await?;
db.execute("CREATE (:Part {sku: 'B2', kind: 'subassembly'})").await?;
db.execute("CREATE (:Part {sku: 'R1', kind: 'raw'})").await?;
db.execute("CREATE (:Part {sku: 'R2', kind: 'raw'})").await?;
db.execute("MATCH (c:Part {sku:'C1'}), (b1:Part {sku:'B1'}) CREATE (c)-[:SOURCED_FROM]->(b1)").await?;
db.execute("MATCH (c:Part {sku:'C1'}), (b2:Part {sku:'B2'}) CREATE (c)-[:SOURCED_FROM]->(b2)").await?;
db.execute("MATCH (b1:Part {sku:'B1'}), (r1:Part {sku:'R1'}) CREATE (b1)-[:SOURCED_FROM]->(r1)").await?;
db.execute("MATCH (b2:Part {sku:'B2'}), (r2:Part {sku:'R2'}) CREATE (b2)-[:SOURCED_FROM]->(r2)").await?;
println!("Seeded graph data");
4) Locy Program¶
Rules derive relations, then QUERY ... WHERE ... RETURN ... projects the final answer.
In [ ]:
Copied!
let program = r#"CREATE RULE upstream AS\nMATCH (a:Part)-[:SOURCED_FROM]->(b:Part)\nYIELD KEY a, KEY b\n\nCREATE RULE upstream AS\nMATCH (a:Part)-[:SOURCED_FROM]->(mid:Part)\nWHERE mid IS upstream TO b\nYIELD KEY a, KEY b\n\nQUERY upstream WHERE a.sku = 'C1' RETURN b.sku AS supplier_sku, b.kind AS supplier_kind"#;
let program = r#"CREATE RULE upstream AS\nMATCH (a:Part)-[:SOURCED_FROM]->(b:Part)\nYIELD KEY a, KEY b\n\nCREATE RULE upstream AS\nMATCH (a:Part)-[:SOURCED_FROM]->(mid:Part)\nWHERE mid IS upstream TO b\nYIELD KEY a, KEY b\n\nQUERY upstream WHERE a.sku = 'C1' RETURN b.sku AS supplier_sku, b.kind AS supplier_kind"#;
5) Evaluate¶
Evaluate the Locy program and inspect stats/rows.
In [ ]:
Copied!
let result = db.locy().evaluate(program).await?;
println!("Derived relations: {:?}", result.derived.keys().collect::<Vec<_>>());
println!("Iterations: {}", result.stats().total_iterations);
println!("Queries executed: {}", result.stats().queries_executed);
for (name, rows) in &result.derived {
println!("{}: {} row(s)", name, rows.len());
}
if let Some(rows) = result.rows() {
println!("Rows: {:?}", rows);
}
let result = db.locy().evaluate(program).await?;
println!("Derived relations: {:?}", result.derived.keys().collect::>());
println!("Iterations: {}", result.stats().total_iterations);
println!("Queries executed: {}", result.stats().queries_executed);
for (name, rows) in &result.derived {
println!("{}: {} row(s)", name, rows.len());
}
if let Some(rows) = result.rows() {
println!("Rows: {:?}", rows);
}
6) What To Expect¶
Use these checks to validate output after evaluation:
- For
C1, output should include both subassemblies (B1,B2) and raw parts (R1,R2). supplier_kindhelps separate immediate suppliers vs deeper upstream tiers.- This same pattern scales to provenance and recall workflows.
Notes¶
- Rust notebooks are included for API parity and learning.
- In this docs build, Rust notebooks are rendered without execution.