dbt-labs/dbt-core · error

expected_field should be Struct

Error message

expected_field should be Struct

What it means

Arrow schema normalization invariant panic in dbt-tracing. normalize_struct_column is only invoked after both the expected and actual fields were verified to be DataType::Struct (the actual check returns an Err above); if the expected field is not Struct at this point, the caller's dispatch logic is wrong. The panic signals a bug in the recursive column-normalization dispatch rather than bad user input.

Source

Thrown at crates/dbt-tracing/src/serialize/arrow.rs:860

fn normalize_struct_column(
    path: &str,
    array: &ArrayRef,
    expected_field: &Field,
    actual_field: &Field,
) -> Result<NormalizedColumn, Vec<String>> {
    let struct_array = array
        .as_any()
        .downcast_ref::<StructArray>()
        .ok_or_else(|| {
            vec![format!(
                "field {path}: expected Struct but found {:?}",
                array.data_type()
            )]
        })?;

    let DataType::Struct(expected_fields) = expected_field.data_type() else {
        unreachable!("expected_field should be Struct");
    };

    let DataType::Struct(actual_fields) = actual_field.data_type() else {
        return Err(vec![format!(
            "field {path}: expected Struct but found {:?}",
            actual_field.data_type()
        )]);
    };

    let mut child_arrays = Vec::with_capacity(expected_fields.len());
    let mut child_fields = Vec::with_capacity(expected_fields.len());
    let mut errors = Vec::new();
    let mut needs_rebuild = false;

    for expected_child in expected_fields.iter() {
        let child_path = format!("{path}.{}", expected_child.name());
        let Some((child_index, actual_child_field)) = actual_fields
            .iter()

View on GitHub (pinned to 0267ce9170)

Solutions

  1. Ensure trace files were produced by a compatible dbt-tracing version
  2. Check recent changes to normalize_column's dispatch logic for struct fields
  3. Regenerate the trace file if its schema was manually modified
  4. Report a bug with the schema that triggers the panic

Example fix

// before
let DataType::Struct(expected_fields) = expected_field.data_type() else {
    unreachable!("expected_field should be Struct");
};
// after
let DataType::Struct(expected_fields) = expected_field.data_type() else {
    return Err(vec![format!(
        "field {path}: expected Struct but found {:?}",
        expected_field.data_type()
    )]);
};
Defensive patterns

Strategy: validation

Validate before calling

fn is_struct(field: &arrow_schema::Field) -> bool {
    matches!(field.data_type(), arrow_schema::DataType::Struct(_))
}

Type guard

fn as_struct_fields(dt: &DataType) -> Option<&Fields> {
    if let DataType::Struct(f) = dt { Some(f) } else { None }
}

Try / catch

if !is_struct(expected_field) {
    return Err(vec![format!("field {path}: expected Struct")]);
}

Prevention

When it happens

Trigger: normalize_column dispatching to normalize_struct_column with an expected_field whose data_type is not DataType::Struct.

Common situations: Schema evolution in the trace parquet format; hand-edited or older trace files whose schema diverges from the code's expectations; bugs introduced when adding new nested column types.

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 dbt-labs/dbt-core@0267ce9170 (2026-09-07). Data as JSON: /api/errors/f6714108a2ddbf59. Report an issue: GitHub.