risingwavelabs/risingwave · error
shared node should be handled specially in PlanRef::clone_wi
Error message
shared node should be handled specially in PlanRef::clone_with_input
What it means
`LogicalShare::clone_with_input` unconditionally panics via `unreachable!()`. LogicalShare is a shared (common-subplan) node; cloning it with a fresh input would break the share semantics, so the framework is supposed to handle shared nodes specially in `PlanRef::clone_with_input` rather than call this method. Reaching it means a PlanTreeNode code path bypassed the share-aware handling.
Source
Thrown at src/frontend/src/optimizer/plan_node/logical_share.rs:87
Self::with_core(ctx.logical_share(share_id))
}
pub fn create(input: PlanRef) -> PlanRef {
LogicalShare::new(input).into()
}
pub(super) fn pretty_fields(base: impl GenericPlanRef, name: &str) -> XmlNode<'_> {
childless_record(name, vec![("id", Pretty::debug(&base.id().0))])
}
}
impl PlanTreeNodeUnary<Logical> for LogicalShare {
fn input(&self) -> PlanRef {
self.core.input()
}
fn clone_with_input(&self, _input: PlanRef) -> Self {
unreachable!("shared node should be handled specially in PlanRef::clone_with_input")
}
fn rewrite_with_input(
&self,
input: PlanRef,
input_col_change: ColIndexMapping,
) -> (Self, ColIndexMapping) {
(Self::new(input), input_col_change)
}
}
impl_plan_tree_node_for_unary! { Logical, LogicalShare}
impl ShareNode<Logical> for LogicalShare {
fn share_id(&self) -> ShareId {
self.core.share_id()
}
View on GitHub (pinned to 6469eb736d)
Solutions
- Do not call `clone_with_input` on a shared node; use the share-aware `PlanRef::clone_with_input` entry point which special-cases LogicalShare.
- If you are writing an optimizer rule, clone the LogicalShare's underlying `LogicalPlanRef` core and construct a new LogicalShare instead of treating it as a plain unary node.
- If this fires from stock rules, file a RisingWave bug with the explain output of the query.
Example fix
// before let cloned = shared_node.clone_with_input(new_input); // after let cloned = PlanRef::from(shared_node.clone()).clone_with_input(new_input); // share-aware path
Defensive patterns
Strategy: type-guard
Validate before calling
fn is_share(plan: &PlanRef) -> bool { plan.as_logical_share().is_some() } Type guard
fn as_non_share(plan: PlanRef) -> Option<PlanRef> {
if plan.as_logical_share().is_some() { None } else { Some(plan) }
} Try / catch
// Panic cannot be caught in Rust; guard the call site instead. assert!(!is_share(&node), "use share-aware PlanRef::clone_with_input");
Prevention
- Never downcast to LogicalShare before calling generic unary-node helpers.
- Route all clone/rebuild operations through `PlanRef::clone_with_input`.
- Add unit tests with common-subplan-eliminated plans when writing optimizer rules.
- Read PlanTreeNodeUnary docs: shared nodes are excluded from the standard contract.
When it happens
Trigger: Any optimizer rule or rewrite that calls `clone_with_input` on a `PlanRef` wrapping a LogicalShare directly (e.g. a generic unary-node transformation) instead of routing through the share-aware `PlanRef::clone_with_input`.
Common situations: Seen by RisingWave contributors while writing new optimizer rules (column pruning, predicate pushdown, merge rules) that clone/rebuild plan trees and forgot that common-subplan elimination inserts LogicalShare nodes.
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
- internal error: entered unreachable code
- call prune_col of the PlanRef instead of calling directly on
- call predicate_pushdown of the PlanRef instead of calling di
- TableFunction should be converted to ProjectSet
- update should always be converted to batch plan
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/65b6f9ec09d638f0.
Report an issue: GitHub.