diff --git a/superset/sql/parse.py b/superset/sql/parse.py index f5f93d34d06..93fccc44078 100644 --- a/superset/sql/parse.py +++ b/superset/sql/parse.py @@ -1123,14 +1123,17 @@ class SQLStatement(BaseSQLStatement[exp.Expression]): """ Check if the statement has a subquery. + Covers explicit subqueries, set operations (``UNION``/``INTERSECT``/ + ``EXCEPT``), and any nested ``SELECT`` regardless of the top-level node + type (e.g. when wrapped in parentheses or a set operation). + :return: True if the statement has a subquery. """ - return bool(self._parsed.find(exp.Subquery)) or ( - isinstance(self._parsed, exp.Select) - and any( - isinstance(expression, exp.Select) - for expression in self._parsed.walk() - if expression != self._parsed + return ( + self.is_set_operation() + or bool(self._parsed.find(exp.Subquery)) + or any( + select != self._parsed for select in self._parsed.find_all(exp.Select) ) ) diff --git a/tests/unit_tests/sql/parse_tests.py b/tests/unit_tests/sql/parse_tests.py index 2f3b72c5b38..e6a4e45aa1d 100644 --- a/tests/unit_tests/sql/parse_tests.py +++ b/tests/unit_tests/sql/parse_tests.py @@ -3549,6 +3549,17 @@ def test_tokenize_kql(kql: str, expected: list[tuple[KQLTokenType, str]]) -> Non "postgresql", True, ), + # Set operations: a top-level UNION/INTERSECT/EXCEPT is not an + # exp.Subquery, so it must be detected explicitly. A predicate fragment + # that introduces one (e.g. supplied through a chart filter) must be + # flagged. + ("true UNION SELECT name FROM other_table", "postgresql", True), + ("1 = 1 UNION ALL SELECT password FROM users", "postgresql", True), + ("SELECT 1 INTERSECT SELECT 2", "postgresql", True), + ("SELECT 1 EXCEPT SELECT 2", "postgresql", True), + # Nested SELECT under non-Select top-level nodes (e.g. extra + # parentheses) must still be detected. + ("name IN (((SELECT secret FROM s)))", "postgresql", True), ], ) def test_has_subquery(sql: str, engine: str, expected: bool) -> None: