You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by be...@apache.org on 2026/06/29 21:59:41 UTC
(superset) 01/01: fix(semantic layers): time comparison with 1 row
This is an automated email from the ASF dual-hosted git repository.
betodealmeida pushed a commit to branch sl-fix-1-row-join
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 5dfcdfad071c69f20054e7061ce97d6ba03e902d
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Mon Jun 29 17:59:19 2026 -0400
fix(semantic layers): time comparison with 1 row
---
superset/semantic_layers/mapper.py | 12 +++++++
tests/unit_tests/semantic_layers/mapper_test.py | 47 +++++++++++++++++++++++++
2 files changed, 59 insertions(+)
diff --git a/superset/semantic_layers/mapper.py b/superset/semantic_layers/mapper.py
index e40629c3ce2..a966197a97a 100644
--- a/superset/semantic_layers/mapper.py
+++ b/superset/semantic_layers/mapper.py
@@ -165,6 +165,18 @@ def get_results(query_object: QueryObject) -> QueryResult:
for metric in metric_names:
offset_col_name = TIME_COMPARISON.join([metric, time_offset])
main_df[offset_col_name] = np.nan
+ elif not join_keys:
+ # No dimensions to join on — this is an aggregate-only query
+ # (e.g. ``metrics: ["Orders Count"]`` with empty ``columns``),
+ # which produces a single-row DataFrame. ``pandas.merge`` on an
+ # empty ``on=`` list crashes with ``IndexError`` deep in the
+ # join-indexer code, so we lift the offset metric values from
+ # the first row of ``offset_df`` straight onto ``main_df``.
+ for metric in metric_names:
+ offset_col_name = TIME_COMPARISON.join([metric, time_offset])
+ main_df[offset_col_name] = (
+ offset_df[metric].iloc[0] if metric in offset_df else np.nan
+ )
else:
# Rename metric columns with time offset suffix
# Format: "{metric_name}__{time_offset}"
diff --git a/tests/unit_tests/semantic_layers/mapper_test.py b/tests/unit_tests/semantic_layers/mapper_test.py
index 0cbb59c8430..cce4e6c984d 100644
--- a/tests/unit_tests/semantic_layers/mapper_test.py
+++ b/tests/unit_tests/semantic_layers/mapper_test.py
@@ -1507,6 +1507,53 @@ def test_get_results_with_multiple_time_offsets(
pd.testing.assert_frame_equal(result.df, expected_df)
+def test_get_results_with_time_offset_and_no_dimensions(
+ mock_datasource: MagicMock,
+ mocker: MockerFixture,
+) -> None:
+ """
+ Aggregate-only queries (no dimensions) with a time offset should not
+ crash. Regression for ``IndexError: list index out of range`` inside
+ pandas, which fired when ``main_df.merge(..., on=[])`` was attempted
+ because the empty ``columns`` list left no join keys.
+ """
+ # Aggregate without group-by produces a single row per query.
+ main_df = pd.DataFrame({"total_sales": [1000.0]})
+ offset_df = pd.DataFrame({"total_sales": [950.0]})
+
+ mock_main_result = SemanticResult(
+ requests=[SemanticRequest(type="SQL", definition="SELECT SUM(amount)")],
+ results=pa.Table.from_pandas(main_df.copy()),
+ )
+ mock_offset_result = SemanticResult(
+ requests=[SemanticRequest(type="SQL", definition="SELECT SUM(amount)")],
+ results=pa.Table.from_pandas(offset_df.copy()),
+ )
+ mock_datasource.implementation.get_table = mocker.Mock(
+ side_effect=[mock_main_result, mock_offset_result]
+ )
+
+ query_object = ValidatedQueryObject(
+ datasource=mock_datasource,
+ from_dttm=datetime(2025, 10, 15),
+ to_dttm=datetime(2025, 10, 22),
+ metrics=["total_sales"],
+ columns=[],
+ granularity="order_date",
+ time_offsets=["1 day ago"],
+ )
+
+ # No exception, and the offset value rides alongside the main one.
+ result = get_results(query_object)
+ expected_df = pd.DataFrame(
+ {
+ "total_sales": [1000.0],
+ "total_sales__1 day ago": [950.0],
+ }
+ )
+ pd.testing.assert_frame_equal(result.df, expected_df)
+
+
def test_get_results_with_empty_offset_result(
mock_datasource: MagicMock,
mocker: MockerFixture,