Expand description
Chain relation methods on Many where one or more hops in the chain has
a composite key. Parallels crate::tests::relation_chain which covers
the all-single-key case.
Two positions are interesting:
- A
BelongsTosecond hop whose foreign key spans multiple columns. - A
HasManyfirst hop whose pairedBelongsToon the target has a composite foreign key.
The shared scenario crate::scenarios::composite_chain_relations
arranges User → Todo → Category so both positions are reachable from a
single dataset: Category has composite PK (id, revision) and Todo’s
FK to it spans (category_id, category_revision). The Todo→User FK is
single-column, so category.todos().user() cross-checks that a composite
first hop chains cleanly into a single-column second hop.
Functions§
- category_
todos_ filter - A
category.todos()chain that ends in.filter(...)narrows the terminal model the same way as in the single-key chain tests. This pairs withcomposite_chain_then_filter(which exercises the second-hop composite case) to make sure both endpoints respect filters. - category_
todos_ respects_ revision Category(id=X, revision=1)andCategory(id=X, revision=2)are two distinct categories. The chain starting at one must not pick up the other’s todos — the filter has to discriminate on both FK columns.- category_
todos_ user_ composite_ first_ hop - Chain starting at a model with a composite PK. The first hop’s pair
(Todo’s
belongs_toback to Category) is composite, so the filter generated forTodo.category_idmust include bothcategory_idandcategory_revision. - composite_
chain_ from_ empty_ source_ is_ empty - A chain whose source produces no rows (
userhas no todos) must short-circuit to an empty result, even when there is unrelated data of the matching shape in the table. - composite_
chain_ then_ filter - Filter applied to the chain’s terminal model narrows the chain’s result.
Mirrors
relation_chain::chain_then_filterbut the terminal model has a composite PK. - composite_
has_ many_ through_ has_ many Author → posts → commentswith composite keys onAuthorandPost. The chain involves two HasMany hops, each producing an IN-subquery against a composite-FK pair. Mirrorsrelation_chain::has_many_through_has_many.- filtered_
category_ todos_ user_ composite Category::filter(name = ...).todos().user()— the chain’s source query is filtered by a non-FK column (name). The fallback path insidelift_belongs_to_in_subquery(which can’t lift the filter onto FK columns) must still produce a working composite-FK IN subquery rather than panicking ontodo!("composite keys").- user_
todos_ category_ composite - Happy path mirroring
relation_chain::user_todos_categorybut theTodo → Categorybelongs_to spans(category_id, category_revision). The result must dedupe on the composite key, not just onid. - user_
todos_ category_ distinguishes_ by_ revision - Two Categories that share
idbut differ inrevisionmust both come back as distinct rows from a chain query. This protects against a regression where the IN-subquery was generated overcategory_idonly, silently merging different revisions.