{"record":{"id":"a82f98aaa7171d2c","repo":"dbt-labs/dbt-core","slug":"must-read-an-index-table","errorCode":null,"errorMessage":"must read an index table","messagePattern":"must read an index table","errorType":"validation","errorClass":"IndexError","httpStatus":null,"severity":"error","filePath":"crates/dbt-index-core/src/info_schema/parse_safe.rs","lineNumber":674,"sourceCode":"            let src = if col.src.is_empty() { col.out } else { col.src };\n            selects.push(format!(\"{} AS {}\", self.qualify(src)?, quote(out)));\n        }\n\n        let from = match self.src {\n            Src::Table(table) => format!(\"{BASE_SCHEMA}.{} AS {T}\", quote(table)),\n            Src::Join {\n                left,\n                right,\n                left_on,\n                right_on,\n            } => format!(\n                \"{BASE_SCHEMA}.{} AS {L} LEFT JOIN {BASE_SCHEMA}.{} AS {R} ON {L}.{} = {R}.{}\",\n                quote(left),\n                quote(right),\n                quote(left_on),\n                quote(right_on),\n            ),\n            Src::Own => return Err(self.err(\"must read an index table\".to_string())),\n        };\n\n        let mut sql = format!(\n            \"CREATE OR REPLACE VIEW {VIEW_SCHEMA}.{} AS SELECT {} FROM {from}\",\n            quote(self.name),\n            selects.join(\", \"),\n        );\n        if let Filter::ResourceTypeIn(types) = self.filter {\n            let list = types\n                .iter()\n                .map(|t| format!(\"'{t}'\"))\n                .collect::<Vec<_>>()\n                .join(\", \");\n            write!(sql, \" WHERE {} IN ({list})\", self.qualify(\"resource_type\")?)\n                .expect(\"writing to a String cannot fail\");\n        }\n        Ok(sql)\n    }","sourceCodeStart":656,"sourceCodeEnd":692,"githubUrl":"https://github.com/dbt-labs/dbt-core/blob/0267ce9170576975b76b64ce856b2e5848e96617/crates/dbt-index-core/src/info_schema/parse_safe.rs#L656-L692","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlet from = match self.src {\n    Src::Table(table) => format!(\"{BASE_SCHEMA}.{} AS {T}\", quote(table)),\n    Src::Join { .. } => format!(\"...\"),\n    Src::Own => return Err(self.err(\"must read an index table\".to_string())),\n};\n// after\nif matches!(self.src, Src::Own) {\n    return self.dag_nodes_sql(); // keep the early dispatch before the FROM match\n}\nlet from = match self.src {\n    Src::Table(table) => format!(\"{BASE_SCHEMA}.{} AS {T}\", quote(table)),\n    Src::Join { .. } => format!(\"...\"),\n    Src::Own => unreachable!(\"handled by dag_nodes_sql above\"),\n};","handlingStrategy":"validation","validationCode":"// Rust caller: refuse Src::Own views before generic assembly\nfn can_build_generic_view(v: &ParseSafeView) -> bool {\n    !matches!(v.src, Src::Own)\n}","typeGuard":"fn is_own(src: &Src) -> bool { matches!(src, Src::Own) }","tryCatchPattern":"match view.create_view_sql() {\n    Ok(sql) => execute(sql),\n    Err(IndexError::Other(msg)) if msg.contains(\"must read an index table\") => {\n        // route to dedicated builder instead\n        let sql = view.dag_nodes_sql()?;\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["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."],"tags":["internal-invariant","sql-generation","info-schema"],"backgroundTag":"internal-invariant-violation","analyzedSha":"0267ce9170576975b76b64ce856b2e5848e96617","analyzedAt":"2026-09-07T21:53:39.732Z","contentChangedAt":"2026-09-07T21:53:39.732Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}