Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Step 5: Relationships

Tables can declare relationships using with_one and with_many, then traverse them at runtime with get_ref_as. The relationship system is provided by vantage-table — your backend just needs column_table_values_expr implemented to make it work.

Implement column_table_values_expr — it builds a subquery for a single column respecting current conditions. For SQL backends this is a SELECT "col" FROM "table" WHERE ... expression.

Define relationships when constructing tables — with_one for foreign-key-to-parent, with_many for parent-to-children. Then traverse:

#![allow(unused)]
fn main() {
let mut clients = client_table(db);
clients.add_condition(sqlite_expr!("{} = {}", (clients["is_paying_client"]), true));

let orders = clients.get_ref_as::<SqliteDB, ClientOrder>("orders").unwrap();

// The generated query includes the subquery:
// SELECT ... FROM "client_order"
//   WHERE client_id IN (SELECT "id" FROM "client" WHERE is_paying_client = 1)
assert_eq!(orders.list().await.unwrap().len(), 3);
}

with_expression adds computed fields to a table using correlated subqueries. It pairs with get_subquery_as which produces target.fk = source.id conditions (vs get_ref_as which uses IN (subquery)).

#![allow(unused)]
fn main() {
.with_many("orders", "client_id", Order::sqlite_table)
.with_expression("order_count", |t| {
    t.get_subquery_as::<Order>("orders").unwrap().get_count_query()
})
// Generates: (SELECT COUNT(*) FROM "client_order"
//   WHERE "client_order"."client_id" = "client"."id") AS "order_count"
}

What to implement: override related_correlated_condition in your TableSource to produce table-qualified equality. Default panics — backends without correlated subquery support (CSV) simply can’t use this feature.

#![allow(unused)]
fn main() {
fn related_correlated_condition(&self, target_table: &str, target_field: &str,
    source_table: &str, source_column: &str) -> Self::Condition {
    sqlite_expr!("{} = {}", (ident(target_field).dot_of(target_table)),
        (ident(source_column).dot_of(source_table)))
}
}

Requires SelectableDataSource (Step 3) since aggregate query builders use table.select().