zeroshade opened a new issue, #1255:
URL: https://github.com/apache/arrow-go/issues/1255

   ### Describe the bug, including details regarding any error messages, 
version, and platform.
   
   A `FIXED_LEN_BYTE_ARRAY` column encoded with `BYTE_STREAM_SPLIT` decodes 
**values shifted by one position** when the column chunk contains **both nulls 
and more than one data page**. The data is silently wrong — no error is 
returned.
   
   Found on `main` (`f85b0b25` / `477eb633`), linux/amd64, Go 1.24. This is not 
a recent regression; it reproduces on older commits as well. Surfaced while 
reviewing #1172, which is unrelated and does not cause it.
   
   **Isolation, already narrowed:**
   
   * Encoding-specific — `PLAIN` passes on byte-identical data and writer 
configuration; only `BYTE_STREAM_SPLIT` fails.
   * Requires **both** conditions:
     * a single large data page (1 MiB) with nulls **passes**
     * multiple data pages without nulls **passes**
     * multiple data pages *with* nulls **fails**
   * Reproduced at widths 2, 3, 4, 7, 8, 16, 17, and 32 — not width-specific.
   * First mismatch appears partway through, not at the start (e.g. around 
index 259 for width 4).
   
   Example failure at width 4:
   
   ```
   mismatch at idx 259
   expected: []byte{0x0c, 0x0d, 0x0e, 0x0f}
   actual  : []byte{0x10, 0x11, 0x12, 0x13}
   ```
   
   `0x10 0x11 0x12 0x13` is the value belonging to index 260, i.e. the stream 
is off by exactly one value from that point on.
   
   **Not root-caused.** `ByteStreamSplitFixedLenByteArrayDecoder.SetData` 
recomputing `nvals = len(data)/typeLen` and setting `stride` looks correct on 
inspection, so the fault is likely elsewhere — either per-page decoder reset in 
the column reader or the encoder's per-page flush. I have not determined 
whether the file is written incorrectly or read incorrectly.
   
   **Note on existing coverage:** the spaced-decoding unit tests use a single 
`SetData` followed by one `DecodeSpaced`, so they exercise only the single-page 
case and pass. A fix should come with a multi-page + nulls integration test.
   
   ### Reproducer
   
   Drop into `parquet/pqarrow/` as `bss_flba_repro_test.go` and run
   `go test ./parquet/pqarrow/ -run TestBSSFLBANullsMultiPage -v`.
   
   ```go
   package pqarrow_test
   
   import (
        "bytes"
        "context"
        "fmt"
        "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/apache/arrow-go/v18/parquet"
        "github.com/apache/arrow-go/v18/parquet/file"
        "github.com/apache/arrow-go/v18/parquet/pqarrow"
        "github.com/stretchr/testify/require"
   )
   
   func TestBSSFLBANullsMultiPage(t *testing.T) {
        encs := map[string]parquet.Encoding{
                "BSS":   parquet.Encodings.ByteStreamSplit,
                "PLAIN": parquet.Encodings.Plain,
        }
        for encName, enc := range encs {
                for _, pageSize := range []int64{512, 1 << 20} { // small pages 
vs one big page
                        for _, width := range []int{4, 32} {
                                for _, nulls := range []bool{false, true} {
                                        
t.Run(fmt.Sprintf("%s/page%d/w%d/nulls=%v", encName, pageSize, width, nulls), 
func(t *testing.T) {
                                                mem := memory.DefaultAllocator
                                                const n = 5000
   
                                                dt := 
&arrow.FixedSizeBinaryType{ByteWidth: width}
                                                sc := 
arrow.NewSchema([]arrow.Field{{Name: "v", Type: dt, Nullable: nulls}}, nil)
   
                                                bldr := 
array.NewFixedSizeBinaryBuilder(mem, dt)
                                                defer bldr.Release()
                                                want := make([][]byte, 0, n)
                                                for i := 0; i < n; i++ {
                                                        if nulls && i%7 == 3 {
                                                                
bldr.AppendNull()
                                                                want = 
append(want, nil)
                                                                continue
                                                        }
                                                        v := make([]byte, width)
                                                        for j := range v {
                                                                v[j] = 
byte(i*width + j)
                                                        }
                                                        bldr.Append(v)
                                                        want = append(want, v)
                                                }
                                                arr := bldr.NewArray()
                                                defer arr.Release()
                                                rec := array.NewRecord(sc, 
[]arrow.Array{arr}, int64(n))
                                                defer rec.Release()
   
                                                var buf bytes.Buffer
                                                props := 
parquet.NewWriterProperties(
                                                        
parquet.WithEncoding(enc),
                                                        
parquet.WithDictionaryDefault(false),
                                                        
parquet.WithDataPageSize(pageSize),
                                                        
parquet.WithBatchSize(128),
                                                )
                                                w, err := 
pqarrow.NewFileWriter(sc, &buf, props, pqarrow.DefaultWriterProps())
                                                require.NoError(t, err)
                                                require.NoError(t, w.Write(rec))
                                                require.NoError(t, w.Close())
   
                                                rdr, err := 
file.NewParquetReader(bytes.NewReader(buf.Bytes()))
                                                require.NoError(t, err)
                                                defer rdr.Close()
   
                                                fr, err := 
pqarrow.NewFileReader(rdr, pqarrow.ArrowReadProperties{BatchSize: 137}, mem)
                                                require.NoError(t, err)
                                                tbl, err := 
fr.ReadTable(context.Background())
                                                require.NoError(t, err)
                                                defer tbl.Release()
   
                                                require.EqualValues(t, n, 
tbl.NumRows())
                                                idx := 0
                                                for _, c := range 
tbl.Column(0).Data().Chunks() {
                                                        fsb := 
c.(*array.FixedSizeBinary)
                                                        for i := 0; i < 
fsb.Len(); i++ {
                                                                if want[idx] == 
nil {
                                                                        
require.True(t, fsb.IsNull(i), "idx %d should be null", idx)
                                                                } else {
                                                                        
require.False(t, fsb.IsNull(i), "idx %d should be valid", idx)
                                                                        
require.Equal(t, want[idx], fsb.Value(i), "mismatch at idx %d", idx)
                                                                }
                                                                idx++
                                                        }
                                                }
                                                require.Equal(t, n, idx)
                                        })
                                }
                        }
                }
        }
   }
   ```
   
   Observed result — only the BSS multi-page-with-nulls combinations fail:
   
   ```
   --- PASS: TestBSSFLBANullsMultiPage/BSS/page512/w4/nulls=false
   --- FAIL: TestBSSFLBANullsMultiPage/BSS/page512/w4/nulls=true
   --- PASS: TestBSSFLBANullsMultiPage/BSS/page512/w32/nulls=false
   --- FAIL: TestBSSFLBANullsMultiPage/BSS/page512/w32/nulls=true
   --- PASS: TestBSSFLBANullsMultiPage/BSS/page1048576/w4/nulls=true
   --- PASS: TestBSSFLBANullsMultiPage/BSS/page1048576/w32/nulls=true
   --- PASS: TestBSSFLBANullsMultiPage/PLAIN/... (all)
   ```
   
   ### Component(s)
   
   Parquet
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to