From 4c6ab69ffcd9a885f929ce68d971f9c3c4d58bb5 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Thu, 14 May 2026 18:28:41 -0700 Subject: [PATCH] test(sql-parser): pin WITH+UNION as non-mutating across dialects Closes #25659 Co-Authored-By: Claude Sonnet 4.6 --- tests/unit_tests/sql/parse_tests.py | 45 +++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/unit_tests/sql/parse_tests.py b/tests/unit_tests/sql/parse_tests.py index 78b00f4487d..4c748329972 100644 --- a/tests/unit_tests/sql/parse_tests.py +++ b/tests/unit_tests/sql/parse_tests.py @@ -1164,6 +1164,51 @@ def test_has_mutation(engine: str, sql: str, expected: bool) -> None: assert SQLScript(sql, engine).has_mutation() == expected +@pytest.mark.parametrize( + "engine", + ["oracle", "postgresql", "trino", "presto", "hive", "base"], +) +def test_with_clause_containing_union_is_not_mutating(engine: str) -> None: + """ + Regression for #25659: a SELECT with a WITH clause whose CTEs contain + UNION (or UNION ALL) must not be classified as mutating, on any dialect. + + The original bug surfaced on Oracle, where saving a virtual dataset built + from such a query failed with "Only `SELECT` statements are allowed". The + parser was misclassifying the WITH+UNION construct as DML. + + Multiple dialects are exercised because the bug arose from sqlglot's + per-dialect AST shape — Oracle's representation of the same query may + differ from Postgres/Trino, and a fix that only touches one dialect + leaves the others exposed to the same regression. + """ + sql = """ + WITH set1 AS (SELECT 1 AS n UNION SELECT 2), + set2 AS (SELECT * FROM set1) + SELECT * FROM set2 + """ + assert not SQLScript(sql, engine).has_mutation(), ( + f"WITH+UNION misclassified as mutating on {engine!r}; " + "this would block the query from being saved as a virtual dataset." + ) + + +def test_with_clause_containing_union_all_is_not_mutating_oracle() -> None: + """ + Companion to test_with_clause_containing_union_is_not_mutating: the + original bug report (#25659) used the exact Oracle-flavored shape below + (``SYSDATE FROM DUAL`` is the Oracle no-op for "now"). Pinning the + verbatim repro guards against a future dialect-specific regression that + a generic ``SELECT 1`` test might miss. + """ + sql = """ + WITH SET1 AS (SELECT SYSDATE FROM DUAL UNION SELECT SYSDATE FROM DUAL), + SET2 AS (SELECT * FROM SET1) + SELECT * FROM SET2 + """ + assert not SQLScript(sql, "oracle").has_mutation() + + def test_get_settings() -> None: """ Test `get_settings` in some edge cases.