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 dcb85bfa fix(parquet): bss encoding and tests on big endian systems 
(#663)
dcb85bfa is described below

commit dcb85bfa48c025bae8d8f8e271965a7cc387adc1
Author: daniel-adam-tfs <[email protected]>
AuthorDate: Thu Feb 19 22:26:50 2026 +0100

    fix(parquet): bss encoding and tests on big endian systems (#663)
    
    ### Rationale for this change
    
    To ensure the Arrow and Parquet Go libraries work correctly on
    big-endian architectures.
    
    ### What changes are included in this PR?
    
    Added endianness-aware BYTE_STREAM_SPLIT decoding in the
    parquet/encoding package.
    Fixed tests in the parquet package to handle byte order correctly on
    big-endian systems.
    
    ### Are these changes tested?
    
    Yes, all affected unit tests now pass on both little-endian and
    big-endian machines. The changes specifically address some of the
    previously failing tests on big-endian systems.
    
    ### Are there any user-facing changes?
    
    No user-facing API changes. The changes are internal and ensure correct
    behavior on supported architectures.
---
 parquet/file/file_writer_test.go                   | 18 +++++--
 parquet/internal/encoding/byte_stream_split.go     | 39 ++------------
 .../encoding/byte_stream_split_big_endian.go       | 61 ++++++++++++++++++++++
 .../encoding/byte_stream_split_little_endian.go    | 61 ++++++++++++++++++++++
 .../internal/encoding/encoding_utils_big_endian.go |  8 ++-
 parquet/internal/encoding/plain_encoding_types.go  | 24 +++++----
 6 files changed, 160 insertions(+), 51 deletions(-)

diff --git a/parquet/file/file_writer_test.go b/parquet/file/file_writer_test.go
index 5997b107..7a6c6ae1 100644
--- a/parquet/file/file_writer_test.go
+++ b/parquet/file/file_writer_test.go
@@ -28,6 +28,7 @@ import (
        "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/apache/arrow-go/v18/internal/utils"
        "github.com/apache/arrow-go/v18/parquet"
        "github.com/apache/arrow-go/v18/parquet/compress"
        "github.com/apache/arrow-go/v18/parquet/file"
@@ -498,9 +499,11 @@ type errCloseWriter struct {
 func (c *errCloseWriter) Write(p []byte) (n int, err error) {
        return c.sink.Write(p)
 }
+
 func (c *errCloseWriter) Close() error {
        return fmt.Errorf("error during close")
 }
+
 func (c *errCloseWriter) Bytes() []byte {
        return c.sink.Bytes()
 }
@@ -669,6 +672,7 @@ func NewColumnIndexObject(colIdx metadata.ColumnIndex) (ret 
ColumnIndexObject) {
 }
 
 func simpleEncode[T int32 | int64 | float32 | float64](val T) []byte {
+       val = utils.ToLE(val)
        return unsafe.Slice((*byte)(unsafe.Pointer(&val)), unsafe.Sizeof(val))
 }
 
@@ -987,10 +991,16 @@ func (t *PageIndexRoundTripSuite) TestMultiplePages() {
        t.Equal(t.columnIndexes, []ColumnIndexObject{
                {
                        nullPages: []bool{false, false, false, true},
-                       minValues: [][]byte{simpleEncode(int64(1)), 
simpleEncode(int64(3)),
-                               simpleEncode(int64(6)), {}},
-                       maxValues: [][]byte{simpleEncode(int64(2)), 
simpleEncode(int64(4)),
-                               simpleEncode(int64(6)), {}},
+                       minValues: [][]byte{
+                               simpleEncode(int64(1)), simpleEncode(int64(3)),
+                               simpleEncode(int64(6)),
+                               {},
+                       },
+                       maxValues: [][]byte{
+                               simpleEncode(int64(2)), simpleEncode(int64(4)),
+                               simpleEncode(int64(6)),
+                               {},
+                       },
                        boundaryOrder: metadata.Ascending, nullCounts: 
[]int64{0, 0, 1, 2},
                },
                {
diff --git a/parquet/internal/encoding/byte_stream_split.go 
b/parquet/internal/encoding/byte_stream_split.go
index fab61365..73e32634 100644
--- a/parquet/internal/encoding/byte_stream_split.go
+++ b/parquet/internal/encoding/byte_stream_split.go
@@ -88,38 +88,6 @@ func encodeByteStreamSplitWidth8(data []byte, in []byte) {
        }
 }
 
-// decodeByteStreamSplitBatchWidth4 decodes the batch of nValues raw bytes 
representing a 4-byte datatype provided by 'data',
-// into the output buffer 'out' using BYTE_STREAM_SPLIT encoding.
-// 'out' must have space for at least len(data) bytes.
-func decodeByteStreamSplitBatchWidth4(data []byte, nValues, stride int, out 
[]byte) {
-       const width = 4
-       debug.Assert(len(out) >= nValues*width, fmt.Sprintf("not enough space 
in output buffer for decoding, out: %d bytes, data: %d bytes", len(out), 
len(data)))
-       for element := 0; element < nValues; element++ {
-               out[width*element] = data[element]
-               out[width*element+1] = data[stride+element]
-               out[width*element+2] = data[2*stride+element]
-               out[width*element+3] = data[3*stride+element]
-       }
-}
-
-// decodeByteStreamSplitBatchWidth8 decodes the batch of nValues raw bytes 
representing a 8-byte datatype provided by 'data',
-// into the output buffer 'out' using BYTE_STREAM_SPLIT encoding.
-// 'out' must have space for at least len(data) bytes.
-func decodeByteStreamSplitBatchWidth8(data []byte, nValues, stride int, out 
[]byte) {
-       const width = 8
-       debug.Assert(len(out) >= nValues*width, fmt.Sprintf("not enough space 
in output buffer for decoding, out: %d bytes, data: %d bytes", len(out), 
len(data)))
-       for element := 0; element < nValues; element++ {
-               out[width*element] = data[element]
-               out[width*element+1] = data[stride+element]
-               out[width*element+2] = data[2*stride+element]
-               out[width*element+3] = data[3*stride+element]
-               out[width*element+4] = data[4*stride+element]
-               out[width*element+5] = data[5*stride+element]
-               out[width*element+6] = data[6*stride+element]
-               out[width*element+7] = data[7*stride+element]
-       }
-}
-
 // decodeByteStreamSplitBatchFLBA decodes the batch of nValues 
FixedLenByteArrays provided by 'data',
 // into the output slice 'out' using BYTE_STREAM_SPLIT encoding.
 // 'out' must have space for at least nValues slices.
@@ -303,12 +271,15 @@ func (dec *ByteStreamSplitDecoder[T]) Decode(out []T) 
(int, error) {
                return 0, xerrors.New("parquet: eof exception")
        }
 
+       // reinterpret the output slice as bytes so that we can decode directly 
into it without an intermediate copy
+       // however, the byte stream split encoding is defined in little-endian 
order, so we need to decode the bytes
+       // into the output slice in the correct order based on the machine's 
endianness
        outBytes := arrow.GetBytes(out)
        switch typeLen {
        case 4:
-               decodeByteStreamSplitBatchWidth4(dec.data, toRead, dec.stride, 
outBytes)
+               decodeByteStreamSplitBatchWidth4InByteOrder(dec.data, toRead, 
dec.stride, outBytes)
        case 8:
-               decodeByteStreamSplitBatchWidth8(dec.data, toRead, dec.stride, 
outBytes)
+               decodeByteStreamSplitBatchWidth8InByteOrder(dec.data, toRead, 
dec.stride, outBytes)
        default:
                return 0, fmt.Errorf("encoding ByteStreamSplit is only defined 
for numeric type of width 4 or 8, found: %d", typeLen)
        }
diff --git a/parquet/internal/encoding/byte_stream_split_big_endian.go 
b/parquet/internal/encoding/byte_stream_split_big_endian.go
new file mode 100644
index 00000000..ee73a3ce
--- /dev/null
+++ b/parquet/internal/encoding/byte_stream_split_big_endian.go
@@ -0,0 +1,61 @@
+// 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.
+
+//go:build armbe || arm64be || m68k || mips || mips64 || mips64p32 || ppc || 
ppc64 || s390 || s390x || shbe || sparc || sparc64
+
+package encoding
+
+import (
+       "fmt"
+
+       "github.com/apache/arrow-go/v18/parquet/internal/debug"
+)
+
+// decodeByteStreamSplitBatchWidth4InByteOrder decodes the batch of nValues 
raw bytes representing a 4-byte datatype provided
+// by 'data', into the output buffer 'out' using BYTE_STREAM_SPLIT encoding. 
The values are expected to be in little-endian
+// byte order and are be decoded into the 'out' array in machine's native 
endianness.
+// 'out' must have space for at least len(data) bytes.
+func decodeByteStreamSplitBatchWidth4InByteOrder(data []byte, nValues, stride 
int, out []byte) {
+       const width = 4
+       debug.Assert(len(out) >= nValues*width, fmt.Sprintf("not enough space 
in output buffer for decoding, out: %d bytes, data: %d bytes", len(out), 
len(data)))
+       for element := 0; element < nValues; element++ {
+               // Big Endian: most significant byte first
+               out[width*element+0] = data[3*stride+element]
+               out[width*element+1] = data[2*stride+element]
+               out[width*element+2] = data[stride+element]
+               out[width*element+3] = data[element]
+       }
+}
+
+// decodeByteStreamSplitBatchWidth8InByteOrder decodes the batch of nValues 
raw bytes representing a 8-byte datatype provided
+// by 'data', into the output buffer 'out' using BYTE_STREAM_SPLIT encoding. 
The values are expected to be in little-endian
+// byte order and are be decoded into the 'out' array in machine's native 
endianness.
+// 'out' must have space for at least len(data) bytes.
+func decodeByteStreamSplitBatchWidth8InByteOrder(data []byte, nValues, stride 
int, out []byte) {
+       const width = 8
+       debug.Assert(len(out) >= nValues*width, fmt.Sprintf("not enough space 
in output buffer for decoding, out: %d bytes, data: %d bytes", len(out), 
len(data)))
+       for element := 0; element < nValues; element++ {
+               // Big Endian: most significant byte first
+               out[width*element+0] = data[7*stride+element]
+               out[width*element+1] = data[6*stride+element]
+               out[width*element+2] = data[5*stride+element]
+               out[width*element+3] = data[4*stride+element]
+               out[width*element+4] = data[3*stride+element]
+               out[width*element+5] = data[2*stride+element]
+               out[width*element+6] = data[stride+element]
+               out[width*element+7] = data[element]
+       }
+}
diff --git a/parquet/internal/encoding/byte_stream_split_little_endian.go 
b/parquet/internal/encoding/byte_stream_split_little_endian.go
new file mode 100644
index 00000000..482351ef
--- /dev/null
+++ b/parquet/internal/encoding/byte_stream_split_little_endian.go
@@ -0,0 +1,61 @@
+// 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.
+
+//go:build 386 || amd64 || amd64p32 || alpha || arm || arm64 || loong64 || 
mipsle || mips64le || mips64p32le || nios2 || ppc64le || riscv || riscv64 || sh 
|| wasm
+
+package encoding
+
+import (
+       "fmt"
+
+       "github.com/apache/arrow-go/v18/parquet/internal/debug"
+)
+
+// decodeByteStreamSplitBatchWidth4InByteOrder decodes the batch of nValues 
raw bytes representing a 4-byte datatype provided
+// by 'data', into the output buffer 'out' using BYTE_STREAM_SPLIT encoding. 
The values are expected to be in little-endian
+// byte order and are be decoded into the 'out' array in machine's native 
endianness.
+// 'out' must have space for at least len(data) bytes.
+func decodeByteStreamSplitBatchWidth4InByteOrder(data []byte, nValues, stride 
int, out []byte) {
+       const width = 4
+       debug.Assert(len(out) >= nValues*width, fmt.Sprintf("not enough space 
in output buffer for decoding, out: %d bytes, data: %d bytes", len(out), 
len(data)))
+       for element := 0; element < nValues; element++ {
+               // Little Endian: least significant byte first
+               out[width*element+0] = data[element]
+               out[width*element+1] = data[stride+element]
+               out[width*element+2] = data[2*stride+element]
+               out[width*element+3] = data[3*stride+element]
+       }
+}
+
+// decodeByteStreamSplitBatchWidth8InByteOrder decodes the batch of nValues 
raw bytes representing a 8-byte datatype provided
+// by 'data', into the output buffer 'out' using BYTE_STREAM_SPLIT encoding. 
The values are expected to be in little-endian
+// byte order and are be decoded into the 'out' array in machine's native 
endianness.
+// 'out' must have space for at least len(data) bytes.
+func decodeByteStreamSplitBatchWidth8InByteOrder(data []byte, nValues, stride 
int, out []byte) {
+       const width = 8
+       debug.Assert(len(out) >= nValues*width, fmt.Sprintf("not enough space 
in output buffer for decoding, out: %d bytes, data: %d bytes", len(out), 
len(data)))
+       for element := 0; element < nValues; element++ {
+               // Little Endian: least significant byte first
+               out[width*element+0] = data[element]
+               out[width*element+1] = data[stride+element]
+               out[width*element+2] = data[2*stride+element]
+               out[width*element+3] = data[3*stride+element]
+               out[width*element+4] = data[4*stride+element]
+               out[width*element+5] = data[5*stride+element]
+               out[width*element+6] = data[6*stride+element]
+               out[width*element+7] = data[7*stride+element]
+       }
+}
diff --git a/parquet/internal/encoding/encoding_utils_big_endian.go 
b/parquet/internal/encoding/encoding_utils_big_endian.go
index 96783cc9..02acdd66 100644
--- a/parquet/internal/encoding/encoding_utils_big_endian.go
+++ b/parquet/internal/encoding/encoding_utils_big_endian.go
@@ -31,7 +31,9 @@ func writeLE[T fixedLenTypes](enc *encoder, in []T) {
        case parquet.Int96:
                enc.append(getBytes(in))
        default:
-               binary.Write(enc.sink, binary.LittleEndian, in)
+               if err := binary.Write(enc.sink, binary.LittleEndian, in); err 
!= nil {
+                       panic(err)
+               }
        }
 }
 
@@ -42,6 +44,8 @@ func copyFrom[T fixedLenTypes](dst []T, src []byte) {
                copy(dst, fromBytes[T](src))
        default:
                r := bytes.NewReader(src)
-               binary.Read(r, binary.LittleEndian, dst)
+               if err := binary.Read(r, binary.LittleEndian, dst); err != nil {
+                       panic(err)
+               }
        }
 }
diff --git a/parquet/internal/encoding/plain_encoding_types.go 
b/parquet/internal/encoding/plain_encoding_types.go
index e378fed5..f026e7ed 100644
--- a/parquet/internal/encoding/plain_encoding_types.go
+++ b/parquet/internal/encoding/plain_encoding_types.go
@@ -89,7 +89,7 @@ func (dec *PlainDecoder[T]) Decode(out []T) (int, error) {
                        dec.Type(), max, nbytes, len(dec.data))
        }
 
-       copyFrom(out, dec.data[:nbytes])
+       copyFrom(out[:max], dec.data[:nbytes])
        dec.data = dec.data[nbytes:]
        dec.nvals -= max
        return max, nil
@@ -130,13 +130,15 @@ func (dec *PlainDecoder[T]) DecodeSpaced(out []T, 
nullCount int, validBits []byt
        return nvalues, nil
 }
 
-type PlainInt32Encoder = PlainEncoder[int32]
-type PlainInt32Decoder = PlainDecoder[int32]
-type PlainInt64Encoder = PlainEncoder[int64]
-type PlainInt64Decoder = PlainDecoder[int64]
-type PlainFloat32Encoder = PlainEncoder[float32]
-type PlainFloat32Decoder = PlainDecoder[float32]
-type PlainFloat64Encoder = PlainEncoder[float64]
-type PlainFloat64Decoder = PlainDecoder[float64]
-type PlainInt96Encoder = PlainEncoder[parquet.Int96]
-type PlainInt96Decoder = PlainDecoder[parquet.Int96]
+type (
+       PlainInt32Encoder   = PlainEncoder[int32]
+       PlainInt32Decoder   = PlainDecoder[int32]
+       PlainInt64Encoder   = PlainEncoder[int64]
+       PlainInt64Decoder   = PlainDecoder[int64]
+       PlainFloat32Encoder = PlainEncoder[float32]
+       PlainFloat32Decoder = PlainDecoder[float32]
+       PlainFloat64Encoder = PlainEncoder[float64]
+       PlainFloat64Decoder = PlainDecoder[float64]
+       PlainInt96Encoder   = PlainEncoder[parquet.Int96]
+       PlainInt96Decoder   = PlainDecoder[parquet.Int96]
+)

Reply via email to