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


##########
superset/models/helpers.py:
##########
@@ -749,6 +749,109 @@ def modified(self) -> Markup:
 _NO_BYPASS: frozenset[type] = frozenset()
 
 
+def _make_metric_line_comments_safe(  # noqa: C901
+    expression: str,
+) -> tuple[str, bool]:
+    """Convert SQL line comments without regenerating otherwise valid SQL."""
+    result: list[str] = []
+    index = 0
+    quote: str | None = None
+    dollar_quote: str | None = None
+    backslash_escapes = False
+    in_block_comment = False
+    while index < len(expression):
+        if in_block_comment:
+            end = expression.find("*/", index)
+            if end < 0:
+                return expression, False
+            result.append(expression[index : end + 2])
+            index = end + 2
+            in_block_comment = False
+            continue
+        if dollar_quote:
+            end = expression.find(dollar_quote, index)
+            if end < 0:
+                return expression, False
+            result.append(expression[index : end + len(dollar_quote)])
+            index = end + len(dollar_quote)
+            dollar_quote = None
+            continue
+        if quote:
+            character = expression[index]
+            result.append(character)
+            index += 1
+            if backslash_escapes and character == "\\" and index < 
len(expression):
+                result.append(expression[index])
+                index += 1
+            elif character == quote:
+                if index < len(expression) and expression[index] == quote:
+                    result.append(expression[index])
+                    index += 1
+                else:
+                    quote = None
+                    backslash_escapes = False
+            continue
+
+        if expression.startswith("/*", index):
+            in_block_comment = True
+            continue
+        if expression.startswith("--", index):
+            line_end = len(expression)
+            for separator in ("\n", "\r"):
+                separator_index = expression.find(separator, index + 2)
+                if separator_index >= 0:
+                    line_end = min(line_end, separator_index)
+            contents = expression[index + 2 : line_end]
+            if "*/" in contents:
+                return expression, False
+            result.append(f"/*{contents} */")
+            index = line_end
+            continue
+        if expression[index] in {"'", '"'}:
+            quote = expression[index]
+            backslash_escapes = (
+                quote == "'"
+                and index > 0
+                and expression[index - 1] in {"E", "e"}
+                and (index == 1 or not expression[index - 2].isalnum())
+            )
+            result.append(quote)
+            index += 1
+            continue
+        if expression[index] == "$":
+            match = re.match(r"\$[A-Za-z_][A-Za-z0-9_]*\$|\$\$", 
expression[index:])
+            if match:
+                dollar_quote = match.group(0)
+                result.append(dollar_quote)
+                index += len(dollar_quote)
+                continue
+        result.append(expression[index])
+        index += 1
+    if quote or dollar_quote or in_block_comment:
+        return expression, False
+    return "".join(result), True
+
+
+def _normalize_custom_sql_metric(
+    expression: str,
+    engine: str,
+    normalizer: Callable[[str], str],
+) -> tuple[str, bool]:
+    """Normalize a metric and report whether its source is safe to preserve."""
+    expression = normalizer(expression)
+    if engine not in {"postgresql", "redshift"}:
+        return expression, False
+
+    expression, source_is_safe = _make_metric_line_comments_safe(expression)
+    if source_is_safe:
+        return expression, True
+

Review Comment:
   **Suggestion:** The safe-return branch skips the trailing-semicolon trimming 
that `sanitize_clause` normally enforces. `_normalize_custom_sql_metric` is 
also used by saved metrics (`SqlMetric.get_sqla_col`), and that caller does not 
re-sanitize, so a saved metric like `SUM(x);` can be emitted with `;` inside 
the SELECT list and break query compilation/execution. Apply the same 
`rstrip().rstrip(";").rstrip()` normalization before returning the 
`source_is_safe` path. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Saved PostgreSQL metrics ending semicolon cause SQL errors.
   - ⚠️ Redshift charts using such metrics fail to render.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Define a saved SQL metric on a PostgreSQL or Redshift dataset via the UI, 
with an
   expression ending in a semicolon (for example `SUM(amount);`). Saved metrics 
are
   represented by `SqlMetric` in 
`superset/connectors/sqla/models.py:1185-1275`, using the
   `expression` column as the raw SQL.
   
   2. Build a chart that uses this saved metric either as a regular metric or 
as a series
   limit metric. When the query is built, `SqlaTable._get_series_orderby` in
   `superset/connectors/sqla/models.py:1889-1929` (mirrored by
   `SupersetQuery._get_series_orderby` in `superset/models/helpers.py:2924-32`) 
resolves the
   metric name through `metrics_by_name[series_limit_metric].get_sqla_col(...)`.
   
   3. In `SqlMetric.get_sqla_col` 
(`superset/connectors/sqla/models.py:1230-75`), the metric
   expression is read from `self.expression` and passed to
   `_normalize_custom_sql_metric(expression, self.table.database.backend,
   self.table.database.db_engine_spec.normalize_custom_sql_metric)` defined in
   `superset/models/helpers.py:835-133`.
   
   4. For PostgreSQL and Redshift engines, `_normalize_custom_sql_metric` first 
applies the
   engine normalizer (Postgres implementation at 
`superset/db_engine_specs/postgres.py:57-59`
   only canonicalizes `DATE_TRUNC` units), then calls 
`_make_metric_line_comments_safe`.
   Because the expression has no line comments, 
`_make_metric_line_comments_safe` returns the
   original text unchanged and `source_is_safe=True`, and 
`_normalize_custom_sql_metric`
   returns the unmodified `expression` without calling `sanitize_clause` or 
trimming the
   trailing semicolon (helpers.py:842-848).
   
   5. `SqlMetric.get_sqla_col` ignores `_source_is_safe` and constructs 
`sqla_col =
   literal_column(expression)` using the still-terminated SQL, then labels it 
via
   `Database.make_sqla_column_compatible` (`superset/models/core.py:1366-23`). 
The generated
   query ends up with a SELECT list containing `SUM(amount);` as a column 
expression, which
   breaks compilation because `;` terminates the statement and is invalid 
inside a SELECT
   list, leading to a database syntax error. This differs from the adhoc metric 
path in
   `_process_sql_expression` (`superset/models/helpers.py:23-56`), where even 
when
   `source_is_safe` is true, `source_expression` is post-trimmed with
   `.rstrip().rstrip(";").rstrip()` to remove trailing terminators.
   ```
   </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=ae2932c5564741638b2af220283abec6&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=ae2932c5564741638b2af220283abec6&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/models/helpers.py
   **Line:** 846:848
   **Comment:**
        *Api Mismatch: The safe-return branch skips the trailing-semicolon 
trimming that `sanitize_clause` normally enforces. 
`_normalize_custom_sql_metric` is also used by saved metrics 
(`SqlMetric.get_sqla_col`), and that caller does not re-sanitize, so a saved 
metric like `SUM(x);` can be emitted with `;` inside the SELECT list and break 
query compilation/execution. Apply the same `rstrip().rstrip(";").rstrip()` 
normalization before returning the `source_is_safe` path.
   
   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%2F42095&comment_hash=2c1c3f057593bb828516d80a86245a5ed3e8e32b2de57157db233d5f8c12fa62&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F42095&comment_hash=2c1c3f057593bb828516d80a86245a5ed3e8e32b2de57157db233d5f8c12fa62&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