dbt-labs/dbt-core · error · minijinja::Error::SerdeDeserializeError
model_columns: {e}
Error message
model_columns: {e} What it means
parse_columns_and_constraints deserializes the model_columns argument into a BTreeMap<String, DbtColumn> (and then model constraints into Vec<ModelConstraint>). Any serde failure is wrapped in a SerdeDeserializeError prefixed with 'model_columns:'. This validates that the model's column definitions match the expected struct shape before constraint parsing.
Source
Thrown at crates/dbt-adapter/src/adapter/adapter_impl.rs:4669
.map_err(|e| {
minijinja::Error::new(
minijinja::ErrorKind::InvalidOperation,
format!("existing_columns must be iterable: {e}"),
)
})?
.map(|v| {
v.downcast_object_ref::<Column>().cloned().ok_or_else(|| {
minijinja::Error::new(
minijinja::ErrorKind::InvalidOperation,
"existing_columns must contain Column objects",
)
})
})
.collect::<Result<Vec<_>, _>>()?;
let model_columns_map: BTreeMap<String, DbtColumn> =
minijinja_value_to_typed_struct(model_columns.clone()).map_err(|e| {
minijinja::Error::new(
minijinja::ErrorKind::SerdeDeserializeError,
format!("model_columns: {e}"),
)
})?;
let model_constraints_vec: Vec<ModelConstraint> =
minijinja_value_to_typed_struct(model_constraints.clone()).map_err(|e| {
minijinja::Error::new(
minijinja::ErrorKind::SerdeDeserializeError,
format!("model_constraints: {e}"),
)
})?;
let column_refs: Vec<DbtColumnRef> = model_columns_map
.values()
.map(|c| Arc::new(c.clone()))
.collect();
View on GitHub (pinned to 0267ce9170)
Solutions
- Read the text after 'model_columns:' in the error — it names the failing field — and fix the config.
- Pass model.columns as a name -> column map with standard dbt column fields.
- Ensure model constraints follow the documented ModelConstraint structure (name/type/expr etc.).
- Avoid transforming model.columns in custom macros before calling this API.
Example fix
// before
model_columns = [("id", "int")] # list of tuples
adapter.parse_columns_and_constraints(existing, model_columns, name)
// after
model_columns = {"id": {"name": "id", "data_type": "int"}}
adapter.parse_columns_and_constraints(existing, model_columns, name) Defensive patterns
Strategy: validation
Validate before calling
def is_model_column_map(v):
return isinstance(v, dict) and all(
isinstance(k, str) and isinstance(c, dict) and 'name' in c
for k, c in v.items()) Type guard
def as_model_columns(v):
return v if is_model_column_map(v) else None Try / catch
try:
parsed = adapter.parse_columns_and_constraints(existing, model_columns, name)
except Exception as e:
if str(e).startswith('model_columns:'):
raise ValueError(f'model.columns shape invalid: {e}') from e
raise Prevention
- Pass model.columns unmodified as a name -> column map
- Use standard dbt column fields (name, data_type, description, ...) only
- Validate constraint definitions against the ModelConstraint schema
- Compile the model standalone to surface config-shape errors early
When it happens
Trigger: Calling parse_columns_and_constraints with model_columns that cannot deserialize into IndexMap/BTreeMap<String, DbtColumn>: list instead of map, entries missing required DbtColumn fields, or wrong-typed fields.
Common situations: Passing model.config.columns in an unusual shape; custom materializations that reshape columns before this call; constraints defined with unexpected keys in model config; schema.yml columns with nonstandard metadata keys mapped into the column struct.
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
- model_constraints: {e}
- {e}
- get_table_options: Failed to deserialize config: {e}
- get_seed_file_path: Failed to deserialize DbtSeed: {e}
- Failed to deserialize InternalDbtNodeWrapper: {e}
AI-assisted analysis of dbt-labs/dbt-core@0267ce9170 (2026-09-07).
Data as JSON: /api/errors/598d1ee7713f7d04.
Report an issue: GitHub.