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.

Reply via email to