| /* | |
| * 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. | |
| */ | |
| #include "iceberg/expression/evaluator.h" | |
| #include <cstddef> | |
| #include <limits> | |
| #include <memory> | |
| #include <string> | |
| #include <vector> | |
| #include <arrow/array.h> | |
| #include <arrow/array/builder_primitive.h> | |
| #include <arrow/c/bridge.h> | |
| #include <arrow/json/from_string.h> | |
| #include <arrow/testing/gtest_util.h> | |
| #include <arrow/type.h> | |
| #include <gtest/gtest.h> | |
| #include "iceberg/arrow_c_data_guard_internal.h" | |
| #include "iceberg/expression/expressions.h" | |
| #include "iceberg/expression/literal.h" | |
| #include "iceberg/result.h" | |
| #include "iceberg/row/arrow_array_wrapper.h" | |
| #include "iceberg/schema.h" | |
| #include "iceberg/test/matchers.h" | |
| #include "iceberg/type.h" | |
| namespace iceberg { | |
| class EvaluatorTest : public ::testing::Test { | |
| protected: | |
| void SetUp() override { | |
| schema_ = std::make_unique<Schema>(std::vector<SchemaField>{ | |
| SchemaField::MakeRequired(13, "x", int32()), | |
| SchemaField::MakeRequired(14, "y", float64()), | |
| SchemaField::MakeOptional(15, "z", int32()), | |
| SchemaField::MakeOptional( | |
| 16, "s1", | |
| std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField::MakeRequired( | |
| 17, "s2", | |
| std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField::MakeRequired( | |
| 18, "s3", | |
| std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField::MakeRequired( | |
| 19, "s4", | |
| std::make_shared<StructType>(std::vector<SchemaField>{ | |
| SchemaField::MakeRequired(20, "i", | |
| int32())}))}))}))})), | |
| SchemaField::MakeOptional( | |
| 21, "s5", | |
| std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField::MakeRequired( | |
| 22, "s6", | |
| std::make_shared<StructType>(std::vector<SchemaField>{ | |
| SchemaField::MakeRequired(23, "f", float32())}))}))}); | |
| arrow_data_type_ = ::arrow::struct_({ | |
| ::arrow::field("x", ::arrow::int32(), /*nullable=*/false), | |
| ::arrow::field("y", ::arrow::float64(), /*nullable=*/false), | |
| ::arrow::field("z", ::arrow::int32(), /*nullable=*/true), | |
| ::arrow::field("s1", | |
| ::arrow::struct_({::arrow::field( | |
| "s2", | |
| ::arrow::struct_({::arrow::field( | |
| "s3", | |
| ::arrow::struct_({::arrow::field( | |
| "s4", | |
| ::arrow::struct_({::arrow::field("i", ::arrow::int32(), | |
| /*nullable=*/false)}), | |
| /*nullable=*/false)}), | |
| /*nullable=*/false)}), | |
| /*nullable=*/false)}), | |
| /*nullable=*/true), | |
| ::arrow::field("s5", | |
| ::arrow::struct_({::arrow::field( | |
| "s6", | |
| ::arrow::struct_({::arrow::field("f", ::arrow::float32(), | |
| /*nullable=*/false)}), | |
| /*nullable=*/false)}), | |
| /*nullable=*/true), | |
| }); | |
| ASSERT_TRUE(::arrow::ExportType(*arrow_data_type_, &arrow_c_schema_).ok()); | |
| } | |
| void TearDown() override { | |
| if (arrow_c_schema_.release != nullptr) { | |
| ArrowSchemaRelease(&arrow_c_schema_); | |
| } | |
| } | |
| void TestData(const std::string& json_data, Evaluator& evaluator, | |
| bool expected_result) { | |
| auto arrow_array = | |
| ::arrow::json::ArrayFromJSONString(arrow_data_type_, json_data).ValueOrDie(); | |
| ASSERT_EQ(arrow_array->length(), 1) | |
| << "Expected 1 row, got " << arrow_array->length(); | |
| ArrowArray arrow_c_array; | |
| internal::ArrowArrayGuard array_guard(&arrow_c_array); | |
| ASSERT_TRUE(::arrow::ExportArray(*arrow_array, &arrow_c_array).ok()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto struct_like, | |
| ArrowArrayStructLike::Make(arrow_c_schema_, arrow_c_array, 0)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, evaluator.Evaluate(*struct_like)); | |
| ASSERT_EQ(result, expected_result); | |
| } | |
| std::unique_ptr<Schema> schema_; | |
| std::shared_ptr<::arrow::DataType> arrow_data_type_; | |
| ArrowSchema arrow_c_schema_; | |
| }; | |
| TEST_F(EvaluatorTest, LessThan) { | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto evaluator, | |
| Evaluator::Make(*schema_, Expressions::LessThan("x", Literal::Int(7)))); | |
| // 7 < 7 => false | |
| TestData(R"([{"x": 7, "y": 8.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| false); | |
| // 6 < 7 => true | |
| TestData(R"([{"x": 6, "y": 8.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| true); | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto struct_evaluator, | |
| Evaluator::Make(*schema_, Expressions::LessThan("s1.s2.s3.s4.i", Literal::Int(7)))); | |
| // 7 < 7 => false | |
| TestData( | |
| R"([{"x": 7, "y": 8.0, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 7}}}}, "s5": null}])", | |
| *struct_evaluator, false); | |
| // 6 < 7 => true | |
| TestData( | |
| R"([{"x": 6, "y": 8.0, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 6}}}}, "s5": null}])", | |
| *struct_evaluator, true); | |
| } | |
| TEST_F(EvaluatorTest, LessThanOrEqual) { | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto evaluator, | |
| Evaluator::Make(*schema_, Expressions::LessThanOrEqual("x", Literal::Int(7)))); | |
| // 7 <= 7 => true | |
| TestData(R"([{"x": 7, "y": 8.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| true); | |
| // 6 <= 7 => true | |
| TestData(R"([{"x": 6, "y": 8.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| true); | |
| // 8 <= 7 => false | |
| TestData(R"([{"x": 8, "y": 8.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| false); | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto struct_evaluator, | |
| Evaluator::Make(*schema_, | |
| Expressions::LessThanOrEqual("s1.s2.s3.s4.i", Literal::Int(7)))); | |
| // 7 <= 7 => true | |
| TestData( | |
| R"([{"x": 7, "y": 8.0, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 7}}}}, "s5": null}])", | |
| *struct_evaluator, true); | |
| // 6 <= 7 => true | |
| TestData( | |
| R"([{"x": 6, "y": 8.0, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 6}}}}, "s5": null}])", | |
| *struct_evaluator, true); | |
| // 8 <= 7 => false | |
| TestData( | |
| R"([{"x": 6, "y": 8.0, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 8}}}}, "s5": null}])", | |
| *struct_evaluator, false); | |
| } | |
| TEST_F(EvaluatorTest, GreaterThan) { | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto evaluator, | |
| Evaluator::Make(*schema_, Expressions::GreaterThan("x", Literal::Int(7)))); | |
| // 7 > 7 => false | |
| TestData(R"([{"x": 7, "y": 8.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| false); | |
| // 6 > 7 => false | |
| TestData(R"([{"x": 6, "y": 8.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| false); | |
| // 8 > 7 => true | |
| TestData(R"([{"x": 8, "y": 8.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| true); | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto struct_evaluator, | |
| Evaluator::Make(*schema_, | |
| Expressions::GreaterThan("s1.s2.s3.s4.i", Literal::Int(7)))); | |
| // 7 > 7 => false | |
| TestData( | |
| R"([{"x": 7, "y": 8.0, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 7}}}}, "s5": null}])", | |
| *struct_evaluator, false); | |
| // 6 > 7 => false | |
| TestData( | |
| R"([{"x": 7, "y": 8.0, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 6}}}}, "s5": null}])", | |
| *struct_evaluator, false); | |
| // 8 > 7 => true | |
| TestData( | |
| R"([{"x": 7, "y": 8.0, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 8}}}}, "s5": null}])", | |
| *struct_evaluator, true); | |
| } | |
| TEST_F(EvaluatorTest, GreaterThanOrEqual) { | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto evaluator, | |
| Evaluator::Make(*schema_, Expressions::GreaterThanOrEqual("x", Literal::Int(7)))); | |
| // 7 >= 7 => true | |
| TestData(R"([{"x": 7, "y": 8.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| true); | |
| // 6 >= 7 => false | |
| TestData(R"([{"x": 6, "y": 8.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| false); | |
| // 8 >= 7 => true | |
| TestData(R"([{"x": 8, "y": 8.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| true); | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto struct_evaluator, | |
| Evaluator::Make(*schema_, | |
| Expressions::GreaterThanOrEqual("s1.s2.s3.s4.i", Literal::Int(7)))); | |
| // 7 >= 7 => true | |
| TestData( | |
| R"([{"x": 7, "y": 8.0, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 7}}}}, "s5": null}])", | |
| *struct_evaluator, true); | |
| // 6 >= 7 => false | |
| TestData( | |
| R"([{"x": 7, "y": 8.0, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 6}}}}, "s5": null}])", | |
| *struct_evaluator, false); | |
| // 8 >= 7 => true | |
| TestData( | |
| R"([{"x": 7, "y": 8.0, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 8}}}}, "s5": null}])", | |
| *struct_evaluator, true); | |
| } | |
| TEST_F(EvaluatorTest, Equal) { | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto evaluator, | |
| Evaluator::Make(*schema_, Expressions::Equal("x", Literal::Int(7)))); | |
| // 7 == 7 => true | |
| TestData(R"([{"x": 7, "y": 8.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| true); | |
| // 6 == 7 => false | |
| TestData(R"([{"x": 6, "y": 8.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| false); | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto struct_evaluator, | |
| Evaluator::Make(*schema_, Expressions::Equal("s1.s2.s3.s4.i", Literal::Int(7)))); | |
| // 7 == 7 => true | |
| TestData( | |
| R"([{"x": 7, "y": 8.0, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 7}}}}, "s5": null}])", | |
| *struct_evaluator, true); | |
| // 6 == 7 => false | |
| TestData( | |
| R"([{"x": 6, "y": 8.0, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 6}}}}, "s5": null}])", | |
| *struct_evaluator, false); | |
| } | |
| TEST_F(EvaluatorTest, NotEqual) { | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto evaluator, | |
| Evaluator::Make(*schema_, Expressions::NotEqual("x", Literal::Int(7)))); | |
| // 7 != 7 => false | |
| TestData(R"([{"x": 7, "y": 8.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| false); | |
| // 6 != 7 => true | |
| TestData(R"([{"x": 6, "y": 8.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| true); | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto struct_evaluator, | |
| Evaluator::Make(*schema_, Expressions::NotEqual("s1.s2.s3.s4.i", Literal::Int(7)))); | |
| // 7 != 7 => false | |
| TestData( | |
| R"([{"x": 7, "y": 8.0, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 7}}}}, "s5": null}])", | |
| *struct_evaluator, false); | |
| // 6 != 7 => true | |
| TestData( | |
| R"([{"x": 6, "y": 8.0, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 6}}}}, "s5": null}])", | |
| *struct_evaluator, true); | |
| } | |
| TEST_F(EvaluatorTest, StartsWith) { | |
| auto string_schema = std::make_unique<Schema>( | |
| std::vector<SchemaField>{SchemaField::MakeOptional(24, "s", string())}); | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto evaluator, | |
| Evaluator::Make(*string_schema, Expressions::StartsWith("s", "abc"))); | |
| auto arrow_string_type = ::arrow::struct_({::arrow::field("s", ::arrow::utf8())}); | |
| auto arrow_string_array = ::arrow::json::ArrayFromJSONString(arrow_string_type, R"([ | |
| {"s": "abc"}, | |
| {"s": "xabc"}, | |
| {"s": "Abc"}, | |
| {"s": "a"}, | |
| {"s": "abcd"}, | |
| {"s": null} | |
| ])") | |
| .ValueOrDie(); | |
| ArrowSchema c_schema; | |
| ArrowArray c_array; | |
| internal::ArrowSchemaGuard schema_guard(&c_schema); | |
| internal::ArrowArrayGuard array_guard(&c_array); | |
| ASSERT_TRUE(::arrow::ExportType(*arrow_string_type, &c_schema).ok()); | |
| ASSERT_TRUE(::arrow::ExportArray(*arrow_string_array, &c_array).ok()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto struct_like, | |
| ArrowArrayStructLike::Make(c_schema, c_array, /*row_index=*/0)); | |
| // abc startsWith abc => true | |
| ASSERT_THAT(struct_like->Reset(0), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, evaluator->Evaluate(*struct_like)); | |
| EXPECT_TRUE(result); | |
| // xabc startsWith abc => false | |
| ASSERT_THAT(struct_like->Reset(1), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(result, evaluator->Evaluate(*struct_like)); | |
| EXPECT_FALSE(result); | |
| // Abc startsWith abc => false | |
| ASSERT_THAT(struct_like->Reset(2), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(result, evaluator->Evaluate(*struct_like)); | |
| EXPECT_FALSE(result); | |
| // a startsWith abc => false | |
| ASSERT_THAT(struct_like->Reset(3), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(result, evaluator->Evaluate(*struct_like)); | |
| EXPECT_FALSE(result); | |
| // abcd startsWith abc => true | |
| ASSERT_THAT(struct_like->Reset(4), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(result, evaluator->Evaluate(*struct_like)); | |
| EXPECT_TRUE(result); | |
| // null startsWith abc => false | |
| ASSERT_THAT(struct_like->Reset(5), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(result, evaluator->Evaluate(*struct_like)); | |
| EXPECT_FALSE(result); | |
| } | |
| TEST_F(EvaluatorTest, NotStartsWith) { | |
| auto string_schema = std::make_unique<Schema>( | |
| std::vector<SchemaField>{SchemaField::MakeRequired(24, "s", string())}); | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto evaluator, | |
| Evaluator::Make(*string_schema, Expressions::NotStartsWith("s", "abc"))); | |
| auto arrow_string_type = ::arrow::struct_({::arrow::field("s", ::arrow::utf8())}); | |
| auto arrow_string_array = ::arrow::json::ArrayFromJSONString(arrow_string_type, R"([ | |
| {"s": "abc"}, | |
| {"s": "xabc"}, | |
| {"s": "Abc"}, | |
| {"s": "a"}, | |
| {"s": "abcde"}, | |
| {"s": "Abcde"} | |
| ])") | |
| .ValueOrDie(); | |
| ArrowSchema c_schema; | |
| ArrowArray c_array; | |
| internal::ArrowSchemaGuard schema_guard(&c_schema); | |
| internal::ArrowArrayGuard array_guard(&c_array); | |
| ASSERT_TRUE(::arrow::ExportType(*arrow_string_type, &c_schema).ok()); | |
| ASSERT_TRUE(::arrow::ExportArray(*arrow_string_array, &c_array).ok()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto struct_like, | |
| ArrowArrayStructLike::Make(c_schema, c_array, /*row_index=*/0)); | |
| // abc notStartsWith abc => false | |
| ASSERT_THAT(struct_like->Reset(0), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, evaluator->Evaluate(*struct_like)); | |
| EXPECT_FALSE(result); | |
| // xabc notStartsWith abc => true | |
| ASSERT_THAT(struct_like->Reset(1), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(result, evaluator->Evaluate(*struct_like)); | |
| EXPECT_TRUE(result); | |
| // Abc notStartsWith abc => true | |
| ASSERT_THAT(struct_like->Reset(2), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(result, evaluator->Evaluate(*struct_like)); | |
| EXPECT_TRUE(result); | |
| // a notStartsWith abc => true | |
| ASSERT_THAT(struct_like->Reset(3), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(result, evaluator->Evaluate(*struct_like)); | |
| EXPECT_TRUE(result); | |
| // abcde notStartsWith abc => false | |
| ASSERT_THAT(struct_like->Reset(4), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(result, evaluator->Evaluate(*struct_like)); | |
| EXPECT_FALSE(result); | |
| // Abcde notStartsWith abc => true | |
| ASSERT_THAT(struct_like->Reset(5), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(result, evaluator->Evaluate(*struct_like)); | |
| EXPECT_TRUE(result); | |
| } | |
| TEST_F(EvaluatorTest, AlwaysTrue) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto evaluator, | |
| Evaluator::Make(*schema_, Expressions::AlwaysTrue())); | |
| TestData(R"([{"x": 7, "y": 8.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| true); | |
| } | |
| TEST_F(EvaluatorTest, AlwaysFalse) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto evaluator, | |
| Evaluator::Make(*schema_, Expressions::AlwaysFalse())); | |
| TestData(R"([{"x": 7, "y": 8.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| false); | |
| } | |
| TEST_F(EvaluatorTest, IsNull) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto evaluator, | |
| Evaluator::Make(*schema_, Expressions::IsNull("z"))); | |
| // null is null => true | |
| TestData(R"([{"x": 1, "y": 2.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| true); | |
| // 3 is not null => false | |
| TestData(R"([{"x": 1, "y": 2.0, "z": 3, "s1": null, "s5": null}])", *evaluator, false); | |
| ICEBERG_UNWRAP_OR_FAIL(auto struct_evaluator, | |
| Evaluator::Make(*schema_, Expressions::IsNull("s1.s2.s3.s4.i"))); | |
| // 3 is not null => false | |
| TestData( | |
| R"([{"x": 1, "y": 2.0, "z": 3, "s1": {"s2": {"s3": {"s4": {"i": 3}}}}, "s5": null}])", | |
| *struct_evaluator, false); | |
| } | |
| TEST_F(EvaluatorTest, NotNull) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto evaluator, | |
| Evaluator::Make(*schema_, Expressions::NotNull("z"))); | |
| // null is null => false | |
| TestData(R"([{"x": 1, "y": 2.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| false); | |
| // 3 is not null => true | |
| TestData(R"([{"x": 1, "y": 2.0, "z": 3, "s1": null, "s5": null}])", *evaluator, true); | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto struct_evaluator, | |
| Evaluator::Make(*schema_, Expressions::NotNull("s1.s2.s3.s4.i"))); | |
| // 3 is not null => true | |
| TestData( | |
| R"([{"x": 1, "y": 2.0, "z": 3, "s1": {"s2": {"s3": {"s4": {"i": 3}}}}, "s5": null}])", | |
| *struct_evaluator, true); | |
| } | |
| TEST_F(EvaluatorTest, IsNaN) { | |
| auto double_schema = std::make_unique<Schema>( | |
| std::vector<SchemaField>{SchemaField::MakeRequired(25, "d", float64())}); | |
| ICEBERG_UNWRAP_OR_FAIL(auto evaluator, | |
| Evaluator::Make(*double_schema, Expressions::IsNaN("d"))); | |
| auto arrow_double_type = ::arrow::struct_({::arrow::field("d", ::arrow::float64())}); | |
| // Build array with NaN and regular values | |
| ::arrow::DoubleBuilder builder; | |
| ASSERT_TRUE(builder.Append(std::numeric_limits<double>::quiet_NaN()).ok()); | |
| ASSERT_TRUE(builder.Append(2.0).ok()); | |
| ASSERT_TRUE(builder.Append(std::numeric_limits<double>::infinity()).ok()); | |
| auto double_array = builder.Finish().ValueOrDie(); | |
| auto struct_array = | |
| ::arrow::StructArray::Make({double_array}, {arrow_double_type->field(0)}) | |
| .ValueOrDie(); | |
| ArrowSchema c_schema; | |
| ArrowArray c_array; | |
| internal::ArrowSchemaGuard schema_guard(&c_schema); | |
| internal::ArrowArrayGuard array_guard(&c_array); | |
| ASSERT_TRUE(::arrow::ExportType(*arrow_double_type, &c_schema).ok()); | |
| ASSERT_TRUE(::arrow::ExportArray(*struct_array, &c_array).ok()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto struct_like, | |
| ArrowArrayStructLike::Make(c_schema, c_array, /*row_index=*/0)); | |
| // NaN is NaN => true | |
| ASSERT_THAT(struct_like->Reset(0), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, evaluator->Evaluate(*struct_like)); | |
| EXPECT_TRUE(result); | |
| // 2.0 is not NaN => false | |
| ASSERT_THAT(struct_like->Reset(1), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(result, evaluator->Evaluate(*struct_like)); | |
| EXPECT_FALSE(result); | |
| // Infinity is not NaN => false | |
| ASSERT_THAT(struct_like->Reset(2), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(result, evaluator->Evaluate(*struct_like)); | |
| EXPECT_FALSE(result); | |
| } | |
| TEST_F(EvaluatorTest, NotNaN) { | |
| auto double_schema = std::make_unique<Schema>( | |
| std::vector<SchemaField>{SchemaField::MakeRequired(25, "d", float64())}); | |
| ICEBERG_UNWRAP_OR_FAIL(auto evaluator, | |
| Evaluator::Make(*double_schema, Expressions::NotNaN("d"))); | |
| auto arrow_double_type = ::arrow::struct_({::arrow::field("d", ::arrow::float64())}); | |
| // Build array with NaN and regular values | |
| ::arrow::DoubleBuilder builder; | |
| ASSERT_TRUE(builder.Append(std::numeric_limits<double>::quiet_NaN()).ok()); | |
| ASSERT_TRUE(builder.Append(2.0).ok()); | |
| ASSERT_TRUE(builder.Append(std::numeric_limits<double>::infinity()).ok()); | |
| auto double_array = builder.Finish().ValueOrDie(); | |
| auto struct_array = | |
| ::arrow::StructArray::Make({double_array}, {arrow_double_type->field(0)}) | |
| .ValueOrDie(); | |
| ArrowSchema c_schema; | |
| ArrowArray c_array; | |
| internal::ArrowSchemaGuard schema_guard(&c_schema); | |
| internal::ArrowArrayGuard array_guard(&c_array); | |
| ASSERT_TRUE(::arrow::ExportType(*arrow_double_type, &c_schema).ok()); | |
| ASSERT_TRUE(::arrow::ExportArray(*struct_array, &c_array).ok()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto struct_like, | |
| ArrowArrayStructLike::Make(c_schema, c_array, /*row_index=*/0)); | |
| // NaN is NaN => false | |
| ASSERT_THAT(struct_like->Reset(0), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, evaluator->Evaluate(*struct_like)); | |
| EXPECT_FALSE(result); | |
| // 2.0 is not NaN => true | |
| ASSERT_THAT(struct_like->Reset(1), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(result, evaluator->Evaluate(*struct_like)); | |
| EXPECT_TRUE(result); | |
| // Infinity is not NaN => true | |
| ASSERT_THAT(struct_like->Reset(2), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(result, evaluator->Evaluate(*struct_like)); | |
| EXPECT_TRUE(result); | |
| } | |
| TEST_F(EvaluatorTest, And) { | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto evaluator, | |
| Evaluator::Make(*schema_, Expressions::And(Expressions::Equal("x", Literal::Int(7)), | |
| Expressions::NotNull("z")))); | |
| // 7, 3 => true | |
| TestData(R"([{"x": 7, "y": 0.0, "z": 3, "s1": null, "s5": null}])", *evaluator, true); | |
| // 8, 3 => false | |
| TestData(R"([{"x": 8, "y": 0.0, "z": 3, "s1": null, "s5": null}])", *evaluator, false); | |
| // 7, null => false | |
| TestData(R"([{"x": 7, "y": 0.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| false); | |
| // 8, null => false | |
| TestData(R"([{"x": 8, "y": 0.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| false); | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto struct_evaluator, | |
| Evaluator::Make( | |
| *schema_, Expressions::And(Expressions::Equal("s1.s2.s3.s4.i", Literal::Int(7)), | |
| Expressions::NotNull("s1.s2.s3.s4.i")))); | |
| // 7, 7 => true | |
| TestData( | |
| R"([{"x": 7, "y": 0.0, "z": 3, "s1": {"s2": {"s3": {"s4": {"i": 7}}}}, "s5": null}])", | |
| *struct_evaluator, true); | |
| // 8, 8 => false | |
| TestData( | |
| R"([{"x": 8, "y": 0.0, "z": 3, "s1": {"s2": {"s3": {"s4": {"i": 8}}}}, "s5": null}])", | |
| *struct_evaluator, false); | |
| // 8, 8 => false (different x value) | |
| TestData( | |
| R"([{"x": 8, "y": 0.0, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 8}}}}, "s5": null}])", | |
| *struct_evaluator, false); | |
| } | |
| TEST_F(EvaluatorTest, Or) { | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto evaluator, | |
| Evaluator::Make(*schema_, Expressions::Or(Expressions::Equal("x", Literal::Int(7)), | |
| Expressions::NotNull("z")))); | |
| // 7, 3 => true | |
| TestData(R"([{"x": 7, "y": 0.0, "z": 3, "s1": null, "s5": null}])", *evaluator, true); | |
| // 8, 3 => true | |
| TestData(R"([{"x": 8, "y": 0.0, "z": 3, "s1": null, "s5": null}])", *evaluator, true); | |
| // 7, null => true | |
| TestData(R"([{"x": 7, "y": 0.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| true); | |
| // 8, null => false | |
| TestData(R"([{"x": 8, "y": 0.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| false); | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto struct_evaluator, | |
| Evaluator::Make( | |
| *schema_, Expressions::Or(Expressions::Equal("s1.s2.s3.s4.i", Literal::Int(7)), | |
| Expressions::NotNull("s1.s2.s3.s4.i")))); | |
| // 7, 7 => true | |
| TestData( | |
| R"([{"x": 7, "y": 0.0, "z": 3, "s1": {"s2": {"s3": {"s4": {"i": 7}}}}, "s5": null}])", | |
| *struct_evaluator, true); | |
| // 8, 8 => true | |
| TestData( | |
| R"([{"x": 8, "y": 0.0, "z": 3, "s1": {"s2": {"s3": {"s4": {"i": 8}}}}, "s5": null}])", | |
| *struct_evaluator, true); | |
| // 7, notnull => true | |
| TestData( | |
| R"([{"x": 7, "y": 0.0, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 7}}}}, "s5": null}])", | |
| *struct_evaluator, true); | |
| } | |
| TEST_F(EvaluatorTest, Not) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto evaluator, | |
| Evaluator::Make(*schema_, Expressions::Not(Expressions::Equal( | |
| "x", Literal::Int(7))))); | |
| // not(7 == 7) => false | |
| TestData(R"([{"x": 7, "y": 0.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| false); | |
| // not(8 == 7) => true | |
| TestData(R"([{"x": 8, "y": 0.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| true); | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto struct_evaluator, | |
| Evaluator::Make(*schema_, Expressions::Not(Expressions::Equal("s1.s2.s3.s4.i", | |
| Literal::Int(7))))); | |
| // not(7 == 7) => false | |
| TestData( | |
| R"([{"x": 7, "y": null, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 7}}}}, "s5": null}])", | |
| *struct_evaluator, false); | |
| // not(8 == 7) => true | |
| TestData( | |
| R"([{"x": 8, "y": null, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 8}}}}, "s5": null}])", | |
| *struct_evaluator, true); | |
| } | |
| TEST_F(EvaluatorTest, CaseInsensitiveNot) { | |
| // Use case-insensitive binding (false) | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto evaluator, | |
| Evaluator::Make(*schema_, | |
| Expressions::Not(Expressions::Equal("X", Literal::Int(7))), | |
| /*case_sensitive=*/false)); | |
| // not(7 == 7) => false | |
| TestData(R"([{"x": 7, "y": 0.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| false); | |
| // not(8 == 7) => true | |
| TestData(R"([{"x": 8, "y": 0.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| true); | |
| // Test with nested struct | |
| ICEBERG_UNWRAP_OR_FAIL(auto struct_evaluator, | |
| Evaluator::Make(*schema_, | |
| Expressions::Not(Expressions::Equal( | |
| "s1.s2.s3.s4.i", Literal::Int(7))), | |
| /*case_sensitive=*/false)); | |
| // not(7 == 7) => false | |
| TestData( | |
| R"([{"x": 7, "y": null, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 7}}}}, "s5": null}])", | |
| *struct_evaluator, false); | |
| // not(8 == 7) => true | |
| TestData( | |
| R"([{"x": 8, "y": null, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 8}}}}, "s5": null}])", | |
| *struct_evaluator, true); | |
| } | |
| TEST_F(EvaluatorTest, CaseSensitiveNot) { | |
| // Should fail to bind with case-sensitive matching | |
| auto result = Evaluator::Make( | |
| *schema_, Expressions::Not(Expressions::Equal("X", Literal::Int(7))), | |
| /*case_sensitive=*/true); | |
| EXPECT_THAT(result, IsError(ErrorKind::kInvalidExpression)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot find field 'X'")); | |
| } | |
| TEST_F(EvaluatorTest, In) { | |
| ASSERT_EQ(Expressions::In("s", {Literal::Int(7), Literal::Int(8), Literal::Int(9)}) | |
| ->literals() | |
| .size(), | |
| size_t{3}); | |
| ASSERT_EQ(Expressions::In("s", {Literal::Int(7), Literal::Double(8.1), | |
| Literal::Long(std::numeric_limits<int64_t>::max())}) | |
| ->literals() | |
| .size(), | |
| size_t{3}); | |
| ASSERT_EQ(Expressions::In("s", {Literal::String("abc"), Literal::String("abd"), | |
| Literal::String("abc")}) | |
| ->literals() | |
| .size(), | |
| size_t{3}); | |
| ASSERT_EQ(Expressions::In("s", {Literal::Int(5)})->literals().size(), size_t{1}); | |
| ASSERT_EQ(Expressions::In("s", {Literal::Int(5), Literal::Int(5)})->literals().size(), | |
| size_t{2}); | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto evaluator, | |
| Evaluator::Make( | |
| *schema_, | |
| Expressions::In("x", {Literal::Int(7), Literal::Int(8), | |
| Literal::Long(std::numeric_limits<int64_t>::max())}))); | |
| // 7 in [7, 8] => true | |
| TestData(R"([{"x": 7, "y": 8.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| true); | |
| // 9 in [7, 8] => false | |
| TestData(R"([{"x": 9, "y": 8.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| false); | |
| // Test with integer field | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto integer_evaluator, | |
| Evaluator::Make( | |
| *schema_, | |
| Expressions::In("x", {Literal::Long(std::numeric_limits<int64_t>::max()), | |
| Literal::Int(std::numeric_limits<int32_t>::max()), | |
| Literal::Long(std::numeric_limits<int64_t>::min())}))); | |
| // Integer.MAX_VALUE in [Integer.MAX_VALUE] => true | |
| TestData(R"([{"x": 2147483647, "y": 8.0, "z": null, "s1": null, "s5": null}])", | |
| *integer_evaluator, true); | |
| // 6 in [Integer.MAX_VALUE] => false | |
| TestData(R"([{"x": 6, "y": 6.8, "z": null, "s1": null, "s5": null}])", | |
| *integer_evaluator, false); | |
| // Test with double field | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto double_evaluator, | |
| Evaluator::Make(*schema_, Expressions::In("y", {Literal::Int(7), Literal::Int(8), | |
| Literal::Double(9.1)}))); | |
| // 7.0 in [7, 8, 9.1] => true | |
| TestData(R"([{"x": 0, "y": 7.0, "z": null, "s1": null, "s5": null}])", | |
| *double_evaluator, true); | |
| // 9.1 in [7, 8, 9.1] => true | |
| TestData(R"([{"x": 7, "y": 9.1, "z": null, "s1": null, "s5": null}])", | |
| *double_evaluator, true); | |
| // 6.8 in [7, 8, 9.1] => false | |
| TestData(R"([{"x": 6, "y": 6.8, "z": null, "s1": null, "s5": null}])", | |
| *double_evaluator, false); | |
| // Test with nested struct | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto struct_evaluator, | |
| Evaluator::Make(*schema_, | |
| Expressions::In("s1.s2.s3.s4.i", {Literal::Int(7), Literal::Int(8), | |
| Literal::Int(9)}))); | |
| // 7 in [7, 8, 9] => true | |
| TestData( | |
| R"([{"x": 7, "y": 8.0, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 7}}}}, "s5": null}])", | |
| *struct_evaluator, true); | |
| // 6 in [7, 8, 9] => false | |
| TestData( | |
| R"([{"x": 6, "y": 8.0, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 6}}}}, "s5": null}])", | |
| *struct_evaluator, false); | |
| } | |
| TEST_F(EvaluatorTest, NotIn) { | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto evaluator, | |
| Evaluator::Make( | |
| *schema_, | |
| Expressions::NotIn("x", {Literal::Int(7), Literal::Int(8), | |
| Literal::Long(std::numeric_limits<int64_t>::max())}))); | |
| // 7 not in [7, 8] => false | |
| TestData(R"([{"x": 7, "y": 8.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| false); | |
| // 9 not in [7, 8] => true | |
| TestData(R"([{"x": 9, "y": 8.0, "z": null, "s1": null, "s5": null}])", *evaluator, | |
| true); | |
| // Test with double field | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto double_evaluator, | |
| Evaluator::Make(*schema_, Expressions::NotIn("y", {Literal::Int(7), Literal::Int(8), | |
| Literal::Double(9.1)}))); | |
| // 7.0 not in [7, 8, 9.1] => false | |
| TestData(R"([{"x": 0, "y": 7.0, "z": null, "s1": null, "s5": null}])", | |
| *double_evaluator, false); | |
| // 9.1 not in [7, 8, 9.1] => false | |
| TestData(R"([{"x": 7, "y": 9.1, "z": null, "s1": null, "s5": null}])", | |
| *double_evaluator, false); | |
| // 6.8 not in [7, 8, 9.1] => true | |
| TestData(R"([{"x": 6, "y": 6.8, "z": null, "s1": null, "s5": null}])", | |
| *double_evaluator, true); | |
| // Test with nested struct | |
| ICEBERG_UNWRAP_OR_FAIL( | |
| auto struct_evaluator, | |
| Evaluator::Make( | |
| *schema_, Expressions::NotIn("s1.s2.s3.s4.i", {Literal::Int(7), Literal::Int(8), | |
| Literal::Int(9)}))); | |
| // 7 not in [7, 8, 9] => false | |
| TestData( | |
| R"([{"x": 7, "y": 8.0, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 7}}}}, "s5": null}])", | |
| *struct_evaluator, false); | |
| // 6 not in [7, 8, 9] => true | |
| TestData( | |
| R"([{"x": 6, "y": 8.0, "z": null, "s1": {"s2": {"s3": {"s4": {"i": 6}}}}, "s5": null}])", | |
| *struct_evaluator, true); | |
| } | |
| TEST_F(EvaluatorTest, InExceptions) { | |
| { | |
| auto result = Evaluator::Make( | |
| *schema_, | |
| Expressions::In("x", {Literal::Int(7), Literal::Int(8), Literal::Null(int32())}), | |
| /*case_sensitive=*/false); | |
| EXPECT_THAT(result, IsError(ErrorKind::kInvalidExpression)); | |
| EXPECT_THAT(result, | |
| HasErrorMessage("Invalid value for conversion to type int: null (int)")); | |
| } | |
| { | |
| auto result = Evaluator::Make( | |
| *schema_, | |
| Expressions::In("x", {Literal::Int(7), Literal::Int(8), Literal::Double(9.1)}), | |
| /*case_sensitive=*/false); | |
| EXPECT_THAT(result, IsError(ErrorKind::kNotSupported)); | |
| EXPECT_THAT(result, HasErrorMessage("Cast from Double to int is not supported")); | |
| } | |
| { | |
| auto result = UnboundPredicateImpl<BoundReference>::Make(Expression::Operation::kIn, | |
| Expressions::Ref("x"), {}); | |
| EXPECT_THAT(result, IsError(ErrorKind::kInvalidExpression)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot create IN predicate without a value")); | |
| } | |
| } | |
| } // namespace iceberg |