mirror of
https://github.com/apache/superset.git
synced 2026-07-27 00:52:33 +00:00
feat(table/pivot-table): correct non-additive totals/subtotals via DB rollup [SIP-216] (#41184)
Co-authored-by: Superset Dev <dev@superset.apache.org> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Amin Ghadersohi <amin.ghadersohi@gmail.com>
This commit is contained in:
@@ -2151,3 +2151,108 @@ def test_raise_for_access_evaluates_access_before_validate():
|
||||
processor.raise_for_access()
|
||||
|
||||
query.validate.assert_not_called()
|
||||
|
||||
|
||||
def test_grouping_sets_fallback_handles_adhoc_and_physical_columns() -> None:
|
||||
"""
|
||||
The fallback used on engines without native GROUPING SETS support must
|
||||
build ``label_to_column`` from the same labels used to run each level's
|
||||
subquery, for both physical (string) and adhoc (dict) columns, and in the
|
||||
original column order. Otherwise an adhoc column ahead of a physical one
|
||||
in ``query_object.columns`` misaligns the mapping, and adhoc groupby
|
||||
columns referenced in ``grouping_sets`` are silently dropped.
|
||||
"""
|
||||
import copy
|
||||
from datetime import timedelta
|
||||
|
||||
from superset.common.query_object import QueryObject
|
||||
from superset.models.helpers import QueryResult
|
||||
from superset.superset_typing import AdhocColumn
|
||||
|
||||
adhoc_col: AdhocColumn = {
|
||||
"sqlExpression": "DATE_TRUNC('month', dttm)",
|
||||
"label": "month",
|
||||
}
|
||||
mock_datasource = MagicMock()
|
||||
|
||||
query_obj = QueryObject(
|
||||
datasource=mock_datasource,
|
||||
columns=[adhoc_col, "state"],
|
||||
grouping_sets=[["month", "state"], ["month"], []],
|
||||
)
|
||||
|
||||
processor = QueryContextProcessor(MagicMock())
|
||||
processor._qc_datasource = mock_datasource
|
||||
|
||||
captured_columns: list[list[Any]] = []
|
||||
|
||||
def fake_get_query_result(sub_query: QueryObject) -> QueryResult:
|
||||
captured_columns.append(copy.copy(sub_query.columns))
|
||||
return QueryResult(
|
||||
df=pd.DataFrame({"metric": [1]}),
|
||||
query="SELECT 1",
|
||||
duration=timedelta(seconds=0),
|
||||
)
|
||||
|
||||
mock_datasource.get_query_result.side_effect = fake_get_query_result
|
||||
|
||||
processor._grouping_sets_fallback(query_obj)
|
||||
|
||||
# Level ["month", "state"] must include both the adhoc and physical column,
|
||||
# each mapped to its own definition (not swapped).
|
||||
assert captured_columns[0] == [adhoc_col, "state"]
|
||||
# Level ["month"] must still include the adhoc column, not drop it.
|
||||
assert captured_columns[1] == [adhoc_col]
|
||||
# Grand total level has no groupby columns.
|
||||
assert captured_columns[2] == []
|
||||
|
||||
|
||||
def test_grouping_sets_fallback_applies_row_offset_once_globally() -> None:
|
||||
"""
|
||||
The native GROUPING SETS path applies `row_offset` exactly once, to the
|
||||
combined multi-level result (see the unconditional `qry.offset()` call in
|
||||
`models/helpers.py`). The fallback must match that: it must not apply the
|
||||
same offset independently to every per-level subquery, since that would
|
||||
apply it once per level (and can drop an entire low-row-count level, e.g.
|
||||
a single grand-total row, outright).
|
||||
"""
|
||||
from datetime import timedelta
|
||||
|
||||
from superset.common.query_object import QueryObject
|
||||
from superset.models.helpers import QueryResult
|
||||
|
||||
mock_datasource = MagicMock()
|
||||
|
||||
query_obj = QueryObject(
|
||||
datasource=mock_datasource,
|
||||
columns=["state"],
|
||||
grouping_sets=[["state"], []],
|
||||
row_offset=1,
|
||||
)
|
||||
|
||||
processor = QueryContextProcessor(MagicMock())
|
||||
processor._qc_datasource = mock_datasource
|
||||
|
||||
captured_offsets: list[int] = []
|
||||
|
||||
# Each level returns 2 rows regardless of the (should-be-ignored) offset,
|
||||
# emulating a real datasource that would otherwise apply row_offset itself.
|
||||
def fake_get_query_result(sub_query: QueryObject) -> QueryResult:
|
||||
captured_offsets.append(sub_query.row_offset)
|
||||
return QueryResult(
|
||||
df=pd.DataFrame({"state": ["CA", "NY"]})
|
||||
if sub_query.columns
|
||||
else pd.DataFrame({"state": ["total"]}),
|
||||
query="SELECT 1",
|
||||
duration=timedelta(seconds=0),
|
||||
)
|
||||
|
||||
mock_datasource.get_query_result.side_effect = fake_get_query_result
|
||||
|
||||
result = processor._grouping_sets_fallback(query_obj)
|
||||
|
||||
# Each per-level subquery must run unoffset...
|
||||
assert captured_offsets == [0, 0]
|
||||
# ...and the requested offset is applied exactly once, to the combined
|
||||
# result: 2 + 1 = 3 total rows in, minus an offset of 1 = 2 rows out.
|
||||
assert len(result.df) == 2
|
||||
|
||||
Reference in New Issue
Block a user