risingwavelabs/risingwave · critical

Time column should be Timestamp or Timestamptz

Error message

Time column should be Timestamp or Timestamptz

What it means

In gap-fill (GAP FILL / RANGE window aggregation), the time column of each row is converted to the fill timestamp; only `Timestamp` and `Timestamptz` scalar types are supported. When the time column holds another type, the `unreachable!` panics with this message.

Solutions

  1. Cast the time column to TIMESTAMP/TIMESTAMPTZ before gap-filling, e.g. `GAP FILL ... ORDER BY time_col::TIMESTAMP`.
  2. If using an epoch integer column, convert it with `TO_TIMESTAMP(col)` in the ORDER BY expression.
  3. If the column already appears to be timestamp-typed, check for a DATE/TIMESTAMP mismatch and report a frontend validation gap that allowed the unsupported type.

Example fix

// before
SELECT GAP FILL(time_col, INTERVAL '1 minute') FROM t GROUP BY time_col;
// after
SELECT GAP FILL(time_col::TIMESTAMP, INTERVAL '1 minute') FROM t GROUP BY time_col::TIMESTAMP;
Defensive patterns

Strategy: validation

Validate before calling

-- Before creating a gap-fill MV, verify the time column type:
SELECT data_type FROM rw_catalog.rw_columns
WHERE relation_name = 't' AND name = 'time_col';
-- must be: timestamp without time zone or timestamp with time zone

Type guard

fn is_supported_time_scalar(d: Option<ScalarRefImpl<'_>>) -> bool {
    matches!(d, Some(ScalarRefImpl::Timestamp(_)) | Some(ScalarRefImpl::Timestamptz(_)))
}

Try / catch

// Cast defensively in SQL before the executor sees it:
// ORDER BY time_col::TIMESTAMPTZ  (or ::TIMESTAMP for naive timestamps)

Prevention

When it happens

Trigger: Running a `GAP FILL` query whose time index column is not TIMESTAMP or TIMESTAMPTZ (e.g. a DATE, TIME, or integer column) so the `match` on `ScalarRefImpl` falls to the `_ => unreachable!(...)` arm at src/stream/src/executor/gap_fill.rs:394.

Common situations: Users gap-filling over a DATE-typed column or a numeric epoch column; schema changes that altered the time column type after the MV was created.

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

Appendix: source

Thrown at src/stream/src/executor/gap_fill.rs:394

                    Some(t) => t,
                    None => break,
                };
                continue;
            }
            let mut new_row_data = Vec::with_capacity(prev_row.len());

            for col_idx in 0..prev_row.len() {
                let datum = if col_idx == time_column_index {
                    // Time column: use the incremented timestamp
                    let fill_time_scalar = match prev_time_scalar {
                        ScalarRefImpl::Timestamp(_) => ScalarImpl::Timestamp(fill_time),
                        ScalarRefImpl::Timestamptz(_) => {
                            let micros = fill_time.0.and_utc().timestamp_micros();
                            ScalarImpl::Timestamptz(
                                risingwave_common::types::Timestamptz::from_micros_uncheck(micros),
                            )
                        }
                        _ => unreachable!("Time column should be Timestamp or Timestamptz"),
                    };
                    Some(fill_time_scalar)
                } else if partition_by_indices.contains(&col_idx) {
                    // Gap-filled rows must stay in the same partition as the surrounding anchors.
                    prev_row.datum_at(col_idx).to_owned_datum()
                } else if let Some(strategy) = fill_columns.get(&col_idx) {
                    // Apply the fill strategy for this column
                    match strategy {
                        FillStrategy::Locf => prev_row.datum_at(col_idx).to_owned_datum(),
                        FillStrategy::Null => None,
                        FillStrategy::Interpolate => {
                            // Apply interpolation step and update cumulative value
                            if let Some(step) = &interpolation_steps[col_idx] {
                                apply_interpolation_step(&mut interpolation_states[col_idx], step);
                                interpolation_states[col_idx].clone()
                            } else {
                                // If interpolation step is None, fill with NULL
                                None

View on GitHub (pinned to 6469eb736d)