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 b35450588 refactor(catalog)!: split catalog capability interfaces 
(#2349)
b35450588 is described below

commit b35450588e2d586c883bd90e77c9faa6ae8396fe
Author: Minh Vu <[email protected]>
AuthorDate: Sat Oct 31 11:09:46 2026 +0100

    refactor(catalog)!: split catalog capability interfaces (#2349)
---
 catalog/as_test.go                                 |  93 +++++++++++++++++++
 catalog/catalog.go                                 | 101 +++++++++++++++++----
 catalog/example_multi_table_transaction_test.go    |   2 +
 catalog/glue/catalog_name_test.go                  |  69 ++++++++++++++
 catalog/glue/glue.go                               |  10 +-
 catalog/glue/options.go                            |   8 ++
 catalog/hadoop/hadoop.go                           |   2 +
 catalog/hadoop/hadoop_test.go                      |   2 +
 .../{glue/options.go => hive/catalog_name_test.go} |  30 ++----
 catalog/hive/hive.go                               |  15 ++-
 catalog/hive/options.go                            |   9 ++
 catalog/multi_table_transaction.go                 |   4 +-
 catalog/multi_table_transaction_test.go            |   2 +
 catalog/rest/rest.go                               |   4 +-
 cmd/iceberg/create_test.go                         |   2 +
 cmd/iceberg/drop_test.go                           |   2 +
 cmd/iceberg/properties_test.go                     |   2 +
 cmd/iceberg/upgrade_rollback_test.go               |   2 +
 website/src/releases.md                            |   1 +
 19 files changed, 309 insertions(+), 51 deletions(-)

diff --git a/catalog/as_test.go b/catalog/as_test.go
new file mode 100644
index 000000000..223c12dc9
--- /dev/null
+++ b/catalog/as_test.go
@@ -0,0 +1,93 @@
+// 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 catalog
+
+import (
+       "context"
+       "testing"
+
+       "github.com/apache/iceberg-go/table"
+       "github.com/stretchr/testify/require"
+)
+
+type capabilityTestDecorator struct {
+       Catalog
+}
+
+func (d *capabilityTestDecorator) Unwrap() Catalog { return d.Catalog }
+
+type interceptingTestDecorator struct {
+       Catalog
+}
+
+func (d *interceptingTestDecorator) Unwrap() Catalog { return d.Catalog }
+
+func (d *interceptingTestDecorator) CommitTransaction(context.Context, 
[]table.TableCommit) error {
+       return nil
+}
+
+func TestAsCatalogCapabilities(t *testing.T) {
+       backend := &fullStubCatalog{}
+       inner := &capabilityTestDecorator{Catalog: backend}
+       outer := &capabilityTestDecorator{Catalog: inner}
+
+       capability, ok := As[TransactionalCatalog](outer)
+       require.True(t, ok)
+       require.Same(t, backend, capability)
+
+       concrete, ok := As[*fullStubCatalog](outer)
+       require.True(t, ok)
+       require.Same(t, backend, concrete)
+
+       _, ok = As[RegisterableCatalog](outer)
+       require.False(t, ok)
+
+       _, ok = As[TransactionalCatalog](nil)
+       require.False(t, ok)
+
+       // Embedding Catalog alone hides optional capabilities without Unwrap.
+       opaque := struct{ Catalog }{Catalog: backend}
+       _, ok = As[TransactionalCatalog](opaque)
+       require.False(t, ok)
+}
+
+func TestAsPrefersOutermostCapability(t *testing.T) {
+       backend := &fullStubCatalog{}
+       decorator := &interceptingTestDecorator{Catalog: backend}
+
+       capability, ok := As[TransactionalCatalog](decorator)
+       require.True(t, ok)
+       require.Same(t, decorator, capability)
+}
+
+func TestNewMultiTableTransactionUnwrapsCatalog(t *testing.T) {
+       backend := &fullStubCatalog{}
+       wrapped := &capabilityTestDecorator{
+               Catalog: &capabilityTestDecorator{Catalog: backend},
+       }
+       mtx, err := NewMultiTableTransaction(wrapped)
+       require.NoError(t, err)
+
+       tx := mtxTestTable(t, "db", "t").NewTransaction()
+       require.NoError(t, tx.SetProperties(map[string]string{"k": "v"}))
+       require.NoError(t, mtx.AddTransaction(tx))
+       require.NoError(t, mtx.Commit(context.Background()))
+
+       require.Len(t, backend.commits, 1)
+       require.Len(t, backend.commits[0], 1)
+}
diff --git a/catalog/catalog.go b/catalog/catalog.go
index ff64071a8..6026d9633 100644
--- a/catalog/catalog.go
+++ b/catalog/catalog.go
@@ -38,6 +38,7 @@ import (
 
        "github.com/apache/iceberg-go"
        "github.com/apache/iceberg-go/table"
+       "github.com/apache/iceberg-go/view"
 )
 
 type Type string
@@ -113,20 +114,11 @@ func NewCreateViewCfg() CreateViewCfg {
        }
 }
 
-// Catalog provides table and namespace operations plus an explicit resource
-// lifecycle. Implementations must release catalog-owned resources in Close.
-// Ownership of constructor-supplied dependencies depends on the 
implementation.
-// Using a catalog after Close is unsupported; repeated Close calls have
-// implementation-specific behavior.
-type Catalog interface {
-       io.Closer
-
-       // PurgeableCatalog provides the mandatory physical table deletion API.
+// TableCatalog contains the table operations supported by every catalog.
+// Callers that only manage tables can depend on this narrower interface.
+type TableCatalog interface {
        PurgeableCatalog
 
-       // CatalogType returns the type of the catalog.
-       CatalogType() Type
-
        // CreateTable creates a new iceberg table in the catalog using the 
provided identifier
        // and schema. Options can be used to optionally provide location, 
partition spec, sort order,
        // and custom properties.
@@ -145,6 +137,11 @@ type Catalog interface {
        RenameTable(ctx context.Context, from, to table.Identifier) 
(*table.Table, error)
        // CheckTableExists reports whether the table exists.
        CheckTableExists(ctx context.Context, identifier table.Identifier) 
(bool, error)
+}
+
+// NamespaceCatalog contains the namespace operations supported by every
+// catalog. All built-in catalogs implement these operations.
+type NamespaceCatalog interface {
        // ListNamespaces returns the list of available namespaces, optionally 
filtering by a
        // parent namespace
        ListNamespaces(ctx context.Context, parent table.Identifier) 
([]table.Identifier, error)
@@ -162,11 +159,79 @@ type Catalog interface {
                removals []string, updates iceberg.Properties) 
(PropertiesUpdateSummary, error)
 }
 
+// Catalog combines universal table and namespace operations with catalog
+// identity and resource lifecycle. Implementations must release catalog-owned
+// resources in Close. Ownership of constructor-supplied dependencies depends
+// on the implementation. Using a catalog after Close is unsupported; repeated
+// Close calls have implementation-specific behavior.
+//
+// After v1, add new capabilities as optional interfaces, never as methods
+// on Catalog, to avoid breaking third-party catalog implementations.
+type Catalog interface {
+       io.Closer
+
+       // Name returns the catalog's configured logical name. Glue and Hive
+       // catalogs built without WithName return "glue" and "hive".
+       Name() string
+
+       // CatalogType returns the type of the catalog.
+       CatalogType() Type
+
+       TableCatalog
+       NamespaceCatalog
+}
+
+// As finds the first catalog in the wrapping chain that implements T.
+// Catalog decorators can expose a wrapped catalog with Unwrap() Catalog.
+// The outermost catalog is checked first, allowing decorators to intercept
+// optional operations. Unwrap may return nil to end the chain, and the
+// chain must terminate without cycles.
+func As[T any](cat Catalog) (T, bool) {
+       for cat != nil {
+               if capability, ok := any(cat).(T); ok {
+                       return capability, true
+               }
+
+               wrapper, ok := cat.(interface{ Unwrap() Catalog })
+               if !ok {
+                       break
+               }
+               cat = wrapper.Unwrap()
+       }
+
+       var zero T
+
+       return zero, false
+}
+
+// ViewCatalog is an optional capability for standard view operations.
+// Use As to discover support on a Catalog, including through decorators.
+// RenameView, RegisterView, and UpdateView are not part of this interface.
+type ViewCatalog interface {
+       // ListViews lists view identifiers in a namespace.
+       ListViews(ctx context.Context, namespace table.Identifier) 
iter.Seq2[table.Identifier, error]
+       // LoadView loads a view by its identifier.
+       LoadView(ctx context.Context, identifier table.Identifier) (*view.View, 
error)
+       // CreateView creates a view using the given version and schema.
+       CreateView(ctx context.Context, identifier table.Identifier, version 
*view.Version, schema *iceberg.Schema, opts ...CreateViewOpt) (*view.View, 
error)
+       // DropView removes an existing view.
+       DropView(ctx context.Context, identifier table.Identifier) error
+       // CheckViewExists reports whether the view exists.
+       CheckViewExists(ctx context.Context, identifier table.Identifier) 
(bool, error)
+}
+
+// UpdatableViewCatalog is an optional capability for updating existing views.
+// The REST catalog implements it; discover support with As.
+type UpdatableViewCatalog interface {
+       // UpdateView applies requirements and updates and returns the updated 
view.
+       UpdateView(ctx context.Context, ident table.Identifier, requirements 
[]view.Requirement, updates []view.Update) (*view.View, error)
+}
+
 // TransactionalCatalog is an optional interface implemented by catalogs
 // that support atomic multi-table commits. Callers should check for this
-// capability via a type assertion:
+// capability via [As]:
 //
-//     if tc, ok := cat.(catalog.TransactionalCatalog); ok {
+//     if tc, ok := catalog.As[catalog.TransactionalCatalog](cat); ok {
 //         err := tc.CommitTransaction(ctx, commits)
 //     }
 //
@@ -183,9 +248,9 @@ type TransactionalCatalog interface {
 // without creating a new table or rewriting its data.
 //
 // REST, Hive, and Glue catalogs implement this interface. Callers should
-// check for registration support with a type assertion:
+// check for registration support with [As]:
 //
-//     if registerable, ok := cat.(catalog.RegisterableCatalog); ok {
+//     if registerable, ok := catalog.As[catalog.RegisterableCatalog](cat); ok 
{
 //             tbl, err := registerable.RegisterTable(ctx, ident, 
metadataLocation)
 //     }
 //
@@ -217,9 +282,9 @@ type PurgeableCatalog interface {
 // RefreshableCredentialCatalog is an optional interface implemented by 
catalogs that
 // support temporarily-vended credentials. Temporarily-vended credentials 
scope object
 // storage access to just files allowed by the user making a related request 
to the
-// catalog. Callers should check for this capability via a type assertion:
+// catalog. Callers should check for this capability via [As]:
 //
-//     if rc, ok := cat.(catalog.RefreshableCredentialCatalog); ok {
+//     if rc, ok := catalog.As[catalog.RefreshableCredentialCatalog](cat); ok {
 //         refreshed, err := rc.RefreshTableCredentials(ctx, tbl)
 //         if err == nil {
 //             tbl = refreshed
diff --git a/catalog/example_multi_table_transaction_test.go 
b/catalog/example_multi_table_transaction_test.go
index 7188d87a1..a2629ccec 100644
--- a/catalog/example_multi_table_transaction_test.go
+++ b/catalog/example_multi_table_transaction_test.go
@@ -36,6 +36,8 @@ type exampleCatalog struct {
 
 func (c *exampleCatalog) Close() error { return nil }
 
+func (c *exampleCatalog) Name() string { return "example" }
+
 func (c *exampleCatalog) CatalogType() catalog.Type { return catalog.REST }
 
 func (c *exampleCatalog) CommitTransaction(_ context.Context, _ 
[]table.TableCommit) error {
diff --git a/catalog/glue/catalog_name_test.go 
b/catalog/glue/catalog_name_test.go
new file mode 100644
index 000000000..1449cc461
--- /dev/null
+++ b/catalog/glue/catalog_name_test.go
@@ -0,0 +1,69 @@
+// 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 glue
+
+import (
+       "testing"
+
+       "github.com/apache/iceberg-go"
+       "github.com/apache/iceberg-go/catalog"
+)
+
+func TestGlueCatalogName(t *testing.T) {
+       for _, tc := range []struct {
+               name string
+               opts []Option
+       }{
+               {name: "glue"},
+               {name: "analytics", opts: []Option{WithName("analytics")}},
+       } {
+               t.Run(tc.name, func(t *testing.T) {
+                       cat, err := NewCatalog(tc.opts...)
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+                       if got := cat.Name(); got != tc.name {
+                               t.Fatalf("catalog name = %q, want %q", got, 
tc.name)
+                       }
+               })
+       }
+}
+
+func TestGlueCatalogRegistrationForwardsName(t *testing.T) {
+       const name = "analytics"
+       cat, err := catalog.Load(t.Context(), name, iceberg.Properties{
+               "type":          "glue",
+               Region:          "us-east-1",
+               AccessKeyID:     "test-access-key",
+               SecretAccessKey: "test-secret-key",
+       })
+       if err != nil {
+               t.Fatal(err)
+       }
+       t.Cleanup(func() {
+               if err := cat.Close(); err != nil {
+                       t.Errorf("close Glue catalog: %v", err)
+               }
+       })
+       if got := cat.Name(); got != name {
+               t.Fatalf("registered catalog name = %q, want %q", got, name)
+       }
+       if got := cat.CatalogType(); got != catalog.Glue {
+               t.Fatalf("registered catalog type = %q, want %q", got, 
catalog.Glue)
+       }
+}
diff --git a/catalog/glue/glue.go b/catalog/glue/glue.go
index f1273893e..4e65ac4bf 100644
--- a/catalog/glue/glue.go
+++ b/catalog/glue/glue.go
@@ -98,13 +98,13 @@ var (
 )
 
 func init() {
-       catalog.MustRegister("glue", catalog.RegistrarFunc(func(ctx 
context.Context, _ string, props iceberg.Properties) (catalog.Catalog, error) {
+       catalog.MustRegister("glue", catalog.RegistrarFunc(func(ctx 
context.Context, name string, props iceberg.Properties) (catalog.Catalog, 
error) {
                awsConfig, err := toAwsConfig(ctx, props)
                if err != nil {
                        return nil, err
                }
 
-               return NewCatalog(WithAWSConfig(awsConfig), 
WithAWSProperties(AWSProperties(props)))
+               return NewCatalog(WithName(name), WithAWSConfig(awsConfig), 
WithAWSProperties(AWSProperties(props)))
        }))
 }
 
@@ -160,6 +160,7 @@ type glueAPI interface {
 var _ catalog.Catalog = (*Catalog)(nil)
 
 type Catalog struct {
+       name      string
        glueSvc   glueAPI
        catalogId *string
        awsCfg    *aws.Config
@@ -182,7 +183,7 @@ type Catalog struct {
 // catalog, err := glue.NewCatalog(glue.WithAWSConfig(awsCfg))
 
 func NewCatalog(opts ...Option) (*Catalog, error) {
-       glueOps := &options{}
+       glueOps := &options{name: string(catalog.Glue)}
 
        for _, o := range opts {
                o(glueOps)
@@ -200,6 +201,7 @@ func NewCatalog(opts ...Option) (*Catalog, error) {
        }
 
        cat := &Catalog{
+               name:      glueOps.name,
                glueSvc:   glue.NewFromConfig(glueOps.awsConfig),
                catalogId: catalogId,
                awsCfg:    &glueOps.awsConfig,
@@ -270,6 +272,8 @@ func (c *Catalog) LoadTable(ctx context.Context, identifier 
table.Identifier) (*
        return c.convertGlueToIceberg(ctx, glueTable)
 }
 
+func (c *Catalog) Name() string { return c.name }
+
 func (c *Catalog) CatalogType() catalog.Type {
        return catalog.Glue
 }
diff --git a/catalog/glue/options.go b/catalog/glue/options.go
index 2ff3817c0..180f9cf85 100644
--- a/catalog/glue/options.go
+++ b/catalog/glue/options.go
@@ -25,6 +25,13 @@ type AWSProperties map[string]string
 
 type Option func(*options)
 
+// WithName sets the logical name of this Glue catalog.
+func WithName(name string) Option {
+       return func(o *options) {
+               o.name = name
+       }
+}
+
 // WithAWSConfig sets the AWS configuration for the catalog.
 func WithAWSConfig(cfg aws.Config) Option {
        return func(o *options) {
@@ -39,6 +46,7 @@ func WithAWSProperties(props AWSProperties) Option {
 }
 
 type options struct {
+       name          string
        awsConfig     aws.Config
        awsProperties AWSProperties
 }
diff --git a/catalog/hadoop/hadoop.go b/catalog/hadoop/hadoop.go
index 5963554ac..46400050e 100644
--- a/catalog/hadoop/hadoop.go
+++ b/catalog/hadoop/hadoop.go
@@ -281,6 +281,8 @@ func NewCatalog(ctx context.Context, name, warehouse 
string, props iceberg.Prope
        return cat, nil
 }
 
+func (c *Catalog) Name() string { return c.name }
+
 func (c *Catalog) CatalogType() catalog.Type {
        return catalog.Hadoop
 }
diff --git a/catalog/hadoop/hadoop_test.go b/catalog/hadoop/hadoop_test.go
index 1a2e1e8e1..9725bc78c 100644
--- a/catalog/hadoop/hadoop_test.go
+++ b/catalog/hadoop/hadoop_test.go
@@ -357,6 +357,7 @@ func (f selectiveFailingWalkFS) WalkDir(ctx 
context.Context, path string, fn fs.
 
 func (s *HadoopCatalogTestSuite) TestCatalogType() {
        s.Equal(catalog.Hadoop, s.cat.CatalogType())
+       s.Equal("test", s.cat.Name())
 }
 
 func (s *HadoopCatalogTestSuite) TestRegistration() {
@@ -366,6 +367,7 @@ func (s *HadoopCatalogTestSuite) TestRegistration() {
        })
        s.Require().NoError(err)
        s.Equal(catalog.Hadoop, cat.CatalogType())
+       s.Equal("test", cat.Name())
 }
 
 func (s *HadoopCatalogTestSuite) TestRegistrationMissingWarehouse() {
diff --git a/catalog/glue/options.go b/catalog/hive/catalog_name_test.go
similarity index 63%
copy from catalog/glue/options.go
copy to catalog/hive/catalog_name_test.go
index 2ff3817c0..273db7553 100644
--- a/catalog/glue/options.go
+++ b/catalog/hive/catalog_name_test.go
@@ -15,30 +15,18 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package glue
+package hive
 
-import (
-       "github.com/aws/aws-sdk-go-v2/aws"
-)
+import "testing"
 
-type AWSProperties map[string]string
-
-type Option func(*options)
-
-// WithAWSConfig sets the AWS configuration for the catalog.
-func WithAWSConfig(cfg aws.Config) Option {
-       return func(o *options) {
-               o.awsConfig = cfg
+func TestHiveCatalogName(t *testing.T) {
+       cat := NewCatalogWithClient(nil, nil)
+       if got := cat.Name(); got != "hive" {
+               t.Fatalf("default catalog name = %q, want %q", got, "hive")
        }
-}
 
-func WithAWSProperties(props AWSProperties) Option {
-       return func(o *options) {
-               o.awsProperties = props
+       named := NewCatalogWithClient(nil, nil, WithName("analytics"))
+       if got := named.Name(); got != "analytics" {
+               t.Fatalf("configured catalog name = %q, want %q", got, 
"analytics")
        }
 }
-
-type options struct {
-       awsConfig     aws.Config
-       awsProperties AWSProperties
-}
diff --git a/catalog/hive/hive.go b/catalog/hive/hive.go
index 025246feb..a70c6c122 100644
--- a/catalog/hive/hive.go
+++ b/catalog/hive/hive.go
@@ -45,17 +45,16 @@ const (
 
 var (
        _ catalog.Catalog             = (*Catalog)(nil)
+       _ catalog.ViewCatalog         = (*Catalog)(nil)
        _ catalog.RegisterableCatalog = (*Catalog)(nil)
 )
 
 func init() {
-       catalog.MustRegister("hive", catalog.RegistrarFunc(func(ctx 
context.Context, _ string, props iceberg.Properties) (catalog.Catalog, error) {
-               return NewCatalog(props)
+       catalog.MustRegister("hive", catalog.RegistrarFunc(func(ctx 
context.Context, name string, props iceberg.Properties) (catalog.Catalog, 
error) {
+               return NewCatalog(props, WithName(name))
        }))
 }
 
-var _ catalog.Catalog = (*Catalog)(nil)
-
 type Catalog struct {
        client HiveClient
        opts   *HiveOptions
@@ -87,16 +86,22 @@ func NewCatalog(props iceberg.Properties, opts ...Option) 
(*Catalog, error) {
        }, nil
 }
 
-func NewCatalogWithClient(client HiveClient, props iceberg.Properties) 
*Catalog {
+func NewCatalogWithClient(client HiveClient, props iceberg.Properties, opts 
...Option) *Catalog {
        o := NewHiveOptions()
        o.ApplyProperties(props)
 
+       for _, opt := range opts {
+               opt(o)
+       }
+
        return &Catalog{
                client: client,
                opts:   o,
        }
 }
 
+func (c *Catalog) Name() string { return c.opts.Name }
+
 func (c *Catalog) CatalogType() catalog.Type {
        return catalog.Hive
 }
diff --git a/catalog/hive/options.go b/catalog/hive/options.go
index 74676ec6d..4d1c98557 100644
--- a/catalog/hive/options.go
+++ b/catalog/hive/options.go
@@ -73,6 +73,7 @@ const (
 )
 
 type HiveOptions struct {
+       Name      string
        URI       string
        Warehouse string
        props     iceberg.Properties
@@ -85,6 +86,7 @@ type HiveOptions struct {
 
 func NewHiveOptions() *HiveOptions {
        return &HiveOptions{
+               Name:            "hive",
                props:           iceberg.Properties{},
                LockMinWaitTime: DefaultLockCheckMinWaitTime,
                LockMaxWaitTime: DefaultLockCheckMaxWaitTime,
@@ -147,6 +149,13 @@ func durationFromProps(props iceberg.Properties, 
javaMsKey, legacyDurationKey st
 
 type Option func(*HiveOptions)
 
+// WithName sets the logical name of this Hive catalog.
+func WithName(name string) Option {
+       return func(o *HiveOptions) {
+               o.Name = name
+       }
+}
+
 // WithURI sets the Thrift URI for the Hive Metastore.
 func WithURI(uri string) Option {
        return func(o *HiveOptions) {
diff --git a/catalog/multi_table_transaction.go 
b/catalog/multi_table_transaction.go
index 4c31934e1..290d51058 100644
--- a/catalog/multi_table_transaction.go
+++ b/catalog/multi_table_transaction.go
@@ -58,9 +58,9 @@ type MultiTableTransaction struct {
 
 // NewMultiTableTransaction creates a new multi-table transaction backed
 // by the given catalog. Returns an error if the catalog does not
-// implement [TransactionalCatalog].
+// expose [TransactionalCatalog], including through an Unwrap chain.
 func NewMultiTableTransaction(cat Catalog) (*MultiTableTransaction, error) {
-       tc, ok := cat.(TransactionalCatalog)
+       tc, ok := As[TransactionalCatalog](cat)
        if !ok {
                return nil, errors.New("catalog does not support multi-table 
transactions")
        }
diff --git a/catalog/multi_table_transaction_test.go 
b/catalog/multi_table_transaction_test.go
index 03c3be089..49d3aef56 100644
--- a/catalog/multi_table_transaction_test.go
+++ b/catalog/multi_table_transaction_test.go
@@ -115,6 +115,8 @@ func (s *fullStubCatalog) 
UpdateNamespaceProperties(context.Context, table.Ident
 
 func (s *fullStubCatalog) Close() error { return nil }
 
+func (s *fullStubCatalog) Name() string { return "test" }
+
 func (s *fullStubCatalog) CatalogType() Type { return REST }
 
 func mtxTestTable(t *testing.T, ns, name string) *table.Table {
diff --git a/catalog/rest/rest.go b/catalog/rest/rest.go
index 203ce18e9..1f3c007b9 100644
--- a/catalog/rest/rest.go
+++ b/catalog/rest/rest.go
@@ -50,6 +50,8 @@ import (
 
 var (
        _ catalog.Catalog                      = (*Catalog)(nil)
+       _ catalog.ViewCatalog                  = (*Catalog)(nil)
+       _ catalog.UpdatableViewCatalog         = (*Catalog)(nil)
        _ catalog.TransactionalCatalog         = (*Catalog)(nil)
        _ catalog.RefreshableCredentialCatalog = (*Catalog)(nil)
        _ catalog.RegisterableCatalog          = (*Catalog)(nil)
@@ -898,8 +900,6 @@ func toProps(o *options) iceberg.Properties {
        return props
 }
 
-var _ catalog.Catalog = (*Catalog)(nil)
-
 type Catalog struct {
        baseURI *url.URL
        cl      *http.Client
diff --git a/cmd/iceberg/create_test.go b/cmd/iceberg/create_test.go
index e607efa54..a94ddffa2 100644
--- a/cmd/iceberg/create_test.go
+++ b/cmd/iceberg/create_test.go
@@ -36,6 +36,8 @@ type mockCatalogForCreate struct {
        createNamespaceErr    error
 }
 
+func (m *mockCatalogForCreate) Name() string { return "mock" }
+
 func (m *mockCatalogForCreate) CatalogType() catalog.Type {
        return catalog.REST
 }
diff --git a/cmd/iceberg/drop_test.go b/cmd/iceberg/drop_test.go
index aa141be4b..eb49872b8 100644
--- a/cmd/iceberg/drop_test.go
+++ b/cmd/iceberg/drop_test.go
@@ -43,6 +43,8 @@ type mockCatalogForDrop struct {
 
 func (m *mockCatalogForDrop) Close() error { return nil }
 
+func (m *mockCatalogForDrop) Name() string { return string(m.catalogType) }
+
 func (m *mockCatalogForDrop) CatalogType() catalog.Type {
        return m.catalogType
 }
diff --git a/cmd/iceberg/properties_test.go b/cmd/iceberg/properties_test.go
index fb812fb90..4a6ecf892 100644
--- a/cmd/iceberg/properties_test.go
+++ b/cmd/iceberg/properties_test.go
@@ -45,6 +45,8 @@ func (m *mockCatalogForProperties) LoadTable(ctx 
context.Context, ident table.Id
 
 func (m *mockCatalogForProperties) Close() error { return nil }
 
+func (m *mockCatalogForProperties) Name() string { return "mock" }
+
 func (m *mockCatalogForProperties) CatalogType() catalog.Type {
        panic("CatalogType must not be called")
 }
diff --git a/cmd/iceberg/upgrade_rollback_test.go 
b/cmd/iceberg/upgrade_rollback_test.go
index 2dec3923a..f4a90fcf8 100644
--- a/cmd/iceberg/upgrade_rollback_test.go
+++ b/cmd/iceberg/upgrade_rollback_test.go
@@ -410,6 +410,8 @@ type panicCatalog struct {
 
 func (p *panicCatalog) Close() error { return nil }
 
+func (p *panicCatalog) Name() string { return "panic" }
+
 func (p *panicCatalog) CatalogType() catalog.Type { return "panic" }
 func (p *panicCatalog) CreateTable(context.Context, table.Identifier, 
*iceberg.Schema, ...catalog.CreateTableOpt) (*table.Table, error) {
        panic("CreateTable must not be called")
diff --git a/website/src/releases.md b/website/src/releases.md
index b2c461e99..bc247b2c0 100644
--- a/website/src/releases.md
+++ b/website/src/releases.md
@@ -42,6 +42,7 @@ Per-release notes (highlights, breaking changes, 
contributors) are published on
 
 ### Unreleased compatibility notes
 
+- **Catalog capabilities split into narrower interfaces.** `catalog.Catalog` 
now embeds `TableCatalog` and `NamespaceCatalog` with unchanged table and 
namespace methods. `Name() string` is now required, breaking third-party 
catalogs and test fakes. `ViewCatalog` is optional (REST and Hive), while REST 
also implements `UpdatableViewCatalog` for `UpdateView`. Use 
`catalog.As[T](cat)` and the `Unwrap() Catalog` convention to discover optional 
capabilities through decorators. Future capabili [...]
 - **`ReplaceFilesWithDeleteFiles` removed.** For delete-file replacements, use 
`tx.NewRewrite(snapshotProps)`, call `DeleteFile` for each source file and 
`AddDeleteFileWithDataSequenceNumber` for each replacement, then commit the 
builder. Rewrites record a `replace` snapshot instead of `overwrite`. The 
five-argument `ReplaceFiles` API is unchanged and remains available for 
compaction and delete-file removals.
 - **Path-based data-file addition renamed.** Replace `Transaction.AddFiles` 
with `AddDataFilesByPath`, `AddFilesOption` with `AddDataFilesByPathOption`, 
and `WithAddFilesConcurrency` with `WithAddDataFilesByPathConcurrency`. 
Behavior is unchanged.
 - **Manifest builder constructor renamed.** Replace 
`iceberg.NewManifestFile(...)` with `iceberg.NewManifestBuilder(...)`. It still 
returns a `ManifestBuilder`.

Reply via email to