Relix

Problem solving

When the rule is recursive

Grain: one row per derived pair · Class: Graph · Signals: ancestors, explosion, all descendants, parts of parts, keep expanding, until nothing new · Operators: FIX

The problem

"Our bill of materials stores 'assembly contains part' as direct edges. Give me every component of a bike at any depth — the full parts explosion."

How to recognise it

The question needs a rule applied to its own output, repeatedly — all descendants, parts of parts, ancestors, keep expanding until nothing new appears. It is the same "follow the chain" shape as reachability, but reach for FIX when the two-column CLOSURE cannot say it: the step needs extra columns, a filter, or a join against another relation each round. FIX is the general recursion operator — SQL's WITH RECURSIVE, one Datalog rule.

You give it a base (where to start) and a step (how to derive more from what you have so far); it repeats the step until the answer stops growing.

The data

Contains := [
| assembly | part  |
|----------|-------|
| Bike     | Frame |
| Bike     | Wheel |
| Wheel    | Rim   |
| Wheel    | Spoke |
| Wheel    | Tyre  |
| Tyre     | Tube  |
];

A bike contains a wheel, a wheel contains a tyre, a tyre contains a tube — three levels deep.

Recipe: base and step to a fixpoint (FIX)

FIX name (base, step) binds name inside the step to everything derived so far. The step joins what you have to the next edge and re-labels the result to stay union-compatible with the base:

Query
Explosion := { FIX BOM (
  Contains,
  π assembly, sub → part (BOM ⋈ ρ Edge(part, sub) (Contains))
) };
query { τ assembly, part (Explosion) };
Result
 assembly  part
 ────────  ─────
 Bike      Frame
 Bike      Rim
 Bike      Spoke
 Bike      Tube
 Bike      Tyre
 Bike      Wheel
 Tyre      Tube
 Wheel     Rim
 Wheel     Spoke
 Wheel     Tube
 Wheel     Tyre
(11 rows)

Read the step: renaming Contains to Edge(part, sub) makes its first column share the name part, so BOM ⋈ Edge links each known part to what it contains; the projection relabels the deeper sub back to part. "Everything to order for a bike" is then a selection — folded into the recursion by the optimiser, since assembly is carried through unchanged:

Query
query { π part (σ assembly = "Bike" (Explosion)) };
Result
 part
 ─────
 Frame
 Wheel
 Rim
 Spoke
 Tyre
 Tube
(6 rows)

Variations

Pitfalls

Check it