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)