laskoviymishka commented on code in PR #2172:
URL: https://github.com/apache/iceberg-go/pull/2172#discussion_r4241789290


##########
catalog/catalogtest/catalogtest.go:
##########
@@ -89,6 +89,12 @@ type Config struct {
        // are directories with nowhere to keep them. Loading is required of 
every
        // catalog and is tested either way.
        SupportsNamespaceProperties bool
+
+       // SupportsRenameTable reports whether the catalog can rename tables.
+       // When false the suite verifies that RenameTable returns
+       // iceberg.ErrNotImplemented, and the remaining rename subtests are
+       // skipped. See #1097 for Hadoop rename support.
+       SupportsRenameTable bool

Review Comment:
   The zero value here is load-bearing: false doesn't mean "rename untested", 
it means `RenameTable` must return `iceberg.ErrNotImplemented`. I'd spell that 
out in the comment, since any new adopter picking up `Config` at its default 
inherits that assertion.



##########
catalog/catalogtest/tables.go:
##########
@@ -0,0 +1,205 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package catalogtest
+
+import (
+       "context"
+       "testing"
+
+       iceberg "github.com/apache/iceberg-go"
+       "github.com/apache/iceberg-go/catalog"
+       "github.com/apache/iceberg-go/table"
+       "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
+)
+
+// listTables collects every table identifier the catalog reports for 
namespace.
+func listTables(t *testing.T, cat catalog.Catalog, namespace table.Identifier) 
[]table.Identifier {
+       t.Helper()
+
+       var out []table.Identifier
+       for ident, err := range cat.ListTables(context.Background(), namespace) 
{
+               require.NoError(t, err)
+               out = append(out, ident)
+       }
+
+       return out
+}
+
+// testListTables asserts that ListTables reflects creates and drops.
+func testListTables(t *testing.T, cfg Config) {
+       ctx := context.Background()
+       cat := cfg.NewCatalog(t)
+       namespace, ident := newIdentifiers()
+
+       require.NoError(t, cat.CreateNamespace(ctx, namespace, nil))
+       t.Cleanup(func() { _ = cat.DropNamespace(ctx, namespace) })
+
+       assert.Empty(t, listTables(t, cat, namespace), "new namespace should 
have no tables")
+
+       _, err := cat.CreateTable(ctx, ident, Schema)
+       require.NoError(t, err)
+       t.Cleanup(func() { _ = cat.DropTable(ctx, ident) })
+
+       assert.Equal(t, []table.Identifier{ident}, listTables(t, cat, 
namespace))
+
+       require.NoError(t, cat.DropTable(ctx, ident))
+       assert.Empty(t, listTables(t, cat, namespace), "dropped table should 
not be listed")
+}
+
+// testDropTable asserts that a dropped table is no longer visible.
+func testDropTable(t *testing.T, cfg Config) {
+       ctx := context.Background()
+       cat := cfg.NewCatalog(t)
+       namespace, ident := newIdentifiers()
+
+       require.NoError(t, cat.CreateNamespace(ctx, namespace, nil))
+       t.Cleanup(func() { _ = cat.DropNamespace(ctx, namespace) })
+
+       _, err := cat.CreateTable(ctx, ident, Schema)
+       require.NoError(t, err)
+
+       require.NoError(t, cat.DropTable(ctx, ident))
+
+       exists, err := cat.CheckTableExists(ctx, ident)
+       require.NoError(t, err)
+       assert.False(t, exists, "table should not exist after drop")
+
+       _, err = cat.LoadTable(ctx, ident)
+       assert.ErrorIs(t, err, catalog.ErrNoSuchTable)
+}
+
+// testDropMissingTable asserts that dropping a table that was never created
+// reports that the table does not exist.
+func testDropMissingTable(t *testing.T, cfg Config) {
+       ctx := context.Background()
+       cat := cfg.NewCatalog(t)
+       namespace, ident := newIdentifiers()
+
+       require.NoError(t, cat.CreateNamespace(ctx, namespace, nil))
+       t.Cleanup(func() { _ = cat.DropNamespace(ctx, namespace) })
+
+       assert.ErrorIs(t, cat.DropTable(ctx, ident), catalog.ErrNoSuchTable)
+}
+
+func skipWithoutRenameTable(t *testing.T, cfg Config) {
+       t.Helper()
+       if !cfg.SupportsRenameTable {
+               t.Skip("catalog does not support renaming tables")
+       }
+}
+
+// testRenameTableNotSupported asserts that a catalog which does not support
+// rename returns ErrNotImplemented rather than silently succeeding or
+// returning an untyped error.
+func testRenameTableNotSupported(t *testing.T, cfg Config) {
+       if cfg.SupportsRenameTable {
+               t.Skip("catalog supports renaming tables")
+       }
+
+       ctx := context.Background()
+       cat := cfg.NewCatalog(t)
+       namespace, from := newIdentifiers()
+       to := table.Identifier{namespace[0], "renamed"}
+
+       require.NoError(t, cat.CreateNamespace(ctx, namespace, nil))
+       t.Cleanup(func() { _ = cat.DropNamespace(ctx, namespace) })
+
+       _, err := cat.RenameTable(ctx, from, to)

Review Comment:
   This asserts the unsupported contract against a table that was never 
created, so it only passes for a catalog that returns `ErrNotImplemented` 
before it looks at the source. A perfectly valid unsupported impl that 
validates the source first would return `ErrNoSuchTable` here and fail the 
suite, and a stub that short-circuits passes trivially without proving 
anything. I'd create `from` first, rename a real existing table, then assert 
the source still exists afterward so a silent "rename succeeded" regression 
also gets caught.



##########
catalog/catalogtest/tables.go:
##########
@@ -0,0 +1,205 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package catalogtest
+
+import (
+       "context"
+       "testing"
+
+       iceberg "github.com/apache/iceberg-go"
+       "github.com/apache/iceberg-go/catalog"
+       "github.com/apache/iceberg-go/table"
+       "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
+)
+
+// listTables collects every table identifier the catalog reports for 
namespace.
+func listTables(t *testing.T, cat catalog.Catalog, namespace table.Identifier) 
[]table.Identifier {
+       t.Helper()
+
+       var out []table.Identifier
+       for ident, err := range cat.ListTables(context.Background(), namespace) 
{
+               require.NoError(t, err)
+               out = append(out, ident)
+       }
+
+       return out
+}
+
+// testListTables asserts that ListTables reflects creates and drops.
+func testListTables(t *testing.T, cfg Config) {
+       ctx := context.Background()
+       cat := cfg.NewCatalog(t)
+       namespace, ident := newIdentifiers()
+
+       require.NoError(t, cat.CreateNamespace(ctx, namespace, nil))
+       t.Cleanup(func() { _ = cat.DropNamespace(ctx, namespace) })
+
+       assert.Empty(t, listTables(t, cat, namespace), "new namespace should 
have no tables")
+
+       _, err := cat.CreateTable(ctx, ident, Schema)
+       require.NoError(t, err)
+       t.Cleanup(func() { _ = cat.DropTable(ctx, ident) })
+
+       assert.Equal(t, []table.Identifier{ident}, listTables(t, cat, 
namespace))
+
+       require.NoError(t, cat.DropTable(ctx, ident))
+       assert.Empty(t, listTables(t, cat, namespace), "dropped table should 
not be listed")
+}
+
+// testDropTable asserts that a dropped table is no longer visible.
+func testDropTable(t *testing.T, cfg Config) {
+       ctx := context.Background()
+       cat := cfg.NewCatalog(t)
+       namespace, ident := newIdentifiers()
+
+       require.NoError(t, cat.CreateNamespace(ctx, namespace, nil))
+       t.Cleanup(func() { _ = cat.DropNamespace(ctx, namespace) })
+
+       _, err := cat.CreateTable(ctx, ident, Schema)
+       require.NoError(t, err)
+
+       require.NoError(t, cat.DropTable(ctx, ident))
+
+       exists, err := cat.CheckTableExists(ctx, ident)
+       require.NoError(t, err)
+       assert.False(t, exists, "table should not exist after drop")
+
+       _, err = cat.LoadTable(ctx, ident)
+       assert.ErrorIs(t, err, catalog.ErrNoSuchTable)
+}
+
+// testDropMissingTable asserts that dropping a table that was never created
+// reports that the table does not exist.
+func testDropMissingTable(t *testing.T, cfg Config) {
+       ctx := context.Background()
+       cat := cfg.NewCatalog(t)
+       namespace, ident := newIdentifiers()
+
+       require.NoError(t, cat.CreateNamespace(ctx, namespace, nil))
+       t.Cleanup(func() { _ = cat.DropNamespace(ctx, namespace) })
+
+       assert.ErrorIs(t, cat.DropTable(ctx, ident), catalog.ErrNoSuchTable)
+}
+
+func skipWithoutRenameTable(t *testing.T, cfg Config) {
+       t.Helper()
+       if !cfg.SupportsRenameTable {
+               t.Skip("catalog does not support renaming tables")
+       }
+}
+
+// testRenameTableNotSupported asserts that a catalog which does not support
+// rename returns ErrNotImplemented rather than silently succeeding or
+// returning an untyped error.
+func testRenameTableNotSupported(t *testing.T, cfg Config) {
+       if cfg.SupportsRenameTable {
+               t.Skip("catalog supports renaming tables")
+       }
+
+       ctx := context.Background()
+       cat := cfg.NewCatalog(t)
+       namespace, from := newIdentifiers()
+       to := table.Identifier{namespace[0], "renamed"}
+
+       require.NoError(t, cat.CreateNamespace(ctx, namespace, nil))
+       t.Cleanup(func() { _ = cat.DropNamespace(ctx, namespace) })
+
+       _, err := cat.RenameTable(ctx, from, to)
+       assert.ErrorIs(t, err, iceberg.ErrNotImplemented)
+}
+
+// testRenameTable asserts that a renamed table is reachable only under its new
+// identifier and keeps its schema.
+func testRenameTable(t *testing.T, cfg Config) {
+       skipWithoutRenameTable(t, cfg)
+
+       ctx := context.Background()
+       cat := cfg.NewCatalog(t)
+       namespace, from := newIdentifiers()
+       to := table.Identifier{namespace[0], "renamed"}

Review Comment:
   testRenameTable only moves within a single namespace, which skips the 
riskiest path: Hive's `toDB != fromDB` case takes separate locks and mutates 
`DbName`, and right now that's only exercised by a mock. I'd add a 
cross-namespace variant here so the real backends actually run it.



##########
catalog/hive/hive.go:
##########
@@ -539,6 +539,16 @@ func (c *Catalog) RenameTable(ctx context.Context, from, 
to table.Identifier) (_
                        table.ErrCommitFailed, fromDB, fromTable, 
sourceMetadataLocation, lockedMetadataLocation)
        }
 
+       // Use a raw GetTable instead of CheckTableExists so that non-Iceberg
+       // Hive tables at the destination are also detected as conflicts.
+       _, err = c.client.GetTable(ctx, toDB, toTable)

Review Comment:
   The pre-check is good for the common case, but it's advisory. Hive's lock is 
cooperative, so a non-Iceberg writer that creates `toDB.toTable` via a plain 
`create_table` can land between this `GetTable` and the `AlterTable` below. 
When that happens HMS rejects the alter itself with `AlreadyExistsException`, 
and that path still wraps it as a generic `failed to rename` error, so it never 
matches `catalog.ErrTableAlreadyExists`. `isAlreadyExistsError` is already in 
this file; I'd map it (and `isNoSuchObjectError` to `ErrNoSuchTable`) on the 
`AlterTable` result too, which is what Java's `renameTable` does.



##########
catalog/hive/hive_test.go:
##########
@@ -891,6 +891,33 @@ func TestHiveRenameTableRejectsChangedMetadataAfterLock(t 
*testing.T) {
        mockClient.AssertExpectations(t)
 }
 
+func TestHiveRenameTableToExistingTable(t *testing.T) {
+       mockClient := &mockHiveClient{}
+       destination := *testIcebergHiveTable1

Review Comment:
   This builds the destination from `testIcebergHiveTable1`, so it only covers 
the Iceberg-table conflict. The whole reason for the raw `GetTable` is to also 
catch a non-Iceberg destination, and that case is untested; so is the 
`!isNoSuchObjectError` branch where the destination lookup returns a transport 
error. A case for each would exercise both new branches.



##########
catalog/rest/rest.go:
##########
@@ -1980,7 +1980,7 @@ func (r *Catalog) RenameTable(ctx context.Context, from, 
to table.Identifier) (*
        }
 
        _, err = doPost[payload, any](ctx, r.baseURI, path, payload{Source: 
src, Destination: dst}, r.cl,
-               map[int]error{http.StatusNotFound: catalog.ErrNoSuchTable}, 
allowNoContent())
+               map[int]error{http.StatusNotFound: catalog.ErrNoSuchTable, 
http.StatusConflict: catalog.ErrTableAlreadyExists}, allowNoContent())

Review Comment:
   409 to `ErrTableAlreadyExists` matches PyIceberg, so no objection to the 
mapping. Two lossy edges worth noting, not blocking: a missing destination 
namespace also comes back as 404 and gets reported as `ErrNoSuchTable`, and a 
409 can mean a view occupies the destination rather than a table. If we want to 
be precise, inspect the error body `type` before picking the sentinel; 
otherwise a comment documenting the ambiguity is enough.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to