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 85222f5c1 perf(transforms): avoid redundant integer truncate modulus 
(#2145)
85222f5c1 is described below

commit 85222f5c197a1eea13a8cdeceeb12be582c509f3
Author: Minh Vu <[email protected]>
AuthorDate: Fri Oct 9 18:19:56 2026 +0200

    perf(transforms): avoid redundant integer truncate modulus (#2145)
    
    * perf(transforms): avoid redundant integer truncate modulus
    
    * test(transforms): correct minimum integer truncate expectation
    
    * test(transforms): strengthen integer truncate coverage
    
    Signed-off-by: Minh Vu <[email protected]>
---
 transforms.go                    | 12 +++++-
 transforms_integer_bench_test.go | 83 ++++++++++++++++++++++++++++++++++++++++
 transforms_test.go               | 23 ++++++++++-
 3 files changed, 115 insertions(+), 3 deletions(-)

diff --git a/transforms.go b/transforms.go
index 6c66797df..5fea782eb 100644
--- a/transforms.go
+++ b/transforms.go
@@ -742,8 +742,12 @@ func (t TruncateTransform) Transformer(src Type) 
(func(any) any, error) {
 
                        val := v.(int32)
                        width := int32(t.Width)
+                       remainder := val % width
+                       if remainder < 0 {
+                               remainder += width
+                       }
 
-                       return val - (((val % width) + width) % width)
+                       return val - remainder
                }, nil
        case Int64Type:
                return func(v any) any {
@@ -753,8 +757,12 @@ func (t TruncateTransform) Transformer(src Type) 
(func(any) any, error) {
 
                        val := v.(int64)
                        width := int64(t.Width)
+                       remainder := val % width
+                       if remainder < 0 {
+                               remainder += width
+                       }
 
-                       return val - (((val % width) + width) % width)
+                       return val - remainder
                }, nil
        case StringType:
                return func(v any) any {
diff --git a/transforms_integer_bench_test.go b/transforms_integer_bench_test.go
new file mode 100644
index 000000000..a8849dced
--- /dev/null
+++ b/transforms_integer_bench_test.go
@@ -0,0 +1,83 @@
+// 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 iceberg_test
+
+import (
+       "math"
+       "testing"
+
+       "github.com/apache/iceberg-go"
+)
+
+var benchmarkIntegerTruncateResult any
+
+func BenchmarkTruncateTransformInteger(b *testing.B) {
+       tests := []struct {
+               name   string
+               typ    iceberg.Type
+               width  int
+               values []any
+       }{
+               {
+                       name:  "int32/mixed",
+                       typ:   iceberg.PrimitiveTypes.Int32,
+                       width: 97,
+                       values: []any{
+                               int32(-1000), int32(-98), int32(-1), int32(0),
+                               int32(1), int32(96), int32(97), int32(1000),
+                       },
+               },
+               {
+                       name:  "int64/mixed",
+                       typ:   iceberg.PrimitiveTypes.Int64,
+                       width: 97,
+                       values: []any{
+                               int64(-1 << 40), int64(-1000), int64(-1), 
int64(0),
+                               int64(1), int64(1000), int64(1 << 40),
+                       },
+               },
+               {
+                       name:  "int32/width-max",
+                       typ:   iceberg.PrimitiveTypes.Int32,
+                       width: math.MaxInt32,
+                       values: []any{
+                               int32(math.MinInt32), int32(-1), int32(0),
+                               int32(math.MaxInt32 - 1), int32(math.MaxInt32),
+                       },
+               },
+       }
+
+       for _, tt := range tests {
+               b.Run(tt.name, func(b *testing.B) {
+                       transformer, err := (iceberg.TruncateTransform{Width: 
tt.width}).Transformer(tt.typ)
+                       if err != nil {
+                               b.Fatal(err)
+                       }
+
+                       b.ReportAllocs()
+                       index := 0
+                       for b.Loop() {
+                               benchmarkIntegerTruncateResult = 
transformer(tt.values[index])
+                               index++
+                               if index == len(tt.values) {
+                                       index = 0
+                               }
+                       }
+               })
+       }
+}
diff --git a/transforms_test.go b/transforms_test.go
index 493a53441..2024c1ebb 100644
--- a/transforms_test.go
+++ b/transforms_test.go
@@ -1137,8 +1137,24 @@ func TestTruncateTransform(t *testing.T) {
        }{
                {10, iceberg.Int32Literal(1), iceberg.Int32Literal(0)},
                {10, iceberg.Int32Literal(-1), iceberg.Int32Literal(-10)},
+               {10, iceberg.Int32Literal(-10), iceberg.Int32Literal(-10)},
+               {10, iceberg.Int32Literal(-11), iceberg.Int32Literal(-20)},
+               {1, iceberg.Int32Literal(math.MinInt32), 
iceberg.Int32Literal(math.MinInt32)},
+               {1 << 30, iceberg.Int32Literal((1 << 30) - 1), 
iceberg.Int32Literal(0)},
+               {(1 << 30) + 1, iceberg.Int32Literal((1 << 30) - 1), 
iceberg.Int32Literal(0)},
+               {math.MaxInt32, iceberg.Int32Literal(math.MaxInt32 - 1), 
iceberg.Int32Literal(0)},
+               {math.MaxInt32, iceberg.Int32Literal(math.MaxInt32), 
iceberg.Int32Literal(math.MaxInt32)},
+               {math.MaxInt32, iceberg.Int32Literal(-1), 
iceberg.Int32Literal(-math.MaxInt32)},
+               // The final subtraction retains signed integer wraparound at 
the minimum value.
+               {math.MaxInt32, iceberg.Int32Literal(math.MinInt32), 
iceberg.Int32Literal(2)},
                {10, iceberg.Int64Literal(1), iceberg.Int64Literal(0)},
                {10, iceberg.Int64Literal(-1), iceberg.Int64Literal(-10)},
+               {10, iceberg.Int64Literal(-10), iceberg.Int64Literal(-10)},
+               {10, iceberg.Int64Literal(-11), iceberg.Int64Literal(-20)},
+               {1, iceberg.Int64Literal(math.MinInt64), 
iceberg.Int64Literal(math.MinInt64)},
+               {10, iceberg.Int64Literal(math.MaxInt64), 
iceberg.Int64Literal(math.MaxInt64 - 7)},
+               {10, iceberg.Int64Literal(math.MinInt64), 
iceberg.Int64Literal(math.MaxInt64 - 1)},
+               {math.MaxInt32, iceberg.Int64Literal(math.MaxInt64), 
iceberg.Int64Literal(math.MaxInt64 - 1)},
                {50, iceberg.DecimalLiteral{
                        Val:   decimal128.FromI64(1065),
                        Scale: 2,
@@ -1154,8 +1170,13 @@ func TestTruncateTransform(t *testing.T) {
        }
 
        for _, tt := range tests {
-               t.Run(fmt.Sprintf("width=%d value=%v", tt.width, 
tt.value.Any()), func(t *testing.T) {
+               t.Run(fmt.Sprintf("type=%s/width=%d/value=%v", tt.value.Type(), 
tt.width, tt.value.Any()), func(t *testing.T) {
                        transform := iceberg.TruncateTransform{Width: tt.width}
+                       transformer, err := 
transform.Transformer(tt.value.Type())
+                       require.NoError(t, err)
+                       assert.Equal(t, tt.expected.Any(), 
transformer(tt.value.Any()))
+                       assert.Nil(t, transformer(nil))
+
                        result := 
transform.Apply(iceberg.Optional[iceberg.Literal]{Val: tt.value, Valid: true})
                        require.True(t, result.Valid)
                        assert.Equal(t, tt.expected, result.Val)

Reply via email to