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`.