{"record":{"id":"fbdcda8583406400","repo":"dbt-labs/dbt-core","slug":"should-split-on-top-level-union-all","errorCode":null,"errorMessage":"should split on top-level UNION ALL","messagePattern":"should split on top-level UNION ALL","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/dbt-adapter/src/sql/diff.rs","lineNumber":3777,"sourceCode":"        compare_sql(sql_fusion, sql_recorded, AdapterType::Snowflake)\n            .expect(\"Forward-fill projection column order drift should be ignored\");\n    }\n\n    #[test]\n    fn test_split_union_top_level_splits_and_handles_unicode() {\n        // Regression test: previously this could panic if the scan index landed in the middle\n        // of a multi-byte UTF-8 char (e.g. “).\n        let sql = \"select 1 as a /* “unicode” */ UNION      select 2 as b\";\n        let parts = split_union_top_level(sql).expect(\"should split on top-level UNION\");\n        assert_eq!(parts, vec![\"select 1 as a\", \"select 2 as b\"]);\n    }\n\n    #[test]\n    fn test_split_union_all_top_level_splits_and_handles_unicode() {\n        // Regression test: previously this could panic if the scan index landed in the middle\n        // of a multi-byte UTF-8 char (e.g. “).\n        let sql = \"select 1 as a /* “unicode” */ UNION   ALL   select 2 as b\";\n        let parts = split_union_all_top_level(sql).expect(\"should split on top-level UNION ALL\");\n        assert_eq!(parts, vec![\"select 1 as a\", \"select 2 as b\"]);\n    }\n\n    #[test]\n    fn test_split_union_all_top_level_does_not_split_inside_parentheses() {\n        let sql = \"select 1 as a union all select (select 2 as b union all select 3 as c)\";\n        let parts =\n            split_union_all_top_level(sql).expect(\"should split on the top-level UNION ALL\");\n        assert_eq!(\n            parts,\n            vec![\n                \"select 1 as a\",\n                \"select (select 2 as b union all select 3 as c)\"\n            ]\n        );\n    }\n\n    #[test]","sourceCodeStart":3759,"sourceCodeEnd":3795,"githubUrl":"https://github.com/dbt-labs/dbt-core/blob/0267ce9170576975b76b64ce856b2e5848e96617/crates/dbt-adapter/src/sql/diff.rs#L3759-L3795","documentation":"Test panic from Option::expect at crates/dbt-adapter/src/sql/diff.rs:3777: split_union_all_top_level(sql) returned None, i.e. the splitter did not recognize a top-level UNION ALL in the input and could not produce the parts vector. The scanner must locate UNION ALL outside parentheses, strings, and comments while advancing safely over multi-byte UTF-8 characters.","triggerScenarios":"cargo test -p dbt-adapter with test_split_union_all_top_level_splits_and_handles_unicode: input \"select 1 as a /* “unicode” */ UNION   ALL   select 2 as b\" and split_union_all_top_level returns None, panicking \"should split on top-level UNION ALL\".","commonSituations":"Comment bodies with smart quotes break byte-indexed scanning; keyword matching requires exactly one space between UNION and ALL so varied whitespace fails; a regression in the shared top-level scan used by compare_sql_structurally prevents splitting comment-bearing UNION ALL queries.","solutions":["Use char_indices-based scanning to avoid multi-byte char boundary issues (the exact regression this test guards)","Make the UNION ALL match whitespace-tolerant (skip arbitrary whitespace between UNION and ALL)","Ensure comments are skipped before keyword matching so UNION ALL inside /* */ never matches and top-level ones always do","Confirm the caller (compare_sql_structurally, diff.rs:1418) surfaces the None as a clean error rather than masking it"],"exampleFix":"// before: rigid match\nif &s[i..i+9] == \"UNION ALL\" { ... }\n// after: whitespace-tolerant, char-safe scan\nlet rest = skip_ws(s, after_union_keyword);\nif rest.starts_with(\"ALL\") { parts.push(...); }","handlingStrategy":"try-catch","validationCode":"fn has_top_level_union_all(sql: &str) -> bool {\n    scan_top_level_ws_tolerant(sql, \"UNION\", \"ALL\")\n}\nif !has_top_level_union_all(sql) { eprintln!(\"no top-level UNION ALL; not splittable\"); }","typeGuard":"fn is_split(parts: &Option<Vec<String>>) -> bool { matches!(parts, Some(v) if v.len() > 1) }","tryCatchPattern":"let parts = match split_union_all_top_level(sql) {\n    Some(p) => p,\n    None => { vec![sql.to_string()] } // treat as single statement, don't panic\n};","preventionTips":["Allow arbitrary whitespace between UNION and ALL when matching","Scan with char_indices so multi-byte comment text can't break state","Test splitters with smart quotes, comments, and nested operators","Never expect() on the splitter in library code; the None is a valid outcome"],"tags":["rust","sql-parsing","test-panic","unicode","union-all"],"backgroundTag":"sql-parse-failed","analyzedSha":"0267ce9170576975b76b64ce856b2e5848e96617","analyzedAt":"2026-09-07T21:53:39.732Z","contentChangedAt":"2026-09-07T21:53:39.732Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}