Files
wehub-resource-sync 498b235461
Build and test / Build and test AMD64 Ubuntu 22.04 (push) Failing after 0s
Publish Builder / amazonlinux2023 (push) Failing after 1s
Build and test / UT for Go (push) Has been skipped
Publish KRTE Images / KRTE (push) Failing after 1s
Build and test / Integration Test (push) Has been skipped
Build and test / Upload Code Coverage (push) Has been skipped
Publish Builder / rockylinux9 (push) Failing after 1s
Publish Builder / ubuntu22.04 (push) Failing after 0s
Publish Builder / ubuntu24.04 (push) Failing after 0s
Publish Gpu Builder / publish-gpu-builder (push) Failing after 1s
Publish Test Images / PyTest (push) Failing after 0s
Build and test / UT for Cpp (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:31:17 +08:00

808 lines
26 KiB
Go

/*
* # Licensed to the LF AI & Data foundation 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 expr
import (
"testing"
"github.com/apache/arrow/go/v17/arrow"
"github.com/apache/arrow/go/v17/arrow/array"
"github.com/apache/arrow/go/v17/arrow/memory"
"github.com/stretchr/testify/suite"
"github.com/milvus-io/milvus-proto/go-api/v3/schemapb"
"github.com/milvus-io/milvus/internal/util/function/chain/types"
"github.com/milvus-io/milvus/pkg/v3/util/merr"
)
// =============================================================================
// Test Suite
// =============================================================================
type NumCombineExprTestSuite struct {
suite.Suite
pool *memory.CheckedAllocator
}
func (s *NumCombineExprTestSuite) SetupTest() {
s.pool = memory.NewCheckedAllocator(memory.NewGoAllocator())
}
func (s *NumCombineExprTestSuite) TearDownTest() {
s.pool.AssertSize(s.T(), 0)
}
func TestNumCombineExprTestSuite(t *testing.T) {
suite.Run(t, new(NumCombineExprTestSuite))
}
// =============================================================================
// Helper Functions
// =============================================================================
func (s *NumCombineExprTestSuite) createFloat32ChunkedArray(values []float32) *arrow.Chunked {
builder := array.NewFloat32Builder(s.pool)
defer builder.Release()
for _, v := range values {
builder.Append(v)
}
arr := builder.NewArray()
chunked := arrow.NewChunked(arrow.PrimitiveTypes.Float32, []arrow.Array{arr})
arr.Release()
return chunked
}
func (s *NumCombineExprTestSuite) createNullableFloat32ChunkedArray(values []float32, valid []bool) *arrow.Chunked {
builder := array.NewFloat32Builder(s.pool)
defer builder.Release()
for i, v := range values {
if valid[i] {
builder.Append(v)
} else {
builder.AppendNull()
}
}
arr := builder.NewArray()
chunked := arrow.NewChunked(arrow.PrimitiveTypes.Float32, []arrow.Array{arr})
arr.Release()
return chunked
}
func (s *NumCombineExprTestSuite) createMultiChunkFloat32Array(chunk1, chunk2 []float32) *arrow.Chunked {
builder1 := array.NewFloat32Builder(s.pool)
defer builder1.Release()
for _, v := range chunk1 {
builder1.Append(v)
}
arr1 := builder1.NewArray()
builder2 := array.NewFloat32Builder(s.pool)
defer builder2.Release()
for _, v := range chunk2 {
builder2.Append(v)
}
arr2 := builder2.NewArray()
chunked := arrow.NewChunked(arrow.PrimitiveTypes.Float32, []arrow.Array{arr1, arr2})
arr1.Release()
arr2.Release()
return chunked
}
// =============================================================================
// Constructor Tests
// =============================================================================
func (s *NumCombineExprTestSuite) TestNewNumCombineExpr_Valid() {
expr, err := NewNumCombineExpr(ModeMultiply, nil)
s.Require().NoError(err)
s.NotNil(expr)
s.Equal(NumCombineFuncName, expr.Name())
s.Equal(ModeMultiply, expr.mode)
}
func (s *NumCombineExprTestSuite) TestNewNumCombineExpr_AllModes() {
modes := []string{ModeMultiply, ModeSum, ModeMax, ModeMin, ModeAvg}
for _, mode := range modes {
expr, err := NewNumCombineExpr(mode, nil)
s.Require().NoError(err, "mode: %s", mode)
s.NotNil(expr)
s.Equal(mode, expr.mode)
}
}
func (s *NumCombineExprTestSuite) TestNewNumCombineExpr_WeightedMode() {
weights := []float64{0.5, 0.3, 0.2}
expr, err := NewNumCombineExpr(ModeWeighted, weights)
s.Require().NoError(err)
s.NotNil(expr)
s.Equal(weights, expr.weights)
}
func (s *NumCombineExprTestSuite) TestNewNumCombineExpr_InvalidMode() {
_, err := NewNumCombineExpr("invalid_mode", nil)
s.Error(err)
s.Contains(err.Error(), "invalid mode")
}
func (s *NumCombineExprTestSuite) TestNewNumCombineExpr_WeightedModeNoWeights() {
_, err := NewNumCombineExpr(ModeWeighted, nil)
s.Error(err)
s.Contains(err.Error(), "weighted mode requires weights")
}
// =============================================================================
// Factory Tests
// =============================================================================
func (s *NumCombineExprTestSuite) TestNewNumCombineExprFromParams_Valid() {
params := map[string]*schemapb.FunctionParamValue{
"mode": stringParam("multiply"),
}
expr, err := NewNumCombineExprFromParams(types.FunctionBuildContext{}, types.FunctionConfig{Params: params})
s.Require().NoError(err)
s.NotNil(expr)
s.Equal(NumCombineFuncName, expr.Name())
}
func (s *NumCombineExprTestSuite) TestNewNumCombineExprFromParams_WithWeights() {
params := map[string]*schemapb.FunctionParamValue{
"mode": stringParam("weighted"),
"weights": arrayParam(doubleParam(0.6), doubleParam(0.4)),
}
expr, err := NewNumCombineExprFromParams(types.FunctionBuildContext{}, types.FunctionConfig{Params: params})
s.Require().NoError(err)
combineExpr := expr.(*NumCombineExpr)
s.Equal([]float64{0.6, 0.4}, combineExpr.weights)
}
func (s *NumCombineExprTestSuite) TestNewNumCombineExprFromParams_WeightsAsInts() {
params := map[string]*schemapb.FunctionParamValue{
"mode": stringParam("weighted"),
"weights": arrayParam(intParam(1), intParam(2)),
}
expr, err := NewNumCombineExprFromParams(types.FunctionBuildContext{}, types.FunctionConfig{Params: params})
s.Require().NoError(err)
s.NotNil(expr)
}
func (s *NumCombineExprTestSuite) TestNewNumCombineExprFromParams_DefaultMode() {
expr, err := NewNumCombineExprFromParams(types.FunctionBuildContext{}, types.FunctionConfig{Params: map[string]*schemapb.FunctionParamValue{}})
s.Require().NoError(err)
combineExpr := expr.(*NumCombineExpr)
s.Equal(ModeMultiply, combineExpr.mode)
}
// =============================================================================
// Execute Tests
// =============================================================================
func (s *NumCombineExprTestSuite) TestExecute_Multiply() {
col1 := s.createFloat32ChunkedArray([]float32{2.0, 3.0, 4.0})
defer col1.Release()
col2 := s.createFloat32ChunkedArray([]float32{0.5, 0.5, 0.5})
defer col2.Release()
expr, err := NewNumCombineExpr(ModeMultiply, nil)
s.Require().NoError(err)
ctx := types.NewFuncContext(s.pool)
outputs, err := expr.Execute(ctx, []*arrow.Chunked{col1, col2})
s.Require().NoError(err)
s.Len(outputs, 1)
defer outputs[0].Release()
// Check results: 2.0*0.5=1.0, 3.0*0.5=1.5, 4.0*0.5=2.0
result := outputs[0].Chunk(0).(*array.Float32)
s.InDelta(1.0, result.Value(0), 0.001)
s.InDelta(1.5, result.Value(1), 0.001)
s.InDelta(2.0, result.Value(2), 0.001)
}
func (s *NumCombineExprTestSuite) TestExecute_Sum() {
col1 := s.createFloat32ChunkedArray([]float32{1.0, 2.0, 3.0})
defer col1.Release()
col2 := s.createFloat32ChunkedArray([]float32{0.5, 0.5, 0.5})
defer col2.Release()
expr, err := NewNumCombineExpr(ModeSum, nil)
s.Require().NoError(err)
ctx := types.NewFuncContext(s.pool)
outputs, err := expr.Execute(ctx, []*arrow.Chunked{col1, col2})
s.Require().NoError(err)
defer outputs[0].Release()
// Check results: 1.0+0.5=1.5, 2.0+0.5=2.5, 3.0+0.5=3.5
result := outputs[0].Chunk(0).(*array.Float32)
s.InDelta(1.5, result.Value(0), 0.001)
s.InDelta(2.5, result.Value(1), 0.001)
s.InDelta(3.5, result.Value(2), 0.001)
}
func (s *NumCombineExprTestSuite) TestExecute_Max() {
col1 := s.createFloat32ChunkedArray([]float32{1.0, 5.0, 3.0})
defer col1.Release()
col2 := s.createFloat32ChunkedArray([]float32{2.0, 2.0, 4.0})
defer col2.Release()
expr, err := NewNumCombineExpr(ModeMax, nil)
s.Require().NoError(err)
ctx := types.NewFuncContext(s.pool)
outputs, err := expr.Execute(ctx, []*arrow.Chunked{col1, col2})
s.Require().NoError(err)
defer outputs[0].Release()
// Check results: max(1.0, 2.0)=2.0, max(5.0, 2.0)=5.0, max(3.0, 4.0)=4.0
result := outputs[0].Chunk(0).(*array.Float32)
s.InDelta(2.0, result.Value(0), 0.001)
s.InDelta(5.0, result.Value(1), 0.001)
s.InDelta(4.0, result.Value(2), 0.001)
}
func (s *NumCombineExprTestSuite) TestExecute_Min() {
col1 := s.createFloat32ChunkedArray([]float32{1.0, 5.0, 3.0})
defer col1.Release()
col2 := s.createFloat32ChunkedArray([]float32{2.0, 2.0, 4.0})
defer col2.Release()
expr, err := NewNumCombineExpr(ModeMin, nil)
s.Require().NoError(err)
ctx := types.NewFuncContext(s.pool)
outputs, err := expr.Execute(ctx, []*arrow.Chunked{col1, col2})
s.Require().NoError(err)
defer outputs[0].Release()
// Check results: min(1.0, 2.0)=1.0, min(5.0, 2.0)=2.0, min(3.0, 4.0)=3.0
result := outputs[0].Chunk(0).(*array.Float32)
s.InDelta(1.0, result.Value(0), 0.001)
s.InDelta(2.0, result.Value(1), 0.001)
s.InDelta(3.0, result.Value(2), 0.001)
}
func (s *NumCombineExprTestSuite) TestExecute_Avg() {
col1 := s.createFloat32ChunkedArray([]float32{2.0, 4.0, 6.0})
defer col1.Release()
col2 := s.createFloat32ChunkedArray([]float32{4.0, 6.0, 8.0})
defer col2.Release()
expr, err := NewNumCombineExpr(ModeAvg, nil)
s.Require().NoError(err)
ctx := types.NewFuncContext(s.pool)
outputs, err := expr.Execute(ctx, []*arrow.Chunked{col1, col2})
s.Require().NoError(err)
defer outputs[0].Release()
// Check results: avg(2.0, 4.0)=3.0, avg(4.0, 6.0)=5.0, avg(6.0, 8.0)=7.0
result := outputs[0].Chunk(0).(*array.Float32)
s.InDelta(3.0, result.Value(0), 0.001)
s.InDelta(5.0, result.Value(1), 0.001)
s.InDelta(7.0, result.Value(2), 0.001)
}
func (s *NumCombineExprTestSuite) TestExecute_Weighted() {
col1 := s.createFloat32ChunkedArray([]float32{10.0, 20.0, 30.0})
defer col1.Release()
col2 := s.createFloat32ChunkedArray([]float32{1.0, 2.0, 3.0})
defer col2.Release()
// weights: 0.8 for col1, 0.2 for col2
expr, err := NewNumCombineExpr(ModeWeighted, []float64{0.8, 0.2})
s.Require().NoError(err)
ctx := types.NewFuncContext(s.pool)
outputs, err := expr.Execute(ctx, []*arrow.Chunked{col1, col2})
s.Require().NoError(err)
defer outputs[0].Release()
// Check results: 10.0*0.8 + 1.0*0.2 = 8.2, 20.0*0.8 + 2.0*0.2 = 16.4, 30.0*0.8 + 3.0*0.2 = 24.6
result := outputs[0].Chunk(0).(*array.Float32)
s.InDelta(8.2, result.Value(0), 0.001)
s.InDelta(16.4, result.Value(1), 0.001)
s.InDelta(24.6, result.Value(2), 0.001)
}
func (s *NumCombineExprTestSuite) TestExecute_PropagatesNullInputsByDefault() {
col1 := s.createNullableFloat32ChunkedArray([]float32{2.0, 0.0, 0.0}, []bool{true, false, false})
defer col1.Release()
col2 := s.createNullableFloat32ChunkedArray([]float32{3.0, 4.0, 0.0}, []bool{true, true, false})
defer col2.Release()
expr, err := NewNumCombineExpr(ModeSum, nil)
s.Require().NoError(err)
ctx := types.NewFuncContext(s.pool)
outputs, err := expr.Execute(ctx, []*arrow.Chunked{col1, col2})
s.Require().NoError(err)
defer outputs[0].Release()
result := outputs[0].Chunk(0).(*array.Float32)
s.False(result.IsNull(0))
s.InDelta(5.0, result.Value(0), 0.001)
s.True(result.IsNull(1))
s.True(result.IsNull(2))
}
func (s *NumCombineExprTestSuite) TestExecute_TreatsNullInputsAsZero() {
col1 := s.createNullableFloat32ChunkedArray([]float32{2.0, 0.0, 0.0}, []bool{true, false, false})
defer col1.Release()
col2 := s.createNullableFloat32ChunkedArray([]float32{3.0, 4.0, 0.0}, []bool{true, true, false})
defer col2.Release()
col3 := s.createNullableFloat32ChunkedArray([]float32{0.0, 5.0, 0.0}, []bool{false, true, false})
defer col3.Release()
expr, err := NewNumCombineExpr(ModeSum, nil, WithNullPolicy(NumCombineNullAsZero))
s.Require().NoError(err)
ctx := types.NewFuncContext(s.pool)
outputs, err := expr.Execute(ctx, []*arrow.Chunked{col1, col2, col3})
s.Require().NoError(err)
defer outputs[0].Release()
result := outputs[0].Chunk(0).(*array.Float32)
s.False(result.IsNull(0))
s.InDelta(5.0, result.Value(0), 0.001)
s.False(result.IsNull(1))
s.InDelta(9.0, result.Value(1), 0.001)
s.False(result.IsNull(2))
s.InDelta(0.0, result.Value(2), 0.001)
}
func (s *NumCombineExprTestSuite) TestExecute_TreatsNullInputsAsZeroWeighted() {
col1 := s.createNullableFloat32ChunkedArray([]float32{10.0, 0.0}, []bool{true, false})
defer col1.Release()
col2 := s.createNullableFloat32ChunkedArray([]float32{0.0, 20.0}, []bool{false, true})
defer col2.Release()
expr, err := NewNumCombineExpr(ModeWeighted, []float64{0.8, 0.2}, WithNullPolicy(NumCombineNullAsZero))
s.Require().NoError(err)
ctx := types.NewFuncContext(s.pool)
outputs, err := expr.Execute(ctx, []*arrow.Chunked{col1, col2})
s.Require().NoError(err)
defer outputs[0].Release()
result := outputs[0].Chunk(0).(*array.Float32)
s.InDelta(8.0, result.Value(0), 0.001)
s.InDelta(4.0, result.Value(1), 0.001)
}
func (s *NumCombineExprTestSuite) TestExecute_SkipsNullInputs() {
col1 := s.createNullableFloat32ChunkedArray([]float32{2.0, 0.0, 0.0}, []bool{true, false, false})
defer col1.Release()
col2 := s.createNullableFloat32ChunkedArray([]float32{3.0, 4.0, 0.0}, []bool{true, true, false})
defer col2.Release()
col3 := s.createNullableFloat32ChunkedArray([]float32{0.0, 5.0, 0.0}, []bool{false, true, false})
defer col3.Release()
expr, err := NewNumCombineExpr(ModeSum, nil, WithNullPolicy(NumCombineNullSkip))
s.Require().NoError(err)
ctx := types.NewFuncContext(s.pool)
outputs, err := expr.Execute(ctx, []*arrow.Chunked{col1, col2, col3})
s.Require().NoError(err)
defer outputs[0].Release()
result := outputs[0].Chunk(0).(*array.Float32)
s.False(result.IsNull(0))
s.InDelta(5.0, result.Value(0), 0.001)
s.False(result.IsNull(1))
s.InDelta(9.0, result.Value(1), 0.001)
s.True(result.IsNull(2))
}
func (s *NumCombineExprTestSuite) TestExecute_SkipsNullInputsMultiply() {
col1 := s.createNullableFloat32ChunkedArray([]float32{2.0, 0.0}, []bool{true, false})
defer col1.Release()
col2 := s.createNullableFloat32ChunkedArray([]float32{3.0, 3.0}, []bool{true, true})
defer col2.Release()
expr, err := NewNumCombineExpr(ModeMultiply, nil, WithNullPolicy(NumCombineNullSkip))
s.Require().NoError(err)
ctx := types.NewFuncContext(s.pool)
outputs, err := expr.Execute(ctx, []*arrow.Chunked{col1, col2})
s.Require().NoError(err)
defer outputs[0].Release()
result := outputs[0].Chunk(0).(*array.Float32)
s.InDelta(6.0, result.Value(0), 0.001)
s.InDelta(3.0, result.Value(1), 0.001)
}
func (s *NumCombineExprTestSuite) TestExecute_TreatsNullInputsAsZeroMultiply() {
col1 := s.createNullableFloat32ChunkedArray([]float32{2.0, 0.0}, []bool{true, false})
defer col1.Release()
col2 := s.createNullableFloat32ChunkedArray([]float32{3.0, 3.0}, []bool{true, true})
defer col2.Release()
expr, err := NewNumCombineExpr(ModeMultiply, nil, WithNullPolicy(NumCombineNullAsZero))
s.Require().NoError(err)
ctx := types.NewFuncContext(s.pool)
outputs, err := expr.Execute(ctx, []*arrow.Chunked{col1, col2})
s.Require().NoError(err)
defer outputs[0].Release()
result := outputs[0].Chunk(0).(*array.Float32)
s.InDelta(6.0, result.Value(0), 0.001)
s.InDelta(0.0, result.Value(1), 0.001)
}
func (s *NumCombineExprTestSuite) TestExecute_MultipleChunks() {
col1 := s.createMultiChunkFloat32Array([]float32{1.0, 2.0}, []float32{3.0, 4.0})
defer col1.Release()
col2 := s.createMultiChunkFloat32Array([]float32{2.0, 2.0}, []float32{2.0, 2.0})
defer col2.Release()
expr, err := NewNumCombineExpr(ModeMultiply, nil)
s.Require().NoError(err)
ctx := types.NewFuncContext(s.pool)
outputs, err := expr.Execute(ctx, []*arrow.Chunked{col1, col2})
s.Require().NoError(err)
defer outputs[0].Release()
// Should have 2 chunks
s.Equal(2, len(outputs[0].Chunks()))
// Check first chunk: 1.0*2.0=2.0, 2.0*2.0=4.0
chunk0 := outputs[0].Chunk(0).(*array.Float32)
s.InDelta(2.0, chunk0.Value(0), 0.001)
s.InDelta(4.0, chunk0.Value(1), 0.001)
// Check second chunk: 3.0*2.0=6.0, 4.0*2.0=8.0
chunk1 := outputs[0].Chunk(1).(*array.Float32)
s.InDelta(6.0, chunk1.Value(0), 0.001)
s.InDelta(8.0, chunk1.Value(1), 0.001)
}
func (s *NumCombineExprTestSuite) TestExecute_ThreeInputColumns() {
col1 := s.createFloat32ChunkedArray([]float32{2.0, 3.0})
defer col1.Release()
col2 := s.createFloat32ChunkedArray([]float32{3.0, 4.0})
defer col2.Release()
col3 := s.createFloat32ChunkedArray([]float32{1.0, 1.0})
defer col3.Release()
expr, err := NewNumCombineExpr(ModeSum, nil)
s.Require().NoError(err)
ctx := types.NewFuncContext(s.pool)
outputs, err := expr.Execute(ctx, []*arrow.Chunked{col1, col2, col3})
s.Require().NoError(err)
defer outputs[0].Release()
// Check results: 2.0+3.0+1.0=6.0, 3.0+4.0+1.0=8.0
result := outputs[0].Chunk(0).(*array.Float32)
s.InDelta(6.0, result.Value(0), 0.001)
s.InDelta(8.0, result.Value(1), 0.001)
}
func (s *NumCombineExprTestSuite) TestExecute_TooFewInputs() {
col1 := s.createFloat32ChunkedArray([]float32{1.0, 2.0})
defer col1.Release()
expr, err := NewNumCombineExpr(ModeMultiply, nil)
s.Require().NoError(err)
ctx := types.NewFuncContext(s.pool)
// Execute with only 1 input
_, err = expr.Execute(ctx, []*arrow.Chunked{col1})
s.Error(err)
s.ErrorIs(err, merr.ErrParameterInvalid)
s.Contains(err.Error(), "at least 2 input columns")
}
func (s *NumCombineExprTestSuite) TestExecute_WeightedModeWrongWeightsCount() {
col1 := s.createFloat32ChunkedArray([]float32{1.0, 2.0})
defer col1.Release()
col2 := s.createFloat32ChunkedArray([]float32{3.0, 4.0})
defer col2.Release()
col3 := s.createFloat32ChunkedArray([]float32{5.0, 6.0})
defer col3.Release()
// Create with 2 weights, but execute with 3 inputs
expr, err := NewNumCombineExpr(ModeWeighted, []float64{0.5, 0.5})
s.Require().NoError(err)
ctx := types.NewFuncContext(s.pool)
_, err = expr.Execute(ctx, []*arrow.Chunked{col1, col2, col3})
s.Error(err)
s.ErrorIs(err, merr.ErrParameterInvalid)
s.Contains(err.Error(), "weighted mode requires 3 weights, got 2")
}
// =============================================================================
// Interface Method Tests
// =============================================================================
func (s *NumCombineExprTestSuite) TestOutputDataTypes() {
expr, err := NewNumCombineExpr(ModeMultiply, nil)
s.Require().NoError(err)
outputTypes := expr.OutputDataTypes()
s.Len(outputTypes, 1)
s.Equal(arrow.PrimitiveTypes.Float32, outputTypes[0])
}
func (s *NumCombineExprTestSuite) TestIsRunnable() {
expr, err := NewNumCombineExpr(ModeMultiply, nil)
s.Require().NoError(err)
// num_combine can run in all stages
s.True(expr.IsRunnable("L0_rerank"))
s.True(expr.IsRunnable("L1_rerank"))
s.True(expr.IsRunnable("L2_rerank"))
}
// =============================================================================
// Integration Tests
// =============================================================================
// Memory Leak Tests
// =============================================================================
func (s *NumCombineExprTestSuite) TestMemoryLeak_NumCombineExecution() {
for range 10 {
col1 := s.createFloat32ChunkedArray([]float32{1.0, 2.0, 3.0})
col2 := s.createFloat32ChunkedArray([]float32{0.5, 0.5, 0.5})
expr, err := NewNumCombineExpr(ModeMultiply, nil)
s.Require().NoError(err)
ctx := types.NewFuncContext(s.pool)
outputs, err := expr.Execute(ctx, []*arrow.Chunked{col1, col2})
s.Require().NoError(err)
outputs[0].Release()
col1.Release()
col2.Release()
}
// Memory leak check happens in TearDownTest
}
// =============================================================================
// Combine Function Unit Tests
// =============================================================================
func (s *NumCombineExprTestSuite) TestCombine_Multiply() {
expr := &NumCombineExpr{mode: ModeMultiply}
result := expr.combine([]float64{2.0, 3.0, 4.0}, nil)
s.InDelta(24.0, result, 0.001) // 2*3*4 = 24
}
func (s *NumCombineExprTestSuite) TestCombine_Sum() {
expr := &NumCombineExpr{mode: ModeSum}
result := expr.combine([]float64{1.0, 2.0, 3.0}, nil)
s.InDelta(6.0, result, 0.001) // 1+2+3 = 6
}
func (s *NumCombineExprTestSuite) TestCombine_Max() {
expr := &NumCombineExpr{mode: ModeMax}
result := expr.combine([]float64{1.0, 5.0, 3.0}, nil)
s.InDelta(5.0, result, 0.001)
}
func (s *NumCombineExprTestSuite) TestCombine_Min() {
expr := &NumCombineExpr{mode: ModeMin}
result := expr.combine([]float64{1.0, 5.0, 3.0}, nil)
s.InDelta(1.0, result, 0.001)
}
func (s *NumCombineExprTestSuite) TestCombine_Avg() {
expr := &NumCombineExpr{mode: ModeAvg}
result := expr.combine([]float64{2.0, 4.0, 6.0}, nil)
s.InDelta(4.0, result, 0.001) // (2+4+6)/3 = 4
}
func (s *NumCombineExprTestSuite) TestCombine_Weighted() {
expr := &NumCombineExpr{
mode: ModeWeighted,
weights: []float64{0.5, 0.3, 0.2},
}
result := expr.combine([]float64{10.0, 20.0, 30.0}, []float64{0.5, 0.3, 0.2})
// 10*0.5 + 20*0.3 + 30*0.2 = 5 + 6 + 6 = 17
s.InDelta(17.0, result, 0.001)
}
func (s *NumCombineExprTestSuite) TestGetNumericValue() {
// Test Float32
builder32 := array.NewFloat32Builder(s.pool)
builder32.Append(1.5)
arr32 := builder32.NewArray()
defer arr32.Release()
builder32.Release()
val, err := GetNumericValue(arr32, 0)
s.Require().NoError(err)
s.InDelta(1.5, val, 0.001)
// Test Float64
builder64 := array.NewFloat64Builder(s.pool)
builder64.Append(2.5)
arr64 := builder64.NewArray()
defer arr64.Release()
builder64.Release()
val, err = GetNumericValue(arr64, 0)
s.Require().NoError(err)
s.InDelta(2.5, val, 0.001)
// Test Int64
builderInt := array.NewInt64Builder(s.pool)
builderInt.Append(100)
arrInt := builderInt.NewArray()
defer arrInt.Release()
builderInt.Release()
val, err = GetNumericValue(arrInt, 0)
s.Require().NoError(err)
s.InDelta(100.0, val, 0.001)
}
func (s *NumCombineExprTestSuite) TestNewNumCombineExprFromParams_Invalid() {
_, err := NewNumCombineExprFromParams(types.FunctionBuildContext{}, types.FunctionConfig{Params: map[string]*schemapb.FunctionParamValue{
"mode": stringParam("bad-mode"),
}})
s.Error(err)
s.Contains(err.Error(), "invalid mode")
_, err = NewNumCombineExprFromParams(types.FunctionBuildContext{}, types.FunctionConfig{Params: map[string]*schemapb.FunctionParamValue{
"mode": stringParam("weighted"),
"weights": arrayParam(stringParam("bad")),
}})
s.Error(err)
s.Contains(err.Error(), "weights")
}
func (s *NumCombineExprTestSuite) TestExecute_SkipsNullInputsWeighted() {
col1 := s.createNullableFloat32ChunkedArray([]float32{10.0, 0.0, 0.0}, []bool{true, false, false})
defer col1.Release()
col2 := s.createNullableFloat32ChunkedArray([]float32{20.0, 20.0, 0.0}, []bool{true, true, false})
defer col2.Release()
expr, err := NewNumCombineExpr(ModeWeighted, []float64{0.8, 0.2}, WithNullPolicy(NumCombineNullSkip))
s.Require().NoError(err)
ctx := types.NewFuncContext(s.pool)
outputs, err := expr.Execute(ctx, []*arrow.Chunked{col1, col2})
s.Require().NoError(err)
defer outputs[0].Release()
result := outputs[0].Chunk(0).(*array.Float32)
s.InDelta(12.0, result.Value(0), 0.001)
s.InDelta(4.0, result.Value(1), 0.001)
s.True(result.IsNull(2))
}
func (s *NumCombineExprTestSuite) TestExecute_RejectsMismatchedChunksAndUnsupportedTypes() {
col1 := s.createFloat32ChunkedArray([]float32{1.0, 2.0})
defer col1.Release()
col2 := s.createMultiChunkFloat32Array([]float32{1.0}, []float32{2.0})
defer col2.Release()
expr, err := NewNumCombineExpr(ModeSum, nil)
s.Require().NoError(err)
ctx := types.NewFuncContext(s.pool)
_, err = expr.Execute(ctx, []*arrow.Chunked{col1, col2})
s.Error(err)
s.Contains(err.Error(), "input 0 has 1 chunks")
shortCol := s.createFloat32ChunkedArray([]float32{1.0})
defer shortCol.Release()
_, err = expr.Execute(ctx, []*arrow.Chunked{col1, shortCol})
s.Error(err)
s.Contains(err.Error(), "has 2 rows")
stringBuilder := array.NewStringBuilder(s.pool)
stringBuilder.AppendValues([]string{"a", "b"}, nil)
stringArray := stringBuilder.NewArray()
stringBuilder.Release()
stringCol := arrow.NewChunked(arrow.BinaryTypes.String, []arrow.Array{stringArray})
stringArray.Release()
defer stringCol.Release()
_, err = expr.Execute(ctx, []*arrow.Chunked{col1, stringCol})
s.Error(err)
s.Contains(err.Error(), "unsupported input column type")
}
func (s *NumCombineExprTestSuite) TestNewNumericReaderAllSupportedTypes() {
cases := []struct {
name string
arr arrow.Array
want float64
}{
{name: "int8", arr: func() arrow.Array {
b := array.NewInt8Builder(s.pool)
b.Append(1)
arr := b.NewArray()
b.Release()
return arr
}(), want: 1},
{name: "int16", arr: func() arrow.Array {
b := array.NewInt16Builder(s.pool)
b.Append(2)
arr := b.NewArray()
b.Release()
return arr
}(), want: 2},
{name: "int32", arr: func() arrow.Array {
b := array.NewInt32Builder(s.pool)
b.Append(3)
arr := b.NewArray()
b.Release()
return arr
}(), want: 3},
{name: "int64", arr: func() arrow.Array {
b := array.NewInt64Builder(s.pool)
b.Append(4)
arr := b.NewArray()
b.Release()
return arr
}(), want: 4},
{name: "float32", arr: func() arrow.Array {
b := array.NewFloat32Builder(s.pool)
b.Append(5.5)
arr := b.NewArray()
b.Release()
return arr
}(), want: 5.5},
{name: "float64", arr: func() arrow.Array {
b := array.NewFloat64Builder(s.pool)
b.Append(6.5)
arr := b.NewArray()
b.Release()
return arr
}(), want: 6.5},
}
for _, tc := range cases {
s.Run(tc.name, func() {
defer tc.arr.Release()
reader, ok := newNumericReader(tc.arr)
s.True(ok)
s.False(reader.IsNull(0))
s.InDelta(tc.want, reader.Float64(0), 0.001)
})
}
stringBuilder := array.NewStringBuilder(s.pool)
stringBuilder.Append("not numeric")
stringArray := stringBuilder.NewArray()
stringBuilder.Release()
defer stringArray.Release()
_, ok := newNumericReader(stringArray)
s.False(ok)
}