dbt-labs/dbt-core · error · InvalidArgument

Table.join: right_table must be a Table: {right_table} found

Error message

Table.join: right_table must be a Table: {right_table} found instead

What it means

Thrown by `Table.join()` when the `right_table` argument is not an AgateTable object. The method downcasts the passed Value to an AgateTable reference, and if the downcast fails it raises this InvalidArgument error. Both operands of a join must be Table instances.

Source

Thrown at crates/dbt-agate/src/table.rs:1297

            "join" => {
                let iter = ArgsIter::new("Table.join", &["right_table"], args);
                let right_table = iter.next_arg::<&Value>()?;
                let left_key = iter.next_kwarg::<Option<&Value>>("left_key")?;
                let right_key = iter.next_kwarg::<Option<&Value>>("right_key")?;
                let inner = iter.next_kwarg::<Option<bool>>("inner")?.unwrap_or(false);
                let full_outer = iter
                    .next_kwarg::<Option<bool>>("full_outer")?
                    .unwrap_or(false);
                let require_match = iter
                    .next_kwarg::<Option<bool>>("require_match")?
                    .unwrap_or(false);
                let columns = iter.next_kwarg::<Option<&Value>>("columns")?;
                iter.finish()?;

                let right_table = match right_table.downcast_object_ref::<AgateTable>() {
                    Some(table) => table,
                    None => {
                        return Err(Error::new(
                            ErrorKind::InvalidArgument,
                            format!(
                                "Table.join: right_table must be a Table: {right_table} found instead"
                            ),
                        ));
                    }
                };

                let join_type = if inner {
                    if full_outer {
                        return Err(Error::new(
                            ErrorKind::InvalidArgument,
                            "A join can not be both \"inner\" and \"full_outer\".",
                        ));
                    }
                    JoinType::Inner
                } else if full_outer {
                    JoinType::FullOuter

View on GitHub (pinned to 0267ce9170)

Solutions

  1. Ensure the right operand is an agate Table constructed via Table(...)
  2. If you have a TableSet, select the specific table first, e.g. `table_set['name']`
  3. Check for variable shadowing/reassignment of the right_table variable
  4. Print `type(right_table)` before the join to confirm

Example fix

// before
table.join(table.group_by('k'), 'k')  # TableSet, not Table
// after
joined = table.join(other_table, 'k')
Defensive patterns

Strategy: type-guard

Validate before calling

if not isinstance(right, agate.Table):
    raise TypeError(f'right_table must be a Table, got {type(right).__name__}')

Type guard

def is_agate_table(v):
    return isinstance(v, agate.Table)

Try / catch

try:
    joined = table.join(right, 'k')
except Exception as e:
    if 'right_table must be a Table' in str(e):
        raise TypeError('join requires an agate.Table as right_table') from e
    raise

Prevention

When it happens

Trigger: Calling `table.join(x, ...)` where `x` is a TableSet, a dict, a list of rows, or any non-Table value instead of an agate Table.

Common situations: Passing the result of `table.group_by(...)` (a TableSet) to join by mistake; passing raw data structures that were never constructed into a Table; variable shadowing where the right table variable was reassigned.

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


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