dbt-labs/dbt-core · error · minijinja::Error::InvalidOperation

agate_table must be an AgateTable

Error message

agate_table must be an AgateTable

What it means

`convert_type` converts an AgateTable column's agate type to the adapter's SQL data type. It requires the first argument (`agate_table`) to downcast to an AgateTable; this error is thrown when it cannot. The library enforces the type so it can index into the table's column type list with `col_idx`.

Source

Thrown at crates/dbt-adapter/src/adapter/mod.rs:741

    /// https://github.com/dbt-labs/dbt-adapters/blob/main/dbt-adapters/src/dbt/adapters/base/impl.py#L1221
    ///
    /// ```python
    /// def convert_type(
    ///     cls,
    ///     agate_table: "agate.Table",
    ///     col_idx: int
    /// ) -> Optional[str]
    /// ```
    #[tracing::instrument(skip_all, level = "trace")]
    pub fn convert_type(&self, state: &State, args: &[Value]) -> Result<Value, minijinja::Error> {
        match &self.inner {
            Typed { adapter, .. } => {
                let iter = ArgsIter::new("convert_type", &["agate_table", "col_idx"], args);
                let table = iter
                    .next_arg::<&Value>()?
                    .downcast_object::<AgateTable>()
                    .ok_or_else(|| {
                        minijinja::Error::new(
                            minijinja::ErrorKind::InvalidOperation,
                            "agate_table must be an AgateTable",
                        )
                    })?;
                let col_idx = iter.next_arg::<i64>()?;
                iter.finish()?;

                let result = adapter.convert_type(state, table, col_idx)?;
                Ok(Value::from(result))
            }
            Parse(_) => Ok(empty_string_value()),
        }
    }

    /// Render raw model constraints.
    ///
    /// https://github.com/dbt-labs/dbt-adapters/blob/main/dbt-adapters/src/dbt/adapters/base/impl.py#L1891
    ///

View on GitHub (pinned to 0267ce9170)

Solutions

  1. Ensure the caller wraps results in an AgateTable via load_agate_table() before invoking convert_type
  2. Check that the table object, not a column or a row, is being passed as the first argument
  3. If calling from Rust, pass Value::from_object(AgateTable) rather than Value::from_serialize

Example fix

// before
adapter.convert_type(raw_result, 0)
// after
adapter.convert_type(load_agate_table(raw_result), 0)
Defensive patterns

Strategy: type-guard

Validate before calling

# python guard
if not isinstance(agate_table, agate.Table):
    raise TypeError('convert_type requires an agate.Table, got ' + type(agate_table).__name__)

Type guard

fn is_agate_table(v: &Value) -> bool {
    v.downcast_object::<AgateTable>().is_some()
}

Try / catch

match value.downcast_object::<AgateTable>() {
    Some(table) => convert(table, col_idx),
    None => Err(minijinja::Error::new(minijinja::ErrorKind::InvalidOperation, "agate_table must be an AgateTable")),
}

Prevention

When it happens

Trigger: Calling convert_type(agate_table, col_idx) with a value that is not an AgateTable — e.g. passing the table as a serialized dict, a list of columns, or forgetting that the macro wrapper should pass the loaded agate table.

Common situations: Custom type-conversion macros (type_code / convert_type hooks) receive raw query results from an overridden load_dataframe or a custom materialization passes the wrong variable.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of dbt-labs/dbt-core@0267ce9170 (2026-09-07). Data as JSON: /api/errors/2917c695ce9996a8. Report an issue: GitHub.