{"record":{"id":"4dbee99a9d398ed0","repo":"dbt-labs/dbt-core","slug":"should-split-on-top-level-union","errorCode":null,"errorMessage":"should split on top-level UNION","messagePattern":"should split on top-level UNION","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/dbt-adapter/src/sql/diff.rs","lineNumber":3768,"sourceCode":"    partition by billing_group_id\n    order by __as_of\n    rows between unbounded preceding and current row\n  ) as a,\n  rn\nfrom filled_data\nqualify row_number() over (partition by billing_group_id, __as_of order by rn desc) = 1\n\"#;\n\n        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!(","sourceCodeStart":3750,"sourceCodeEnd":3786,"githubUrl":"https://github.com/dbt-labs/dbt-core/blob/0267ce9170576975b76b64ce856b2e5848e96617/crates/dbt-adapter/src/sql/diff.rs#L3750-L3786","documentation":"Test panic from Option::expect at crates/dbt-adapter/src/sql/diff.rs:3768: split_union_top_level(sql) returned None, meaning the scanner failed to find a top-level UNION keyword outside parentheses/strings/comments, so it could not return the vector of statement parts. The splitter must skip multi-byte characters safely (the regression this test guards: an index landing mid-UTF-8 char in “unicode” could panic or miss the UNION).","triggerScenarios":"cargo test -p dbt-adapter with test_split_union_top_level_splits_and_handles_unicode: input \"select 1 as a /* “unicode” */ UNION      select 2 as b\" and split_union_top_level returns None, panicking \"should split on top-level UNION\".","commonSituations":"SQL containing smart quotes or other multi-byte characters in comments near set operators; byte-index scanning after a char-boundary fix broke keyword detection; callers in compare_sql_structurally (diff.rs:1418) pass comment-bearing UNION queries that previously split fine.","solutions":["Iterate chars (char_indices) instead of raw byte indexing so multi-byte characters cannot corrupt the scan position","Verify the comment scanner consumes /* “unicode” */ entirely before looking for UNION at top level","Normalize whitespace around the UNION keyword before matching, and ensure case-insensitive matching","If the SQL genuinely has no top-level UNION, confirm returning None is intended and the test input is correct"],"exampleFix":"// before (byte indexing can split a multi-byte char)\nwhile i < s.len() { let b = s.as_bytes()[i]; ... }\n// after\nfor (i, c) in s.char_indices() { /* scan, skip comments/strings, detect top-level UNION */ }","handlingStrategy":"try-catch","validationCode":"// confirm a top-level UNION exists before calling the splitter\nfn has_top_level_union(sql: &str) -> bool {\n    scan_top_level(sql, |s| s.eq_ignore_ascii_case(\"UNION\"))\n}\nif !has_top_level_union(sql) { /* skip split path */ }","typeGuard":"fn is_split(parts: &Option<Vec<String>>) -> bool { matches!(parts, Some(v) if v.len() > 1) }","tryCatchPattern":"match split_union_top_level(sql) {\n    Some(parts) => compare_parts(parts),\n    None => compare_whole(sql), // graceful fallback instead of expect\n}","preventionTips":["Use char_indices, never byte indexing, when scanning SQL text","Skip comments/strings before keyword matching","Whitespace-tolerant keyword matching for set operators","Return None deliberately and handle it, rather than expect()ing in callers"],"tags":["rust","sql-parsing","test-panic","unicode","set-operator"],"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-14T11:17:12.474Z"}