zeroshade commented on code in PR #1099:
URL: https://github.com/apache/arrow-go/pull/1099#discussion_r3752105148


##########
parquet/pqarrow/column_readers.go:
##########
@@ -451,6 +451,10 @@ func (lr *listReader) LoadBatch(nrecords int64) error {
 }
 
 func (lr *listReader) BuildArray(lenBound int64) (*arrow.Chunked, error) {
+       return lr.buildArray(lenBound, false)
+}
+
+func (lr *listReader) buildArray(lenBound int64, fixedSize bool) 
(*arrow.Chunked, error) {

Review Comment:
   Minor: the `fixedSize` parameter looks redundant. `fixedSizeListReader` 
embeds `*listReader`, and the code this replaces decided the same thing inline 
with `lr.field.Type.ID() == arrow.FIXED_SIZE_LIST`. Keeping that type check 
here would drop both the bool parameter and the 
`fixedSizeListReader.BuildArray` override, for the same behaviour.
   
   Not a blocker, just more indirection than the one-line condition it replaced.



##########
parquet/pqarrow/column_readers.go:
##########
@@ -529,18 +541,85 @@ func (lr *listReader) BuildArray(lenBound int64) 
(*arrow.Chunked, error) {
 
        data := array.NewData(lr.field.Type, int(validityIO.Read), buffers, 
[]arrow.ArrayData{item}, int(validityIO.NullCount), 0)
        defer data.Release()
-       if lr.field.Type.ID() == arrow.FIXED_SIZE_LIST {
-               defer data.Buffers()[1].Release()
-               offsetData := 
arrow.Int32Traits.CastFromBytes(offsetsBuffer.Bytes())
-               listSize := lr.field.Type.(*arrow.FixedSizeListType).Len()
-               for x := 1; x < data.Len(); x++ {
-                       size := offsetData[x] - offsetData[x-1]
-                       if size != listSize {
-                               return nil, fmt.Errorf("expected all lists to 
be of size=%d, but index %d had size=%d", listSize, x, size)
+       out := array.MakeFromData(data)
+       defer out.Release()
+       return arrow.NewChunked(lr.field.Type, []arrow.Array{out}), nil
+}
+
+func (lr *listReader) buildFixedSizeListArray(length int, offsets []int32, 
validityBuffer *memory.Buffer,
+       nullCount int64, item arrow.Array) (*arrow.Chunked, error) {
+       listType := lr.field.Type.(*arrow.FixedSizeListType)
+       listSize := int(listType.Len())
+
+       if offsets[0] != 0 {
+               return nil, fmt.Errorf("fixed-size list first offset must be 
zero, got %d", offsets[0])
+       }
+       if int64(offsets[length]) != int64(item.Len()) {
+               return nil, fmt.Errorf("fixed-size list final offset %d does 
not match decoded child length %d",
+                       offsets[length], item.Len())
+       }
+
+       if nullCount == 0 {
+               for idx := 0; idx < length; idx++ {
+                       if size := offsets[idx+1] - offsets[idx]; size != 
int32(listSize) {
+                               return nil, fmt.Errorf("expected all lists to 
be of size=%d, but index %d had size=%d", listSize, idx, size)
+                       }
+               }
+               data := array.NewData(lr.field.Type, length, 
[]*memory.Buffer{nil},
+                       []arrow.ArrayData{item.Data()}, 0, 0)
+               defer data.Release()
+               out := array.MakeFromData(data)
+               defer out.Release()
+               return arrow.NewChunked(lr.field.Type, []arrow.Array{out}), nil
+       }
+
+       pieces := make([]arrow.Array, 0, length)
+       defer func() { releaseArrays(pieces) }()
+
+       for i := 0; i < length; {
+               valid := !lr.field.Nullable || 
bitutil.BitIsSet(validityBuffer.Bytes(), i)
+               end := i + 1
+               for end < length {
+                       nextValid := !lr.field.Nullable || 
bitutil.BitIsSet(validityBuffer.Bytes(), end)
+                       if nextValid != valid {
+                               break
                        }
+                       end++
                }
-               data.Buffers()[1] = nil
+
+               if valid {
+                       for idx := i; idx < end; idx++ {
+                               if size := offsets[idx+1] - offsets[idx]; size 
!= int32(listSize) {
+                                       return nil, fmt.Errorf("expected all 
lists to be of size=%d, but index %d had size=%d", listSize, idx, size)
+                               }
+                       }
+                       pieces = append(pieces, array.NewSlice(item, 
int64(offsets[i]), int64(offsets[end])))
+               } else {
+                       for idx := i; idx < end; idx++ {
+                               if size := offsets[idx+1] - offsets[idx]; size 
!= 0 {
+                                       return nil, fmt.Errorf("null fixed-size 
list at index %d consumed %d child values", idx, size)
+                               }
+                       }
+                       pieces = append(pieces, 
array.MakeArrayOfNull(lr.rctx.mem, listType.Elem(), (end-i)*listSize))
+               }
+               i = end
+       }
+
+       if len(pieces) == 0 {
+               pieces = append(pieces, array.MakeArrayOfNull(lr.rctx.mem, 
listType.Elem(), 0))
+       }
+       child, err := array.Concatenate(pieces, lr.rctx.mem)

Review Comment:
   Question: is this safe for dictionary-encoded or extension child types?
   
   The `nullCount == 0` fast path hands the decoded child array through 
untouched, so it never reaches `Concatenate`. But as soon as there's a null 
parent, the child goes through this instead — so a fixed-size list of 
dictionary values would take a different code path depending only on whether 
any parent happened to be null. If `Concatenate` unifies or rejects 
dictionaries, that's an asymmetry worth knowing about. I haven't confirmed 
either way.
   
   Separately, worth a brief comment noting the cost profile: parents that 
alternate null/valid produce one array per run, so this is O(length) 
allocations plus the concat in the worst case. Still strictly better than the 
previous behaviour of erroring out, but it's the kind of thing that's easier to 
reason about later with a note here.



-- 
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