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

  1. 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.
  2. 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.
  3. 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

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


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