This is an automated email from the ASF dual-hosted git repository.

laskoviymishka pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-go.git


The following commit(s) were added to refs/heads/main by this push:
     new 861b4cad0 refactor(catalog): move namespace-property helper into 
catalog/internal, drop go:linkname (#2066)
861b4cad0 is described below

commit 861b4cad0d46c9011f81f08648c14704150e657d
Author: Neelesh Salian <[email protected]>
AuthorDate: Mon Sep 28 15:08:14 2026 -0700

    refactor(catalog): move namespace-property helper into catalog/internal, 
drop go:linkname (#2066)
    
    * refactor(catalog): move namespace-property helper into catalog/internal, 
drop go:linkname
    
    * PR fixes
---
 catalog/catalog.go             |  51 ---------------
 catalog/glue/glue.go           |   9 +--
 catalog/hive/hive.go           |   6 +-
 catalog/internal/utils.go      |  58 +++++++++++++++--
 catalog/internal/utils_test.go | 137 +++++++++++++++++++++++++++++++++++++++++
 catalog/sql/sql.go             |   9 +--
 6 files changed, 194 insertions(+), 76 deletions(-)

diff --git a/catalog/catalog.go b/catalog/catalog.go
index b7348c304..a075b6cd3 100644
--- a/catalog/catalog.go
+++ b/catalog/catalog.go
@@ -32,12 +32,10 @@ import (
        "errors"
        "fmt"
        "iter"
-       "maps"
        "strings"
        "unicode"
 
        "github.com/apache/iceberg-go"
-       iceinternal "github.com/apache/iceberg-go/internal"
        "github.com/apache/iceberg-go/table"
 )
 
@@ -357,52 +355,3 @@ func WithViewProperties(config iceberg.Properties) 
CreateViewOpt {
                cfg.Properties = config
        }
 }
-
-//lint:ignore U1000 this is linked to by catalogs via go:linkname but we don't 
want to export it
-func checkForOverlap(removals []string, updates iceberg.Properties) error {
-       overlap := []string{}
-       for _, key := range removals {
-               if _, ok := updates[key]; ok {
-                       overlap = append(overlap, key)
-               }
-       }
-       if len(overlap) > 0 {
-               return fmt.Errorf("conflict between removals and updates for 
keys: %v", overlap)
-       }
-
-       return nil
-}
-
-//lint:ignore U1000 this is linked to by catalogs via go:linkname but we don't 
want to export it
-func getUpdatedPropsAndUpdateSummary(currentProps iceberg.Properties, removals 
[]string, updates iceberg.Properties) (iceberg.Properties, 
PropertiesUpdateSummary, error) {
-       if err := checkForOverlap(removals, updates); err != nil {
-               return nil, PropertiesUpdateSummary{}, err
-       }
-       var (
-               updatedProps = maps.Clone(currentProps)
-               removed      = make([]string, 0, len(removals))
-               updated      = make([]string, 0, len(updates))
-       )
-
-       for _, key := range removals {
-               if _, exists := updatedProps[key]; exists {
-                       delete(updatedProps, key)
-                       removed = append(removed, key)
-               }
-       }
-
-       for key, value := range updates {
-               if updatedProps[key] != value {
-                       updated = append(updated, key)
-                       updatedProps[key] = value
-               }
-       }
-
-       summary := PropertiesUpdateSummary{
-               Removed: removed,
-               Updated: updated,
-               Missing: iceinternal.Difference(removals, removed),
-       }
-
-       return updatedProps, summary, nil
-}
diff --git a/catalog/glue/glue.go b/catalog/glue/glue.go
index c44aa1a7f..d33abd274 100644
--- a/catalog/glue/glue.go
+++ b/catalog/glue/glue.go
@@ -27,7 +27,6 @@ import (
        "strconv"
        "strings"
        "time"
-       _ "unsafe"
 
        "github.com/apache/iceberg-go"
        "github.com/apache/iceberg-go/catalog"
@@ -766,12 +765,6 @@ func (c *Catalog) LoadNamespaceProperties(ctx 
context.Context, namespace table.I
        return props, nil
 }
 
-// avoid circular dependency while still avoiding having to export the 
getUpdatedPropsAndUpdateSummary function
-// so that we can re-use it in the catalog implementations without duplicating 
the code.
-
-//go:linkname getUpdatedPropsAndUpdateSummary 
github.com/apache/iceberg-go/catalog.getUpdatedPropsAndUpdateSummary
-func getUpdatedPropsAndUpdateSummary(currentProps iceberg.Properties, removals 
[]string, updates iceberg.Properties) (iceberg.Properties, 
catalog.PropertiesUpdateSummary, error)
-
 // UpdateNamespaceProperties updates the properties of an Iceberg namespace in 
the Glue catalog.
 // The removals list contains the keys to remove, and the updates map contains 
the keys and values to update.
 func (c *Catalog) UpdateNamespaceProperties(ctx context.Context, namespace 
table.Identifier,
@@ -782,7 +775,7 @@ func (c *Catalog) UpdateNamespaceProperties(ctx 
context.Context, namespace table
                return catalog.PropertiesUpdateSummary{}, err
        }
 
-       updatedProperties, propertiesUpdateSummary, err := 
getUpdatedPropsAndUpdateSummary(currentProps, removals, updates)
+       updatedProperties, propertiesUpdateSummary, err := 
internal.GetUpdatedPropsAndUpdateSummary(currentProps, removals, updates)
        if err != nil {
                return catalog.PropertiesUpdateSummary{}, err
        }
diff --git a/catalog/hive/hive.go b/catalog/hive/hive.go
index 604d5b1d5..af4e206fb 100644
--- a/catalog/hive/hive.go
+++ b/catalog/hive/hive.go
@@ -25,7 +25,6 @@ import (
        "log"
        "maps"
        "strings"
-       _ "unsafe"
 
        "github.com/apache/iceberg-go"
        "github.com/apache/iceberg-go/catalog"
@@ -934,9 +933,6 @@ func (c *Catalog) LoadNamespaceProperties(ctx 
context.Context, namespace table.I
        return props, nil
 }
 
-//go:linkname getUpdatedPropsAndUpdateSummary 
github.com/apache/iceberg-go/catalog.getUpdatedPropsAndUpdateSummary
-func getUpdatedPropsAndUpdateSummary(currentProps iceberg.Properties, removals 
[]string, updates iceberg.Properties) (iceberg.Properties, 
catalog.PropertiesUpdateSummary, error)
-
 // UpdateNamespaceProperties updates the properties for a namespace.
 func (c *Catalog) UpdateNamespaceProperties(ctx context.Context, namespace 
table.Identifier,
        removals []string, updates iceberg.Properties,
@@ -946,7 +942,7 @@ func (c *Catalog) UpdateNamespaceProperties(ctx 
context.Context, namespace table
                return catalog.PropertiesUpdateSummary{}, err
        }
 
-       updatedProperties, propertiesUpdateSummary, err := 
getUpdatedPropsAndUpdateSummary(currentProps, removals, updates)
+       updatedProperties, propertiesUpdateSummary, err := 
internal.GetUpdatedPropsAndUpdateSummary(currentProps, removals, updates)
        if err != nil {
                return catalog.PropertiesUpdateSummary{}, err
        }
diff --git a/catalog/internal/utils.go b/catalog/internal/utils.go
index fbb088e83..203ac680d 100644
--- a/catalog/internal/utils.go
+++ b/catalog/internal/utils.go
@@ -33,7 +33,7 @@ import (
 
        "github.com/apache/iceberg-go"
        "github.com/apache/iceberg-go/catalog"
-       "github.com/apache/iceberg-go/internal"
+       iceinternal "github.com/apache/iceberg-go/internal"
        icebergio "github.com/apache/iceberg-go/io"
        "github.com/apache/iceberg-go/table"
        "github.com/google/uuid"
@@ -52,21 +52,21 @@ func WriteTableMetadata(metadata table.Metadata, fs 
icebergio.WriteFileIO, loc s
        if err != nil {
                return err
        }
-       defer internal.CheckedClose(out, &err)
+       defer iceinternal.CheckedClose(out, &err)
 
        var writer io.Writer = out
        switch compression {
        case table.MetadataCompressionCodecGzip:
                gzw := gzip.NewWriter(out)
                writer = gzw
-               defer internal.CheckedClose(gzw, &err)
+               defer iceinternal.CheckedClose(gzw, &err)
        case table.MetadataCompressionCodecZstd:
                enc, zErr := zstd.NewWriter(out)
                if zErr != nil {
                        return zErr
                }
                writer = enc
-               defer internal.CheckedClose(enc, &err)
+               defer iceinternal.CheckedClose(enc, &err)
        }
 
        err = json.NewEncoder(writer).Encode(metadata)
@@ -324,3 +324,53 @@ func UpdateAndStageTable(ctx context.Context, catprops 
iceberg.Properties, curre
                ),
        }, nil
 }
+
+func checkForOverlap(removals []string, updates iceberg.Properties) error {
+       overlap := []string{}
+       for _, key := range removals {
+               if _, ok := updates[key]; ok {
+                       overlap = append(overlap, key)
+               }
+       }
+       if len(overlap) > 0 {
+               return fmt.Errorf("conflict between removals and updates for 
keys: %v", overlap)
+       }
+
+       return nil
+}
+
+// GetUpdatedPropsAndUpdateSummary applies removals and updates to currentProps
+// and returns the updated properties alongside a summary of the changes. It is
+// shared by the catalog backend implementations.
+func GetUpdatedPropsAndUpdateSummary(currentProps iceberg.Properties, removals 
[]string, updates iceberg.Properties) (iceberg.Properties, 
catalog.PropertiesUpdateSummary, error) {
+       if err := checkForOverlap(removals, updates); err != nil {
+               return nil, catalog.PropertiesUpdateSummary{}, err
+       }
+       var (
+               updatedProps = maps.Clone(currentProps)
+               removed      = make([]string, 0, len(removals))
+               updated      = make([]string, 0, len(updates))
+       )
+
+       for _, key := range removals {
+               if _, exists := updatedProps[key]; exists {
+                       delete(updatedProps, key)
+                       removed = append(removed, key)
+               }
+       }
+
+       for key, value := range updates {
+               if updatedProps[key] != value {
+                       updated = append(updated, key)
+                       updatedProps[key] = value
+               }
+       }
+
+       summary := catalog.PropertiesUpdateSummary{
+               Removed: removed,
+               Updated: updated,
+               Missing: iceinternal.Difference(removals, removed),
+       }
+
+       return updatedProps, summary, nil
+}
diff --git a/catalog/internal/utils_test.go b/catalog/internal/utils_test.go
new file mode 100644
index 000000000..319e50236
--- /dev/null
+++ b/catalog/internal/utils_test.go
@@ -0,0 +1,137 @@
+// 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 internal
+
+import (
+       "maps"
+       "testing"
+
+       "github.com/apache/iceberg-go"
+       "github.com/apache/iceberg-go/catalog"
+       "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
+)
+
+func TestGetUpdatedPropsAndUpdateSummary(t *testing.T) {
+       tests := []struct {
+               name            string
+               current         iceberg.Properties
+               removals        []string
+               updates         iceberg.Properties
+               wantErrContains string
+               wantProps       iceberg.Properties
+               wantRemoved     []string
+               wantUpdated     []string
+               wantMissing     []string
+       }{
+               {
+                       name:            "removal and update of the same key 
conflicts",
+                       current:         iceberg.Properties{"a": "1"},
+                       removals:        []string{"a"},
+                       updates:         iceberg.Properties{"a": "2"},
+                       wantErrContains: "conflict between removals and updates 
for keys: [a]",
+               },
+               {
+                       name:        "existing key is removed",
+                       current:     iceberg.Properties{"a": "1", "b": "2"},
+                       removals:    []string{"a"},
+                       wantProps:   iceberg.Properties{"b": "2"},
+                       wantRemoved: []string{"a"},
+                       wantUpdated: []string{},
+                       wantMissing: []string{},
+               },
+               {
+                       name:        "removal of absent key is reported as 
missing",
+                       current:     iceberg.Properties{"a": "1"},
+                       removals:    []string{"ghost"},
+                       wantProps:   iceberg.Properties{"a": "1"},
+                       wantRemoved: []string{},
+                       wantUpdated: []string{},
+                       wantMissing: []string{"ghost"},
+               },
+               {
+                       name:        "existing key gets a new value",
+                       current:     iceberg.Properties{"a": "old"},
+                       updates:     iceberg.Properties{"a": "new"},
+                       wantProps:   iceberg.Properties{"a": "new"},
+                       wantRemoved: []string{},
+                       wantUpdated: []string{"a"},
+                       wantMissing: []string{},
+               },
+               {
+                       name:        "new key is added",
+                       current:     iceberg.Properties{"a": "1"},
+                       updates:     iceberg.Properties{"b": "2"},
+                       wantProps:   iceberg.Properties{"a": "1", "b": "2"},
+                       wantRemoved: []string{},
+                       wantUpdated: []string{"b"},
+                       wantMissing: []string{},
+               },
+               {
+                       name:        "update with unchanged value is not 
reported",
+                       current:     iceberg.Properties{"a": "1"},
+                       updates:     iceberg.Properties{"a": "1"},
+                       wantProps:   iceberg.Properties{"a": "1"},
+                       wantRemoved: []string{},
+                       wantUpdated: []string{},
+                       wantMissing: []string{},
+               },
+               {
+                       name:        "no removals or updates is a no-op",
+                       current:     iceberg.Properties{"a": "1"},
+                       wantProps:   iceberg.Properties{"a": "1"},
+                       wantRemoved: []string{},
+                       wantUpdated: []string{},
+                       wantMissing: []string{},
+               },
+               {
+                       name:        "add, update, remove and missing in one 
call",
+                       current:     iceberg.Properties{"keep": "v", "drop": 
"v", "upd": "old"},
+                       removals:    []string{"drop", "ghost"},
+                       updates:     iceberg.Properties{"upd": "new", "add": 
"v", "keep": "v"},
+                       wantProps:   iceberg.Properties{"keep": "v", "upd": 
"new", "add": "v"},
+                       wantRemoved: []string{"drop"},
+                       wantUpdated: []string{"upd", "add"},
+                       wantMissing: []string{"ghost"},
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       before := maps.Clone(tt.current)
+
+                       props, summary, err := 
GetUpdatedPropsAndUpdateSummary(tt.current, tt.removals, tt.updates)
+
+                       assert.Equal(t, before, tt.current, "currentProps must 
not be mutated")
+
+                       if tt.wantErrContains != "" {
+                               require.ErrorContains(t, err, 
tt.wantErrContains)
+                               assert.Nil(t, props)
+                               assert.Equal(t, 
catalog.PropertiesUpdateSummary{}, summary)
+
+                               return
+                       }
+
+                       require.NoError(t, err)
+                       assert.Equal(t, tt.wantProps, props)
+                       assert.ElementsMatch(t, tt.wantRemoved, summary.Removed)
+                       assert.ElementsMatch(t, tt.wantUpdated, summary.Updated)
+                       assert.ElementsMatch(t, tt.wantMissing, summary.Missing)
+               })
+       }
+}
diff --git a/catalog/sql/sql.go b/catalog/sql/sql.go
index 39c2d20c4..61370d1c6 100644
--- a/catalog/sql/sql.go
+++ b/catalog/sql/sql.go
@@ -30,7 +30,6 @@ import (
        "strings"
        "sync"
        "time"
-       _ "unsafe"
 
        "github.com/apache/iceberg-go"
        "github.com/apache/iceberg-go/catalog"
@@ -1518,12 +1517,6 @@ func (c *Catalog) ListNamespaces(ctx context.Context, 
parent table.Identifier) (
        return ret, nil
 }
 
-// avoid circular dependency while still avoiding having to export the 
getUpdatedPropsAndUpdateSummary function
-// so that we can re-use it in the catalog implementations without duplicating 
the code.
-
-//go:linkname getUpdatedPropsAndUpdateSummary 
github.com/apache/iceberg-go/catalog.getUpdatedPropsAndUpdateSummary
-func getUpdatedPropsAndUpdateSummary(currentProps iceberg.Properties, removals 
[]string, updates iceberg.Properties) (iceberg.Properties, 
catalog.PropertiesUpdateSummary, error)
-
 func (c *Catalog) UpdateNamespaceProperties(ctx context.Context, namespace 
table.Identifier, removals []string, updates iceberg.Properties) 
(catalog.PropertiesUpdateSummary, error) {
        var summary catalog.PropertiesUpdateSummary
        if err := checkValidNamespace(namespace); err != nil {
@@ -1555,7 +1548,7 @@ func (c *Catalog) UpdateNamespaceProperties(ctx 
context.Context, namespace table
                        currentProps[prop.PropertyKey] = 
prop.PropertyValue.String
                }
 
-               _, nextSummary, err := 
getUpdatedPropsAndUpdateSummary(currentProps, removals, updates)
+               _, nextSummary, err := 
internal.GetUpdatedPropsAndUpdateSummary(currentProps, removals, updates)
                if err != nil {
                        return err
                }

Reply via email to