82 Inverse relationships
Predica lets you navigate forward along attributes using paths such as
shipment.supplier or shipment.part.color. Inverse relationships
answer the complementary question:
Given a forward attribute, how do we navigate backwards to find all objects that refer to the current one?
Predica introduces the inverse keyword for this purpose. It inverts a
downward path. Informally, it selects the objects that map to
this along that path.
Example: suppliers and shipments
Suppose each Shipment has a supplier, and we want each
Supplier to expose all shipments that refer to it:
entity Supplier
{
Integer id;
String name;
Integer status;
String city;
key {id};
→ shipment* = inverse shipment.supplier;
}
The derived attribute shipment* is defined as
inverse shipment.supplier. Informally, this means:
This derived attribute is read-only unless an explicit view-update rule is specified. Recording a new shipment updates the base shipment collection from which the inverse is derived.
AllShipmentobjects whosesupplierattribute equals thisSupplier.
The expression inside inverse is a downward path:
shipment.supplier. Predica resolves this path starting from the parent
scope of Supplier, finds the collection of shipments, and then inverts
the supplier attribute relative to the current supplier.
Forward and inverse navigation
Forward navigation follows declared attributes:
shipment.supplier: from aShipmentto itsSuppliershipment.part: from aShipmentto itsPart
Inverse navigation goes the other way, by inverting a path:
inverse shipment.supplier: from aSupplierto allShipmentobjects that refer to itinverse shipment.part: from aPartto allShipmentobjects that refer to it
The key idea is that inverse does not name an entity directly. It operates
on a path, and Predica uses the surrounding structure to resolve that path.
Complete shipments example
Here is a complete model of suppliers, parts, and shipments using inverse relationships:
entity Supplier
{
Integer id;
String name;
Integer status;
String city;
key {id};
→ shipment* = inverse shipment.supplier;
}
entity Part
{
Integer id;
String name;
Color color;
Real weight;
String city;
key {id};
→ shipment* = inverse shipment.part;
}
entity Shipment
{
Supplier supplier;
Part part;
Integer qty;
key {supplier, part};
}
entity ShipmentsSystem
{
Supplier supplier*;
Part part*;
Shipment shipment*;
constraints
{
shipment.supplier.Σ ⊆ supplier.Σ;
shipment.part.Σ ⊆ part.Σ;
}
}
In Supplier, the expression inverse shipment.supplier is resolved
by looking up shipment in the parent scope ShipmentsSystem, then
following the supplier attribute inside Shipment, and finally
inverting that mapping relative to the current supplier.
Informal semantics of inverse
An expression of the form:
inverse p
where p is a downward path, is interpreted as:
“The set of all objects at the start of pathpwhose value alongpequalsthis.”
In the supplier example:
pisshipment.supplier- the start of
pis the collection of allShipmentobjects - the end of
pis aSupplier - inside
Supplier,inverse shipment.supplierreturns all shipments whose supplier isthis
Constraints on inverse
The inverse keyword is not a general function; it is a structural operator
with specific constraints. An expression inverse p is valid inside an entity
T only if all of the following hold:
-
Downward path:
pmust be a downward path expression (a sequence of attribute selections). -
Type match:
The final attribute of
pmust have typeT, the entity in whichinverse pappears. This ensures that the path “lands on” objects of the current type. -
Upward resolution (hiding rule):
Resolution of
pstarts in the parent scope ofTand walks upward through ancestor scopes until a matching declaration is found. The nearest matching scope wins.
These constraints guarantee that inverse is:
- structural: it operates on paths, not global entity names
- type-safe: the inverted path always ends at the current entity type
- compositional: it respects the same scoping and hiding rules as normal paths
Why inverse relationships matter
Inverse relationships let you express “backwards” navigation declaratively, without introducing explicit foreign-key style attributes or manual queries. They are especially useful when:
- one entity holds a reference to another (e.g.
Shipment.supplier) - the referenced entity wants to expose the set of all referring objects (e.g. all shipments for a supplier)
By defining inverse attributes once in the schema, you make these queries available everywhere in the model as simple path expressions, such as:
supplier.shipment.part.color
Inverse navigation composes with ordinary query notation, so it can be filtered, reshaped, folded, and observed just like a forward path.