Record Class EmptyRelationNode

java.lang.Object
java.lang.Record
com.darkcollective.relix.ast.EmptyRelationNode
Record Components:
heading - the expression whose schema this empty relation has; must not be null
location - the source location of this node; never null
All Implemented Interfaces:
RelNode

public record EmptyRelationNode(RelNode heading, SourceLocation location) extends Record implements RelNode
The relation with no rows and the heading of another expression — the ∅ that a provably-unsatisfiable query collapses to.

Introduced only by the optimizer's empty-relation propagation (EMPTY-001/EMPTY-002), never by the parser: a contradictory filter such as σ x > 5 ∧ x < 3 (Orders) admits no row whatever Orders contains, so the whole sub-tree is replaced by this node and neither scanned nor joined.

Why not EMPTY / DUM?

TruthRelationNode.empty(SourceLocation) is also row-less, but it is the zero-column relation — the truth value false. Its own Javadoc makes the distinction from the other side: R × EMPTY is empty but keeps R's heading, so it is not EMPTY itself. Every law this node exists to serve is heading-preserving — σ false (R) still has R's columns, and a query that selects from it must still type-check — so a row-less relation that carries a heading is a genuinely different thing from the nullary literal, and gets its own node rather than a flag on that one.

The heading is carried, not computed

relix-ast has no dependency on the symbol layer, so this node cannot hold a Schema directly. It holds the sub-expression it replaced, and schema inference reports that expression's schema — which is exactly the heading required, at every step of the propagation: replacing ∅ ⋈ X means carrying the heading of the whole join, not of the empty side.

The heading is not a child

RelNode.children() is empty and RelNode.mapChildren(java.util.function.UnaryOperator<com.darkcollective.relix.ast.RelNode>) returns this, so this is a leaf and the carried expression is inert: no pass recurses into it, nothing rewrites it, and the executor never touches it. That is deliberate in both directions. It is dead code — running any of it would be the work this rule exists to avoid — and, more sharply, a rewrite that changed the carried expression's schema would silently change this node's heading, which would be a correctness bug rather than a missed optimisation.

  • Constructor Details

    • EmptyRelationNode

      public EmptyRelationNode(RelNode heading, SourceLocation location)
      Validates that both components are present.
  • Method Details

    • of

      public static EmptyRelationNode of(RelNode heading)
      Creates an empty relation with heading's schema, taking its source location from that expression so a diagnostic still points at the query text the rule collapsed.
      Parameters:
      heading - the expression whose schema and location to adopt; must not be null
      Returns:
      the empty relation; never null
    • accept

      public <R> R accept(RelNodeVisitor<R> visitor)
      Specified by:
      accept in interface RelNode
    • toString

      public final String toString()
      Returns a string representation of this record class. The representation contains the name of the class, followed by the name and value of each of the record components.
      Specified by:
      toString in class Record
      Returns:
      a string representation of this object
    • hashCode

      public final int hashCode()
      Returns a hash code value for this object. The value is derived from the hash code of each of the record components.
      Specified by:
      hashCode in class Record
      Returns:
      a hash code value for this object
    • equals

      public final boolean equals(Object o)
      Indicates whether some other object is "equal to" this one. The objects are equal if the other object is of the same class and if all the record components are equal. All components in this record class are compared with Objects::equals(Object,Object).
      Specified by:
      equals in class Record
      Parameters:
      o - the object with which to compare
      Returns:
      true if this object is the same as the o argument; false otherwise.
    • heading

      public RelNode heading()
      Returns the value of the heading record component.
      Returns:
      the value of the heading record component
    • location

      public SourceLocation location()
      Returns the value of the location record component.
      Specified by:
      location in interface RelNode
      Returns:
      the value of the location record component