Skip to content

feat(sqllab): Add optional pre-run query cost warnings - #45313

Open
elizabeth-ilina wants to merge 67 commits into
apache:masterfrom
elizabeth-ilina:feat/bigquery-cost-warnings
Open

elizabeth-ilina wants to merge 67 commits into
apache:masterfrom
elizabeth-ilina:feat/bigquery-cost-warnings

Conversation

@elizabeth-ilina

@elizabeth-ilina elizabeth-ilina commented Oct 15, 2026 •

Copy link
Copy Markdown

SUMMARY

Add optional cost warnings to SQL Lab's Run workflow, initially supporting BigQuery and PostgreSQL. Users can review an expensive query before it executes. This is an advisory warning, not an enforced spending or resource limit.

The operator enables SQLLAB_QUERY_COST_WARNINGS and ESTIMATE_QUERY_COST. An administrator then opens Database Connections > Edit > Advanced > SQL Lab, enables query cost estimation and pre-run cost warnings, and sets a threshold for that connection. The new warning feature is off by default; no threshold is chosen automatically. The rule is stored in the connection's existing extra JSON, without a database migration.

Each engine uses its own metric:

  • BigQuery: estimated bytes processed from a dry run.
  • PostgreSQL: total planner cost from EXPLAIN (FORMAT JSON), without ANALYZE. This is a relative cost, not dollars, memory or execution time.

For a configured connection, Run checks the query before submitting execution:

  • An estimate at or below the threshold, including a valid zero estimate, proceeds normally.
  • An estimate above the threshold offers Cancel or Run anyway.
  • An unavailable estimate explains the limitation and offers Cancel or Run without estimate. Errors retain their error behavior.

The comparison uses raw values before display rounding. Estimation uses the query's execution context and effective row limit. Cancelling or changing relevant inputs invalidates the pending attempt; repeated confirmation submits the query once.

The Run tooltip shows when warnings are enabled and the configured metric and threshold. The manual estimator remains available with its existing formatted response. Disabled warnings skip the estimate request. Engine hooks define support, so unsupported connections do not acquire arbitrary warning rules.

This covers deliberate SQL Lab runs, including keyboard, selection and query-history entry points. Dashboard queries, scheduled execution and CTAS/CVAS are outside this change. Templates, multiple statements and custom SQL transformations do not receive a pre-run estimate in this implementation.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

Before: SQL Lab offers manual estimation, but Run does not compare the estimate against a connection threshold.

After: administrators configure a warning rule, users can see it in the Run tooltip, and expensive queries prompt before execution.

BigQuery settings

image

PostgreSQL settings

image

Run tooltip

image

BigQuery warning

image

PostgreSQL warning

image

TESTING INSTRUCTIONS

  1. Enable both feature flags. On a supported connection, enable query cost estimation and pre-run warnings, set a positive threshold, save, and reopen the settings.
  2. Hover over Run. Confirm that the tooltip shows the connection's warning metric and threshold. Switch connections and confirm it updates.
  3. Run a read-only SELECT below the threshold. It should execute without a warning. Run one above the threshold: Cancel should submit no execution; Run anyway should submit exactly one execution, including after repeated clicks.
  4. Revise a query after cancelling. For example, on BigQuery with a 2 MB warning threshold, selecting all columns from bigquery-public-data.samples.shakespeare estimates more work than selecting only word, even with LIMIT 1. The narrower query should fall below this example threshold. These are demonstration values, not suggested defaults.
  5. Disable warnings, refresh SQL Lab's connection metadata and run again. There should be no estimate request; the saved threshold should remain available when reopening settings.
  6. Check that manual estimation still works. With warnings enabled, try multiple statements and confirm the unavailable-estimate explanation. Cancel while a check is pending or change the SQL; a late response must not execute the old query.
  7. Repeat the cancellation, confirmation and disabled-warning cases on a connection configured for asynchronous execution with a real Celery worker and results backend.

Validation evidence:

  • Core live validation at 38eff85d7f covered real BigQuery and PostgreSQL warning/revision flows. BigQuery estimated scanning fell from 6,432,064 to 1,332,943 bytes by selecting only the needed column. A PostgreSQL fixture changed from a sequential scan to an index scan after removing an unnecessary cast; planner cost fell from 459 to 8.3 and both queries returned the same result.
  • Real BigQuery async execution was checked with Redis and a separate Celery worker: Cancel submitted no execution or task; repeated confirmation submitted one; disabled warnings submitted no estimate and one execution. Both executed queries returned results in SQL Lab. PostgreSQL async validation at that commit was also reported from the other development environment.
  • After warm-up, 10 enabled estimate requests per engine had median round-trip times of 583 ms for BigQuery and 12 ms for local PostgreSQL. These are local observations, not production performance guarantees. Disabled warnings send no estimate request.
  • The tooltip added in 54b3c9bb0c was rendered in the browser. Core execution behavior was not changed by that commit.

Local validation does not replace the required GitHub checks. Keep the PR draft until those checks have completed successfully.

ADDITIONAL INFORMATION

  • Has associated issue: [SIP-180] Implement a "Cost Estimation" Pre-Query Check #34384
  • Required feature flags: SQLLAB_QUERY_COST_WARNINGS, ESTIMATE_QUERY_COST
  • 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

@elizabeth-ilina
elizabeth-ilina marked this pull request as draft October 15, 2026 02:08
@netlify

netlify Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit 37be080
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6ae42e0e61f161dad1b5c3e9
😎 Deploy Preview https://deploy-preview-45313--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.

@github-actions github-actions Bot added api Related to the REST API doc Namespace | Anything related to documentation labels Oct 15, 2026
Comment thread superset-frontend/src/SqlLab/components/SqlEditor/index.tsx Outdated
Comment thread superset/db_engine_specs/bigquery.py Outdated
Comment thread superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx Outdated
@codeant-ai-for-open-source

Copy link
Copy Markdown
Contributor

CodeAnt PR Risk: Medium Risk

  • The PR needs attention before merging.
  • Changing the threshold unit rescales the stored byte threshold; selecting GB can turn a 100 MB warning limit into 100 GB.
  • If a keyboard shortcut clears selected SQL while estimation is pending, the input comparison may discard the estimate and prevent the selected statement from running.

Assessed commit: 874ee59a4a88

@github-actions github-actions Bot added i18n Namespace | Anything related to localization i18n:spanish Translation related to Spanish language i18n:italian Translation related to Italian language i18n:french Translation related to French language labels Oct 19, 2026
This reverts commit c01ed6b. Keep the shared build-system change separate from the query cost warning feature.
@github-actions github-actions Bot added the requires:rebase Requires rebasing on top of current master label Oct 26, 2026
Comment thread superset/commands/database/update.py Outdated
"""Validate replacement or retained settings without adding omitted extra."""
assert self._model
serialized_extra = self._properties.get("extra", self._model.extra) or "{}"
if "query_cost_warning" not in serialized_extra:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This gate does a substring test on the raw extra string, so a JSON-escaped key ("\u0071uery_cost_warning") skips it. extra_validator only runs QueryWarningRuleSchema over the parsed value, so an update with threshold: -1 or a metric the engine doesn't support is stored without the engine/threshold validation, and the Run-time check then returns 400 for that connection. Could this test the parsed extra dict for the key instead (parsing first, as normalize_query_cost_warning already does)?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey, thanks for catching this. Fixed by parsing the JSON before checking for the warning setting. Added regression coverage for escaped keys in both submitted and retained settings, including invalid thresholds and unsupported metrics.

}
timeout = app.config["SQLLAB_QUERY_COST_ESTIMATE_TIMEOUT"]
try:
with utils.timeout(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The new utils.timeout(...) wrapper around check.assess isn't covered. test_warning_known_timeout_is_unavailable has the mocked check raise SupersetTimeoutException directly, so it would still pass if this wrapper were removed or ignored SQLLAB_QUERY_COST_ESTIMATE_TIMEOUT, and a slow estimate would then block Run indefinitely. Could a command-level test patch utils.timeout and assert it is entered with the configured seconds, then that the confirm/unavailable result is returned when it raises?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks, agreed - the previous test only covered handling the exception. Updated it to check that the timeout context is entered with the configured duration, and that a timeout returns confirm/unavailable without calling the estimator.

@github-actions github-actions Bot removed the requires:rebase Requires rebasing on top of current master label Oct 29, 2026
{"query": []},
{"query": {"totalBytesProcessed": "1"}},
{"rowLevelSecurityStatistics": "masked"},
{"rowLevelSecurityStatistics": {"rowLevelSecurityApplied": "false"}},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The string-valued rowLevelSecurityApplied case has no totalBytesProcessed or accuracy, so _query_cost_warning_estimate returns an unusable estimate for it even if the boolean check in bigquery.py is removed, and this assertion still passes. A regression there could let a dry run with malformed row-security metadata but a valid byte count return proceed. Could this case also include "totalBytesProcessed": "1" and "totalBytesProcessedAccuracy": "PRECISE" so it fails without the guard?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks, fixed. The case now includes a valid byte count and PRECISE accuracy, so it specifically tests the row-security guard. I also fixed a few other test issues: mock leakage between tests, cancellation assertions that ran too early, and malformed-response cases that weren't testing the intended guard.

MagicMock(),
False,
)
execute._set_query_limit_if_required(sql)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These assertions check the prepared SQL limit but not limiting_factor, so they still pass if every assignment is removed from the shared set_query_limit. The only tests that assert those values (sqllab_tests.py) are skipped, so a wrong attribution would change the truncated-result guidance with nothing failing. Could a unit test on set_query_limit assert QUERY for SQL limit 5 / dropdown 100, DROPDOWN for 500 / 100, and QUERY_AND_DROPDOWN for 100 / 100?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks, added unit coverage for all three cases, asserting both the effective limit and limiting_factor.

This branch has not been deployed

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

Labels

api Related to the REST API doc Namespace | Anything related to documentation i18n:brazilian i18n:chinese Translation related to Chinese language i18n:czech i18n:dutch i18n:french Translation related to French language i18n:italian Translation related to Italian language i18n:japanese Translation related to Japanese language i18n:korean Translation related to Korean language i18n:latvian i18n:persian i18n:portuguese i18n:russian Translation related to Russian language i18n:slovak i18n:spanish Translation related to Spanish language i18n:traditional-chinese i18n:ukrainian i18n Namespace | Anything related to localization packages size/XXL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants