dbt-labs/dbt-core · error · minijinja::Error (InvalidOperation)
Column 'data_type' must be a string
Error message
Column 'data_type' must be a string
What it means
Thrown by convert_value_to_column_definitions when building contract column definitions: the Jinja object's 'data_type' attribute exists but its value is not a string (minijinja value fails as_str()). The library needs a plain string data type to compare model contracts against actual column definitions, so it refuses any other value type rather than coercing silently.
Source
Thrown at crates/dbt-jinja-utils/src/functions/contract_error.rs:171
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
.get_attr("formatted")
.ok()
.and_then(|v| v.as_str().map(|s| s.to_string()));
columns.push(ColumnDefinition {
name,
data_type,
formatted,
});
}
}View on GitHub (pinned to 0267ce9170)
Solutions
- Ensure the 'data_type' attribute of each column object is a string before invoking contract mismatch checking (coerce with ~ or |string in Jinja, or .to_string() in Rust).
- Inspect the adapter/macro producing the column objects and fix it to emit data_type as text, not a native value.
- Log the offending column object with |tojson to see the actual type and source of the bad value.
Example fix
// before (Rust-side or Jinja-side construction) column.data_type = inferred_type; // e.g. minijinja value of type int // after column.data_type = inferred_type.as_str().unwrap_or_default().to_string();
Defensive patterns
Strategy: type-guard
Validate before calling
-- Jinja, before contract check
{% for col in columns %}
{% if col.data_type is not string %}
{{ exceptions.raise_compiler_error("column " ~ col.name ~ " data_type must be a string, got: " ~ col.data_type | tojson) }}
{% endif %}
{% endfor %} Type guard
fn is_string_attr(item: &MinijinjaValue, key: &str) -> bool {
item.get_attr(key).ok().and_then(|v| v.as_str()).is_some()
} Try / catch
match convert_value_to_column_definitions(value) {
Err(e) if e.message().contains("'data_type' must be a string") => {
// coerce or log column objects, then retry with stringified data_type
}
other => other?,
} Prevention
- Always build contract column data_type via |string in Jinja macros.
- Add a unit test feeding contract-check helpers a non-string data_type.
- Keep adapter column metadata conversion in one place that guarantees strings.
When it happens
Trigger: A column object passed into get_contract_mismatches has a 'data_type' attribute that is a non-string minijinja value (e.g. an integer, dict, list, or undefined-ish object) instead of a string like 'varchar'.
Common situations: Custom materializations or macros that build contract check results programmatically and set data_type from numeric/config values; adapters returning column metadata with typed values instead of strings; hand-rolled contract test code populating data_type with a dict or number.
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
- Argument must be a string
- Column 'name' must be a string
- Expected a list of column definitions
- Failed to convert payload to string
- {type(df)} is not a supported type for dbt Python materializ
AI-assisted analysis of dbt-labs/dbt-core@0267ce9170 (2026-09-07).
Data as JSON: /api/errors/aa04b7694b0a2709.
Report an issue: GitHub.