dbt-labs/dbt-core · error

AgateTable exists

Error message

AgateTable exists

What it means

`convert_macro_result_to_record_batch` (crates/dbt-adapter/src/macro_exec.rs:44) expects a macro result dynamic object to be either a `ResultObject` with a non-None `table` or an `AgateTable`. The `.expect("AgateTable exists")` panics when a `ResultObject` is returned whose `table` field is None — i.e. the macro produced a result without a table.

Source

Thrown at crates/dbt-adapter/src/macro_exec.rs:44

    execute_macro_with_package(state, args, macro_name, "dbt")
}

pub fn execute_macro_wrapper_with_package(
    state: &State,
    args: &[Value],
    macro_name: &str,
    package: &str,
) -> Result<Arc<RecordBatch>, AdapterError> {
    let result: Value = execute_macro_with_package(state, args, macro_name, package)?;
    convert_macro_result_to_record_batch(&result)
}

pub fn convert_macro_result_to_record_batch(
    result: &Value,
) -> Result<Arc<RecordBatch>, AdapterError> {
    // Depending on the macro impl, result can be either ResultObject or AgateTable
    let table = if let Some(result) = result.downcast_object::<ResultObject>() {
        result.table.as_ref().expect("AgateTable exists").to_owned()
    } else if let Some(result) = result.downcast_object::<AgateTable>() {
        result.as_ref().to_owned()
    } else {
        return Err(AdapterError::new(
            AdapterErrorKind::UnexpectedResult,
            format!("Unexpected result type {result}"),
        ));
    };

    let record_batch = table.original_record_batch();
    Ok(record_batch)
}

pub fn execute_macro_with_package(
    state: &State,
    args: &[Value],
    macro_name: &str,
    package: &str,

View on GitHub (pinned to 0267ce9170)

Solutions

  1. Fix the macro to always set a table on its `ResultObject`
  2. Change the code to handle `result.table` being None by returning an `AdapterError` (or empty RecordBatch) instead of `.expect`
  3. Check which macro implementation was dispatched and verify its return contract
  4. Guard callers: only invoke this conversion for macros documented to return tables

Example fix

// before
result.table.as_ref().expect("AgateTable exists").to_owned()
// after
result.table.as_ref().map(|t| t.to_owned()).ok_or_else(|| {
    AdapterError::new(AdapterErrorKind::UnexpectedResult, "macro ResultObject has no table".into())
})?
Defensive patterns

Strategy: type-guard

Validate before calling

// check macro result shape before conversion
let ok = result.downcast_object::<ResultObject>().map(|r| r.table.is_some()).unwrap_or(false)
    || result.downcast_object::<AgateTable>().is_some();
if !ok { return Err(/* UnexpectedResult */); }

Type guard

fn has_convertible_table(result: &Value) -> bool {
    result.downcast_object::<ResultObject>().map_or(false, |r| r.table.is_some())
        || result.downcast_object::<AgateTable>().is_some()
}

Try / catch

std::panic::catch_unwind(AssertUnwindSafe(|| convert_macro_result_to_record_batch(&result)))

Prevention

When it happens

Trigger: Calling any of `get_columns_in_relation_via_macro`, `try_columns_from_json_describe`, `execute_macro_wrapper_with_package`, `fetch_json_metadata`, `fetch_catalog_data` when the executed macro returns a `ResultObject` with `table: None` (or an unsupported third type, which yields the UnexpectedResult error instead).

Common situations: A custom/dispatched macro variant returns a plain value or empty result instead of a table; a macro was refactored to return JSON metadata without a table; adapter package versions disagree on the macro's return shape.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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