Skip to content

fix(sqllab): reject cost estimation of a templated query instead of a syntax error - #42785

Open
mapledan wants to merge 2 commits into
apache:masterfrom
mapledan:fix/sqllab-estimate-reject-jinja
Open

fix(sqllab): reject cost estimation of a templated query instead of a syntax error#42785
mapledan wants to merge 2 commits into
apache:masterfrom
mapledan:fix/sqllab-estimate-reject-jinja

Conversation

@mapledan

@mapledan mapledan commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

SUMMARY

QueryEstimationCommand.run() renders Jinja only when template_params is non-empty. But get_time_filter(), current_username() and friends need no declared parameter, and SQL Lab always POSTs template_params: {} to /api/v1/sqllab/estimate/ — so rendering is skipped, the raw {% reaches SQLScript(), and the user gets Issue 1003, "there is a syntax error in the SQL query, perhaps there was a misspelling or a typo", for a query that runs fine in SQL Lab.

Rendering unconditionally would silence the error but estimate the wrong thing: a template expands at run time, and with no dashboard in play get_time_filter() yields no filter at all, so the reported cost would be for a query missing its time predicate. The query is refused instead, with an explanation.

Whether SQL carries a template is asked of the template processor (new has_template()), which lexes with its own environment — so ENABLE_TEMPLATE_PROCESSING being off, and SQL that merely contains braces such as '{{1,2},{3,4}}', both behave correctly.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

Before

A valid query is reported as a typo.

before

After

The reason is stated, and what to do about it.

after

TESTING INSTRUCTIONS

  1. Enable ESTIMATE_QUERY_COST and ENABLE_TEMPLATE_PROCESSING in FEATURE_FLAGS.
  2. Add "cost_estimate_enabled": true to a Postgres database's extra.
  3. In SQL Lab against that database, run this query — it succeeds:
    SELECT 1 AS n
    {% set tf = get_time_filter(strftime="%Y-%m-%d") %}
    {% if tf.from_expr %} WHERE 1 = 1 {% endif %}
  4. Click Estimate cost. Before this change: Error parsing near '{%' at line 2:2. After: the explanation above.
  5. Confirm plain SQL is unaffected — SELECT 1 still estimates normally.
  6. Confirm SQL that merely looks templated is unaffected — SELECT '{{1,2},{3,4}}'::int[] still estimates normally.

Unit tests: pytest tests/unit_tests/commands/sql_lab/test_estimate.py tests/unit_tests/jinja_context_test.py

ADDITIONAL INFORMATION

  • Has associated issue:
  • 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

… syntax error

QueryEstimationCommand.run() only renders Jinja when template_params is
non-empty, but a whole class of template functions needs no declared
parameter at all (get_time_filter(), current_username(), url_param()).
A query using one of them skips rendering entirely and the raw {%/{{/{#
reaches SQLScript(), which raises a generic parse error -- "Perhaps
there was a misspelling or a typo" for a query that is perfectly valid.

Rather than rendering unconditionally to avoid that error, refuse the
estimate: a templated query has no single execution plan. What it
expands to is decided at run time (a dashboard's time range, the current
user, a URL parameter), and different expansions can produce different
plans. Estimating one of them -- here the emptiest one, with no such
context to expand from -- would report the plan of a different query
than the one that runs, with nothing to signal the difference.

Whether SQL carries a template is asked of the template processor, which
owns that knowledge and answers with its own environment, so customized
delimiters are honored and NoOpTemplateProcessor correctly reports none
when ENABLE_TEMPLATE_PROCESSING is off. It lexes rather than parses, so
that a comment -- which leaves no trace in a parsed template -- counts,
and so that SQL merely containing braces, such as the PostgreSQL nested
array literal '{{1,2},{3,4}}', does not.
@dosubot dosubot Bot added global:jinja Related to Jinja templating sqllab Namespace | Anything related to the SQL Lab labels Aug 5, 2026
@bito-code-review

bito-code-review Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #aab734

Actionable Suggestions - 0
Filtered by Review Rules

Bito filtered these suggestions based on rules created automatically for your feedback. Manage rules.

  • superset/commands/sql_lab/estimate.py - 1
Review Details
  • Files reviewed - 5 · Commit Range: 1a7f6e8..1a7f6e8
    • superset/commands/sql_lab/estimate.py
    • superset/exceptions.py
    • superset/jinja_context.py
    • tests/unit_tests/commands/sql_lab/test_estimate.py
    • tests/unit_tests/jinja_context_test.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

Comment thread superset/jinja_context.py Outdated
@bito-code-review

Copy link
Copy Markdown
Contributor

The issue is that kinds > {"data"} requires the lexer to return at least one token that is not a data token (e.g., variable_begin, block_begin). However, if the SQL contains only data tokens (which happens for plain SQL) or if the Jinja lexer identifies tokens differently for certain templates, the condition might fail incorrectly.

To resolve this, you should check if there are any tokens other than data tokens. A more robust approach is to check if the set of tokens contains anything besides data:

        return any(kind != "data" for _, kind, _ in self.env.lex(sql))

This change ensures that if any token is found that is not classified as data, the function correctly identifies the presence of a template.

superset/jinja_context.py

return any(kind != "data" for _, kind, _ in self.env.lex(sql))

@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 56.25000% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.78%. Comparing base (e871ee2) to head (a61fcd8).
⚠️ Report is 5 commits behind head on master.

Files with missing lines Patch % Lines
superset/jinja_context.py 62.50% 3 Missing ⚠️
superset/commands/sql_lab/estimate.py 33.33% 1 Missing and 1 partial ⚠️
superset/exceptions.py 60.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #42785      +/-   ##
==========================================
- Coverage   65.79%   65.78%   -0.01%     
==========================================
  Files        2841     2842       +1     
  Lines      162082   162117      +35     
  Branches    37145    37148       +3     
==========================================
+ Hits       106638   106656      +18     
- Misses      53380    53395      +15     
- Partials     2064     2066       +2     
Flag Coverage Δ
hive 38.07% <31.25%> (+<0.01%) ⬆️
mysql 57.90% <56.25%> (+<0.01%) ⬆️
postgres 57.94% <56.25%> (-0.01%) ⬇️
presto 40.00% <31.25%> (ø)
python 59.32% <56.25%> (-0.01%) ⬇️
sqlite 57.57% <56.25%> (+<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.

`kinds > {"data"}` is a strict superset test, so it required a `data`
token to be present alongside the template ones. SQL that is nothing but
a template -- `{{ dataset(1) }}` -- lexes without any `data` token, so
has_template() returned False for it and the raw Jinja went on to
SQLScript(), producing exactly the misleading syntax error this change
set out to replace.

Asking for any kind other than `data` instead. The whole token stream is
still consumed before deciding, which any() would not do: `'{{1,2},{3,4}}'`
opens like a template and only turns out not to be one further along.
@sadpandajoe
sadpandajoe requested review from rusackas and sadpandajoe and a lite review from Copilot August 6, 2026 17:17

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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

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

Labels

global:jinja Related to Jinja templating size/L sqllab Namespace | Anything related to the SQL Lab

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants