risingwavelabs/risingwave · error

TableFunction should be converted to ProjectSet

Error message

TableFunction should be converted to ProjectSet

What it means

`LogicalTableFunction::to_batch` unconditionally panics with `unreachable!()`. Table functions (set-returning functions like `generate_series` or unnest) are never converted directly to a batch physical node; they are first rewritten into a `LogicalProjectSet` (or `LogicalFilter`/`LogicalJoin` wrapper) by an earlier normalization rule, so this conversion should never run.

Source

Thrown at src/frontend/src/optimizer/plan_node/logical_table_function.rs:106

impl ExprVisitable for LogicalTableFunction {
    fn visit_exprs(&self, v: &mut dyn ExprVisitor) {
        self.core.visit_exprs(v);
    }
}

impl PredicatePushdown for LogicalTableFunction {
    fn predicate_pushdown(
        &self,
        predicate: Condition,
        _ctx: &mut PredicatePushdownContext,
    ) -> PlanRef {
        LogicalFilter::create(self.clone().into(), predicate)
    }
}

impl ToBatch for LogicalTableFunction {
    fn to_batch(&self) -> Result<crate::optimizer::plan_node::BatchPlanRef> {
        unreachable!("TableFunction should be converted to ProjectSet")
    }
}

impl ToStream for LogicalTableFunction {
    fn to_stream(
        &self,
        _ctx: &mut ToStreamContext,
    ) -> Result<crate::optimizer::plan_node::StreamPlanRef> {
        unreachable!("TableFunction should be converted to ProjectSet")
    }

    fn logical_rewrite_for_stream(
        &self,
        _ctx: &mut RewriteStreamContext,
    ) -> Result<(PlanRef, ColIndexMapping)> {
        unreachable!("TableFunction should be converted to ProjectSet")
    }
}

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Ensure the plan is normalized so table functions are wrapped in LogicalProjectSet before batch optimization (re-run the frontend rewrite rules).
  2. Rewrite the query to avoid the raw set-returning function in the offending position (e.g. move it into a SELECT list or CROSS JOIN LATERAL form that normalization covers).
  3. Report the query to RisingWave developers; this indicates a missing rewrite rule path.

Example fix

// before
impl ToBatch for LogicalTableFunction {
    fn to_batch(&self) -> Result<BatchPlanRef> { unreachable!("TableFunction should be converted to ProjectSet") }
}
// after (caller side)
let normalized = plan_node.as_logical_table_function()
    .map(|tf| LogicalProjectSet::create(tf.clone().into()))
    .unwrap_or_else(|| plan_node.clone());
let batch = normalized.to_batch()?;
Defensive patterns

Strategy: validation

Validate before calling

// Ensure table functions are normalized to ProjectSet before batch conversion.
if let Some(tf) = plan.as_logical_table_function() {
    plan = LogicalProjectSet::create(tf.clone().into()).into();
}

Type guard

fn assert_no_raw_table_function(plan: &PlanRef) {
    assert!(plan.as_logical_table_function().is_none(), "wrap in LogicalProjectSet first");
}

Prevention

When it happens

Trigger: The batch optimizer's ToBatch pass visits a `LogicalTableFunction` node that was not first rewritten to ProjectSet — i.e. the normalization rule inserting ProjectSet did not run or a new plan path bypassed it.

Common situations: Seen by RisingWave contributors adding new table functions or new plan entry points that skip the table-function-to-ProjectSet normalization; users see it only as an internal error when such a bug ships.

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/fef3fe582dbb7c40. Report an issue: GitHub.