This is an automated email from the ASF dual-hosted git repository.
zeroshade 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 bc6105cb4 refactor(catalog/sql)!: align view APIs with ViewCatalog
(#2378)
bc6105cb4 is described below
commit bc6105cb408d0da5493d8d768424da927ef1cd5e
Author: Minh Vu <[email protected]>
AuthorDate: Wed Nov 4 18:44:39 2026 +0100
refactor(catalog/sql)!: align view APIs with ViewCatalog (#2378)
* refactor(catalog/sql)!: align view APIs with ViewCatalog
Signed-off-by: Minh Vu <[email protected]>
* fix(catalog/sql): satisfy gofumpt and nlreturn
* fix(catalog/sql): satisfy gofumpt and nlreturn
* fix(catalog/sql): satisfy gofumpt and nlreturn
---------
Signed-off-by: Minh Vu <[email protected]>
---
catalog/sql/drop_namespace_internal_test.go | 9 ++-
catalog/sql/listing_test.go | 2 +-
catalog/sql/sql.go | 51 +++++++++++-----
catalog/sql/sql_integration_test.go | 2 +-
catalog/sql/sql_test.go | 95 +++++++++++++++++++++++------
website/src/releases.md | 5 +-
6 files changed, 128 insertions(+), 36 deletions(-)
diff --git a/catalog/sql/drop_namespace_internal_test.go
b/catalog/sql/drop_namespace_internal_test.go
index 113531879..b58ab4845 100644
--- a/catalog/sql/drop_namespace_internal_test.go
+++ b/catalog/sql/drop_namespace_internal_test.go
@@ -235,7 +235,7 @@ func TestDropNamespaceDoesNotRaceWithObjectCreation(t
*testing.T) {
{
name: "create view",
create: func(_ *testing.T, ctx context.Context, cat
*Catalog, ident table.Identifier) error {
- return cat.CreateView(ctx, ident, schema,
view.Representation{SQL: "SELECT 1", Dialect: "spark"}, nil)
+ return createTestView(ctx, cat, ident, schema,
view.Representation{SQL: "SELECT 1", Dialect: "spark"}, nil)
},
exists: func(ctx context.Context, cat *Catalog, ident
table.Identifier) (bool, error) {
return cat.CheckViewExists(ctx, ident)
@@ -383,3 +383,10 @@ func
TestDropNamespaceDoesNotRaceWithUpdateNamespaceProperties(t *testing.T) {
require.NoError(t, err)
require.False(t, exists)
}
+
+func createTestView(ctx context.Context, cat *Catalog, identifier
table.Identifier, schema *iceberg.Schema, representation view.Representation,
props iceberg.Properties) error {
+ version := &view.Version{Representations:
[]view.Representation{view.NewRepresentation(representation)}}
+ _, err := cat.CreateView(ctx, identifier, version, schema,
catalog.WithViewProperties(props))
+
+ return err
+}
diff --git a/catalog/sql/listing_test.go b/catalog/sql/listing_test.go
index 910101a80..21016f5a7 100644
--- a/catalog/sql/listing_test.go
+++ b/catalog/sql/listing_test.go
@@ -44,7 +44,7 @@ func (s *SqliteCatalogTestSuite)
TestListTablesAndViewsShareNamespaceButNotRows(
viewSchema := iceberg.NewSchema(1, iceberg.NestedField{
ID: 1, Name: "id", Type: iceberg.PrimitiveTypes.Int32,
Required: true,
})
- s.Require().NoError(cat.CreateView(ctx, viewID, viewSchema,
view.Representation{SQL: "SELECT 1", Dialect: "spark"}, nil))
+ s.Require().NoError(createTestView(ctx, cat, viewID, viewSchema,
view.Representation{SQL: "SELECT 1", Dialect: "spark"}, nil))
// A legacy V0 table migrated to V1 can have a NULL iceberg_type.
// It must remain visible as a table without appearing in the view list.
diff --git a/catalog/sql/sql.go b/catalog/sql/sql.go
index 37a147db8..4a0f3bdef 100644
--- a/catalog/sql/sql.go
+++ b/catalog/sql/sql.go
@@ -222,7 +222,10 @@ func init() {
}))
}
-var _ catalog.Catalog = (*Catalog)(nil)
+var (
+ _ catalog.Catalog = (*Catalog)(nil)
+ _ catalog.ViewCatalog = (*Catalog)(nil)
+)
const namespaceExistsProperty = "exists"
@@ -1566,34 +1569,50 @@ func (c *Catalog) CheckNamespaceExists(ctx
context.Context, namespace table.Iden
}
// CreateView creates a new view in the catalog.
-func (c *Catalog) CreateView(ctx context.Context, identifier table.Identifier,
schema *iceberg.Schema, representation view.Representation, props
iceberg.Properties) error {
+func (c *Catalog) CreateView(ctx context.Context, identifier table.Identifier,
version *view.Version, schema *iceberg.Schema, opts ...catalog.CreateViewOpt)
(*view.View, error) {
if c.isV0() {
- return errViewsUnsupportedOnV0
+ return nil, errViewsUnsupportedOnV0
}
if err := catalog.ValidateViewIdentifier(identifier); err != nil {
- return err
+ return nil, err
}
nsIdent := catalog.NamespaceFromIdent(identifier)
viewIdent := catalog.ObjectNameFromIdent(identifier)
ns, exists, err := c.resolveNamespaceKey(ctx, nsIdent)
if err != nil {
- return err
+ return nil, err
}
if !exists {
- return fmt.Errorf("%w: %s", catalog.ErrNoSuchNamespace, ns)
+ return nil, fmt.Errorf("%w: %s", catalog.ErrNoSuchNamespace, ns)
+ }
+
+ cfg := catalog.NewCreateViewCfg()
+ for _, opt := range opts {
+ opt(&cfg)
}
loc, err := internal.ResolveTableLocationWithNamespace(
- ctx, "", nsIdent, ns, viewIdent, c.props,
c.LoadNamespaceProperties)
+ ctx, cfg.Location, nsIdent, ns, viewIdent, c.props,
c.LoadNamespaceProperties)
if err != nil {
- return err
+ return nil, err
+ }
+
+ ioProps := maps.Clone(c.props)
+ if ioProps == nil {
+ ioProps = make(iceberg.Properties)
}
+ maps.Copy(ioProps, cfg.Properties)
- createdView, err := view.CreateView(ctx, c.name, identifier, schema,
representation, nsIdent, loc, props)
+ if version != nil && version.DefaultCatalog == "" {
+ version = version.Clone()
+ version.DefaultCatalog = c.name
+ }
+
+ createdView, err := view.CreateViewFromVersion(ctx, identifier, schema,
version, loc, ioProps, cfg.Properties)
if err != nil {
- return err
+ return nil, err
}
metadataLocation := createdView.MetadataLocation()
@@ -1630,13 +1649,17 @@ func (c *Catalog) CreateView(ctx context.Context,
identifier table.Identifier, s
err = collisionErr
}
}
- loadFS := io.LoadFSFunc(c.props, metadataLocation)
+ loadFS := io.LoadFSFunc(ioProps, metadataLocation)
if cleanupErr := removeUncommittedMetadata(ctx,
metadataLocation, loadFS); cleanupErr != nil {
err = errors.Join(err, cleanupErr)
}
}
- return err
+ if err != nil {
+ return nil, err
+ }
+
+ return createdView, nil
}
// DropView deletes a view from the catalog.
@@ -1745,7 +1768,7 @@ func (c *Catalog) CheckViewExists(ctx context.Context,
identifier table.Identifi
}
// LoadView loads a view from the catalog.
-func (c *Catalog) LoadView(ctx context.Context, identifier table.Identifier)
(view.Metadata, error) {
+func (c *Catalog) LoadView(ctx context.Context, identifier table.Identifier)
(*view.View, error) {
if c.isV0() {
return nil, errViewsUnsupportedOnV0
}
@@ -1790,5 +1813,5 @@ func (c *Catalog) LoadView(ctx context.Context,
identifier table.Identifier) (vi
return nil, err
}
- return loadedView.Metadata(), nil
+ return loadedView, nil
}
diff --git a/catalog/sql/sql_integration_test.go
b/catalog/sql/sql_integration_test.go
index f33bcc2b5..ccc56e440 100644
--- a/catalog/sql/sql_integration_test.go
+++ b/catalog/sql/sql_integration_test.go
@@ -238,7 +238,7 @@ func (s *SQLIntegrationSuite) TestCreateView() {
// Create a view
viewSQL := fmt.Sprintf("SELECT * FROM %s.%s", TestNamespaceIdent,
"test-table")
- err = s.cat.CreateView(s.ctx, catalog.ToIdentifier(TestNamespaceIdent,
"test-view"), tableSchemaSimple, view.Representation{SQL: viewSQL, Dialect:
"spark"}, iceberg.Properties{"foobar": "baz"})
+ err = createTestView(s.ctx, s.cat,
catalog.ToIdentifier(TestNamespaceIdent, "test-view"), tableSchemaSimple,
view.Representation{SQL: viewSQL, Dialect: "spark"},
iceberg.Properties{"foobar": "baz"})
s.Require().NoError(err)
exists, err = s.cat.CheckViewExists(s.ctx,
catalog.ToIdentifier(TestNamespaceIdent, "test-view"))
diff --git a/catalog/sql/sql_test.go b/catalog/sql/sql_test.go
index 747b10608..2025dc386 100644
--- a/catalog/sql/sql_test.go
+++ b/catalog/sql/sql_test.go
@@ -640,7 +640,7 @@ func (s *SqliteCatalogTestSuite)
TestV0SchemaViewOperationsUnsupported() {
})
viewID := []string{nsName, tableName()}
- err := cat.CreateView(ctx, viewID, schema, view.Representation{SQL:
"SELECT 1", Dialect: "spark"}, nil)
+ err := createTestView(ctx, cat, viewID, schema,
view.Representation{SQL: "SELECT 1", Dialect: "spark"}, nil)
s.ErrorContains(err, "V0", "CreateView must be rejected on a V0
catalog")
_, err = cat.LoadView(ctx, viewID)
@@ -1320,7 +1320,7 @@ func (s *SqliteCatalogTestSuite)
TestDottedNamespaceUsesOwnLocationProperties()
s.Equal(location+"/orders", createdTable.Location())
viewIdent := append(slices.Clone(namespace), "orders_view")
- s.Require().NoError(cat.CreateView(ctx, viewIdent,
tableSchemaNested, view.Representation{SQL: "SELECT * FROM orders", Dialect:
"spark"}, nil))
+ s.Require().NoError(createTestView(ctx, cat, viewIdent,
tableSchemaNested, view.Representation{SQL: "SELECT * FROM orders", Dialect:
"spark"}, nil))
createdView, err := cat.LoadView(ctx, viewIdent)
s.Require().NoError(err)
s.Equal(location+"/orders_view", createdView.Location())
@@ -2530,7 +2530,7 @@ func (s *SqliteCatalogTestSuite)
TestDropNamespaceRejectsViewOnlyNamespace() {
})
s.Require().NoError(cat.CreateNamespace(ctx, namespace, nil))
- s.Require().NoError(cat.CreateView(ctx, viewID, schema,
view.Representation{SQL: viewSQL, Dialect: "spark"}, nil))
+ s.Require().NoError(createTestView(ctx, cat, viewID, schema,
view.Representation{SQL: viewSQL, Dialect: "spark"}, nil))
err := cat.DropNamespace(ctx, namespace)
s.ErrorIs(err, catalog.ErrNamespaceNotEmpty)
@@ -3267,8 +3267,8 @@ func (s *SqliteCatalogTestSuite) TestCreateView() {
schema := iceberg.NewSchema(1, iceberg.NestedField{
ID: 1, Name: "id", Type: iceberg.PrimitiveTypes.Int32,
Required: true,
})
- s.Require().NoError(db.CreateView(context.Background(),
[]string{nsName, viewName}, schema, view.Representation{SQL: viewSQL, Dialect:
"spark"}, nil))
- s.Require().NoError(db.CreateView(context.Background(),
append(nestedNamespace, viewName), schema, view.Representation{SQL: viewSQL,
Dialect: "spark"}, nil))
+ s.Require().NoError(createTestView(context.Background(), db,
[]string{nsName, viewName}, schema, view.Representation{SQL: viewSQL, Dialect:
"spark"}, nil))
+ s.Require().NoError(createTestView(context.Background(), db,
append(nestedNamespace, viewName), schema, view.Representation{SQL: viewSQL,
Dialect: "spark"}, nil))
exists, err := db.CheckViewExists(context.Background(),
[]string{nsName, viewName})
s.Require().NoError(err)
@@ -3314,7 +3314,7 @@ func TestViewOperationsRejectInvalidIdentifiers(t
*testing.T) {
for _, test := range invalid {
t.Run(test.name, func(t *testing.T) {
- err := cat.CreateView(context.Background(),
test.identifier, nil, view.Representation{SQL: "", Dialect: "spark"}, nil)
+ err := createTestView(context.Background(), cat,
test.identifier, nil, view.Representation{SQL: "", Dialect: "spark"}, nil)
assert.ErrorIs(t, err, catalog.ErrNoSuchView)
_, err = cat.CheckViewExists(context.Background(),
test.identifier)
@@ -3345,7 +3345,7 @@ func (s *SqliteCatalogTestSuite)
TestCreateTableConflictsWithView() {
db := s.getCatalogSqlite()
identifier := s.randomTableIdentifier()
s.Require().NoError(db.CreateNamespace(ctx,
catalog.NamespaceFromIdent(identifier), nil))
- s.Require().NoError(db.CreateView(ctx, identifier, tableSchemaNested,
view.Representation{SQL: "SELECT 1", Dialect: "spark"}, nil))
+ s.Require().NoError(createTestView(ctx, db, identifier,
tableSchemaNested, view.Representation{SQL: "SELECT 1", Dialect: "spark"}, nil))
metadataDir := filepath.Join(s.warehouse, identifier[0]+".db",
identifier[1], "metadata")
before, err := os.ReadDir(metadataDir)
@@ -3371,7 +3371,7 @@ func (s *SqliteCatalogTestSuite)
TestCreateViewConflictsWithTable() {
before, err := os.ReadDir(metadataDir)
s.Require().NoError(err)
- err = db.CreateView(ctx, identifier, tableSchemaNested,
view.Representation{SQL: "SELECT 1", Dialect: "spark"}, nil)
+ err = createTestView(ctx, db, identifier, tableSchemaNested,
view.Representation{SQL: "SELECT 1", Dialect: "spark"}, nil)
s.ErrorIs(err, catalog.ErrTableAlreadyExists)
after, err := os.ReadDir(metadataDir)
@@ -3403,7 +3403,7 @@ func (s *SqliteCatalogTestSuite)
TestConcurrentTableViewCollisionReturnsCatalogS
}()
go func() {
<-start
- err := db.CreateView(ctx, identifier, tableSchemaNested,
view.Representation{SQL: "SELECT 1", Dialect: "spark"}, nil)
+ err := createTestView(ctx, db, identifier, tableSchemaNested,
view.Representation{SQL: "SELECT 1", Dialect: "spark"}, nil)
results <- createResult{objectType: sqlcat.ViewType, err: err}
}()
close(start)
@@ -3474,7 +3474,7 @@ func (s *SqliteCatalogTestSuite)
TestConcurrentTableViewCollisionUnderLockConten
}()
go func() {
<-start
- err := db.CreateView(ctx, identifier, tableSchemaNested,
view.Representation{SQL: "SELECT 1", Dialect: "spark"}, nil)
+ err := createTestView(ctx, db, identifier, tableSchemaNested,
view.Representation{SQL: "SELECT 1", Dialect: "spark"}, nil)
results <- createResult{objectType: sqlcat.ViewType, err: err}
}()
close(start)
@@ -3525,7 +3525,7 @@ func (s *SqliteCatalogTestSuite)
TestCreateRemovesMetadataIfCatalogInsertFails()
{
name: "view",
create: func(identifier table.Identifier) error {
- return db.CreateView(ctx, identifier,
tableSchemaNested, view.Representation{SQL: "SELECT 1", Dialect: "spark"}, nil)
+ return createTestView(ctx, db, identifier,
tableSchemaNested, view.Representation{SQL: "SELECT 1", Dialect: "spark"}, nil)
},
},
}
@@ -3556,7 +3556,7 @@ func (s *SqliteCatalogTestSuite) TestDropView() {
schema := iceberg.NewSchema(1, iceberg.NestedField{
ID: 1, Name: "id", Type: iceberg.PrimitiveTypes.Int32,
Required: true,
})
- s.Require().NoError(db.CreateView(context.Background(),
[]string{nsName, viewName}, schema, view.Representation{SQL: viewSQL, Dialect:
"spark"}, nil))
+ s.Require().NoError(createTestView(context.Background(), db,
[]string{nsName, viewName}, schema, view.Representation{SQL: viewSQL, Dialect:
"spark"}, nil))
exists, err := db.CheckViewExists(context.Background(),
[]string{nsName, viewName})
s.Require().NoError(err)
@@ -3615,7 +3615,7 @@ func (s *SqliteCatalogTestSuite)
TestDropViewWithInvalidMetadataLocation() {
schema := iceberg.NewSchema(1, iceberg.NestedField{
ID: 1, Name: "id", Type: iceberg.PrimitiveTypes.Int32,
Required: true,
})
- s.Require().NoError(db.CreateView(context.Background(),
[]string{nsName, viewName}, schema, view.Representation{SQL: viewSQL, Dialect:
"spark"}, nil))
+ s.Require().NoError(createTestView(context.Background(), db,
[]string{nsName, viewName}, schema, view.Representation{SQL: viewSQL, Dialect:
"spark"}, nil))
// Manually update the metadata location to a URL with an unsupported
scheme
// This will cause io.LoadFS to fail with "IO for file '...' not
implemented"
@@ -3651,7 +3651,7 @@ func (s *SqliteCatalogTestSuite) TestCheckViewExists() {
schema := iceberg.NewSchema(1, iceberg.NestedField{
ID: 1, Name: "id", Type: iceberg.PrimitiveTypes.Int32,
Required: true,
})
- s.Require().NoError(db.CreateView(context.Background(),
[]string{nsName, viewName}, schema, view.Representation{SQL: viewSQL, Dialect:
"spark"}, nil))
+ s.Require().NoError(createTestView(context.Background(), db,
[]string{nsName, viewName}, schema, view.Representation{SQL: viewSQL, Dialect:
"spark"}, nil))
exists, err = db.CheckViewExists(context.Background(), []string{nsName,
viewName})
s.Require().NoError(err)
@@ -3671,7 +3671,7 @@ func (s *SqliteCatalogTestSuite) TestListViews() {
schema := iceberg.NewSchema(1, iceberg.NestedField{
ID: 1, Name: "id", Type: iceberg.PrimitiveTypes.Int32,
Required: true,
})
- s.Require().NoError(db.CreateView(context.Background(),
[]string{nsName, viewName}, schema, view.Representation{SQL: viewSQL, Dialect:
"spark"}, nil))
+ s.Require().NoError(createTestView(context.Background(), db,
[]string{nsName, viewName}, schema, view.Representation{SQL: viewSQL, Dialect:
"spark"}, nil))
}
var foundViews []table.Identifier
@@ -3725,12 +3725,12 @@ func (s *SqliteCatalogTestSuite) TestLoadView() {
"comment": "Test view",
"owner": "test-user",
}
- s.Require().NoError(db.CreateView(context.Background(),
[]string{nsName, viewName}, schema, view.Representation{SQL: viewSQL, Dialect:
"spark"}, props))
+ s.Require().NoError(createTestView(context.Background(), db,
[]string{nsName, viewName}, schema, view.Representation{SQL: viewSQL, Dialect:
"spark"}, props))
viewInfo, err := db.LoadView(context.Background(), []string{nsName,
viewName})
s.Require().NoError(err)
- s.Equal(1, viewInfo.FormatVersion())
+ s.Equal(1, viewInfo.Metadata().FormatVersion())
s.Contains(viewInfo.Properties(), "comment")
s.Equal("Test view", viewInfo.Properties()["comment"])
@@ -3742,3 +3742,64 @@ func (s *SqliteCatalogTestSuite) TestLoadView() {
func TestSqlCatalog(t *testing.T) {
suite.Run(t, new(SqliteCatalogTestSuite))
}
+
+func createTestView(ctx context.Context, cat *sqlcat.Catalog, identifier
table.Identifier, schema *iceberg.Schema, representation view.Representation,
props iceberg.Properties) error {
+ version := &view.Version{Representations:
[]view.Representation{view.NewRepresentation(representation)}}
+ _, err := cat.CreateView(ctx, identifier, version, schema,
catalog.WithViewProperties(props))
+
+ return err
+}
+
+func (s *SqliteCatalogTestSuite) TestViewCatalogVersionAndOptions() {
+ ctx := context.Background()
+ cat := s.getCatalogSqlite()
+ id := table.Identifier{databaseName(), "view"}
+ s.Require().NoError(cat.CreateNamespace(ctx, id[:1], nil))
+ version, err := view.NewVersion(7, 1, []view.Representation{
+ view.NewRepresentation(view.Representation{SQL: "SELECT 1",
Dialect: "spark"}),
+ view.NewRepresentation(view.Representation{SQL: "SELECT 2",
Dialect: "trino"}),
+ }, table.Identifier{"other"}, view.WithDefaultViewCatalog("prod"),
view.WithVersionSummary(view.VersionSummary{"engine-version": "1"}),
view.WithTimestampMS(123456))
+ s.Require().NoError(err)
+ location := "file://" + filepath.Join(s.warehouse, "custom-view")
+ props := iceberg.Properties{"comment": "hello"}
+ var views catalog.ViewCatalog = cat
+ created, err := views.CreateView(ctx, id, version, tableSchemaNested,
catalog.WithViewLocation(location+"/"), catalog.WithViewProperties(props))
+ s.Require().NoError(err)
+ s.Equal(location, created.Location())
+ s.Equal(id, created.Identifier())
+ loaded, err := views.LoadView(ctx, id)
+ s.Require().NoError(err)
+ s.Equal(created.MetadataLocation(), loaded.MetadataLocation())
+ s.Equal(props, loaded.Properties())
+ got := loaded.CurrentVersion()
+ s.Equal(version.Representations, got.Representations)
+ s.Equal(version.DefaultNamespace, got.DefaultNamespace)
+ s.Equal(version.DefaultCatalog, got.DefaultCatalog)
+ s.Equal(version.Summary, got.Summary)
+ s.Equal(version.TimestampMS, got.TimestampMS)
+ s.Equal(int64(7), version.VersionID)
+ s.Equal(int64(view.InitialVersionID), got.VersionID)
+ _, err = views.CreateView(ctx, table.Identifier{id[0], "nil_version"},
nil, tableSchemaNested)
+ s.ErrorIs(err, view.ErrInvalidViewMetadata)
+}
+
+func (s *SqliteCatalogTestSuite)
TestCreateViewDefaultsCatalogWithoutMutatingVersion() {
+ ctx := context.Background()
+ cat := s.getCatalogSqlite()
+ identifier := table.Identifier{databaseName(), "view"}
+ s.Require().NoError(cat.CreateNamespace(ctx, identifier[:1], nil))
+
+ version := &view.Version{
+ Representations: []view.Representation{
+ view.NewRepresentation(view.Representation{SQL: "SELECT
1", Dialect: "spark"}),
+ },
+ }
+ created, err := cat.CreateView(ctx, identifier, version,
tableSchemaNested)
+ s.Require().NoError(err)
+ s.Equal(cat.Name(), created.CurrentVersion().DefaultCatalog)
+ s.Empty(version.DefaultCatalog)
+
+ loaded, err := cat.LoadView(ctx, identifier)
+ s.Require().NoError(err)
+ s.Equal(cat.Name(), loaded.CurrentVersion().DefaultCatalog)
+}
diff --git a/website/src/releases.md b/website/src/releases.md
index 92c9c6327..f7837145c 100644
--- a/website/src/releases.md
+++ b/website/src/releases.md
@@ -42,9 +42,10 @@ Per-release notes (highlights, breaking changes,
contributors) are published on
### Unreleased compatibility notes
+- **SQL view APIs align with `catalog.ViewCatalog`.** `CreateView` now takes a
`*view.Version`, schema and options, and returns `*view.View`. `LoadView` now
returns `*view.View`; use `Metadata()` to access its metadata. If the caller
leaves `DefaultCatalog` empty, SQL still sets it to the catalog name.
- **`LocationProvider.NewDataLocation` takes the partition path.** The method
is now `NewDataLocation(dataFileName, partitionPath string)`, matching Iceberg
Java and PyIceberg, so custom `LocationProvider` implementations must add the
argument. Callers pass the partition path there, or `""` for an unpartitioned
file, instead of prefixing the file name.
- **Partitioned data files follow the table's location provider.** With
`write.object-storage.enabled=true` and the default
`write.object-storage.partitioned-paths=true`, new partitioned files land under
`data/<hash>/<partition>/` instead of `data/<partition>/<hash>/`, which can
affect prefix-based lifecycle rules and bucket policies.
`write.object-storage.partitioned-paths=false` and `write.data.path` now apply
to partitioned writes. Partitioned paths also no longer percent-encode the t
[...]
-- **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 [...]
+- **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, Hive, and SQL), while
REST also implements `UpdatableViewCatalog` for `UpdateView`. Use
`catalog.As[T](cat)` and the `Unwrap() Catalog` convention to discover optional
capabilities through decorators. Future ca [...]
- **`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`.
@@ -63,7 +64,7 @@ Per-release notes (highlights, breaking changes,
contributors) are published on
- **KMS factory registration now takes a context.** Change
`encryption.KMSFactory` implementations to accept `(context.Context,
map[string]string)` instead of only properties, and call
`encryption.LoadKeyManagementClient(ctx, props)` instead of
`LoadKeyManagementClient(props)`. The existing global `RegisterKMS`,
`UnregisterKMS`, and `GetRegisteredKMSNames` functions still use the default
registry; `RegisterKMS` still panics for invalid registrations. Use
`encryption.MustRegisterKMS` for [...]
- **Name-mapping child lookup renamed.** Replace `MappedField.GetField(name)`
with `MappedField.FindFieldByName(name)`, which returns `(*MappedField, bool)`.
The new method returns a pointer to the actual child field rather than a copy,
and reports a missing name with `false`.
- **Data file block size setter removed.**
`iceberg.DataFileBuilder.BlockSizeInBytes` is removed. New v1 manifests use the
Avro schema's 64 MiB default when the field is unset. V2/v3 omit this
deprecated field, and legacy v1 values remain readable.
-- **View creation APIs take a named SQL representation.**
`view.NewVersionFromSQL`, `view.CreateView`, `view.CreateViewWithIOProperties`,
and `sql.Catalog.CreateView` now accept a `view.Representation{SQL: sql,
Dialect: dialect}` instead of positional SQL/dialect strings. The dialect is
required; Hive preserves the selected representation's dialect.
+- **View creation APIs take a named SQL representation.**
`view.NewVersionFromSQL`, `view.CreateView`, and
`view.CreateViewWithIOProperties` now accept a `view.Representation{SQL: sql,
Dialect: dialect}` instead of positional SQL/dialect strings. The dialect is
required; Hive preserves the selected representation's dialect.
- **View SQL representation construction uses named fields.** Replace
`view.NewRepresentation(sql, dialect)` with
`view.NewRepresentation(view.Representation{SQL: sql, Dialect: dialect})`.
Serialized JSON is unchanged.
- **Hive views keep the caller's view version.** `hive.Catalog.CreateView` now
stores every representation, the summary, the default namespace and
`default-catalog` as passed, instead of one representation with
`default-catalog` set to `"hive"`.
- **Reserved table properties now use a read-only API.** Replace references to
`table.ReservedProperties` with `table.ReservedProperties()`. The function
returns a fresh slice, so callers cannot change metadata validation by
modifying it.