risingwavelabs/risingwave · error
file_scan_tasks must be Some
Error message
file_scan_tasks must be Some
What it means
After decoding a seq-id range for a stream chunk, no data rows were found (`ops.is_empty()`). A chunk read must yield at least one row; an empty result means the range selection pointed at seq ids that contain nothing (or only non-row ops), indicating a bug in the reader's range computation rather than a legitimate empty read.
Source
Thrown at src/batch/executors/src/executor/iceberg_scan.rs:101
schema,
file_scan_tasks: Some(file_scan_tasks),
identity,
file_scan_metrics,
need_seq_num,
need_file_path_and_pos,
limit,
}
}
#[try_stream(ok = DataChunk, error = BatchError)]
async fn do_execute(mut self: Box<Self>) {
let table = self.iceberg_config.load_table().await?;
let data_types = self.schema.data_types();
let data_file_scan_tasks = match Option::take(&mut self.file_scan_tasks) {
Some(file_scan_tasks) => file_scan_tasks.into_tasks(),
None => {
bail!("file_scan_tasks must be Some")
}
};
let mut remaining_limit = self
.limit
.map(|limit| usize::try_from(limit).unwrap_or(usize::MAX));
for data_file_scan_task in data_file_scan_tasks {
if matches!(remaining_limit, Some(0)) {
return Ok(());
}
#[for_await]
for chunk in scan_task_to_chunk_with_deletes(
table.clone(),
data_file_scan_task,
IcebergScanOpts {
chunk_size: self.chunk_size,
need_seq_num: self.need_seq_num,View on GitHub (pinned to 6469eb736d)
Solutions
- Verify the range selection logic only issues reads over non-empty, non-truncated seq-id spans.
- Re-check truncation timing: if rows were truncated between range computation and read, re-select the range.
- Inspect the KV store at start_seq_id/end_seq_id to confirm entries exist.
- Ensure the writer never advances visible seq ids without writing rows.
Defensive patterns
Strategy: validation
Validate before calling
// rust
// confirm the range is non-empty and not yet truncated before decoding
anyhow::ensure!(end_seq_id > start_seq_id, "empty seq range [{}, {})", start_seq_id, end_seq_id);
anyhow::ensure!(start_seq_id > reader.current_truncate_seq_id(), "range [{}, {}) already truncated", start_seq_id, end_seq_id); Try / catch
// rust
match reader.deserialize_stream_chunk(start, end, epoch).await {
Ok(chunk) => process(chunk),
Err(e) if e.to_string().contains("empty row") => {
// re-select range from the store's current watermark before retrying
}
Err(e) => return Err(e),
} Prevention
- Recompute read ranges from the reader's current watermark, not cached values.
- Guard reads against concurrent truncation.
- Never advance visible seq ids without a written row.
When it happens
Trigger: Calling `deserialize_stream_chunk` with `[start_seq_id, end_seq_id)` whose decoded ops list is empty at the final check. Thrown at serde.rs:514.
Common situations: Read range where all entries are barriers or were truncated concurrently; stale start/end seq ids after truncation; writer skipped seq ids leaving gaps the reader assumed were populated.
Understand the failure class
Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.
Related errors
- GetChannelDeltaStatsExecutor should have no child!
- Iceberg metadata scan should not have input executors
- Iceberg metadata type is unspecified
- Iceberg metadata scan received a non-Iceberg connector
- Chunk size can't be zero!
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/0471d30a87452c34.
Report an issue: GitHub.