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 = itemView on GitHub (pinned to 0267ce9170)
Solutions
- Ensure the object passed to `get_contract_mismatches` yields items with a string `name` attribute.
- Inspect how the columns collection is built (custom materialization/macro) and fix the `name` field.
- Verify no macro is overwriting `name` with a non-string value before contract validation.
- 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
- Ensure custom materializations/macros emit column objects with a string `name` attribute.
- Type-check result column metadata right after query execution, before contract comparison.
- Keep adapter versions aligned so column metadata objects keep the expected shape.
- Add a unit test feeding representative column objects into get_contract_mismatches.
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
- Compilation Error for {} from {}: {}
- Contract Error for {} from {}: {}
- Contract Error: {message}
- Argument must be a string
- Column 'data_type' must be a string
AI-assisted analysis of dbt-labs/dbt-core@0267ce9170 (2026-09-07).
Data as JSON: /api/errors/3d2087777a88934c.
Report an issue: GitHub.