Skip to content

fix(chart-data): roll back db.session after swallowed metadata-DB errors - #44653

Merged
alexandrusoare merged 9 commits into
masterfrom
alexandrusoare/fix/pending-rollback-chart-data-impersonation
Sep 29, 2026
Merged

alexandrusoare merged 9 commits into
masterfrom
alexandrusoare/fix/pending-rollback-chart-data-impersonation

Conversation

@alexandrusoare

@alexandrusoare alexandrusoare commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

SUMMARY

Chart-data requests could fail with PendingRollbackError from security_manager.find_user() in Database._get_sqla_engine().

That lookup isn't the cause. Earlier steps in the same request catch a DB error, treat it as non-fatal, and carry on without rolling back — leaving db.session in a failed transaction. Every later statement on that session then fails with PendingRollbackError, so whoever reads it next takes the blame. Guarding the lookup alone would just move the error to the next reader.

Fix — repair the session where it breaks:

  • SupersetMetastoreCache.set/get roll back and re-raise, matching what add and compare_and_set already do. This covers every metastore-cache caller rather than individual call sites, and leaves a Redis-backed cache untouched.
  • utils/rls.py, models/helpers.py and jinja_context.py roll back on a swallowed DB error. This completes fix(rls): roll back db.session after RLS failure in get_from_clause #43883, which fixed the same bug in a sibling function and missed these.

Also: the impersonation email lookup existed in four copies (core plus StarRocks, Snowflake, gsheets), each able to fail the same way. Consolidated into find_user_for_impersonation() behind two Database methods, so engine
specs no longer do their own lookup. It rolls back and retries once on PendingRollbackError; any other SQLAlchemyError describes the lookup's own failure and propagates untouched.

Two behaviour changes worth flagging:

  • The lookup now raises SupersetErrorException instead of the raw PendingRollbackError, and retries once first. It already raised before this PR, so there's nothing for UPDATING.md. It doesn't fall back to the un-resolved login (what the ticket proposed) — that value is the identity the analytic DB connects as, so falling back could query as the wrong principal.
  • Fixes a latent gsheets bug: it looked up a username that had already been substituted to an email prefix, so subject was silently left unset whenever login and prefix differed.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

TESTING INSTRUCTIONS

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

@netlify

netlify Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit deaabeb
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6aba462c1ad92a00083344a0
😎 Deploy Preview https://deploy-preview-44653--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 Sep 25, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.52542% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.14%. Comparing base (f3e3c3f) to head (3548c6d).
⚠️ Report is 81 commits behind head on master.

Files with missing lines Patch % Lines
superset/models/core.py 85.71% 1 Missing and 1 partial ⚠️
superset/db_engine_specs/gsheets.py 66.66% 0 Missing and 1 partial ⚠️
superset/db_engine_specs/snowflake.py 66.66% 0 Missing and 1 partial ⚠️
superset/db_engine_specs/starrocks.py 50.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #44653      +/-   ##
==========================================
- Coverage   81.22%   81.14%   -0.08%     
==========================================
  Files        2968     2963       -5     
  Lines      179399   179020     -379     
  Branches    41601    41203     -398     
==========================================
- Hits       145719   145271     -448     
- Misses      30970    31027      +57     
- Partials     2710     2722      +12     
Flag Coverage Δ
hive 36.69% <23.72%> (-0.04%) ⬇️
mysql 55.98% <35.59%> (-0.08%) ⬇️
postgres 55.99% <35.59%> (-0.08%) ⬇️
presto 38.62% <23.72%> (-0.01%) ⬇️
python 85.66% <91.52%> (+0.16%) ⬆️
sqlite 55.71% <35.59%> (-0.08%) ⬇️
unit 78.40% <89.83%> (+0.60%) ⬆️

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.

…andrusoare/fix/pending-rollback-chart-data-impersonation
@github-actions github-actions Bot added the i18n Namespace | Anything related to localization label Sep 28, 2026
@alexandrusoare
alexandrusoare marked this pull request as ready for review September 28, 2026 11:08
Comment thread superset/jinja_context.py Resolved
Comment thread superset/models/core.py Resolved

@EnxDev EnxDev 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.

The diagnosis holds up, and consolidating the four impersonation lookups into one is a good cleanup.

Left a few notes inline. The main ones: the cache rollbacks might sit better inside SupersetMetastoreCache, the retry catches more than it needs to, and the five rollback sites have no tests yet (codecov patch is at 29%).

One small thing on the description: the old find_user call had no try either, so a failing lookup already raised before this. The "raises instead of falling back" change is really a different exception type plus the retry, which is good news since nothing breaking needs to go in UPDATING.md.

Comment thread superset/db_engine_specs/gsheets.py Outdated
# substituted the email prefix into ``username``, so looking it up
# here as if it were still the login finds nothing whenever the two
# differ, silently leaving the subject unset.
if email := database.get_impersonation_email():

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.

This fixes the prefix bug, but the updated test mocks get_impersonation_email on a MagicMock, so nothing would catch it coming back.

Worth adding a test with a real Database, IMPERSONATE_WITH_EMAIL_PREFIX on, login alice and email alice.doe@example.org, asserting subject is the full email?

Comment thread superset/utils/database.py Resolved
Comment thread superset/utils/rls.py Resolved

@bito-code-review bito-code-review Bot 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.

Code Review Agent Run #7d49e8

Actionable Suggestions - 3
  • superset/models/helpers.py - 1
  • tests/unit_tests/extensions/metastore_cache_test.py - 2
Additional Suggestions - 6
  • tests/unit_tests/models/core_test.py - 1
    • Test setup duplication · Line 851-920
      The three new tests repeat identical setup: `Database(database_name="my_db", sqlalchemy_uri="trino://", impersonate_user=True)` and the `get_username` patch (lines 867-871, 893-897, 913-917). A shared helper would remove the duplication and keep the email-prefix cases in sync.
  • superset/utils/database.py - 1
    • Inline imports lack cycle comment · Line 155-159
      These four function-level imports carry only a `pylint: disable` marker. BITO rule 12745 allows inline imports solely to break circular dependencies and requires an explanatory comment naming the cycle. None is present. If `superset.models.core` -> `superset.utils.database` (models/core.py:91) is the cycle, document it; otherwise hoist the imports to module top.
  • tests/unit_tests/utils/rls_test.py - 1
    • Weak assertion in parse-failure block · Line 261-266
      The second block discards `collect_rls_predicates_for_sql`'s return value and asserts only the rollback side effect, so this block alone would not catch a changed parse-failure fallback (e.g. returning an empty list) under a mocked `db`. Per rule 9460, assert the computed value too: capture the result and compare it to the user-42 sentinel, mirroring the first block.
  • superset/utils/rls.py - 1
    • Rollback may discard pending writes · Line 338-339
      The rollback on any `SQLAlchemyError` mirrors the session-poisoning concern in `find_user_for_impersonation`, but that helper deliberately narrows rollback to `PendingRollbackError` because a blanket rollback can silently discard the caller's uncommitted session writes (superset/utils/database.py:140). If `collect_rls_predicates_for_sql` ever runs while `db.session` holds pending writes, an unrelated IntegrityError here would discard them. Worth confirming the read-only assumption. ([CWE-460](https://cwe.mitre.org/data/definitions/460.html))
  • tests/unit_tests/extensions/metastore_cache_test.py - 1
    • Untyped mock variable · Line 52-52
      Per the repo rule requiring explicit annotations for mock variables in test files, `session = mocker.patch("superset.extensions.metastore_cache.db").session` (also lines 68 and 84) is untyped; mypy infers `Any` here, so a typo like `session.rollbck` would not be caught. Annotate as `session: MagicMock` with `from unittest.mock import MagicMock`.
  • superset/models/helpers.py - 1
    • Blind exception catch in RLS retry · Line 3813-3813
      Catching a blind `Exception` at line 3813 is flagged by BLE001. Consider catching `SQLAlchemyError` explicitly and adding a separate `except Exception` for the fail-closed path, or add `# noqa: BLE001` if the broad catch is intentional.
Review Details
  • Files reviewed - 19 · Commit Range: 47aa423..43c79b2
    • superset/common/utils/query_cache_manager.py
    • superset/db_engine_specs/gsheets.py
    • superset/db_engine_specs/snowflake.py
    • superset/db_engine_specs/starrocks.py
    • superset/extensions/metastore_cache.py
    • superset/jinja_context.py
    • superset/models/core.py
    • superset/models/helpers.py
    • superset/translations/messages.pot
    • superset/utils/database.py
    • superset/utils/rls.py
    • tests/unit_tests/db_engine_specs/test_gsheets.py
    • tests/unit_tests/db_engine_specs/test_starrocks.py
    • tests/unit_tests/extensions/metastore_cache_test.py
    • tests/unit_tests/jinja_context_test.py
    • tests/unit_tests/models/core_test.py
    • tests/unit_tests/models/helpers_rls_rollback_test.py
    • tests/unit_tests/utils/rls_test.py
    • tests/unit_tests/utils/test_database.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/models/helpers.py Resolved

@EnxDev EnxDev 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.

Second pass, on top of my earlier review.

Everything from last time is in: the rollback lives in SupersetMetastoreCache.get/set now, the retry only catches PendingRollbackError, the rls/jinja/helpers sites only roll back on a DB error, and every site has a test that asserts the rollback. The gsheets test goes through a real Database and would fail on the old lookup. CI is green. LGTM from my side.

One small gap left inline on Snowflake. The description still says a failed lookup "now raises instead of falling back", which the old code never did. Might be worth a tweak so nobody goes hunting for an UPDATING.md entry.

Comment thread superset/db_engine_specs/snowflake.py Resolved
Comment thread superset/utils/rls.py Resolved
Comment thread superset/db_engine_specs/gsheets.py Resolved
Comment thread superset/db_engine_specs/snowflake.py Resolved
Comment thread superset/utils/database.py Resolved
@bito-code-review

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

Copy link
Copy Markdown
Contributor

Code Review Agent Run #da509d

Actionable Suggestions - 0
Review Details
  • Files reviewed - 9 · Commit Range: 43c79b2..2eb8ed4
    • superset/models/helpers.py
    • tests/unit_tests/db_engine_specs/test_snowflake.py
    • tests/unit_tests/extensions/metastore_cache_test.py
    • tests/unit_tests/models/helpers_rls_rollback_test.py
    • superset/db_engine_specs/gsheets.py
    • superset/db_engine_specs/snowflake.py
    • superset/utils/rls.py
    • tests/unit_tests/db_engine_specs/test_gsheets.py
    • tests/unit_tests/utils/rls_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 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

@alexandrusoare
alexandrusoare merged commit e65de1f into master Sep 29, 2026
94 checks passed
@alexandrusoare
alexandrusoare deleted the alexandrusoare/fix/pending-rollback-chart-data-impersonation branch September 29, 2026 15:05
@bito-code-review

Copy link
Copy Markdown
Contributor

Bito Automatic Review Skipped – PR Already Merged

Bito scheduled an automatic review for this pull request, but the review was skipped because this PR was merged before the review could be run.
No action is needed if you didn't intend to review it. To get a review, you can type /review in a comment and save it

villebro pushed a commit that referenced this pull request Sep 30, 2026
villebro added a commit that referenced this pull request Sep 30, 2026
Native run of scripts/translations/babel_update.sh after the second
backport pass. Picks up the parameterized annotation-chart query-context
message (#44584) and the impersonation-lookup error (#44653), whose
master-side messages.pot hunks conflicted and were resolved to 7.0's
side, and propagates every newly backported msgid into the .po catalogs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

i18n Namespace | Anything related to localization size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants