You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by at...@apache.org on 2026/07/07 09:53:02 UTC
(iceberg-go) branch main updated: fix(table): clone selected fields in scan options (#1390)
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 058bc238 fix(table): clone selected fields in scan options (#1390)
058bc238 is described below
commit 058bc2388d161ed4aa3c8848989c337c659153ec
Author: Minh Vu <vu...@gmail.com>
AuthorDate: Tue Jul 7 11:52:56 2026 +0200
fix(table): clone selected fields in scan options (#1390)
## Summary
- fix(table): clone selected fields inside WithSelectedFields
- add regression test that mutating caller slice after option creation
does not affect scan.selectedFields
## Testing
- go test ./table -run TestWithSelectedFields -count=1
- go test ./table -count=1
---
cmd/iceberg/output.go | 16 ++++++--
cmd/iceberg/output_test.go | 99 ++++++++++++++++++++++++++++++++++++++++++++++
table/scan_options_test.go | 37 +++++++++++++++++
table/table.go | 2 +-
4 files changed, 149 insertions(+), 5 deletions(-)
diff --git a/cmd/iceberg/output.go b/cmd/iceberg/output.go
index ef3394dd..8f20fd37 100644
--- a/cmd/iceberg/output.go
+++ b/cmd/iceberg/output.go
@@ -59,6 +59,14 @@ type Output interface {
type textOutput struct{}
+func snapshotSchemaText(schemaID *int) string {
+ if schemaID == nil {
+ return "unknown"
+ }
+
+ return strconv.Itoa(*schemaID)
+}
+
func (textOutput) Identifiers(idlist []table.Identifier) {
data := pterm.TableData{[]string{"IDs"}}
for _, ids := range idlist {
@@ -90,8 +98,8 @@ func (t textOutput) DescribeTable(tbl *table.Table) {
}
snapshotList = append(snapshotList, pterm.LeveledListItem{
- Level: 0, Text: fmt.Sprintf("Snapshot %d, schema %d%s",
- s.SnapshotID, *s.SchemaID, manifest),
+ Level: 0, Text: fmt.Sprintf("Snapshot %d, schema %s%s",
+ s.SnapshotID, snapshotSchemaText(s.SchemaID), manifest),
})
}
@@ -142,8 +150,8 @@ func (t textOutput) Files(tbl *table.Table, history bool) {
snapshotTree = append(snapshotTree, pterm.LeveledListItem{
Level: 0,
- Text: fmt.Sprintf("Snapshot %d, schema %d%s",
- snap.SnapshotID, *snap.SchemaID, manifest),
+ Text: fmt.Sprintf("Snapshot %d, schema %s%s",
+ snap.SnapshotID, snapshotSchemaText(snap.SchemaID), manifest),
})
afs, err := tbl.FS(context.TODO())
diff --git a/cmd/iceberg/output_test.go b/cmd/iceberg/output_test.go
index 3cb20196..4d149b90 100644
--- a/cmd/iceberg/output_test.go
+++ b/cmd/iceberg/output_test.go
@@ -19,9 +19,12 @@ package main
import (
"bytes"
+ "context"
"os"
+ "strings"
"testing"
+ iceio "github.com/apache/iceberg-go/io"
"github.com/apache/iceberg-go/table"
"github.com/pterm/pterm"
"github.com/stretchr/testify/assert"
@@ -407,3 +410,99 @@ func Test_jsonOutput_DescribeTable(t *testing.T) {
})
}
}
+
+func Test_textOutput_DescribeTableSnapshotWithoutSchemaID(t *testing.T) {
+ meta := `{
+ "format-version": 2,
+ "table-uuid": "9c12d441-03fe-4693-9a96-a0705ddf69c1",
+ "location": "mem://default/table",
+ "last-sequence-number": 1,
+ "last-updated-ms": 1602638573590,
+ "last-column-id": 1,
+ "current-schema-id": 0,
+ "schemas": [
+ {"type": "struct", "schema-id": 0, "fields": [{"id": 1, "name": "id", "required": true, "type": "int"}]}
+ ],
+ "default-spec-id": 0,
+ "partition-specs": [{"spec-id": 0, "fields": []}],
+ "last-partition-id": 1000,
+ "default-sort-order-id": 0,
+ "sort-orders": [
+ {"order-id": 0, "fields": []}
+ ],
+ "properties": {},
+ "current-snapshot-id": 100,
+ "snapshots": [
+ {
+ "snapshot-id": 100,
+ "timestamp-ms": 1602638573590,
+ "sequence-number": 1,
+ "summary": {"operation": "append"}
+ }
+ ],
+ "snapshot-log": [],
+ "refs": {}
+}`
+
+ var buf bytes.Buffer
+ pterm.SetDefaultOutput(&buf)
+ pterm.DisableColor()
+
+ parsed, err := table.ParseMetadataBytes([]byte(meta))
+ assert.NoError(t, err)
+ tbl := table.New([]string{"t"}, parsed, "", nil, nil)
+
+ textOutput{}.DescribeTable(tbl)
+
+ assert.True(t, strings.Contains(buf.String(), "Snapshot 100, schema unknown"))
+}
+
+func Test_textOutput_FilesSnapshotWithoutSchemaID(t *testing.T) {
+ meta := `{
+ "format-version": 2,
+ "table-uuid": "c8e7f6d4-03fe-4693-9a96-a0705ddf69c2",
+ "location": "mem://default/table",
+ "last-sequence-number": 1,
+ "last-updated-ms": 1602638573590,
+ "last-column-id": 1,
+ "current-schema-id": 0,
+ "schemas": [
+ {"type": "struct", "schema-id": 0, "fields": [{"id": 1, "name": "id", "required": true, "type": "int"}]}
+ ],
+ "default-spec-id": 0,
+ "partition-specs": [{"spec-id": 0, "fields": []}],
+ "last-partition-id": 1000,
+ "default-sort-order-id": 0,
+ "sort-orders": [
+ {"order-id": 0, "fields": []}
+ ],
+ "properties": {},
+ "current-snapshot-id": 100,
+ "snapshots": [
+ {
+ "snapshot-id": 100,
+ "timestamp-ms": 1602638573590,
+ "sequence-number": 1,
+ "summary": {"operation": "append"}
+ }
+ ],
+ "snapshot-log": [],
+ "refs": {}
+}`
+
+ var buf bytes.Buffer
+ pterm.SetDefaultOutput(&buf)
+ pterm.DisableColor()
+
+ memFS, err := iceio.LoadFS(context.Background(), nil, "mem://default/table")
+ assert.NoError(t, err)
+ parsed, err := table.ParseMetadataBytes([]byte(meta))
+ assert.NoError(t, err)
+ tbl := table.New([]string{"db", "events"}, parsed, "", func(_ context.Context) (iceio.IO, error) {
+ return memFS, nil
+ }, nil)
+
+ textOutput{}.Files(tbl, false)
+
+ assert.True(t, strings.Contains(buf.String(), "Snapshot 100, schema unknown"))
+}
diff --git a/table/scan_options_test.go b/table/scan_options_test.go
new file mode 100644
index 00000000..ac42d8a8
--- /dev/null
+++ b/table/scan_options_test.go
@@ -0,0 +1,37 @@
+// 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 table
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestWithSelectedFieldsDoesNotAliasCallerSlice(t *testing.T) {
+ fields := []string{"id", "name"}
+
+ option := WithSelectedFields(fields...)
+ scan := &Scan{}
+ option(scan)
+
+ fields[0] = "updated_id"
+
+ assert.Equal(t, []string{"id", "name"}, scan.selectedFields)
+ assert.NotEqual(t, fields[0], scan.selectedFields[0])
+}
diff --git a/table/table.go b/table/table.go
index ac554bfb..b443fe79 100644
--- a/table/table.go
+++ b/table/table.go
@@ -769,7 +769,7 @@ func WithSelectedFields(fields ...string) ScanOption {
}
return func(scan *Scan) {
- scan.selectedFields = fields
+ scan.selectedFields = slices.Clone(fields)
}
}