This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-cpp.git
The following commit(s) were added to refs/heads/main by this push:
new c814882 test(orc): add unit tests for orc format (#136)
c814882 is described below
commit c8148825833e12b2d42ba9693e5a549e80a2c303
Author: lszskye <[email protected]>
AuthorDate: Thu Jul 2 19:17:17 2026 -0700
test(orc): add unit tests for orc format (#136)
---
src/paimon/format/orc/complex_predicate_test.cpp | 310 ++++++++++
src/paimon/format/orc/orc_adapter_test.cpp | 669 ++++++++++++++++++++++
src/paimon/format/orc/predicate_pushdown_test.cpp | 666 +++++++++++++++++++++
3 files changed, 1645 insertions(+)
diff --git a/src/paimon/format/orc/complex_predicate_test.cpp
b/src/paimon/format/orc/complex_predicate_test.cpp
new file mode 100644
index 0000000..079cb31
--- /dev/null
+++ b/src/paimon/format/orc/complex_predicate_test.cpp
@@ -0,0 +1,310 @@
+/*
+ * 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 <cstdint>
+#include <map>
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "arrow/api.h"
+#include "arrow/array/array_base.h"
+#include "arrow/array/array_nested.h"
+#include "arrow/c/abi.h"
+#include "arrow/c/bridge.h"
+#include "arrow/ipc/json_simple.h"
+#include "gtest/gtest.h"
+#include "orc/OrcFile.hh"
+#include "paimon/common/utils/date_time_utils.h"
+#include "paimon/common/utils/decimal_utils.h"
+#include "paimon/data/decimal.h"
+#include "paimon/data/timestamp.h"
+#include "paimon/defs.h"
+#include "paimon/format/orc/orc_file_batch_reader.h"
+#include "paimon/format/orc/orc_format_defs.h"
+#include "paimon/format/orc/orc_input_stream_impl.h"
+#include "paimon/fs/file_system.h"
+#include "paimon/fs/local/local_file_system.h"
+#include "paimon/memory/memory_pool.h"
+#include "paimon/predicate/literal.h"
+#include "paimon/predicate/predicate_builder.h"
+#include "paimon/result.h"
+#include "paimon/status.h"
+#include "paimon/testing/utils/read_result_collector.h"
+#include "paimon/testing/utils/testharness.h"
+#include "paimon/utils/roaring_bitmap32.h"
+
+namespace paimon {
+class Predicate;
+} // namespace paimon
+
+namespace paimon::orc::test {
+
+// test predicate push down for decimal & timestamp & date
+class ComplexPredicateTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ pool_ = GetDefaultPool();
+ batch_size_ = 10;
+ }
+ void TearDown() override {}
+
+ std::unique_ptr<OrcFileBatchReader> PrepareOrcFileBatchReader(
+ const std::string& file_name, const arrow::Schema* read_schema,
+ const std::shared_ptr<Predicate>& predicate, int32_t batch_size) {
+ std::shared_ptr<FileSystem> file_system =
std::make_shared<LocalFileSystem>();
+ EXPECT_OK_AND_ASSIGN(std::shared_ptr<InputStream> input_stream,
+ file_system->Open(file_name));
+ EXPECT_TRUE(input_stream);
+ EXPECT_OK_AND_ASSIGN(auto in_stream,
+ OrcInputStreamImpl::Create(input_stream,
DEFAULT_NATURAL_READ_SIZE));
+ EXPECT_TRUE(in_stream);
+ EXPECT_OK_AND_ASSIGN(
+ auto orc_batch_reader,
+ OrcFileBatchReader::Create(std::move(in_stream), pool_,
+
/*options=*/{{"orc.timestamp-ltz.legacy.type", "false"}},
+ batch_size));
+ EXPECT_TRUE(orc_batch_reader);
+ std::unique_ptr<ArrowSchema> c_schema =
std::make_unique<ArrowSchema>();
+ auto arrow_status = arrow::ExportSchema(*read_schema, c_schema.get());
+ EXPECT_TRUE(arrow_status.ok());
+ EXPECT_OK(orc_batch_reader->SetReadSchema(c_schema.get(), predicate,
+
/*selection_bitmap=*/std::nullopt));
+ return orc_batch_reader;
+ }
+
+ void CheckResult(const std::string& file_name,
+ const std::shared_ptr<arrow::Schema>& read_schema,
+ const std::shared_ptr<Predicate>& predicate,
+ const std::shared_ptr<arrow::Array>& expected_array) {
+ auto orc_batch_reader =
+ PrepareOrcFileBatchReader(file_name, read_schema.get(), predicate,
batch_size_);
+ ASSERT_OK_AND_ASSIGN(auto arrow_array,
paimon::test::ReadResultCollector::CollectResult(
+ orc_batch_reader.get()));
+ // check result
+ if (expected_array) {
+ ASSERT_TRUE(arrow_array);
+ auto expected_chunk_array =
std::make_shared<arrow::ChunkedArray>(expected_array);
+ ASSERT_TRUE(expected_chunk_array->Equals(arrow_array));
+ } else {
+ ASSERT_FALSE(arrow_array);
+ }
+ }
+
+ private:
+ std::shared_ptr<MemoryPool> pool_;
+ int32_t batch_size_;
+};
+
+TEST_F(ComplexPredicateTest, TestSimple) {
+ std::string file_name = paimon::test::GetDataDir() +
+
"/orc/append_complex_data.db/append_complex_data/f1=10/bucket-0/"
+ "data-14a30421-7650-486c-9876-66a1fa4356ff-0.orc";
+ arrow::FieldVector fields = {
+ arrow::field("f1", arrow::int32()),
+ arrow::field("f2", arrow::int32()),
+ arrow::field("f3", arrow::date32()),
+ arrow::field("f4", arrow::timestamp(arrow::TimeUnit::NANO)),
+ arrow::field("f5", arrow::decimal128(23, 5)),
+ };
+ auto read_schema = arrow::schema(fields);
+ auto expected_array = std::dynamic_pointer_cast<arrow::StructArray>(
+ arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(fields), R"([
+ [10, 1, 1234, "2033-05-18 03:33:20.0",
"123456789987654321.45678"],
+ [10, 1, 19909, "2033-05-18 03:33:20.000001001", "12.30000"],
+ [10, 1, 0, "2008-12-28 00:00:00.000123456", null],
+ [10, 1, 100, "2008-12-28 00:00:00.00012345", "-123.45000"],
+ [10, 1, null, "1899-01-01 00:59:20.001001001", "0.00000"],
+ [10, 1, 20006, "2024-10-10 10:10:10.100100100", "1728551410100.10010"]
+ ])")
+ .ValueOrDie());
+
+ // date
+ {
+ auto predicate = PredicateBuilder::Equal(/*field_index=*/2,
/*field_name=*/"f3",
+ FieldType::DATE,
Literal(FieldType::DATE, 4));
+ CheckResult(file_name, read_schema, predicate, expected_array);
+ }
+ {
+ auto predicate = PredicateBuilder::Equal(/*field_index=*/2,
/*field_name=*/"f3",
+ FieldType::DATE,
Literal(FieldType::DATE, -111));
+ CheckResult(file_name, read_schema, predicate,
/*expected_array=*/nullptr);
+ }
+ {
+ auto predicate = PredicateBuilder::LessThan(
+ /*field_index=*/2, /*field_name=*/"f3", FieldType::DATE,
+ Literal(FieldType::DATE, 20006));
+ CheckResult(file_name, read_schema, predicate, expected_array);
+ }
+ {
+ auto predicate = PredicateBuilder::GreaterThan(
+ /*field_index=*/2, /*field_name=*/"f3", FieldType::DATE,
+ Literal(FieldType::DATE, 20006));
+ CheckResult(file_name, read_schema, predicate,
/*expected_array=*/nullptr);
+ }
+
+ // timestamp
+ {
+ auto predicate =
+ PredicateBuilder::Equal(/*field_index=*/3, /*field_name=*/"f4",
FieldType::TIMESTAMP,
+ Literal(Timestamp(1230422400000l,
999999)));
+ CheckResult(file_name, read_schema, predicate, expected_array);
+ }
+ {
+ auto predicate =
+ PredicateBuilder::Equal(/*field_index=*/3, /*field_name=*/"f4",
FieldType::TIMESTAMP,
+ Literal(Timestamp(2240521239999l, 0)));
+ CheckResult(file_name, read_schema, predicate,
/*expected_array=*/nullptr);
+ }
+ {
+ auto predicate =
+ PredicateBuilder::LessThan(/*field_index=*/3, /*field_name=*/"f4",
FieldType::TIMESTAMP,
+ Literal(Timestamp(1230422400000l,
123460)));
+ CheckResult(file_name, read_schema, predicate, expected_array);
+ }
+ {
+ auto predicate = PredicateBuilder::GreaterThan(/*field_index=*/3,
/*field_name=*/"f4",
+ FieldType::TIMESTAMP,
+
Literal(Timestamp(2000000000000l, 1001)));
+ CheckResult(file_name, read_schema, predicate,
/*expected_array=*/nullptr);
+ }
+
+ // decimal
+ {
+ auto predicate = PredicateBuilder::Equal(
+ /*field_index=*/4, /*field_name=*/"f5", FieldType::DECIMAL,
+ Literal(Decimal(23, 5, 123456)));
+ CheckResult(file_name, read_schema, predicate, expected_array);
+ }
+ {
+ auto predicate = PredicateBuilder::Equal(
+ /*field_index=*/4, /*field_name=*/"f5", FieldType::DECIMAL,
+ Literal(Decimal(22, 3, -123456)));
+ CheckResult(file_name, read_schema, predicate,
/*expected_array=*/nullptr);
+ }
+ {
+ auto predicate = PredicateBuilder::LessThan(
+ /*field_index=*/4, /*field_name=*/"f5", FieldType::DECIMAL,
+ Literal(Decimal(23, 3,
DecimalUtils::StrToInt128("123456789987654321567").value())));
+ CheckResult(file_name, read_schema, predicate, expected_array);
+ }
+ {
+ auto predicate = PredicateBuilder::GreaterThan(
+ /*field_index=*/4, /*field_name=*/"f5", FieldType::DECIMAL,
+ Literal(Decimal(23, 3,
DecimalUtils::StrToInt128("123456789987654321567").value())));
+ CheckResult(file_name, read_schema, predicate,
/*expected_array=*/nullptr);
+ }
+ {
+ auto predicate = PredicateBuilder::In(
+ /*field_index=*/4, /*field_name=*/"f5", FieldType::DECIMAL,
+ {Literal(Decimal(23, 5,
DecimalUtils::StrToInt128("-12345678998765432134567").value())),
+ Literal(Decimal(23, 5, 1234567))});
+ CheckResult(file_name, read_schema, predicate, expected_array);
+ }
+ {
+ auto predicate = PredicateBuilder::NotIn(
+ /*field_index=*/4, /*field_name=*/"f5", FieldType::DECIMAL,
+ {Literal(Decimal(23, 5,
DecimalUtils::StrToInt128("-12345678998765432134567").value())),
+ Literal(Decimal(23, 5, 1234567))});
+ CheckResult(file_name, read_schema, predicate, expected_array);
+ }
+}
+
+TEST_F(ComplexPredicateTest, TestTimestampType) {
+ auto timezone = DateTimeUtils::GetLocalTimezoneName();
+ std::string file_name = paimon::test::GetDataDir() +
+
"/orc/append_with_multiple_ts_precision_and_timezone.db/"
+
"append_with_multiple_ts_precision_and_timezone/bucket-0/"
+ "data-3f58c403-1672-49a3-93c0-d90cfff9bd8a-0.orc";
+ arrow::FieldVector fields = {
+ arrow::field("ts_sec", arrow::timestamp(arrow::TimeUnit::SECOND)),
+ arrow::field("ts_milli", arrow::timestamp(arrow::TimeUnit::MILLI)),
+ arrow::field("ts_micro", arrow::timestamp(arrow::TimeUnit::MICRO)),
+ arrow::field("ts_nano", arrow::timestamp(arrow::TimeUnit::NANO)),
+ arrow::field("ts_tz_sec", arrow::timestamp(arrow::TimeUnit::SECOND,
timezone)),
+ arrow::field("ts_tz_milli", arrow::timestamp(arrow::TimeUnit::MILLI,
timezone)),
+ arrow::field("ts_tz_micro", arrow::timestamp(arrow::TimeUnit::MICRO,
timezone)),
+ arrow::field("ts_tz_nano", arrow::timestamp(arrow::TimeUnit::NANO,
timezone)),
+ };
+ auto expected_array = std::dynamic_pointer_cast<arrow::StructArray>(
+ arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(fields), R"([
+["1970-01-01 00:00:01", "1970-01-01 00:00:00.001", "1970-01-01
00:00:00.000001", "1970-01-01 00:00:00.000000001", "1970-01-01 00:00:02",
"1970-01-01 00:00:00.002", "1970-01-01 00:00:00.000002", "1970-01-01
00:00:00.000000002"],
+["1970-01-01 00:00:03", "1970-01-01 00:00:00.003", null, "1970-01-01
00:00:00.000000003", "1970-01-01 00:00:04", "1970-01-01 00:00:00.004",
"1970-01-01 00:00:00.000004", "1970-01-01 00:00:00.000000004"],
+["1970-01-01 00:00:05", "1970-01-01 00:00:00.005", null, null, "1970-01-01
00:00:06", null, "1970-01-01 00:00:00.000006", null]
+ ])")
+ .ValueOrDie());
+ auto read_schema = arrow::schema(fields);
+ {
+ auto predicate = PredicateBuilder::IsNull(/*field_index=*/0,
/*field_name=*/"ts_sec",
+ FieldType::TIMESTAMP);
+ CheckResult(file_name, read_schema, predicate,
/*expected_array=*/nullptr);
+ }
+ {
+ auto predicate = PredicateBuilder::IsNull(/*field_index=*/2,
/*field_name=*/"ts_micro",
+ FieldType::TIMESTAMP);
+ CheckResult(file_name, read_schema, predicate, expected_array);
+ }
+ {
+ auto predicate = PredicateBuilder::IsNotNull(/*field_index=*/2,
/*field_name=*/"ts_micro",
+ FieldType::TIMESTAMP);
+ CheckResult(file_name, read_schema, predicate, expected_array);
+ }
+ {
+ auto predicate = PredicateBuilder::Equal(/*field_index=*/0,
/*field_name=*/"ts_sec",
+ FieldType::TIMESTAMP,
Literal(Timestamp(1000, 0)));
+ CheckResult(file_name, read_schema, predicate, expected_array);
+ }
+ {
+ auto predicate = PredicateBuilder::Equal(/*field_index=*/6,
/*field_name=*/"ts_tz_micro",
+ FieldType::TIMESTAMP,
Literal(Timestamp(0, 2000)));
+ CheckResult(file_name, read_schema, predicate, expected_array);
+ }
+ {
+ auto predicate = PredicateBuilder::NotEqual(
+ /*field_index=*/2, /*field_name=*/"ts_micro", FieldType::TIMESTAMP,
+ Literal(Timestamp(0, 1000)));
+ CheckResult(file_name, read_schema, predicate,
/*expected_array=*/nullptr);
+ }
+ {
+ auto predicate = PredicateBuilder::GreaterThan(
+ /*field_index=*/5, /*field_name=*/"ts_tz_milli",
FieldType::TIMESTAMP,
+ Literal(Timestamp(3, 0)));
+ CheckResult(file_name, read_schema, predicate, expected_array);
+ }
+ {
+ auto predicate = PredicateBuilder::GreaterThan(
+ /*field_index=*/5, /*field_name=*/"ts_tz_milli",
FieldType::TIMESTAMP,
+ Literal(Timestamp(10, 0)));
+ CheckResult(file_name, read_schema, predicate,
/*expected_array=*/nullptr);
+ }
+ {
+ auto predicate = PredicateBuilder::LessThan(/*field_index=*/1,
/*field_name=*/"ts_milli",
+ FieldType::TIMESTAMP,
Literal(Timestamp(2, 0)));
+ CheckResult(file_name, read_schema, predicate, expected_array);
+ }
+ {
+ auto predicate = PredicateBuilder::GreaterThan(
+ /*field_index=*/1, /*field_name=*/"ts_milli", FieldType::TIMESTAMP,
+ Literal(Timestamp(10, 0)));
+ CheckResult(file_name, read_schema, predicate,
/*expected_array=*/nullptr);
+ }
+}
+} // namespace paimon::orc::test
diff --git a/src/paimon/format/orc/orc_adapter_test.cpp
b/src/paimon/format/orc/orc_adapter_test.cpp
new file mode 100644
index 0000000..2678a28
--- /dev/null
+++ b/src/paimon/format/orc/orc_adapter_test.cpp
@@ -0,0 +1,669 @@
+/*
+ * 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 "paimon/format/orc/orc_adapter.h"
+
+#include <cassert>
+#include <cstdint>
+#include <utility>
+#include <vector>
+
+#include "arrow/api.h"
+#include "arrow/array/array_base.h"
+#include "arrow/array/array_nested.h"
+#include "arrow/c/abi.h"
+#include "arrow/c/bridge.h"
+#include "arrow/ipc/json_simple.h"
+#include "arrow/result.h"
+#include "arrow/status.h"
+#include "arrow/type_fwd.h"
+#include "gtest/gtest.h"
+#include "orc/MemoryPool.hh"
+#include "orc/OrcFile.hh"
+#include "orc/Reader.hh"
+#include "orc/Type.hh"
+#include "orc/Vector.hh"
+#include "orc/Writer.hh"
+#include "paimon/common/types/data_field.h"
+#include "paimon/common/utils/date_time_utils.h"
+#include "paimon/format/orc/orc_format_defs.h"
+#include "paimon/format/orc/orc_input_stream_impl.h"
+#include "paimon/format/orc/orc_memory_pool.h"
+#include "paimon/format/orc/orc_output_stream_impl.h"
+#include "paimon/fs/file_system.h"
+#include "paimon/fs/local/local_file_system.h"
+#include "paimon/memory/memory_pool.h"
+#include "paimon/testing/utils/dict_array_converter.h"
+#include "paimon/testing/utils/testharness.h"
+
+namespace paimon::orc::test {
+class OrcAdapterTest : public ::testing::Test,
+ public ::testing::WithParamInterface<std::pair<double,
bool>> {
+ public:
+ void SetUp() override {}
+ void TearDown() override {}
+
+ std::pair<std::unique_ptr<::orc::RowReader>,
std::unique_ptr<::orc::ColumnVectorBatch>>
+ GenerateOrcReadBatch(const std::shared_ptr<arrow::Array>& src_array) const
{
+ auto [dict_key_size_threshold, enable_lazy_decoding] = GetParam();
+ arrow::Schema src_schema(src_array->type()->fields());
+ EXPECT_OK_AND_ASSIGN(std::unique_ptr<::orc::Type> orc_type,
+ OrcAdapter::GetOrcType(src_schema));
+ EXPECT_TRUE(orc_type);
+
+ auto test_root_dir = paimon::test::UniqueTestDirectory::Create();
+ EXPECT_TRUE(test_root_dir);
+ std::string test_root = test_root_dir->Str();
+ std::shared_ptr<FileSystem> file_system =
std::make_shared<LocalFileSystem>();
+ EXPECT_OK(file_system->Mkdirs(test_root));
+ std::string file_name = test_root + "/test.orc";
+
+ EXPECT_OK_AND_ASSIGN(std::shared_ptr<OutputStream> out,
+ file_system->Create(file_name,
/*overwrite=*/true));
+ EXPECT_TRUE(out);
+ std::unique_ptr<OrcOutputStreamImpl> orc_output_stream =
+ OrcOutputStreamImpl::Create(out).value();
+ ::orc::WriterOptions writer_options;
+ writer_options.setUseTightNumericVector(true);
+ writer_options.setDictionaryKeySizeThreshold(dict_key_size_threshold);
+ std::unique_ptr<::orc::Writer> writer =
+ ::orc::createWriter(*orc_type, orc_output_stream.get(),
writer_options);
+ std::unique_ptr<::orc::ColumnVectorBatch> write_batch =
+ writer->createRowBatch(src_array->length());
+ // Convert from arrow array to orc batch
+ EXPECT_OK(OrcAdapter::WriteBatch(src_array, write_batch.get()));
+ writer->add(*write_batch);
+ writer->close();
+ EXPECT_OK(out->Close());
+
+ EXPECT_OK_AND_ASSIGN(std::shared_ptr<InputStream> in,
file_system->Open(file_name));
+ EXPECT_TRUE(in);
+ EXPECT_OK_AND_ASSIGN(auto orc_input_stream,
+ OrcInputStreamImpl::Create(in,
DEFAULT_NATURAL_READ_SIZE));
+ EXPECT_TRUE(orc_input_stream);
+ ::orc::ReaderOptions reader_options;
+ std::unique_ptr<::orc::Reader> reader =
+ ::orc::createReader(std::move(orc_input_stream), reader_options);
+ ::orc::RowReaderOptions options;
+ options.setUseTightNumericVector(true);
+ options.setEnableLazyDecoding(enable_lazy_decoding);
+ std::unique_ptr<::orc::RowReader> row_reader =
reader->createRowReader(options);
+ auto read_batch = row_reader->createRowBatch(src_array->length() + 10);
+ [[maybe_unused]] bool not_eof = row_reader->next(*read_batch);
+ return std::make_pair(std::move(row_reader), std::move(read_batch));
+ }
+};
+
+INSTANTIATE_TEST_SUITE_P(EnableDictionaryAndEnableLazyEncoding, OrcAdapterTest,
+ ::testing::ValuesIn({
+ std::make_pair(0.1, true),
+ std::make_pair(0.1, false),
+ std::make_pair(0.8, true),
+ std::make_pair(0.8, false),
+ }));
+
+TEST_F(OrcAdapterTest, TestGetArrowType) {
+ {
+ // suppose to return null type
+ ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> data_type,
+ OrcAdapter::GetArrowType(nullptr));
+ ASSERT_TRUE(data_type);
+ }
+ {
+ auto timezone = DateTimeUtils::GetLocalTimezoneName();
+ std::unique_ptr<::orc::Type> orc_type =
::orc::Type::buildTypeFromString(
+
"struct<col1:bigint,col2:int,col3:smallint,col4:tinyint,col5:double,col6:float,col7:"
+
"boolean,col8:char(3),col9:varchar(8),col10:string,col11:binary,col12:date,col13:"
+ "timestamp,"
+
"col14:decimal(20,4),col15:decimal(18,5),col16:array<bigint>,col17:map<string,bigint>,"
+ "col18:decimal(10,2),col19:timestamp with local time zone>");
+ ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::DataType> data_type,
+ OrcAdapter::GetArrowType(orc_type.get()));
+ ASSERT_TRUE(data_type);
+ ASSERT_EQ(19, data_type->num_fields());
+ ASSERT_EQ(
+ "struct<col1: int64, col2: int32, col3: int16, col4: int8, col5:
double, col6: float, "
+ "col7: bool, col8: string, col9: string, col10: string, col11:
binary, col12: "
+ "date32[day], col13: timestamp[ns], col14: decimal128(20, 4),
col15: decimal128(18, "
+ "5), col16: list<item: int64>, col17: map<string, int64>, col18:
decimal128(10, 2), "
+ "col19: timestamp[ns, tz=" +
+ timezone + "]>",
+ data_type->ToString());
+ }
+}
+
+TEST_F(OrcAdapterTest, TestGetArrowTypeWithInvalidOrcType) {
+ std::unique_ptr<::orc::Type> orc_type =
+ ::orc::Type::buildTypeFromString("struct<col1:uniontype<int,string>>");
+ ASSERT_TRUE(orc_type);
+ ASSERT_NOK(OrcAdapter::GetArrowType(orc_type.get()));
+}
+
+TEST_F(OrcAdapterTest, TestGetOrcType) {
+ // also test lower and higher case
+ auto timezone = DateTimeUtils::GetLocalTimezoneName();
+ auto col1_field = arrow::field("Col1", arrow::int64());
+ auto col2_field = arrow::field("Col2", arrow::int32());
+ auto col3_field = arrow::field("col3", arrow::int16());
+ auto col4_field = arrow::field("col4", arrow::int8());
+ auto col5_field = arrow::field("col5", arrow::float64());
+ auto col6_field = arrow::field("col6", arrow::float32());
+ auto col7_field = arrow::field("col7", arrow::boolean());
+ auto col8_field = arrow::field("col8", arrow::utf8());
+ auto col9_field = arrow::field("col9", arrow::binary());
+ auto col10_field = arrow::field("col10", arrow::date32());
+ auto col11_field = arrow::field("col11", arrow::decimal128(20, 4));
+ auto col12_field = arrow::field("col12", arrow::decimal128(18, 5));
+ auto col13_field = arrow::field("col13", arrow::list(arrow::int64()));
+ auto col14_field = arrow::field("col14", arrow::map(arrow::utf8(),
arrow::int64()));
+ auto col15_field = arrow::field("col15",
arrow::timestamp(arrow::TimeUnit::NANO));
+ auto col16_field = arrow::field(
+ "col16", arrow::struct_({std::make_shared<arrow::Field>("sub1",
arrow::int8()),
+ std::make_shared<arrow::Field>("sub2",
arrow::int16()),
+ std::make_shared<arrow::Field>("sub3",
arrow::int64())}));
+ auto col17_field = arrow::field("col17",
arrow::timestamp(arrow::TimeUnit::SECOND));
+ auto col18_field = arrow::field("col18",
arrow::timestamp(arrow::TimeUnit::MILLI));
+ auto col19_field = arrow::field("col19",
arrow::timestamp(arrow::TimeUnit::MICRO));
+ auto col20_field = arrow::field("col20",
arrow::timestamp(arrow::TimeUnit::SECOND, timezone));
+ auto col21_field = arrow::field("col21",
arrow::timestamp(arrow::TimeUnit::MILLI, timezone));
+ auto col22_field = arrow::field("col22",
arrow::timestamp(arrow::TimeUnit::MICRO, timezone));
+ auto col23_field = arrow::field("col23",
arrow::timestamp(arrow::TimeUnit::NANO, timezone));
+
+ auto arrow_schema = std::make_shared<arrow::Schema>(arrow::FieldVector(
+ {col1_field, col2_field, col3_field, col4_field, col5_field,
col6_field,
+ col7_field, col8_field, col9_field, col10_field, col11_field,
col12_field,
+ col13_field, col14_field, col15_field, col16_field, col17_field,
col18_field,
+ col19_field, col20_field, col21_field, col22_field, col23_field}));
+ ASSERT_OK_AND_ASSIGN(std::unique_ptr<::orc::Type> orc_type,
+ OrcAdapter::GetOrcType(*arrow_schema));
+ ASSERT_TRUE(orc_type);
+ ASSERT_EQ(
+
"struct<Col1:bigint,Col2:int,col3:smallint,col4:tinyint,col5:double,col6:float,col7:"
+
"boolean,col8:string,col9:binary,col10:date,col11:decimal(20,4),col12:decimal(18,5),col13:"
+
"array<bigint>,col14:map<string,bigint>,col15:timestamp,col16:struct<sub1:tinyint,sub2:"
+
"smallint,sub3:bigint>,col17:timestamp,col18:timestamp,col19:timestamp,col20:timestamp
"
+ "with local time zone,col21:timestamp with local time
zone,col22:timestamp with local time "
+ "zone,col23:timestamp with local time zone>",
+ orc_type->toString());
+}
+
+TEST_F(OrcAdapterTest, TestGetOrcTypeWithInvalidArrowType) {
+ {
+ auto col1_field = arrow::field("col1", arrow::large_utf8());
+ auto arrow_schema = arrow::schema(arrow::FieldVector({col1_field}));
+ ASSERT_NOK(OrcAdapter::GetOrcType(*arrow_schema));
+ }
+ {
+ auto col1_field = arrow::field("col1", arrow::large_binary());
+ auto arrow_schema = arrow::schema(arrow::FieldVector({col1_field}));
+ ASSERT_NOK(OrcAdapter::GetOrcType(*arrow_schema));
+ }
+ {
+ auto col1_field = arrow::field("col1", arrow::uint32());
+ auto arrow_schema = arrow::schema(arrow::FieldVector({col1_field}));
+ ASSERT_NOK(OrcAdapter::GetOrcType(*arrow_schema));
+ }
+ {
+ auto union_type = arrow::sparse_union(
+ {arrow::field("_union_0", arrow::int32()),
arrow::field("_union_1", arrow::utf8())});
+ auto col1_field = arrow::field("col1", union_type);
+ auto arrow_schema = arrow::schema(arrow::FieldVector({col1_field}));
+ ASSERT_NOK(OrcAdapter::GetOrcType(*arrow_schema));
+ }
+ {
+ auto col1_field = arrow::field("col1", arrow::date64());
+ auto arrow_schema = arrow::schema(arrow::FieldVector({col1_field}));
+ ASSERT_NOK(OrcAdapter::GetOrcType(*arrow_schema));
+ }
+}
+
+TEST_F(OrcAdapterTest, TestAppendAndWriteBatchWithSimpleBatch) {
+ int32_t length = 4;
+ arrow::FieldVector fields = {
+ arrow::field("col1", arrow::utf8()), arrow::field("col2",
arrow::int32()),
+ arrow::field("col3", arrow::timestamp(arrow::TimeUnit::NANO)),
+ arrow::field("col4", arrow::boolean()), arrow::field("col5",
arrow::float32())};
+ auto arrow_schema = arrow::schema(fields);
+ std::shared_ptr<arrow::Array> array =
+ arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(fields), R"([
+ [null, null, null, null, null],
+ ["abc", 1, "1970-01-01 00:02:03.123123", false, 1.1],
+ [null, null, null, null, null],
+ ["cba", 3, "1970-01-01 00:00:00.0", true, 3.1]
+ ])")
+ .ValueOrDie();
+
+ // Convert from arrow schema to orc type
+ ASSERT_OK_AND_ASSIGN(std::unique_ptr<::orc::Type> orc_type,
+ OrcAdapter::GetOrcType(*arrow_schema));
+ auto test_root_dir = paimon::test::UniqueTestDirectory::Create();
+ ASSERT_TRUE(test_root_dir);
+ std::string test_root = test_root_dir->Str();
+ std::shared_ptr<FileSystem> file_system =
std::make_shared<LocalFileSystem>();
+ std::string file_name = test_root + "/test.orc";
+ ASSERT_OK_AND_ASSIGN(std::shared_ptr<OutputStream> out,
+ file_system->Create(file_name, /*overwrite=*/true));
+ ASSERT_TRUE(out);
+ ASSERT_OK_AND_ASSIGN(std::unique_ptr<OrcOutputStreamImpl>
orc_output_stream,
+ OrcOutputStreamImpl::Create(out));
+
+ ::orc::WriterOptions writer_options;
+ writer_options.setUseTightNumericVector(true);
+ std::unique_ptr<::orc::Writer> writer =
+ ::orc::createWriter(*orc_type, orc_output_stream.get(),
writer_options);
+ std::unique_ptr<::orc::ColumnVectorBatch> orc_batch =
writer->createRowBatch(length);
+
+ // Convert from arrow array to orc batch
+ ASSERT_OK(OrcAdapter::WriteBatch(array, orc_batch.get()));
+
+ // Convert from orc type to arrow type
+ ASSERT_OK_AND_ASSIGN(auto struct_type_2,
OrcAdapter::GetArrowType(orc_type.get()));
+ ASSERT_TRUE(struct_type_2);
+ // Convert from orc batch to arrow array
+ ASSERT_OK_AND_ASSIGN(
+ std::shared_ptr<arrow::Array> array2,
+ OrcAdapter::AppendBatch(struct_type_2, orc_batch.get(),
arrow::default_memory_pool()));
+ // check array equals to array2
+ ASSERT_TRUE(array->Equals(array2));
+}
+
+TEST_F(OrcAdapterTest, TestAppendAndWriteBatchWithComplexType) {
+ int32_t length = 4;
+ auto pool = arrow::default_memory_pool();
+ // prepare an arrow array with struct<col1:list<item: int64>,
col2:map<int8, int16>,
+ // col3:date32[day],
+ // col4:decimal128(10, 2)>
+ auto list_field = arrow::field("col1", arrow::list(arrow::int64()));
+ auto map_field = arrow::field("col2", arrow::map(arrow::int8(),
arrow::int16()));
+ auto date_field = arrow::field("col3", arrow::date32());
+ auto decimal128_field = arrow::field("col4", arrow::decimal128(10, 2));
+
+ arrow::FieldVector fields = {list_field, map_field, date_field,
decimal128_field};
+ auto arrow_schema = arrow::schema(fields);
+ std::shared_ptr<arrow::Array> array =
+ arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(fields), R"([
+ [null, null, null, null],
+ [[12345, 54321], [[1, 12]], 2345, "0.22"],
+ [[12345], [[1, 12], [-1, -12]], 234, "0.12"],
+ [[], [[1, 12], [-1, -12], [0, 128]], 23, "0.02"]
+ ])")
+ .ValueOrDie();
+
+ // Convert from arrow schema to orc type
+ ASSERT_OK_AND_ASSIGN(std::unique_ptr<::orc::Type> orc_type,
+ OrcAdapter::GetOrcType(*arrow_schema));
+ auto test_root_dir = paimon::test::UniqueTestDirectory::Create();
+ ASSERT_TRUE(test_root_dir);
+ std::string test_root = test_root_dir->Str();
+ std::shared_ptr<FileSystem> file_system =
std::make_shared<LocalFileSystem>();
+ std::string file_name = test_root + "/test.orc";
+ ASSERT_OK_AND_ASSIGN(std::shared_ptr<OutputStream> out,
+ file_system->Create(file_name, /*overwrite=*/true));
+ ASSERT_TRUE(out);
+ ASSERT_OK_AND_ASSIGN(std::unique_ptr<OrcOutputStreamImpl>
orc_output_stream,
+ OrcOutputStreamImpl::Create(out));
+
+ ::orc::WriterOptions writer_options;
+ writer_options.setUseTightNumericVector(true);
+ std::unique_ptr<::orc::Writer> writer =
+ ::orc::createWriter(*orc_type, orc_output_stream.get(),
writer_options);
+ std::unique_ptr<::orc::ColumnVectorBatch> orc_batch =
writer->createRowBatch(length);
+
+ // Convert from arrow array to orc batch
+ ASSERT_OK(OrcAdapter::WriteBatch(array, orc_batch.get()));
+ // Convert from orc type to arrow type
+ ASSERT_OK_AND_ASSIGN(auto struct_type_2,
OrcAdapter::GetArrowType(orc_type.get()));
+ ASSERT_TRUE(struct_type_2);
+ // Convert from orc batch to arrow array
+ ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::Array> array2,
+ OrcAdapter::AppendBatch(struct_type_2,
orc_batch.get(), pool));
+ // check array equals to array2
+ ASSERT_TRUE(array->Equals(array2));
+}
+
+TEST_F(OrcAdapterTest, TestUnImplementedBatch) {
+ int32_t length = 10;
+ auto union_type = arrow::sparse_union(
+ {arrow::field("_union_0", arrow::int32()), arrow::field("_union_1",
arrow::utf8())});
+ auto union_field = arrow::field("col1", union_type);
+
+ auto struct_type = arrow::struct_({union_field});
+ auto arrow_schema = arrow::schema(arrow::FieldVector({union_field}));
+ assert(arrow_schema);
+ std::shared_ptr<arrow::Array> array =
+
arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_({union_field}), R"([
+ [null]
+ ])")
+ .ValueOrDie();
+
+ // Convert from arrow schema to orc type
+ ASSERT_NOK(OrcAdapter::GetOrcType(*arrow_schema));
+
+ auto test_root_dir = paimon::test::UniqueTestDirectory::Create();
+ ASSERT_TRUE(test_root_dir);
+ std::string test_root = test_root_dir->Str();
+ std::shared_ptr<FileSystem> file_system =
std::make_shared<LocalFileSystem>();
+ std::string file_name = test_root + "/test.orc";
+ ASSERT_OK_AND_ASSIGN(std::shared_ptr<OutputStream> out,
+ file_system->Create(file_name, /*overwrite=*/true));
+ ASSERT_TRUE(out);
+ ASSERT_OK_AND_ASSIGN(std::unique_ptr<OrcOutputStreamImpl>
orc_output_stream,
+ OrcOutputStreamImpl::Create(out));
+
+ auto valid_orc_type =
::orc::Type::buildTypeFromString("struct<col1:uniontype<int,string>>");
+ ASSERT_TRUE(valid_orc_type);
+
+ ::orc::WriterOptions writer_options;
+ writer_options.setUseTightNumericVector(true);
+ writer_options.setDictionaryKeySizeThreshold(0.8);
+ std::unique_ptr<::orc::Writer> writer =
+ ::orc::createWriter(*valid_orc_type, orc_output_stream.get(),
writer_options);
+ std::unique_ptr<::orc::ColumnVectorBatch> orc_batch =
writer->createRowBatch(length);
+
+ // Convert from arrow array to orc batch
+ ASSERT_NOK(OrcAdapter::WriteBatch(array, orc_batch.get()));
+}
+
+TEST_P(OrcAdapterTest, TestEmptyBatch) {
+ arrow::FieldVector fields = {arrow::field("f0", arrow::utf8()),
+ arrow::field("f1", arrow::binary())};
+ auto arrow_type = arrow::struct_(fields);
+ auto src_array = std::dynamic_pointer_cast<arrow::StructArray>(
+ arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(fields), R"([
+ ])")
+ .ValueOrDie());
+ auto [orc_reader_holder, read_batch] = GenerateOrcReadBatch(src_array);
+
+ ASSERT_OK_AND_ASSIGN(
+ std::shared_ptr<arrow::Array> target_array,
+ OrcAdapter::AppendBatch(arrow_type, read_batch.get(),
arrow::default_memory_pool()));
+ ASSERT_TRUE(target_array);
+ ASSERT_OK_AND_ASSIGN(auto converted_array,
paimon::test::DictArrayConverter::ConvertDictArray(
+ target_array,
arrow::default_memory_pool()));
+ ASSERT_TRUE(converted_array);
+ ASSERT_TRUE(converted_array->Equals(src_array)) <<
converted_array->ToString();
+ ASSERT_TRUE(src_array->Equals(converted_array));
+}
+
+TEST_P(OrcAdapterTest, TestDictionary) {
+ arrow::FieldVector fields = {
+ arrow::field("f0", arrow::utf8()), arrow::field("f1",
arrow::float32()),
+ arrow::field("f2", arrow::float64()), arrow::field("f3",
arrow::int8()),
+ arrow::field("f4", arrow::int16()), arrow::field("f5",
arrow::int32()),
+ arrow::field("f6", arrow::int64()), arrow::field("f7",
arrow::boolean()),
+ arrow::field("f8", arrow::utf8()), arrow::field("f9",
arrow::boolean())};
+ auto arrow_type = arrow::struct_(fields);
+ auto src_array = std::dynamic_pointer_cast<arrow::StructArray>(
+ arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(fields), R"([
+ ["data", 4.0, 5.1, 10, 100, 1000, 10000, true, "data1", true],
+ ["data", 4.1, 6.2, null, 200, 2000, 20000, null, "data2", false],
+ ["data", 4.2, null, 30, 300, null, 30000, true, "data3", true],
+ ["data", 4.3, 8.4, 40, 400, 40000, 40000, false, "data4", false],
+ ["data", 4.5, 8.4, 40, 400, 40000, 40000, false, "data5", true],
+ ["data", 4.0, 8.0, 70, 500, 50000, 40000, true, "data6", true]
+ ])")
+ .ValueOrDie());
+
+ // test with dict (f0), without dict (f1) orthogonal with
enable_lazy_decoding = {true, false}
+ // all 3 conditions are touched
+ auto [orc_reader_holder, read_batch] = GenerateOrcReadBatch(src_array);
+ ASSERT_OK_AND_ASSIGN(
+ std::shared_ptr<arrow::Array> target_array,
+ OrcAdapter::AppendBatch(arrow_type, read_batch.get(),
arrow::default_memory_pool()));
+ ASSERT_OK_AND_ASSIGN(auto converted_array,
paimon::test::DictArrayConverter::ConvertDictArray(
+ target_array,
arrow::default_memory_pool()));
+ ASSERT_TRUE(converted_array);
+ ASSERT_TRUE(converted_array->Equals(src_array)) <<
converted_array->ToString();
+}
+
+TEST_P(OrcAdapterTest, TestShadowCopyWithBlob) {
+ // when enable_dict & enable_lazy_encoding, meet condition2 in
MakeOrcBackedBinaryBuilder,
+ // return dict array
+ arrow::FieldVector fields = {
+ arrow::field("f0", arrow::utf8()), arrow::field("f1",
arrow::float32()),
+ arrow::field("f2", arrow::float64()), arrow::field("f3",
arrow::int8()),
+ arrow::field("f4", arrow::int16()), arrow::field("f5",
arrow::int32()),
+ arrow::field("f6", arrow::int64()), arrow::field("f7",
arrow::boolean()),
+ arrow::field("f8", arrow::utf8()), arrow::field("f9",
arrow::boolean())};
+ auto arrow_type = arrow::struct_(fields);
+ auto src_array = std::dynamic_pointer_cast<arrow::StructArray>(
+ arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(fields), R"([
+ ["apple", 4.0, 5.1, 10, 100, 1000, 10000, true, "apple", true],
+ ["banana", 4.1, 6.2, null, 200, 2000, 20000, null, "banana", false],
+ [null, 4.2, null, 30, 300, null, 30000, true, "apple", true],
+ ["data", 4.3, 8.4, 40, 400, 40000, 40000, false, "data", false],
+ ["data", 4.5, 8.4, 40, 400, 40000, 40000, false, "data", true],
+ ["data", 4.0, 8.0, 70, 500, 50000, 40000, true, "data", true]
+ ])")
+ .ValueOrDie());
+ auto [orc_reader_holder, read_batch] = GenerateOrcReadBatch(src_array);
+
+ ASSERT_OK_AND_ASSIGN(
+ std::shared_ptr<arrow::Array> target_array,
+ OrcAdapter::AppendBatch(arrow_type, read_batch.get(),
arrow::default_memory_pool()));
+ ASSERT_OK_AND_ASSIGN(auto converted_array,
paimon::test::DictArrayConverter::ConvertDictArray(
+ target_array,
arrow::default_memory_pool()));
+ ASSERT_TRUE(converted_array);
+ ASSERT_TRUE(converted_array->Equals(src_array)) <<
converted_array->ToString();
+ ASSERT_TRUE(src_array->Equals(converted_array));
+}
+
+TEST_P(OrcAdapterTest, TestDeepCopyWithString) {
+ // when enable_dict & disable_lazy encoding, f0 and f1 meet condition3,
+ // degrade to deep copy
+ arrow::FieldVector fields = {arrow::field("f0", arrow::utf8()),
+ arrow::field("f1", arrow::utf8())};
+ auto arrow_type = arrow::struct_(fields);
+ auto src_array = std::dynamic_pointer_cast<arrow::StructArray>(
+ arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(fields), R"([
+ ["apple", "apple"],
+ ["banana", "banana"],
+ [null, "apple"],
+ ["data", "data"],
+ ["data", "data"],
+ ["data", "data"]
+ ])")
+ .ValueOrDie());
+ auto [orc_reader_holder, read_batch] = GenerateOrcReadBatch(src_array);
+
+ ASSERT_OK_AND_ASSIGN(
+ std::shared_ptr<arrow::Array> target_array,
+ OrcAdapter::AppendBatch(arrow_type, read_batch.get(),
arrow::default_memory_pool()));
+ ASSERT_TRUE(target_array);
+ ASSERT_OK_AND_ASSIGN(auto converted_array,
paimon::test::DictArrayConverter::ConvertDictArray(
+ target_array,
arrow::default_memory_pool()));
+ ASSERT_TRUE(converted_array);
+ ASSERT_TRUE(converted_array->Equals(src_array)) <<
converted_array->ToString();
+ ASSERT_TRUE(src_array->Equals(converted_array));
+}
+
+TEST_P(OrcAdapterTest, TestComplexTypeShallowCopyWithBlob) {
+ // when disable_dict, meet condition1 in MakeOrcBackedBinaryBuilder, use
shallow copy with valid
+ // blob
+ arrow::FieldVector fields = {
+ arrow::field("f0", arrow::list(arrow::int32())),
+ arrow::field("f1", arrow::map(arrow::utf8(), arrow::int64())),
+ arrow::field("f2",
+ arrow::struct_({field("sub1", arrow::int64()),
field("sub2", arrow::float64()),
+ field("sub3", arrow::boolean())})),
+ };
+ auto arrow_type = arrow::struct_(fields);
+ auto src_array = std::dynamic_pointer_cast<arrow::StructArray>(
+ arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(fields), R"([
+ null,
+ [[1, 2, 3], [["apple", 3], ["banana", 4]], [10, 10.1,
false]],
+ [[4, 5], [["cat", 5], ["dog", 6], ["mouse", 7]], [20, 20.1,
true]],
+ [[6], [["elephant", 7], ["fox", 8]], [null, 30.1,
true]],
+ [[7], [["giraffe", 9]], [null, 40.1,
true]],
+ [null, [["horse", 10], ["Panda", 11]], [50, 50.1,
null]],
+ [[9], null, [60, 60.1,
false]],
+ [[10, 11, 12], [["rabbit", null], ["tiger", 13]], null]
+ ])")
+ .ValueOrDie());
+
+ auto [orc_reader_holder, read_batch] = GenerateOrcReadBatch(src_array);
+ ASSERT_OK_AND_ASSIGN(
+ std::shared_ptr<arrow::Array> target_array,
+ OrcAdapter::AppendBatch(arrow_type, read_batch.get(),
arrow::default_memory_pool()));
+ ASSERT_TRUE(target_array->Equals(src_array));
+ ASSERT_TRUE(src_array->Equals(target_array));
+}
+
+TEST_P(OrcAdapterTest, TestAppendBatchWithBinary) {
+ arrow::FieldVector fields = {arrow::field("f0", arrow::binary()),
+ arrow::field("f1", arrow::binary())};
+ auto arrow_type = arrow::struct_(fields);
+ auto src_array = std::dynamic_pointer_cast<arrow::StructArray>(
+ arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(fields), R"([
+ ["apple", "apple"],
+ ["banana", "banana"],
+ [null, "apple"],
+ ["data", "data"],
+ ["data", "data"],
+ ["data", "data"]
+ ])")
+ .ValueOrDie());
+ auto [orc_reader_holder, read_batch] = GenerateOrcReadBatch(src_array);
+
+ ASSERT_OK_AND_ASSIGN(
+ std::shared_ptr<arrow::Array> target_array,
+ OrcAdapter::AppendBatch(arrow_type, read_batch.get(),
arrow::default_memory_pool()));
+ ASSERT_OK_AND_ASSIGN(auto converted_array,
paimon::test::DictArrayConverter::ConvertDictArray(
+ target_array,
arrow::default_memory_pool()));
+ ASSERT_TRUE(converted_array);
+ ASSERT_TRUE(converted_array->Equals(src_array)) <<
converted_array->ToString();
+}
+
+TEST_P(OrcAdapterTest, TestAppendBatchWithBinaryForAllNull) {
+ arrow::FieldVector fields = {arrow::field("f0", arrow::binary())};
+ auto arrow_type = arrow::struct_(fields);
+ auto src_array = std::dynamic_pointer_cast<arrow::StructArray>(
+ arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(fields), R"([
+ [null],
+ [null]
+ ])")
+ .ValueOrDie());
+ auto [orc_reader_holder, read_batch] = GenerateOrcReadBatch(src_array);
+
+ ASSERT_OK_AND_ASSIGN(
+ std::shared_ptr<arrow::Array> target_array,
+ OrcAdapter::AppendBatch(arrow_type, read_batch.get(),
arrow::default_memory_pool()));
+ ASSERT_OK_AND_ASSIGN(auto converted_array,
paimon::test::DictArrayConverter::ConvertDictArray(
+ target_array,
arrow::default_memory_pool()));
+ ASSERT_TRUE(converted_array);
+ ASSERT_TRUE(converted_array->Equals(src_array)) <<
converted_array->ToString();
+}
+
+TEST_P(OrcAdapterTest, TestDecimalAndTimestamp) {
+ auto timezone = DateTimeUtils::GetLocalTimezoneName();
+ arrow::FieldVector fields = {
+ arrow::field("f0", arrow::utf8()),
+ arrow::field("f1", arrow::int32()),
+ arrow::field("f2", arrow::int32()),
+ arrow::field("f3", arrow::date32()),
+ arrow::field("f4", arrow::timestamp(arrow::TimeUnit::NANO)),
+ arrow::field("f5", arrow::decimal128(23, 5)),
+ arrow::field("f6", arrow::timestamp(arrow::TimeUnit::NANO)),
+ arrow::field("f7", arrow::timestamp(arrow::TimeUnit::SECOND)),
+ arrow::field("f8", arrow::timestamp(arrow::TimeUnit::MILLI)),
+ arrow::field("f9", arrow::timestamp(arrow::TimeUnit::MICRO)),
+ arrow::field("f10", arrow::timestamp(arrow::TimeUnit::NANO, timezone)),
+ arrow::field("f11", arrow::timestamp(arrow::TimeUnit::SECOND,
timezone)),
+ arrow::field("f12", arrow::timestamp(arrow::TimeUnit::MILLI,
timezone)),
+ arrow::field("f13", arrow::timestamp(arrow::TimeUnit::MICRO,
timezone)),
+ };
+ auto arrow_type = arrow::struct_(fields);
+ auto src_array = std::dynamic_pointer_cast<arrow::StructArray>(
+ arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(fields), R"([
+ ["Alice", 10, 1, 1234, "2033-05-18 03:33:20.0",
"123456789987654321.45678", "2033-05-18 03:33:20.0", "2033-05-18 03:33:20",
"2033-05-18 03:33:20.001", "2033-05-18 03:33:20.001001",
+ "2033-05-18 03:33:20.0", "2033-05-18 03:33:20", "2033-05-18
03:33:20.001", "2033-05-18 03:33:20.001001"],
+ ["Bob", 10, 1, null, "1899-01-01 00:59:20.001001001",
"-123456789987654321.45678", "1899-01-01 00:59:20.001001001", "1899-01-01
00:59:20", "1899-01-01 00:59:20.001","1899-01-01 00:59:20.001001",
+ "1899-01-01 00:59:20.001001001", "1899-01-01 00:59:20", "1899-01-01
00:59:20.001","1899-01-01 00:59:20.001001"],
+ ["Cool", 10, 1, -1234, null, "123.45678",
"2024-12-10 09:20:20.02", "2024-12-10 09:20:20", "2024-12-10 09:20:20.001",
"2024-12-10 09:20:20.001001",
+ "2024-12-10 09:20:20.02", "2024-12-10 09:20:20", "2024-12-10
09:20:20.001", "2024-12-10 09:20:20.001001"],
+ ["Dad", 10, 1, 0, "2008-12-28", null,
"2008-12-28", "2008-12-28", "2008-12-28", "2008-12-28",
+ "2008-12-28", "2008-12-28", "2008-12-28", "2008-12-28"]
+ ])")
+ .ValueOrDie());
+
+ auto [orc_reader_holder, read_batch] = GenerateOrcReadBatch(src_array);
+ ASSERT_OK_AND_ASSIGN(
+ std::shared_ptr<arrow::Array> target_array,
+ OrcAdapter::AppendBatch(arrow_type, read_batch.get(),
arrow::default_memory_pool()));
+ ASSERT_TRUE(target_array->Equals(src_array)) << target_array->ToString();
+ ASSERT_TRUE(src_array->Equals(target_array));
+}
+
+TEST_F(OrcAdapterTest, TestBridgeForMapType) {
+ // map type will lose meta of key and value field after c <-> c++ bridge
+ auto key_field = DataField::ConvertDataFieldToArrowField(
+ DataField(100, arrow::field("key", arrow::int32())));
+ auto value_field = DataField::ConvertDataFieldToArrowField(
+ DataField(101, arrow::field("value", arrow::utf8())));
+ auto map_type = std::make_shared<arrow::MapType>(key_field, value_field);
+ auto map_field = arrow::field("f0", map_type);
+ arrow::Schema arrow_schema({map_field});
+ // from c++ to c
+ auto c_schema = std::make_unique<::ArrowSchema>();
+ ASSERT_TRUE(arrow::ExportSchema(arrow_schema, c_schema.get()).ok());
+
+ // from c to c++
+ auto new_arrow_schema = arrow::ImportSchema(c_schema.get()).ValueOrDie();
+ ASSERT_TRUE(new_arrow_schema->Equals(arrow_schema,
/*check_metadata=*/false));
+ // check meta failed
+ ASSERT_FALSE(new_arrow_schema->Equals(arrow_schema,
/*check_metadata=*/true));
+}
+
+TEST_F(OrcAdapterTest, TestDataBufferSetData) {
+ {
+ // buffer1 !ownBuf
+ OrcMemoryPool orc_pool(GetDefaultPool());
+ ::orc::DataBuffer<int32_t> buffer1(orc_pool, /*size=*/0,
/*ownBuf=*/false);
+ std::vector<int32_t> data = {1, 2, 3, 1};
+ buffer1.setData(data.data(), /*bufSize=*/4 * 4);
+
+ ASSERT_EQ(buffer1.size(), 4);
+ ASSERT_EQ(buffer1.capacity(), 4);
+ ASSERT_EQ(buffer1[1], 2);
+
+ ::orc::DataBuffer<int32_t> buffer2(std::move(buffer1));
+ ASSERT_EQ(buffer2.size(), 4);
+ ASSERT_EQ(buffer2.capacity(), 4);
+ ASSERT_EQ(buffer2[1], 2);
+ }
+ {
+ // buffer1 ownBuf
+ OrcMemoryPool orc_pool(GetDefaultPool());
+ ::orc::DataBuffer<int32_t> buffer1(orc_pool, /*size=*/4,
/*ownBuf=*/true);
+ buffer1[0] = 1;
+ buffer1[1] = 2;
+ buffer1[2] = 3;
+ buffer1[3] = 2;
+ ASSERT_EQ(buffer1.size(), 4);
+ ASSERT_EQ(buffer1.capacity(), 4);
+ ASSERT_EQ(buffer1[1], 2);
+
+ ::orc::DataBuffer<int32_t> buffer2(std::move(buffer1));
+ ASSERT_EQ(buffer2.size(), 4);
+ ASSERT_EQ(buffer2.capacity(), 4);
+ ASSERT_EQ(buffer2[1], 2);
+ }
+}
+
+} // namespace paimon::orc::test
diff --git a/src/paimon/format/orc/predicate_pushdown_test.cpp
b/src/paimon/format/orc/predicate_pushdown_test.cpp
new file mode 100644
index 0000000..643d435
--- /dev/null
+++ b/src/paimon/format/orc/predicate_pushdown_test.cpp
@@ -0,0 +1,666 @@
+/*
+ * 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 <cstdint>
+#include <map>
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "arrow/api.h"
+#include "arrow/array/array_base.h"
+#include "arrow/array/array_nested.h"
+#include "arrow/c/abi.h"
+#include "arrow/c/bridge.h"
+#include "arrow/ipc/json_simple.h"
+#include "gtest/gtest.h"
+#include "orc/OrcFile.hh"
+#include "paimon/defs.h"
+#include "paimon/format/orc/orc_file_batch_reader.h"
+#include "paimon/format/orc/orc_format_defs.h"
+#include "paimon/format/orc/orc_format_writer.h"
+#include "paimon/format/orc/orc_input_stream_impl.h"
+#include "paimon/format/orc/orc_output_stream_impl.h"
+#include "paimon/fs/file_system.h"
+#include "paimon/memory/memory_pool.h"
+#include "paimon/predicate/literal.h"
+#include "paimon/predicate/predicate_builder.h"
+#include "paimon/result.h"
+#include "paimon/status.h"
+#include "paimon/testing/utils/read_result_collector.h"
+#include "paimon/testing/utils/testharness.h"
+#include "paimon/utils/roaring_bitmap32.h"
+
+namespace paimon {
+class Predicate;
+} // namespace paimon
+
+namespace paimon::orc::test {
+
+class PredicatePushdownTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ pool_ = GetDefaultPool();
+ batch_size_ = 10;
+
+ arrow::FieldVector fields = {
+ arrow::field("f0", arrow::utf8()), arrow::field("f1",
arrow::float32()),
+ arrow::field("f2", arrow::int64()), arrow::field("f3",
arrow::boolean()),
+ arrow::field("f4", arrow::int64()), arrow::field("f5",
arrow::binary())};
+
+ struct_array_ = std::dynamic_pointer_cast<arrow::StructArray>(
+ arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(fields),
R"([
+ ["apple", 4.0, 4, true, null, "add"], ["banana", 4.0, 6, true, null,
"bad"],
+ ["camera", 4.0, 8, true, null, "cat"], ["data", null, 10, true, null,
"dad"]
+ ])")
+ .ValueOrDie());
+ dir_ = paimon::test::UniqueTestDirectory::Create();
+ ASSERT_TRUE(dir_);
+ file_name_ = dir_->Str() + "/test.data";
+ fs_ = dir_->GetFileSystem();
+ PrepareTestData(struct_array_);
+ }
+ void TearDown() override {}
+
+ void PrepareTestData(const std::shared_ptr<arrow::StructArray>& array) {
+ auto data_type = array->struct_type();
+ auto data_schema = arrow::schema(data_type->fields());
+ auto data_arrow_array = std::make_unique<ArrowArray>();
+ ASSERT_TRUE(arrow::ExportArray(*array, data_arrow_array.get()).ok());
+ ASSERT_OK_AND_ASSIGN(std::shared_ptr<OutputStream> out,
+ fs_->Create(file_name_, /*overwrite=*/true));
+ ASSERT_OK_AND_ASSIGN(auto out_stream,
OrcOutputStreamImpl::Create(out));
+ ASSERT_OK_AND_ASSIGN(
+ auto format_writer,
+ OrcFormatWriter::Create(std::move(out_stream), *data_schema,
/*options=*/{},
+ /*compression=*/"zstd", batch_size_,
pool_));
+ ASSERT_OK(format_writer->AddBatch(data_arrow_array.get()));
+ ASSERT_OK(format_writer->Finish());
+ ASSERT_OK(out->Close());
+ }
+
+ void CheckResult(const std::shared_ptr<arrow::Schema>& read_schema,
+ const std::shared_ptr<Predicate>& predicate,
+ const std::shared_ptr<arrow::Array>& expected_array, bool
result_ok = true) {
+ ASSERT_OK_AND_ASSIGN(std::shared_ptr<InputStream> in,
fs_->Open(file_name_));
+ ASSERT_OK_AND_ASSIGN(auto in_stream,
+ OrcInputStreamImpl::Create(in,
DEFAULT_NATURAL_READ_SIZE));
+ ASSERT_OK_AND_ASSIGN(auto orc_batch_reader,
+ OrcFileBatchReader::Create(std::move(in_stream),
pool_,
+ /*options=*/{},
batch_size_));
+ std::unique_ptr<ArrowSchema> c_schema =
std::make_unique<ArrowSchema>();
+ auto arrow_status = arrow::ExportSchema(*read_schema, c_schema.get());
+ ASSERT_TRUE(arrow_status.ok());
+ ASSERT_OK(orc_batch_reader->SetReadSchema(c_schema.get(), predicate,
+
/*selection_bitmap=*/std::nullopt));
+ auto result =
paimon::test::ReadResultCollector::CollectResult(orc_batch_reader.get());
+ if (result_ok) {
+ ASSERT_TRUE(result.ok());
+ // check result
+ auto& arrow_array = result.value();
+ if (expected_array) {
+ ASSERT_TRUE(arrow_array);
+ auto expected_chunk_array =
std::make_shared<arrow::ChunkedArray>(expected_array);
+ ASSERT_TRUE(expected_chunk_array->Equals(arrow_array)) <<
arrow_array->ToString();
+ } else {
+ ASSERT_FALSE(arrow_array);
+ }
+ } else {
+ ASSERT_FALSE(result.ok());
+ }
+ }
+
+ private:
+ std::shared_ptr<MemoryPool> pool_;
+ int32_t batch_size_;
+ std::shared_ptr<arrow::StructArray> struct_array_;
+ std::shared_ptr<FileSystem> fs_;
+ std::unique_ptr<paimon::test::UniqueTestDirectory> dir_;
+ std::string file_name_;
+};
+
+TEST_F(PredicatePushdownTest, TestIntDoubleData) {
+ auto data_type = struct_array_->struct_type();
+ arrow::FieldVector fields = {data_type->GetFieldByName("f0"),
data_type->GetFieldByName("f1"),
+ data_type->GetFieldByName("f2"),
data_type->GetFieldByName("f3"),
+ data_type->GetFieldByName("f4")};
+ auto read_schema = arrow::schema(fields);
+ std::shared_ptr<arrow::Array> expected_array =
+ arrow::StructArray::Make(
+ {struct_array_->GetFieldByName("f0"),
struct_array_->GetFieldByName("f1"),
+ struct_array_->GetFieldByName("f2"),
struct_array_->GetFieldByName("f3"),
+ struct_array_->GetFieldByName("f4")},
+ fields)
+ .ValueOrDie();
+ {
+ // f1 == 4, has data
+ auto predicate =
+ PredicateBuilder::Equal(/*field_index=*/1, /*field_name=*/"f1",
FieldType::FLOAT,
+ Literal(static_cast<float>(4.0)));
+ CheckResult(read_schema, predicate, expected_array);
+ }
+ {
+ // f1 == 6, no data
+ auto predicate =
+ PredicateBuilder::Equal(/*field_index=*/1, /*field_name=*/"f1",
FieldType::FLOAT,
+ Literal(static_cast<float>(6.0)));
+ CheckResult(read_schema, predicate, /*expected_array=*/
+ nullptr);
+ }
+ {
+ // f1 != 4, no data
+ auto predicate = PredicateBuilder::NotEqual(
+ /*field_index=*/1, /*field_name=*/"f1", FieldType::FLOAT,
+ Literal(static_cast<float>(4.0)));
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+ {
+ // f2 != 4, has data
+ auto predicate = PredicateBuilder::NotEqual(/*field_index=*/2,
/*field_name=*/"f2",
+ FieldType::BIGINT,
Literal(4l));
+ CheckResult(read_schema, predicate, expected_array);
+ }
+ {
+ // f2 == 6, has data
+ auto predicate = PredicateBuilder::Equal(/*field_index=*/2,
/*field_name=*/"f2",
+ FieldType::BIGINT,
Literal(6l));
+ CheckResult(read_schema, predicate, expected_array);
+ }
+ {
+ // f2 == 1, no data
+ auto predicate = PredicateBuilder::Equal(/*field_index=*/2,
/*field_name=*/"f2",
+ FieldType::BIGINT,
Literal(1l));
+ CheckResult(read_schema, predicate, /*expected_array=*/
+ nullptr);
+ }
+ {
+ // f2 in [1,2,3], no data
+ auto predicate =
+ PredicateBuilder::In(/*field_index=*/2, /*field_name=*/"f2",
FieldType::BIGINT,
+ {Literal(1l), Literal(2l), Literal(3l)});
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+ {
+ // f2 not in [1,2,3], has data
+ auto predicate =
+ PredicateBuilder::NotIn(/*field_index=*/2, /*field_name=*/"f2",
FieldType::BIGINT,
+ {Literal(1l), Literal(2l), Literal(3l)});
+ CheckResult(read_schema, predicate, expected_array);
+ }
+ {
+ // f2 in [2,3,4], has data
+ auto predicate =
+ PredicateBuilder::In(/*field_index=*/2, /*field_name=*/"f2",
FieldType::BIGINT,
+ {Literal(2l), Literal(3l), Literal(4l)});
+ CheckResult(read_schema, predicate, expected_array);
+ }
+ {
+ // f2 not in [2,3,4], has data
+ auto predicate =
+ PredicateBuilder::NotIn(/*field_index=*/2, /*field_name=*/"f2",
FieldType::BIGINT,
+ {Literal(2l), Literal(3l), Literal(4l)});
+ CheckResult(read_schema, predicate, expected_array);
+ }
+}
+
+TEST_F(PredicatePushdownTest, TestBoolData) {
+ auto data_type = struct_array_->struct_type();
+ arrow::FieldVector fields = {data_type->GetFieldByName("f0"),
data_type->GetFieldByName("f1"),
+ data_type->GetFieldByName("f2"),
data_type->GetFieldByName("f3"),
+ data_type->GetFieldByName("f4")};
+ auto read_schema = arrow::schema(fields);
+ std::shared_ptr<arrow::Array> expected_array =
+ arrow::StructArray::Make(
+ {struct_array_->GetFieldByName("f0"),
struct_array_->GetFieldByName("f1"),
+ struct_array_->GetFieldByName("f2"),
struct_array_->GetFieldByName("f3"),
+ struct_array_->GetFieldByName("f4")},
+ fields)
+ .ValueOrDie();
+ {
+ // f3 is null, no data
+ auto predicate =
+ PredicateBuilder::IsNull(/*field_index=*/3, /*field_name=*/"f3",
FieldType::BOOLEAN);
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+ {
+ // f3 is not null, has data
+ auto predicate =
+ PredicateBuilder::IsNotNull(/*field_index=*/3,
/*field_name=*/"f3", FieldType::BOOLEAN);
+ CheckResult(read_schema, predicate, expected_array);
+ }
+ {
+ // f3 == true, has data
+ auto predicate = PredicateBuilder::Equal(/*field_index=*/3,
/*field_name=*/"f3",
+ FieldType::BOOLEAN,
Literal(true));
+ CheckResult(read_schema, predicate, expected_array);
+ }
+ {
+ // f3 in [true, false], has data
+ auto predicate = PredicateBuilder::In(/*field_index=*/3,
/*field_name=*/"f3",
+ FieldType::BOOLEAN,
{Literal(false), Literal(true)});
+ CheckResult(read_schema, predicate, expected_array);
+ }
+}
+
+TEST_F(PredicatePushdownTest, TestStringData) {
+ auto data_type = struct_array_->struct_type();
+ arrow::FieldVector fields = {data_type->GetFieldByName("f0"),
data_type->GetFieldByName("f1"),
+ data_type->GetFieldByName("f2"),
data_type->GetFieldByName("f3"),
+ data_type->GetFieldByName("f4")};
+ auto read_schema = arrow::schema(fields);
+ std::shared_ptr<arrow::Array> expected_array =
+ arrow::StructArray::Make(
+ {struct_array_->GetFieldByName("f0"),
struct_array_->GetFieldByName("f1"),
+ struct_array_->GetFieldByName("f2"),
struct_array_->GetFieldByName("f3"),
+ struct_array_->GetFieldByName("f4")},
+ fields)
+ .ValueOrDie();
+ {
+ // f0 is null, no data
+ auto predicate =
+ PredicateBuilder::IsNull(/*field_index=*/0, /*field_name=*/"f0",
FieldType::STRING);
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+ {
+ // f0 is not null, has data
+ auto predicate =
+ PredicateBuilder::IsNotNull(/*field_index=*/0,
/*field_name=*/"f0", FieldType::STRING);
+ CheckResult(read_schema, predicate, expected_array);
+ }
+ {
+ // f0 == apple, has data
+ auto predicate =
+ PredicateBuilder::Equal(/*field_index=*/0, /*field_name=*/"f0",
FieldType::STRING,
+ Literal(FieldType::STRING, "apple", 5));
+ CheckResult(read_schema, predicate, expected_array);
+ }
+
+ // orc string type need set option for statistics, otherwise predicate
always return
+ // YES_NO_NULL (has data)
+ {
+ // f0 == anything, no data
+ auto predicate =
+ PredicateBuilder::Equal(/*field_index=*/0, /*field_name=*/"f0",
FieldType::STRING,
+ Literal(FieldType::STRING, "anything", 8));
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+ {
+ // f0 > zooooooo, no data
+ auto predicate = PredicateBuilder::GreaterThan(
+ /*field_index=*/0, /*field_name=*/"f0", FieldType::STRING,
+ Literal(FieldType::STRING, "zooooooo", 8));
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+}
+
+TEST_F(PredicatePushdownTest, TestBinaryData) {
+ auto data_type = struct_array_->struct_type();
+ arrow::FieldVector fields = {data_type->GetFieldByName("f5")};
+ auto read_schema = arrow::schema(fields);
+ std::shared_ptr<arrow::Array> expected_array =
+ arrow::StructArray::Make({struct_array_->GetFieldByName("f5")},
fields).ValueOrDie();
+ // paimon do not pushdown binary type to orc, will skip this predicate
+ {
+ // f5 < anything, has data
+ auto predicate =
+ PredicateBuilder::LessThan(/*field_index=*/5, /*field_name=*/"f5",
FieldType::BINARY,
+ Literal(FieldType::BINARY, "anything",
8));
+ CheckResult(read_schema, predicate, expected_array);
+ }
+ {
+ // f5 >= anything, has data
+ auto predicate = PredicateBuilder::GreaterOrEqual(
+ /*field_index=*/5, /*field_name=*/"f5", FieldType::BINARY,
+ Literal(FieldType::BINARY, "anything", 8));
+ CheckResult(read_schema, predicate, expected_array);
+ }
+}
+
+TEST_F(PredicatePushdownTest, TestPredicatePushdownWithAllDataNull) {
+ auto data_type = struct_array_->struct_type();
+ arrow::FieldVector fields = {data_type->GetFieldByName("f0"),
data_type->GetFieldByName("f1"),
+ data_type->GetFieldByName("f2"),
data_type->GetFieldByName("f3"),
+ data_type->GetFieldByName("f4")};
+ auto read_schema = arrow::schema(fields);
+ std::shared_ptr<arrow::Array> expected_array =
+ arrow::StructArray::Make(
+ {struct_array_->GetFieldByName("f0"),
struct_array_->GetFieldByName("f1"),
+ struct_array_->GetFieldByName("f2"),
struct_array_->GetFieldByName("f3"),
+ struct_array_->GetFieldByName("f4")},
+ fields)
+ .ValueOrDie();
+ {
+ // f4 == null, has data
+ auto predicate = PredicateBuilder::Equal(/*field_index=*/4,
/*field_name=*/"f4",
+ FieldType::BIGINT,
Literal(FieldType::BIGINT));
+ CheckResult(read_schema, predicate, expected_array);
+ }
+ {
+ // f4 is null, has data
+ auto predicate =
+ PredicateBuilder::IsNull(/*field_index=*/4, /*field_name=*/"f4",
FieldType::BIGINT);
+ CheckResult(read_schema, predicate, expected_array);
+ }
+
+ // other predicate, always return IS_NULL (no data)
+ {
+ // f4 in [1,2], no data
+ auto predicate = PredicateBuilder::In(/*field_index=*/4,
/*field_name=*/"f4",
+ FieldType::BIGINT, {Literal(1l),
Literal(2l)});
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+ {
+ // f4 not in [1,2], no data
+ auto predicate = PredicateBuilder::NotIn(/*field_index=*/4,
/*field_name=*/"f4",
+ FieldType::BIGINT,
{Literal(1l), Literal(2l)});
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+ {
+ // f4 >= 3, no data
+ auto predicate = PredicateBuilder::GreaterOrEqual(/*field_index=*/4,
/*field_name=*/"f4",
+ FieldType::BIGINT,
Literal(3l));
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+ {
+ // f4 <= 3, no data
+ auto predicate = PredicateBuilder::LessOrEqual(/*field_index=*/4,
/*field_name=*/"f4",
+ FieldType::BIGINT,
Literal(3l));
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+}
+
+TEST_F(PredicatePushdownTest, TestPredicatePushdownWithNullLiteral) {
+ auto data_type = struct_array_->struct_type();
+ arrow::FieldVector fields = {data_type->GetFieldByName("f0"),
data_type->GetFieldByName("f1"),
+ data_type->GetFieldByName("f2"),
data_type->GetFieldByName("f3"),
+ data_type->GetFieldByName("f4")};
+ auto read_schema = arrow::schema(fields);
+ std::shared_ptr<arrow::Array> expected_array =
+ arrow::StructArray::Make(
+ {struct_array_->GetFieldByName("f0"),
struct_array_->GetFieldByName("f1"),
+ struct_array_->GetFieldByName("f2"),
struct_array_->GetFieldByName("f3"),
+ struct_array_->GetFieldByName("f4")},
+ fields)
+ .ValueOrDie();
+ {
+ // f2 in [], orc create reader throw exception
+ auto predicate =
+ PredicateBuilder::In(/*field_index=*/2, /*field_name=*/"f2",
FieldType::BIGINT, {});
+ ASSERT_OK_AND_ASSIGN(std::shared_ptr<InputStream> in,
fs_->Open(file_name_));
+ ASSERT_OK_AND_ASSIGN(auto in_stream,
+ OrcInputStreamImpl::Create(in,
DEFAULT_NATURAL_READ_SIZE));
+ ASSERT_OK_AND_ASSIGN(auto orc_batch_reader,
+ OrcFileBatchReader::Create(std::move(in_stream),
pool_,
+ /*options=*/{},
batch_size_));
+ std::unique_ptr<ArrowSchema> c_schema =
std::make_unique<ArrowSchema>();
+ auto arrow_status = arrow::ExportSchema(*read_schema, c_schema.get());
+ ASSERT_TRUE(arrow_status.ok());
+ ASSERT_NOK_WITH_MSG(
+ orc_batch_reader->SetReadSchema(c_schema.get(), predicate,
/*selection_bitmap=*/
+ std::nullopt),
+ "predicate [In] need literal on field f2");
+ }
+ {
+ // f2 < null, orc NextBatch throw exception
+ auto predicate = PredicateBuilder::LessThan(
+ /*field_index=*/2, /*field_name=*/"f2", FieldType::BIGINT,
Literal(FieldType::BIGINT));
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr,
+ /*result_ok=*/false);
+ }
+ {
+ // f2 >= null, orc NextBatch throw exception
+ auto predicate = PredicateBuilder::GreaterOrEqual(
+ /*field_index=*/2, /*field_name=*/"f2", FieldType::BIGINT,
Literal(FieldType::BIGINT));
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr,
+ /*result_ok=*/false);
+ }
+ {
+ // f2 in [1,null,2], no data
+ auto predicate = PredicateBuilder::In(
+ /*field_index=*/2, /*field_name=*/"f2", FieldType::BIGINT,
+ {Literal(1l), Literal(FieldType::BIGINT), Literal(2l)});
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+ {
+ // f2 in [1,null,2,4], has data
+ auto predicate = PredicateBuilder::In(
+ /*field_index=*/2, /*field_name=*/"f2", FieldType::BIGINT,
+ {Literal(1l), Literal(FieldType::BIGINT), Literal(2l),
Literal(4l)});
+ CheckResult(read_schema, predicate, expected_array);
+ }
+}
+
+TEST_F(PredicatePushdownTest, TestCompoundPredicate) {
+ auto read_schema = arrow::schema(struct_array_->struct_type()->fields());
+ std::shared_ptr<arrow::Array> expected_array = struct_array_;
+ {
+ // f2 < 6 and f1 == 4 and f3 == true, has data
+ ASSERT_OK_AND_ASSIGN(
+ auto predicate,
+ PredicateBuilder::And(
+ {PredicateBuilder::LessThan(/*field_index=*/2,
/*field_name=*/"f2",
+ FieldType::BIGINT, Literal(6l)),
+ PredicateBuilder::Equal(/*field_index=*/1,
/*field_name=*/"f1", FieldType::FLOAT,
+ Literal(static_cast<float>(4.0))),
+ PredicateBuilder::Equal(/*field_index=*/3,
/*field_name=*/"f3", FieldType::BOOLEAN,
+ Literal(true))}));
+ ASSERT_TRUE(predicate);
+ CheckResult(read_schema, predicate, expected_array);
+ }
+ {
+ // f2 < 6 and f1 == 4 and f3 is null, no data
+ ASSERT_OK_AND_ASSIGN(
+ auto predicate,
+ PredicateBuilder::And(
+ {PredicateBuilder::LessThan(/*field_index=*/2,
/*field_name=*/"f2",
+ FieldType::BIGINT, Literal(6l)),
+ PredicateBuilder::Equal(/*field_index=*/1,
/*field_name=*/"f1", FieldType::FLOAT,
+ Literal(static_cast<float>(4.0))),
+ PredicateBuilder::IsNull(/*field_index=*/3,
/*field_name=*/"f3",
+ FieldType::BOOLEAN)}));
+ ASSERT_TRUE(predicate);
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+ {
+ // f2 < 6 and f1 == 4 and f5 is null, will ignore binary predicate,
has data
+ ASSERT_OK_AND_ASSIGN(
+ auto predicate,
+ PredicateBuilder::And(
+ {PredicateBuilder::LessThan(/*field_index=*/2,
/*field_name=*/"f2",
+ FieldType::BIGINT, Literal(6l)),
+ PredicateBuilder::Equal(/*field_index=*/1,
/*field_name=*/"f1", FieldType::FLOAT,
+ Literal(static_cast<float>(4.0))),
+ PredicateBuilder::IsNull(/*field_index=*/5,
/*field_name=*/"f5",
+ FieldType::BINARY)}));
+ ASSERT_TRUE(predicate);
+ CheckResult(read_schema, predicate, expected_array);
+ }
+ {
+ // f2 < 6 and f1 == 5 and f5 is null, will ignore binary predicate, no
data
+ ASSERT_OK_AND_ASSIGN(
+ auto predicate,
+ PredicateBuilder::And(
+ {PredicateBuilder::LessThan(/*field_index=*/2,
/*field_name=*/"f2",
+ FieldType::BIGINT, Literal(6l)),
+ PredicateBuilder::Equal(/*field_index=*/1,
/*field_name=*/"f1", FieldType::FLOAT,
+ Literal(static_cast<float>(5.0))),
+ PredicateBuilder::IsNull(/*field_index=*/5,
/*field_name=*/"f5",
+ FieldType::BINARY)}));
+ ASSERT_TRUE(predicate);
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+ {
+ // f2 < 6 or f1 == 4, has data
+ ASSERT_OK_AND_ASSIGN(
+ auto predicate,
+ PredicateBuilder::Or(
+ {PredicateBuilder::LessThan(/*field_index=*/2,
/*field_name=*/"f2",
+ FieldType::BIGINT, Literal(6l)),
+ PredicateBuilder::Equal(/*field_index=*/1,
/*field_name=*/"f1", FieldType::FLOAT,
+ Literal(static_cast<float>(4.0)))}));
+ ASSERT_TRUE(predicate);
+ CheckResult(read_schema, predicate, expected_array);
+ }
+ {
+ // f2 < 6 or f1 == 5, has data
+ ASSERT_OK_AND_ASSIGN(
+ auto predicate,
+ PredicateBuilder::Or(
+ {PredicateBuilder::LessThan(/*field_index=*/2,
/*field_name=*/"f2",
+ FieldType::BIGINT, Literal(6l)),
+ PredicateBuilder::Equal(/*field_index=*/1,
/*field_name=*/"f1", FieldType::FLOAT,
+ Literal(static_cast<float>(5.0)))}));
+ ASSERT_TRUE(predicate);
+ CheckResult(read_schema, predicate, expected_array);
+ }
+ {
+ // f2 < 2 or f5 is null, will skip this predicate, has data
+ ASSERT_OK_AND_ASSIGN(
+ auto predicate,
+
PredicateBuilder::Or({PredicateBuilder::LessThan(/*field_index=*/2,
/*field_name=*/"f2",
+
FieldType::BIGINT, Literal(2l)),
+ PredicateBuilder::IsNull(/*field_index=*/5,
/*field_name=*/"f5",
+
FieldType::BINARY)}));
+ ASSERT_TRUE(predicate);
+ CheckResult(read_schema, predicate, expected_array);
+ }
+ {
+ // f2 < 2 or f1 == 4 or f3 == false, has data
+ ASSERT_OK_AND_ASSIGN(
+ auto predicate,
+ PredicateBuilder::Or(
+ {PredicateBuilder::LessThan(/*field_index=*/2,
/*field_name=*/"f2",
+ FieldType::BIGINT, Literal(2l)),
+ PredicateBuilder::Equal(/*field_index=*/1,
/*field_name=*/"f1", FieldType::FLOAT,
+ Literal(static_cast<float>(4.0))),
+ PredicateBuilder::Equal(/*field_index=*/3,
/*field_name=*/"f3", FieldType::BOOLEAN,
+ Literal(false))}));
+ ASSERT_TRUE(predicate);
+ CheckResult(read_schema, predicate, expected_array);
+ }
+ {
+ // f2 < 2 or f1 == 5 or f3 is null, no data
+ ASSERT_OK_AND_ASSIGN(
+ auto predicate,
+ PredicateBuilder::Or(
+ {PredicateBuilder::LessThan(/*field_index=*/2,
/*field_name=*/"f2",
+ FieldType::BIGINT, Literal(2l)),
+ PredicateBuilder::Equal(/*field_index=*/1,
/*field_name=*/"f1", FieldType::FLOAT,
+ Literal(static_cast<float>(5.0))),
+ PredicateBuilder::IsNull(/*field_index=*/3,
/*field_name=*/"f3",
+ FieldType::BOOLEAN)}));
+ ASSERT_TRUE(predicate);
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+}
+
+TEST_F(PredicatePushdownTest, TestAllNullOrAllSameValue) {
+ arrow::FieldVector fields = {arrow::field("f1", arrow::int32()),
+ arrow::field("f2", arrow::int32())};
+ auto read_schema = arrow::schema(fields);
+ auto expected_array = std::dynamic_pointer_cast<arrow::StructArray>(
+ arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(fields), R"([
+ [null, 10],
+ [null, 10],
+ [null, 10]
+ ])")
+ .ValueOrDie());
+ PrepareTestData(expected_array);
+ // for f1
+ {
+ auto predicate =
+ PredicateBuilder::IsNull(/*field_index=*/0, /*field_name=*/"f1",
FieldType::INT);
+ CheckResult(read_schema, predicate, expected_array);
+ }
+ {
+ auto predicate =
+ PredicateBuilder::IsNotNull(/*field_index=*/0,
/*field_name=*/"f1", FieldType::INT);
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+ {
+ auto predicate = PredicateBuilder::Equal(/*field_index=*/0,
/*field_name=*/"f1",
+ FieldType::INT, Literal(10));
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+ {
+ auto predicate = PredicateBuilder::NotEqual(/*field_index=*/0,
/*field_name=*/"f1",
+ FieldType::INT,
Literal(10));
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+ {
+ auto predicate = PredicateBuilder::GreaterThan(/*field_index=*/0,
/*field_name=*/"f1",
+ FieldType::INT,
Literal(10));
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+ {
+ auto predicate = PredicateBuilder::GreaterOrEqual(/*field_index=*/0,
/*field_name=*/"f1",
+ FieldType::INT,
Literal(10));
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+ {
+ auto predicate = PredicateBuilder::LessThan(/*field_index=*/0,
/*field_name=*/"f1",
+ FieldType::INT,
Literal(10));
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+ {
+ auto predicate = PredicateBuilder::LessOrEqual(/*field_index=*/0,
/*field_name=*/"f1",
+ FieldType::INT,
Literal(10));
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+ {
+ auto predicate = PredicateBuilder::In(/*field_index=*/0,
/*field_name=*/"f1",
+ FieldType::INT, {Literal(10),
Literal(20)});
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+ {
+ auto predicate = PredicateBuilder::NotIn(/*field_index=*/0,
/*field_name=*/"f1",
+ FieldType::INT, {Literal(10),
Literal(20)});
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+ // for f2
+ {
+ auto predicate = PredicateBuilder::NotEqual(/*field_index=*/1,
/*field_name=*/"f2",
+ FieldType::INT,
Literal(10));
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+ {
+ auto predicate = PredicateBuilder::NotEqual(/*field_index=*/1,
/*field_name=*/"f2",
+ FieldType::INT,
Literal(30));
+ CheckResult(read_schema, predicate, expected_array);
+ }
+ {
+ auto predicate = PredicateBuilder::In(/*field_index=*/1,
/*field_name=*/"f2",
+ FieldType::INT, {Literal(10),
Literal(20)});
+ CheckResult(read_schema, predicate, expected_array);
+ }
+ {
+ auto predicate = PredicateBuilder::NotIn(/*field_index=*/1,
/*field_name=*/"f2",
+ FieldType::INT, {Literal(10),
Literal(20)});
+ CheckResult(read_schema, predicate, /*expected_array=*/nullptr);
+ }
+ {
+ auto predicate = PredicateBuilder::NotIn(/*field_index=*/1,
/*field_name=*/"f2",
+ FieldType::INT, {Literal(20),
Literal(30)});
+ CheckResult(read_schema, predicate, expected_array);
+ }
+}
+
+} // namespace paimon::orc::test