blob: 3ea5257c7a7e7652c3d936b8e2176de27b37295d [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"
"strconv"
"testing"
"github.com/apache/iceberg-go"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type ExprA struct{}
func (ExprA) String() string { return "ExprA" }
func (ExprA) Op() iceberg.Operation { return iceberg.OpFalse }
func (ExprA) Negate() iceberg.BooleanExpression { return ExprB{} }
func (ExprA) Equals(o iceberg.BooleanExpression) bool {
_, ok := o.(ExprA)
return ok
}
type ExprB struct{}
func (ExprB) String() string { return "ExprB" }
func (ExprB) Op() iceberg.Operation { return iceberg.OpTrue }
func (ExprB) Negate() iceberg.BooleanExpression { return ExprA{} }
func (ExprB) Equals(o iceberg.BooleanExpression) bool {
_, ok := o.(ExprB)
return ok
}
func TestUnaryExpr(t *testing.T) {
assert.PanicsWithError(t, "invalid argument: invalid operation for unary predicate: LessThan", func() {
iceberg.UnaryPredicate(iceberg.OpLT, iceberg.Reference("a"))
})
assert.PanicsWithError(t, "invalid argument: cannot create unary predicate with nil term", func() {
iceberg.UnaryPredicate(iceberg.OpIsNull, nil)
})
t.Run("negate", func(t *testing.T) {
n := iceberg.IsNull(iceberg.Reference("a")).Negate()
exp := iceberg.NotNull(iceberg.Reference("a"))
assert.Equal(t, exp, n)
assert.True(t, exp.Equals(n))
assert.True(t, n.Equals(exp))
})
sc := iceberg.NewSchema(1, iceberg.NestedField{
ID: 2, Name: "a", Type: iceberg.PrimitiveTypes.Int32})
sc2 := iceberg.NewSchema(1, iceberg.NestedField{
ID: 2, Name: "a", Type: iceberg.PrimitiveTypes.Float64})
sc3 := iceberg.NewSchema(1, iceberg.NestedField{
ID: 2, Name: "a", Type: iceberg.PrimitiveTypes.Int32, Required: true})
sc4 := iceberg.NewSchema(1, iceberg.NestedField{
ID: 2, Name: "a", Type: iceberg.PrimitiveTypes.Float32, Required: true})
t.Run("isnull and notnull", func(t *testing.T) {
t.Run("bind", func(t *testing.T) {
n, err := iceberg.IsNull(iceberg.Reference("a")).Bind(sc, true)
require.NoError(t, err)
assert.Equal(t, iceberg.OpIsNull, n.Op())
assert.Implements(t, (*iceberg.BoundUnaryPredicate)(nil), n)
p := n.(iceberg.BoundUnaryPredicate)
assert.IsType(t, iceberg.PrimitiveTypes.Int32, p.Term().Type())
assert.Same(t, p.Ref(), p.Term().Ref())
assert.Same(t, p.Ref(), p.Ref().Ref())
f := p.Ref().Field()
assert.True(t, f.Equals(sc.Field(0)))
})
t.Run("negate and bind", func(t *testing.T) {
n1, err := iceberg.IsNull(iceberg.Reference("a")).Bind(sc, true)
require.NoError(t, err)
n2, err := iceberg.NotNull(iceberg.Reference("a")).Bind(sc, true)
require.NoError(t, err)
assert.True(t, n1.Negate().Equals(n2))
assert.True(t, n2.Negate().Equals(n1))
})
t.Run("null bind required", func(t *testing.T) {
n1, err := iceberg.IsNull(iceberg.Reference("a")).Bind(sc3, true)
require.NoError(t, err)
n2, err := iceberg.NotNull(iceberg.Reference("a")).Bind(sc3, true)
require.NoError(t, err)
assert.True(t, n1.Equals(iceberg.AlwaysFalse{}))
assert.True(t, n2.Equals(iceberg.AlwaysTrue{}))
})
})
t.Run("isnan notnan", func(t *testing.T) {
t.Run("negate and bind", func(t *testing.T) {
n1, err := iceberg.IsNaN(iceberg.Reference("a")).Bind(sc2, true)
require.NoError(t, err)
n2, err := iceberg.NotNaN(iceberg.Reference("a")).Bind(sc2, true)
require.NoError(t, err)
assert.True(t, n1.Negate().Equals(n2))
assert.True(t, n2.Negate().Equals(n1))
})
t.Run("bind float", func(t *testing.T) {
n, err := iceberg.IsNaN(iceberg.Reference("a")).Bind(sc4, true)
require.NoError(t, err)
assert.Equal(t, iceberg.OpIsNan, n.Op())
assert.Implements(t, (*iceberg.BoundUnaryPredicate)(nil), n)
p := n.(iceberg.BoundUnaryPredicate)
assert.IsType(t, iceberg.PrimitiveTypes.Float32, p.Term().Type())
n2, err := iceberg.NotNaN(iceberg.Reference("a")).Bind(sc4, true)
require.NoError(t, err)
assert.Equal(t, iceberg.OpNotNan, n2.Op())
assert.Implements(t, (*iceberg.BoundUnaryPredicate)(nil), n2)
p2 := n2.(iceberg.BoundUnaryPredicate)
assert.IsType(t, iceberg.PrimitiveTypes.Float32, p2.Term().Type())
})
t.Run("bind double", func(t *testing.T) {
n, err := iceberg.IsNaN(iceberg.Reference("a")).Bind(sc2, true)
require.NoError(t, err)
assert.Equal(t, iceberg.OpIsNan, n.Op())
assert.Implements(t, (*iceberg.BoundUnaryPredicate)(nil), n)
p := n.(iceberg.BoundUnaryPredicate)
assert.IsType(t, iceberg.PrimitiveTypes.Float64, p.Term().Type())
n2, err := iceberg.NotNaN(iceberg.Reference("a")).Bind(sc2, true)
require.NoError(t, err)
assert.Equal(t, iceberg.OpNotNan, n2.Op())
assert.Implements(t, (*iceberg.BoundUnaryPredicate)(nil), n2)
p2 := n2.(iceberg.BoundUnaryPredicate)
assert.IsType(t, iceberg.PrimitiveTypes.Float64, p2.Term().Type())
})
t.Run("bind non floating", func(t *testing.T) {
n1, err := iceberg.IsNaN(iceberg.Reference("a")).Bind(sc, true)
require.NoError(t, err)
n2, err := iceberg.NotNaN(iceberg.Reference("a")).Bind(sc, true)
require.NoError(t, err)
assert.True(t, n1.Equals(iceberg.AlwaysFalse{}))
assert.True(t, n2.Equals(iceberg.AlwaysTrue{}))
})
})
}
func TestRefBindingCaseSensitive(t *testing.T) {
ref1, ref2 := iceberg.Reference("foo"), iceberg.Reference("Foo")
bound1, err := ref1.Bind(tableSchemaSimple, true)
require.NoError(t, err)
assert.True(t, bound1.Type().Equals(iceberg.PrimitiveTypes.String))
_, err = ref2.Bind(tableSchemaSimple, true)
assert.ErrorIs(t, err, iceberg.ErrInvalidSchema)
assert.ErrorContains(t, err, "could not bind reference 'Foo', caseSensitive=true")
bound2, err := ref2.Bind(tableSchemaSimple, false)
require.NoError(t, err)
assert.True(t, bound1.Equals(bound2))
_, err = iceberg.Reference("foot").Bind(tableSchemaSimple, false)
assert.ErrorIs(t, err, iceberg.ErrInvalidSchema)
assert.ErrorContains(t, err, "could not bind reference 'foot', caseSensitive=false")
}
func TestRefTypes(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)})
t.Run("bind term", func(t *testing.T) {
for i := 0; i < sc.NumFields(); i++ {
fld := sc.Field(i)
t.Run(fld.Type.String(), func(t *testing.T) {
ref, err := iceberg.Reference(fld.Name).Bind(sc, true)
require.NoError(t, err)
assert.True(t, ref.Type().Equals(fld.Type))
assert.True(t, fld.Equals(ref.Ref().Field()))
})
}
})
t.Run("bind unary", func(t *testing.T) {
for i := 0; i < sc.NumFields(); i++ {
fld := sc.Field(i)
t.Run(fld.Type.String(), func(t *testing.T) {
b, err := iceberg.IsNull(iceberg.Reference(fld.Name)).Bind(sc, true)
require.NoError(t, err)
assert.True(t, b.(iceberg.BoundUnaryPredicate).Ref().Type().Equals(fld.Type))
un := b.(iceberg.BoundUnaryPredicate).AsUnbound(iceberg.Reference("foo"))
assert.Equal(t, b.Op(), un.Op())
})
}
})
t.Run("bind literal", func(t *testing.T) {
t.Run("bool", func(t *testing.T) {
b1, err := iceberg.EqualTo(iceberg.Reference("a"), true).Bind(sc, true)
require.NoError(t, err)
assert.Equal(t, iceberg.OpEQ, b1.Op())
assert.True(t, b1.(iceberg.BoundLiteralPredicate).Ref().Type().Equals(iceberg.PrimitiveTypes.Bool))
})
for i := 1; i < 9; i++ {
fld := sc.Field(i)
t.Run(fld.Type.String(), func(t *testing.T) {
b, err := iceberg.EqualTo(iceberg.Reference(fld.Name), int32(5)).Bind(sc, true)
require.NoError(t, err)
assert.Equal(t, iceberg.OpEQ, b.Op())
assert.True(t, b.(iceberg.BoundLiteralPredicate).Literal().Type().Equals(fld.Type))
assert.True(t, b.(iceberg.BoundLiteralPredicate).Ref().Type().Equals(fld.Type))
})
}
t.Run("string-binary", func(t *testing.T) {
str, err := iceberg.EqualTo(iceberg.Reference("j"), "foobar").Bind(sc, true)
require.NoError(t, err)
bin, err := iceberg.EqualTo(iceberg.Reference("k"), []byte("foobar")).Bind(sc, true)
require.NoError(t, err)
assert.Equal(t, iceberg.OpEQ, str.Op())
assert.True(t, str.(iceberg.BoundLiteralPredicate).Literal().Type().Equals(iceberg.PrimitiveTypes.String))
assert.Equal(t, iceberg.OpEQ, bin.Op())
assert.True(t, bin.(iceberg.BoundLiteralPredicate).Literal().Type().Equals(iceberg.PrimitiveTypes.Binary))
})
t.Run("fixed", func(t *testing.T) {
fx, err := iceberg.EqualTo(iceberg.Reference("m"), []byte{0, 1, 2, 3, 4}).Bind(sc, true)
require.NoError(t, err)
assert.Equal(t, iceberg.OpEQ, fx.Op())
assert.True(t, fx.(iceberg.BoundLiteralPredicate).Literal().Type().Equals(iceberg.FixedTypeOf(5)))
})
t.Run("uuid", func(t *testing.T) {
uid, err := iceberg.EqualTo(iceberg.Reference("l"), uuid.New().String()).Bind(sc, true)
require.NoError(t, err)
assert.Equal(t, iceberg.OpEQ, uid.Op())
assert.True(t, uid.(iceberg.BoundLiteralPredicate).Literal().Type().Equals(iceberg.PrimitiveTypes.UUID))
})
})
t.Run("bind set", func(t *testing.T) {
t.Run("bool", func(t *testing.T) {
b, err := iceberg.IsIn(iceberg.Reference("a"), true, false).(iceberg.UnboundPredicate).Bind(sc, true)
require.NoError(t, err)
assert.Equal(t, iceberg.OpIn, b.Op())
})
for i := 1; i < 9; i++ {
fld := sc.Field(i)
t.Run(fld.Type.String(), func(t *testing.T) {
b, err := iceberg.IsIn(iceberg.Reference(fld.Name), int32(10), int32(5), int32(5)).(iceberg.UnboundPredicate).
Bind(sc, true)
require.NoError(t, err)
assert.Equal(t, iceberg.OpIn, b.Op())
assert.True(t, b.(iceberg.BoundSetPredicate).Ref().Type().Equals(fld.Type))
for _, v := range b.(iceberg.BoundSetPredicate).Literals().Members() {
assert.True(t, v.Type().Equals(fld.Type))
}
})
}
t.Run("string-binary", func(t *testing.T) {
str, err := iceberg.IsIn(iceberg.Reference("j"), "hello", "foobar").(iceberg.UnboundPredicate).
Bind(sc, true)
require.NoError(t, err)
bin, err := iceberg.IsIn(iceberg.Reference("k"), []byte("baz"), []byte("foobar")).(iceberg.UnboundPredicate).
Bind(sc, true)
require.NoError(t, err)
assert.Equal(t, iceberg.OpIn, str.Op())
assert.Equal(t, iceberg.OpIn, bin.Op())
assert.True(t, str.(iceberg.BoundSetPredicate).Ref().Type().Equals(iceberg.PrimitiveTypes.String))
for _, v := range str.(iceberg.BoundSetPredicate).Literals().Members() {
assert.True(t, v.Type().Equals(iceberg.PrimitiveTypes.String))
}
assert.True(t, bin.(iceberg.BoundSetPredicate).Ref().Type().Equals(iceberg.PrimitiveTypes.Binary))
for _, v := range bin.(iceberg.BoundSetPredicate).Literals().Members() {
assert.True(t, v.Type().Equals(iceberg.PrimitiveTypes.Binary))
}
})
t.Run("fixed", func(t *testing.T) {
fx, err := iceberg.IsIn(iceberg.Reference("m"), []byte{4, 5, 6, 7, 8}, []byte{0, 1, 2, 3, 4}).(iceberg.UnboundPredicate).
Bind(sc, true)
require.NoError(t, err)
assert.Equal(t, iceberg.OpIn, fx.Op())
assert.True(t, fx.(iceberg.BoundSetPredicate).Ref().Type().Equals(iceberg.FixedTypeOf(5)))
for _, v := range fx.(iceberg.BoundSetPredicate).Literals().Members() {
assert.True(t, v.Type().Equals(iceberg.FixedTypeOf(5)))
}
})
t.Run("uuid", func(t *testing.T) {
uid, err := iceberg.IsIn(iceberg.Reference("l"), uuid.New().String(), uuid.New().String()).(iceberg.UnboundPredicate).
Bind(sc, true)
require.NoError(t, err)
assert.Equal(t, iceberg.OpIn, uid.Op())
assert.True(t, uid.(iceberg.BoundSetPredicate).Ref().Type().Equals(iceberg.PrimitiveTypes.UUID))
for _, v := range uid.(iceberg.BoundSetPredicate).Literals().Members() {
assert.True(t, v.Type().Equals(iceberg.PrimitiveTypes.UUID))
}
})
})
}
func TestInNotInSimplifications(t *testing.T) {
assert.PanicsWithError(t, "invalid argument: invalid operation for SetPredicate: LessThan",
func() { iceberg.SetPredicate(iceberg.OpLT, iceberg.Reference("x"), nil) })
assert.PanicsWithError(t, "invalid argument: cannot create set predicate with nil term",
func() { iceberg.SetPredicate(iceberg.OpIn, nil, nil) })
assert.NotPanics(t, func() { iceberg.SetPredicate(iceberg.OpIn, iceberg.Reference("x"), nil) })
t.Run("in to eq", func(t *testing.T) {
a := iceberg.IsIn(iceberg.Reference("x"), 34.56)
b := iceberg.EqualTo(iceberg.Reference("x"), 34.56)
assert.True(t, a.Equals(b))
})
t.Run("notin to notequal", func(t *testing.T) {
a := iceberg.NotIn(iceberg.Reference("x"), 34.56)
b := iceberg.NotEqualTo(iceberg.Reference("x"), 34.56)
assert.True(t, a.Equals(b))
})
t.Run("empty", func(t *testing.T) {
a := iceberg.IsIn[float32](iceberg.Reference("x"))
b := iceberg.NotIn[float32](iceberg.Reference("x"))
assert.Equal(t, iceberg.AlwaysFalse{}, a)
assert.Equal(t, iceberg.AlwaysTrue{}, b)
})
t.Run("bind and negate", func(t *testing.T) {
inexp := iceberg.IsIn(iceberg.Reference("foo"), "hello", "world")
notin := iceberg.NotIn(iceberg.Reference("foo"), "hello", "world")
assert.True(t, inexp.Negate().Equals(notin))
assert.True(t, notin.Negate().Equals(inexp))
assert.Equal(t, iceberg.OpIn, inexp.Op())
assert.Equal(t, iceberg.OpNotIn, notin.Op())
boundin, err := inexp.(iceberg.UnboundPredicate).Bind(tableSchemaSimple, true)
require.NoError(t, err)
boundnot, err := notin.(iceberg.UnboundPredicate).Bind(tableSchemaSimple, true)
require.NoError(t, err)
assert.True(t, boundin.Negate().Equals(boundnot))
assert.True(t, boundnot.Negate().Equals(boundin))
})
t.Run("bind dedup", func(t *testing.T) {
isin := iceberg.IsIn(iceberg.Reference("foo"), "hello", "world", "world")
bound, err := isin.(iceberg.UnboundPredicate).Bind(tableSchemaSimple, true)
require.NoError(t, err)
assert.Implements(t, (*iceberg.BoundSetPredicate)(nil), bound)
bsp := bound.(iceberg.BoundSetPredicate)
assert.Equal(t, 2, bsp.Literals().Len())
assert.True(t, bsp.Literals().Contains(iceberg.NewLiteral("hello")))
assert.True(t, bsp.Literals().Contains(iceberg.NewLiteral("world")))
})
t.Run("bind dedup to eq", func(t *testing.T) {
isin := iceberg.IsIn(iceberg.Reference("foo"), "world", "world")
bound, err := isin.(iceberg.UnboundPredicate).Bind(tableSchemaSimple, true)
require.NoError(t, err)
assert.Equal(t, iceberg.OpEQ, bound.Op())
assert.Equal(t, iceberg.NewLiteral("world"),
bound.(iceberg.BoundLiteralPredicate).Literal())
})
}
func TestLiteralPredicateErrors(t *testing.T) {
assert.PanicsWithError(t, "invalid argument: invalid operation for LiteralPredicate: In",
func() { iceberg.LiteralPredicate(iceberg.OpIn, iceberg.Reference("foo"), iceberg.NewLiteral("hello")) })
assert.PanicsWithError(t, "invalid argument: cannot create literal predicate with nil term",
func() { iceberg.LiteralPredicate(iceberg.OpLT, nil, iceberg.NewLiteral("hello")) })
assert.PanicsWithError(t, "invalid argument: cannot create literal predicate with nil literal",
func() { iceberg.LiteralPredicate(iceberg.OpLT, iceberg.Reference("foo"), nil) })
}
func TestNegations(t *testing.T) {
ref := iceberg.Reference("foo")
tests := []struct {
name string
ex1, ex2 iceberg.UnboundPredicate
}{
{"equal-not", iceberg.EqualTo(ref, "hello"), iceberg.NotEqualTo(ref, "hello")},
{"greater-equal-less", iceberg.GreaterThanEqual(ref, "hello"), iceberg.LessThan(ref, "hello")},
{"greater-less-equal", iceberg.GreaterThan(ref, "hello"), iceberg.LessThanEqual(ref, "hello")},
{"starts-with", iceberg.StartsWith(ref, "hello"), iceberg.NotStartsWith(ref, "hello")},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.False(t, tt.ex1.Equals(tt.ex2))
assert.False(t, tt.ex2.Equals(tt.ex1))
assert.True(t, tt.ex1.Negate().Equals(tt.ex2))
assert.True(t, tt.ex2.Negate().Equals(tt.ex1))
b1, err := tt.ex1.Bind(tableSchemaSimple, true)
require.NoError(t, err)
b2, err := tt.ex2.Bind(tableSchemaSimple, true)
require.NoError(t, err)
assert.False(t, b1.Equals(b2))
assert.False(t, b2.Equals(b1))
assert.True(t, b1.Negate().Equals(b2))
assert.True(t, b2.Negate().Equals(b1))
})
}
}
func TestBoolExprEQ(t *testing.T) {
tests := []struct {
exp, testexpra, testexprb iceberg.BooleanExpression
}{
{iceberg.NewAnd(ExprA{}, ExprB{}),
iceberg.NewAnd(ExprA{}, ExprB{}),
iceberg.NewOr(ExprA{}, ExprB{})},
{iceberg.NewOr(ExprA{}, ExprB{}),
iceberg.NewOr(ExprA{}, ExprB{}),
iceberg.NewAnd(ExprA{}, ExprB{})},
{iceberg.NewAnd(ExprA{}, ExprB{}),
iceberg.NewAnd(ExprB{}, ExprA{}),
iceberg.NewOr(ExprB{}, ExprA{})},
{iceberg.NewOr(ExprA{}, ExprB{}),
iceberg.NewOr(ExprB{}, ExprA{}),
iceberg.NewAnd(ExprB{}, ExprA{})},
{iceberg.NewNot(ExprA{}), iceberg.NewNot(ExprA{}), ExprB{}},
{ExprA{}, ExprA{}, ExprB{}},
{ExprB{}, ExprB{}, ExprA{}},
{iceberg.IsIn(iceberg.Reference("foo"), "hello", "world"),
iceberg.IsIn(iceberg.Reference("foo"), "hello", "world"),
iceberg.IsIn(iceberg.Reference("not_foo"), "hello", "world")},
{iceberg.IsIn(iceberg.Reference("foo"), "hello", "world"),
iceberg.IsIn(iceberg.Reference("foo"), "hello", "world"),
iceberg.IsIn(iceberg.Reference("foo"), "goodbye", "world")},
}
for i, tt := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
assert.True(t, tt.exp.Equals(tt.testexpra))
assert.False(t, tt.exp.Equals(tt.testexprb))
})
}
}
func TestBoolExprNegate(t *testing.T) {
tests := []struct {
lhs, rhs iceberg.BooleanExpression
}{
{iceberg.NewAnd(ExprA{}, ExprB{}), iceberg.NewOr(ExprB{}, ExprA{})},
{iceberg.NewOr(ExprB{}, ExprA{}), iceberg.NewAnd(ExprA{}, ExprB{})},
{iceberg.NewNot(ExprA{}), ExprA{}},
{iceberg.IsIn(iceberg.Reference("foo"), "hello", "world"),
iceberg.NotIn(iceberg.Reference("foo"), "hello", "world")},
{iceberg.NotIn(iceberg.Reference("foo"), "hello", "world"),
iceberg.IsIn(iceberg.Reference("foo"), "hello", "world")},
{iceberg.GreaterThan(iceberg.Reference("foo"), int32(5)),
iceberg.LessThanEqual(iceberg.Reference("foo"), int32(5))},
{iceberg.LessThan(iceberg.Reference("foo"), int32(5)),
iceberg.GreaterThanEqual(iceberg.Reference("foo"), int32(5))},
{iceberg.EqualTo(iceberg.Reference("foo"), int32(5)),
iceberg.NotEqualTo(iceberg.Reference("foo"), int32(5))},
{ExprA{}, ExprB{}},
}
for _, tt := range tests {
assert.True(t, tt.lhs.Negate().Equals(tt.rhs))
}
}
func TestBoolExprPanics(t *testing.T) {
assert.PanicsWithError(t, "invalid argument: cannot construct AndExpr with nil arguments",
func() { iceberg.NewAnd(nil, ExprA{}) })
assert.PanicsWithError(t, "invalid argument: cannot construct AndExpr with nil arguments",
func() { iceberg.NewAnd(ExprA{}, nil) })
assert.PanicsWithError(t, "invalid argument: cannot construct AndExpr with nil arguments",
func() { iceberg.NewAnd(ExprA{}, ExprA{}, nil) })
assert.PanicsWithError(t, "invalid argument: cannot construct OrExpr with nil arguments",
func() { iceberg.NewOr(nil, ExprA{}) })
assert.PanicsWithError(t, "invalid argument: cannot construct OrExpr with nil arguments",
func() { iceberg.NewOr(ExprA{}, nil) })
assert.PanicsWithError(t, "invalid argument: cannot construct OrExpr with nil arguments",
func() { iceberg.NewOr(ExprA{}, ExprA{}, nil) })
assert.PanicsWithError(t, "invalid argument: cannot create NotExpr with nil child",
func() { iceberg.NewNot(nil) })
}
func TestExprFolding(t *testing.T) {
tests := []struct {
lhs, rhs iceberg.BooleanExpression
}{
{iceberg.NewAnd(ExprA{}, ExprB{}, ExprA{}),
iceberg.NewAnd(iceberg.NewAnd(ExprA{}, ExprB{}), ExprA{})},
{iceberg.NewOr(ExprA{}, ExprB{}, ExprA{}),
iceberg.NewOr(iceberg.NewOr(ExprA{}, ExprB{}), ExprA{})},
{iceberg.NewNot(iceberg.NewNot(ExprA{})), ExprA{}},
}
for _, tt := range tests {
assert.True(t, tt.lhs.Equals(tt.rhs))
}
}
func TestBaseAlwaysTrueAlwaysFalse(t *testing.T) {
tests := []struct {
lhs, rhs iceberg.BooleanExpression
}{
{iceberg.NewAnd(iceberg.AlwaysTrue{}, ExprB{}), ExprB{}},
{iceberg.NewAnd(iceberg.AlwaysFalse{}, ExprB{}), iceberg.AlwaysFalse{}},
{iceberg.NewAnd(ExprB{}, iceberg.AlwaysTrue{}), ExprB{}},
{iceberg.NewOr(iceberg.AlwaysTrue{}, ExprB{}), iceberg.AlwaysTrue{}},
{iceberg.NewOr(iceberg.AlwaysFalse{}, ExprB{}), ExprB{}},
{iceberg.NewOr(ExprA{}, iceberg.AlwaysFalse{}), ExprA{}},
{iceberg.NewNot(iceberg.NewNot(ExprA{})), ExprA{}},
{iceberg.NewNot(iceberg.AlwaysTrue{}), iceberg.AlwaysFalse{}},
{iceberg.NewNot(iceberg.AlwaysFalse{}), iceberg.AlwaysTrue{}},
}
for _, tt := range tests {
assert.True(t, tt.lhs.Equals(tt.rhs))
}
}
func TestNegateAlways(t *testing.T) {
assert.Equal(t, iceberg.OpTrue, iceberg.AlwaysTrue{}.Op())
assert.Equal(t, iceberg.OpFalse, iceberg.AlwaysFalse{}.Op())
assert.Equal(t, iceberg.AlwaysTrue{}, iceberg.AlwaysFalse{}.Negate())
assert.Equal(t, iceberg.AlwaysFalse{}, iceberg.AlwaysTrue{}.Negate())
}
func TestBoundReferenceToString(t *testing.T) {
ref, err := iceberg.Reference("foo").Bind(tableSchemaSimple, true)
require.NoError(t, err)
assert.Equal(t, "BoundReference(field=1: foo: optional string, accessor=Accessor(position=0, inner=<nil>))",
ref.String())
}
func TestToString(t *testing.T) {
schema := iceberg.NewSchema(1,
iceberg.NestedField{ID: 1, Name: "a", Type: iceberg.PrimitiveTypes.String},
iceberg.NestedField{ID: 2, Name: "b", Type: iceberg.PrimitiveTypes.String},
iceberg.NestedField{ID: 3, Name: "c", Type: iceberg.PrimitiveTypes.String},
iceberg.NestedField{ID: 4, Name: "d", Type: iceberg.PrimitiveTypes.Int32},
iceberg.NestedField{ID: 5, Name: "e", Type: iceberg.PrimitiveTypes.Int32},
iceberg.NestedField{ID: 6, Name: "f", Type: iceberg.PrimitiveTypes.Int32},
iceberg.NestedField{ID: 7, Name: "g", Type: iceberg.PrimitiveTypes.Float32},
iceberg.NestedField{ID: 8, Name: "h", Type: iceberg.DecimalTypeOf(8, 4)},
iceberg.NestedField{ID: 9, Name: "i", Type: iceberg.PrimitiveTypes.UUID},
iceberg.NestedField{ID: 10, Name: "j", Type: iceberg.PrimitiveTypes.Bool},
iceberg.NestedField{ID: 11, Name: "k", Type: iceberg.PrimitiveTypes.Bool},
iceberg.NestedField{ID: 12, Name: "l", Type: iceberg.PrimitiveTypes.Binary})
null := iceberg.IsNull(iceberg.Reference("a"))
nan := iceberg.IsNaN(iceberg.Reference("g"))
boundNull, _ := null.Bind(schema, true)
boundNan, _ := nan.Bind(schema, true)
equal := iceberg.EqualTo(iceberg.Reference("c"), "a")
grtequal := iceberg.GreaterThanEqual(iceberg.Reference("a"), "a")
greater := iceberg.GreaterThan(iceberg.Reference("a"), "a")
startsWith := iceberg.StartsWith(iceberg.Reference("b"), "foo")
boundEqual, _ := equal.Bind(schema, true)
boundGrtEqual, _ := grtequal.Bind(schema, true)
boundGreater, _ := greater.Bind(schema, true)
boundStarts, _ := startsWith.Bind(schema, true)
tests := []struct {
e iceberg.BooleanExpression
expected string
}{
{iceberg.NewAnd(null, nan),
"And(left=IsNull(term=Reference(name='a')), right=IsNaN(term=Reference(name='g')))"},
{iceberg.NewOr(null, nan),
"Or(left=IsNull(term=Reference(name='a')), right=IsNaN(term=Reference(name='g')))"},
{iceberg.NewNot(null),
"Not(child=IsNull(term=Reference(name='a')))"},
{iceberg.AlwaysTrue{}, "AlwaysTrue()"},
{iceberg.AlwaysFalse{}, "AlwaysFalse()"},
{boundNull,
"BoundIsNull(term=BoundReference(field=1: a: optional string, accessor=Accessor(position=0, inner=<nil>)))"},
{boundNull.Negate(),
"BoundNotNull(term=BoundReference(field=1: a: optional string, accessor=Accessor(position=0, inner=<nil>)))"},
{boundNan,
"BoundIsNaN(term=BoundReference(field=7: g: optional float, accessor=Accessor(position=6, inner=<nil>)))"},
{boundNan.Negate(),
"BoundNotNaN(term=BoundReference(field=7: g: optional float, accessor=Accessor(position=6, inner=<nil>)))"},
{equal,
"Equal(term=Reference(name='c'), literal=a)"},
{equal.Negate(),
"NotEqual(term=Reference(name='c'), literal=a)"},
{grtequal,
"GreaterThanEqual(term=Reference(name='a'), literal=a)"},
{grtequal.Negate(),
"LessThan(term=Reference(name='a'), literal=a)"},
{greater,
"GreaterThan(term=Reference(name='a'), literal=a)"},
{greater.Negate(),
"LessThanEqual(term=Reference(name='a'), literal=a)"},
{startsWith,
"StartsWith(term=Reference(name='b'), literal=foo)"},
{startsWith.Negate(),
"NotStartsWith(term=Reference(name='b'), literal=foo)"},
{boundEqual,
"BoundEqual(term=BoundReference(field=3: c: optional string, accessor=Accessor(position=2, inner=<nil>)), literal=a)"},
{boundEqual.Negate(),
"BoundNotEqual(term=BoundReference(field=3: c: optional string, accessor=Accessor(position=2, inner=<nil>)), literal=a)"},
{boundGreater,
"BoundGreaterThan(term=BoundReference(field=1: a: optional string, accessor=Accessor(position=0, inner=<nil>)), literal=a)"},
{boundGreater.Negate(),
"BoundLessThanEqual(term=BoundReference(field=1: a: optional string, accessor=Accessor(position=0, inner=<nil>)), literal=a)"},
{boundGrtEqual,
"BoundGreaterThanEqual(term=BoundReference(field=1: a: optional string, accessor=Accessor(position=0, inner=<nil>)), literal=a)"},
{boundGrtEqual.Negate(),
"BoundLessThan(term=BoundReference(field=1: a: optional string, accessor=Accessor(position=0, inner=<nil>)), literal=a)"},
{boundStarts,
"BoundStartsWith(term=BoundReference(field=2: b: optional string, accessor=Accessor(position=1, inner=<nil>)), literal=foo)"},
{boundStarts.Negate(),
"BoundNotStartsWith(term=BoundReference(field=2: b: optional string, accessor=Accessor(position=1, inner=<nil>)), literal=foo)"},
}
for _, tt := range tests {
assert.Equal(t, tt.expected, tt.e.String())
}
}
func TestBindAboveBelowIntMax(t *testing.T) {
sc := iceberg.NewSchema(1,
iceberg.NestedField{ID: 1, Name: "a", Type: iceberg.PrimitiveTypes.Int32},
iceberg.NestedField{ID: 2, Name: "b", Type: iceberg.PrimitiveTypes.Float32},
)
ref, ref2 := iceberg.Reference("a"), iceberg.Reference("b")
above, below := int64(math.MaxInt32)+1, int64(math.MinInt32)-1
above2, below2 := float64(math.MaxFloat32)+1e37, float64(-math.MaxFloat32)-1e37
tests := []struct {
pred iceberg.UnboundPredicate
exp iceberg.BooleanExpression
}{
{iceberg.EqualTo(ref, above), iceberg.AlwaysFalse{}},
{iceberg.EqualTo(ref, below), iceberg.AlwaysFalse{}},
{iceberg.NotEqualTo(ref, above), iceberg.AlwaysTrue{}},
{iceberg.NotEqualTo(ref, below), iceberg.AlwaysTrue{}},
{iceberg.LessThan(ref, above), iceberg.AlwaysTrue{}},
{iceberg.LessThan(ref, below), iceberg.AlwaysFalse{}},
{iceberg.LessThanEqual(ref, above), iceberg.AlwaysTrue{}},
{iceberg.LessThanEqual(ref, below), iceberg.AlwaysFalse{}},
{iceberg.GreaterThan(ref, above), iceberg.AlwaysFalse{}},
{iceberg.GreaterThan(ref, below), iceberg.AlwaysTrue{}},
{iceberg.GreaterThanEqual(ref, above), iceberg.AlwaysFalse{}},
{iceberg.GreaterThanEqual(ref, below), iceberg.AlwaysTrue{}},
{iceberg.EqualTo(ref2, above2), iceberg.AlwaysFalse{}},
{iceberg.EqualTo(ref2, below2), iceberg.AlwaysFalse{}},
{iceberg.NotEqualTo(ref2, above2), iceberg.AlwaysTrue{}},
{iceberg.NotEqualTo(ref2, below2), iceberg.AlwaysTrue{}},
{iceberg.LessThan(ref2, above2), iceberg.AlwaysTrue{}},
{iceberg.LessThan(ref2, below2), iceberg.AlwaysFalse{}},
{iceberg.LessThanEqual(ref2, above2), iceberg.AlwaysTrue{}},
{iceberg.LessThanEqual(ref2, below2), iceberg.AlwaysFalse{}},
{iceberg.GreaterThan(ref2, above2), iceberg.AlwaysFalse{}},
{iceberg.GreaterThan(ref2, below2), iceberg.AlwaysTrue{}},
{iceberg.GreaterThanEqual(ref2, above2), iceberg.AlwaysFalse{}},
{iceberg.GreaterThanEqual(ref2, below2), iceberg.AlwaysTrue{}},
}
for _, tt := range tests {
t.Run(tt.pred.String(), func(t *testing.T) {
b, err := tt.pred.Bind(sc, true)
require.NoError(t, err)
assert.Equal(t, tt.exp, b)
})
}
}