risingwavelabs/risingwave · error

call prune_col of the PlanRef instead of calling directly on

Error message

call prune_col of the PlanRef instead of calling directly on LogicalShare

What it means

`LogicalShare::prune_col` is deliberately `unimplemented!()`. Column pruning on a shared node must go through `PlanRef::prune_col`, which handles LogicalShare specially (pruning once, at the share root, considering all consumers). Calling the trait method directly on the LogicalShare value indicates a caller violated the module's API contract.

Source

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

        self.ctx()
            .update_logical_share(self.share_id(), plan.clone());
        Self::with_core(self.core.with_input(plan)).into()
    }

    fn fork_with_input(&self, plan: PlanRef) -> PlanRef {
        Self::new(plan).into()
    }
}

impl Distill for LogicalShare {
    fn distill<'a>(&self) -> XmlNode<'a> {
        Self::pretty_fields(&self.base, "LogicalShare")
    }
}

impl ColPrunable for LogicalShare {
    fn prune_col(&self, _required_cols: &[usize], _ctx: &mut ColumnPruningContext) -> PlanRef {
        unimplemented!("call prune_col of the PlanRef instead of calling directly on LogicalShare")
    }
}

impl ExprRewritable<Logical> for LogicalShare {}

impl ExprVisitable for LogicalShare {}

impl PredicatePushdown for LogicalShare {
    fn predicate_pushdown(
        &self,
        _predicate: Condition,
        _ctx: &mut PredicatePushdownContext,
    ) -> PlanRef {
        unimplemented!(
            "call predicate_pushdown of the PlanRef instead of calling directly on LogicalShare"
        )
    }
}

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Convert the node to `PlanRef` (`self.clone().into()`) and call `prune_col` on that; the PlanRef-level implementation handles sharing correctly.
  2. Refactor your rule to work on `PlanRef` throughout so downcasts never yield direct LogicalShare trait calls.
  3. If stock code reaches this, report a RisingWave optimizer bug with the query plan.

Example fix

// before
let pruned = logical_share.prune_col(&required_cols, ctx);
// after
let pruned = PlanRef::from(logical_share.clone()).prune_col(&required_cols, ctx);
Defensive patterns

Strategy: type-guard

Validate before calling

// Ensure the node is dispatched via PlanRef, not the typed LogicalShare.
if node.as_logical_share().is_some() {
    let node = PlanRef::from(node.clone());
    // proceed with node.prune_col(...)
}

Type guard

fn prunable_ref(plan: PlanRef) -> PlanRef {
    assert!(plan.as_logical_share().is_none(), "prune via PlanRef only");
    plan
}

Prevention

When it happens

Trigger: Calling `logical_share.prune_col(required_cols, ctx)` on a `LogicalShare` value directly instead of converting to `PlanRef` first and calling `plan_ref.prune_col(...)`.

Common situations: Hit by contributors implementing or testing the column-pruning rules who operate on typed plan node values (e.g. after a `as_logical_share()` downcast) rather than on the generic `PlanRef`.

Related errors


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