codeant-ai-for-open-source[bot] commented on code in PR #37516:
URL: https://github.com/apache/superset/pull/37516#discussion_r3571145897


##########
superset/commands/chart/data/get_data_command.py:
##########
@@ -25,49 +27,111 @@
     ChartDataQueryFailedError,
 )
 from superset.common.chart_data import ChartDataResultType
+from superset.common.chart_data_timing import (
+    CacheWriteOutcome,
+    chart_data_execution_scope,
+    ChartDataExecutionResult,
+    emit_query_timing,
+)
 from superset.common.query_context import QueryContext
-from superset.exceptions import CacheLoadError
+from superset.exceptions import CacheLoadError, QueryObjectValidationError
 
 logger = logging.getLogger(__name__)
 
 
+class ChartDataExecutionMode(str, Enum):
+    """Controls whether query data is returned or only persisted to cache."""
+
+    MATERIALIZE = "materialize"
+    CACHE_ONLY = "cache_only"
+
+
+@dataclass(frozen=True)
+class ChartDataExecutionOptions:
+    """Immutable options for one chart-data execution."""
+
+    mode: ChartDataExecutionMode = ChartDataExecutionMode.MATERIALIZE
+    force_cached: bool = False
+    cache_query_context: bool = False
+    require_cache_writes: bool = False
+
+
 class ChartDataCommand(BaseCommand):
     _query_context: QueryContext
 
     def __init__(self, query_context: QueryContext):
         self._query_context = query_context
 
     def run(self, **kwargs: Any) -> dict[str, Any]:
-        # caching is handled in query_context.get_df_payload
-        # (also evals `force` property)
-        cache_query_context = kwargs.get("cache", False)
-        force_cached = kwargs.get("force_cached", False)
-        try:
-            payload = self._query_context.get_payload(
-                cache_query_context=cache_query_context, 
force_cached=force_cached
-            )
-        except CacheLoadError as ex:
-            raise ChartDataCacheLoadError(ex.message) from ex
-
-        # Skip error check for query-only requests - errors are returned in 
payload
-        # This allows View Query modal to display validation errors
-        for query in payload["queries"]:
-            if (
-                query.get("error")
-                and self._query_context.result_type != 
ChartDataResultType.QUERY
-            ):
-                raise ChartDataQueryFailedError(
-                    _("Error: %(error)s", error=query["error"])
+        """Execute and return the historical payload shape."""
+        options = ChartDataExecutionOptions(
+            force_cached=kwargs.get("force_cached", False),
+            cache_query_context=kwargs.get("cache", False),
+        )
+        return self.execute(options).materialize()
+
+    def execute(
+        self, options: ChartDataExecutionOptions | None = None
+    ) -> ChartDataExecutionResult:
+        """Execute using a typed result with timing outside query payloads."""
+        options = options or ChartDataExecutionOptions()
+        with chart_data_execution_scope() as owns_telemetry:
+            try:
+                result = self._query_context.get_payload_result(
+                    cache_query_context=options.cache_query_context,
+                    force_cached=options.force_cached,
+                    materialize=options.mode == 
ChartDataExecutionMode.MATERIALIZE,
                 )
+            except CacheLoadError as ex:
+                raise ChartDataCacheLoadError(ex.message) from ex
+            except QueryObjectValidationError as ex:
+                raise ChartDataQueryFailedError(ex.message) from ex
+
+            execution = ChartDataExecutionResult(
+                query_context=self._query_context,
+                queries=result.queries,
+                cache_key=result.cache_key,
+            )
+
+            if owns_telemetry:
+                for query in result.queries:
+                    emit_query_timing(query.timing)
 
-        return_value = {
-            "query_context": self._query_context,
-            "queries": payload["queries"],
-        }
-        if cache_query_context:
-            return_value.update(cache_key=payload["cache_key"])
+            # Query-only errors remain in the payload for the View Query modal.
+            for query in result.queries:
+                if (
+                    query.payload.get("error")
+                    and self._query_context.result_type != 
ChartDataResultType.QUERY
+                ):
+                    raise ChartDataQueryFailedError(
+                        _("Error: %(error)s", error=query.payload["error"])
+                    )
+
+            if options.mode == ChartDataExecutionMode.CACHE_ONLY:
+                query_cache_failed = any(
+                    query.timing.cache_write_outcome == 
CacheWriteOutcome.FAILED
+                    or (
+                        options.require_cache_writes
+                        and query.timing.cache_hit is False
+                        and query.timing.cache_write_outcome
+                        != CacheWriteOutcome.SUCCEEDED

Review Comment:
   **Suggestion:** When `require_cache_writes` is enabled, this condition only 
treats query cache writes as missing if `cache_hit is False`. If cache writing 
was required but never attempted (`cache_hit` remains `None`, e.g. no cache 
key/path), the command incorrectly succeeds. Update the check to also fail on 
non-`SUCCEEDED` outcomes when write-required mode is active, regardless of 
`cache_hit`. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Async load_chart_data_into_cache may succeed without query cache.
   - ⚠️ /api/v1/chart/data/<cache_key> may hit uncached queries.
   - ⚠️ Strict cache-only semantics inconsistent for metadata-only query types.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Call the chart data API `/api/v1/chart/data` with a JSON body whose 
top-level
   `result_type` is `FULL` but where one entry in `queries` sets its own 
`result_type` to
   `ChartDataResultType.QUERY` (per-query `result_type` is allowed by
   `ChartDataQueryObjectSchema` in `superset/charts/schemas.py:1233-1239`).
   
   2. With `GLOBAL_ASYNC_QUERIES` enabled and cache timeout not disabled, the 
handler
   `ChartDataRestApi.data` in `superset/charts/data/api.py:86-107` sets 
`use_async` and
   enqueues an async job via `_run_async`, which ultimately calls
   `CreateAsyncChartDataJobCommand.run` and schedules 
`load_chart_data_into_cache` with this
   form data.
   
   3. When the Celery worker executes `load_chart_data_into_cache` in
   `superset/tasks/async_queries.py:87-110`, it constructs a `ChartDataCommand` 
and calls
   `execute(ChartDataExecutionOptions(mode=ChartDataExecutionMode.CACHE_ONLY,
   cache_query_context=True, require_cache_writes=True))`, turning on strict 
cache-write mode
   for all queries in the context.
   
   4. Inside `QueryContextProcessor._execute_query_plan` at
   `superset/common/query_context_processor.py:183-199`, the metadata-only query
   (result_type=`QUERY`) is processed via `get_query_results_cache_only`, which 
for non-data
   result types returns `_metadata_result` from 
`superset/common/query_actions.py:259-268`.
   That function constructs a `QueryDataResult` whose `timing.cache_hit` is 
`None` and
   `timing.cache_write_outcome` is `CacheWriteOutcome.NOT_ATTEMPTED`. Back in
   `ChartDataCommand.execute` at 
`superset/commands/chart/data/get_data_command.py:110-119`,
   `query_cache_failed` is computed using the condition at lines 114-117: the 
inner branch
   requires `options.require_cache_writes` and `query.timing.cache_hit is 
False` before
   checking `cache_write_outcome != CacheWriteOutcome.SUCCEEDED`. Because 
`cache_hit` is
   `None` for this metadata query, the branch does not fire, 
`query_cache_failed` remains
   `False`, and no `ChartDataQueryFailedError` is raised even though strict 
mode was
   requested and no cache write was attempted for that query.
   ```
   </details>
   
   [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=80b2c5a418ef44b28e5d159fdb0e83a9&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=80b2c5a418ef44b28e5d159fdb0e83a9&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/commands/chart/data/get_data_command.py
   **Line:** 115:117
   **Comment:**
        *Logic Error: When `require_cache_writes` is enabled, this condition 
only treats query cache writes as missing if `cache_hit is False`. If cache 
writing was required but never attempted (`cache_hit` remains `None`, e.g. no 
cache key/path), the command incorrectly succeeds. Update the check to also 
fail on non-`SUCCEEDED` outcomes when write-required mode is active, regardless 
of `cache_hit`.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F37516&comment_hash=02df5ca8b91f7dc893726548b130731cffd59249497f0b85853c7535233658ae&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F37516&comment_hash=02df5ca8b91f7dc893726548b130731cffd59249497f0b85853c7535233658ae&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to