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 362f47eb0 refactor(view)!: require named SQL fields and explicit 
dialect (#2324)
362f47eb0 is described below

commit 362f47eb0e3d73fa405a2633a82f91fb72c52e59
Author: Minh Vu <[email protected]>
AuthorDate: Tue Oct 27 01:49:27 2026 +0100

    refactor(view)!: require named SQL fields and explicit dialect (#2324)
    
    * refactor(view)!: require named SQL fields and explicit dialect
    
    Signed-off-by: Minh Vu <[email protected]>
    
    * style(view): satisfy nlreturn lint checks
    
    * fix(view): pass SQL dialect in catalog integration test
    
    * chore: sync release notes with main to resolve conflicts
    
    * fix(view): pass SQL and dialect as a single representation across APIs
    
    ---------
    
    Signed-off-by: Minh Vu <[email protected]>
---
 catalog/hive/hive.go                        | 30 ++++++------
 catalog/hive/hive_integration_test.go       |  9 ++--
 catalog/hive/hive_test.go                   | 14 +++---
 catalog/hive/view_dialect_test.go           | 73 +++++++++++++++++++++++++++++
 catalog/rest/rest_integration_test.go       |  6 +--
 catalog/rest/rest_test.go                   |  8 ++--
 catalog/sql/drop_namespace_internal_test.go |  3 +-
 catalog/sql/listing_test.go                 |  3 +-
 catalog/sql/sql.go                          |  4 +-
 catalog/sql/sql_integration_test.go         |  3 +-
 catalog/sql/sql_test.go                     | 33 ++++++-------
 view/metadata.go                            | 20 ++++----
 view/metadata_builder_test.go               | 31 +++++++-----
 view/metadata_test.go                       | 11 ++++-
 view/view.go                                | 14 +++---
 view/view_test.go                           |  4 +-
 website/src/api.md                          |  2 +-
 website/src/releases.md                     |  4 ++
 18 files changed, 186 insertions(+), 86 deletions(-)

diff --git a/catalog/hive/hive.go b/catalog/hive/hive.go
index a759748f5..83de89d18 100644
--- a/catalog/hive/hive.go
+++ b/catalog/hive/hive.go
@@ -359,7 +359,7 @@ func (c *Catalog) CreateView(ctx context.Context, 
identifier table.Identifier, v
                return nil, err
        }
 
-       viewSQL, err := sqlFromVersion(version)
+       representation, err := sqlFromVersion(version)
        if err != nil {
                return nil, err
        }
@@ -376,7 +376,7 @@ func (c *Catalog) CreateView(ctx context.Context, 
identifier table.Identifier, v
        }
 
        createdView, err := view.CreateViewWithIOProperties(
-               ctx, catalogName, identifier, freshSchema, viewSQL, defaultNS, 
loc, ioProps, cfg.Properties,
+               ctx, catalogName, identifier, freshSchema, representation, 
defaultNS, loc, ioProps, cfg.Properties,
        )
        if err != nil {
                return nil, err
@@ -387,7 +387,7 @@ func (c *Catalog) CreateView(ctx context.Context, 
identifier table.Identifier, v
                maps.Copy(viewProps, cfg.Properties)
        }
 
-       hiveTbl := constructHiveViewTable(database, viewName, loc, 
createdView.MetadataLocation(), freshSchema, viewSQL, viewProps)
+       hiveTbl := constructHiveViewTable(database, viewName, loc, 
createdView.MetadataLocation(), freshSchema, representation.SQL, viewProps)
        if err := c.client.CreateTable(ctx, hiveTbl); err != nil {
                if isAlreadyExistsError(err) {
                        return nil, fmt.Errorf("%w: %s.%s", 
catalog.ErrViewAlreadyExists, database, viewName)
@@ -399,28 +399,30 @@ func (c *Catalog) CreateView(ctx context.Context, 
identifier table.Identifier, v
        return createdView, nil
 }
 
-// sqlFromVersion returns the SQL from the first SQL representation in 
version, preferring dialect "hive".
-func sqlFromVersion(v *view.Version) (string, error) {
+// sqlFromVersion selects a SQL representation, preferring the Hive dialect.
+// Preserve its dialect so creating the view does not rewrite it to "default".
+func sqlFromVersion(v *view.Version) (view.Representation, error) {
        if v == nil || len(v.Representations) == 0 {
-               return "", errors.New("view version has no representations")
+               return view.Representation{}, errors.New("view version has no 
representations")
        }
-       var fallback string
-       for _, r := range v.Representations {
+       var fallback *view.Representation
+       for i := range v.Representations {
+               r := &v.Representations[i]
                if r.Type != "sql" {
                        continue
                }
                if strings.EqualFold(r.Dialect, "hive") {
-                       return r.SQL, nil
+                       return *r, nil
                }
-               if fallback == "" {
-                       fallback = r.SQL
+               if fallback == nil {
+                       fallback = r
                }
        }
-       if fallback != "" {
-               return fallback, nil
+       if fallback != nil {
+               return *fallback, nil
        }
 
-       return "", errors.New("view version has no SQL representation")
+       return view.Representation{}, errors.New("view version has no SQL 
representation")
 }
 
 func (c *Catalog) DropTable(ctx context.Context, identifier table.Identifier) 
(err error) {
diff --git a/catalog/hive/hive_integration_test.go 
b/catalog/hive/hive_integration_test.go
index a5202f154..6f99ff1e9 100644
--- a/catalog/hive/hive_integration_test.go
+++ b/catalog/hive/hive_integration_test.go
@@ -454,7 +454,7 @@ func TestHiveIntegrationCreateView(t *testing.T) {
 
        schema := iceberg.NewSchema(1, iceberg.NestedField{ID: 1, Name: "col", 
Type: iceberg.PrimitiveTypes.Int32, Required: true})
        viewSQL := "SELECT 1 AS col"
-       ver, err := view.NewVersionFromSQL(1, 0, viewSQL, 
table.Identifier{dbName})
+       ver, err := view.NewVersionFromSQL(1, 0, view.Representation{SQL: 
viewSQL, Dialect: "hive"}, table.Identifier{dbName})
        assert.NoError(err)
 
        viewLocation := getTestTableLocation() + "/" + dbName + "/" + viewName
@@ -476,6 +476,7 @@ func TestHiveIntegrationCreateView(t *testing.T) {
        assert.Len(loaded.CurrentVersion().Representations, 1)
        assert.Equal("sql", loaded.CurrentVersion().Representations[0].Type)
        assert.Equal(viewSQL, loaded.CurrentVersion().Representations[0].SQL)
+       assert.Equal("hive", loaded.CurrentVersion().Representations[0].Dialect)
 }
 
 func TestHiveIntegrationCreateViewThenDrop(t *testing.T) {
@@ -494,7 +495,7 @@ func TestHiveIntegrationCreateViewThenDrop(t *testing.T) {
        defer cat.DropNamespace(context.TODO(), DatabaseIdentifier(dbName))
 
        schema := iceberg.NewSchema(1, iceberg.NestedField{ID: 1, Name: "id", 
Type: iceberg.PrimitiveTypes.Int64, Required: true})
-       ver, _ := view.NewVersionFromSQL(1, 0, "SELECT 1 AS id", 
table.Identifier{dbName})
+       ver, _ := view.NewVersionFromSQL(1, 0, view.Representation{SQL: "SELECT 
1 AS id", Dialect: "hive"}, table.Identifier{dbName})
 
        viewLocation := getTestTableLocation() + "/" + dbName + "/" + viewName
        _, err = cat.CreateView(context.TODO(), TableIdentifier(dbName, 
viewName), ver, schema,
@@ -541,7 +542,7 @@ func TestHiveIntegrationCreateView_TableConflict(t 
*testing.T) {
        assert.NoError(err)
        defer cat.DropTable(context.TODO(), TableIdentifier(dbName, tableName))
 
-       ver, _ := view.NewVersionFromSQL(1, 0, "SELECT * FROM t1", 
table.Identifier{dbName})
+       ver, _ := view.NewVersionFromSQL(1, 0, view.Representation{SQL: "SELECT 
* FROM t1", Dialect: "hive"}, table.Identifier{dbName})
        viewLocation := getTestTableLocation() + "/" + dbName + "/" + tableName 
+ "_view"
        _, err = cat.CreateView(context.TODO(), TableIdentifier(dbName, 
tableName), ver, schema,
                catalog.WithViewLocation(viewLocation),
@@ -566,7 +567,7 @@ func TestHiveIntegrationCreateView_ViewConflict(t 
*testing.T) {
        defer cat.DropNamespace(context.TODO(), DatabaseIdentifier(dbName))
 
        schema := iceberg.NewSchema(1, iceberg.NestedField{ID: 1, Name: "col", 
Type: iceberg.PrimitiveTypes.Int32, Required: true})
-       ver, _ := view.NewVersionFromSQL(1, 0, "SELECT 1 AS col", 
table.Identifier{dbName})
+       ver, _ := view.NewVersionFromSQL(1, 0, view.Representation{SQL: "SELECT 
1 AS col", Dialect: "hive"}, table.Identifier{dbName})
        viewLocation := getTestTableLocation() + "/" + dbName + "/" + viewName
 
        _, err = cat.CreateView(context.TODO(), TableIdentifier(dbName, 
viewName), ver, schema,
diff --git a/catalog/hive/hive_test.go b/catalog/hive/hive_test.go
index 9190b7571..9b231b39d 100644
--- a/catalog/hive/hive_test.go
+++ b/catalog/hive/hive_test.go
@@ -1928,7 +1928,7 @@ func TestCreateView_NamespaceNotFound(t *testing.T) {
 
        cat := NewCatalogWithClient(mockClient, iceberg.Properties{})
 
-       ver, _ := view.NewVersionFromSQL(1, 0, "SELECT 1", 
table.Identifier{"missing_db"})
+       ver, _ := view.NewVersionFromSQL(1, 0, view.Representation{SQL: "SELECT 
1", Dialect: "hive"}, table.Identifier{"missing_db"})
        schema := iceberg.NewSchema(1, iceberg.NestedField{ID: 1, Name: "col", 
Type: iceberg.PrimitiveTypes.Int32, Required: true})
 
        _, err := cat.CreateView(context.Background(), 
TableIdentifier("missing_db", "v1"), ver, schema, 
catalog.WithViewLocation("file:///tmp/loc"))
@@ -1947,7 +1947,7 @@ func TestCreateView_ViewAlreadyExists(t *testing.T) {
 
        cat := NewCatalogWithClient(mockClient, iceberg.Properties{})
 
-       ver, _ := view.NewVersionFromSQL(1, 0, "SELECT 1", 
table.Identifier{"test_database"})
+       ver, _ := view.NewVersionFromSQL(1, 0, view.Representation{SQL: "SELECT 
1", Dialect: "hive"}, table.Identifier{"test_database"})
        schema := iceberg.NewSchema(1, iceberg.NestedField{ID: 1, Name: "col", 
Type: iceberg.PrimitiveTypes.Int32, Required: true})
 
        _, err := cat.CreateView(context.Background(), 
TableIdentifier("test_database", "test_view"), ver, schema, 
catalog.WithViewLocation("file:///tmp/loc"))
@@ -1967,7 +1967,7 @@ func TestCreateView_TableAlreadyExists(t *testing.T) {
 
        cat := NewCatalogWithClient(mockClient, iceberg.Properties{})
 
-       ver, _ := view.NewVersionFromSQL(1, 0, "SELECT 1", 
table.Identifier{"test_database"})
+       ver, _ := view.NewVersionFromSQL(1, 0, view.Representation{SQL: "SELECT 
1", Dialect: "hive"}, table.Identifier{"test_database"})
        schema := iceberg.NewSchema(1, iceberg.NestedField{ID: 1, Name: "col", 
Type: iceberg.PrimitiveTypes.Int32, Required: true})
 
        _, err := cat.CreateView(context.Background(), 
TableIdentifier("test_database", "test_table"), ver, schema, 
catalog.WithViewLocation("file:///tmp/loc"))
@@ -1982,7 +1982,7 @@ func TestCreateView_InvalidIdentifier(t *testing.T) {
        schema := iceberg.NewSchema(1, iceberg.NestedField{ID: 1, Name: "col", 
Type: iceberg.PrimitiveTypes.Int32, Required: true})
 
        for _, ident := range []table.Identifier{nil, {}, {"only_db"}} {
-               ver, err := view.NewVersionFromSQL(1, 0, "SELECT 1", 
table.Identifier{"db"})
+               ver, err := view.NewVersionFromSQL(1, 0, 
view.Representation{SQL: "SELECT 1", Dialect: "hive"}, table.Identifier{"db"})
                require.NoError(t, err)
                _, err = cat.CreateView(context.Background(), ident, ver, 
schema)
                require.ErrorIs(t, err, catalog.ErrNoSuchView)
@@ -2015,7 +2015,7 @@ func TestCreateView_Success(t *testing.T) {
 
        cat := NewCatalogWithClient(mockClient, iceberg.Properties{})
 
-       ver, err := view.NewVersionFromSQL(1, 0, "SELECT 1 AS col", 
table.Identifier{"test_database"})
+       ver, err := view.NewVersionFromSQL(1, 0, view.Representation{SQL: 
"SELECT 1 AS col", Dialect: "hive"}, table.Identifier{"test_database"})
        assert.NoError(err)
        schema := iceberg.NewSchema(1, iceberg.NestedField{ID: 1, Name: "col", 
Type: iceberg.PrimitiveTypes.Int32, Required: true})
 
@@ -2046,7 +2046,7 @@ func TestCreateViewDoesNotPersistCatalogProperties(t 
*testing.T) {
        mockClient.On("CreateTable", mock.Anything, 
mock.Anything).Return(nil).Once()
 
        cat := NewCatalogWithClient(mockClient, catalogProps)
-       ver, err := view.NewVersionFromSQL(1, 0, "SELECT 1 AS col", 
table.Identifier{"test_database"})
+       ver, err := view.NewVersionFromSQL(1, 0, view.Representation{SQL: 
"SELECT 1 AS col", Dialect: "hive"}, table.Identifier{"test_database"})
        assert.NoError(err)
        schema := iceberg.NewSchema(1, iceberg.NestedField{ID: 1, Name: "col", 
Type: iceberg.PrimitiveTypes.Int32, Required: true})
 
@@ -2083,7 +2083,7 @@ func TestCreateView_VersionNoSQLRepresentation(t 
*testing.T) {
 
        cat := NewCatalogWithClient(mockClient, iceberg.Properties{})
 
-       ver, err := view.NewVersionFromSQL(1, 0, "SELECT 1", 
table.Identifier{"test_database"})
+       ver, err := view.NewVersionFromSQL(1, 0, view.Representation{SQL: 
"SELECT 1", Dialect: "hive"}, table.Identifier{"test_database"})
        assert.NoError(err)
        ver.Representations = nil // no SQL representation
 
diff --git a/catalog/hive/view_dialect_test.go 
b/catalog/hive/view_dialect_test.go
new file mode 100644
index 000000000..e38aae76c
--- /dev/null
+++ b/catalog/hive/view_dialect_test.go
@@ -0,0 +1,73 @@
+// 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 hive
+
+import (
+       "testing"
+
+       "github.com/apache/iceberg-go/view"
+       "github.com/stretchr/testify/require"
+)
+
+func TestSQLFromVersionPreservesDialect(t *testing.T) {
+       tests := []struct {
+               name        string
+               version     *view.Version
+               wantSQL     string
+               wantDialect string
+               wantErr     string
+       }{
+               {
+                       name: "prefer Hive representation",
+                       version: &view.Version{Representations: 
[]view.Representation{
+                               view.NewRepresentation(view.Representation{SQL: 
"SELECT trino", Dialect: "trino"}),
+                               view.NewRepresentation(view.Representation{SQL: 
"SELECT hive", Dialect: "HIVE"}),
+                       }},
+                       wantSQL:     "SELECT hive",
+                       wantDialect: "HIVE",
+               },
+               {
+                       name: "keep fallback dialect",
+                       version: &view.Version{Representations: 
[]view.Representation{
+                               view.NewRepresentation(view.Representation{SQL: 
"SELECT spark", Dialect: "spark"}),
+                               view.NewRepresentation(view.Representation{SQL: 
"SELECT trino", Dialect: "trino"}),
+                       }},
+                       wantSQL:     "SELECT spark",
+                       wantDialect: "spark",
+               },
+               {name: "nil version", wantErr: "no representations"},
+               {
+                       name:    "no SQL representation",
+                       version: &view.Version{Representations: 
[]view.Representation{{Type: "other"}}},
+                       wantErr: "no SQL representation",
+               },
+       }
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       representation, err := sqlFromVersion(tt.version)
+                       if tt.wantErr != "" {
+                               require.ErrorContains(t, err, tt.wantErr)
+
+                               return
+                       }
+                       require.NoError(t, err)
+                       require.Equal(t, tt.wantSQL, representation.SQL)
+                       require.Equal(t, tt.wantDialect, representation.Dialect)
+               })
+       }
+}
diff --git a/catalog/rest/rest_integration_test.go 
b/catalog/rest/rest_integration_test.go
index ea9d66cbd..419021762 100644
--- a/catalog/rest/rest_integration_test.go
+++ b/catalog/rest/rest_integration_test.go
@@ -213,7 +213,7 @@ func (s *RestIntegrationSuite) TestCreateView() {
        s.True(exists)
 
        // Create a view
-       viewRepr := view.NewRepresentation(fmt.Sprintf("SELECT * FROM  %s.%s", 
TestNamespaceIdent, "test-table"), "trino")
+       viewRepr := view.NewRepresentation(view.Representation{SQL: 
fmt.Sprintf("SELECT * FROM  %s.%s", TestNamespaceIdent, "test-table"), Dialect: 
"trino"})
        viewVersion, err := view.NewVersion(1, 1, 
[]view.Representation{viewRepr}, table.Identifier{TestNamespaceIdent})
        s.Require().NoError(err)
 
@@ -254,7 +254,7 @@ func (s *RestIntegrationSuite) TestUpdateView() {
        s.True(exists)
 
        // Create a view
-       viewRepr := view.NewRepresentation(fmt.Sprintf("SELECT * FROM  %s.%s", 
TestNamespaceIdent, "test-table"), "trino")
+       viewRepr := view.NewRepresentation(view.Representation{SQL: 
fmt.Sprintf("SELECT * FROM  %s.%s", TestNamespaceIdent, "test-table"), Dialect: 
"trino"})
        defaultViewNS := catalog.ToIdentifier(location, TestNamespaceIdent)
        viewVersion, err := view.NewVersion(1, 1, 
[]view.Representation{viewRepr}, defaultViewNS)
 
@@ -309,7 +309,7 @@ func (s *RestIntegrationSuite) TestRenameView() {
        fromIdent := catalog.ToIdentifier(TestNamespaceIdent, "test-view")
        toIdent := catalog.ToIdentifier(TestNamespaceIdent, "renamed-view")
 
-       viewRepr := view.NewRepresentation(fmt.Sprintf("SELECT * FROM %s.%s", 
TestNamespaceIdent, "test-table"), "trino")
+       viewRepr := view.NewRepresentation(view.Representation{SQL: 
fmt.Sprintf("SELECT * FROM %s.%s", TestNamespaceIdent, "test-table"), Dialect: 
"trino"})
        viewVersion, err := view.NewVersion(1, 1, 
[]view.Representation{viewRepr}, table.Identifier{TestNamespaceIdent})
        s.Require().NoError(err)
 
diff --git a/catalog/rest/rest_test.go b/catalog/rest/rest_test.go
index ef4901c58..a46756c12 100644
--- a/catalog/rest/rest_test.go
+++ b/catalog/rest/rest_test.go
@@ -3491,7 +3491,7 @@ func (r *RestCatalogSuite) TestCreateView200() {
                "owner":       "admin",
                "view-format": "iceberg",
        }
-       reprs := []view.Representation{view.NewRepresentation(exampleViewSQL, 
"default")}
+       reprs := 
[]view.Representation{view.NewRepresentation(view.Representation{SQL: 
exampleViewSQL, Dialect: "default"})}
        version, err := view.NewVersion(1, 0, reprs, table.Identifier{ns},
                view.WithDefaultViewCatalog("default-catalog"),
                view.WithTimestampMS(0))
@@ -3555,7 +3555,7 @@ func (r *RestCatalogSuite) TestCreateView409() {
                Required: true,
        })
        sql := "SELECT * FROM table"
-       reprs := []view.Representation{view.NewRepresentation(sql, "default")}
+       reprs := 
[]view.Representation{view.NewRepresentation(view.Representation{SQL: sql, 
Dialect: "default"})}
        version, err := view.NewVersion(1, 1, reprs, table.Identifier{ns})
        r.Require().NoError(err)
 
@@ -3590,7 +3590,7 @@ func (r *RestCatalogSuite) TestCreateView404() {
        })
        sql := "SELECT * FROM table"
        reprs := []view.Representation{
-               view.NewRepresentation(sql, "spark"),
+               view.NewRepresentation(view.Representation{SQL: sql, Dialect: 
"spark"}),
        }
        version, err := view.NewVersion(1, 1, reprs, table.Identifier{ns})
        r.Require().NoError(err)
@@ -3693,7 +3693,7 @@ func (r *RestCatalogSuite) TestCreateViewLabels() {
        schema := iceberg.NewSchemaWithIdentifiers(0, []int{1}, 
iceberg.NestedField{
                ID: 1, Name: "id", Type: iceberg.PrimitiveTypes.Int32, 
Required: true,
        })
-       reprs := []view.Representation{view.NewRepresentation(exampleViewSQL, 
"default")}
+       reprs := 
[]view.Representation{view.NewRepresentation(view.Representation{SQL: 
exampleViewSQL, Dialect: "default"})}
        version, err := view.NewVersion(1, 0, reprs, table.Identifier{ns},
                view.WithDefaultViewCatalog("default-catalog"), 
view.WithTimestampMS(0))
        r.Require().NoError(err)
diff --git a/catalog/sql/drop_namespace_internal_test.go 
b/catalog/sql/drop_namespace_internal_test.go
index 0f3afda63..113531879 100644
--- a/catalog/sql/drop_namespace_internal_test.go
+++ b/catalog/sql/drop_namespace_internal_test.go
@@ -31,6 +31,7 @@ import (
        "github.com/apache/iceberg-go/catalog"
        _ "github.com/apache/iceberg-go/io/gocloud"
        "github.com/apache/iceberg-go/table"
+       "github.com/apache/iceberg-go/view"
        mysql "github.com/go-sql-driver/mysql"
        "github.com/stretchr/testify/require"
        "github.com/uptrace/bun"
@@ -234,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, 
"SELECT 1", nil)
+                               return cat.CreateView(ctx, 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)
diff --git a/catalog/sql/listing_test.go b/catalog/sql/listing_test.go
index 4e7cc6c5e..910101a80 100644
--- a/catalog/sql/listing_test.go
+++ b/catalog/sql/listing_test.go
@@ -23,6 +23,7 @@ import (
        "github.com/apache/iceberg-go"
        "github.com/apache/iceberg-go/catalog"
        "github.com/apache/iceberg-go/table"
+       "github.com/apache/iceberg-go/view"
 )
 
 func (s *SqliteCatalogTestSuite) 
TestListTablesAndViewsShareNamespaceButNotRows() {
@@ -43,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, "SELECT 1", 
nil))
+       s.Require().NoError(cat.CreateView(ctx, 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 c16ba6ae4..57d485313 100644
--- a/catalog/sql/sql.go
+++ b/catalog/sql/sql.go
@@ -1566,7 +1566,7 @@ 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, viewSQL string, props iceberg.Properties) error {
+func (c *Catalog) CreateView(ctx context.Context, identifier table.Identifier, 
schema *iceberg.Schema, representation view.Representation, props 
iceberg.Properties) error {
        if c.isV0() {
                return errViewsUnsupportedOnV0
        }
@@ -1591,7 +1591,7 @@ func (c *Catalog) CreateView(ctx context.Context, 
identifier table.Identifier, s
                return err
        }
 
-       createdView, err := view.CreateView(ctx, c.name, identifier, schema, 
viewSQL, nsIdent, loc, props)
+       createdView, err := view.CreateView(ctx, c.name, identifier, schema, 
representation, nsIdent, loc, props)
        if err != nil {
                return err
        }
diff --git a/catalog/sql/sql_integration_test.go 
b/catalog/sql/sql_integration_test.go
index 802816465..25e3a097f 100644
--- a/catalog/sql/sql_integration_test.go
+++ b/catalog/sql/sql_integration_test.go
@@ -36,6 +36,7 @@ import (
        "github.com/apache/iceberg-go/io"
        _ "github.com/apache/iceberg-go/io/gocloud"
        "github.com/apache/iceberg-go/table"
+       "github.com/apache/iceberg-go/view"
        "github.com/stretchr/testify/suite"
 )
 
@@ -237,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, viewSQL, iceberg.Properties{"foobar": "baz"})
+       err = s.cat.CreateView(s.ctx, 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 838d6d75a..e5005314f 100644
--- a/catalog/sql/sql_test.go
+++ b/catalog/sql/sql_test.go
@@ -49,6 +49,7 @@ import (
        _ "github.com/apache/iceberg-go/io/gocloud"
        "github.com/apache/iceberg-go/metrics"
        "github.com/apache/iceberg-go/table"
+       "github.com/apache/iceberg-go/view"
        "github.com/google/uuid"
        "github.com/stretchr/testify/assert"
        "github.com/stretchr/testify/require"
@@ -639,7 +640,7 @@ func (s *SqliteCatalogTestSuite) 
TestV0SchemaViewOperationsUnsupported() {
        })
        viewID := []string{nsName, tableName()}
 
-       err := cat.CreateView(ctx, viewID, schema, "SELECT 1", nil)
+       err := cat.CreateView(ctx, 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)
@@ -1319,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, "SELECT * FROM orders", nil))
+               s.Require().NoError(cat.CreateView(ctx, 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())
@@ -2117,7 +2118,7 @@ func (s *SqliteCatalogTestSuite) 
TestDropNamespaceRejectsViewOnlyNamespace() {
        })
 
        s.Require().NoError(cat.CreateNamespace(ctx, namespace, nil))
-       s.Require().NoError(cat.CreateView(ctx, viewID, schema, viewSQL, nil))
+       s.Require().NoError(cat.CreateView(ctx, viewID, schema, 
view.Representation{SQL: viewSQL, Dialect: "spark"}, nil))
 
        err := cat.DropNamespace(ctx, namespace)
        s.ErrorIs(err, catalog.ErrNamespaceNotEmpty)
@@ -2854,8 +2855,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, viewSQL, nil))
-       s.Require().NoError(db.CreateView(context.Background(), 
append(nestedNamespace, viewName), schema, viewSQL, nil))
+       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))
 
        exists, err := db.CheckViewExists(context.Background(), 
[]string{nsName, viewName})
        s.Require().NoError(err)
@@ -2901,7 +2902,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, "", nil)
+                       err := cat.CreateView(context.Background(), 
test.identifier, nil, view.Representation{SQL: "", Dialect: "spark"}, nil)
                        assert.ErrorIs(t, err, catalog.ErrNoSuchView)
 
                        _, err = cat.CheckViewExists(context.Background(), 
test.identifier)
@@ -2932,7 +2933,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, 
"SELECT 1", nil))
+       s.Require().NoError(db.CreateView(ctx, 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)
@@ -2958,7 +2959,7 @@ func (s *SqliteCatalogTestSuite) 
TestCreateViewConflictsWithTable() {
        before, err := os.ReadDir(metadataDir)
        s.Require().NoError(err)
 
-       err = db.CreateView(ctx, identifier, tableSchemaNested, "SELECT 1", nil)
+       err = db.CreateView(ctx, identifier, tableSchemaNested, 
view.Representation{SQL: "SELECT 1", Dialect: "spark"}, nil)
        s.ErrorIs(err, catalog.ErrTableAlreadyExists)
 
        after, err := os.ReadDir(metadataDir)
@@ -2990,7 +2991,7 @@ func (s *SqliteCatalogTestSuite) 
TestConcurrentTableViewCollisionReturnsCatalogS
        }()
        go func() {
                <-start
-               err := db.CreateView(ctx, identifier, tableSchemaNested, 
"SELECT 1", nil)
+               err := db.CreateView(ctx, identifier, tableSchemaNested, 
view.Representation{SQL: "SELECT 1", Dialect: "spark"}, nil)
                results <- createResult{objectType: sqlcat.ViewType, err: err}
        }()
        close(start)
@@ -3061,7 +3062,7 @@ func (s *SqliteCatalogTestSuite) 
TestConcurrentTableViewCollisionUnderLockConten
        }()
        go func() {
                <-start
-               err := db.CreateView(ctx, identifier, tableSchemaNested, 
"SELECT 1", nil)
+               err := db.CreateView(ctx, identifier, tableSchemaNested, 
view.Representation{SQL: "SELECT 1", Dialect: "spark"}, nil)
                results <- createResult{objectType: sqlcat.ViewType, err: err}
        }()
        close(start)
@@ -3112,7 +3113,7 @@ func (s *SqliteCatalogTestSuite) 
TestCreateRemovesMetadataIfCatalogInsertFails()
                {
                        name: "view",
                        create: func(identifier table.Identifier) error {
-                               return db.CreateView(ctx, identifier, 
tableSchemaNested, "SELECT 1", nil)
+                               return db.CreateView(ctx, identifier, 
tableSchemaNested, view.Representation{SQL: "SELECT 1", Dialect: "spark"}, nil)
                        },
                },
        }
@@ -3143,7 +3144,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, viewSQL, nil))
+       s.Require().NoError(db.CreateView(context.Background(), 
[]string{nsName, viewName}, schema, view.Representation{SQL: viewSQL, Dialect: 
"spark"}, nil))
 
        exists, err := db.CheckViewExists(context.Background(), 
[]string{nsName, viewName})
        s.Require().NoError(err)
@@ -3202,7 +3203,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, viewSQL, nil))
+       s.Require().NoError(db.CreateView(context.Background(), 
[]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"
@@ -3238,7 +3239,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, viewSQL, nil))
+       s.Require().NoError(db.CreateView(context.Background(), 
[]string{nsName, viewName}, schema, view.Representation{SQL: viewSQL, Dialect: 
"spark"}, nil))
 
        exists, err = db.CheckViewExists(context.Background(), []string{nsName, 
viewName})
        s.Require().NoError(err)
@@ -3258,7 +3259,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, viewSQL, nil))
+               s.Require().NoError(db.CreateView(context.Background(), 
[]string{nsName, viewName}, schema, view.Representation{SQL: viewSQL, Dialect: 
"spark"}, nil))
        }
 
        var foundViews []table.Identifier
@@ -3312,7 +3313,7 @@ func (s *SqliteCatalogTestSuite) TestLoadView() {
                "comment": "Test view",
                "owner":   "test-user",
        }
-       s.Require().NoError(db.CreateView(context.Background(), 
[]string{nsName, viewName}, schema, viewSQL, props))
+       s.Require().NoError(db.CreateView(context.Background(), 
[]string{nsName, viewName}, schema, view.Representation{SQL: viewSQL, Dialect: 
"spark"}, props))
 
        viewInfo, err := db.LoadView(context.Background(), []string{nsName, 
viewName})
        s.Require().NoError(err)
diff --git a/view/metadata.go b/view/metadata.go
index 73c0ecbc0..bf84f14b4 100644
--- a/view/metadata.go
+++ b/view/metadata.go
@@ -109,12 +109,12 @@ type Representation struct {
 }
 type Representations []Representation
 
-func NewRepresentation(sql string, dialect string) Representation {
-       return Representation{
-               Type:    "sql",
-               SQL:     sql,
-               Dialect: dialect,
-       }
+// NewRepresentation creates a SQL view representation from named fields.
+// The SQL and Dialect fields are validated by NewVersion.
+func NewRepresentation(repr Representation) Representation {
+       repr.Type = "sql"
+
+       return repr
 }
 
 type Version struct {
@@ -222,10 +222,10 @@ func validateRepresentation(repr Representation) (string, 
error) {
        return strings.ToLower(dialect), nil
 }
 
-// NewVersionFromSQL creates a new Version with a single representation
-// using the provided SQL and dialect "default".
-func NewVersionFromSQL(id int64, schemaID int, sql string, defaultNS 
table.Identifier, opts ...VersionOpt) (*Version, error) {
-       return NewVersion(id, schemaID, Representations{NewRepresentation(sql, 
"default")}, defaultNS, opts...)
+// NewVersionFromSQL creates a new Version with one SQL representation.
+// SQL and dialect are passed as named fields to avoid swapping them.
+func NewVersionFromSQL(id int64, schemaID int, representation Representation, 
defaultNS table.Identifier, opts ...VersionOpt) (*Version, error) {
+       return NewVersion(id, schemaID, 
Representations{NewRepresentation(representation)}, defaultNS, opts...)
 }
 
 // Equals checks whether the other Version would behave the same
diff --git a/view/metadata_builder_test.go b/view/metadata_builder_test.go
index 58ce7e853..7e6191fa0 100644
--- a/view/metadata_builder_test.go
+++ b/view/metadata_builder_test.go
@@ -40,7 +40,7 @@ func newTestVersion(versionID int64, schemaID int, opts 
...VersionOpt) *Version
        version, _ := NewVersion(
                versionID,
                schemaID,
-               Representations{NewRepresentation("select * from table", 
"sql")},
+               Representations{NewRepresentation(Representation{SQL: "select * 
from table", Dialect: "sql"})},
                table.Identifier{"defaultns"},
                opts...)
 
@@ -51,7 +51,7 @@ func newTestVersionWithSQL(versionID int64, schemaID int, sql 
string, opts ...Ve
        version, _ := NewVersion(
                versionID,
                schemaID,
-               Representations{NewRepresentation(sql, "spark")},
+               Representations{NewRepresentation(Representation{SQL: sql, 
Dialect: "spark"})},
                table.Identifier{"defaultns"},
                opts...)
 
@@ -246,8 +246,8 @@ func TestNewVersion_RepresentationValidation(t *testing.T) {
 
 func TestNewVersionCopiesInputSlices(t *testing.T) {
        representations := Representations{
-               NewRepresentation("SELECT 1", "spark"),
-               NewRepresentation("SELECT 2", "trino"),
+               NewRepresentation(Representation{SQL: "SELECT 1", Dialect: 
"spark"}),
+               NewRepresentation(Representation{SQL: "SELECT 2", Dialect: 
"trino"}),
        }
        defaultNS := table.Identifier{"analytics", "reporting"}
 
@@ -262,8 +262,8 @@ func TestNewVersionCopiesInputSlices(t *testing.T) {
        defaultNS[1] = "schema"
 
        assert.Equal(t, []Representation{
-               NewRepresentation("SELECT 1", "spark"),
-               NewRepresentation("SELECT 2", "trino"),
+               NewRepresentation(Representation{SQL: "SELECT 1", Dialect: 
"spark"}),
+               NewRepresentation(Representation{SQL: "SELECT 2", Dialect: 
"trino"}),
        }, version.Representations)
        assert.Equal(t, table.Identifier{"analytics", "reporting"}, 
version.DefaultNamespace)
 
@@ -276,11 +276,18 @@ func TestNewVersionCopiesInputSlices(t *testing.T) {
 
 func TestNewVersionFromSQLCopiesNamespace(t *testing.T) {
        defaultNS := table.Identifier{"analytics"}
-       version, err := NewVersionFromSQL(1, 0, "SELECT 1", defaultNS)
+       version, err := NewVersionFromSQL(1, 0, Representation{SQL: "SELECT 1", 
Dialect: "spark"}, defaultNS)
        require.NoError(t, err)
 
        defaultNS[0] = "other"
        assert.Equal(t, table.Identifier{"analytics"}, version.DefaultNamespace)
+       assert.Equal(t, "spark", version.Representations[0].Dialect)
+}
+
+func TestNewVersionFromSQLRequiresDialect(t *testing.T) {
+       _, err := NewVersionFromSQL(1, 0, Representation{SQL: "SELECT 1", 
Dialect: "  "}, table.Identifier{"analytics"})
+       require.ErrorIs(t, err, ErrInvalidViewMetadata)
+       require.ErrorContains(t, err, "dialect is required")
 }
 
 func TestAddVersion_InvalidRepresentation_Validation(t *testing.T) {
@@ -741,8 +748,8 @@ func TestDroppingDialectFailsByDefault(t *testing.T) {
        schema := newTestSchema(0, "x")
        version, _ := NewVersion(1, 0,
                Representations{
-                       NewRepresentation("select \"nested\".\"field\" from 
tbl", "trino"),
-                       NewRepresentation("select `nested`.`field` from tbl", 
"spark"),
+                       NewRepresentation(Representation{SQL: "select 
\"nested\".\"field\" from tbl", Dialect: "trino"}),
+                       NewRepresentation(Representation{SQL: "select 
`nested`.`field` from tbl", Dialect: "spark"}),
                },
                table.Identifier{"defaultns"},
        )
@@ -766,8 +773,8 @@ func TestDroppingDialectDoesNotFailWhenAllowed(t 
*testing.T) {
        schema := newTestSchema(0, "x")
        version, _ := NewVersion(1, 0,
                Representations{
-                       NewRepresentation("select \"nested\".\"field\" from 
tbl", "trino"),
-                       NewRepresentation("select `nested`.`field` from tbl", 
"spark"),
+                       NewRepresentation(Representation{SQL: "select 
\"nested\".\"field\" from tbl", Dialect: "trino"}),
+                       NewRepresentation(Representation{SQL: "select 
`nested`.`field` from tbl", Dialect: "spark"}),
                },
                table.Identifier{"defaultns"},
        )
@@ -809,7 +816,7 @@ func TestUnknownRepresentationSurvivesBuilderRoundTrip(t 
*testing.T) {
        nextVersion, err := NewVersion(
                2,
                metadata.CurrentSchemaID(),
-               Representations{NewRepresentation("SELECT 2", "spark")},
+               Representations{NewRepresentation(Representation{SQL: "SELECT 
2", Dialect: "spark"})},
                metadata.CurrentVersion().DefaultNamespace,
        )
        require.NoError(t, err)
diff --git a/view/metadata_test.go b/view/metadata_test.go
index 9d63a86c4..957cb7d28 100644
--- a/view/metadata_test.go
+++ b/view/metadata_test.go
@@ -69,9 +69,16 @@ func TestVersionEquals(t *testing.T) {
        assert.True(t, v1.Equals(v2), fmt.Sprintf("Expected the same SchemaID, 
Summary, Representation for %v, got %v", v1, v2))
 }
 
+func TestNewRepresentationSetsTypeWithoutMutatingInput(t *testing.T) {
+       input := Representation{SQL: "SELECT 1", Dialect: "spark"}
+       repr := NewRepresentation(input)
+       require.Equal(t, Representation{Type: "sql", SQL: "SELECT 1", Dialect: 
"spark"}, repr)
+       require.Empty(t, input.Type)
+}
+
 // TestRepresentationSQLJSON keeps the on-disk SQL key unchanged after the Go 
API rename.
 func TestRepresentationSQLJSON(t *testing.T) {
-       repr := NewRepresentation("SELECT 1", "spark")
+       repr := NewRepresentation(Representation{SQL: "SELECT 1", Dialect: 
"spark"})
        require.Equal(t, "SELECT 1", repr.SQL)
 
        data, err := json.Marshal(repr)
@@ -155,7 +162,7 @@ func TestUnmarshalViewMetadata(t *testing.T) {
        assert.Equal(t, 0, md.CurrentVersion().SchemaID)
        assert.Equal(t, VersionSummary{"summaryProp": "summaryVal"}, 
md.CurrentVersion().Summary)
        assert.Equal(t, []VersionLogEntry{{TimestampMS: 1000, VersionID: 1}}, 
md.VersionLog())
-       assert.Equal(t, []Representation{NewRepresentation("select * from 
ns.tbl", "trino")}, md.CurrentVersion().Representations)
+       assert.Equal(t, []Representation{NewRepresentation(Representation{SQL: 
"select * from ns.tbl", Dialect: "trino"})}, 
md.CurrentVersion().Representations)
        assert.Equal(t, iceberg.Properties{"prop": "value"}, md.Properties())
 }
 
diff --git a/view/view.go b/view/view.go
index 4db9dde22..fcf314779 100644
--- a/view/view.go
+++ b/view/view.go
@@ -137,33 +137,35 @@ func NewFromLocation(
 //
 // Note: This function only supports creating new views with format version 1.
 // It does not support updating existing view metadata.
+// representation holds both SQL text and its dialect.
 func CreateView(
        ctx context.Context,
        catalogName string,
        viewIdent table.Identifier,
        schema *iceberg.Schema,
-       viewSQL string,
+       representation Representation,
        defaultNS table.Identifier,
        loc string,
        props iceberg.Properties,
 ) (*View, error) {
-       return createView(ctx, catalogName, viewIdent, schema, viewSQL, 
defaultNS, loc, props, props)
+       return createView(ctx, catalogName, viewIdent, schema, representation, 
defaultNS, loc, props, props)
 }
 
 // CreateViewWithIOProperties creates a view using ioProps to configure the
 // filesystem and metadataProps as the properties persisted in view metadata.
+// representation holds both SQL text and its dialect.
 func CreateViewWithIOProperties(
        ctx context.Context,
        catalogName string,
        viewIdent table.Identifier,
        schema *iceberg.Schema,
-       viewSQL string,
+       representation Representation,
        defaultNS table.Identifier,
        loc string,
        ioProps iceberg.Properties,
        metadataProps iceberg.Properties,
 ) (*View, error) {
-       return createView(ctx, catalogName, viewIdent, schema, viewSQL, 
defaultNS, loc, ioProps, metadataProps)
+       return createView(ctx, catalogName, viewIdent, schema, representation, 
defaultNS, loc, ioProps, metadataProps)
 }
 
 func createView(
@@ -171,7 +173,7 @@ func createView(
        catalogName string,
        viewIdent table.Identifier,
        schema *iceberg.Schema,
-       viewSQL string,
+       representation Representation,
        defaultNS table.Identifier,
        loc string,
        ioProps iceberg.Properties,
@@ -184,7 +186,7 @@ func createView(
        viewVersion, err := NewVersionFromSQL(
                versionId,
                schema.ID,
-               viewSQL,
+               representation,
                defaultNS,
                WithDefaultViewCatalog(catalogName),
        )
diff --git a/view/view_test.go b/view/view_test.go
index e1329f27b..fbfad8fd7 100644
--- a/view/view_test.go
+++ b/view/view_test.go
@@ -94,7 +94,7 @@ func (t *ViewTestSuite) 
TestCreateViewJoinsTrailingSlashMetadataLocation() {
                "test-catalog",
                []string{"ns", "test_view"},
                newTestSchema(0),
-               "select 1",
+               Representation{SQL: "select 1", Dialect: "spark"},
                []string{"ns"},
                "mem://view-create-location/test-view/",
                nil,
@@ -137,7 +137,7 @@ func TestCreateViewReturnsMetadataCloseError(t *testing.T) {
                "test-catalog",
                []string{"ns", "test_view"},
                newTestSchema(0),
-               "select 1",
+               Representation{SQL: "select 1", Dialect: "spark"},
                []string{"ns"},
                scheme+"://bucket/test-view",
                nil,
diff --git a/website/src/api.md b/website/src/api.md
index b52509255..03edf9bef 100644
--- a/website/src/api.md
+++ b/website/src/api.md
@@ -557,7 +557,7 @@ v, err := view.CreateView(
     "my-catalog",
     table.Identifier{"analytics", "monthly_orders"},
     schema,
-    "SELECT month, sum(amount) FROM orders GROUP BY month",
+    view.Representation{SQL: "SELECT month, sum(amount) FROM orders GROUP BY 
month", Dialect: "spark"},
     table.Identifier{"sales"},                      // default namespace for 
unqualified names
     "s3://my-bucket/views/monthly_orders",
     iceberg.Properties{},
diff --git a/website/src/releases.md b/website/src/releases.md
index 2af23acce..b24209296 100644
--- a/website/src/releases.md
+++ b/website/src/releases.md
@@ -42,6 +42,10 @@ Per-release notes (highlights, breaking changes, 
contributors) are published on
 
 ### Unreleased compatibility notes
 
+- **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 SQL representation construction uses named fields.** Replace 
`view.NewRepresentation(sql, dialect)` with 
`view.NewRepresentation(view.Representation{SQL: sql, Dialect: dialect})`. 
Serialized JSON is unchanged.
+
 - **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.
 - **Fixed and decimal constructors return errors.** `iceberg.FixedTypeOf` and 
`iceberg.DecimalTypeOf` now return `(type, error)` instead of panicking for 
invalid inputs. Use `MustFixedTypeOf` or `MustDecimalTypeOf` only for 
known-valid values.
 - **SQL UDF representations now use struct literals.** The 
`udf.NewSQLRepresentation(dialect, sql)` function, available since v0.7.0, is 
removed. Use `udf.SQLRepresentation{Dialect: "trino", SQL: "x + 1"}` instead. 
Empty fields and duplicate dialects are checked during metadata validation; 
serialized JSON is unchanged.

Reply via email to