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


##########
superset-frontend/plugins/plugin-chart-ag-grid-table/src/buildQuery.ts:
##########
@@ -482,12 +482,76 @@ const buildQuery: BuildQuery<TableChartFormData> = (
         };
       }
 
-      // Add AG Grid complex WHERE clause from ownState (non-metric filters)
+      // Map metric/column labels to SQL expressions for WHERE/HAVING 
resolution
+      const sqlExpressionMap: Record<string, string> = {};
+      (metrics || []).forEach((m: QueryFormMetric) => {
+        if (typeof m === 'object' && 'expressionType' in m) {
+          const label = getMetricLabel(m);
+          if (m.expressionType === 'SQL' && m.sqlExpression) {
+            sqlExpressionMap[label] = m.sqlExpression;
+          } else if (
+            m.expressionType === 'SIMPLE' &&
+            m.aggregate &&
+            m.column?.column_name
+          ) {
+            sqlExpressionMap[label] =
+              `${m.aggregate}(${m.column.column_name})`;
+          }
+        }
+      });
+      // Map dimension columns with custom SQL expressions
+      (columns || []).forEach((col: QueryFormColumn) => {
+        if (typeof col === 'object' && 'sqlExpression' in col) {
+          const label = getColumnLabel(col);
+          if (col.sqlExpression) {
+            sqlExpressionMap[label] = col.sqlExpression;
+          }
+        }
+      });
+      // Merge datasource-level saved metrics and calculated columns
+      if (ownState.metricSqlExpressions) {
+        Object.entries(
+          ownState.metricSqlExpressions as Record<string, string>,
+        ).forEach(([label, expression]) => {
+          if (!sqlExpressionMap[label]) {
+            sqlExpressionMap[label] = expression;
+          }
+        });
+      }
+
+      const resolveLabelsToSQL = (clause: string): string => {
+        let resolved = clause;
+        // Sort by label length descending to prevent substring false positives
+        const sortedEntries = Object.entries(sqlExpressionMap).sort(
+          ([a], [b]) => b.length - a.length,
+        );
+        sortedEntries.forEach(([label, expression]) => {
+          if (resolved.includes(label)) {
+            const escapedLabel = label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+            // Wrap complex expressions in parentheses for valid SQL
+            const isExpression =
+              expression.includes('(') ||
+              expression.toUpperCase().includes('CASE') ||
+              expression.includes('\n');
+            const wrappedExpression = isExpression
+              ? `(${expression})`
+              : `"${expression}"`;
+            resolved = resolved.replace(
+              new RegExp(`\\b${escapedLabel}\\b`, 'g'),
+              wrappedExpression,
+            );
+          }
+        });
+        return resolved;
+      };

Review Comment:
   **Suggestion:** The `resolveLabelsToSQL` helper wraps any SQL expression 
that doesn't contain parentheses/CASE/newlines in double quotes, which turns 
arbitrary expressions like `col1 + col2` into `"col1 + col2"` and causes SQL 
errors because the database interprets them as identifiers rather than 
expressions; instead, non-complex expressions should be used as-is while only 
complex ones are wrapped in parentheses. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Server-side AG Grid metric filters can fail with SQL errors.
   - ⚠️ Users cannot reliably filter on expression-based metrics.
   ```
   </details>
   
   ```suggestion
         const resolveLabelsToSQL = (clause: string): string => {
           let resolved = clause;
           // Sort by label length descending to prevent substring false 
positives
           const sortedEntries = Object.entries(sqlExpressionMap).sort(
             ([a], [b]) => b.length - a.length,
           );
           sortedEntries.forEach(([label, expression]) => {
             if (resolved.includes(label)) {
               const escapedLabel = label.replace(/[.*+?^${}()|[\]\\]/g, 
'\\$&');
               // Wrap complex expressions in parentheses for valid SQL
               const isExpression =
                 expression.includes('(') ||
                 expression.toUpperCase().includes('CASE') ||
                 expression.includes('\n');
               const wrappedExpression = isExpression
                 ? `(${expression})`
                 : expression;
               resolved = resolved.replace(
                 new RegExp(`\\b${escapedLabel}\\b`, 'g'),
                 wrappedExpression,
               );
             }
           });
           return resolved;
         };
   ```
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Configure an AG Grid Table chart on a dataset with `server_pagination: 
true` and a SQL
   metric defined as an ad-hoc metric in the Explore view whose expression 
contains operators
   but no parentheses/CASE, e.g. `col1 + col2` (this metric is sent to the 
backend as a
   `QueryFormMetric` with `expressionType: 'SQL'` and `sqlExpression: 'col1 + 
col2'` and ends
   up in `baseQueryObject.metrics` used in `buildQuery` at
   `superset-frontend/plugins/plugin-chart-ag-grid-table/src/buildQuery.ts:90` 
and lines
   485–499 where `sqlExpressionMap` is built).
   
   2. In the AG Grid UI, enable server-side filtering on this metric and create 
a complex
   filter such as `metric_label > 5`, where `metric_label` is the display label 
of the SQL
   metric; `AgGridTableChart` writes the resulting string filter into
   `ownState.agGridComplexWhere` via `handleFilterChanged` at
   
`superset-frontend/plugins/plugin-chart-ag-grid-table/src/AgGridTableChart.tsx:28–43`
 (see
   `agGridComplexWhere: completeFilterState.complexWhere`).
   
   3. When the chart reloads, `cachedBuildQuery` (exported from `buildQuery.ts` 
at lines
   689–710) is called with this `ownState`, and inside `buildQuery` with
   `formData.server_pagination === true` the server-pagination block at
   `buildQuery.ts:437–581` is executed; there `sqlExpressionMap` is populated 
from `metrics`
   (lines 485–499) so that `sqlExpressionMap[metric_label] = 'col1 + col2'`.
   
   4. Still in the same block, `resolveLabelsToSQL` at `buildQuery.ts:522–545` 
is called with
   `ownState.agGridComplexWhere` (line 549–550); because the mapped expression 
`'col1 +
   col2'` contains no `(`, `CASE`, or newline, `isExpression` is false and
   `wrappedExpression` becomes `"col1 + col2"` (line 536–538), so the final 
`extras.where`
   built at lines 549–554 contains `"col1 + col2" > 5` rather than `col1 + col2 
> 5`, causing
   the generated SQL to reference a non-existent identifier `"col1 + col2"` and 
producing a
   database error when the backend executes the query.
   ```
   </details>
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** 
superset-frontend/plugins/plugin-chart-ag-grid-table/src/buildQuery.ts
   **Line:** 522:546
   **Comment:**
        *Logic Error: The `resolveLabelsToSQL` helper wraps any SQL expression 
that doesn't contain parentheses/CASE/newlines in double quotes, which turns 
arbitrary expressions like `col1 + col2` into `"col1 + col2"` and causes SQL 
errors because the database interprets them as identifiers rather than 
expressions; instead, non-complex expressions should be used as-is while only 
complex ones are wrapped in parentheses.
   
   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.
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F38369&comment_hash=0079ef80d8059af4406e7c9111a03dce6e7bd9795e2d146c606040bf27588e5b&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F38369&comment_hash=0079ef80d8059af4406e7c9111a03dce6e7bd9795e2d146c606040bf27588e5b&reaction=dislike'>👎</a>



##########
superset-frontend/plugins/plugin-chart-ag-grid-table/src/buildQuery.ts:
##########
@@ -498,12 +562,13 @@ const buildQuery: BuildQuery<TableChartFormData> = (
         };
       }

Review Comment:
   **Suggestion:** After resolving AG Grid filter labels to SQL expressions, 
`extras.where` contains the resolved clause while `ownState.agGridComplexWhere` 
still holds the original label-based string, so the later totals query logic 
that tries to strip the AG Grid WHERE fragment using 
`ownState.agGridComplexWhere` no longer matches and leaves the filter applied 
to totals, causing totals to be incorrectly filtered; updating 
`ownState.agGridComplexWhere` to the resolved value keeps the stripping logic 
working. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Totals row in AG Grid table shows filtered aggregates.
   - ⚠️ Dashboards using totals misrepresent overall metric values.
   ```
   </details>
   
   ```suggestion
         // Resolve and apply AG Grid WHERE clause
         if (ownState.agGridComplexWhere && ownState.agGridComplexWhere.trim()) 
{
           const resolvedWhere = 
resolveLabelsToSQL(ownState.agGridComplexWhere);
           // Keep ownState.agGridComplexWhere in sync with the resolved 
expression
           // so totals queries can correctly strip the AG Grid WHERE clause
           (ownState as Record<string, unknown>).agGridComplexWhere = 
resolvedWhere;
           const existingWhere = queryObject.extras?.where;
           const combinedWhere = existingWhere
             ? `${existingWhere} AND ${resolvedWhere}`
             : resolvedWhere;
   
           queryObject = {
             ...queryObject,
             extras: {
               ...queryObject.extras,
               where: combinedWhere,
             },
           };
         }
   ```
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Build an AG Grid Table chart with `server_pagination: true`, `query_mode:
   QueryMode.Aggregate`, at least one metric, and `show_totals: true` so that 
totals queries
   are generated; this passes through `buildQuery` in
   
`superset-frontend/plugins/plugin-chart-ag-grid-table/src/buildQuery.ts:61–685`,
 which
   constructs both the main query and a totals query (see lines 617–663 where 
`extraQueries`
   includes a totals query).
   
   2. In the AG Grid UI, define a metric or calculated column whose label 
requires resolution
   (e.g. SQL metric `my_metric` defined on a saved metric or ad-hoc expression) 
and apply a
   complex filter on it so that `completeFilterState.complexWhere` contains a 
label-based
   condition such as `(my_metric > 5)`; `AgGridTableChart.handleFilterChanged` 
at
   `src/AgGridTableChart.tsx:28–43` stores this into 
`ownState.agGridComplexWhere` via
   `agGridComplexWhere: completeFilterState.complexWhere`.
   
   3. On the next query execution, `cachedBuildQuery` (exported at 
`buildQuery.ts:689–710`)
   calls `buildQuery` with this `ownState`; inside the server-pagination block 
at
   `buildQuery.ts:437–581`, `sqlExpressionMap` is built (lines 485–520) and
   `resolveLabelsToSQL` at lines 522–545 is invoked in the WHERE-clause logic 
at lines
   548–554, producing a `resolvedWhere` string where occurrences of `my_metric` 
are replaced
   with the underlying SQL expression (e.g. `(col1 + col2)`), so 
`queryObject.extras.where`
   now contains the resolved clause, not the original 
`ownState.agGridComplexWhere` string.
   
   4. Later in the same `buildQuery` invocation, the totals query is 
constructed at
   `buildQuery.ts:617–663`; the code copies `totalsExtras = { 
...queryObject.extras }` (line
   627), then attempts to strip the AG Grid filter by doing string replacements 
using
   `ownState.agGridComplexWhere` as `agGridWhere` (lines 628–641), but since
   `ownState.agGridComplexWhere` still holds the original label-based clause 
(e.g.
   `(my_metric > 5)`) and `totalsExtras.where` contains the resolved SQL clause 
(e.g. `(col1
   + col2 > 5)`), none of the `replace` calls match and the AG Grid filter 
remains in
   `totalsExtras.where`, causing the totals query pushed into `extraQueries` at 
lines 653–662
   to include the AG Grid filter and return filtered totals instead of overall 
totals.
   ```
   </details>
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** 
superset-frontend/plugins/plugin-chart-ag-grid-table/src/buildQuery.ts
   **Line:** 548:563
   **Comment:**
        *Logic Error: After resolving AG Grid filter labels to SQL expressions, 
`extras.where` contains the resolved clause while `ownState.agGridComplexWhere` 
still holds the original label-based string, so the later totals query logic 
that tries to strip the AG Grid WHERE fragment using 
`ownState.agGridComplexWhere` no longer matches and leaves the filter applied 
to totals, causing totals to be incorrectly filtered; updating 
`ownState.agGridComplexWhere` to the resolved value keeps the stripping logic 
working.
   
   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.
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F38369&comment_hash=c7339b24c321a63769b66e8bd73cd6bbcde2bdd5b6737175456e6e7102baf10a&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F38369&comment_hash=c7339b24c321a63769b66e8bd73cd6bbcde2bdd5b6737175456e6e7102baf10a&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