risingwavelabs/risingwave · error

Watermark should not be produced by a table function

Error message

Watermark should not be produced by a table function

What it means

In a ProjectSet (project with table functions), watermarks can only be transformed by scalar select items. When the project's watermark expression lands on a `Set` item (a table function), there is no single output row to carry a derived watermark, so the executor bails with this error. Table functions expand one input row to many rows, which makes watermark propagation ill-defined.

Source

Thrown at src/stream/src/executor/project/project_set.rs:305

    }

    async fn handle_watermark(&self, watermark: Watermark) -> StreamExecutorResult<Vec<Watermark>> {
        let expr_indices = match self.watermark_derivations.get_vec(&watermark.col_idx) {
            Some(v) => v,
            None => return Ok(vec![]),
        };
        let mut ret = vec![];
        for expr_idx in expr_indices {
            let expr_idx = *expr_idx;
            let derived_watermark = match &self.select_list[expr_idx] {
                ProjectSetSelectItem::Scalar(expr) => {
                    watermark
                        .clone()
                        .transform_with_expr(expr, expr_idx + PROJ_ROW_ID_OFFSET)
                        .await
                }
                ProjectSetSelectItem::Set(_) => {
                    bail!("Watermark should not be produced by a table function");
                }
            };

            if let Some(derived_watermark) = derived_watermark {
                ret.push(derived_watermark);
            } else {
                warn!(
                    "a NULL watermark is derived with the expression {}!",
                    expr_idx
                );
            }
        }
        Ok(ret)
    }
}

/// Either a scalar expression or a set-returning function.
///

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Restructure the query so the watermark column comes from a scalar projection, not a table function output (e.g. compute the watermark column before applying the table function)
  2. Remove the watermark declaration on that column if it is not needed downstream
  3. Split the query: materialize the table-function output first, then define watermarks on the materialized result
  4. If the planner emitted this shape automatically, file an issue with the SQL query
Defensive patterns

Strategy: validation

Validate before calling

-- Validate at query-design time: the watermark column must come from a scalar projection
-- Ensure no table function (generate_series, unnest, ...) sits between the source watermark column and its consumers
SELECT column_name FROM watermarked_columns
WHERE column_name NOT IN (SELECT output_col_of_table_function);

Try / catch

match pipeline_build {
    Err(e) if e.to_string().contains("Watermark should not be produced by a table function") => {
        // Rewrite the query: emit the watermark column via scalar projection before the table function
        rewrite_query_without_watermark_through_tvf();
    }
    other => other?,
}

Prevention

When it happens

Trigger: Building/planning a streaming pipeline whose watermark columns pass through a table function (e.g. generate_series, unnest) inside a ProjectSet, so `handle_watermark` meets a `ProjectSetSelectItem::Set` at the watermark's expr index.

Common situations: Defining a source/table with watermark columns and then joining/aliasing through table-function expressions in the streaming query; planner bugs letting a watermark expr reference a table-function output.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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