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

yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.1 by this push:
     new cabcf2549d7 branch-4.1:[fix](iceberg) Skip concurrently dropped nested 
namespaces (#69628)
cabcf2549d7 is described below

commit cabcf2549d7658bd40c5088a0eaf8558bd787287
Author: daidai <[email protected]>
AuthorDate: Tue Oct 27 09:51:53 2026 +0800

    branch-4.1:[fix](iceberg) Skip concurrently dropped nested namespaces 
(#69628)
    
    ### What problem does this PR solve?
    
    Issue Number: N/A
    
    Related PR: #68248
    
    Problem Summary:
    When `iceberg.rest.nested-namespace-enabled=true`, Iceberg REST
    namespace discovery recursively lists child namespaces. If another
    session drops a child between listing the parent and listing its
    children, `NoSuchNamespaceException` currently aborts the entire `SHOW
    DATABASES` result.
    
    This change catches `NoSuchNamespaceException` only at the child
    recursion boundary. The stale child and its descendants are skipped,
    while failures to list the root and other authorization or service
    failures are still propagated. The behavior matches the existing master
    implementation from #68248.
    
    ### Release note
    
    Fix Iceberg REST nested namespace listing when another session drops a
    namespace concurrently.
    
    ### Check List (For Author)
    
    - Test
        - [ ] Regression test
        - [x] Unit Test
        - [ ] Manual test (add detailed scripts or steps below)
        - [ ] No need to test or manual test. Explain why:
    - [ ] This is a refactor/code format and no logic has been changed.
            - [ ] Previous test can cover this change.
            - [ ] No code files have been changed.
            - [ ] Other reason
    
    - Behavior changed:
        - [ ] No.
    - [x] Yes. A concurrently dropped child namespace no longer aborts the
    catalog listing.
    
    - Does this need documentation?
        - [x] No.
        - [ ] Yes.
    
    ### Check List (For Reviewer who merge this PR)
    
    - [ ] Confirm the release note
    - [ ] Confirm test cases
    - [ ] Confirm document
    - [ ] Add branch pick label
---
 .../datasource/iceberg/IcebergMetadataOps.java     | 15 +++--
 .../datasource/iceberg/IcebergMetadataOpTest.java  | 78 ++++++++++++++++++++++
 2 files changed, 89 insertions(+), 4 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergMetadataOps.java
 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergMetadataOps.java
index 2d131f31f12..3af3e027054 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergMetadataOps.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergMetadataOps.java
@@ -196,10 +196,17 @@ public class IcebergMetadataOps implements 
ExternalMetadataOps {
         if (nestedNamespaceEnabled) {
             return nsCatalog.listNamespaces(parentNs)
                     .stream()
-                    .flatMap(childNs -> Stream.concat(
-                            Stream.of(childNs.toString()),
-                            listNestedNamespaces(childNs).stream()
-                    )).collect(Collectors.toList());
+                    .flatMap(childNs -> {
+                        try {
+                            List<String> descendants = 
listNestedNamespaces(childNs);
+                            return 
Stream.concat(Stream.of(childNs.toString()), descendants.stream());
+                        } catch (NoSuchNamespaceException e) {
+                            // A child can be dropped after its parent was 
listed. Skip that branch,
+                            // including the stale child name, without hiding 
failures to list the root.
+                            LOG.debug("Namespace {} was dropped during 
listing", childNs, e);
+                            return Stream.empty();
+                        }
+                    }).collect(Collectors.toList());
         }
 
         return nsCatalog.listNamespaces(parentNs)
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/datasource/iceberg/IcebergMetadataOpTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/iceberg/IcebergMetadataOpTest.java
index 5cff492e473..15236f8354f 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/datasource/iceberg/IcebergMetadataOpTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/iceberg/IcebergMetadataOpTest.java
@@ -24,6 +24,7 @@ import 
org.apache.doris.common.security.authentication.ExecutionAuthenticator;
 import org.apache.doris.datasource.CatalogProperty;
 import org.apache.doris.datasource.ExternalDatabase;
 import org.apache.doris.datasource.hive.HMSExternalCatalog;
+import org.apache.doris.datasource.property.metastore.IcebergRestProperties;
 import org.apache.doris.nereids.trees.plans.commands.info.CreateTableInfo;
 
 import org.apache.iceberg.CatalogProperties;
@@ -35,6 +36,9 @@ import org.apache.iceberg.catalog.Namespace;
 import org.apache.iceberg.catalog.SupportsNamespaces;
 import org.apache.iceberg.catalog.TableIdentifier;
 import org.apache.iceberg.catalog.ViewCatalog;
+import org.apache.iceberg.exceptions.ForbiddenException;
+import org.apache.iceberg.exceptions.NoSuchNamespaceException;
+import org.apache.iceberg.exceptions.ServiceFailureException;
 import org.junit.Assert;
 import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentCaptor;
@@ -57,6 +61,80 @@ import java.util.concurrent.atomic.AtomicInteger;
 
 public class IcebergMetadataOpTest {
 
+    @Test
+    public void testListDatabaseNamesSkipsNamespacesDeletedDuringTraversal() {
+        Catalog icebergCatalog = Mockito.mock(Catalog.class,
+                
Mockito.withSettings().extraInterfaces(SupportsNamespaces.class));
+        SupportsNamespaces nsCatalog = (SupportsNamespaces) icebergCatalog;
+        Namespace deleted = Namespace.of("deleted");
+        Namespace parent = Namespace.of("parent");
+        Namespace deletedChild = Namespace.of("parent", "deleted");
+        Namespace emptyChild = Namespace.of("parent", "empty");
+        Namespace sibling = Namespace.of("sibling");
+
+        
Mockito.when(nsCatalog.listNamespaces(Namespace.empty())).thenReturn(Arrays.asList(deleted,
 parent, sibling));
+        
Mockito.when(nsCatalog.listNamespaces(parent)).thenReturn(Arrays.asList(deletedChild,
 emptyChild));
+        
Mockito.when(nsCatalog.listNamespaces(emptyChild)).thenReturn(Collections.emptyList());
+        Mockito.when(nsCatalog.listNamespaces(deleted))
+                .thenThrow(new NoSuchNamespaceException("Namespace does not 
exist: %s", deleted));
+        Mockito.when(nsCatalog.listNamespaces(deletedChild))
+                .thenThrow(new NoSuchNamespaceException("Namespace does not 
exist: %s", deletedChild));
+
+        List<String> result = 
nestedNamespaceOps(icebergCatalog).listDatabaseNames();
+
+        Assert.assertEquals(Arrays.asList("parent", "parent.empty", 
"sibling"), result);
+    }
+
+    @Test
+    public void testListDatabaseNamesPropagatesMissingRootNamespace() {
+        Catalog icebergCatalog = Mockito.mock(Catalog.class,
+                
Mockito.withSettings().extraInterfaces(SupportsNamespaces.class));
+        SupportsNamespaces nsCatalog = (SupportsNamespaces) icebergCatalog;
+        NoSuchNamespaceException failure = new 
NoSuchNamespaceException("Missing root");
+        
Mockito.when(nsCatalog.listNamespaces(Namespace.empty())).thenThrow(failure);
+
+        RuntimeException exception = 
Assert.assertThrows(RuntimeException.class,
+                () -> nestedNamespaceOps(icebergCatalog).listDatabaseNames());
+
+        Assert.assertSame(failure, exception.getCause());
+    }
+
+    @Test
+    public void testListDatabaseNamesPropagatesOtherTraversalFailures() {
+        for (RuntimeException failure : Arrays.asList(
+                new ForbiddenException("Access denied"), new 
ServiceFailureException("Service unavailable"))) {
+            Catalog icebergCatalog = Mockito.mock(Catalog.class,
+                    
Mockito.withSettings().extraInterfaces(SupportsNamespaces.class));
+            SupportsNamespaces nsCatalog = (SupportsNamespaces) icebergCatalog;
+            Namespace child = Namespace.of("child");
+            Mockito.when(nsCatalog.listNamespaces(Namespace.empty()))
+                    .thenReturn(Collections.singletonList(child));
+            Mockito.when(nsCatalog.listNamespaces(child)).thenThrow(failure);
+
+            RuntimeException exception = 
Assert.assertThrows(RuntimeException.class,
+                    () -> 
nestedNamespaceOps(icebergCatalog).listDatabaseNames());
+
+            Assert.assertSame(failure, exception.getCause());
+        }
+    }
+
+    private IcebergMetadataOps nestedNamespaceOps(Catalog icebergCatalog) {
+        IcebergRestExternalCatalog dorisCatalog = 
Mockito.mock(IcebergRestExternalCatalog.class);
+        CatalogProperty catalogProperty = Mockito.mock(CatalogProperty.class);
+        IcebergRestProperties restProperties = 
Mockito.mock(IcebergRestProperties.class);
+        IcebergCatalogResourceTracker tracker = new 
IcebergCatalogResourceTracker();
+
+        
Mockito.when(restProperties.isIcebergRestNestedNamespaceEnabled()).thenReturn(true);
+        
Mockito.when(catalogProperty.getMetastoreProperties()).thenReturn(restProperties);
+        
Mockito.when(dorisCatalog.getCatalogProperty()).thenReturn(catalogProperty);
+        Mockito.when(dorisCatalog.getExecutionAuthenticator()).thenReturn(new 
ExecutionAuthenticator() {
+        });
+        
Mockito.when(dorisCatalog.getProperties()).thenReturn(Collections.emptyMap());
+        Mockito.when(dorisCatalog.beginCatalogOperation(Mockito.any()))
+                .thenAnswer(invocation -> tracker.beginOperation());
+        return new IcebergMetadataOps(dorisCatalog, icebergCatalog);
+    }
+
     @Test
     public void testCatalogOperationDelaysGenerationRetirement() throws 
Exception {
         IcebergExternalCatalog dorisCatalog = 
Mockito.mock(IcebergExternalCatalog.class);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to