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/06/17 22:54:33 UTC
(iceberg-go) branch main updated: fix(transforms): truncate strings by Unicode code points instead of UTF-8 bytes (#1223)
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 ae7b4667 fix(transforms): truncate strings by Unicode code points instead of UTF-8 bytes (#1223)
ae7b4667 is described below
commit ae7b466771d75cf7c60fee6998e2af1999783e10
Author: Badal Prasad Singh <ba...@datazip.io>
AuthorDate: Thu Jun 18 04:24:28 2026 +0530
fix(transforms): truncate strings by Unicode code points instead of UTF-8 bytes (#1223)
## Problem
Iceberg-Go's `iceberg.TruncateTransform.Transformer` for `StringType`
slices the input by raw UTF-8 byte length instead of Unicode code
points. For any non-ASCII input — CJK, emoji, accented Latin — this
produces output that may not be valid UTF-8.
Following the [**Iceberg
spec**](https://iceberg.apache.org/spec/#truncate-transform-details)
defining string truncate as truncation to *N Unicode characters*.
Fixes #1222
## Solution
Iterate the input by Unicode code points using `unicode/utf8`. Keep
binary truncation byte-based.
## Files changed
| File | Description |
|---|---|
| `transforms.go` | Bug Fix |
| `transforms_test.go` | Regression Test |
## Testing
- Ingested data into a partitioned iceberg table in Glue Catalog
- Queried through Trino
---------
Signed-off-by: badalprasadsingh <ba...@datazip.io>
---
transforms.go | 9 +++++++-
transforms_test.go | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 69 insertions(+), 1 deletion(-)
diff --git a/transforms.go b/transforms.go
index a274f572..85293966 100644
--- a/transforms.go
+++ b/transforms.go
@@ -27,6 +27,7 @@ import (
"strconv"
"strings"
"time"
+ "unicode/utf8"
"unsafe"
"github.com/apache/arrow-go/v18/arrow/decimal128"
@@ -447,7 +448,13 @@ func (t TruncateTransform) Transformer(src Type) (func(any) any, error) {
return func(v any) any {
switch v := v.(type) {
case string:
- return v[:min(len(v), t.Width)]
+ byteOff := 0
+ for cp := 0; cp < t.Width && byteOff < len(v); cp++ {
+ _, size := utf8.DecodeRuneInString(v[byteOff:])
+ byteOff += size
+ }
+
+ return v[:byteOff]
case []byte:
return v[:min(len(v), t.Width)]
default:
diff --git a/transforms_test.go b/transforms_test.go
index 223ba96d..22e8a2c2 100644
--- a/transforms_test.go
+++ b/transforms_test.go
@@ -24,6 +24,7 @@ import (
"strings"
"testing"
"time"
+ "unicode/utf8"
"github.com/apache/arrow-go/v18/arrow/decimal"
"github.com/apache/arrow-go/v18/arrow/decimal128"
@@ -716,3 +717,63 @@ func TestTruncateTransform(t *testing.T) {
})
}
}
+
+func TestTruncateTransformStringUnicode(t *testing.T) {
+ tests := []struct {
+ name string
+ width int
+ input string
+ expected string
+ }{
+ {
+ name: "truncate_unicode_string_width_2",
+ width: 2,
+ input: "イロハニホヘト",
+ expected: "イロ",
+ },
+ {
+ name: "truncate_unicode_string_width_3",
+ width: 3,
+ input: "イロハニホヘト",
+ expected: "イロハ",
+ },
+ {
+ name: "truncate_multibyte_string_width_1",
+ width: 1,
+ input: "测试",
+ expected: "测",
+ },
+ {
+ name: "truncate_mixed_unicode_and_ascii",
+ width: 4,
+ input: "测试raul试测",
+ expected: "测试ra",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ transform := iceberg.TruncateTransform{Width: tt.width}
+
+ fn, err := transform.Transformer(iceberg.PrimitiveTypes.String)
+ require.NoError(t, err)
+
+ gotAny := fn(tt.input)
+ got, ok := gotAny.(string)
+ require.True(t, ok, "Transformer must return string for StringType")
+ assert.Equal(t, tt.expected, got, "Transformer truncated value must match Java's code-point semantics")
+ assert.True(t, utf8.ValidString(got),
+ "Transformer output must be valid UTF-8; got bytes %x for input %q",
+ got, tt.input)
+
+ result := transform.Apply(iceberg.Optional[iceberg.Literal]{
+ Val: iceberg.StringLiteral(tt.input), Valid: true,
+ })
+ require.True(t, result.Valid)
+ assert.Equal(t, iceberg.StringLiteral(tt.expected), result.Val,
+ "Apply on StringLiteral must match Transformer semantics")
+ assert.True(t, utf8.ValidString(string(result.Val.(iceberg.StringLiteral))),
+ "Apply output must be valid UTF-8")
+ })
+ }
+}