{"record":{"id":"ff33e0ff2fab4435","repo":"dbt-labs/dbt-core","slug":"neither-left-nor-right-has-a-column-src","errorCode":null,"errorMessage":"neither '{left}' nor '{right}' has a column '{src}'","messagePattern":"neither '(.+?)' nor '(.+?)' has a column '(.+?)'","errorType":"validation","errorClass":"IndexError","httpStatus":null,"severity":"error","filePath":"crates/dbt-index-core/src/info_schema/parse_safe.rs","lineNumber":754,"sourceCode":"        for (table, resource_type) in DAG_SIDE_TABLES {\n            arms.push(select(table, Some(resource_type))?);\n        }\n\n        Ok(format!(\n            \"CREATE OR REPLACE VIEW {VIEW_SCHEMA}.{} AS {}\",\n            quote(self.name),\n            arms.join(\" UNION ALL \"),\n        ))\n    }\n\n    /// Qualify a source column with the alias of the table it comes from. For a join the\n    /// left table wins, as it does in the information schema's own projection.\n    fn qualify(&self, src: &str) -> Result<String, IndexError> {\n        match self.src {\n            Src::Table(_) => Ok(format!(\"{T}.{}\", quote(src))),\n            Src::Join { left, .. } if has_column(left, src) => Ok(format!(\"{L}.{}\", quote(src))),\n            Src::Join { right, .. } if has_column(right, src) => Ok(format!(\"{R}.{}\", quote(src))),\n            Src::Join { left, right, .. } => Err(self.err(format!(\n                \"neither '{left}' nor '{right}' has a column '{src}'\"\n            ))),\n            Src::Own => Err(self.err(\"must read an index table\".to_string())),\n        }\n    }\n\n    fn err(&self, msg: String) -> IndexError {\n        IndexError::Other(format!(\"parse-safe view '{}': {msg}\", self.name))\n    }\n}\n\n/// Every index table [`VIEWS`] reads, deduplicated.\npub fn base_tables() -> Vec<&'static str> {\n    VIEWS\n        .iter()\n        .flat_map(|v| v.base_tables())\n        .collect::<BTreeSet<_>>()\n        .into_iter()","sourceCodeStart":736,"sourceCodeEnd":772,"githubUrl":"https://github.com/dbt-labs/dbt-core/blob/0267ce9170576975b76b64ce856b2e5848e96617/crates/dbt-index-core/src/info_schema/parse_safe.rs#L736-L772","documentation":"During view SQL generation, qualify() resolves a source column name to the alias of the underlying table that provides it. For a join source it checks the left table first, then the right (left wins, matching the information schema's own projection). If the requested column exists in neither joined index table, view construction fails with this error. Like the other parse-safe errors, it reflects a mistake in the static VIEWS declaration, not user data.","triggerScenarios":"A VIEWS entry declares a column whose `src` name does not exist in either table of its Src::Join { left, right, .. } spec (checked via has_column against each table's schema), and create_view_sql() calls qualify() for it.","commonSituations":"Renaming or removing a column in one of the index tables (schema_for) without updating the view spec that joins that table and references the old column name; or copy-pasting a cols entry from a single-table view into a join view where the column only lives in a third table.","solutions":["Correct the column's `src` name in the VIEWS entry to a column that exists in the left or right join table (verify with schema_for / has_column).","If the column truly exists in neither table, drop it from the view's cols list or change the view's Src to include a table that has it.","If a join-table column was renamed, update the information-schema spec (col.src) and any view entries referencing the old name together."],"exampleFix":"// before (VIEWS entry referencing removed column)\ncols: &[\"unique_id\", \"raw_code\"], src: Src::Join { left: \"nodes\", right: \"compiled\", .. }\n// after\ncols: &[\"unique_id\", \"compiled_code\"], src: Src::Join { left: \"nodes\", right: \"compiled\", .. }","handlingStrategy":"validation","validationCode":"// Verify every view column exists in one of the join tables before building SQL\nfn join_cols_exist(src: &Src, cols: &[&str]) -> Vec<String> {\n    if let Src::Join { left, right, .. } = src {\n        cols.iter()\n            .filter(|c| !has_column(left, c) && !has_column(right, c))\n            .map(|c| format!(\"column '{c}' missing from both '{left}' and '{right}'\"))\n            .collect()\n    } else {\n        vec![]\n    }\n}","typeGuard":null,"tryCatchPattern":"match view.create_view_sql() {\n    Ok(sql) => execute(sql),\n    Err(IndexError::Other(msg)) if msg.contains(\"has a column\") => {\n        eprintln!(\"view spec references unknown column (developer fix needed): {msg}\");\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["When renaming/removing an index table column, grep VIEWS for the old `src` name in the same change.","Add a test that validates every view entry's cols against its Src tables via has_column.","Keep per-table schema definitions (schema_for) as the single source of truth for column names."],"tags":["internal-invariant","sql-generation","schema-mismatch"],"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"}