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/arrow-go.git
The following commit(s) were added to refs/heads/main by this push:
new 0151136a fix(parquet/pqarrow): reject timestamp coercion overflow
(#1008)
0151136a is described below
commit 0151136ae8ae1b879b639459b4c862ac3cb19e08
Author: Minh Vu <[email protected]>
AuthorDate: Mon Jul 27 19:08:21 2026 +0200
fix(parquet/pqarrow): reject timestamp coercion overflow (#1008)
### Rationale for this change
Timestamp coercion to a finer unit multiplies int64 values without
checking whether the result fits. Values near the int64 limits can
therefore wrap and be written as a different timestamp.
### What changes are included in this PR?
- Check the int64 range before multiplying a valid timestamp.
- Return `arrow.ErrInvalid` when coercion would overflow.
- Ignore the stored value in null slots, matching the existing
truncation checks.
### Are these changes tested?
Yes. Focused tests cover positive and negative overflow as well as an
overflowing value in a null slot.
---
parquet/pqarrow/encode_arrow.go | 6 +++
.../encode_arrow_timestamp_coercion_test.go | 56 ++++++++++++++++++++++
2 files changed, 62 insertions(+)
diff --git a/parquet/pqarrow/encode_arrow.go b/parquet/pqarrow/encode_arrow.go
index 30e079f3..9ec11ff8 100644
--- a/parquet/pqarrow/encode_arrow.go
+++ b/parquet/pqarrow/encode_arrow.go
@@ -674,6 +674,12 @@ func writeCoerceTimestamps(arr *array.Timestamp, props
*ArrowWriterProperties, o
vals := arr.TimestampValues()
multiply := func(factor int64) error {
for idx, val := range vals {
+ if !arr.IsValid(idx) {
+ continue
+ }
+ if int64(val) > math.MaxInt64/factor || int64(val) <
math.MinInt64/factor {
+ return fmt.Errorf("%w: casting timestamp from
%s to %s would overflow", arrow.ErrInvalid, source, target)
+ }
out[idx] = int64(val) * factor
}
return nil
diff --git a/parquet/pqarrow/encode_arrow_timestamp_coercion_test.go
b/parquet/pqarrow/encode_arrow_timestamp_coercion_test.go
new file mode 100644
index 00000000..676597db
--- /dev/null
+++ b/parquet/pqarrow/encode_arrow_timestamp_coercion_test.go
@@ -0,0 +1,56 @@
+// 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 pqarrow
+
+import (
+ "errors"
+ "math"
+ "testing"
+
+ "github.com/apache/arrow-go/v18/arrow"
+ "github.com/apache/arrow-go/v18/arrow/array"
+ "github.com/apache/arrow-go/v18/arrow/memory"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestWriteCoerceTimestampsOverflow(t *testing.T) {
+ b := array.NewTimestampBuilder(memory.DefaultAllocator,
&arrow.TimestampType{Unit: arrow.Second})
+ defer b.Release()
+ b.AppendValues([]arrow.Timestamp{arrow.Timestamp(math.MaxInt64/1000 +
1), arrow.Timestamp(math.MinInt64/1000 - 1)}, nil)
+ arr := b.NewTimestampArray()
+ defer arr.Release()
+
+ out := make([]int64, arr.Len())
+ props :=
NewArrowWriterProperties(WithCoerceTimestamps(arrow.Millisecond))
+ err := writeCoerceTimestamps(arr, &props, out)
+ require.Error(t, err)
+ assert.True(t, errors.Is(err, arrow.ErrInvalid))
+}
+
+func TestWriteCoerceTimestampsIgnoresNullOverflow(t *testing.T) {
+ b := array.NewTimestampBuilder(memory.DefaultAllocator,
&arrow.TimestampType{Unit: arrow.Second})
+ defer b.Release()
+ b.AppendValues([]arrow.Timestamp{1, arrow.Timestamp(math.MaxInt64)},
[]bool{true, false})
+ arr := b.NewTimestampArray()
+ defer arr.Release()
+
+ out := make([]int64, arr.Len())
+ props :=
NewArrowWriterProperties(WithCoerceTimestamps(arrow.Millisecond))
+ require.NoError(t, writeCoerceTimestamps(arr, &props, out))
+ assert.Equal(t, int64(1000), out[0])
+}