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::FullOuterView on GitHub (pinned to 0267ce9170)
Solutions
- Ensure the right operand is an agate Table constructed via Table(...)
- If you have a TableSet, select the specific table first, e.g. `table_set['name']`
- Check for variable shadowing/reassignment of the right_table variable
- 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
- Never pass a TableSet where a Table is expected
- Check variable provenance before joins
- Use type hints/linters to catch non-Table arguments
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
- Table.select: key must be a string or an array of strings: {
- Table.select: key must be a string or an array of strings: {
- Table.rename: column_names array must contain only strings,
- Table.rename: row_names array must contain only strings, fou
- A join can not be both "inner" and "full_outer".
AI-assisted analysis of dbt-labs/dbt-core@0267ce9170 (2026-09-07).
Data as JSON: /api/errors/79f02e38539971f6.
Report an issue: GitHub.