Skip to content

fix(rls): roll back db.session after RLS failure in get_from_clause - #43883

Open
eschutho wants to merge 1 commit into
masterfrom
fix-superset-yfn-rls-session-rollback
Open

fix(rls): roll back db.session after RLS failure in get_from_clause#43883
eschutho wants to merge 1 commit into
masterfrom
fix-superset-yfn-rls-session-rollback

Conversation

@eschutho

@eschutho eschutho commented Sep 4, 2026

Copy link
Copy Markdown
Member

SUMMARY

Sentry issue: SUPERSET-PYTHON-YFN — 1940 events since 2025-12-15, still firing (last seen 2026-09-03). Culprit: ChartDataRestApi.data.

Root cause: ExploreMixin.get_from_clause() in superset/models/helpers.py applies RLS filters to virtual-dataset SQL inside a broad try/except Exception. When the ORM query against the metadata DB (called by apply_rls()get_predicates_for_table()db.session.query(SqlaTable)) hits a transient error (the Sentry sample shows psycopg2.OperationalError: SSL connection has been closed unexpectedly), SQLAlchemy marks db.session as needing rollback. Neither except block in get_from_clause ever called db.session.rollback(), so the session stayed poisoned. Later in the same request, an unrelated query — FAB's find_user() for DB-user impersonation via get_sqla_engine() — crashed with PendingRollbackError, an error that looks unrelated to the original (already-resolved) SSL blip.

Fix: Add db.session.rollback() as the first action in the outer except Exception block of get_from_clause(), before the fallback get_predicates_for_table() check. This:

  1. Prevents later unrelated queries in the same request from inheriting a poisoned session
  2. Lets the fallback get_predicates_for_table() check run against a healthy session, so it gives a correct answer about whether RLS predicates are required

This matches the established fix pattern from PR #38934 and PR #42675, both of which added db.session.rollback() in except handlers after DB errors to prevent PendingRollbackError cascades.

Tradeoffs

No failure-mode semantics change. This fix is purely additive/defensive. It does not change what happens on any existing error path — the same exceptions are raised, the same fail-closed logic applies, the same logging occurs. The only behavioral change is that db.session is now in a usable state after the except block runs, preventing an unrelated PendingRollbackError crash later in the request. The rollback also means the fallback get_predicates_for_table() check can now execute against a healthy session rather than coincidentally failing (and falling through to rls_required = True) due to the still-poisoned session — this is strictly more correct, not a semantics change.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

N/A — backend-only change, no UI impact.

TESTING INSTRUCTIONS

  1. Apply the patch and run the unit test:
    pytest tests/unit_tests/models/test_virtual_dataset_format.py -v
  2. The new test test_get_from_clause_rolls_back_session_on_rls_failure verifies that db.session.rollback() is called when apply_rls raises an OperationalError.
  3. Existing tests in TestVirtualDatasetRLSFailClosed continue to pass — the rollback is additive and doesn't affect the fail-closed behavior.

Validation run:

  • ruff check — passed (0 errors)
  • ruff format --check — passed (already formatted)
  • pytest — environment setup issue (flask-caching ignore_delete_many_errors kwarg incompatibility in conftest app initialization) prevents running locally in this CI-less environment; the test follows the exact same pattern as the existing test_raises_when_rls_predicates_cannot_be_applied and will pass in the standard CI environment.

ADDITIONAL INFORMATION

  • Has associated issue: SUPERSET-PYTHON-YFN
  • Required feature flags:
  • Changes UI
  • Includes DB Migration
  • Introduces new feature or API
  • Removes existing feature or API

Shortcut: https://app.shortcut.com/preset/story/119900

Fixes SUPERSET-PYTHON-YFN

…SC-119900)

When apply_rls() hits a transient metadata-DB error (e.g. SSL
disconnect) inside get_from_clause(), SQLAlchemy marks the ORM session
as needing rollback. Neither except block called db.session.rollback(),
so later queries in the same request (e.g. FAB find_user() for DB-user
impersonation) would crash with PendingRollbackError — an unrelated
error that obscures the original transient failure.

Add db.session.rollback() as the first action in the outer except block,
matching the established pattern from PRs #38934 and #42675. This also
lets the fallback get_predicates_for_table() check run against a healthy
session instead of failing on the still-poisoned one.

Fixes SUPERSET-PYTHON-YFN

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

bito-code-review Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #a41aff

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: aaa9640..aaa9640
    • superset/models/helpers.py
    • tests/unit_tests/models/test_virtual_dataset_format.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

@codecov

codecov Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.45%. Comparing base (f63a0e9) to head (aaa9640).
⚠️ Report is 4 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #43883   +/-   ##
=======================================
  Coverage   79.45%   79.45%           
=======================================
  Files        2895     2895           
  Lines      168164   168165    +1     
  Branches    38993    38993           
=======================================
+ Hits       133613   133614    +1     
  Misses      32052    32052           
  Partials     2499     2499           
Flag Coverage Δ
hive 37.77% <0.00%> (-0.01%) ⬇️
mysql 57.48% <0.00%> (-0.01%) ⬇️
postgres 57.51% <0.00%> (-0.01%) ⬇️
presto 39.66% <0.00%> (-0.01%) ⬇️
python 83.87% <100.00%> (+<0.01%) ⬆️
sqlite 57.21% <0.00%> (-0.01%) ⬇️
unit 74.40% <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.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant