Compare commits

...
Author SHA1 Message Date
sadpandajoe 648e6712a9 test(explore): cover samples row limit edge cases 2026-08-14 00:30:21 +00:00
sadpandajoe eb75ed356a fix(explore): honor requested samples row limit up to the validated maximum 2026-08-14 00:30:21 +00:00
sadpandajoeandClaude Opus 4.8 315ab16a31 test(explore): add regression guard for samples row-limit cap
The Explore Data panel's Samples-tab row-limit selector offers 5k/10k
options and SamplesRequestSchema accepts per_page up to 10000, but
get_limit_clause() silently resets any per_page above SAMPLES_ROW_LIMIT
(default 1000) back down to it. A user who selects 5k/10k therefore
receives at most 1000 rows with no indication the limit was overridden.

Add a focused unit test asserting get_limit_clause honors a per_page the
samples endpoint accepts. It fails today (returns 1000) and will pass
once the samples path stops silently clamping accepted per_page values.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 00:30:21 +00:00
3 changed files with 51 additions and 2 deletions
+2
View File
@@ -24,6 +24,8 @@ assists people when migrating to a new version.
## Next
- `SAMPLES_ROW_LIMIT` is now the default for `/datasource/samples` requests without a valid explicit `per_page`, rather than a hard per-request ceiling; explicit limits are honored up to the existing global row-limit ceiling, matching `/chart/data` SAMPLES requests.
### OAuth2 database callback metrics include their outcome
The unqualified `DatabaseRestApi.oauth2` StatsD counter has been replaced with
+8 -2
View File
@@ -28,7 +28,11 @@ from superset.common.query_context_factory import QueryContextFactory
from superset.common.utils.query_cache_manager import QueryCacheManager
from superset.constants import CacheRegion
from superset.daos.datasource import DatasourceDAO
from superset.utils.core import extract_dataframe_dtypes, QueryStatus
from superset.utils.core import (
apply_max_row_limit,
extract_dataframe_dtypes,
QueryStatus,
)
from superset.views.datasource.schemas import SamplesPayloadSchema
if TYPE_CHECKING:
@@ -45,9 +49,11 @@ def get_limit_clause(page: Optional[int], per_page: Optional[int]) -> dict[str,
if isinstance(page, int) and isinstance(per_page, int):
limit = int(per_page)
if limit < 0 or limit > samples_row_limit:
if limit < 0:
# reset limit value if input is invalid
limit = samples_row_limit
elif limit:
limit = apply_max_row_limit(limit)
offset = max((int(page) - 1) * limit, 0)
@@ -22,6 +22,7 @@ import pytest
from superset.errors import ErrorLevel, SupersetError, SupersetErrorType
from superset.exceptions import SupersetSecurityException
from superset.views.datasource.utils import get_limit_clause
@patch("superset.views.datasource.utils.get_limit_clause")
@@ -215,3 +216,43 @@ def test_get_samples_count_star_access_denied(mock_get_limit_clause: MagicMock):
mock_samples_context.raise_for_access.assert_called_once()
# Verify count context was also checked
mock_count_context.raise_for_access.assert_called_once()
@pytest.mark.parametrize("per_page", [5000, 10000])
def test_get_limit_clause_honors_per_page_above_samples_row_limit(
per_page: int,
) -> None:
"""Regression guard: the Explore Data panel "Samples" tab silently caps at
``SAMPLES_ROW_LIMIT`` (config default 1000).
The samples row-limit dropdown offers 5k/10k options and the samples
endpoint's ``SamplesRequestSchema`` accepts ``per_page`` up to 10000, yet
``get_limit_clause`` resets any ``per_page`` above ``SAMPLES_ROW_LIMIT``
back down to it. A user who selects 5k/10k therefore silently receives at
most 1000 rows, with no signal that the requested limit was overridden.
The rows a user is allowed to request and the rows actually returned must
stay consistent: a ``per_page`` the endpoint accepts must not be silently
reduced below the request.
"""
assert get_limit_clause(page=1, per_page=per_page) == {
"row_offset": 0,
"row_limit": per_page,
}
@pytest.mark.parametrize(
"per_page,expected_row_limit",
[
(0, 0),
(-1, 1000),
],
)
def test_get_limit_clause_preserves_zero_and_negative_per_page(
per_page: int,
expected_row_limit: int,
) -> None:
assert get_limit_clause(page=1, per_page=per_page) == {
"row_offset": 0,
"row_limit": expected_row_limit,
}