risingwavelabs/risingwave · error
iceberg intermediate scan must have a source catalog
Error message
iceberg intermediate scan must have a source catalog
What it means
`iceberg_side_fields` requires the LogicalIcebergIntermediateScan node to carry a source catalog (`self.core.catalog`); it uses `.expect` to unwrap it. An intermediate Iceberg scan node built without its underlying catalog is an internal invariant violation, so this panics during predicate pushdown when computing Iceberg-side schema fields.
Source
Thrown at src/frontend/src/optimizer/plan_node/logical_iceberg_intermediate_scan.rs:172
time_travel_info,
table_column_type_mapping,
hummock_rewrite,
}
}
pub fn source_catalog(&self) -> Option<&SourceCatalog> {
self.core.catalog.as_deref()
}
/// Fields carrying the iceberg-side column types (before the engine-table Hummock
/// type remapping), for predicate pushdown. Derived from the source catalog, which
/// the remapping never touches.
fn iceberg_side_fields(&self) -> Vec<Field> {
let catalog = self
.core
.catalog
.as_ref()
.expect("iceberg intermediate scan must have a source catalog");
let by_name: HashMap<&str, &ColumnCatalog> =
catalog.columns.iter().map(|c| (c.name(), c)).collect();
self.core
.column_catalog
.iter()
.map(|col| {
let source_col = by_name
.get(col.name())
.expect("output column must exist in the source catalog");
Field::from(&source_col.column_desc)
})
.collect()
}
pub fn output_columns(&self) -> impl ExactSizeIterator<Item = &str> {
self.core.column_catalog.iter().map(|c| c.name.as_str())
}
View on GitHub (pinned to 6469eb736d)
Solutions
- Ensure the LogicalIcebergIntermediateScan is always constructed with a Some(catalog) (check its constructor/`with_core` call sites).
- Make `iceberg_side_fields` return a Result and bail with a descriptive error instead of panicking.
- Reduce to a minimal Iceberg query plan and file a bug with the plan dump and recent changes.
Example fix
// before
let catalog = self.core.catalog.as_ref()
.expect("iceberg intermediate scan must have a source catalog");
// after
let catalog = self.core.catalog.as_ref().ok_or_else(|| {
anyhow!("iceberg intermediate scan is missing its source catalog")
})?; Defensive patterns
Strategy: validation
Validate before calling
// before pushdown
if scan.core.catalog.is_none() {
return Err("iceberg intermediate scan missing source catalog".into());
} Type guard
fn has_source_catalog(scan: &LogicalIcebergIntermediateScan) -> bool {
scan.core.catalog.is_some()
} Try / catch
match plan.predicate_pushdown(...) {
Err(e) | PanicRescue(e) if e.to_string().contains("source catalog") => {
log::error!("plan corruption: intermediate scan without catalog");
Err(anyhow!("invalid iceberg scan node"))
}
r => r,
} Prevention
- Always construct intermediate scans with the source catalog attached
- Add constructor tests asserting catalog is Some
- Convert expects in optimizer code to Result-based errors
- Run Iceberg e2e tests after touching scan node construction
When it happens
Trigger: `predicate_pushdown` on a LogicalIcebergIntermediateScan whose `core.catalog` is None, then mapping columns to Iceberg fields.
Common situations: Optimizer bugs where an intermediate Iceberg scan node is constructed (e.g. by plan rewriting or tests) without attaching the source catalog before pushdown passes run.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- output column must exist in the source catalog
- required column should be kept
- table {} not found
- register_table is not supported in mock catalog
- iceberg sink metadata should have schema_id
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/0b5c343abac6a288.
Report an issue: GitHub.