Skip to content

fix(sql): validate Custom SQL metric has an aggregate under GROUP BY#42199

Open
DamianPendrak wants to merge 2 commits into
apache:masterfrom
DamianPendrak:fix/38913-custom-sql-metric-aggregate
Open

fix(sql): validate Custom SQL metric has an aggregate under GROUP BY#42199
DamianPendrak wants to merge 2 commits into
apache:masterfrom
DamianPendrak:fix/38913-custom-sql-metric-aggregate

Conversation

@DamianPendrak

Copy link
Copy Markdown
Member

SUMMARY

Fixes #38913.

A Deck.gl Screen Grid chart whose Weight is a Custom SQL expression that
is not an aggregate - e.g. GREATEST(confirmed, predicted) - produces a grouped
query that selects an expression which is neither grouped nor aggregated:

SELECT longitude, latitude, GREATEST(confirmed, predicted) AS "..."
FROM incidents
WHERE longitude IS NOT NULL AND latitude IS NOT NULL
GROUP BY longitude, latitude ...
-- ERROR: column "confirmed" must appear in the GROUP BY clause
--        or be used in an aggregate function

Rather than silently auto-wrapping in a guessed aggregate - which would invent semantics and risk
returning wrong numbers - this raises a clear, actionable validation error:

The Custom SQL metric "GREATEST(confirmed, predicted)" is not an aggregate and
can't be combined with a GROUP BY. Wrap it in an aggregate function, e.g.
MAX(GREATEST(confirmed, predicted)).

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

After:
Screenshot 2026-07-18 at 13 21 14

TESTING INSTRUCTIONS

From the issue:

  1. Go to Charts → Create Chart
  2. Select dataset (e.g., incidents)
  3. Choose Deck.gl Screen Grid
  4. Choose longitude & latitude
  5. Weight - Custom SQL -> GREATEST(confirmed, predicted)
  6. Update chart

ADDITIONAL INFORMATION

  • Has associated issue: #38913
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
    • Migration is atomic, supports rollback & is backwards-compatible
    • Confirm DB migration upgrade and downgrade tested
    • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

A Custom SQL metric that is not an aggregate (e.g.
`GREATEST(confirmed, predicted)`) produced an invalid grouped query such as
`SELECT lon, lat, GREATEST(...) ... GROUP BY lon, lat`, which every database
rejects. This is most visible on Deck.gl charts (e.g. a Screen Grid's
"Weight"), where the spatial columns are forced into the GROUP BY.

Detect the case in `get_sqla_query` -- the single choke point shared by both
the modern chart-data API and the legacy viz.py/deck_multi paths -- and raise
a clear `QueryObjectValidationError` telling the user to wrap the expression
in an aggregate (e.g. `MAX(...)`), instead of letting a cryptic database error
(or nondeterministic results on permissive MySQL) through. Custom SQL metrics
own their own aggregation, so we surface guidance rather than guessing one.

Adds `has_aggregate()` in `superset/sql/parse.py` using sqlglot: windowed
aggregates are excluded (they don't collapse rows), and it fails open on parse
errors and unknown/anonymous functions so a valid query is never wrongly
blocked.

Fixes apache#38913

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dosubot dosubot Bot added explore:error Related to general errors of Explore viz:charts:deck.gl Related to deck.gl charts labels Jul 18, 2026
@bito-code-review

bito-code-review Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #a1f309

Actionable Suggestions - 0
Review Details
  • Files reviewed - 4 · Commit Range: 00cb26f..00cb26f
    • superset/models/helpers.py
    • superset/sql/parse.py
    • tests/unit_tests/models/helpers_test.py
    • tests/unit_tests/sql/parse_tests.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@netlify

netlify Bot commented Jul 18, 2026

Copy link
Copy Markdown

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit 00cb26f
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6a5b6c489af4a50008d4f39b
😎 Deploy Preview https://deploy-preview-42199--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@codecov

codecov Bot commented Jul 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 62.50000% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.08%. Comparing base (cce77b4) to head (5b8ab55).
⚠️ Report is 25 commits behind head on master.

Files with missing lines Patch % Lines
superset/sql/parse.py 55.55% 3 Missing and 1 partial ⚠️
superset/models/helpers.py 71.42% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #42199      +/-   ##
==========================================
- Coverage   65.08%   65.08%   -0.01%     
==========================================
  Files        2758     2758              
  Lines      155229   155244      +15     
  Branches    35572    35577       +5     
==========================================
+ Hits       101026   101035       +9     
- Misses      52288    52292       +4     
- Partials     1915     1917       +2     
Flag Coverage Δ
hive 38.93% <12.50%> (-0.01%) ⬇️
mysql 57.62% <62.50%> (+<0.01%) ⬆️
postgres 57.68% <62.50%> (+<0.01%) ⬆️
presto 40.89% <18.75%> (-0.01%) ⬇️
python 59.10% <62.50%> (+<0.01%) ⬆️
sqlite 57.30% <62.50%> (+<0.01%) ⬆️
unit 100.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread superset/models/helpers.py
Comment thread tests/unit_tests/sql/parse_tests.py Outdated
Comment on lines +4421 to +4424
"""
``has_aggregate`` should detect top-level (non-windowed) aggregates and
fail open (return True) when the expression can't be parsed or uses a
function sqlglot can't model (a possible unknown aggregate).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The test documentation says the helper should detect top-level aggregates, but the parametrized cases include a non-top-level aggregate in a subquery marked as valid; this contradiction makes the contract ambiguous and can mislead future changes. Align the docstring or the test case so they describe the same behavior. [docstring mismatch]

Severity Level: Minor 🧹
- ⚠️ Potential minor confusion for future maintainers reading tests.
- ⚠️ Suggestion targets doc clarity; no runtime effect.
- ⚠️ Existing behavior already explicitly encoded by parametrized cases.
Steps of Reproduction ✅
1. Open `tests/unit_tests/sql/parse_tests.py` and locate the docstring for
`test_has_aggregate` at lines 4421-4424, which states the helper should detect top-level
(non-windowed) aggregates and fail open on unparseable or unknown-function expressions.

2. Compare this description with the parametrized test cases at lines 4407-4418,
specifically the case `("a - (SELECT AVG(b) FROM t)", True)` at line 4417.

3. Note that the docstring does not explicitly discuss subquery aggregates, while the test
case treats an aggregate inside a subquery as acceptable (returning `True`), extending the
described fail-open behavior.

4. This minor ambiguity is confined to test documentation; the tests pass and there is no
conflicting behavior in the code-under-test shown in this PR, so the issue is editorial
rather than a functional bug.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** tests/unit_tests/sql/parse_tests.py
**Line:** 4421:4424
**Comment:**
	*Docstring Mismatch: The test documentation says the helper should detect top-level aggregates, but the parametrized cases include a non-top-level aggregate in a subquery marked as valid; this contradiction makes the contract ambiguous and can mislead future changes. Align the docstring or the test case so they describe the same behavior.

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
👍 | 👎

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses a SQL correctness bug in grouped queries (notably Deck.gl Screen Grid “Weight” metrics) by validating that Custom SQL adhoc metrics include an aggregate whenever the query will produce a GROUP BY, and raising a clear QueryObjectValidationError instead of letting an invalid query reach the database.

Changes:

  • Add has_aggregate() SQL parser helper to detect aggregates while attempting to ignore window-only aggregates.
  • Add a guard in SqlaTable.get_sqla_query() to reject non-aggregate Custom SQL metrics when a GROUP BY is present (including timeseries buckets), with an actionable error message.
  • Add unit tests covering the new validation behavior and aggregate detection.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
superset/sql/parse.py Adds has_aggregate() helper to detect aggregates in Custom SQL expressions.
superset/models/helpers.py Adds validation to block non-aggregate Custom SQL metrics when the query is grouped.
tests/unit_tests/sql/parse_tests.py Adds unit tests for has_aggregate() behavior.
tests/unit_tests/models/helpers_test.py Adds unit tests ensuring grouped queries reject non-aggregate Custom SQL metrics and allow valid cases.

Comment thread superset/sql/parse.py
Comment thread tests/unit_tests/sql/parse_tests.py
Addresses the automated code-review comments on apache#42199:

- `has_aggregate` ignored any aggregate with a window *ancestor*, so a real
  aggregate nested inside a windowed one (`SUM(SUM(x)) OVER ()`) was
  misclassified as non-aggregate and would have wrongly rejected a valid
  grouped query. It now ignores only an aggregate that is itself directly
  windowed (immediate parent is `exp.Window`).
- Add a `SUM(SUM(x)) OVER ()` test case.
- Align the `test_has_aggregate` docstring with its parametrized cases (drop
  the "top-level" wording that contradicted the subquery case).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@bito-code-review

bito-code-review Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #81a380

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: 00cb26f..5b8ab55
    • superset/sql/parse.py
    • tests/unit_tests/sql/parse_tests.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

explore:error Related to general errors of Explore size/L viz:charts:deck.gl Related to deck.gl charts

Projects

None yet

Development

Successfully merging this pull request may close these issues.

deck.gl Screen Grid - column must appear in the GROUP BY clause or be used in an aggregate function

2 participants