This is an automated email from the ASF dual-hosted git repository. beto pushed a commit to branch fix-is-mutating-procedure in repository https://gitbox.apache.org/repos/asf/superset.git
commit 5a5c238af66b0560e906c0d330909478c6892c65 Author: Beto Dealmeida <[email protected]> AuthorDate: Wed Jul 30 16:34:59 2025 -0400 fix: prevent anonymous code in Postgres --- .../pythonpath_dev/superset_config_docker_light.py | 1 + superset/sql/parse.py | 10 ++++++ tests/unit_tests/sql/parse_tests.py | 37 ++++++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/docker/pythonpath_dev/superset_config_docker_light.py b/docker/pythonpath_dev/superset_config_docker_light.py index 9a5ae0ae67..1f053c2ce3 100644 --- a/docker/pythonpath_dev/superset_config_docker_light.py +++ b/docker/pythonpath_dev/superset_config_docker_light.py @@ -19,6 +19,7 @@ # Import all settings from the main config first from flask_caching.backends.filesystemcache import FileSystemCache + from superset_config import * # noqa: F403 # Override caching to use simple in-memory cache instead of Redis diff --git a/superset/sql/parse.py b/superset/sql/parse.py index c9ed22ff0f..bcc6b32382 100644 --- a/superset/sql/parse.py +++ b/superset/sql/parse.py @@ -654,6 +654,16 @@ class SQLStatement(BaseSQLStatement[exp.Expression]): if isinstance(self._parsed, exp.Command) and self._parsed.name == "ALTER": return True # pragma: no cover + if ( + self._dialect == Dialects.POSTGRES + and isinstance(self._parsed, exp.Command) + and self._parsed.name == "DO" + ): + # anonymous blocks can be written in many different languages (the default + # is PL/pgSQL), so parsing them it out of scope of this class; we just + # assume the anonymous block is mutating + return True + # Postgres runs DMLs prefixed by `EXPLAIN ANALYZE`, see # https://www.postgresql.org/docs/current/sql-explain.html if ( diff --git a/tests/unit_tests/sql/parse_tests.py b/tests/unit_tests/sql/parse_tests.py index 458caf5fa1..6e4db3c888 100644 --- a/tests/unit_tests/sql/parse_tests.py +++ b/tests/unit_tests/sql/parse_tests.py @@ -1189,6 +1189,43 @@ def test_is_mutating(sql: str, engine: str, expected: bool) -> None: assert SQLStatement(sql, engine).is_mutating() == expected [email protected]( + "sql, expected", + [ + ( + """ +DO $$ +BEGIN + INSERT INTO public.users (name, real_name) + VALUES ('SQLLab bypass DML', 'SQLLab bypass DML'); +END; +$$; + """, + True, + ), + ( + """ +DO $$ +BEGIN + IF (SELECT COUNT(*) FROM orders WHERE status = 'pending') > 100 THEN + RAISE NOTICE 'High pending order volume detected'; + END IF; +END; +$$; + """, + True, + ), + ], +) +def test_is_mutating_anonymous_block(sql: str, expected: bool) -> None: + """ + Test for `is_mutating` with a Postgres anonymous block. + + Since we can't parse the PL/pgSQL inside the block we always assume it is mutating. + """ + assert SQLStatement(sql, "postgresql").is_mutating() == expected + + def test_optimize() -> None: """ Test that the `optimize` method works as expected.
