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/06 20:37:36 UTC

(superset) branch master updated: fix(query-context): match an adhoc granularity_sqla by its expression (#44773)

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 1473d481345 fix(query-context): match an adhoc granularity_sqla by its expression (#44773)
1473d481345 is described below

commit 1473d4813458b2014571be6969a2a7e26e59a34c
Author: Trakshan Mishra <43...@users.noreply.github.com>
AuthorDate: Wed Oct 7 02:07:29 2026 +0530

    fix(query-context): match an adhoc granularity_sqla by its expression (#44773)
---
 superset/common/query_context_factory.py           | 18 +++++++---
 .../common/test_query_context_factory.py           | 40 ++++++++++++++++++++++
 2 files changed, 53 insertions(+), 5 deletions(-)

diff --git a/superset/common/query_context_factory.py b/superset/common/query_context_factory.py
index 825f3a73cfb..31affea616b 100644
--- a/superset/common/query_context_factory.py
+++ b/superset/common/query_context_factory.py
@@ -293,17 +293,25 @@ class QueryContextFactory:  # pylint: disable=too-few-public-methods
             # then the dataset's main datetime column, to match those consumers.
             # Candidates are matched against the dataset's
             # temporal columns so one that has since been dropped, or is no
-            # longer temporal, is ignored.
+            # longer temporal, is ignored. ``granularity_sqla`` can also hold an
+            # adhoc column (the viz migrations in ``migrate_viz/base.py`` handle
+            # that shape). It is matched by its ``sqlExpression``, since a dict
+            # cannot be looked up in the ``temporal_columns`` set.
             candidates = (
-                (form_data or {}).get("granularity"),
-                (form_data or {}).get("granularity_sqla"),
-                getattr(datasource, "main_dttm_col", None),
+                candidate.get("sqlExpression")
+                if isinstance(candidate, dict)
+                else candidate
+                for candidate in (
+                    (form_data or {}).get("granularity"),
+                    (form_data or {}).get("granularity_sqla"),
+                    getattr(datasource, "main_dttm_col", None),
+                )
             )
             query_object.granularity = next(
                 (
                     candidate
                     for candidate in candidates
-                    if candidate in temporal_columns
+                    if isinstance(candidate, str) and candidate in temporal_columns
                 ),
                 None,
             )
diff --git a/tests/unit_tests/common/test_query_context_factory.py b/tests/unit_tests/common/test_query_context_factory.py
index 44d1aaef4e8..bdbcae3dd58 100644
--- a/tests/unit_tests/common/test_query_context_factory.py
+++ b/tests/unit_tests/common/test_query_context_factory.py
@@ -674,6 +674,46 @@ class TestQueryContextFactory:
 
         assert query_object.granularity == "ds"
 
+    @pytest.mark.parametrize(
+        ("granularity_sqla", "expected"),
+        [
+            ({"label": "ds", "sqlExpression": "ds", "expressionType": "SQL"}, "ds"),
+            (
+                {
+                    "label": "day",
+                    "sqlExpression": "DATE_TRUNC('day', ds)",
+                    "expressionType": "SQL",
+                },
+                "other_dttm",
+            ),
+        ],
+    )
+    def test_apply_granularity_matches_adhoc_legacy_granularity_sqla(
+        self, granularity_sqla: dict[str, str], expected: str
+    ) -> None:
+        """An adhoc legacy time column is matched by its SQL expression."""
+        query_object = Mock(spec=QueryObject)
+        query_object.granularity = None
+        query_object.is_timeseries = True
+        query_object.columns = ["ds"]
+        query_object.post_processing = []
+        query_object.filter = []
+
+        datasource = Mock()
+        datasource.main_dttm_col = "other_dttm"
+        datasource.columns = [
+            {"column_name": "ds", "is_dttm": True},
+            {"column_name": "other_dttm", "is_dttm": True},
+        ]
+
+        self.factory._apply_granularity(
+            query_object,
+            {"granularity_sqla": granularity_sqla},
+            datasource,
+        )
+
+        assert query_object.granularity == expected
+
     def test_apply_granularity_keeps_explicit_granularity(self) -> None:
         """An overridden Time Column wins over the legacy form data key."""
         query_object = Mock(spec=QueryObject)