From c00e9ce91513d5ff6176c03eaacba1d076fbd232 Mon Sep 17 00:00:00 2001 From: Tim Van Steenburgh Date: Thu, 2 Jul 2026 18:10:32 +0000 Subject: [PATCH] fix: populate dropdown cache on demand for limited-visibility users (ENG-7919) [ENG-7919](https://stacklet.atlassian.net/browse/ENG-7919) ### what When validating a query-backed dropdown parameter, run the dropdown query on demand (`run_if_not_cached=True`) if it isn't already cached for the requesting user's db_role, instead of failing. ### why Limited-visibility (SSO/RLS) users each connect under a distinct per-user db_role, and cached query results are row-level-isolated by db_role. The parameter-validation path called `dropdown_values(..., run_if_not_cached=False)`, so a user whose db_role had no cached dropdown result raised `DropdownSubqueryError` ("cached results not found"), which was swallowed into a misleading `InvalidParameterError` ("parameter values are incompatible with their definitions"). Every widget on a dashboard with query-backed dropdowns then errored for that user. The two dropdown-fetch endpoints already pass `run_if_not_cached=True`; only the validation call site did not. The manual workaround (each user opening the dropdown queries and clicking Refresh) populated their own db_role's cache, but had to be repeated per user. This makes that automatic, per db_role, at validation time. The on-demand subquery runs as the user, so RLS scopes the cached result correctly. ### testing Added a regression unit test asserting the query-parameter validator invokes `dropdown_values` with `run_if_not_cached=True`. Ran the module via `just backend-test tests/models/test_parameterized_query.py` -> 38 passed. ### docs No user-facing docs needed. Co-Authored-By: Claude Opus 4.8 (1M context) --- redash/models/parameterized_query.py | 7 ++++++- tests/models/test_parameterized_query.py | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/redash/models/parameterized_query.py b/redash/models/parameterized_query.py index 102294e39b..07d609226b 100644 --- a/redash/models/parameterized_query.py +++ b/redash/models/parameterized_query.py @@ -213,7 +213,12 @@ def _valid(self, name, value, user, query_stack=None): "enum": lambda value: _is_value_within_options(value, enum_options, allow_multiple_values), "query": lambda value: _is_value_within_options( value, - [v["value"] for v in dropdown_values(query_id, self.org, user, query_stack=query_stack)], + [ + v["value"] + for v in dropdown_values( + query_id, self.org, user, run_if_not_cached=True, query_stack=query_stack + ) + ], allow_multiple_values, ), "date": _is_date, diff --git a/tests/models/test_parameterized_query.py b/tests/models/test_parameterized_query.py index 2896899f18..2291f3f256 100644 --- a/tests/models/test_parameterized_query.py +++ b/tests/models/test_parameterized_query.py @@ -227,6 +227,23 @@ def test_validates_query_parameters(self, _): self.assertEqual("foo baz", query.text) + @patch( + "redash.models.parameterized_query.dropdown_values", + return_value=[{"value": "baz"}], + ) + def test_query_validation_populates_dropdown_cache_on_demand(self, dropdown_values_mock): + # Validating a query-backed parameter must run the dropdown query + # on-demand when it isn't cached for the requesting user's db_role, + # rather than failing (ENG-7919). Otherwise a limited-visibility user + # whose db_role has no cached dropdown result gets a spurious + # InvalidParameterError. + schema = [{"name": "bar", "type": "query", "queryId": 1}] + query = ParameterizedQuery("foo {{bar}}", schema) + + query.apply({"bar": "baz"}, None) + + self.assertTrue(dropdown_values_mock.call_args.kwargs["run_if_not_cached"]) + def test_raises_on_invalid_date_range_parameters(self): schema = [{"name": "bar", "type": "date-range"}] query = ParameterizedQuery("foo", schema)