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)