mirror of
https://github.com/apache/superset.git
synced 2026-05-07 17:04:58 +00:00
fix(time-comparison): use chart row_limit instead of instance config in offset queries (#39490)
This commit is contained in:
@@ -700,6 +700,233 @@ def test_processing_time_offsets_date_range_enabled(processor):
|
||||
assert isinstance(result["cache_keys"], list)
|
||||
|
||||
|
||||
def test_processing_time_offsets_uses_chart_row_limit(processor):
|
||||
"""Offset subquery inherits the chart's row_limit when one is set."""
|
||||
from superset.common.query_object import QueryObject
|
||||
from superset.models.helpers import ExploreMixin
|
||||
|
||||
processor._qc_datasource.processing_time_offsets = (
|
||||
ExploreMixin.processing_time_offsets.__get__(processor._qc_datasource)
|
||||
)
|
||||
|
||||
df = pd.DataFrame({"__timestamp": ["1990-01-01"], "sum__num": [100]})
|
||||
|
||||
query_object = QueryObject(
|
||||
datasource=MagicMock(),
|
||||
granularity="ds",
|
||||
columns=[],
|
||||
metrics=["sum__num"],
|
||||
is_timeseries=True,
|
||||
row_limit=100,
|
||||
row_offset=0,
|
||||
time_offsets=["1 year ago"],
|
||||
filters=[
|
||||
{
|
||||
"col": "ds",
|
||||
"op": "TEMPORAL_RANGE",
|
||||
"val": "1990-01-01 : 1991-01-01",
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
captured: list[dict[str, Any]] = []
|
||||
|
||||
def fake_query(dct: dict[str, Any]) -> MagicMock:
|
||||
captured.append(dct)
|
||||
result = MagicMock()
|
||||
result.df = pd.DataFrame()
|
||||
result.query = "SELECT 1"
|
||||
return result
|
||||
|
||||
processor._qc_datasource.query = fake_query
|
||||
processor._qc_datasource.normalize_df = MagicMock(return_value=pd.DataFrame())
|
||||
|
||||
with (
|
||||
patch(
|
||||
"superset.models.helpers.get_since_until_from_query_object",
|
||||
return_value=(pd.Timestamp("1990-01-01"), pd.Timestamp("1991-01-01")),
|
||||
),
|
||||
patch(
|
||||
"superset.common.utils.query_cache_manager.QueryCacheManager"
|
||||
) as mock_cache_manager,
|
||||
patch.object(
|
||||
processor._qc_datasource,
|
||||
"get_time_grain",
|
||||
return_value=None,
|
||||
),
|
||||
patch.object(
|
||||
processor._qc_datasource,
|
||||
"join_offset_dfs",
|
||||
return_value=df,
|
||||
),
|
||||
):
|
||||
mock_cache = MagicMock()
|
||||
mock_cache.is_loaded = False
|
||||
mock_cache_manager.get.return_value = mock_cache
|
||||
|
||||
processor._qc_datasource.processing_time_offsets(
|
||||
df, query_object, None, None, False
|
||||
)
|
||||
|
||||
assert len(captured) == 1
|
||||
assert captured[0]["row_limit"] == 100
|
||||
assert captured[0]["row_offset"] == 0
|
||||
|
||||
|
||||
def test_processing_time_offsets_row_offset_extends_window(processor):
|
||||
"""Offset subquery limit covers the main query's window (row_limit + row_offset).
|
||||
|
||||
When the chart has pagination (row_offset > 0), fetching only row_limit rows
|
||||
in the offset period would likely miss the dimensions present in the main
|
||||
query's page, yielding null comparison values. The subquery instead drops
|
||||
row_offset and widens row_limit to cover the full window.
|
||||
"""
|
||||
from superset.common.query_object import QueryObject
|
||||
from superset.models.helpers import ExploreMixin
|
||||
|
||||
processor._qc_datasource.processing_time_offsets = (
|
||||
ExploreMixin.processing_time_offsets.__get__(processor._qc_datasource)
|
||||
)
|
||||
|
||||
df = pd.DataFrame({"__timestamp": ["1990-01-01"], "sum__num": [100]})
|
||||
|
||||
query_object = QueryObject(
|
||||
datasource=MagicMock(),
|
||||
granularity="ds",
|
||||
columns=[],
|
||||
metrics=["sum__num"],
|
||||
is_timeseries=True,
|
||||
row_limit=100,
|
||||
row_offset=10,
|
||||
time_offsets=["1 year ago"],
|
||||
filters=[
|
||||
{
|
||||
"col": "ds",
|
||||
"op": "TEMPORAL_RANGE",
|
||||
"val": "1990-01-01 : 1991-01-01",
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
captured: list[dict[str, Any]] = []
|
||||
|
||||
def fake_query(dct: dict[str, Any]) -> MagicMock:
|
||||
captured.append(dct)
|
||||
result = MagicMock()
|
||||
result.df = pd.DataFrame()
|
||||
result.query = "SELECT 1"
|
||||
return result
|
||||
|
||||
processor._qc_datasource.query = fake_query
|
||||
processor._qc_datasource.normalize_df = MagicMock(return_value=pd.DataFrame())
|
||||
|
||||
with (
|
||||
patch(
|
||||
"superset.models.helpers.get_since_until_from_query_object",
|
||||
return_value=(pd.Timestamp("1990-01-01"), pd.Timestamp("1991-01-01")),
|
||||
),
|
||||
patch(
|
||||
"superset.common.utils.query_cache_manager.QueryCacheManager"
|
||||
) as mock_cache_manager,
|
||||
patch.object(
|
||||
processor._qc_datasource,
|
||||
"get_time_grain",
|
||||
return_value=None,
|
||||
),
|
||||
patch.object(
|
||||
processor._qc_datasource,
|
||||
"join_offset_dfs",
|
||||
return_value=df,
|
||||
),
|
||||
):
|
||||
mock_cache = MagicMock()
|
||||
mock_cache.is_loaded = False
|
||||
mock_cache_manager.get.return_value = mock_cache
|
||||
|
||||
processor._qc_datasource.processing_time_offsets(
|
||||
df, query_object, None, None, False
|
||||
)
|
||||
|
||||
assert len(captured) == 1
|
||||
assert captured[0]["row_limit"] == 110
|
||||
assert captured[0]["row_offset"] == 0
|
||||
|
||||
|
||||
def test_processing_time_offsets_falls_back_to_config_row_limit(processor):
|
||||
"""Offset subquery uses app config ROW_LIMIT when chart has offset but no limit."""
|
||||
from superset.common.query_object import QueryObject
|
||||
from superset.models.helpers import ExploreMixin
|
||||
|
||||
processor._qc_datasource.processing_time_offsets = (
|
||||
ExploreMixin.processing_time_offsets.__get__(processor._qc_datasource)
|
||||
)
|
||||
|
||||
df = pd.DataFrame({"__timestamp": ["1990-01-01"], "sum__num": [100]})
|
||||
|
||||
query_object = QueryObject(
|
||||
datasource=MagicMock(),
|
||||
granularity="ds",
|
||||
columns=[],
|
||||
metrics=["sum__num"],
|
||||
is_timeseries=True,
|
||||
row_limit=None,
|
||||
row_offset=10,
|
||||
time_offsets=["1 year ago"],
|
||||
filters=[
|
||||
{
|
||||
"col": "ds",
|
||||
"op": "TEMPORAL_RANGE",
|
||||
"val": "1990-01-01 : 1991-01-01",
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
captured: list[dict[str, Any]] = []
|
||||
|
||||
def fake_query(dct: dict[str, Any]) -> MagicMock:
|
||||
captured.append(dct)
|
||||
result = MagicMock()
|
||||
result.df = pd.DataFrame()
|
||||
result.query = "SELECT 1"
|
||||
return result
|
||||
|
||||
processor._qc_datasource.query = fake_query
|
||||
processor._qc_datasource.normalize_df = MagicMock(return_value=pd.DataFrame())
|
||||
|
||||
with (
|
||||
patch(
|
||||
"superset.models.helpers.get_since_until_from_query_object",
|
||||
return_value=(pd.Timestamp("1990-01-01"), pd.Timestamp("1991-01-01")),
|
||||
),
|
||||
patch(
|
||||
"superset.common.utils.query_cache_manager.QueryCacheManager"
|
||||
) as mock_cache_manager,
|
||||
patch.object(
|
||||
processor._qc_datasource,
|
||||
"get_time_grain",
|
||||
return_value=None,
|
||||
),
|
||||
patch.object(
|
||||
processor._qc_datasource,
|
||||
"join_offset_dfs",
|
||||
return_value=df,
|
||||
),
|
||||
patch("superset.models.helpers.app") as mock_app,
|
||||
):
|
||||
mock_app.config = {"ROW_LIMIT": 4242}
|
||||
mock_cache = MagicMock()
|
||||
mock_cache.is_loaded = False
|
||||
mock_cache_manager.get.return_value = mock_cache
|
||||
|
||||
processor._qc_datasource.processing_time_offsets(
|
||||
df, query_object, None, None, False
|
||||
)
|
||||
|
||||
assert len(captured) == 1
|
||||
assert captured[0]["row_limit"] == 4242
|
||||
assert captured[0]["row_offset"] == 0
|
||||
|
||||
|
||||
def test_ensure_totals_available_updates_cache_values():
|
||||
"""
|
||||
Test that ensure_totals_available() updates the query objects AND
|
||||
|
||||
Reference in New Issue
Block a user