mirror of https://github.com/grafana/loki
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
908 lines
24 KiB
908 lines
24 KiB
package executor
|
|
|
|
import (
|
|
"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/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/grafana/loki/v3/pkg/engine/internal/types"
|
|
)
|
|
|
|
// Helper function to create a boolean array
|
|
func createBoolArray(values []bool, nulls []bool) arrow.Array {
|
|
builder := array.NewBooleanBuilder(memory.DefaultAllocator)
|
|
|
|
for i, val := range values {
|
|
if nulls != nil && i < len(nulls) && nulls[i] {
|
|
builder.AppendNull()
|
|
} else {
|
|
builder.Append(val)
|
|
}
|
|
}
|
|
|
|
return builder.NewArray()
|
|
}
|
|
|
|
// Helper function to create a string array
|
|
func createStringArray(values []string, nulls []bool) arrow.Array {
|
|
builder := array.NewStringBuilder(memory.DefaultAllocator)
|
|
|
|
for i, val := range values {
|
|
if nulls != nil && i < len(nulls) && nulls[i] {
|
|
builder.AppendNull()
|
|
} else {
|
|
builder.Append(val)
|
|
}
|
|
}
|
|
|
|
return builder.NewArray()
|
|
}
|
|
|
|
// Helper function to create an int64 array
|
|
func createInt64Array(values []int64, nulls []bool) arrow.Array {
|
|
builder := array.NewInt64Builder(memory.DefaultAllocator)
|
|
|
|
for i, val := range values {
|
|
if nulls != nil && i < len(nulls) && nulls[i] {
|
|
builder.AppendNull()
|
|
} else {
|
|
builder.Append(val)
|
|
}
|
|
}
|
|
|
|
return builder.NewArray()
|
|
}
|
|
|
|
// Helper function to create a arrow.Timestamp array
|
|
func createTimestampArray(values []arrow.Timestamp, nulls []bool) arrow.Array {
|
|
builder := array.NewTimestampBuilder(memory.DefaultAllocator, &arrow.TimestampType{Unit: arrow.Nanosecond, TimeZone: "UTC"})
|
|
|
|
for i, val := range values {
|
|
if nulls != nil && i < len(nulls) && nulls[i] {
|
|
builder.AppendNull()
|
|
} else {
|
|
builder.Append(val)
|
|
}
|
|
}
|
|
|
|
return builder.NewArray()
|
|
}
|
|
|
|
// Helper function to create a float64 array
|
|
func createFloat64Array(values []float64, nulls []bool) arrow.Array {
|
|
builder := array.NewFloat64Builder(memory.DefaultAllocator)
|
|
|
|
for i, val := range values {
|
|
if nulls != nil && i < len(nulls) && nulls[i] {
|
|
builder.AppendNull()
|
|
} else {
|
|
builder.Append(val)
|
|
}
|
|
}
|
|
|
|
return builder.NewArray()
|
|
}
|
|
|
|
// Helper function to extract boolean values from result
|
|
func extractBoolValues(result arrow.Array) ([]bool, []bool) {
|
|
arr := result.(*array.Boolean)
|
|
|
|
values := make([]bool, arr.Len())
|
|
nulls := make([]bool, arr.Len())
|
|
for i := 0; i < arr.Len(); i++ {
|
|
if arr.IsNull(i) {
|
|
nulls[i] = true
|
|
} else {
|
|
values[i] = arr.Value(i)
|
|
}
|
|
}
|
|
return values, nulls
|
|
}
|
|
|
|
func TestBinaryFunctionRegistry_GetForSignature(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
op types.BinaryOp
|
|
dataType arrow.DataType
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "valid equality operation for boolean",
|
|
op: types.BinaryOpEq,
|
|
dataType: arrow.FixedWidthTypes.Boolean,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "valid equality operation for string",
|
|
op: types.BinaryOpEq,
|
|
dataType: arrow.BinaryTypes.String,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "valid equality operation for int64",
|
|
op: types.BinaryOpEq,
|
|
dataType: arrow.PrimitiveTypes.Int64,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "valid equality operation for timestamp",
|
|
op: types.BinaryOpEq,
|
|
dataType: arrow.FixedWidthTypes.Timestamp_ns,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "valid equality operation for float64",
|
|
op: types.BinaryOpEq,
|
|
dataType: arrow.PrimitiveTypes.Float64,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "valid string contains operation",
|
|
op: types.BinaryOpMatchSubstr,
|
|
dataType: arrow.BinaryTypes.String,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "valid regex match operation",
|
|
op: types.BinaryOpMatchRe,
|
|
dataType: arrow.BinaryTypes.String,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "valid div operation",
|
|
op: types.BinaryOpDiv,
|
|
dataType: arrow.PrimitiveTypes.Float64,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "valid add operation",
|
|
op: types.BinaryOpAdd,
|
|
dataType: arrow.PrimitiveTypes.Float64,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "valid Mul operation",
|
|
op: types.BinaryOpMul,
|
|
dataType: arrow.PrimitiveTypes.Float64,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "valid sub operation",
|
|
op: types.BinaryOpSub,
|
|
dataType: arrow.PrimitiveTypes.Float64,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "invalid data type for operation",
|
|
op: types.BinaryOpEq,
|
|
dataType: arrow.PrimitiveTypes.Int32, // Not registered
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
fn, err := binaryFunctions.GetForSignature(tt.op, tt.dataType)
|
|
|
|
if tt.expectError {
|
|
assert.Error(t, err)
|
|
assert.Nil(t, fn)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, fn)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBooleanComparisonFunctions(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
op types.BinaryOp
|
|
lhs []bool
|
|
rhs []bool
|
|
expected []bool
|
|
}{
|
|
{
|
|
name: "boolean equality",
|
|
op: types.BinaryOpEq,
|
|
lhs: []bool{true, false, true, false},
|
|
rhs: []bool{true, false, false, true},
|
|
expected: []bool{true, true, false, false},
|
|
},
|
|
{
|
|
name: "boolean inequality",
|
|
op: types.BinaryOpNeq,
|
|
lhs: []bool{true, false, true, false},
|
|
rhs: []bool{true, false, false, true},
|
|
expected: []bool{false, false, true, true},
|
|
},
|
|
{
|
|
name: "boolean greater than",
|
|
op: types.BinaryOpGt,
|
|
lhs: []bool{true, false, true, false},
|
|
rhs: []bool{false, true, true, false},
|
|
expected: []bool{true, false, false, false},
|
|
},
|
|
{
|
|
name: "boolean greater than or equal",
|
|
op: types.BinaryOpGte,
|
|
lhs: []bool{true, false, true, false},
|
|
rhs: []bool{false, true, true, false},
|
|
expected: []bool{true, false, true, true},
|
|
},
|
|
{
|
|
name: "boolean less than",
|
|
op: types.BinaryOpLt,
|
|
lhs: []bool{true, false, true, false},
|
|
rhs: []bool{false, true, true, false},
|
|
expected: []bool{false, true, false, false},
|
|
},
|
|
{
|
|
name: "boolean less than or equal",
|
|
op: types.BinaryOpLte,
|
|
lhs: []bool{true, false, true, false},
|
|
rhs: []bool{false, true, true, false},
|
|
expected: []bool{false, true, true, true},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
lhsArray := createBoolArray(tt.lhs, nil)
|
|
rhsArray := createBoolArray(tt.rhs, nil)
|
|
|
|
fn, err := binaryFunctions.GetForSignature(tt.op, arrow.FixedWidthTypes.Boolean)
|
|
require.NoError(t, err)
|
|
|
|
result, err := fn.Evaluate(lhsArray, rhsArray, false, false)
|
|
require.NoError(t, err)
|
|
|
|
actual, _ := extractBoolValues(result)
|
|
assert.Equal(t, tt.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBooleanLogicalOperations(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
op types.BinaryOp
|
|
lhs []bool
|
|
rhs []bool
|
|
expected []bool
|
|
}{
|
|
{
|
|
name: "logical AND",
|
|
op: types.BinaryOpAnd,
|
|
lhs: []bool{true, true, false, false},
|
|
rhs: []bool{true, false, true, false},
|
|
expected: []bool{true, false, false, false},
|
|
},
|
|
{
|
|
name: "logical OR",
|
|
op: types.BinaryOpOr,
|
|
lhs: []bool{true, true, false, false},
|
|
rhs: []bool{true, false, true, false},
|
|
expected: []bool{true, true, true, false},
|
|
},
|
|
{
|
|
name: "logical XOR",
|
|
op: types.BinaryOpXor,
|
|
lhs: []bool{true, true, false, false},
|
|
rhs: []bool{true, false, true, false},
|
|
expected: []bool{false, true, true, false},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
lhsArray := createBoolArray(tt.lhs, nil)
|
|
rhsArray := createBoolArray(tt.rhs, nil)
|
|
|
|
fn, err := binaryFunctions.GetForSignature(tt.op, arrow.FixedWidthTypes.Boolean)
|
|
require.NoError(t, err)
|
|
|
|
result, err := fn.Evaluate(lhsArray, rhsArray, false, false)
|
|
require.NoError(t, err)
|
|
|
|
actual, _ := extractBoolValues(result)
|
|
assert.Equal(t, tt.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStringComparisonFunctions(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
op types.BinaryOp
|
|
lhs []string
|
|
rhs []string
|
|
expected []bool
|
|
}{
|
|
{
|
|
name: "string equality",
|
|
op: types.BinaryOpEq,
|
|
lhs: []string{"hello", "world", "test", ""},
|
|
rhs: []string{"hello", "world", "different", ""},
|
|
expected: []bool{true, true, false, true},
|
|
},
|
|
{
|
|
name: "string inequality",
|
|
op: types.BinaryOpNeq,
|
|
lhs: []string{"hello", "world", "test", ""},
|
|
rhs: []string{"hello", "world", "different", ""},
|
|
expected: []bool{false, false, true, false},
|
|
},
|
|
{
|
|
name: "string greater than",
|
|
op: types.BinaryOpGt,
|
|
lhs: []string{"b", "a", "z", "hello"},
|
|
rhs: []string{"a", "b", "a", "world"},
|
|
expected: []bool{true, false, true, false},
|
|
},
|
|
{
|
|
name: "string greater than or equal",
|
|
op: types.BinaryOpGte,
|
|
lhs: []string{"b", "a", "z", "hello"},
|
|
rhs: []string{"a", "a", "a", "hello"},
|
|
expected: []bool{true, true, true, true},
|
|
},
|
|
{
|
|
name: "string less than",
|
|
op: types.BinaryOpLt,
|
|
lhs: []string{"a", "b", "a", "world"},
|
|
rhs: []string{"b", "a", "z", "hello"},
|
|
expected: []bool{true, false, true, false},
|
|
},
|
|
{
|
|
name: "string less than or equal",
|
|
op: types.BinaryOpLte,
|
|
lhs: []string{"a", "a", "a", "hello"},
|
|
rhs: []string{"b", "a", "z", "hello"},
|
|
expected: []bool{true, true, true, true},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
lhsArray := createStringArray(tt.lhs, nil)
|
|
rhsArray := createStringArray(tt.rhs, nil)
|
|
|
|
fn, err := binaryFunctions.GetForSignature(tt.op, arrow.BinaryTypes.String)
|
|
require.NoError(t, err)
|
|
|
|
result, err := fn.Evaluate(lhsArray, rhsArray, false, false)
|
|
require.NoError(t, err)
|
|
|
|
actual, _ := extractBoolValues(result)
|
|
assert.Equal(t, tt.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIntegerComparisonFunctions(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
op types.BinaryOp
|
|
lhs []int64
|
|
rhs []int64
|
|
expected []bool
|
|
}{
|
|
{
|
|
name: "int64 equality",
|
|
op: types.BinaryOpEq,
|
|
lhs: []int64{1, 2, 3, 0, -1},
|
|
rhs: []int64{1, 3, 3, 0, 1},
|
|
expected: []bool{true, false, true, true, false},
|
|
},
|
|
{
|
|
name: "int64 inequality",
|
|
op: types.BinaryOpNeq,
|
|
lhs: []int64{1, 2, 3, 0, -1},
|
|
rhs: []int64{1, 3, 3, 0, 1},
|
|
expected: []bool{false, true, false, false, true},
|
|
},
|
|
{
|
|
name: "int64 greater than",
|
|
op: types.BinaryOpGt,
|
|
lhs: []int64{2, 1, 3, 0, -1},
|
|
rhs: []int64{1, 2, 3, 0, -2},
|
|
expected: []bool{true, false, false, false, true},
|
|
},
|
|
{
|
|
name: "int64 greater than or equal",
|
|
op: types.BinaryOpGte,
|
|
lhs: []int64{2, 1, 3, 0, -1},
|
|
rhs: []int64{1, 1, 3, 0, -1},
|
|
expected: []bool{true, true, true, true, true},
|
|
},
|
|
{
|
|
name: "int64 less than",
|
|
op: types.BinaryOpLt,
|
|
lhs: []int64{1, 2, 3, 0, -2},
|
|
rhs: []int64{2, 1, 3, 0, -1},
|
|
expected: []bool{true, false, false, false, true},
|
|
},
|
|
{
|
|
name: "int64 less than or equal",
|
|
op: types.BinaryOpLte,
|
|
lhs: []int64{1, 1, 3, 0, -1},
|
|
rhs: []int64{2, 1, 3, 0, -1},
|
|
expected: []bool{true, true, true, true, true},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
lhsArray := createInt64Array(tt.lhs, nil)
|
|
rhsArray := createInt64Array(tt.rhs, nil)
|
|
|
|
fn, err := binaryFunctions.GetForSignature(tt.op, arrow.PrimitiveTypes.Int64)
|
|
require.NoError(t, err)
|
|
|
|
result, err := fn.Evaluate(lhsArray, rhsArray, false, false)
|
|
require.NoError(t, err)
|
|
|
|
actual, _ := extractBoolValues(result)
|
|
assert.Equal(t, tt.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTimestampComparisonFunctions(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
op types.BinaryOp
|
|
lhs []arrow.Timestamp
|
|
rhs []arrow.Timestamp
|
|
expected []bool
|
|
}{
|
|
{
|
|
name: "timestamp equality",
|
|
op: types.BinaryOpEq,
|
|
lhs: []arrow.Timestamp{1, 2, 3, 0, 100},
|
|
rhs: []arrow.Timestamp{1, 3, 3, 0, 50},
|
|
expected: []bool{true, false, true, true, false},
|
|
},
|
|
{
|
|
name: "timestamp inequality",
|
|
op: types.BinaryOpNeq,
|
|
lhs: []arrow.Timestamp{1, 2, 3, 0, 100},
|
|
rhs: []arrow.Timestamp{1, 3, 3, 0, 50},
|
|
expected: []bool{false, true, false, false, true},
|
|
},
|
|
{
|
|
name: "timestamp greater than",
|
|
op: types.BinaryOpGt,
|
|
lhs: []arrow.Timestamp{2, 1, 3, 0, 100},
|
|
rhs: []arrow.Timestamp{1, 2, 3, 0, 50},
|
|
expected: []bool{true, false, false, false, true},
|
|
},
|
|
{
|
|
name: "timestamp greater than or equal",
|
|
op: types.BinaryOpGte,
|
|
lhs: []arrow.Timestamp{2, 1, 3, 0, 100},
|
|
rhs: []arrow.Timestamp{1, 1, 3, 0, 100},
|
|
expected: []bool{true, true, true, true, true},
|
|
},
|
|
{
|
|
name: "timestamp less than",
|
|
op: types.BinaryOpLt,
|
|
lhs: []arrow.Timestamp{1, 2, 3, 0, 50},
|
|
rhs: []arrow.Timestamp{2, 1, 3, 0, 100},
|
|
expected: []bool{true, false, false, false, true},
|
|
},
|
|
{
|
|
name: "timestamp less than or equal",
|
|
op: types.BinaryOpLte,
|
|
lhs: []arrow.Timestamp{1, 1, 3, 0, 100},
|
|
rhs: []arrow.Timestamp{2, 1, 3, 0, 100},
|
|
expected: []bool{true, true, true, true, true},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
lhsArray := createTimestampArray(tt.lhs, nil)
|
|
rhsArray := createTimestampArray(tt.rhs, nil)
|
|
|
|
fn, err := binaryFunctions.GetForSignature(tt.op, arrow.FixedWidthTypes.Timestamp_ns)
|
|
require.NoError(t, err)
|
|
|
|
result, err := fn.Evaluate(lhsArray, rhsArray, false, false)
|
|
require.NoError(t, err)
|
|
|
|
actual, _ := extractBoolValues(result)
|
|
assert.Equal(t, tt.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFloat64ComparisonFunctions(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
op types.BinaryOp
|
|
lhs []float64
|
|
rhs []float64
|
|
expected []bool
|
|
}{
|
|
{
|
|
name: "float64 equality",
|
|
op: types.BinaryOpEq,
|
|
lhs: []float64{1.0, 2.5, 3.14, 0.0, -1.5},
|
|
rhs: []float64{1.0, 2.6, 3.14, 0.0, 1.5},
|
|
expected: []bool{true, false, true, true, false},
|
|
},
|
|
{
|
|
name: "float64 inequality",
|
|
op: types.BinaryOpNeq,
|
|
lhs: []float64{1.0, 2.5, 3.14, 0.0, -1.5},
|
|
rhs: []float64{1.0, 2.6, 3.14, 0.0, 1.5},
|
|
expected: []bool{false, true, false, false, true},
|
|
},
|
|
{
|
|
name: "float64 greater than",
|
|
op: types.BinaryOpGt,
|
|
lhs: []float64{2.0, 1.5, 3.14, 0.0, -1.0},
|
|
rhs: []float64{1.0, 2.0, 3.14, 0.0, -2.0},
|
|
expected: []bool{true, false, false, false, true},
|
|
},
|
|
{
|
|
name: "float64 greater than or equal",
|
|
op: types.BinaryOpGte,
|
|
lhs: []float64{2.0, 1.5, 3.14, 0.0, -1.0},
|
|
rhs: []float64{1.0, 1.5, 3.14, 0.0, -1.0},
|
|
expected: []bool{true, true, true, true, true},
|
|
},
|
|
{
|
|
name: "float64 less than",
|
|
op: types.BinaryOpLt,
|
|
lhs: []float64{1.0, 2.0, 3.14, 0.0, -2.0},
|
|
rhs: []float64{2.0, 1.5, 3.14, 0.0, -1.0},
|
|
expected: []bool{true, false, false, false, true},
|
|
},
|
|
{
|
|
name: "float64 less than or equal",
|
|
op: types.BinaryOpLte,
|
|
lhs: []float64{1.0, 1.5, 3.14, 0.0, -1.0},
|
|
rhs: []float64{2.0, 1.5, 3.14, 0.0, -1.0},
|
|
expected: []bool{true, true, true, true, true},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
lhsArray := createFloat64Array(tt.lhs, nil)
|
|
rhsArray := createFloat64Array(tt.rhs, nil)
|
|
|
|
fn, err := binaryFunctions.GetForSignature(tt.op, arrow.PrimitiveTypes.Float64)
|
|
require.NoError(t, err)
|
|
|
|
result, err := fn.Evaluate(lhsArray, rhsArray, false, false)
|
|
require.NoError(t, err)
|
|
|
|
actual, _ := extractBoolValues(result)
|
|
assert.Equal(t, tt.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStringMatchingFunctions(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
op types.BinaryOp
|
|
lhs []string
|
|
rhs []string
|
|
expected []bool
|
|
}{
|
|
{
|
|
name: "string contains",
|
|
op: types.BinaryOpMatchSubstr,
|
|
lhs: []string{"hello world", "test string", "foobar", ""},
|
|
rhs: []string{"world", "test", "baz", ""},
|
|
expected: []bool{true, true, false, true},
|
|
},
|
|
{
|
|
name: "string does not contain",
|
|
op: types.BinaryOpNotMatchSubstr,
|
|
lhs: []string{"hello world", "test string", "foobar", ""},
|
|
rhs: []string{"world", "test", "baz", ""},
|
|
expected: []bool{false, false, true, false},
|
|
},
|
|
{
|
|
name: "regex match",
|
|
op: types.BinaryOpMatchRe,
|
|
lhs: []string{"hello123", "test456", "abc", ""},
|
|
rhs: []string{"^hello\\d+$", "^\\d+", "^[a-z]+$", ".+"},
|
|
expected: []bool{true, false, true, false},
|
|
},
|
|
{
|
|
name: "regex not match",
|
|
op: types.BinaryOpNotMatchRe,
|
|
lhs: []string{"hello123", "test456", "abc", ""},
|
|
rhs: []string{"^hello\\d+$", "^\\d+", "^[a-z]+$", ".+"},
|
|
expected: []bool{false, true, false, true},
|
|
},
|
|
{
|
|
name: "case sensitive substring matching",
|
|
op: types.BinaryOpMatchSubstr,
|
|
lhs: []string{"Hello World", "TEST", "CaseSensitive"},
|
|
rhs: []string{"hello", "test", "Case"},
|
|
expected: []bool{false, false, true},
|
|
},
|
|
{
|
|
name: "special characters in contains",
|
|
op: types.BinaryOpMatchSubstr,
|
|
lhs: []string{"hello@world.com", "test[123]", "foo.bar", ""},
|
|
rhs: []string{"@world", "[123]", ".", ""},
|
|
expected: []bool{true, true, true, true},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
lhsArray := createStringArray(tt.lhs, nil)
|
|
rhsArray := createStringArray(tt.rhs, nil)
|
|
|
|
fn, err := binaryFunctions.GetForSignature(tt.op, arrow.BinaryTypes.String)
|
|
require.NoError(t, err)
|
|
|
|
result, err := fn.Evaluate(lhsArray, rhsArray, false, false)
|
|
require.NoError(t, err)
|
|
|
|
actual, _ := extractBoolValues(result)
|
|
assert.Equal(t, tt.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCompileRegexMatchFunctions(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
op types.BinaryOp
|
|
lhs []string
|
|
rhs []string
|
|
expected []bool
|
|
}{
|
|
{
|
|
name: "regex match", // |~ "^\w+\d+$"
|
|
op: types.BinaryOpMatchRe,
|
|
lhs: []string{"foo123", "foo", "bar456", "bar"},
|
|
rhs: []string{"^\\w+\\d+$", "^\\w+\\d+$", "^\\w+\\d+$", "^\\w+\\d+$"},
|
|
expected: []bool{true, false, true, false},
|
|
},
|
|
{
|
|
name: "regex not match", // !~ "^\w+\d+$"
|
|
op: types.BinaryOpNotMatchRe,
|
|
lhs: []string{"foo123", "foo", "bar456", "bar"},
|
|
rhs: []string{"^\\w+\\d+$", "^\\w+\\d+$", "^\\w+\\d+$", "^\\w+\\d+$"},
|
|
expected: []bool{false, true, false, true},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
lhsArray := createStringArray(tt.lhs, nil)
|
|
rhsArray := createStringArray(tt.rhs, nil)
|
|
|
|
fn, err := binaryFunctions.GetForSignature(tt.op, arrow.BinaryTypes.String)
|
|
require.NoError(t, err)
|
|
|
|
result, err := fn.Evaluate(lhsArray, rhsArray, false, true)
|
|
require.NoError(t, err)
|
|
|
|
actual, _ := extractBoolValues(result)
|
|
assert.Equal(t, tt.expected, actual)
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
func TestNullValueHandling(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
op types.BinaryOp
|
|
dataType arrow.DataType
|
|
setup func() (arrow.Array, arrow.Array)
|
|
expected []bool
|
|
}{
|
|
{
|
|
name: "boolean with nulls",
|
|
op: types.BinaryOpEq,
|
|
dataType: arrow.FixedWidthTypes.Boolean,
|
|
setup: func() (arrow.Array, arrow.Array) {
|
|
lhs := createBoolArray([]bool{true, false, true}, []bool{false, true, false})
|
|
rhs := createBoolArray([]bool{true, false, false}, []bool{true, false, false})
|
|
return lhs, rhs
|
|
},
|
|
expected: []bool{false, false, false}, // nulls should result in false
|
|
},
|
|
{
|
|
name: "string with nulls",
|
|
op: types.BinaryOpEq,
|
|
dataType: arrow.BinaryTypes.String,
|
|
setup: func() (arrow.Array, arrow.Array) {
|
|
lhs := createStringArray([]string{"hello", "world", "test"}, []bool{false, true, false})
|
|
rhs := createStringArray([]string{"hello", "world", "different"}, []bool{true, false, false})
|
|
return lhs, rhs
|
|
},
|
|
expected: []bool{false, false, false}, // nulls should result in false
|
|
},
|
|
{
|
|
name: "int64 with nulls",
|
|
op: types.BinaryOpGt,
|
|
dataType: arrow.PrimitiveTypes.Int64,
|
|
setup: func() (arrow.Array, arrow.Array) {
|
|
lhs := createInt64Array([]int64{5, 10, 15}, []bool{false, true, false})
|
|
rhs := createInt64Array([]int64{3, 8, 20}, []bool{true, false, false})
|
|
return lhs, rhs
|
|
},
|
|
expected: []bool{false, false, false}, // nulls should result in false
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
lhs, rhs := tt.setup()
|
|
|
|
fn, err := binaryFunctions.GetForSignature(tt.op, tt.dataType)
|
|
require.NoError(t, err)
|
|
|
|
result, err := fn.Evaluate(lhs, rhs, false, false)
|
|
require.NoError(t, err)
|
|
|
|
actual, _ := extractBoolValues(result)
|
|
assert.Equal(t, tt.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestArrayLengthMismatch(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
op types.BinaryOp
|
|
dataType arrow.DataType
|
|
setup func() (arrow.Array, arrow.Array)
|
|
}{
|
|
{
|
|
name: "boolean length mismatch",
|
|
op: types.BinaryOpEq,
|
|
dataType: arrow.FixedWidthTypes.Boolean,
|
|
setup: func() (arrow.Array, arrow.Array) {
|
|
lhs := createBoolArray([]bool{true, false}, nil)
|
|
rhs := createBoolArray([]bool{true, false, true}, nil)
|
|
return lhs, rhs
|
|
},
|
|
},
|
|
{
|
|
name: "string length mismatch",
|
|
op: types.BinaryOpEq,
|
|
dataType: arrow.BinaryTypes.String,
|
|
setup: func() (arrow.Array, arrow.Array) {
|
|
lhs := createStringArray([]string{"hello"}, nil)
|
|
rhs := createStringArray([]string{"hello", "world"}, nil)
|
|
return lhs, rhs
|
|
},
|
|
},
|
|
{
|
|
name: "int64 length mismatch",
|
|
op: types.BinaryOpGt,
|
|
dataType: arrow.PrimitiveTypes.Int64,
|
|
setup: func() (arrow.Array, arrow.Array) {
|
|
lhs := createInt64Array([]int64{1, 2, 3}, nil)
|
|
rhs := createInt64Array([]int64{1, 2}, nil)
|
|
return lhs, rhs
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
lhs, rhs := tt.setup()
|
|
|
|
fn, err := binaryFunctions.GetForSignature(tt.op, tt.dataType)
|
|
require.NoError(t, err)
|
|
|
|
result, err := fn.Evaluate(lhs, rhs, false, false)
|
|
assert.Error(t, err)
|
|
assert.Nil(t, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRegexCompileError(t *testing.T) {
|
|
// Test with invalid regex patterns
|
|
lhs := createStringArray([]string{"hello", "world"}, nil)
|
|
rhs := createStringArray([]string{"[", "("}, nil) // Invalid regex patterns
|
|
|
|
fn, err := binaryFunctions.GetForSignature(types.BinaryOpMatchRe, arrow.BinaryTypes.String)
|
|
require.NoError(t, err)
|
|
|
|
_, err = fn.Evaluate(lhs, rhs, false, false)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestBoolToIntConversion(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input bool
|
|
expected int
|
|
}{
|
|
{
|
|
name: "true converts to 1",
|
|
input: true,
|
|
expected: 1,
|
|
},
|
|
{
|
|
name: "false converts to 0",
|
|
input: false,
|
|
expected: 0,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := boolToInt(tt.input)
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEmptyArrays(t *testing.T) {
|
|
// Test with empty arrays
|
|
lhs := createStringArray([]string{}, nil)
|
|
rhs := createStringArray([]string{}, nil)
|
|
|
|
fn, err := binaryFunctions.GetForSignature(types.BinaryOpEq, arrow.BinaryTypes.String)
|
|
require.NoError(t, err)
|
|
|
|
result, err := fn.Evaluate(lhs, rhs, false, false)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, int(0), result.Len())
|
|
}
|
|
|
|
func TestUnaryNot(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input []bool
|
|
nulls []bool
|
|
expected []bool
|
|
}{
|
|
{
|
|
name: "NOT operation",
|
|
input: []bool{true, false, true, false},
|
|
nulls: nil,
|
|
expected: []bool{false, true, false, true},
|
|
},
|
|
{
|
|
name: "NOT with nulls",
|
|
input: []bool{true, false, true},
|
|
nulls: []bool{false, true, false},
|
|
expected: []bool{false, false, false}, // nulls result in false in extractBoolValues
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
inputArray := createBoolArray(tt.input, tt.nulls)
|
|
|
|
fn, err := unaryFunctions.GetForSignature(types.UnaryOpNot, arrow.FixedWidthTypes.Boolean)
|
|
require.NoError(t, err)
|
|
|
|
result, err := fn.Evaluate(inputArray)
|
|
require.NoError(t, err)
|
|
|
|
actual, nulls := extractBoolValues(result)
|
|
assert.Equal(t, tt.expected, actual)
|
|
if tt.nulls != nil {
|
|
assert.Equal(t, tt.nulls, nulls)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|