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

(superset) branch master updated: fix(semantic-layer): handle absent fetch-values predicate (#45539)

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

rusackas 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 7ff7f9048bf fix(semantic-layer): handle absent fetch-values predicate (#45539)
7ff7f9048bf is described below

commit 7ff7f9048bfdda992acb4f815d2d60ae00e0ab71
Author: Mike Bridge <mi...@preset.io>
AuthorDate: Wed Oct 21 18:12:49 2026 -0600

    fix(semantic-layer): handle absent fetch-values predicate (#45539)
    
    Co-authored-by: Mike Bridge <mi...@ext.preset.io>
---
 docs/developer_docs/extensions/semantic-filters.md |  4 ++
 superset/semantic_layers/models.py                 |  3 +
 .../semantic_layers/metric_filter_test.py          | 66 ++++++++++++++++++++++
 3 files changed, 73 insertions(+)

diff --git a/docs/developer_docs/extensions/semantic-filters.md b/docs/developer_docs/extensions/semantic-filters.md
index 907f9bf7cf0..a49f0235ae8 100644
--- a/docs/developer_docs/extensions/semantic-filters.md
+++ b/docs/developer_docs/extensions/semantic-filters.md
@@ -39,6 +39,10 @@ and variable-length tuples. SDK `FilterValues` also includes `None`.
 
 ## Provider compatibility
 
+Semantic views do not define a dataset-level SQL fetch-values predicate. Setting
+`apply_fetch_values_predicate: true` on a chart-data query is therefore a no-op for
+semantic views; explicit query filters still apply, including group-limit filters.
+
 `OrFilter` is mandatory core vocabulary, not an optional capability. Every
 provider must render groups for main queries, row counts and group-limit
 subqueries. Upgrade adapters before, or atomically with, the host/SDK upgrade;
diff --git a/superset/semantic_layers/models.py b/superset/semantic_layers/models.py
index 3675c44bf9c..9e946f7edcd 100644
--- a/superset/semantic_layers/models.py
+++ b/superset/semantic_layers/models.py
@@ -340,6 +340,9 @@ class SemanticView(AuditMixinNullable, Model):
     supports_samples: bool = False
     supports_drill_to_detail: bool = False
 
+    # Semantic views have no dataset-level SQL predicate for fetching filter values.
+    fetch_values_predicate: None = None
+
     # Use integer as the primary key for cross-database auto-increment
     # compatibility (sa.Identity() is not supported in MySQL or SQLite).
     # The uuid column is a secondary unique identifier used in URLs and perms.
diff --git a/tests/unit_tests/semantic_layers/metric_filter_test.py b/tests/unit_tests/semantic_layers/metric_filter_test.py
index 7760849d37a..b9ef50978ae 100644
--- a/tests/unit_tests/semantic_layers/metric_filter_test.py
+++ b/tests/unit_tests/semantic_layers/metric_filter_test.py
@@ -18,6 +18,7 @@
 """Non-dimension filters fail instead of returning silently unfiltered data."""
 
 from collections.abc import Callable
+from datetime import datetime
 from typing import Any
 from unittest.mock import MagicMock, PropertyMock
 
@@ -35,6 +36,7 @@ from superset_core.semantic_layers.types import (
     SemanticQuery,
     SemanticResult,
 )
+from superset_core.semantic_layers.view import SemanticViewFeature
 from werkzeug.test import TestResponse
 
 from superset.charts.data.dashboard_filter_context import apply_dashboard_filter_context
@@ -45,6 +47,7 @@ from superset.mcp_service.chart.chart_helpers import (
     merge_extra_form_data_filters_into_query,
 )
 from superset.semantic_layers.mapper import (
+    _get_group_limit_filters,
     map_query_object,
     validate_query_object,
     ValidatedQueryObject,
@@ -112,6 +115,69 @@ def _request(filters: list[QueryObjectFilterClause]) -> dict[str, Any]:
     }
 
 
+@pytest.mark.parametrize("group_limit", [False, True])
+def test_chart_data_fetch_values_predicate_without_configured_predicate(
+    client: FlaskClient,
+    full_api_access: None,
+    metric_view: tuple[SemanticView, MagicMock],
+    group_limit: bool,
+) -> None:
+    """An absent optional predicate must not fail or discard explicit filters."""
+    payload: dict[str, Any] = _request(
+        [{"col": "category", "op": "==", "val": "Books"}]
+    )
+    payload["queries"][0]["apply_fetch_values_predicate"] = True
+    if group_limit:
+        metric_view[1].features = frozenset({SemanticViewFeature.GROUP_LIMIT})
+        payload["queries"][0].update(
+            series_limit=1,
+            series_columns=["category"],
+        )
+
+    response: TestResponse = client.post("/api/v1/chart/data", json=payload)
+
+    assert response.status_code == 200, response.json
+    assert response.json is not None
+    assert response.json["result"][0]["data"] == [
+        {"category": "Books", "total_amount": 7}
+    ]
+    query: SemanticQuery = metric_view[1].get_table.call_args.args[0]
+    expected_filters: set[Filter] = {
+        Filter(
+            type=PredicateType.WHERE,
+            column=next(iter(metric_view[1].get_dimensions())),
+            operator=Operator.EQUALS,
+            value="Books",
+        )
+    }
+    assert query.filters == expected_filters
+    if group_limit:
+        assert query.group_limit is not None
+        assert query.group_limit.filters is None  # Inherit the main query filters.
+
+
+def test_group_limit_fetch_values_predicate_without_configured_predicate(
+    metric_view: tuple[SemanticView, MagicMock],
+) -> None:
+    """Separate inner bounds also tolerate the absent optional predicate."""
+    dimension: Dimension = next(iter(metric_view[1].get_dimensions()))
+    query: ValidatedQueryObject = ValidatedQueryObject(
+        datasource=metric_view[0],
+        apply_fetch_values_predicate=True,
+        inner_from_dttm=datetime(2025, 10, 1),
+        inner_to_dttm=datetime(2025, 10, 2),
+        filters=[{"col": "category", "op": "==", "val": "Books"}],
+    )
+    assert _get_group_limit_filters(query, {dimension.name: dimension}) == {
+        Filter(
+            type=PredicateType.WHERE,
+            column=dimension,
+            operator=Operator.EQUALS,
+            value="Books",
+        )
+    }
+
+
 @pytest.mark.parametrize(("column", "error"), REFUSED_CHART_FILTERS)
 @pytest.mark.parametrize("mixed", [False, True])
 def test_chart_data_rejects_non_dimension_filters(