Skip to content

Conversation

DamianPendrak
Copy link
Contributor

@DamianPendrak DamianPendrak commented Aug 13, 2025

SUMMARY

Introduce CUSTOM_DATABASE_ERRORS in the config to replace raw database exceptions with a custom message and optional documentation links.

custom_doc_links - contains links that will show after the error message
show_issue_info - if set to False, hides the "See more" button that contains the issue code and link to Superset documentation

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

Before

Screenshot 2025-08-12 at 09 40 07

After

Screenshot 2025-08-12 at 09 43 44

TESTING INSTRUCTIONS

  1. In SQL Lab, run a query that results in a database error, ex. "SELECT * FROM non_existing_table"
  2. Find a string that will match this error with a regex, ex. "TrinoUserError"
  3. Add the custom errors in the superset/custom_database_errors.py file or add CUSTOM_DATABASE_ERRORS to superset_config.py or other config file with this structure:
CUSTOM_DATABASE_ERRORS = {
    "examples": {
        re.compile('permission denied for view'): (
            __(
                'Permission denied'
            ),
            SupersetErrorType.GENERIC_DB_ENGINE_ERROR,
            {
                "custom_doc_links": [
                    {
                        "url": "https://example.com/docs/1",
                        "label": "Check documentation"
                    },
                ],
                "show_issue_info": False,
            }
        )
    },
}

examples - the database connection name.
permission denied for view - part of the original message.

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

@github-actions github-actions bot added the doc Namespace | Anything related to documentation label Aug 13, 2025
@dosubot dosubot bot added change:backend Requires changing the backend change:frontend Requires changing the frontend labels Aug 13, 2025
Copy link

@korbit-ai korbit-ai bot left a comment

Choose a reason for hiding this comment

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

Review by Korbit AI

Korbit automatically attempts to detect when you fix issues in new commits.
Category Issue Status
Functionality Unclear Icon Purpose for Documentation Link ▹ view 🧠 Not in standard
Performance Sequential Regex Pattern Matching ▹ view 🧠 Incorrect
Security Missing URL Validation ▹ view 🧠 Not in standard
Functionality Unsafe Issue Codes Access ▹ view 🧠 Not in scope
Files scanned
File Path Reviewed
superset-frontend/src/components/ErrorMessage/CustomDocLink.tsx
superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.tsx
docs/docs/configuration/databases.mdx
superset/db_engine_specs/base.py
superset/config.py

Explore our documentation to understand the languages and file types we support and the files we ignore.

Check out our docs on how you can make Korbit work best for you and your team.

Loving Korbit!? Share us on LinkedIn Reddit and X

const theme = useTheme();
return (
<a href={url} target="_blank" rel="noopener noreferrer">
{label} <Icons.Full iconSize="m" iconColor={theme.colorPrimary} />

This comment was marked as resolved.

export const CustomDocLink = ({ url, label }: CustomDocLinkProps) => {
const theme = useTheme();
return (
<a href={url} target="_blank" rel="noopener noreferrer">

This comment was marked as resolved.

<IssueCode {...issueCode} key={issueCode.code} />
))
.reduce((prev, curr) => [prev, <br />, curr])}
{extra.issue_codes?.flatMap((issueCode, idx, arr) => [

This comment was marked as resolved.

Comment on lines 1335 to 1359
for regex, (message, error_type, extra) in [
*config_custom_errors.items(),
*cls.custom_errors.items(),
]:
if match := regex.search(raw_message):

This comment was marked as resolved.

Copy link

codecov bot commented Aug 13, 2025

Codecov Report

❌ Patch coverage is 65.51724% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.85%. Comparing base (7f38405) to head (ad1ee1a).

Files with missing lines Patch % Lines
superset/db_engine_specs/databricks.py 0.00% 4 Missing ⚠️
superset/db_engine_specs/base.py 76.92% 1 Missing and 2 partials ⚠️
superset/config.py 50.00% 2 Missing ⚠️
superset/commands/database/validate.py 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           master   #34674       +/-   ##
===========================================
+ Coverage        0   71.85%   +71.85%     
===========================================
  Files           0      588      +588     
  Lines           0    43534    +43534     
  Branches        0     4708     +4708     
===========================================
+ Hits            0    31280    +31280     
- Misses          0    11023    +11023     
- Partials        0     1231     +1231     
Flag Coverage Δ
hive 46.26% <51.72%> (?)
mysql 70.87% <65.51%> (?)
postgres 70.93% <65.51%> (?)
presto 49.95% <51.72%> (?)
python 71.81% <65.51%> (?)
sqlite 70.52% <65.51%> (?)
unit 100.00% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 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.

@yousoph yousoph requested a review from geido August 13, 2025 18:46
@sadpandajoe
Copy link
Member

@DamianPendrak so would you need files per database as different databases could have different links and different messages? Would it be helpful to also have a link or a collapsed box that will show the original error?

@DamianPendrak
Copy link
Contributor Author

@sadpandajoe My idea was to separate them based on the regex. But now I see that it might be an issue if there is the same error message from different databases, and you can't separate them using regex. I pushed an update with a changed structure of the CUSTOM_DATABASE_ERRORS so we can separate them by database engine names, and apply them accordingly:

CUSTOM_DATABASE_ERRORS = {
  "trino": {
    re.compile(r"TrinoUserError"): (
        "Custom error message",
        SupersetErrorType.GENERIC_DB_ENGINE_ERROR,
        {
            "custom_doc_links": [
                {
                    "url": "https://example.com/docs",
                    "label": "Check documentation"
                },
            ],
        }
    ),
  },
  "postgres": {}
}

About the original error - there is a similar way to overwrite the error messages in the db engine file in superset/db_engine_specs. Currently, it only shows an issue code and a link to the documentation in a collapsed component. The original error might be useful, but then I think it should be available in both custom error messages (db_engine_specs file and superset_config.py)
Screenshot 2025-08-06 at 13 23 28

@sadpandajoe
Copy link
Member

@DamianPendrak one other question. If you want to override multiple error messages and you have multiple databases, won't the config file get huge? Wondering if that is fine or not.

@DamianPendrak
Copy link
Contributor Author

@sadpandajoe, good point. The config file could become huge, so it's a good idea to put it into a separate file. I pushed the change that imports the custom errors, but you can still overwrite them in the config file

@geido geido requested a review from Vitor-Avila August 18, 2025 17:48
# Example:
# CUSTOM_DATABASE_ERRORS = {
# "trino": {
# re.compile(r'message="(?P<message>[^"]*)"'): (
Copy link
Member

Choose a reason for hiding this comment

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

Could we perhaps make it easier to append a custom message or just the doc links to the original message? I know that this regex does that, but who likes writing regexes 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, that's probably too advanced example. I added a simpler one that matches with a part of the error message "permission denied for view". The regex is also used in the custom errors in the database engine files, so it keeps the same approach

const theme = useTheme();
return (
<a href={url} target="_blank" rel="noopener noreferrer">
{label} <Icons.Full iconSize="m" iconColor={theme.colorPrimary} />
Copy link
Member

Choose a reason for hiding this comment

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

Nit: I think the icon is not centered vertically with the text. Also, the text's color changes on hover, but icon's color doesn't

Image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

config_custom_errors = app.config.get("CUSTOM_DATABASE_ERRORS", {})
if not isinstance(config_custom_errors, dict):
config_custom_errors = {}
db_engine_custom_errors = config_custom_errors.get(cls.engine_name, {})
Copy link
Member

Choose a reason for hiding this comment

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

Should we also check for cls.engine? I tested on sqlite, and when I used sqlite as key in CUSTOM_DATABASE_ERRORS dict, it didn't work because cls.engine_name returns SQLite. Perhaps we could allow for both "human readable" and "normal" engine names?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. Works with both now

@Vitor-Avila
Copy link
Contributor

hey @DamianPendrak could it be a concern if I have 2 trino connections (maybe with different auth methods each) and I just want to show a custom error message on one of them?

@DamianPendrak
Copy link
Contributor Author

@Vitor-Avila it could be an issue if the error message is the same and cannot be distinguished from another error message using regex. If one of the auth methods returned a different error message, then it can be achieved.

Copy link
Member

@kgabryje kgabryje left a comment

Choose a reason for hiding this comment

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

LGTM

@kgabryje kgabryje added the hold! On hold label Sep 2, 2025
@kgabryje
Copy link
Member

kgabryje commented Sep 2, 2025

Added hold! label per contributor's request

@DamianPendrak
Copy link
Contributor Author

Thanks @Vitor-Avila for pointing that out. I changed the implementation to match errors with the database unique name instead of the database engine name. It allows users to create more precise custom errors. @kgabryje are you okay with reviewing the changes?

if isinstance(database_errors, dict):
db_engine_custom_errors.update(database_errors)

if not isinstance(db_engine_custom_errors, dict):
Copy link
Member

@kgabryje kgabryje Sep 11, 2025

Choose a reason for hiding this comment

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

Is there any scenario where db_engine_custom_errors is not a dict? We initialize it as dict in line 1343, and then update it only with other dicts

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's right. Removed the unnecessary check

if not isinstance(config_custom_errors, dict):
config_custom_errors = {}

db_engine_custom_errors = {}
Copy link
Member

Choose a reason for hiding this comment

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

This part is very similar to base.py. Does it make sense to DRY it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. Moved it to a method

Copy link
Contributor

🎪 Showtime deployed environment on GHA for 55d3b07

Environment: http://34.219.185.149:8080 (admin/admin)
Lifetime: 48h auto-cleanup
Updates: New commits create fresh environments automatically

Copy link
Contributor

⚠️ DEPRECATED WORKFLOW ⚠️

@kgabryje This workflow is deprecated! Please use the new Superset Showtime system instead:

Processing your ephemeral environment request here. Action: up. More information on how to use or configure ephemeral environments

Copy link
Contributor

@kgabryje Ephemeral environment spinning up at http://44.251.62.68:8080. Credentials are 'admin'/'admin'. Please allow several minutes for bootstrapping and startup.

@DamianPendrak DamianPendrak force-pushed the custom-db-error-messages branch from 55d3b07 to ad1ee1a Compare September 24, 2025 20:13
@github-actions github-actions bot added 🎪 🔒 showtime-blocked and removed 🎪 55d3b07 🌐 34.219.185.149:8080 Environment 55d3b07 URL: http://34.219.185.149:8080 (click to visit) 🎪 55d3b07 📅 2025-09-23T14-46 Environment 55d3b07 created at 2025-09-23T14-46 🎪 55d3b07 🤡 kgabryje Environment 55d3b07 requested by kgabryje 🎪 55d3b07 ⌛ 48h Environment 55d3b07 expires after 48h 🎪 55d3b07 🚦 running Environment 55d3b07 status: running labels Sep 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
change:backend Requires changing the backend change:frontend Requires changing the frontend doc Namespace | Anything related to documentation hold! On hold 🎪 🔒 showtime-blocked size/XL testenv-up
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants