Skip to content

fix(api): handle DBAPI ProgrammingError on database connect in schemas and chart data - #45867

Open
eschutho wants to merge 3 commits into
apache:masterfrom
eschutho:fix-dbapi-programmingerror-connect-schemas-chartdata
Open

eschutho wants to merge 3 commits into
apache:masterfrom
eschutho:fix-dbapi-programmingerror-connect-schemas-chartdata

Conversation

@eschutho

Copy link
Copy Markdown
Member

SUMMARY

Handle sqlalchemy.exc.ProgrammingError (and other non-OperationalError DBAPIErrors) raised while connecting to a user database, instead of letting them escape as unhandled 500s with ERROR logs.

PROBLEM

Some Postgres-wire-compatible engines don't implement every GUC that SQLAlchemy's postgresql dialect queries on first connect (e.g. SHOW standard_conforming_strings). The resulting ProgrammingError is a sibling of OperationalError under DBAPIError, so it escapes two places:

  • GET /api/v1/database/<pk>/schemas/ (and /catalogs/) only catch OperationalError.
  • POST|GET /api/v1/chart/data: the connection is first opened while computing the cache key (query_cache_key -> get_extra_cache_keys -> get_default_schema -> get_inspector), which runs outside the code that wraps query-execution failures as ChartDataQueryFailedError, so a raw DBAPIError reaches _get_data_response unhandled.

FIX

  • schemas / catalogs: catch DBAPIError (these endpoints run no user SQL, so any DBAPI error is a connect/introspection failure). Response is unchanged (500, "There was an error connecting to the database"); a WARNING with traceback is now logged.
  • _get_data_response: catch DBAPIError, log at WARNING, return 422 with a generic message passed through sanitize_error_message (raw driver error / SQL is not returned).

Tradeoffs

  • Any DBAPIError raised while acquiring the chart payload (including transient connection drops) now returns 422 instead of 500.
  • Not covered: ChartDataRestApi._run_async has a similar gap for async_mode requests; left for a follow-up since the fix there differs.

TESTING INSTRUCTIONS

pytest tests/unit_tests/databases/api_test.py tests/unit_tests/charts/test_chart_data_api.py (new tests fail on master, pass here).

ADDITIONAL INFORMATION

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

🤖 Generated with Claude Code

… schemas and chart data

Some Postgres-wire-compatible engines do not implement every GUC that
SQLAlchemy's postgresql dialect queries on first connect (for example
`show standard_conforming_strings`). The resulting
`sqlalchemy.exc.ProgrammingError` is a sibling of `OperationalError`
under `DBAPIError`, so it escaped the existing handlers and surfaced as
an unhandled 500 with an ERROR log from two endpoints:

- GET /api/v1/database/<pk>/schemas/ (and the sibling /catalogs/), which
  only caught `OperationalError`.
- POST/GET /api/v1/chart/data, where the connection is first opened while
  computing the cache key (`get_default_schema` -> `get_inspector`),
  outside the code that wraps query execution failures as
  `ChartDataQueryFailedError`.

The schemas and catalogs handlers now catch `DBAPIError` (these endpoints
run no user SQL, so any DBAPI error is a connect/introspection failure),
keep the existing 500 response, and log a warning with the traceback so
the failure is not silent.

`_get_data_response` now catches `DBAPIError`, logs at WARNING, and
returns a 422 with a generic, sanitized message instead of leaking the
raw driver error or SQL. Genuine query execution failures are already
wrapped as `ChartDataQueryFailedError` and keep their 400 response.

Tradeoff: any DBAPIError acquiring the chart payload (including transient
connection drops) now returns 422 rather than 500.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the api Related to the REST API label Oct 30, 2026
Comment thread superset/charts/data/api.py Outdated
QueryObjectValidationError,
) as exc:
return self.response_400(message=sanitize_error_message(exc.message))
except DBAPIError:

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.

Suggestion: async_mode requests take _run_async before reaching this handler, so a connection failure during its cache lookup still escapes as an unhandled 500.

Assessment: 🟠 Major · 🔁 Occurrence: Sometimes · 🏷️ Incomplete implementation

Use CodeAnt Skill Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset/charts/data/api.py
**Line:** 703:703
**Comment:**
	*Incomplete Implementation: `async_mode` requests take `_run_async` before reaching this handler, so a connection failure during its cache lookup still escapes as an unhandled 500.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@codeant-ai-for-open-source

codeant-ai-for-open-source Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

CodeAnt  CodeAnt PR Risk

🟡  Medium Risk

  • The PR needs attention before merging.
  • Async chart-data requests may bypass this handler, leaving connection failures during cache lookup as 500s.

Assessed commit: d43c0ab9820b

@codecov

codecov Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 84.71%. Comparing base (6fced4f) to head (d43c0ab).
⚠️ Report is 32 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #45867      +/-   ##
==========================================
+ Coverage   84.70%   84.71%   +0.01%     
==========================================
  Files        3069     3069              
  Lines      205649   205800     +151     
  Branches    48335    48370      +35     
==========================================
+ Hits       174200   174349     +149     
+ Misses      28156    28152       -4     
- Partials     3293     3299       +6     
Flag Coverage Δ
hive 34.49% <23.52%> (-0.03%) ⬇️
mysql 52.59% <23.52%> (+0.01%) ⬆️
postgres 52.61% <23.52%> (+0.02%) ⬆️
presto 36.21% <23.52%> (-0.03%) ⬇️
python 87.77% <100.00%> (+0.01%) ⬆️
sqlite 52.36% <23.52%> (+0.01%) ⬆️
unit 82.53% <100.00%> (+0.04%) ⬆️

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.

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

Richard's agent here 👋

Reviewed at dc8a966. Thanks for working on this. Turning the dialect-init ProgrammingError into a handled, sanitized response is the right call, and the chart-data test that checks the driver detail and SQL stay out of the body is a good guard. A few things I think are worth addressing before merge:

Worth fixing before merge

  1. CI is red: new string missing from messages.pot (superset/charts/data/api.py:712)
    babel-extract and tests/unit_tests/scripts/translations/check_pot_drift_test.py both fail because _("There was an error connecting to the database") is a new msgid. Could we run ./scripts/translations/babel_update.sh and commit the regenerated messages.pot? While there, the schemas/catalogs handlers return the same text as an untranslated literal. Totally optional to share one translated string across all three.

  2. except DBAPIError also catches metadata-database errors (superset/charts/data/api.py:703, superset/databases/api.py:785, :873)
    DBAPIError is raised by Superset's own metadata DB too, not only by the configured database, and the chart-data try does several metadata queries. For example, query() builds the SQL in get_query_str_extended() before its own try (superset/models/helpers.py:2698), and that step loads RLS filters from the metadata DB (get_sqla_row_level_filters → security_manager.get_rls_filters). Native annotation layers are another such query. So if the metadata DB drops a connection, or a deploy runs ahead of a migration (ProgrammingError: column ... does not exist), that error now comes back as a 422 saying "There was an error connecting to the database", logged only at WARNING. The user is told their warehouse is broken, and the Superset-side fault drops out of 5xx/ERROR alerting. The schemas/catalogs handlers have the same issue through get_schemas_accessible_by_user / get_catalogs_accessible_by_user.
    There's already a helper for exactly this split: superset.databases.error_provenance.is_database_engine_error (#42996). It marks errors raised by configured-database engines, and nothing calls it yet. I checked on the pinned SQLAlchemy 2.0.55: an error raised from dialect.initialize() on first connect does fire the engine's handle_error listener, so the SHOW standard_conforming_strings case gets marked. Could we narrow the handlers to something like:

    except DBAPIError as ex:
        if not is_database_engine_error(ex):
            raise
        ...

    That keeps the fix for the connect failure and leaves metadata-DB and raw-cursor errors on their current path. WDYT?

Smaller suggestions

  • The same connect failure now gets a 422 from /chart/data but a 500 from /schemas/ and /catalogs/. Small suggestion: could we use the same status for both (500, or 503 for "upstream unavailable")? A 4xx tells clients and dashboards the request was at fault, and transient warehouse outages would stop showing up as server errors. Happy to keep 422 if it matches the existing ChartDataCacheLoadError convention on purpose.
  • The inline comment ("Query execution failures are wrapped as ChartDataQueryFailedError…") overstates things a bit, since SQL compilation and RLS lookup run outside that wrapping. Could we adjust it once the handler is narrowed?

Testing

  • With the marker check in place, it would be great to add one negative test: an unmarked DBAPIError (as from the metadata DB) is not turned into the 422 / "error connecting" response. The current tests raise straight from a mocked command.execute / get_all_schema_names, so they don't tell the two sources apart.

Happy to take another look once updated!

… status codes

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@pull-request-size pull-request-size Bot added size/L and removed size/M labels Oct 31, 2026
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the i18n Namespace | Anything related to localization label Oct 31, 2026
@netlify

netlify Bot commented Oct 31, 2026

Copy link
Copy Markdown

✅ Deploy Preview for superset-docs-preview ready!

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

@netlify

netlify Bot commented Oct 31, 2026

Copy link
Copy Markdown

✅ Deploy Preview for superset-docs-preview ready!

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

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 i18n Namespace | Anything related to localization size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants