dbt-labs/dbt-core · error · IndexError
must read an index table
Error message
must read an index table
What it means
This error fires while building the FROM clause of a parse-safe information-schema view: the view is declared with Src::Own, meaning it is assembled in code from the index's own tables (like dbt.dag_nodes), but code path took the generic single-table/join branch instead of the dedicated builder. The dedicated builder dag_nodes_sql() is supposed to intercept Src::Own at the top of create_view_sql(), so reaching line 674 means a view entry marked Src::Own fell through to generic SQL assembly. It is an internal consistency error in the VIEWS table, not a problem with the user's project.
Source
Thrown at crates/dbt-index-core/src/info_schema/parse_safe.rs:674
let src = if col.src.is_empty() { col.out } else { col.src };
selects.push(format!("{} AS {}", self.qualify(src)?, quote(out)));
}
let from = match self.src {
Src::Table(table) => format!("{BASE_SCHEMA}.{} AS {T}", quote(table)),
Src::Join {
left,
right,
left_on,
right_on,
} => format!(
"{BASE_SCHEMA}.{} AS {L} LEFT JOIN {BASE_SCHEMA}.{} AS {R} ON {L}.{} = {R}.{}",
quote(left),
quote(right),
quote(left_on),
quote(right_on),
),
Src::Own => return Err(self.err("must read an index table".to_string())),
};
let mut sql = format!(
"CREATE OR REPLACE VIEW {VIEW_SCHEMA}.{} AS SELECT {} FROM {from}",
quote(self.name),
selects.join(", "),
);
if let Filter::ResourceTypeIn(types) = self.filter {
let list = types
.iter()
.map(|t| format!("'{t}'"))
.collect::<Vec<_>>()
.join(", ");
write!(sql, " WHERE {} IN ({list})", self.qualify("resource_type")?)
.expect("writing to a String cannot fail");
}
Ok(sql)
}View on GitHub (pinned to 0267ce9170)
Solutions
- Restore or extend the early dispatch `if matches!(self.src, Src::Own) { return self.dag_nodes_sql(); }` at the top of create_view_sql() so Src::Own never reaches the FROM-clause match.
- If the new Src::Own view needs different SQL, add a dedicated builder method for it and route Src::Own variants there instead of the generic path.
- Add a test enumerating every entry in VIEWS and asserting create_view_sql() succeeds, so the regression is caught at CI time.
Example fix
// before
let from = match self.src {
Src::Table(table) => format!("{BASE_SCHEMA}.{} AS {T}", quote(table)),
Src::Join { .. } => format!("..."),
Src::Own => return Err(self.err("must read an index table".to_string())),
};
// after
if matches!(self.src, Src::Own) {
return self.dag_nodes_sql(); // keep the early dispatch before the FROM match
}
let from = match self.src {
Src::Table(table) => format!("{BASE_SCHEMA}.{} AS {T}", quote(table)),
Src::Join { .. } => format!("..."),
Src::Own => unreachable!("handled by dag_nodes_sql above"),
}; Defensive patterns
Strategy: validation
Validate before calling
// Rust caller: refuse Src::Own views before generic assembly
fn can_build_generic_view(v: &ParseSafeView) -> bool {
!matches!(v.src, Src::Own)
} Type guard
fn is_own(src: &Src) -> bool { matches!(src, Src::Own) } Try / catch
match view.create_view_sql() {
Ok(sql) => execute(sql),
Err(IndexError::Other(msg)) if msg.contains("must read an index table") => {
// route to dedicated builder instead
let sql = view.dag_nodes_sql()?;
}
Err(e) => return Err(e),
} Prevention
- Keep the `if matches!(self.src, Src::Own)` dispatch as the first statement of create_view_sql().
- Add a test iterating all VIEWS entries calling create_view_sql() and asserting Ok.
- Prefer exhaustive enums with unreachable!() over silent error arms for statically-known inputs.
When it happens
Trigger: Calling create_view_sql() on a view whose src is Src::Own without the early dag_nodes_sql() dispatch having handled it (i.e., a code change removes or bypasses the `if matches!(self.src, Src::Own)` guard at parse_safe.rs:637), or a new Src::Own view added to VIEWS while the Own special-case path fails to cover it.
Common situations: Contributors adding a new self-assembled view or refactoring create_view_sql() and accidentally letting Src::Own reach the match on `self.src` that only knows how to emit Table/Join FROM clauses. Never triggered by end-user configuration or data.
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
- no expression for '{col}'
- neither '{left}' nor '{right}' has a column '{src}'
- inconsistent park_timeout state: {n}
- inconsistent state in unpark
- failed to generate unique thread ID: bitspace exhausted
AI-assisted analysis of dbt-labs/dbt-core@0267ce9170 (2026-09-07).
Data as JSON: /api/errors/a82f98aaa7171d2c.
Report an issue: GitHub.