risingwavelabs/risingwave · error · StreamExecutorError

Gap interval expression returned null

Error message

Gap interval expression returned null

What it means

During gap-fill executor initialization (`execute_inner`), the gap interval expression is evaluated once against an empty dummy row. Because the interval expression must be constant, evaluating to NULL is a fatal configuration error, so the executor returns an anyhow error "Gap interval expression returned null" instead of proceeding. Gap fill needs a concrete positive interval to know how far to advance the time column for each missing window.

Source

Thrown at src/stream/src/executor/eowc/eowc_gap_fill.rs:365

    #[try_stream(ok = Message, error = StreamExecutorError)]
    async fn execute_inner(self) {
        let Self {
            input,
            inner: mut this,
        } = self;

        let mut input = input.execute();

        let barrier = expect_first_barrier(&mut input).await?;
        let first_epoch = barrier.epoch;
        yield Message::Barrier(barrier);
        this.prev_row_table.init_epoch(first_epoch).await?;

        // Calculate and validate gap interval once at initialization
        let dummy_row = OwnedRow::new(vec![]);
        let interval_datum = this.gap_interval.eval_row_infallible(&dummy_row).await;
        let interval = interval_datum
            .ok_or_else(|| anyhow::anyhow!("Gap interval expression returned null"))?
            .into_interval();

        // Validate that gap interval is positive.
        if interval <= Interval::from_month_day_usec(0, 0, 0) {
            Err(anyhow::anyhow!("Gap interval must be positive"))?;
        }

        let mut vars = ExecutionVars {
            staging_prev_rows: HashMap::new(),
        };

        #[for_await]
        for msg in input {
            match msg? {
                // Drop the time watermark: a late anchor makes gap fill back-fill below it.
                // PARTITION BY column watermarks (if any) pass through unchanged.
                Message::Watermark(watermark)
                    if this.partition_by_indices.contains(&watermark.col_idx) =>

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Replace the gap interval expression with a non-null constant literal such as `INTERVAL '1 minute'` in the gap-fill options.
  2. If the interval is computed, precompute the value and inline the literal; the executor only evaluates against an empty row, so anything row-dependent yields NULL.
  3. Check the MV WITH clause/options for a typo that drops the interval value so it defaults to NULL.
  4. Recreate the materialized view with a corrected gap interval.

Example fix

// before
WITH (gap_interval = NULL)
// after
WITH (gap_interval = INTERVAL '5 minutes')
Defensive patterns

Strategy: validation

Validate before calling

-- Ensure the gap interval is a non-null constant:
SELECT INTERVAL '5 minutes' IS NOT NULL AS gap_ok; -- true
-- Use only constant literals in gap_fill options, never column references.

Prevention

When it happens

Trigger: Creating an EMIT ON WINDOW CLOSE query with a gap-fill `gap_interval` (or `GAP`/interval argument) expression that evaluates to NULL at runtime — e.g., the interval literal is NULL, or the expression references a column/value that is NULL on an empty row.

Common situations: Typing `gap_interval => NULL` in the WITH options; using a parameterized or computed expression that is non-constant or NULL rather than a literal like `INTERVAL '5 minutes'`; copy-pasting config where the interval value was lost.

Related errors


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