risingwavelabs/risingwave · error · StreamExecutorError
Gap interval must be positive
Error message
Gap interval must be positive
What it means
After evaluating the GAP interval expression, the gap-fill executor validates that the interval is strictly greater than zero. A zero or negative interval would cause an infinite/empty fill loop, so `execute_inner` fails with this anyhow error at src/stream/src/executor/gap_fill.rs:565.
Solutions
- Use a strictly positive INTERVAL literal, e.g. `GAP INTERVAL '1 minute'`.
- If the gap comes from an expression, add GREATEST to clamp it positive: `GAP GREATEST(expr, INTERVAL '1 second')`.
- Check the value bound to the gap (variable/config) before creating the materialized view.
Example fix
// before SELECT GAP FILL(ts, INTERVAL '0 minutes') FROM t GROUP BY ts; // after SELECT GAP FILL(ts, INTERVAL '1 minute') FROM t GROUP BY ts;
Defensive patterns
Strategy: validation
Validate before calling
-- Check the interval is strictly positive before use: SELECT gap_expr > INTERVAL '0 seconds' AS ok; -- must be true -- Or clamp: GREATEST(gap_expr, INTERVAL '1 second')
Try / catch
match result {
Err(e) if e.to_string().contains("Gap interval must be positive") => {
// correct the GAP literal and re-create the materialized view
}
other => other?,
} Prevention
- Never pass 0 or negative INTERVALs to GAP FILL.
- Clamp parameterized gaps with GREATEST(expr, INTERVAL '1 second').
- Verify any externally supplied gap value before creating the MV.
When it happens
Trigger: A `GAP FILL` query with `GAP INTERVAL '0 seconds'`, a negative interval, or an expression that evaluates to a non-positive interval at MV creation time.
Common situations: Config mistakes where a user-supplied gap parameter defaults to 0; arithmetic expressions like `end_ts - start_ts` that evaluate to zero at creation.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Gap interval expression returned null
- compaction resolver sink
- for frame bound offset of type `interval`, each field…
- Time column should be Timestamp or Timestamptz
- {0}
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/dc88953aa03e0af5.
Report an issue: GitHub.
Appendix: source
Thrown at src/stream/src/executor/gap_fill.rs:565
metrics,
} = *self;
let mut input = input.execute();
let barrier = expect_first_barrier(&mut input).await?;
let first_epoch = barrier.epoch;
yield Message::Barrier(barrier);
managed_state.init_epoch(first_epoch).await?;
// Calculate and validate gap interval once at initialization
let dummy_row = OwnedRow::new(vec![]);
let interval_datum = gap_interval.eval_row_infallible(&dummy_row).await;
let interval = interval_datum
.ok_or_else(|| anyhow::anyhow!("Gap interval expression returned null"))?
.into_interval();
if interval <= Interval::from_month_day_usec(0, 0, 0) {
Err(anyhow::anyhow!("Gap interval must be positive"))?;
}
let generation_context = GapFillGenerationContext {
metrics: &metrics,
high_amplification_threshold: high_gap_fill_amplification_threshold,
actor_ctx: &ctx,
};
let partition_by_indices = managed_state.partition_by_indices.clone();
let pointer_key_indices = managed_state.pointer_key_indices.clone();
// Interpolation re-slopes every fill, so a changed anchor changes all of them; only
// LOCF/NULL fills can be reused by the diff.
let has_interpolate = fill_columns
.values()
.any(|strategy| matches!(strategy, FillStrategy::Interpolate));
#[for_await]
for msg in input {
match msg? {View on GitHub (pinned to 6469eb736d)