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

  1. Ensure the LogicalIcebergIntermediateScan is always constructed with a Some(catalog) (check its constructor/`with_core` call sites).
  2. Make `iceberg_side_fields` return a Result and bail with a descriptive error instead of panicking.
  3. 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

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


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/0b5c343abac6a288. Report an issue: GitHub.