dbt-labs/dbt-core · error · minijinja::Error (InvalidOperation)

Column 'name' must be a string

Error message

Column 'name' must be a string

What it means

While converting the model's actual query result columns into contract column definitions (`convert_value_to_column_definitions`, called by `get_contract_mismatches`), each item must expose a `name` attribute that is a string. If `name` is missing or not a string, the library raises this InvalidOperation error because the contract comparison cannot proceed without a usable column name.

Source

Thrown at crates/dbt-jinja-utils/src/functions/contract_error.rs:160

    Ok(Box::leak(Box::new(table)))
}

/// Helper function to convert Value to Vec<ColumnDefinition>
fn convert_value_to_column_definitions(value: Value) -> Result<Vec<ColumnDefinition>, Error> {
    if value.is_undefined() {
        return Ok(Vec::new());
    }

    let mut columns = Vec::new();

    match value.try_iter() {
        Ok(iter) => {
            for item in iter {
                let name_value = item.get_attr("name")?;
                let name = name_value
                    .as_str()
                    .ok_or_else(|| {
                        Error::new(
                            ErrorKind::InvalidOperation,
                            "Column 'name' must be a string",
                        )
                    })?
                    .to_string();

                let data_type_value = item.get_attr("data_type")?;
                let data_type = data_type_value
                    .as_str()
                    .ok_or_else(|| {
                        Error::new(
                            ErrorKind::InvalidOperation,
                            "Column 'data_type' must be a string",
                        )
                    })?
                    .to_string();

                let formatted = item

View on GitHub (pinned to 0267ce9170)

Solutions

  1. Ensure the object passed to `get_contract_mismatches` yields items with a string `name` attribute.
  2. Inspect how the columns collection is built (custom materialization/macro) and fix the `name` field.
  3. Verify no macro is overwriting `name` with a non-string value before contract validation.
  4. Upgrade/align adapter versions so column metadata objects match the expected shape.

Example fix

# before (malformed column object)
{'name': {'first': 'id'}}

# after
{'name': 'id'}
Defensive patterns

Strategy: type-guard

Validate before calling

// Validate column objects before calling get_contract_mismatches
columns.iter().all(|c| {
    c.get_attr("name").ok().and_then(|v| v.as_str().ok()).is_some()
})

Type guard

fn has_string_name(item: &Value) -> bool {
    item.get_attr("name").ok()
        .and_then(|v| v.as_str().ok())
        .is_some()
}

Try / catch

match get_contract_mismatches(cols, contract_cols) {
    Err(e) if e.to_string().contains("Column 'name' must be a string") => {
        // inspect/normalize the columns collection before retrying
    }
    other => other,
}

Prevention

When it happens

Trigger: `get_contract_mismatches` is called with the model's result columns; an item in the iterated collection either has no `name` attribute or its `name` attribute is a non-string value (e.g. a dict, list, or number) instead of the expected string column name.

Common situations: A macro or adapter producing result column objects with malformed/renamed attributes; custom materializations returning rows whose column metadata does not match the expected `name` attribute contract; upstream object shape changes after an upgrade.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of dbt-labs/dbt-core@0267ce9170 (2026-09-07). Data as JSON: /api/errors/3d2087777a88934c. Report an issue: GitHub.