fix(time-comparison): use chart row_limit instead of instance config in offset queries (#39490)

This commit is contained in:
jesperct
2026-05-01 20:24:59 -03:00
committed by GitHub
parent dbe26d81ce
commit d8dd2d99b3
3 changed files with 433 additions and 34 deletions

View File

@@ -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