risingwavelabs/risingwave · error

should already bail out after subquery unnesting

Error message

should already bail out after subquery unnesting

What it means

`LogicalMaxOneRow::to_stream` is an `unreachable!("should already bail out after subquery unnesting")`. A `MaxOneRow` node (scalar-subquery wrapper, e.g. from `(SELECT max(x) ...)` used as an expression) must have been eliminated by subquery unnesting before the stream planner runs, per `LogicalOptimizer::gen_optimized_logical_plan_for_stream`. Reaching this node means an unnested scalar subquery survived into the to-stream phase.

Source

Thrown at src/frontend/src/optimizer/plan_node/logical_max_one_row.rs:112

        gen_filter_and_pushdown(self, predicate, Condition::true_cond(), ctx)
    }
}

impl ToBatch for LogicalMaxOneRow {
    fn to_batch(&self) -> Result<crate::optimizer::plan_node::BatchPlanRef> {
        let input = self.input().to_batch()?;
        let core = generic::MaxOneRow { input };
        Ok(BatchMaxOneRow::new(core).into())
    }
}

impl ToStream for LogicalMaxOneRow {
    fn to_stream(
        &self,
        _ctx: &mut ToStreamContext,
    ) -> Result<crate::optimizer::plan_node::StreamPlanRef> {
        // Check `LogicalOptimizer::gen_optimized_logical_plan_for_stream`.
        unreachable!("should already bail out after subquery unnesting")
    }

    fn logical_rewrite_for_stream(
        &self,
        ctx: &mut RewriteStreamContext,
    ) -> Result<(PlanRef, ColIndexMapping)> {
        let (input, input_col_change) = self.input().logical_rewrite_for_stream(ctx)?;
        let (this, out_col_change) = self.rewrite_with_input(input, input_col_change);
        Ok((this.into(), out_col_change))
    }
}

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Rewrite the query to avoid the scalar subquery, e.g. convert it to a GROUP BY join or window function that unnesting supports.
  2. Check `gen_optimized_logical_plan_for_stream` for a missing bail-out: it should reject the query with a clear error instead of reaching the panic; fix/extend the unnesting pass.
  3. Test whether the same query works as a batch query to confirm it is the scalar-subquery/stream limitation, then report the query upstream.

Example fix

-- before: scalar subquery left MaxOneRow in the stream plan
CREATE MATERIALIZED VIEW mv AS SELECT a, (SELECT max(b) FROM t2) AS m FROM t1;
-- after: use a join + group by instead
CREATE MATERIALIZED VIEW mv AS
SELECT t1.a, t2.m FROM t1 JOIN (SELECT max(b) AS m FROM t2) t2 ON true;
Defensive patterns

Strategy: validation

Validate before calling

// before CREATE MATERIALIZED VIEW, check the query for scalar subqueries:
// SELECT ... WHERE x = (SELECT max(y) FROM t2) -- must be unnestable, else rewrite it

Try / catch

match create_result {
    Err(e) if e.to_string().contains("subquery unnesting") => {
        eprintln!("Rewrite scalar subqueries as joins/group-bys for streaming");
    }
    r => r?,
}

Prevention

When it happens

Trigger: Creating a streaming plan (e.g. `CREATE MATERIALIZED VIEW`) whose query contains a scalar subquery that the unnesting pass failed to rewrite away, leaving `LogicalMaxOneRow` in the tree.

Common situations: Scalar subqueries in SELECT/WHERE of a materialized view definition using an unsupported placement or correlation pattern that unnesting does not handle; regressions in the unnesting rule after version upgrades.

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


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