You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by jl...@apache.org on 2026/09/07 04:51:22 UTC

(superset) 02/06: fix(database): retry transient schema-listing failures during permission sync

This is an automated email from the ASF dual-hosted git repository.

sadpandajoe pushed a commit to branch fix-dar-import-schema-permissions
in repository https://gitbox.apache.org/repos/asf/superset.git

commit f2ae11a9072a3efa83b2cca6276393e58a8881e8
Author: sadpandajoe <jc...@gmail.com>
AuthorDate: Sat Sep 5 21:04:11 2026 +0000

    fix(database): retry transient schema-listing failures during permission sync
    
    add_permissions() gave up on an entire catalog's schema_access grants after
    a single failed attempt at listing its schemas. The exception caught there
    (GenericDBException, a bare alias for the built-in Exception) doesn't
    distinguish a genuinely unlistable catalog from a one-off transient
    hiccup, so a schema that needed a first-time grant during a failing
    attempt was silently never granted, while the import/create still reported
    success -- with the only workaround being to later edit and re-save the
    connection. Retry the schema-listing call once, immediately, before giving
    up on a catalog, without changing which exceptions are tolerated (a
    persistently unlistable catalog, eg AWS RDS's rdsadmin, still fails both
    attempts and is skipped exactly as before).
---
 superset/commands/database/utils.py               | 20 +++++++---
 tests/unit_tests/databases/commands/utils_test.py | 45 +++++++++++++++++++++--
 2 files changed, 57 insertions(+), 8 deletions(-)

diff --git a/superset/commands/database/utils.py b/superset/commands/database/utils.py
index 0c25f8173c2..359985a7150 100644
--- a/superset/commands/database/utils.py
+++ b/superset/commands/database/utils.py
@@ -82,8 +82,20 @@ def add_permissions(database: Database) -> None:
         catalogs = [None]
 
     for catalog in catalogs:
-        try:
-            for schema in database.get_all_schema_names(catalog=catalog, cache=False):
+        # A single failure while listing a catalog's schemas is tolerated (some
+        # catalogs are visible but not listable, eg the ``rdsadmin`` catalog on
+        # AWS RDS), but the exception caught here doesn't distinguish that from
+        # a one-off transient hiccup (eg schema metadata not yet visible right
+        # after it was created). Retry once, immediately, so a schema that
+        # needs a first-time grant isn't permanently skipped over a fluke.
+        for attempt in range(2):
+            try:
+                schemas = database.get_all_schema_names(catalog=catalog, cache=False)
+            except GenericDBException:  # pylint: disable=broad-except
+                if attempt:
+                    logger.warning("Error processing catalog '%s'", catalog)
+                continue
+            for schema in schemas:
                 security_manager.add_permission_view_menu(
                     "schema_access",
                     security_manager.get_schema_perm(
@@ -92,9 +104,7 @@ def add_permissions(database: Database) -> None:
                         schema,
                     ),
                 )
-        except GenericDBException:  # pylint: disable=broad-except
-            logger.warning("Error processing catalog '%s'", catalog)
-            continue
+            break
 
 
 def add_vm(
diff --git a/tests/unit_tests/databases/commands/utils_test.py b/tests/unit_tests/databases/commands/utils_test.py
index 266c4e5d864..1f81c4d30ce 100644
--- a/tests/unit_tests/databases/commands/utils_test.py
+++ b/tests/unit_tests/databases/commands/utils_test.py
@@ -78,14 +78,20 @@ def test_add_permissions_get_default_catalog(mocker: MockerFixture):
 
 def test_add_permissions_handle_failures(mocker: MockerFixture) -> None:
     """
-    Test adding permissions to a database when it's created in case
-    the request to get all schemas for one fo the catalogs fail.
+    Test adding permissions to a database when it's created in case the
+    request to get all schemas for one of the catalogs keeps failing --
+    each failing catalog is retried once before being given up on.
     """
     database = mocker.MagicMock()
     database.database_name = "my_db"
     database.db_engine_spec.supports_catalog = True
     database.get_all_catalog_names.return_value = ["catalog1", "catalog2", "catalog3"]
-    database.get_all_schema_names.side_effect = [["schema1"], Exception, ["schema3"]]
+    database.get_all_schema_names.side_effect = [
+        ["schema1"],
+        Exception,
+        Exception,
+        ["schema3"],
+    ]
     add_permission_view_menu = mocker.patch(
         "superset.commands.database.importers.v1.utils.security_manager."
         "add_permission_view_menu"
@@ -102,3 +108,36 @@ def test_add_permissions_handle_failures(mocker: MockerFixture) -> None:
             mocker.call("schema_access", "[my_db].[catalog3].[schema3]"),
         ]
     )
+
+
+def test_add_permissions_retries_transient_failure(mocker: MockerFixture) -> None:
+    """
+    A catalog whose schema listing fails only once (eg a transient driver
+    hiccup) must still get its schema permissions granted via the retry,
+    instead of being permanently skipped like a genuinely unlistable
+    catalog would be.
+    """
+    database = mocker.MagicMock()
+    database.database_name = "my_db"
+    database.db_engine_spec.supports_catalog = True
+    database.get_all_catalog_names.return_value = ["catalog1", "catalog2"]
+    database.get_all_schema_names.side_effect = [
+        ["schema1"],
+        Exception,
+        ["schema2"],
+    ]
+    add_permission_view_menu = mocker.patch(
+        "superset.commands.database.importers.v1.utils.security_manager."
+        "add_permission_view_menu"
+    )
+
+    add_permissions(database)
+
+    add_permission_view_menu.assert_has_calls(
+        [
+            mocker.call("catalog_access", "[my_db].[catalog1]"),
+            mocker.call("catalog_access", "[my_db].[catalog2]"),
+            mocker.call("schema_access", "[my_db].[catalog1].[schema1]"),
+            mocker.call("schema_access", "[my_db].[catalog2].[schema2]"),
+        ]
+    )