risingwavelabs/risingwave · error
unimplemented
Error message
unimplemented
What it means
LogicalIntersect (SQL INTERSECT) does not implement a direct-to-batch conversion: its to_batch method calls unimplemented!(), which panics. INTERSECT must first be rewritten into equivalent EXCEPT/semijoin (or union-of-except) forms by an earlier logical rewrite rule; reaching to_batch on the raw node means that rewrite never happened.
Source
Thrown at src/frontend/src/optimizer/plan_node/logical_intersect.rs:100
impl PredicatePushdown for LogicalIntersect {
fn predicate_pushdown(
&self,
predicate: Condition,
ctx: &mut PredicatePushdownContext,
) -> PlanRef {
let new_inputs = self
.inputs()
.iter()
.map(|input| input.predicate_pushdown(predicate.clone(), ctx))
.collect_vec();
self.clone_with_inputs(&new_inputs)
}
}
impl ToBatch for LogicalIntersect {
fn to_batch(&self) -> Result<crate::optimizer::plan_node::BatchPlanRef> {
unimplemented!()
}
}
impl ToStream for LogicalIntersect {
fn to_stream(
&self,
_ctx: &mut ToStreamContext,
) -> Result<crate::optimizer::plan_node::StreamPlanRef> {
unimplemented!()
}
fn logical_rewrite_for_stream(
&self,
_ctx: &mut RewriteStreamContext,
) -> Result<(PlanRef, ColIndexMapping)> {
unimplemented!()
}
}View on GitHub (pinned to 6469eb736d)
Solutions
- Upgrade RisingWave: in released versions, INTERSECT queries are rewritten to EXCEPT before batch planning, so this panic indicates an optimizer bug worth reporting.
- Simplify or rewrite the query using EXCEPT or equivalent joins if you hit this on a patched/dev build.
- As a developer, ensure the rewrite rule converting LogicalIntersect to EXCEPT runs before to_batch, or implement to_batch properly.
Example fix
// before
impl ToBatch for LogicalIntersect {
fn to_batch(&self) -> Result<BatchPlanRef> {
unimplemented!()
}
}
// after
impl ToBatch for LogicalIntersect {
fn to_batch(&self) -> Result<BatchPlanRef> {
bail!("LogicalIntersect should be rewritten to LogicalExcept before to_batch")
}
} Defensive patterns
Strategy: try-catch
Validate before calling
// For dev tooling: ensure INTERSECT queries are rewritten before batch conversion
if plan_contains_intersect(batch_plan) {
return Err("apply intersect-to-except rewrite first".into());
} Type guard
fn contains_intersect(plan: &PlanRef) -> bool {
plan.walk().any(|n| matches!(n.as_logical(), Some(LogicalNode::Intersect(_))))
} Try / catch
let r = std::panic::catch_unwind(|| planner.to_batch(plan));
match r {
Ok(v) => v?,
Err(_) => return Err(anyhow!("unimplemented to_batch on intersect; apply the rewrite rule or report a bug")),
} Prevention
- Report this panic with the SQL — released planners rewrite INTERSECT before to_batch.
- Prefer EXCEPT/JOIN formulations on dev or patched builds.
- Ensure the intersect rewrite rule is enabled in the optimizer pipeline.
- Add tests asserting LogicalIntersect never survives to batch conversion.
When it happens
Trigger: The batch conversion pass (to_batch) is invoked on a LogicalIntersect node that was not rewritten into EXCEPT form first — e.g. a planner regression or a code path that skips the intersect-to-except rewrite.
Common situations: Running INTERSECT queries after changes to the logical rewrite rules; bugs where the rewrite from INTERSECT to EXCEPT/set-operations is skipped; interacting with optimizer internals in development.
Related errors
- unreachable
- insert should always be converted to batch plan
- IcebergSinkWriter should be initialized before barrier
- BatchPosixFsReader should not hit this branch. refer to `bat
- duplicate path: {:?}
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/07368301bc2f26b3.
Report an issue: GitHub.