blob: 0bccc9b80f5f2ed42acb44c60da026a7d58a1897 [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.
import pytest
from pyparsing import ParseException
import pyiceberg.expressions.parser as parser
from pyiceberg.expressions import (
AlwaysFalse,
AlwaysTrue,
And,
EqualTo,
GreaterThan,
GreaterThanOrEqual,
In,
IsNaN,
IsNull,
LessThan,
LessThanOrEqual,
Not,
NotEqualTo,
NotIn,
NotNaN,
NotNull,
NotStartsWith,
Or,
StartsWith,
)
def test_true() -> None:
assert AlwaysTrue() == parser.parse("true")
def test_false() -> None:
assert AlwaysFalse() == parser.parse("false")
def test_is_null() -> None:
assert IsNull("foo") == parser.parse("foo is null")
assert IsNull("foo") == parser.parse("foo IS NULL")
assert IsNull("foo") == parser.parse("table.foo IS NULL")
def test_not_null() -> None:
assert NotNull("foo") == parser.parse("foo is not null")
assert NotNull("foo") == parser.parse("foo IS NOT NULL")
def test_is_nan() -> None:
assert IsNaN("foo") == parser.parse("foo is nan")
assert IsNaN("foo") == parser.parse("foo IS NAN")
def test_not_nan() -> None:
assert NotNaN("foo") == parser.parse("foo is not nan")
assert NotNaN("foo") == parser.parse("foo IS NOT NaN")
def test_less_than() -> None:
assert LessThan("foo", 5) == parser.parse("foo < 5")
assert LessThan("foo", "a") == parser.parse("'a' > foo")
def test_less_than_or_equal() -> None:
assert LessThanOrEqual("foo", 5) == parser.parse("foo <= 5")
assert LessThanOrEqual("foo", "a") == parser.parse("'a' >= foo")
def test_greater_than() -> None:
assert GreaterThan("foo", 5) == parser.parse("foo > 5")
assert GreaterThan("foo", "a") == parser.parse("'a' < foo")
def test_greater_than_or_equal() -> None:
assert GreaterThanOrEqual("foo", 5) == parser.parse("foo >= 5")
assert GreaterThanOrEqual("foo", "a") == parser.parse("'a' <= foo")
def test_equal_to() -> None:
assert EqualTo("foo", 5) == parser.parse("foo = 5")
assert EqualTo("foo", "a") == parser.parse("'a' = foo")
assert EqualTo("foo", "a") == parser.parse("foo == 'a'")
assert EqualTo("foo", 5) == parser.parse("5 == foo")
def test_not_equal_to() -> None:
assert NotEqualTo("foo", 5) == parser.parse("foo != 5")
assert NotEqualTo("foo", "a") == parser.parse("'a' != foo")
assert NotEqualTo("foo", "a") == parser.parse("foo <> 'a'")
assert NotEqualTo("foo", 5) == parser.parse("5 <> foo")
def test_in() -> None:
assert In("foo", {5, 6, 7}) == parser.parse("foo in (5, 6, 7)")
assert In("foo", {"a", "b", "c"}) == parser.parse("foo IN ('a', 'b', 'c')")
def test_in_different_types() -> None:
with pytest.raises(ParseException):
parser.parse("foo in (5, 'a')")
def test_not_in() -> None:
assert NotIn("foo", {5, 6, 7}) == parser.parse("foo not in (5, 6, 7)")
assert NotIn("foo", {"a", "b", "c"}) == parser.parse("foo NOT IN ('a', 'b', 'c')")
def test_not_in_different_types() -> None:
with pytest.raises(ParseException):
parser.parse("foo not in (5, 'a')")
def test_simple_and() -> None:
assert And(GreaterThanOrEqual("foo", 5), LessThan("foo", 10)) == parser.parse("5 <= foo and foo < 10")
def test_and_with_not() -> None:
assert And(Not(GreaterThanOrEqual("foo", 5)), LessThan("foo", 10)) == parser.parse("not 5 <= foo and foo < 10")
assert And(GreaterThanOrEqual("foo", 5), Not(LessThan("foo", 10))) == parser.parse("5 <= foo and not foo < 10")
def test_or_with_not() -> None:
assert Or(Not(LessThan("foo", 5)), GreaterThan("foo", 10)) == parser.parse("not foo < 5 or 10 < foo")
assert Or(LessThan("foo", 5), Not(GreaterThan("foo", 10))) == parser.parse("foo < 5 or not 10 < foo")
def test_simple_or() -> None:
assert Or(LessThan("foo", 5), GreaterThan("foo", 10)) == parser.parse("foo < 5 or 10 < foo")
def test_and_or_without_parens() -> None:
assert Or(And(NotNull("foo"), LessThan("foo", 5)), GreaterThan("foo", 10)) == parser.parse(
"foo is not null and foo < 5 or 10 < foo"
)
assert Or(IsNull("foo"), And(GreaterThanOrEqual("foo", 5), LessThan("foo", 10))) == parser.parse(
"foo is null or 5 <= foo and foo < 10"
)
def test_and_or_with_parens() -> None:
assert And(NotNull("foo"), Or(LessThan("foo", 5), GreaterThan("foo", 10))) == parser.parse(
"foo is not null and (foo < 5 or 10 < foo)"
)
assert Or(IsNull("foo"), And(GreaterThanOrEqual("foo", 5), Not(LessThan("foo", 10)))) == parser.parse(
"(foo is null) or (5 <= foo) and not(foo < 10)"
)
def test_multiple_and_or() -> None:
assert And(EqualTo("foo", 1), EqualTo("bar", 2), EqualTo("baz", 3)) == parser.parse("foo = 1 and bar = 2 and baz = 3")
assert Or(EqualTo("foo", 1), EqualTo("foo", 2), EqualTo("foo", 3)) == parser.parse("foo = 1 or foo = 2 or foo = 3")
assert Or(
And(NotNull("foo"), LessThan("foo", 5)), And(GreaterThan("foo", 10), LessThan("foo", 100), IsNull("bar"))
) == parser.parse("foo is not null and foo < 5 or (foo > 10 and foo < 100 and bar is null)")
def test_like_equality() -> None:
assert EqualTo("foo", "data") == parser.parse("foo LIKE 'data'")
assert EqualTo("foo", "data%") == parser.parse("foo LIKE 'data\\%'")
def test_starts_with() -> None:
assert StartsWith("foo", "data") == parser.parse("foo LIKE 'data%'")
assert StartsWith("foo", "some % data") == parser.parse("foo LIKE 'some \\% data%'")
assert StartsWith("foo", "some data%") == parser.parse("foo LIKE 'some data\\%%'")
def test_invalid_likes() -> None:
invalid_statements = ["foo LIKE '%data%'", "foo LIKE 'da%ta'", "foo LIKE '%data'"]
for statement in invalid_statements:
with pytest.raises(ValueError) as exc_info:
parser.parse(statement)
assert "LIKE expressions only supports wildcard, '%', at the end of a string" in str(exc_info)
def test_not_starts_with() -> None:
assert NotEqualTo("foo", "data") == parser.parse("foo NOT LIKE 'data'")
assert NotStartsWith("foo", "data") == parser.parse("foo NOT LIKE 'data%'")
def test_with_function() -> None:
with pytest.raises(ParseException) as exc_info:
parser.parse("foo = 1 and lower(bar) = '2'")
assert "Expected end of text, found 'and'" in str(exc_info)