risingwavelabs/risingwave · error
BatchGapFill is not implemented yet
Error message
BatchGapFill is not implemented yet
What it means
GapFill (time-series gap filling, used with range windows) has no batch execution path, so `ToBatch for LogicalGapFill` unconditionally bails with this message. Gap fill is only lowered to stream nodes (StreamEowcGapFill / StreamGapFill / StreamEowcSort).
Solutions
- Execute the gap-fill query in streaming mode (e.g. against a materialized view) instead of batch mode.
- Implement gap filling manually in batch SQL: generate the time grid with generate_series and LEFT JOIN the data onto it.
- Implement `ToBatch for LogicalGapFill` if batch support is required upstream.
Example fix
-- before (batch query with gap fill)
SELECT gap_fill(ts, INTERVAL '1 minute'), avg(v) FROM metrics GROUP BY ...;
-- after (manual batch gap fill)
SELECT g.ts, m.avg_v
FROM generate_series('2024-01-01', '2024-01-02', INTERVAL '1 minute') AS g(ts)
LEFT JOIN (SELECT ts, avg(v) AS avg_v FROM metrics GROUP BY ts) m
ON m.ts = g.ts; Defensive patterns
Strategy: try-catch
Validate before calling
if query.is_batch() && query.uses_gap_fill() {
return Err("gap fill requires streaming execution".into());
} Type guard
fn gap_fill_in_batch(plan: &LogicalPlan) -> bool {
matches!(plan.node_type(), PlanNodeType::LogicalGapFill)
} Try / catch
match to_batch(plan) {
Err(e) if e.to_string().contains("BatchGapFill") =>
run_streaming_or_manual_series_fallback(query),
r => r,
} Prevention
- Run gap-fill queries in streaming mode against MVs
- Use generate_series + LEFT JOIN for batch gap filling
- Check engine support matrix before using time-series functions in batch
- Add a planner rule or early check that rejects gap fill in batch mode
When it happens
Trigger: Calling `to_batch` on a LogicalGapFill node — i.e. running a batch (ad-hoc) SELECT whose plan contains gap filling, typically a query using gap-fill semantics against a batch plan.
Common situations: Developers run a time-series gap-fill query in batch mode (not against a materialized view) or attempt distributed batch planning over gap-fill expressions.
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
- Expr error
- `LogicalNow` can only be converted to stream
- no affected rows in output
- query_epoch not set in distributed lookup join
- Time column should be Timestamp or Timestamptz
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/dd5a95259bf260eb.
Report an issue: GitHub.
Appendix: source
Thrown at src/frontend/src/optimizer/plan_node/logical_gap_fill.rs:177
self.core.visit_exprs(v);
}
}
impl PredicatePushdown for LogicalGapFill {
fn predicate_pushdown(
&self,
predicate: Condition,
ctx: &mut PredicatePushdownContext,
) -> PlanRef {
// No pushdown, but keep recursing with a `true` condition so that shares below
// still receive a contribution from this parent (see `PredicatePushdownContext`).
gen_filter_and_pushdown(self, predicate, Condition::true_cond(), ctx)
}
}
impl ToBatch for LogicalGapFill {
fn to_batch(&self) -> Result<super::BatchPlanRef> {
bail!("BatchGapFill is not implemented yet")
}
}
impl ToStream for LogicalGapFill {
fn to_stream(&self, ctx: &mut ToStreamContext) -> Result<super::StreamPlanRef> {
use super::{StreamEowcGapFill, StreamEowcSort, StreamGapFill};
use crate::error::ErrorCode;
use crate::optimizer::property::RequiredDist;
let stream_input = self.input().to_stream(ctx)?;
let partition_cols = &self.core.partition_by_cols;
// Choose distribution based on partition_by_cols
let new_input = if partition_cols.is_empty() {
// No partition: singleton distribution (backward compatible)
RequiredDist::single().streaming_enforce_if_not_satisfies(stream_input)?
} else {
// With partition: hash distribute by partition columnsView on GitHub (pinned to 6469eb736d)