Relations
A relation is the pair of columns that joins a parent row to its children. has_many names
that pair, and Select::with loads the children for every parent row with one extra
statement. Nothing is registered in the schema and nothing is hidden in a join.
The result has one entry per parent row, in the parent query's order. The parent columns are
decoded into User and the children into a Vec<Post> beside it.
The two statements
with runs the parent query and then one child query. On PostgreSQL that is:
The parent statement selects the columns it would have selected on its own, plus its key. The child statement selects every declared child column, plus the foreign key again. The repetition is deliberate: the model consumes its columns in declaration order, and the trailing copy is where the loader reads the grouping key from.
Both statements are built and run independently, so each numbers its own placeholders from the start. The keys are deduplicated before the second statement, so a parent result with three distinct keys binds three parameters, however many parents there are.
Why it is not a join
A join returns a tuple, (User, Option<Post>), and repeats the same parent columns once per
child. Grouping those rows back into a User with its posts is left to the caller, and the
parent data crosses the wire once per child.
with returns (User, Vec<Post>) instead. It costs one extra round trip for the whole
result, not one per parent, and the parent row is sent once. This is why it is not N+1: the
child statement runs once regardless of how many parents matched. It does not run at all
when the parent result is empty, because IN () is not portable and an empty query is not
worth a round trip.
A parent with no children
Every parent row is present, with an empty Vec when nothing matched. A childless parent is
never missing from the result.
Filters, ordering and pick
The parent query keeps its filters, ordering, limit and projection. with only appends the
parent key to what is selected, so the key does not appear in R:
Inspecting the SQL
to_sql(keys) renders both halves without running either. The keys stand in for the parents
a real load would have found:
What is not modelled
- Many-to-many. A join table needs two column pairs, and no relation type describes that.
- The other direction. There is no
belongs_to: a child does not carry a loaded parent. Query the parent by key, or use a join. - Nesting.
withtakes one relation. A relation on the child table cannot be loaded by the same call. - Per-parent limits and ordering. The child statement has no
ORDER BYand noLIMIT. Members arrive in whatever order the database returned them; sort theVecyourself, or issue the child query by hand. - Writing. Inserting a parent and its children together is not part of this API.
- Schema. Nothing is stored on
TableDef. The foreign key is a separate declaration, and a relation works whether or not the database enforces it.
Not supported yet. Many-to-many,
belongs_to, nested relations and per-parent limits. See #97.
What is checked
- Both columns must exist on their tables, be
NonNull, and share oneSqlType. A nullable foreign key is a compile error; so is ani64key paired with ani32key. withis only available on a single-tableSelect. After an inner or left join the model type is a tuple, and the method is gone.- The parent column is treated as a key. Nothing checks that it is unique, so a non-unique parent key will attach the same child to several parents.
Gotchas
- The relation is not registered. It does not add a foreign key, and
pushdoes not know it exists. withis a second round trip, so it is not free under a very tight latency budget. The child half is one statement for the whole result, not one per row.- A child row whose foreign key is
NULLis outside the relation, becausehas_manytakes twoNonNullcolumns. Declare the key non-null, or query those children separately. - The child statement binds one parameter per distinct key. A parent result with more distinct keys than the database's parameter limit allows will fail at build time; narrow the parent query.
