Skip to content

fix(query-context): resolve legacy granularity_sqla for stored queries - #44460

Merged
rusackas merged 3 commits into
apache:masterfrom
shoemoney:fix/42926-granularity-sqla-fallback
Sep 29, 2026
Merged

rusackas merged 3 commits into
apache:masterfrom
shoemoney:fix/42926-granularity-sqla-fallback

Conversation

@shoemoney

@shoemoney shoemoney commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

SUMMARY

Fixes #42926 (the second symptom, the one left open after #42927).

A chart saved before the x-axis control existed keeps its time column in params as the legacy granularity_sqla, and its stored query_context has is_timeseries: true with granularity: null:

{"is_timeseries": true, "columns": ["ds"], "metrics": ["count"], "filters": [], "granularity": null}

QueryContextFactory._apply_granularity (superset/common/query_context_factory.py:245-276 on master) only infers a granularity when form_data carries an adhoc x_axis plus a temporal range bound. With no x_axis, is_adhoc_column(None) is false, should_infer_filter_granularity is false, the if granularity := query_object.granularity: block at line 278 is skipped because the granularity is None, and the method returns having set nothing. superset/models/helpers.py:4715 then hits

if not granularity and is_timeseries:
    raise QueryObjectValidationError(
        _("Datetime column not provided as part table configuration "
          "and is required by this type of chart")
    )

and the request fails with a 400 out of ChartDataQueryFailedError. The backward-compatibility coercion just above it (helpers.py:4704) is explicitly gated on granularity is not None, so a null granularity is not rescued there, and the granularity_sqla to granularity rename at query_object.py:69 is query-object level only and never reads form_data.

The chart still renders in Explore, because the paths that rebuild a query from form data already resolve the legacy key: extractExtras.ts:77-80 for Explore, and form_data_query_context.py:349 for the Excel export and the MCP tools. Only the consumers that replay the stored query_context verbatim through GET /api/v1/chart/<id>/data/ fail, which is alerts and reports, thumbnails, cache warm-up and data export.

When the query object is a timeseries with no granularity, check form_data's granularity, then legacy granularity_sqla, then the dataset's main datetime column. This matches the precedence used by Explore and the form-data query builder. Returning after inference also preserves independent temporal filters.

Two deliberate narrowings, both to avoid disturbing existing behavior:

  • Candidates are matched against the already computed temporal_columns set, so a column that old params still names but the dataset has since dropped or made non temporal is ignored rather than injected into the query.
  • The fallback is skipped when form_data carries an x_axis. Setting a granularity there would fall into the block below and rewrite the x-axis column to the granularity, which is exactly what test_apply_granularity_preserves_physical_temporal_axis guards against. An x_axis query is also not the reported shape: is_timeseries defaults to DTTM_ALIAS in columns (query_object.py:202), which is the pre-x-axis form. A stored query that somehow has both an explicit is_timeseries: true and an x_axis still reaches the raise, and is left for a separate change rather than risking the column rewrite here.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

Not applicable, backend only.

TESTING INSTRUCTIONS

Current validation: pytest tests/unit_tests/common -q passes 343 tests. A five-case regression covers distinct valid modern/legacy columns and missing, empty, dropped, or non-temporal modern values; the distinct-column case fails on the previous head and passes with the fix. All applicable staged-file pre-commit hooks pass, including mypy, Ruff and Pylint.

Original fallback validation, retained for context:

Six unit tests were added to tests/unit_tests/common/test_query_context_factory.py: the granularity_sqla fallback, the main_dttm_col fallback, a stale legacy column being ignored, an explicit granularity not being overridden, a non timeseries query being left alone, and an x-axis query being left alone.

pytest tests/unit_tests/common/test_query_context_factory.py

Red, with the three new fallback tests against unmodified query_context_factory.py:

FFF...                                                                   [100%]
>       assert query_object.granularity == "ds"
E       AssertionError: assert None == 'ds'
E        +  where None = <Mock spec='QueryObject' id='4709837200'>.granularity
tests/unit_tests/common/test_query_context_factory.py:567: AssertionError
...
FAILED tests/unit_tests/common/test_query_context_factory.py::TestQueryContextFactory::test_apply_granularity_falls_back_to_legacy_granularity_sqla
FAILED tests/unit_tests/common/test_query_context_factory.py::TestQueryContextFactory::test_apply_granularity_falls_back_to_main_dttm_col
FAILED tests/unit_tests/common/test_query_context_factory.py::TestQueryContextFactory::test_apply_granularity_ignores_stale_legacy_granularity_sqla
3 failed, 3 passed, 45 deselected in 0.55s

Green, same file with the fix applied (45 pre-existing plus the 6 new):

...................................................                      [100%]
51 passed in 0.37s

The rest of the directory is unaffected:

$ pytest tests/unit_tests/common -q
337 passed in 6.90s

tests/unit_tests/charts and tests/unit_tests/models were also run: 1154 passed with this branch against 1148 on master, with the same 20 failures in models/core_test.py and models/test_hours_offset_bound_truncation.py before and after, so they are pre-existing in my environment and unrelated.

pre-commit run on the two staged files passes, including mypy (main), ruff, ruff-format and pylint.

Manual check: save a chart whose query_context has is_timeseries: true and no granularity while its params carry only granularity_sqla, then call GET /api/v1/chart/<pk>/data/. It returns the data instead of "Datetime column not provided as part table configuration".

Environment used: Python 3.11 venv, pip install -r requirements/base.txt then pip install -e . --no-deps, plus pytest. No end-to-end API test is included in this PR, since the change is confined to one method and the unit file already owns the _apply_granularity cases. Happy to add an integration test that stores a legacy-shaped query_context and asserts a 200 from the data endpoint if a reviewer would rather see it pinned there too.

ADDITIONAL INFORMATION

@bito-code-review

bito-code-review Bot commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #5a3714

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: cafc5bb..cafc5bb
    • superset/common/query_context_factory.py
    • tests/unit_tests/common/test_query_context_factory.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 an incremental AI Review.

  • /review full - 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/common/query_context_factory.py
@bito-code-review

Copy link
Copy Markdown
Contributor

The flagged issue is correct. The current implementation of _apply_granularity in superset/common/query_context_factory.py does not account for the possibility that the first temporal filter might be an independent filter on a different datetime column, rather than the one being replaced by the inferred granularity. To resolve this, you should verify that the filter being removed is indeed the one associated with the previous granularity or is redundant, rather than blindly removing the first temporal filter found in the query object's filter list.

superset/common/query_context_factory.py

# Instead of blindly removing the first temporal filter:
            # filter_to_remove = next((f for f in query_object.filter if f.get("col") == old_granularity), None)
            # if filter_to_remove:
            #     query_object.filter.remove(filter_to_remove)

@codecov

codecov Bot commented Sep 20, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.10%. Comparing base (599026a) to head (717be3b).

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #44460      +/-   ##
==========================================
- Coverage   81.10%   81.10%   -0.01%     
==========================================
  Files        2955     2955              
  Lines      178385   178389       +4     
  Branches    41336    41337       +1     
==========================================
+ Hits       144680   144682       +2     
- Misses      30996    30998       +2     
  Partials     2709     2709              
Flag Coverage Δ
hive 36.79% <0.00%> (-0.01%) ⬇️
mysql 55.99% <0.00%> (-0.01%) ⬇️
postgres 56.00% <0.00%> (-0.01%) ⬇️
presto 38.70% <0.00%> (-0.01%) ⬇️
python 85.39% <100.00%> (-0.01%) ⬇️
sqlite 55.72% <0.00%> (-0.01%) ⬇️
unit 77.67% <100.00%> (-0.01%) ⬇️

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.

@rusackas rusackas left a comment

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.

Nice diagnosis on this one. The granularity_sqla fallback makes sense, and the temporal_columns guard keeps it from picking up a stale column. Codeant's filter-removal concern looks addressed in cafc5bb too. LGTM! We'll get this merged.

@netlify

netlify Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for superset-docs-preview ready!

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

@shoemoney

Copy link
Copy Markdown
Contributor Author

The Translations failure is reproducible on the CI base. I ran python scripts/translations/check_pot_drift.py with the same Babel 2.17.0 environment on clean base 156549ff and the tested merge 88a88e8: both exit 1 with identical output (5 missing and 4 stale alert/report/screenshot strings). The checker, extraction config and committed template are identical between them.

The Codecov report currently has no unit-test upload; Python-Unit is still awaiting workflow approval. Locally, all 52 query-context tests pass with coverage and execute all four added statements, including the early return. No translation changes added to this PR.

@bito-code-review

bito-code-review Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #52b24d

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: cafc5bb..650a43a
    • superset/common/query_context_factory.py
    • tests/unit_tests/common/test_query_context_factory.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 an incremental AI Review.

  • /review full - 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

@shoemoney

Copy link
Copy Markdown
Contributor Author

Heads up on the red CI here — the unit-tests / babel-extract failures are pre-existing drift on master, not something this PR introduces.

tests/unit_tests/scripts/translations/check_pot_drift_test.py fails on 5 strings missing from superset/translations/messages.pot:

  • Configure retries when alert or report generation fails before delivery.
  • Contact the report owner for error detai…livery failures are not replayed.
  • Screenshot cache is unavailable
  • Screenshot request is temporarily unavailable
  • (one more in the same alert/report + screenshot group)

None of those strings appear in this diff (this change is confined to granularity_sqla resolution in the query context), and master at 22d37f3 does not contain them in messages.pot either — so any PR branched off current master hits the same failure. Whoever landed the alert-retry / screenshot-cache copy needs to regenerate the .pot.

Happy to regenerate it here if you'd rather unblock this PR that way, but it would add unrelated translation churn to the diff, so I've left it alone. Let me know which you prefer.

@shoemoney
shoemoney force-pushed the fix/42926-granularity-sqla-fallback branch from 650a43a to 6cd1291 Compare September 23, 2026 00:06
@shoemoney

Copy link
Copy Markdown
Contributor Author

Heads up on the red babel-extract / unit-tests-required here — it is not this PR, it reproduces on a clean master checkout:

$ git checkout master && python scripts/translations/check_pot_drift.py
superset/translations/messages.pot is out of sync with source:
1 string(s) in source are missing from the template,
1 string(s) in the template no longer exist in source.

+ "This update would change the connection's effective destination (host/port, engine
   parameters, SSH tunnel endpoint, or OAuth2 endpoint URIs) while reusing the stored
   credential. Provide the real password (or SSH tunnel credential / OAuth2 client
   secret) to confirm a connection move."

- "This update would change the connection's effective destination (host/port, engine
   parameters, or SSH tunnel endpoint) while reusing the stored credential. Provide the
   real password (or SSH tunnel credential) to confirm a connection move."

The OAuth2 reword landed in superset/commands/database/{exceptions,update}.py and
superset/db_engine_specs/{base,trino}.py without a matching babel_update.sh run, so
messages.pot still carries the pre-reword msgid. Every PR branched off current master
inherits it.

This branch touches no translatable strings, so I have left messages.pot alone rather
than bundling an unrelated translations diff into an approved PR. Happy to push the
one-string regeneration here if you would rather unblock this way — otherwise a
regeneration on master clears it for everyone at once. Just say which you prefer.

@bito-code-review

bito-code-review Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #7f5a76

Actionable Suggestions - 0
Additional Suggestions - 2
  • tests/unit_tests/common/test_query_context_factory.py - 1
    • Test setup duplication · Line 545-704
      The 7 new tests repeat the same ~15-line `Mock(spec=QueryObject)` + datasource setup (granularity/is_timeseries/columns/post_processing/filter, then `main_dttm_col`/`columns`). A shared helper or fixture would remove the duplication and make the per-branch deltas explicit. If the mock contract changes, 7 sites currently need updating.
  • superset/common/query_context_factory.py - 1
    • Granularity precedence mismatch · Line 297-301
      The new legacy-resolution block prefers `granularity_sqla` over `granularity`, but the sibling paths it claims to match resolve the opposite way: `form_data_query_context.py:349` uses `granularity or granularity_sqla`, and `extractExtras.ts:77-80` uses `formData.granularity || formData.granularity_sqla`. When both keys carry different values, the two consumers pick different time columns, contradicting the comment's "same time subject" goal. Swap the tuple order to `granularity`, `granularity_sqla`, `main_dttm_col`.
Review Details
  • Files reviewed - 2 · Commit Range: 66989cf..6cd1291
    • superset/common/query_context_factory.py
    • tests/unit_tests/common/test_query_context_factory.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 an incremental AI Review.

  • /review full - 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

@shoemoney

Copy link
Copy Markdown
Contributor Author

Addressed the precedence finding in e967242: granularity now precedes granularity_sqla, matching the existing form-data consumers. The five-case regression reproduces the old mismatch and verifies fallback for missing, empty, stale, and non-temporal modern values. All 343 common unit tests and the applicable staged pre-commit hooks pass.

@bito-code-review

bito-code-review Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #6934b3

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: 6cd1291..e967242
    • superset/common/query_context_factory.py
    • tests/unit_tests/common/test_query_context_factory.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 an incremental AI Review.

  • /review full - 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

@github-actions github-actions Bot added i18n Namespace | Anything related to localization size/L i18n:spanish Translation related to Spanish language i18n:italian Translation related to Italian language i18n:french Translation related to French language i18n:chinese Translation related to Chinese language i18n:japanese Translation related to Japanese language i18n:russian Translation related to Russian language i18n:korean Translation related to Korean language i18n:dutch i18n:slovak labels Sep 25, 2026
A chart saved before the x-axis control existed carries its time column in
form_data as the legacy granularity_sqla key, and its stored query_context
has is_timeseries true with granularity null. _apply_granularity only
infers a granularity when form_data has an adhoc x_axis plus a temporal
range filter, so that shape falls through with granularity still unset and
lands on the "not granularity and is_timeseries" guard in
superset/models/helpers.py, which raises "Datetime column not provided as
part table configuration and is required by this type of chart".

The chart renders in Explore because the paths that rebuild a query from
form data already resolve the legacy key (extractExtras.ts for Explore,
form_data_query_context.py for the Excel export and the MCP tools). Every
consumer that replays the stored query_context verbatim through
/api/v1/chart/<id>/data/ fails instead, which covers alerts and reports,
thumbnails, cache warm-up and data export.

When a stored query object is a timeseries, has no granularity and carries
no x-axis, fall back to form_data's granularity_sqla and only then to the
dataset's main datetime column. Candidates are matched against the
dataset's temporal columns, so a column that has since been dropped or is
no longer temporal is ignored. Queries that already carry a granularity,
non timeseries queries and x-axis queries are untouched.

Fixes apache#42926
@shoemoney
shoemoney force-pushed the fix/42926-granularity-sqla-fallback branch from 9c95ec7 to 717be3b Compare September 25, 2026 04:52
@github-actions github-actions Bot removed 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 i18n:chinese Translation related to Chinese language i18n:japanese Translation related to Japanese language i18n:russian Translation related to Russian language i18n:korean Translation related to Korean language i18n:dutch i18n:slovak i18n:ukrainian i18n:portuguese i18n:brazilian i18n:traditional-chinese i18n:persian i18n:czech i18n:latvian labels Sep 25, 2026
@bito-code-review

bito-code-review Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #4a5ce9

Actionable Suggestions - 0
Additional Suggestions - 1
  • tests/unit_tests/common/test_query_context_factory.py - 1
    • Unannotated mock variables · Line 549-556
      BITO adaptive rule 12787 requires explicit type annotations on mock variables. The eight new tests declare `query_object = Mock(spec=QueryObject)` and `datasource = Mock()` unannotated (repeated at 585, 616, 641, 658, 679, 700, 721). Annotate as `query_object: Mock` / `datasource: Mock` so the mock type is visible to readers and static tooling.
Review Details
  • Files reviewed - 2 · Commit Range: 13b2ddd..717be3b
    • superset/common/query_context_factory.py
    • tests/unit_tests/common/test_query_context_factory.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 an incremental AI Review.

  • /review full - 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

@rusackas
rusackas merged commit 506716f into apache:master Sep 29, 2026
80 checks passed
villebro pushed a commit that referenced this pull request Sep 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A chart's stored query_context is never migrated, so every non-Explore path 500s on charts saved by an older version

2 participants