Compare commits

...
Author SHA1 Message Date
Elizabeth Thompson 4a24fb5a0e fix(sql-lab): raise SupersetTemplateException instead of raw UndefinedError in SQLExecutor.execute_async
_render_sql_template() called process_template() with no try/except, so a
Jinja UndefinedError for an undefined variable not called as a function
would leak raw past execute_async() (execute() already caught it via its
broad except Exception). Wrap the call and re-raise as
SupersetTemplateException, consistent with how process_template() itself
already handles this failure mode elsewhere.
2026-08-06 16:47:15 +00:00
2 changed files with 44 additions and 1 deletions
+7 -1
View File
@@ -66,6 +66,7 @@ from typing import Any, NoReturn, TYPE_CHECKING
from flask import current_app as app, g, has_app_context
from flask_babel import gettext as __
from jinja2.exceptions import TemplateError
from superset import db
from superset.errors import ErrorLevel, SupersetError, SupersetErrorType
@@ -75,6 +76,7 @@ from superset.exceptions import (
SupersetErrorException,
SupersetParseError,
SupersetSecurityException,
SupersetTemplateException,
SupersetTimeoutException,
)
from superset.extensions import cache_manager
@@ -751,6 +753,7 @@ class SQLExecutor:
:param sql: SQL string potentially containing Jinja2 templates
:param template_params: Parameters to pass to the template
:returns: Rendered SQL string
:raises SupersetTemplateException: if the template fails to render
"""
if template_params is None:
return sql
@@ -758,7 +761,10 @@ class SQLExecutor:
from superset.jinja_context import get_template_processor
tp = get_template_processor(database=self.database)
return tp.process_template(sql, **template_params)
try:
return tp.process_template(sql, **template_params)
except TemplateError as ex:
raise SupersetTemplateException(str(ex)) from ex
def _apply_limit_to_script(self, script: SQLScript, opts: QueryOptions) -> None:
"""
@@ -44,6 +44,7 @@ from superset_core.queries.types import (
)
from superset.models.core import Database
from tests.unit_tests.conftest import with_feature_flags
# Note: database, database_with_dml, mock_db_session fixtures and
# mock_query_execution helper are imported from conftest.py
@@ -789,6 +790,42 @@ def test_execute_async_dml_without_permission_raises(
database.execute_async("INSERT INTO users (name) VALUES ('test')")
@with_feature_flags(ENABLE_TEMPLATE_PROCESSING=True)
def test_execute_async_undefined_template_var_raises_superset_template_exception(
mocker: MockerFixture, database: Database, app_context: None
) -> None:
"""A Jinja template referencing an undefined variable (not called as a
function) must not leak a raw ``jinja2.exceptions.UndefinedError`` out of
``execute_async`` - it should surface as ``SupersetTemplateException``."""
from superset.exceptions import SupersetTemplateException
mocker.patch.dict(
current_app.config, {"SQL_QUERY_MUTATOR": None, "SQLLAB_TIMEOUT": 30}
)
options = QueryOptions(template_params={"foo": "bar"})
with pytest.raises(SupersetTemplateException):
database.execute_async("SELECT {{ missing_var[0] }}", options=options)
@with_feature_flags(ENABLE_TEMPLATE_PROCESSING=True)
def test_execute_sync_undefined_template_var_returns_failed_result(
mocker: MockerFixture, database: Database, app_context: None
) -> None:
"""The sync ``execute`` path's broad ``except Exception`` still catches the
template rendering failure and returns a FAILED ``QueryResult``, unchanged
by the ``_render_sql_template`` fix."""
mocker.patch.dict(
current_app.config, {"SQL_QUERY_MUTATOR": None, "SQLLAB_TIMEOUT": 30}
)
options = QueryOptions(template_params={"foo": "bar"})
result = database.execute("SELECT {{ missing_var[0] }}", options=options)
assert result.status == QueryStatus.FAILED
def test_async_handle_get_status(
mocker: MockerFixture,
database: Database,