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

   **Describe the bug**
   
   With `ExecCtx.ChunkSize` set below the input length, `and_kleene` and 
`or_kleene` return `false`
   where the answer is null. The result depends on the chunk size, which it 
must not.
   
   ```go
   // left:  [true, null, true, null, false, true, null, true]
   // right: [null, true, true, false, null, null, true, true]
   ectx := compute.DefaultExecCtx()
   ectx.ChunkSize = 1
   ctx := compute.SetExecCtx(context.Background(), ectx)
   out, _ := compute.CallFunction(ctx, "and_kleene", nil, leftDatum, rightDatum)
   ```
   
   | ChunkSize | and_kleene result |
   |---|---|
   | default | `[null null true false false null null true]` |
   | 1 | `[null false true false false null false true]` |
   | 2 | `[null null true false false false false true]` |
   | 3 and above | correct |
   
   arrow-go main (281776d) and v18.7.0, whose `SetSlice` is the same code; Go 
1.27.1, macOS 26.6 arm64. Full test in the attached file; the
   CheckedAllocator in it reports no leak, this is a values bug only.
   
   **Cause**
   
   `iterateExecSpans` reuses one `ArraySpan` per argument and advances it with 
`SetSlice`
   (executor.go:902). `ArraySpan.SetSlice` (exec/span.go:211-230) reuses the 
cached `Nulls` for the
   new slice when it is `0` ("no nulls") or equal to the old `Len` ("all 
null"), and only otherwise
   resets it to `UnknownNullCount`. That is right only while `Nulls` describes 
the whole array. The
   Kleene kernels call `UpdateNullCount()` on the span they are given 
(scalar_boolean.go:49, 54, 97,
   121, 131, 174, 198), which stores the *current slice's* count into the 
shared span; on the next
   `SetSlice` a slice that happened to be all valid makes every following slice 
"no nulls", and a
   slice that was all null makes every following slice "all null". Any kernel 
that calls
   `UpdateNullCount()` on its input span is exposed; the Kleene kernels are the 
ones on main that do.
   
   **Expected behavior**
   
   Results independent of `ChunkSize`. The C++ `ArraySpan::SetSlice` never 
carries a cached count
   when a validity bitmap is present; the same rule here fixes it:
   
   ```go
   if a.Type.ID() != arrow.NULL {
       if a.Nulls != 0 || len(a.Buffers[0].Buf) != 0 {
           a.Nulls = array.UnknownNullCount
       }
   } else {
       a.Nulls = length
   }
   ```
   
   With that change the attached test passes for every chunk size and `go test 
./arrow/compute/...`
   still passes. I can open the PR with the fix and the test if that is welcome.
   
   **Component(s)**
   
   Go, Compute
   
   <details>
   <summary>and_kleene_chunksize_test.go (package compute_test; go test -run 
TestAndKleeneChunkSize ./arrow/compute/)</summary>
   
   ```go
   package compute_test
   
   import (
        "context"
        "testing"
   
        "github.com/apache/arrow-go/v18/arrow/array"
        "github.com/apache/arrow-go/v18/arrow/compute"
        "github.com/apache/arrow-go/v18/arrow/memory"
   )
   
   // and_kleene over two boolean arrays with nulls, executed with different
   // ExecCtx.ChunkSize values. The result must not depend on ChunkSize.
   func TestAndKleeneChunkSize(t *testing.T) {
        mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
        defer mem.AssertSize(t, 0)
   
        // left:  [true, null, true, null, false, true, null, true]
        // right: [null, true, true, false, null, null, true, true]
        lb := array.NewBooleanBuilder(mem)
        lb.AppendValues([]bool{true, false, true, false, false, true, false, 
true}, []bool{true, false, true, false, true, true, false, true})
        left := lb.NewArray()
        lb.Release()
        defer left.Release()
        rb := array.NewBooleanBuilder(mem)
        rb.AppendValues([]bool{false, true, true, false, false, false, true, 
true}, []bool{false, true, true, true, false, false, true, true})
        right := rb.NewArray()
        rb.Release()
        defer right.Release()
   
        run := func(chunk int64) string {
                ectx := compute.DefaultExecCtx()
                ectx.ChunkSize = chunk
                ctx := 
compute.SetExecCtx(compute.WithAllocator(context.Background(), mem), ectx)
                out, err := compute.CallFunction(ctx, "and_kleene", nil, 
&compute.ArrayDatum{Value: left.Data()}, &compute.ArrayDatum{Value: 
right.Data()})
                if err != nil {
                        t.Fatal(err)
                }
                defer out.Release()
                arr := out.(*compute.ArrayDatum).MakeArray()
                defer arr.Release()
                return arr.String()
        }
        ref := run(compute.DefaultMaxChunkSize)
        t.Logf("ChunkSize default: %s", ref)
        for _, n := range []int64{1, 2, 3, 4, 8} {
                got := run(n)
                t.Logf("ChunkSize %d: %s", n, got)
                if got != ref {
                        t.Errorf("ChunkSize %d: got %s, want %s", n, got, ref)
                }
        }
   }
   ```
   
   </details>
   


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