dbt-labs/dbt-core · error · minijinja::Error::InvalidOperation
existing_columns must contain Column objects
Error message
existing_columns must contain Column objects
What it means
After successful iteration, every element of existing_columns must be a Column object; the code downcasts each Value to Column and raises InvalidOperation 'existing_columns must contain Column objects' on the first element that is not one. The adapter needs typed Column objects to extract names/types and build constraints.
Source
Thrown at crates/dbt-adapter/src/adapter/adapter_impl.rs:4659
if self.adapter_type() != Databricks && self.adapter_type() != Spark {
return Err(minijinja::Error::new(
minijinja::ErrorKind::InvalidOperation,
"parse_columns_and_constraints is only available for Databricks/Spark adapter",
));
}
let columns: Vec<Column> = existing_columns
.try_iter()
.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(View on GitHub (pinned to 0267ce9170)
Solutions
- Build elements with the Column type (e.g. api.Column / adapter column class) instead of plain dicts.
- Convert each dict into a Column before calling: Column(name=..., dtype=...).
- Use get_columns_in_relation output directly, which already contains Column objects.
- Filter out any non-Column entries (e.g. stray strings) from the list before the call.
Example fix
// before
cols = [{"name": "id", "dtype": "int"}]
adapter.parse_columns_and_constraints(cols, model_columns, name)
// after
cols = [api.Column("id", "int")]
adapter.parse_columns_and_constraints(cols, model_columns, name) Defensive patterns
Strategy: validation
Validate before calling
def all_columns_are_objects(v):
return all(getattr(i, 'name', None) is not None or (isinstance(i, dict) and 'name' in i) for i in v) Type guard
def is_column_obj(x):
return hasattr(x, 'name') and hasattr(x, 'dtype') Try / catch
try:
parsed = adapter.parse_columns_and_constraints(existing, model_columns, name)
except Exception as e:
if 'must contain Column objects' in str(e):
raise ValueError('Convert dicts to api.Column objects before calling parse_columns_and_constraints') from e
raise Prevention
- Construct column entries with the Column type, never raw dicts
- Prefer reusing get_columns_in_relation output over hand-built lists
- Convert JSON/dict column data into Column objects at the macro boundary
- Keep mixed lists (objects + dicts) out of this API
When it happens
Trigger: existing_columns is iterable but contains plain dicts, strings, or other Jinja values instead of Column objects — e.g. a hand-built list of {name, type} dicts.
Common situations: Constructing columns manually in a macro as dicts instead of using api.Column / the adapter's Column type; deserializing columns from JSON into plain maps; mixing Column objects with raw values in one list.
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
- existing_columns must be iterable: {e}
- {}
- Failed to downcast jinja value to Column; expected Column ob
- only available with Databricksadapter
- only available with Databricks adapter
AI-assisted analysis of dbt-labs/dbt-core@0267ce9170 (2026-09-07).
Data as JSON: /api/errors/8426735a97507a5b.
Report an issue: GitHub.