You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by rb...@apache.org on 2026/10/07 17:19:22 UTC

(superset) branch master updated: fix(semantic): reject SQL clauses on semantic views (#44899)

This is an automated email from the ASF dual-hosted git repository.

rebenitez1802 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new fca6bdad57b fix(semantic): reject SQL clauses on semantic views (#44899)
fca6bdad57b is described below

commit fca6bdad57bc25361576dc9c49d5e9081bed1803
Author: Mike Bridge <mi...@preset.io>
AuthorDate: Wed Oct 7 11:19:14 2026 -0600

    fix(semantic): reject SQL clauses on semantic views (#44899)
---
 .../using-superset/semantic-member-selections.mdx  |  4 ++++
 superset/common/query_object.py                    | 15 +++++++++++++++
 superset/semantic_layers/mapper.py                 | 14 ++++++++++----
 superset/translations/messages.pot                 |  3 +++
 tests/unit_tests/queries/query_object_test.py      | 22 ++++++++++++++++++++--
 5 files changed, 52 insertions(+), 6 deletions(-)

diff --git a/docs/docs/using-superset/semantic-member-selections.mdx b/docs/docs/using-superset/semantic-member-selections.mdx
index b57fc56688a..e3e5a75288e 100644
--- a/docs/docs/using-superset/semantic-member-selections.mdx
+++ b/docs/docs/using-superset/semantic-member-selections.mdx
@@ -56,6 +56,10 @@ MCP `list_metrics`). The server never adds a missing marker from metadata.
 Explore query controls remain unavailable until explicit field initialization,
 so legacy filter subjects cannot trigger value suggestions before reselection.
 
+Custom SQL `WHERE` and `HAVING` clauses are not supported on semantic views.
+Remove those clauses or use semantic dimension filters instead. Chart-data API
+requests with these clauses return HTTP 400 before querying the provider.
+
 For versioned semantic views, Matrixify **All** mode with **A to Z** or **Z to A**
 sorting cannot populate or refresh dimension values from suggestions. Existing
 saved values are preserved. For a new selection, switch to **Members** and enter
diff --git a/superset/common/query_object.py b/superset/common/query_object.py
index 51e4f0290bb..bd354205910 100644
--- a/superset/common/query_object.py
+++ b/superset/common/query_object.py
@@ -411,6 +411,7 @@ class QueryObject:  # pylint: disable=too-many-instance-attributes
     ) -> QueryObjectValidationError | None:
         """Validate query object"""
         try:
+            self._validate_semantic_view_extras()
             if self.datasource and self.datasource.type == "semantic_view":
                 implementation: SemanticViewABC = cast(
                     "SemanticView", self.datasource
@@ -454,6 +455,20 @@ class QueryObject:  # pylint: disable=too-many-instance-attributes
                 raise
             return ex
 
+    def _validate_semantic_view_extras(self) -> None:
+        """Reject unsupported SQL clauses before resolving a semantic provider."""
+        if (
+            self.datasource
+            and self.datasource.type == "semantic_view"
+            and (self.extras.get("where") or self.extras.get("having"))
+        ):
+            raise QueryObjectValidationError(
+                _(
+                    "SQL WHERE/HAVING expressions are not supported for semantic "
+                    "views. Remove the SQL clause or use a semantic dimension filter."
+                )
+            )
+
     def _validate_no_have_duplicate_labels(self) -> None:
         all_labels = self.metric_names + self.column_names
         if len(set(all_labels)) < len(all_labels):
diff --git a/superset/semantic_layers/mapper.py b/superset/semantic_layers/mapper.py
index 5e7973f738d..303e73f17da 100644
--- a/superset/semantic_layers/mapper.py
+++ b/superset/semantic_layers/mapper.py
@@ -499,11 +499,17 @@ def _get_filters_from_query_object(
 
 def _get_filters_from_extras(extras: dict[str, Any]) -> set[Filter]:
     """
-    Extract filters from the extras dict.
+    Convert SQL extras into ADHOC filters for direct mapper callers.
+
+    The chart-data path rejects non-empty SQL WHERE/HAVING extras for semantic
+    views before reaching this mapper. This conversion is therefore unreachable
+    through that validated path; it does not establish provider support for
+    arbitrary SQL predicates. Exposing it there would require a provider-specific
+    sanitization contract.
 
     The extras dict can contain various keys that affect query behavior:
 
-    Supported keys (converted to filters):
+    Direct mapper inputs (converted to filters, not chart-data support):
     - "where": SQL WHERE clause expression (e.g., "customer_id > 100")
     - "having": SQL HAVING clause expression (e.g., "SUM(sales) > 1000")
 
@@ -511,8 +517,8 @@ def _get_filters_from_extras(extras: dict[str, Any]) -> set[Filter]:
     - "time_grain_sqla": Time granularity (e.g., "P1D", "PT1H")
       Handled in _convert_time_grain() and used for dimension grain matching
 
-    Note: The WHERE and HAVING clauses from extras are SQL expressions that
-    are passed through as-is to the semantic layer as adhoc Filter objects.
+    For direct mapper calls, WHERE and HAVING text is passed unchanged into
+    ADHOC Filter objects; this helper does not sanitize it.
     """
     filters: set[Filter] = set()
 
diff --git a/superset/translations/messages.pot b/superset/translations/messages.pot
index d9e722be8c6..a0711b09d89 100644
--- a/superset/translations/messages.pot
+++ b/superset/translations/messages.pot
@@ -11256,6 +11256,9 @@ msgstr ""
 msgid "SQL Query"
 msgstr ""
 
+msgid "SQL WHERE/HAVING expressions are not supported for semantic views. Remove the SQL clause or use a semantic dimension filter."
+msgstr ""
+
 msgid "SQL expression"
 msgstr ""
 
diff --git a/tests/unit_tests/queries/query_object_test.py b/tests/unit_tests/queries/query_object_test.py
index c1d4ab70616..616658f49b8 100644
--- a/tests/unit_tests/queries/query_object_test.py
+++ b/tests/unit_tests/queries/query_object_test.py
@@ -16,7 +16,7 @@
 # under the License.
 from contextlib import contextmanager
 from datetime import datetime
-from unittest.mock import call, patch
+from unittest.mock import call, patch, PropertyMock
 
 import pandas as pd
 import pytest
@@ -25,8 +25,9 @@ from pandas import DataFrame
 
 from superset.common.query_object import QueryObject
 from superset.connectors.sqla.models import SqlaTable
-from superset.exceptions import InvalidPostProcessingError
+from superset.exceptions import InvalidPostProcessingError, QueryObjectValidationError
 from superset.models.core import Database
+from superset.semantic_layers.models import SemanticView
 from superset.superset_typing import Metric
 from superset.utils import pandas_postprocessing
 from superset.utils.core import override_user
@@ -90,6 +91,23 @@ def test_default_query_object_to_dict():
     }
 
 
+@pytest.mark.parametrize("clause", ["where", "having"])
+def test_semantic_view_rejects_custom_sql_extras(clause: str) -> None:
+    """Unsupported SQL clauses fail before semantic provider access."""
+    view: SemanticView = SemanticView(name="Orders")
+    provider_access: PropertyMock = PropertyMock(
+        side_effect=AssertionError("provider accessed before SQL extras validation")
+    )
+    query: QueryObject = QueryObject(datasource=view, extras={clause: "amount > 10"})
+
+    with patch.object(SemanticView, "implementation", provider_access):
+        with pytest.raises(
+            QueryObjectValidationError, match="SQL WHERE/HAVING.*semantic views"
+        ):
+            query.validate()
+    provider_access.assert_not_called()
+
+
 def test_exec_post_processing_rejects_unsupported_operation():
     """
     An unknown operation reports itself in the error message rather than failing