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
- Convert the node to `PlanRef` (`self.clone().into()`) and call `prune_col` on that; the PlanRef-level implementation handles sharing correctly.
- Refactor your rule to work on `PlanRef` throughout so downcasts never yield direct LogicalShare trait calls.
- 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
- Always call column pruning on `PlanRef`, never on typed plan nodes.
- Keep rule code generic over PlanRef to avoid downcast-induced trait calls.
- Test pruning on plans containing shared subplans.
- Note the module's unimplemented!() markers: they document which dispatch paths are forbidden.
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
- call predicate_pushdown of the PlanRef instead of calling di
- required column should be kept
- shared node should be handled specially in PlanRef::clone_wi
- not implemented
- iceberg intermediate scan must have a source catalog
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/4046734f469bee44.
Report an issue: GitHub.