Class RelNodeOperands
RelNode.children(): a single, exhaustive
registration point for the Operand and Predicate expressions each
RelNode carries directly.
children() answers "what relations feed this node"; this answers "what
expressions does this node evaluate". The second question had no single answer before
— every analysis that needed it either wrote its own partial switch or, more
often, missed the expressions entirely. Two node kinds are the classic blind spot,
because they carry expressions that are not relational children and therefore
do not show up in any structural traversal at all:
RelationFunctionCall.arguments() (a leaf, so children() is empty) and
LateralJoinNode.arguments() (children() reports only the left input).
Like children(), the implementation is an exhaustive switch over the
sealed hierarchy with no default arm, so a new RelNode does not
compile until its expressions are registered here. That is the point: an analysis
built on this walker cannot silently go stale when the AST grows.
The walk comes in two forms — forEach(com.darkcollective.relix.ast.RelNode, java.util.function.Consumer<com.darkcollective.relix.ast.Operand>, java.util.function.Consumer<com.darkcollective.relix.ast.Predicate>) reads the expressions, and
map(com.darkcollective.relix.ast.RelNode, java.util.function.UnaryOperator<com.darkcollective.relix.ast.Operand>, java.util.function.UnaryOperator<com.darkcollective.relix.ast.Predicate>) rewrites them, returning the rebuilt node. Both are exhaustive, so the
"which expressions does this node carry" and "how is this node rebuilt around new
expressions" knowledge sits in one place rather than being re-derived per consumer.
What it does and does not recurse
This is a shallow walk: it reports the expressions of node itself
and does not descend into its children. Compose it with RelNode.children() to
walk a whole tree, and with OperandWalker to descend within a reported
operand or predicate:
static void walkTree(RelNode node, Consumer<FunctionCall> onCall) {
RelNodeOperands.forEach(node,
operand -> OperandWalker.walk(operand, attr -> { }, onCall),
predicate -> OperandWalker.walk(predicate, attr -> { }, onCall));
node.children().forEach(child -> walkTree(child, onCall));
}
EmptyRelationNode.heading() is deliberately not reported. It is an inert
carrier for a replaced sub-expression rather than something the node evaluates — the
same reason children() omits it.
This module carries no dependencies beyond the JDK, so the walker is safe to reuse from any module that depends on the AST.
-
Method Summary
Modifier and TypeMethodDescriptionstatic voidstatic RelNodemap(RelNode node, UnaryOperator<Operand> onOperand, UnaryOperator<Predicate> onPredicate) The rewriting counterpart offorEach(com.darkcollective.relix.ast.RelNode, java.util.function.Consumer<com.darkcollective.relix.ast.Operand>, java.util.function.Consumer<com.darkcollective.relix.ast.Predicate>): replaces everyOperandandPredicatethatnodecarries directly with the result of applyingonOperand/onPredicate, and returns the rebuilt node.static booleanusesSystemState(RelNode node) Whethernodeproduces a result that depends on state outside the query — the clock, a random source — ignoring the expressionsforEach(com.darkcollective.relix.ast.RelNode, java.util.function.Consumer<com.darkcollective.relix.ast.Operand>, java.util.function.Consumer<com.darkcollective.relix.ast.Predicate>)reports and its children.
-
Method Details
-
forEach
public static void forEach(RelNode node, Consumer<Operand> onOperand, Consumer<Predicate> onPredicate) - Parameters:
node- the node to inspect; must not be nullonOperand- invoked for each operand expression the node evaluatesonPredicate- invoked for each predicate the node evaluates
-
usesSystemState
Whethernodeproduces a result that depends on state outside the query — the clock, a random source — ignoring the expressionsforEach(com.darkcollective.relix.ast.RelNode, java.util.function.Consumer<com.darkcollective.relix.ast.Operand>, java.util.function.Consumer<com.darkcollective.relix.ast.Predicate>)reports and its children.An unseeded
SAMPLE/RESERVOIRdraws from a fresh random source on each execution, so running it twice over the same input can give two different relations. A seeded one is reproducible and therefore not system-dependent.Exhaustive over the sealed hierarchy for the same reason
forEach(com.darkcollective.relix.ast.RelNode, java.util.function.Consumer<com.darkcollective.relix.ast.Operand>, java.util.function.Consumer<com.darkcollective.relix.ast.Predicate>)is: a new node kind that reads system state must be classified here rather than defaulting, silently, to "reproducible".- Parameters:
node- the node to classify; must not be null- Returns:
truewhen the node itself reads system state
-
map
public static RelNode map(RelNode node, UnaryOperator<Operand> onOperand, UnaryOperator<Predicate> onPredicate) The rewriting counterpart offorEach(com.darkcollective.relix.ast.RelNode, java.util.function.Consumer<com.darkcollective.relix.ast.Operand>, java.util.function.Consumer<com.darkcollective.relix.ast.Predicate>): replaces everyOperandandPredicatethatnodecarries directly with the result of applyingonOperand/onPredicate, and returns the rebuilt node.Like
forEachthis is shallow — the node's children are left untouched. Compose it withRelNode.mapChildren(java.util.function.UnaryOperator<com.darkcollective.relix.ast.RelNode>)to rewrite a whole tree:static RelNode rewrite(RelNode node) { RelNode withChildren = node.mapChildren(RelNodeOperands::rewrite); return RelNodeOperands.map(withChildren, Simplifier::operand, Simplifier::predicate); }Following the same convention as
mapChildren,nodeitself is returned when every mapped expression comes back identical by reference, so a caller can use reference inequality as a "something changed" signal. A mapping function that rebuilds an unchanged expression therefore defeats that signal — return the argument unchanged when no rule fires.Exhaustive over the sealed hierarchy with no
defaultarm, for the same reasonforEachis.- Parameters:
node- the node to rewrite; must not be nullonOperand- applied to each operand expression the node evaluatesonPredicate- applied to each predicate the node evaluates- Returns:
- the rebuilt node, or
nodewhen no expression changed
-