dbt-labs/dbt-core · error · minijinja::Error (InvalidOperation)
Expected a list of column definitions
Error message
Expected a list of column definitions
What it means
Thrown by convert_value_to_column_definitions when the value it is iterating is not a list of column definitions. The contract mismatch logic only accepts a sequence whose items are objects with attributes like name/data_type; anything else (e.g. a plain string, dict-of-dicts, or non-sequence) falls into the Err arm of the iterator conversion and aborts.
Source
Thrown at crates/dbt-jinja-utils/src/functions/contract_error.rs:191
"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,
});
}
}
Err(_) => {
return Err(Error::new(
ErrorKind::InvalidOperation,
"Expected a list of column definitions",
));
}
}
Ok(columns)
}
#[cfg(test)]
mod tests {
use super::*;
use dbt_agate::MappedSequence;
#[test]
fn test_contract_error_perfect_match() {
let yaml_columns = vec![
ColumnDefinition {View on GitHub (pinned to 0267ce9170)
Solutions
- Make sure the columns value passed to contract checking is a list (Jinja array) of column objects each with name and data_type attributes.
- If your columns are a mapping, convert to a list of objects (e.g. [{'name': k, 'data_type': v} ...]).
- Print the value with |tojson before the call to confirm its shape.
Example fix
-- before (Jinja)
{% do contract_check(columns={'id': 'int', 'name': 'text'}) %}
-- after
{% do contract_check(columns=[{'name': 'id', 'data_type': 'int'}, {'name': 'name', 'data_type': 'text'}]) %} Defensive patterns
Strategy: validation
Validate before calling
-- Jinja, before contract check
{% if columns is not sequence or columns is string or columns is mapping %}
{{ exceptions.raise_compiler_error("columns must be a list of column definitions, got: " ~ columns | tojson) }}
{% endif %} Try / catch
match convert_value_to_column_definitions(value) {
Err(e) if e.message().contains("Expected a list of column definitions") => {
// dump value with |tojson, fix shape to a list of objects
}
other => other?,
} Prevention
- Standardize on a list of {name, data_type} dicts for all contract-check macros.
- Convert name-keyed column mappings to lists before checking contracts.
- Snapshot macro output with |tojson in tests to catch shape regressions.
When it happens
Trigger: get_contract_mismatches receives a 'columns' value that is not a list (e.g. a mapping, a single object, or a string), or list items are not objects with column attributes, causing the minijinja iteration/attribute extraction to fail.
Common situations: Passing a dict keyed by column name instead of a list of column dicts; macros returning a single column instead of a list; whitespace/formatting changes in rendered macro output producing malformed results; version drift between custom contract macros and the engine.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Compilation Error for {} from {}: {}
- Contract Error for {} from {}: {}
- Contract Error: {message}
- Column 'name' 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/81cb7b05fdbc9c49.
Report an issue: GitHub.