Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions reference/database/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,14 +397,14 @@ type Network @table @export {

### `@relationship(from: attribute, to: attribute)` — foreign key to foreign key

Both `from` and `to` can be specified together to define a relationship where neither side uses the primary key — a foreign key to foreign key join. This is useful for many-to-many relationships that join on non-primary-key attributes.
Both `from` and `to` can be specified together to define a relationship where neither side uses the primary key — a foreign key to foreign key join. Cardinality is determined by the target (`to`) attribute: a scalar `to` attribute gives a many-to-one join (as below), while an array `to` attribute gives a many-to-many join.

```graphql
type OrderItem @table @export {
id: Long @primaryKey
orderId: Long @indexed
productSku: Long @indexed
product: Product @relationship(from: productSku, to: sku) # join on sku, not primary key
product: Product @relationship(from: productSku, to: sku) # many-to-one join on sku, not primary key

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High: the corrected example does not actually work in Harper

Relabeling the join as many-to-one is directionally right for the intended cardinality, but the schema as written doesn't resolve at all on current main (513f87471), regardless of label. Table.ts's attribute-wiring (updatedAttributes(), around resources/Table.ts:4414-4450) is if (relationship.to) { if (attribute.elements?.definition) {...} } else if (relationship.from) {...}. Since to: sku is set, it takes the first branch unconditionally — and because product: Product is a scalar field (no attribute.elements), that branch's else fires: console.error('...must have an array type referencing a table as the elements'), and attribute.resolve is left null. The else if (relationship.from) branch — the one that actually knows how to resolve a single scalar relationship — is never reached when to is also set.

I verified this empirically against a live instance built from origin/main with this exact schema: GET /OrderItem/:id (no select) silently omits product from the response entirely (not even null), and GET /OrderItem/:id?select(...,product{...}) (the standard way to expand a relationship, as used elsewhere in these docs/tests) 500s with TypeError: Cannot read properties of undefined (reading 'propertyResolvers') in Resource.ts (transformForSelect), because it dereferences attribute.resolve.definition on a null resolve.

The combined from+to resolver path only registers when the field type is an array (attribute.elements?.definition) — there is no code path that resolves a scalar foreign-key-to-foreign-key relationship by a non-primary-key to attribute. So the fix that actually makes this example work is products: [Product] @relationship(from: productSku, to: sku) (array field), not a comment/prose relabel — and once it's array-typed, it's structurally a one-to-many/many-to-many resolver in the code regardless of how unique sku is in practice.

This is a pre-existing bug in the example (not introduced by this PR), but the PR's stated purpose is specifically to make this example accurate, and its test plan only checked search.ts's isManyToMany (the query-filter cardinality calc), not Table.ts's resolver wiring (the code path that determines whether the field resolves at all). Worth fixing before merge so the docs don't keep shipping a schema that 500s when a reader follows it.

Suggested fix: change the field to an array type (products: [Product] @relationship(from: productSku, to: sku)) and update the surrounding prose (line 400) accordingly, or drop the to argument and this section's non-primary-key framing until Harper actually supports scalar-target foreign-key-to-foreign-key resolution.


Generated by Barber AI

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This issue sounds legit.

}

type Product @table @export {
Expand Down