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
- Cast the time column to TIMESTAMP/TIMESTAMPTZ before gap-filling, e.g. `GAP FILL ... ORDER BY time_col::TIMESTAMP`.
- If using an epoch integer column, convert it with `TO_TIMESTAMP(col)` in the ORDER BY expression.
- 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
- Only use TIMESTAMP or TIMESTAMPTZ columns as the gap-fill ORDER BY time column.
- Convert DATE or epoch-integer columns explicitly with casts or TO_TIMESTAMP.
- Re-check the column type after any upstream schema change.
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
- Gap interval expression returned null
- Gap interval must be positive
- not yet implemented: is_dirty
- test_error
- Time column should be Timestamp or Timestamptz
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
NoneView on GitHub (pinned to 6469eb736d)