blob: cd93a600edd793466d1c37f144a20f0c3212f37d [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) 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 iceberg_test
import (
"math"
"strings"
"testing"
"github.com/apache/arrow-go/v18/arrow/decimal128"
"github.com/apache/iceberg-go"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type ExampleVisitor struct {
visitHistory []string
}
func (e *ExampleVisitor) VisitTrue() []string {
e.visitHistory = append(e.visitHistory, "TRUE")
return e.visitHistory
}
func (e *ExampleVisitor) VisitFalse() []string {
e.visitHistory = append(e.visitHistory, "FALSE")
return e.visitHistory
}
func (e *ExampleVisitor) VisitNot([]string) []string {
e.visitHistory = append(e.visitHistory, "NOT")
return e.visitHistory
}
func (e *ExampleVisitor) VisitAnd(_, _ []string) []string {
e.visitHistory = append(e.visitHistory, "AND")
return e.visitHistory
}
func (e *ExampleVisitor) VisitOr(_, _ []string) []string {
e.visitHistory = append(e.visitHistory, "OR")
return e.visitHistory
}
func (e *ExampleVisitor) VisitUnbound(pred iceberg.UnboundPredicate) []string {
e.visitHistory = append(e.visitHistory, strings.ToUpper(pred.Op().String()))
return e.visitHistory
}
func (e *ExampleVisitor) VisitBound(pred iceberg.BoundPredicate) []string {
e.visitHistory = append(e.visitHistory, strings.ToUpper(pred.Op().String()))
return e.visitHistory
}
type FooBoundExprVisitor struct {
ExampleVisitor
}
func (e *FooBoundExprVisitor) VisitBound(pred iceberg.BoundPredicate) []string {
return iceberg.VisitBoundPredicate(pred, e)
}
func (e *FooBoundExprVisitor) VisitUnbound(pred iceberg.UnboundPredicate) []string {
panic("found unbound predicate when evaluating")
}
func (e *FooBoundExprVisitor) VisitIn(iceberg.BoundTerm, iceberg.Set[iceberg.Literal]) []string {
e.visitHistory = append(e.visitHistory, "IN")
return e.visitHistory
}
func (e *FooBoundExprVisitor) VisitNotIn(iceberg.BoundTerm, iceberg.Set[iceberg.Literal]) []string {
e.visitHistory = append(e.visitHistory, "NOT_IN")
return e.visitHistory
}
func (e *FooBoundExprVisitor) VisitIsNan(iceberg.BoundTerm) []string {
e.visitHistory = append(e.visitHistory, "IS_NAN")
return e.visitHistory
}
func (e *FooBoundExprVisitor) VisitNotNan(iceberg.BoundTerm) []string {
e.visitHistory = append(e.visitHistory, "NOT_NAN")
return e.visitHistory
}
func (e *FooBoundExprVisitor) VisitIsNull(iceberg.BoundTerm) []string {
e.visitHistory = append(e.visitHistory, "IS_NULL")
return e.visitHistory
}
func (e *FooBoundExprVisitor) VisitNotNull(iceberg.BoundTerm) []string {
e.visitHistory = append(e.visitHistory, "NOT_NULL")
return e.visitHistory
}
func (e *FooBoundExprVisitor) VisitEqual(iceberg.BoundTerm, iceberg.Literal) []string {
e.visitHistory = append(e.visitHistory, "EQUAL")
return e.visitHistory
}
func (e *FooBoundExprVisitor) VisitNotEqual(iceberg.BoundTerm, iceberg.Literal) []string {
e.visitHistory = append(e.visitHistory, "NOT_EQUAL")
return e.visitHistory
}
func (e *FooBoundExprVisitor) VisitGreaterEqual(iceberg.BoundTerm, iceberg.Literal) []string {
e.visitHistory = append(e.visitHistory, "GREATER_THAN_OR_EQUAL")
return e.visitHistory
}
func (e *FooBoundExprVisitor) VisitGreater(iceberg.BoundTerm, iceberg.Literal) []string {
e.visitHistory = append(e.visitHistory, "GREATER_THAN")
return e.visitHistory
}
func (e *FooBoundExprVisitor) VisitLessEqual(iceberg.BoundTerm, iceberg.Literal) []string {
e.visitHistory = append(e.visitHistory, "LESS_THAN_OR_EQUAL")
return e.visitHistory
}
func (e *FooBoundExprVisitor) VisitLess(iceberg.BoundTerm, iceberg.Literal) []string {
e.visitHistory = append(e.visitHistory, "LESS_THAN")
return e.visitHistory
}
func (e *FooBoundExprVisitor) VisitStartsWith(iceberg.BoundTerm, iceberg.Literal) []string {
e.visitHistory = append(e.visitHistory, "STARTS_WITH")
return e.visitHistory
}
func (e *FooBoundExprVisitor) VisitNotStartsWith(iceberg.BoundTerm, iceberg.Literal) []string {
e.visitHistory = append(e.visitHistory, "NOT_STARTS_WITH")
return e.visitHistory
}
func TestBooleanExprVisitor(t *testing.T) {
expr := iceberg.NewAnd(
iceberg.NewOr(
iceberg.NewNot(iceberg.EqualTo(iceberg.Reference("a"), int32(1))),
iceberg.NewNot(iceberg.NotEqualTo(iceberg.Reference("b"), int32(0))),
iceberg.EqualTo(iceberg.Reference("a"), int32(1)),
iceberg.NotEqualTo(iceberg.Reference("b"), int32(0)),
),
iceberg.NewNot(iceberg.EqualTo(iceberg.Reference("a"), int32(1))),
iceberg.NotEqualTo(iceberg.Reference("b"), int32(0)))
visitor := ExampleVisitor{visitHistory: make([]string, 0)}
result, err := iceberg.VisitExpr(expr, &visitor)
require.NoError(t, err)
assert.Equal(t, []string{
"EQUAL",
"NOT",
"NOTEQUAL",
"NOT",
"OR",
"EQUAL",
"OR",
"NOTEQUAL",
"OR",
"EQUAL",
"NOT",
"AND",
"NOTEQUAL",
"AND",
}, result)
}
func TestBindVisitorAlready(t *testing.T) {
bound, err := iceberg.EqualTo(iceberg.Reference("foo"), "hello").
Bind(tableSchemaSimple, false)
require.NoError(t, err)
_, err = iceberg.BindExpr(tableSchemaSimple, bound, true)
assert.ErrorIs(t, err, iceberg.ErrInvalidArgument)
assert.ErrorContains(t, err, "found already bound predicate: BoundEqual(term=BoundReference(field=1: foo: optional string, accessor=Accessor(position=0, inner=<nil>)), literal=hello)")
}
func TestAlwaysExprBinding(t *testing.T) {
tests := []struct {
expr iceberg.BooleanExpression
expected iceberg.BooleanExpression
}{
{iceberg.AlwaysTrue{}, iceberg.AlwaysTrue{}},
{iceberg.AlwaysFalse{}, iceberg.AlwaysFalse{}},
{iceberg.NewAnd(iceberg.AlwaysTrue{}, iceberg.AlwaysFalse{}), iceberg.AlwaysFalse{}},
{iceberg.NewOr(iceberg.AlwaysTrue{}, iceberg.AlwaysFalse{}), iceberg.AlwaysTrue{}},
}
for _, tt := range tests {
t.Run(tt.expr.String(), func(t *testing.T) {
bound, err := iceberg.BindExpr(tableSchemaSimple, tt.expr, true)
require.NoError(t, err)
assert.Equal(t, tt.expected, bound)
})
}
}
func TestBoundBoolExprVisitor(t *testing.T) {
tests := []struct {
expr iceberg.BooleanExpression
expected []string
}{
{iceberg.NewAnd(iceberg.IsIn(iceberg.Reference("foo"), "foo", "bar"),
iceberg.IsIn(iceberg.Reference("bar"), int32(1), int32(2))), []string{"IN", "IN", "AND"}},
{iceberg.NewOr(iceberg.NewNot(iceberg.IsIn(iceberg.Reference("foo"), "foo", "bar")),
iceberg.NewNot(iceberg.IsIn(iceberg.Reference("bar"), int32(1), int32(2)))),
[]string{"IN", "NOT", "IN", "NOT", "OR"}},
{iceberg.EqualTo(iceberg.Reference("bar"), int32(1)), []string{"EQUAL"}},
{iceberg.NotEqualTo(iceberg.Reference("foo"), "foo"), []string{"NOT_EQUAL"}},
{iceberg.AlwaysTrue{}, []string{"TRUE"}},
{iceberg.AlwaysFalse{}, []string{"FALSE"}},
{iceberg.NotIn(iceberg.Reference("foo"), "bar", "foo"), []string{"NOT_IN"}},
{iceberg.IsNull(iceberg.Reference("foo")), []string{"IS_NULL"}},
{iceberg.NotNull(iceberg.Reference("foo")), []string{"NOT_NULL"}},
{iceberg.GreaterThan(iceberg.Reference("foo"), "foo"), []string{"GREATER_THAN"}},
{iceberg.GreaterThanEqual(iceberg.Reference("foo"), "foo"), []string{"GREATER_THAN_OR_EQUAL"}},
{iceberg.LessThan(iceberg.Reference("foo"), "foo"), []string{"LESS_THAN"}},
{iceberg.LessThanEqual(iceberg.Reference("foo"), "foo"), []string{"LESS_THAN_OR_EQUAL"}},
{iceberg.StartsWith(iceberg.Reference("foo"), "foo"), []string{"STARTS_WITH"}},
{iceberg.NotStartsWith(iceberg.Reference("foo"), "foo"), []string{"NOT_STARTS_WITH"}},
}
for _, tt := range tests {
t.Run(tt.expr.String(), func(t *testing.T) {
bound, err := iceberg.BindExpr(tableSchemaNested,
tt.expr,
true)
require.NoError(t, err)
visitor := FooBoundExprVisitor{ExampleVisitor: ExampleVisitor{visitHistory: []string{}}}
result, err := iceberg.VisitExpr(bound, &visitor)
require.NoError(t, err)
assert.Equal(t, tt.expected, result)
})
}
}
type rowTester []any
func (r rowTester) Size() int { return len(r) }
func (r rowTester) Get(pos int) any { return r[pos] }
func (r rowTester) Set(pos int, val any) {
r[pos] = val
}
func rowOf(vals ...any) rowTester {
return rowTester(vals)
}
var testSchema = iceberg.NewSchema(1,
iceberg.NestedField{ID: 13, Name: "x",
Type: iceberg.PrimitiveTypes.Int32, Required: true},
iceberg.NestedField{ID: 14, Name: "y",
Type: iceberg.PrimitiveTypes.Float64, Required: true},
iceberg.NestedField{ID: 15, Name: "z",
Type: iceberg.PrimitiveTypes.Int32},
iceberg.NestedField{ID: 16, Name: "s1",
Type: &iceberg.StructType{
FieldList: []iceberg.NestedField{{
ID: 17, Name: "s2", Required: true,
Type: &iceberg.StructType{
FieldList: []iceberg.NestedField{{
ID: 18, Name: "s3", Required: true,
Type: &iceberg.StructType{
FieldList: []iceberg.NestedField{{
ID: 19, Name: "s4", Required: true,
Type: &iceberg.StructType{
FieldList: []iceberg.NestedField{{
ID: 20, Name: "i", Required: true,
Type: iceberg.PrimitiveTypes.Int32,
}},
},
}},
},
}},
},
}},
}},
iceberg.NestedField{ID: 21, Name: "s5", Type: &iceberg.StructType{
FieldList: []iceberg.NestedField{{
ID: 22, Name: "s6", Required: true, Type: &iceberg.StructType{
FieldList: []iceberg.NestedField{{
ID: 23, Name: "f", Required: true, Type: iceberg.PrimitiveTypes.Float32,
}},
},
}},
}},
iceberg.NestedField{ID: 24, Name: "s", Type: iceberg.PrimitiveTypes.String})
func TestExprEvaluator(t *testing.T) {
type testCase struct {
str string
row rowTester
result bool
}
tests := []struct {
exp iceberg.BooleanExpression
cases []testCase
}{
{iceberg.AlwaysTrue{}, []testCase{{"always true", rowOf(), true}}},
{iceberg.AlwaysFalse{}, []testCase{{"always false", rowOf(), false}}},
{iceberg.LessThan(iceberg.Reference("x"), int32(7)), []testCase{
{"7 < 7 => false", rowOf(7, 8, nil, nil), false},
{"6 < 7 => true", rowOf(6, 8, nil, nil), true},
}},
{iceberg.LessThan(iceberg.Reference("s1.s2.s3.s4.i"), int32(7)), []testCase{
{"7 < 7 => false", rowOf(7, 8, nil, rowOf(rowOf(rowOf(rowOf(7))))), false},
{"6 < 7 => true", rowOf(7, 8, nil, rowOf(rowOf(rowOf(rowOf(6))))), true},
{"nil < 7 => true", rowOf(7, 8, nil, nil), true},
}},
{iceberg.LessThanEqual(iceberg.Reference("x"), int32(7)), []testCase{
{"7 <= 7 => true", rowOf(7, 8, nil), true},
{"6 <= 7 => true", rowOf(6, 8, nil), true},
{"8 <= 7 => false", rowOf(8, 8, nil), false},
}},
{iceberg.LessThanEqual(iceberg.Reference("s1.s2.s3.s4.i"), int32(7)), []testCase{
{"7 <= 7 => true", rowOf(7, 8, nil, rowOf(rowOf(rowOf(rowOf(7))))), true},
{"6 <= 7 => true", rowOf(7, 8, nil, rowOf(rowOf(rowOf(rowOf(6))))), true},
{"8 <= 7 => false", rowOf(7, 8, nil, rowOf(rowOf(rowOf(rowOf(8))))), false},
}},
{iceberg.GreaterThan(iceberg.Reference("x"), int32(7)), []testCase{
{"7 > 7 => false", rowOf(7, 8, nil), false},
{"6 > 7 => false", rowOf(6, 8, nil), false},
{"8 > 7 => true", rowOf(8, 8, nil), true},
}},
{iceberg.GreaterThan(iceberg.Reference("s1.s2.s3.s4.i"), int32(7)), []testCase{
{"7 > 7 => false", rowOf(7, 8, nil, rowOf(rowOf(rowOf(rowOf(7))))), false},
{"6 > 7 => false", rowOf(7, 8, nil, rowOf(rowOf(rowOf(rowOf(6))))), false},
{"8 > 7 => true", rowOf(7, 8, nil, rowOf(rowOf(rowOf(rowOf(8))))), true},
}},
{iceberg.GreaterThanEqual(iceberg.Reference("x"), int32(7)), []testCase{
{"7 >= 7 => true", rowOf(7, 8, nil), true},
{"6 >= 7 => false", rowOf(6, 8, nil), false},
{"8 >= 7 => true", rowOf(8, 8, nil), true},
}},
{iceberg.GreaterThanEqual(iceberg.Reference("s1.s2.s3.s4.i"), int32(7)), []testCase{
{"7 >= 7 => true", rowOf(7, 8, nil, rowOf(rowOf(rowOf(rowOf(7))))), true},
{"6 >= 7 => false", rowOf(7, 8, nil, rowOf(rowOf(rowOf(rowOf(6))))), false},
{"8 >= 7 => true", rowOf(7, 8, nil, rowOf(rowOf(rowOf(rowOf(8))))), true},
}},
{iceberg.EqualTo(iceberg.Reference("x"), int32(7)), []testCase{
{"7 == 7 => true", rowOf(7, 8, nil), true},
{"6 == 7 => false", rowOf(6, 8, nil), false},
}},
{iceberg.EqualTo(iceberg.Reference("s1.s2.s3.s4.i"), int32(7)), []testCase{
{"7 == 7 => true", rowOf(7, 8, nil, rowOf(rowOf(rowOf(rowOf(7))))), true},
{"6 == 7 => false", rowOf(7, 8, nil, rowOf(rowOf(rowOf(rowOf(6))))), false},
}},
{iceberg.NotEqualTo(iceberg.Reference("x"), int32(7)), []testCase{
{"7 != 7 => false", rowOf(7, 8, nil), false},
{"6 != 7 => true", rowOf(6, 8, nil), true},
}},
{iceberg.NotEqualTo(iceberg.Reference("s1.s2.s3.s4.i"), int32(7)), []testCase{
{"7 != 7 => false", rowOf(7, 8, nil, rowOf(rowOf(rowOf(rowOf(7))))), false},
{"6 != 7 => true", rowOf(7, 8, nil, rowOf(rowOf(rowOf(rowOf(6))))), true},
}},
{iceberg.IsNull(iceberg.Reference("z")), []testCase{
{"nil is null", rowOf(1, 2, nil), true},
{"3 is not null", rowOf(1, 2, 3), false},
}},
{iceberg.IsNull(iceberg.Reference("s1.s2.s3.s4.i")), []testCase{
{"3 is not null", rowOf(1, 2, 3, rowOf(rowOf(rowOf(rowOf(3))))), false},
}},
{iceberg.NotNull(iceberg.Reference("z")), []testCase{
{"nil is null", rowOf(1, 2, nil), false},
{"3 is not null", rowOf(1, 2, 3), true},
}},
{iceberg.NotNull(iceberg.Reference("s1.s2.s3.s4.i")), []testCase{
{"3 is not null", rowOf(1, 2, 3, rowOf(rowOf(rowOf(rowOf(3))))), true},
}},
{iceberg.IsNaN(iceberg.Reference("y")), []testCase{
{"NaN is NaN", rowOf(1, math.NaN(), 3), true},
{"2 is not NaN", rowOf(1, 2.0, 3), false},
}},
{iceberg.IsNaN(iceberg.Reference("s5.s6.f")), []testCase{
{"NaN is NaN", rowOf(1, 2, 3, nil, rowOf(rowOf(math.NaN()))), true},
{"4 is not NaN", rowOf(1, 2, 3, nil, rowOf(rowOf(4.0))), false},
{"nil is not NaN", rowOf(1, 2, 3, nil, nil), false},
}},
{iceberg.NotNaN(iceberg.Reference("y")), []testCase{
{"NaN is NaN", rowOf(1, math.NaN(), 3), false},
{"2 is not NaN", rowOf(1, 2.0, 3), true},
}},
{iceberg.NotNaN(iceberg.Reference("s5.s6.f")), []testCase{
{"NaN is NaN", rowOf(1, 2, 3, nil, rowOf(rowOf(math.NaN()))), false},
{"4 is not NaN", rowOf(1, 2, 3, nil, rowOf(rowOf(4.0))), true},
}},
{iceberg.NewAnd(iceberg.EqualTo(iceberg.Reference("x"), int32(7)), iceberg.NotNull(iceberg.Reference("z"))), []testCase{
{"7, 3 => true", rowOf(7, 0, 3), true},
{"8, 3 => false", rowOf(8, 0, 3), false},
{"7, null => false", rowOf(7, 0, nil), false},
{"8, null => false", rowOf(8, 0, nil), false},
}},
{iceberg.NewAnd(iceberg.EqualTo(iceberg.Reference("s1.s2.s3.s4.i"), int32(7)),
iceberg.NotNull(iceberg.Reference("s1.s2.s3.s4.i"))), []testCase{
{"7, 7 => true", rowOf(5, 0, 3, rowOf(rowOf(rowOf(rowOf(7))))), true},
{"8, 8 => false", rowOf(7, 0, 3, rowOf(rowOf(rowOf(rowOf(8))))), false},
{"7, null => false", rowOf(5, 0, 3, nil), false},
{"8, notnull => false", rowOf(7, 0, 3, rowOf(rowOf(rowOf(rowOf(8))))), false},
}},
{iceberg.NewOr(iceberg.EqualTo(iceberg.Reference("x"), int32(7)), iceberg.NotNull(iceberg.Reference("z"))), []testCase{
{"7, 3 => true", rowOf(7, 0, 3), true},
{"8, 3 => true", rowOf(8, 0, 3), true},
{"7, null => true", rowOf(7, 0, nil), true},
{"8, null => false", rowOf(8, 0, nil), false},
}},
{iceberg.NewOr(iceberg.EqualTo(iceberg.Reference("s1.s2.s3.s4.i"), int32(7)),
iceberg.NotNull(iceberg.Reference("s1.s2.s3.s4.i"))), []testCase{
{"7, 7 => true", rowOf(5, 0, 3, rowOf(rowOf(rowOf(rowOf(7))))), true},
{"8, notnull => true", rowOf(7, 0, 3, rowOf(rowOf(rowOf(rowOf(8))))), true},
{"7, null => false", rowOf(5, 0, 3, nil), false},
{"8, notnull => true", rowOf(7, 0, 3, rowOf(rowOf(rowOf(rowOf(8))))), true},
}},
{iceberg.NewNot(iceberg.EqualTo(iceberg.Reference("x"), int32(7))), []testCase{
{"not(7 == 7) => false", rowOf(7), false},
{"not(8 == 7) => true", rowOf(8), true},
}},
{iceberg.NewNot(iceberg.EqualTo(iceberg.Reference("s1.s2.s3.s4.i"), int32(7))), []testCase{
{"not(7 == 7) => false", rowOf(7, nil, nil, rowOf(rowOf(rowOf(rowOf(7))))), false},
{"not(8 == 7) => true", rowOf(7, nil, nil, rowOf(rowOf(rowOf(rowOf(8))))), true},
}},
{iceberg.IsIn(iceberg.Reference("x"), int64(7), 8, math.MaxInt64), []testCase{
{"7 in [7, 8, Int64Max] => true", rowOf(7, 8, nil), true},
{"9 in [7, 8, Int64Max] => false", rowOf(9, 8, nil), false},
{"8 in [7, 8, Int64Max] => true", rowOf(8, 8, nil), true},
}},
{iceberg.IsIn(iceberg.Reference("x"), int64(math.MaxInt64), math.MaxInt32, math.MinInt64), []testCase{
{"Int32Max in [Int64Max, Int32Max, Int64Min] => true", rowOf(math.MaxInt32, 7.0, nil), true},
{"6 in [Int64Max, Int32Max, Int64Min] => false", rowOf(6, 6.9, nil), false},
}},
{iceberg.IsIn(iceberg.Reference("y"), float64(7), 8, 9.1), []testCase{
{"7.0 in [7, 8, 9.1] => true", rowOf(0, 7.0, nil), true},
{"9.1 in [7, 8, 9.1] => true", rowOf(7, 9.1, nil), true},
{"6.8 in [7, 8, 9.1] => false", rowOf(7, 6.8, nil), false},
}},
{iceberg.IsIn(iceberg.Reference("s1.s2.s3.s4.i"), int32(7), 8, 9), []testCase{
{"7 in [7, 8, 9] => true", rowOf(7, 8, nil, rowOf(rowOf(rowOf(rowOf(7))))), true},
{"6 in [7, 8, 9] => true", rowOf(7, 8, nil, rowOf(rowOf(rowOf(rowOf(6))))), false},
{"nil in [7, 8, 9] => false", rowOf(7, 8, nil, nil), false},
}},
{iceberg.NotIn(iceberg.Reference("x"), int64(7), 8, math.MaxInt64), []testCase{
{"7 not in [7, 8, Int64Max] => false", rowOf(7, 8, nil), false},
{"9 not in [7, 8, Int64Max] => true", rowOf(9, 8, nil), true},
{"8 not in [7, 8, Int64Max] => false", rowOf(8, 8, nil), false},
}},
{iceberg.NotIn(iceberg.Reference("x"), int64(math.MaxInt64), math.MaxInt32, math.MinInt64), []testCase{
{"Int32Max not in [Int64Max, Int32Max, Int64Min] => false", rowOf(math.MaxInt32, 7.0, nil), false},
{"6 not in [Int64Max, Int32Max, Int64Min] => true", rowOf(6, 6.9, nil), true},
}},
{iceberg.NotIn(iceberg.Reference("y"), float64(7), 8, 9.1), []testCase{
{"7.0 not in [7, 8, 9.1] => false", rowOf(0, 7.0, nil), false},
{"9.1 not in [7, 8, 9.1] => false", rowOf(7, 9.1, nil), false},
{"6.8 not in [7, 8, 9.1] => true", rowOf(7, 6.8, nil), true},
}},
{iceberg.NotIn(iceberg.Reference("s1.s2.s3.s4.i"), int32(7), 8, 9), []testCase{
{"7 not in [7, 8, 9] => false", rowOf(7, 8, nil, rowOf(rowOf(rowOf(rowOf(7))))), false},
{"6 not in [7, 8, 9] => false", rowOf(7, 8, nil, rowOf(rowOf(rowOf(rowOf(6))))), true},
}},
{iceberg.EqualTo(iceberg.Reference("s"), "abc"), []testCase{
{"abc == abc => true", rowOf(1, 2, nil, nil, nil, "abc"), true},
{"abd == abc => false", rowOf(1, 2, nil, nil, nil, "abd"), false},
}},
{iceberg.StartsWith(iceberg.Reference("s"), "abc"), []testCase{
{"abc startsWith abc => true", rowOf(1, 2, nil, nil, nil, "abc"), true},
{"xabc startsWith abc => false", rowOf(1, 2, nil, nil, nil, "xabc"), false},
{"Abc startsWith abc => false", rowOf(1, 2, nil, nil, nil, "Abc"), false},
{"a startsWith abc => false", rowOf(1, 2, nil, nil, nil, "a"), false},
{"abcd startsWith abc => true", rowOf(1, 2, nil, nil, nil, "abcd"), true},
{"nil startsWith abc => false", rowOf(1, 2, nil, nil, nil, nil), false},
}},
{iceberg.NotStartsWith(iceberg.Reference("s"), "abc"), []testCase{
{"abc not startsWith abc => false", rowOf(1, 2, nil, nil, nil, "abc"), false},
{"xabc not startsWith abc => true", rowOf(1, 2, nil, nil, nil, "xabc"), true},
{"Abc not startsWith abc => true", rowOf(1, 2, nil, nil, nil, "Abc"), true},
{"a not startsWith abc => true", rowOf(1, 2, nil, nil, nil, "a"), true},
{"abcd not startsWith abc => false", rowOf(1, 2, nil, nil, nil, "abcd"), false},
{"nil not startsWith abc => true", rowOf(1, 2, nil, nil, nil, nil), true},
}},
}
for _, tt := range tests {
t.Run(tt.exp.String(), func(t *testing.T) {
ev, err := iceberg.ExpressionEvaluator(testSchema, tt.exp, true)
require.NoError(t, err)
for _, c := range tt.cases {
res, err := ev(c.row)
require.NoError(t, err)
assert.Equal(t, c.result, res, c.str)
}
})
}
}
func TestEvaluatorCmpTypes(t *testing.T) {
sc := iceberg.NewSchema(1,
iceberg.NestedField{ID: 1, Name: "a", Type: iceberg.PrimitiveTypes.Bool},
iceberg.NestedField{ID: 2, Name: "b", Type: iceberg.PrimitiveTypes.Int32},
iceberg.NestedField{ID: 3, Name: "c", Type: iceberg.PrimitiveTypes.Int64},
iceberg.NestedField{ID: 4, Name: "d", Type: iceberg.PrimitiveTypes.Float32},
iceberg.NestedField{ID: 5, Name: "e", Type: iceberg.PrimitiveTypes.Float64},
iceberg.NestedField{ID: 6, Name: "f", Type: iceberg.PrimitiveTypes.Date},
iceberg.NestedField{ID: 7, Name: "g", Type: iceberg.PrimitiveTypes.Time},
iceberg.NestedField{ID: 8, Name: "h", Type: iceberg.PrimitiveTypes.Timestamp},
iceberg.NestedField{ID: 9, Name: "i", Type: iceberg.DecimalTypeOf(9, 2)},
iceberg.NestedField{ID: 10, Name: "j", Type: iceberg.PrimitiveTypes.String},
iceberg.NestedField{ID: 11, Name: "k", Type: iceberg.PrimitiveTypes.Binary},
iceberg.NestedField{ID: 12, Name: "l", Type: iceberg.PrimitiveTypes.UUID},
iceberg.NestedField{ID: 13, Name: "m", Type: iceberg.FixedTypeOf(5)})
rowData := rowOf(true,
5, 5, float32(5.0), float64(5.0),
29, 51661919000, 1503066061919234,
iceberg.Decimal{Scale: 2, Val: decimal128.FromI64(3456)},
"abcdef", []byte{0x01, 0x02, 0x03},
uuid.New(), []byte{0xDE, 0xAD, 0xBE, 0xEF, 0x0})
tests := []struct {
ref iceberg.BooleanExpression
exp bool
}{
{iceberg.EqualTo(iceberg.Reference("a"), true), true},
{iceberg.EqualTo(iceberg.Reference("a"), false), false},
{iceberg.EqualTo(iceberg.Reference("c"), int64(5)), true},
{iceberg.EqualTo(iceberg.Reference("c"), int64(6)), false},
{iceberg.EqualTo(iceberg.Reference("d"), int64(5)), true},
{iceberg.EqualTo(iceberg.Reference("d"), int64(6)), false},
{iceberg.EqualTo(iceberg.Reference("e"), int64(5)), true},
{iceberg.EqualTo(iceberg.Reference("e"), int64(6)), false},
{iceberg.EqualTo(iceberg.Reference("f"), "1970-01-30"), true},
{iceberg.EqualTo(iceberg.Reference("f"), "1970-01-31"), false},
{iceberg.EqualTo(iceberg.Reference("g"), "14:21:01.919"), true},
{iceberg.EqualTo(iceberg.Reference("g"), "14:21:02.919"), false},
{iceberg.EqualTo(iceberg.Reference("h"), "2017-08-18T14:21:01.919234"), true},
{iceberg.EqualTo(iceberg.Reference("h"), "2017-08-19T14:21:01.919234"), false},
{iceberg.LessThan(iceberg.Reference("i"), "32.22"), false},
{iceberg.GreaterThan(iceberg.Reference("i"), "32.22"), true},
{iceberg.LessThanEqual(iceberg.Reference("j"), "abcd"), false},
{iceberg.GreaterThan(iceberg.Reference("j"), "abcde"), true},
{iceberg.GreaterThan(iceberg.Reference("k"), []byte{0x00}), true},
{iceberg.LessThan(iceberg.Reference("k"), []byte{0x00}), false},
{iceberg.EqualTo(iceberg.Reference("l"), uuid.New().String()), false},
{iceberg.EqualTo(iceberg.Reference("l"), rowData[11].(uuid.UUID)), true},
{iceberg.EqualTo(iceberg.Reference("m"), []byte{0xDE, 0xAD, 0xBE, 0xEF, 0x1}), false},
{iceberg.EqualTo(iceberg.Reference("m"), []byte{0xDE, 0xAD, 0xBE, 0xEF, 0x0}), true},
}
for _, tt := range tests {
t.Run(tt.ref.String(), func(t *testing.T) {
ev, err := iceberg.ExpressionEvaluator(sc, tt.ref, true)
require.NoError(t, err)
res, err := ev(rowData)
require.NoError(t, err)
assert.Equal(t, tt.exp, res)
})
}
}
func TestRewriteNot(t *testing.T) {
tests := []struct {
expr, expected iceberg.BooleanExpression
}{
{iceberg.NewNot(iceberg.EqualTo(iceberg.Reference("x"), 34.56)),
iceberg.NotEqualTo(iceberg.Reference("x"), 34.56)},
{iceberg.NewNot(iceberg.NotEqualTo(iceberg.Reference("x"), 34.56)),
iceberg.EqualTo(iceberg.Reference("x"), 34.56)},
{iceberg.NewNot(iceberg.IsIn(iceberg.Reference("x"), 34.56, 23.45)),
iceberg.NotIn(iceberg.Reference("x"), 34.56, 23.45)},
{iceberg.NewNot(iceberg.NewAnd(
iceberg.EqualTo(iceberg.Reference("x"), 34.56), iceberg.EqualTo(iceberg.Reference("y"), 34.56))),
iceberg.NewOr(
iceberg.NotEqualTo(iceberg.Reference("x"), 34.56), iceberg.NotEqualTo(iceberg.Reference("y"), 34.56))},
{iceberg.NewNot(iceberg.NewOr(
iceberg.EqualTo(iceberg.Reference("x"), 34.56), iceberg.EqualTo(iceberg.Reference("y"), 34.56))),
iceberg.NewAnd(iceberg.NotEqualTo(iceberg.Reference("x"), 34.56), iceberg.NotEqualTo(iceberg.Reference("y"), 34.56))},
{iceberg.NewNot(iceberg.AlwaysFalse{}), iceberg.AlwaysTrue{}},
{iceberg.NewNot(iceberg.AlwaysTrue{}), iceberg.AlwaysFalse{}},
}
for _, tt := range tests {
t.Run(tt.expr.String(), func(t *testing.T) {
out, err := iceberg.RewriteNotExpr(tt.expr)
require.NoError(t, err)
assert.True(t, out.Equals(tt.expected))
})
}
}