risingwavelabs/risingwave · error

Method not available for `LogicalMultiJoin` which is a place

Error message

Method not available for `LogicalMultiJoin` which is a placeholder node with a temporary lifetime. It only facilitates join reordering during logical planning.

What it means

`LogicalMultiJoin::logical_rewrite_for_stream` panics because `LogicalMultiJoin` is only a temporary placeholder used during join-order enumeration in logical planning; it is always replaced by concrete join nodes before the to-stream rewrite phase. Any call means a `LogicalMultiJoin` escaped the join-reordering stage.

Source

Thrown at src/frontend/src/optimizer/plan_node/logical_multi_join.rs:814

    left: Option<Box<JoinTreeNode>>,
    right: Option<Box<JoinTreeNode>>,
    height: usize,
}

// join graph internal representation
#[derive(Clone, Debug)]
struct GraphNode {
    id: usize,
    join_tree: JoinTreeNode,
    relations: BTreeSet<usize>,
}

impl ToStream for LogicalMultiJoin {
    fn logical_rewrite_for_stream(
        &self,
        _ctx: &mut RewriteStreamContext,
    ) -> Result<(PlanRef, ColIndexMapping)> {
        panic!(
            "Method not available for `LogicalMultiJoin` which is a placeholder node with \
             a temporary lifetime. It only facilitates join reordering during logical planning."
        )
    }

    fn to_stream(
        &self,
        _ctx: &mut ToStreamContext,
    ) -> Result<crate::optimizer::plan_node::StreamPlanRef> {
        panic!(
            "Method not available for `LogicalMultiJoin` which is a placeholder node with \
             a temporary lifetime. It only facilitates join reordering during logical planning."
        )
    }
}

impl ToBatch for LogicalMultiJoin {
    fn to_batch(&self) -> Result<crate::optimizer::plan_node::BatchPlanRef> {

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Ensure join reordering runs and replaces `LogicalMultiJoin` with concrete joins before stream rewriting (check optimizer pass ordering/flags).
  2. Do not construct or persist `LogicalMultiJoin` outside the join-reordering stage in custom code or tests.
  3. If a normal multi-way JOIN query triggers it, capture the SQL and report an optimizer bug upstream.

Example fix

// before: placeholder node survives into stream rewrite
let (plan, _) = multi_join.logical_rewrite_for_stream(ctx)?; // panics
// after: run join reordering first to replace the placeholder
let plan = reorder_joins(multi_join)?; // produces LogicalJoin tree
let (plan, _) = plan.logical_rewrite_for_stream(ctx)?;
Defensive patterns

Strategy: type-guard

Validate before calling

debug_assert!(!plan_contains::<LogicalMultiJoin>(&plan), "LogicalMultiJoin placeholder must be consumed by join reordering");

Type guard

fn is_multi_join(node: &PlanNode) -> bool { matches!(node, PlanNode::LogicalMultiJoin(_)) }

Try / catch

let (plan, _) = node.logical_rewrite_for_stream(ctx)
    .unwrap_or_else(|e| panic!("MultiJoin placeholder survived; run join reordering first: {e}"));

Prevention

When it happens

Trigger: Calling `logical_rewrite_for_stream` on a plan tree still containing `LogicalMultiJoin`, i.e. join reordering (`dphyp`/greedy join ordering) did not run or did not replace the node.

Common situations: Optimizer configuration or feature flags that disable join reordering; tests or tools building `LogicalMultiJoin` directly and running stream conversion; regressions in the join-order pass.

Related errors


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