14 Pushing relationships down into the entities

Schema which aligns closely with the RM

Predica can also express the supplier-and-parts database in a form that closely mirrors the classical RM structure:


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.Σ;
    }
}

This mirrors the classical RM structure. The base attribute ShipmentsSystem.shipment resembles a global shipments relvar with two foreign keys supplier and part. The foreign keys are expressed as inclusion dependencies:


constraints 
{
    shipment.supplier.Σ ⊆ supplier.Σ;
    shipment.part.Σ ⊆ part.Σ;
}

There are two derived attributes Supplier.shipment and Part.shipment declared using inverse:


entity Supplier
{
    ...
    → shipment* = inverse shipment.supplier;
}
entity Part
{
    ...
    → shipment* = inverse shipment.part;
}

These enable path-based navigation from suppliers to shipments and from parts to shipments. For example, the following expression returns the parts shipped by London suppliers:


system.supplier.[city = "London"].shipment.part;

This is a faithful RM-style design, and it works perfectly in Predica. But it carries the same structural implications as the RM:

  • ShipmentsSystem.shipment behaves like a global relationship base relvar.
  • Navigation from suppliers/parts to shipments depends on inverse.
  • A physical implementation would likely materialise a join table.

Predica supports this, but it does not require it.

Pushing relationships down into the entities

The schema used in the introduction pushes the relationship down into the entities that participate in it. This gives a logically equivalent schema that maps directly to a more efficient physical implementation, avoiding the need for inverse entirely:


entity Supplier
{
    Integer id;
    String name;
    Integer status;
    String city;
    Shipment shipment* where shipment.supplier = this;
    key {id};
}

entity Part
{
    Integer id;
    String name;
    Color color;
    Real weight;
    String city;
    Shipment shipment* where shipment.part = this;
    key {id};
}

entity Shipment
{
    Supplier supplier;
    Part part;
    Integer qty;
    key {supplier,part};
}

entity ShipmentsSystem
{
    Supplier supplier*;
    Part part*;
    → Shipment shipment* = either supplier.shipment, part.shipment;
}

This version is:

  • logically equivalent to the RM-style schema
  • physically more efficient
  • free of inverse declarations
  • free of global relationship relvars

The key idea is simple: A relationship belongs with the entities that participate in it. In Predica, this is not just a stylistic preference, it is a semantic advantage.

Why this works so well

A shipment is naturally “owned” by its supplier and its part. Predica lets you express this directly:


entity Supplier
{
    ...
    Shipment shipment* where shipment.supplier = this;
}

entity Part
{
    ...
    Shipment shipment* where shipment.part = this;
}

These are declarative constraints, not physical pointers. For the supplier it means: The shipments belonging to this supplier are exactly those whose supplier attribute equals this supplier. This expresses the same information as a foreign key, but in a way that:

  • supports downward navigation
  • avoids global join tables
  • avoids inverse declarations
  • avoids surrogate identifiers

Shared ownership emerges naturally

Because both Supplier and Part define their shipments, the system exhibits logical shared ownership:

  • each supplier records the shipments for which it is the supplier
  • each part records the shipments that ship that part

This is shared ownership without:

  • object identity
  • reference semantics
  • object identifiers
  • aliasing problems

It is a purely logical notion of sharing, not an OO one.

The global relationship becomes derived, not stored


→ Shipment shipment* = either supplier.shipment, part.shipment;

This is a derived attribute, not a stored one. It declares two equivalent definitions, and the compiler may use whichever one is cheaper in a given query. This is the Predica analogue of:

  • a view
  • a derived relvar
  • a logical relationship

but without the RM’s suggestion for a physical join table.

Emphasising local relationships in queries

Consider the following query in Tutorial D which finds the parts shipped by London suppliers:


(SP JOIN S WHERE CITY='London'){PNO} JOIN P

The most direct counterpart in Predica uses the global shipment predicate:


s.shipment.[supplier.city = "London"].part

However, we can take advantage of the local navigation from supplier to shipment and instead write:


s.supplier.[city = "London"].shipment.part

The second is preferred because it follows the structure of the schema, avoids filtering a global relationship, and is suggestive of an efficient implementation using local navigation.