You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by at...@apache.org on 2026/08/05 10:32:00 UTC

(iceberg-go) branch main updated: fix(hive): propagate object-loading failures during listing (#1572)

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

laskoviymishka pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-go.git


The following commit(s) were added to refs/heads/main by this push:
     new efcbcaf39 fix(hive): propagate object-loading failures during listing (#1572)
efcbcaf39 is described below

commit efcbcaf3983b5096d7e2fd7668d19d7b5d5f6309
Author: Minh Vu <vu...@gmail.com>
AuthorDate: Wed Aug 5 12:31:55 2026 +0200

    fix(hive): propagate object-loading failures during listing (#1572)
    
    * fix(hive): propagate object-loading failures during listing
    
    Signed-off-by: Minh Vu <vu...@gmail.com>
    
    * test(hive): verify listing error context
    
    Signed-off-by: Minh Vu <vu...@gmail.com>
    
    * fix(hive): narrow missing object classification
    
    * test(hive): remove redundant listing assertion
    
    * style(hive): satisfy nlreturn in listing tests
    
    ---------
    
    Signed-off-by: Minh Vu <vu...@gmail.com>
---
 catalog/hive/hive.go      |  26 ++++++++----
 catalog/hive/hive_test.go | 102 +++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 120 insertions(+), 8 deletions(-)

diff --git a/catalog/hive/hive.go b/catalog/hive/hive.go
index 1c916a3cc..f5e79474a 100644
--- a/catalog/hive/hive.go
+++ b/catalog/hive/hive.go
@@ -150,7 +150,13 @@ func (c *Catalog) ListTables(ctx context.Context, namespace table.Identifier) it
 		for _, tableName := range tableNames {
 			tbl, err := c.client.GetTable(ctx, database, tableName)
 			if err != nil {
-				continue
+				if isNoSuchObjectError(err) {
+					continue
+				}
+
+				yield(table.Identifier{}, fmt.Errorf("failed to load table %s.%s while listing: %w", database, tableName, err))
+
+				return
 			}
 			if isIcebergTable(tbl) {
 				if !yield(TableIdentifier(database, tableName), nil) {
@@ -675,8 +681,13 @@ func (c *Catalog) ListViews(ctx context.Context, namespace table.Identifier) ite
 		for _, viewName := range viewNames {
 			tbl, err := c.client.GetTable(ctx, database, viewName)
 			if err != nil {
-				// Skip tables we fail to load, mirroring table listing behavior.
-				continue
+				if isNoSuchObjectError(err) {
+					continue
+				}
+
+				yield(table.Identifier{}, fmt.Errorf("failed to load view %s.%s while listing: %w", database, viewName, err))
+
+				return
 			}
 			if isIcebergView(tbl) {
 				if !yield(TableIdentifier(database, viewName), nil) {
@@ -1027,11 +1038,12 @@ func isNoSuchObjectError(err error) bool {
 		return false
 	}
 
-	errStr := err.Error()
+	var noSuchObjectErr *hive_metastore.NoSuchObjectException
+	if errors.As(err, &noSuchObjectErr) {
+		return true
+	}
 
-	return strings.Contains(errStr, "NoSuchObjectException") ||
-		strings.Contains(errStr, "not found") ||
-		strings.Contains(errStr, "does not exist")
+	return strings.Contains(err.Error(), "NoSuchObjectException")
 }
 
 func isAlreadyExistsError(err error) bool {
diff --git a/catalog/hive/hive_test.go b/catalog/hive/hive_test.go
index 734feeaa8..f4f3095de 100644
--- a/catalog/hive/hive_test.go
+++ b/catalog/hive/hive_test.go
@@ -205,7 +205,7 @@ var testIcebergHiveView = &hive_metastore.Table{
 
 // Error helpers for mocking
 var (
-	errNoSuchObject     = errors.New("NoSuchObjectException: object not found")
+	errNoSuchObject     = &hive_metastore.NoSuchObjectException{}
 	errAlreadyExists    = errors.New("AlreadyExistsException: object already exists")
 	errInvalidOperation = errors.New("InvalidOperationException: Database is not empty")
 )
@@ -280,6 +280,65 @@ func TestHiveListTablesEmpty(t *testing.T) {
 	mockClient.AssertExpectations(t)
 }
 
+func TestHiveListTablesHandlesObjectLoadingErrors(t *testing.T) {
+	t.Run("skips concurrently deleted tables", func(t *testing.T) {
+		mockClient := &mockHiveClient{}
+		mockClient.On("GetTables", mock.Anything, "test_database", "*").Return([]string{"deleted", "test_table"}, nil).Once()
+		mockClient.On("GetTable", mock.Anything, "test_database", "deleted").Return(nil, errNoSuchObject).Once()
+		mockClient.On("GetTable", mock.Anything, "test_database", "test_table").Return(testIcebergHiveTable1, nil).Once()
+
+		var tables []table.Identifier
+		for identifier, err := range NewCatalogWithClient(mockClient, iceberg.Properties{}).ListTables(context.Background(), DatabaseIdentifier("test_database")) {
+			require.NoError(t, err)
+			tables = append(tables, identifier)
+		}
+		require.Equal(t, []table.Identifier{TableIdentifier("test_database", "test_table")}, tables)
+		mockClient.AssertExpectations(t)
+	})
+
+	t.Run("propagates operational failures", func(t *testing.T) {
+		loadErr := errors.New("metastore host not found")
+		mockClient := &mockHiveClient{}
+		mockClient.On("GetTables", mock.Anything, "test_database", "*").Return([]string{"deleted", "broken", "test_table"}, nil).Once()
+		mockClient.On("GetTable", mock.Anything, "test_database", "deleted").Return(nil, errNoSuchObject).Once()
+		mockClient.On("GetTable", mock.Anything, "test_database", "broken").Return(nil, loadErr).Once()
+
+		var gotErr error
+		for identifier, err := range NewCatalogWithClient(mockClient, iceberg.Properties{}).ListTables(context.Background(), DatabaseIdentifier("test_database")) {
+			if err != nil {
+				require.Equal(t, table.Identifier{}, identifier)
+				gotErr = err
+
+				break
+			}
+		}
+		require.ErrorIs(t, gotErr, loadErr)
+		require.ErrorContains(t, gotErr, "failed to load table test_database.broken while listing")
+		mockClient.AssertNumberOfCalls(t, "GetTable", 2)
+		mockClient.AssertExpectations(t)
+	})
+}
+
+func TestIsNoSuchObjectError(t *testing.T) {
+	t.Run("recognizes typed thrift exception", func(t *testing.T) {
+		require.True(t, isNoSuchObjectError(&hive_metastore.NoSuchObjectException{}))
+	})
+
+	t.Run("recognizes wrapped thrift exception", func(t *testing.T) {
+		err := errors.Join(errors.New("failed to load object"), &hive_metastore.NoSuchObjectException{})
+
+		require.True(t, isNoSuchObjectError(err))
+	})
+
+	t.Run("recognizes narrow exception name fallback", func(t *testing.T) {
+		require.True(t, isNoSuchObjectError(errors.New("NoSuchObjectException: object disappeared")))
+	})
+
+	t.Run("does not classify operational not-found message", func(t *testing.T) {
+		require.False(t, isNoSuchObjectError(errors.New("metastore host not found")))
+	})
+}
+
 func TestHiveListNamespaces(t *testing.T) {
 	assert := require.New(t)
 
@@ -1477,6 +1536,47 @@ func TestHiveListViews(t *testing.T) {
 	mockClient.AssertExpectations(t)
 }
 
+func TestHiveListViewsHandlesObjectLoadingErrors(t *testing.T) {
+	t.Run("skips concurrently deleted views", func(t *testing.T) {
+		mockClient := &mockHiveClient{}
+		mockClient.On("GetTables", mock.Anything, "test_database", "*").Return([]string{"deleted", "test_view"}, nil).Once()
+		mockClient.On("GetDatabase", mock.Anything, "test_database").Return(&hive_metastore.Database{Name: "test_database"}, nil).Once()
+		mockClient.On("GetTable", mock.Anything, "test_database", "deleted").Return(nil, errNoSuchObject).Once()
+		mockClient.On("GetTable", mock.Anything, "test_database", "test_view").Return(testIcebergHiveView, nil).Once()
+
+		var views []table.Identifier
+		for identifier, err := range NewCatalogWithClient(mockClient, iceberg.Properties{}).ListViews(context.Background(), DatabaseIdentifier("test_database")) {
+			require.NoError(t, err)
+			views = append(views, identifier)
+		}
+		require.Equal(t, []table.Identifier{TableIdentifier("test_database", "test_view")}, views)
+		mockClient.AssertExpectations(t)
+	})
+
+	t.Run("propagates operational failures", func(t *testing.T) {
+		loadErr := errors.New("metastore host not found")
+		mockClient := &mockHiveClient{}
+		mockClient.On("GetTables", mock.Anything, "test_database", "*").Return([]string{"deleted", "broken", "test_view"}, nil).Once()
+		mockClient.On("GetDatabase", mock.Anything, "test_database").Return(&hive_metastore.Database{Name: "test_database"}, nil).Once()
+		mockClient.On("GetTable", mock.Anything, "test_database", "deleted").Return(nil, errNoSuchObject).Once()
+		mockClient.On("GetTable", mock.Anything, "test_database", "broken").Return(nil, loadErr).Once()
+
+		var gotErr error
+		for identifier, err := range NewCatalogWithClient(mockClient, iceberg.Properties{}).ListViews(context.Background(), DatabaseIdentifier("test_database")) {
+			if err != nil {
+				require.Equal(t, table.Identifier{}, identifier)
+				gotErr = err
+
+				break
+			}
+		}
+		require.ErrorIs(t, gotErr, loadErr)
+		require.ErrorContains(t, gotErr, "failed to load view test_database.broken while listing")
+		mockClient.AssertNumberOfCalls(t, "GetTable", 2)
+		mockClient.AssertExpectations(t)
+	})
+}
+
 func TestHiveLoadView(t *testing.T) {
 	assert := require.New(t)