risingwavelabs/risingwave · error

logical join should store predicate as Condition

Error message

logical join should store predicate as Condition

What it means

LogicalJoin::on() expects the generic JoinOn core to hold a full Condition and panics with expect() if it does not (e.g. it holds only an EqPredicate representation). The invariant is that after planning, a logical join's predicate is stored as a Condition.

Source

Thrown at src/frontend/src/optimizer/plan_node/logical_join.rs:133

    pub fn internal_column_num(&self) -> usize {
        self.core.internal_column_num()
    }

    pub fn i2l_col_mapping_ignore_join_type(&self) -> ColIndexMapping {
        self.core.i2l_col_mapping_ignore_join_type()
    }

    pub fn i2r_col_mapping_ignore_join_type(&self) -> ColIndexMapping {
        self.core.i2r_col_mapping_ignore_join_type()
    }

    /// Get a reference to the logical join's on.
    pub fn on(&self) -> &Condition {
        self.core
            .on
            .as_condition_ref()
            .expect("logical join should store predicate as Condition")
    }

    pub fn core(&self) -> &generic::Join<PlanRef> {
        &self.core
    }

    /// Collect all input ref in the on condition. And separate them into left and right.
    pub fn input_idx_on_condition(&self) -> (Vec<usize>, Vec<usize>) {
        let input_refs = self
            .core
            .on
            .as_condition_ref()
            .expect("logical join should store predicate as Condition")
            .collect_input_refs(self.core.left.schema().len() + self.core.right.schema().len());
        let index_group = input_refs
            .ones()
            .chunk_by(|i| *i < self.core.left.schema().len());
        let left_index = index_group

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Construct the join core with generic::JoinOn::Condition(cond) instead of JoinOn::EqPredicate
  2. Before calling .on(), check as_condition_ref() and fall back to converting the eq-predicate to a Condition
  3. Use the appropriate accessor (e.g. as_eq_predicate_ref) when you know the representation
  4. Report as an optimizer bug if the join came from the standard planner path

Example fix

// before
let core = generic::Join { on: generic::JoinOn::EqPredicate(pred), .. };
let cond = join.on();
// after
let core = generic::Join { on: generic::JoinOn::Condition(Condition::with_exprs(pred.into_iter().collect())), .. };
let cond = join.on();
Defensive patterns

Strategy: type-guard

Validate before calling

if join.core().on.as_condition_ref().is_none() { /* build or convert to Condition first */ }

Type guard

fn on_condition(join: &LogicalJoin) -> Option<&Condition> { join.core().on.as_condition_ref() }

Prevention

When it happens

Trigger: Calling on() on a LogicalJoin whose core.on was constructed as generic::JoinOn::EqPredicate (e.g. joins created internally by temporal/asof join paths) rather than Condition.

Common situations: Optimizer rule authors or index-lookup/temporal rewrite passes building joins with JoinOn::EqPredicate and later calling .on(); version changes where JoinOn representation was refactored.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/ba99d5ca7436a9f3. Report an issue: GitHub.