blob: 71727ad44f772a3b47bdf2059efd4017390293f6 [file]
/*
* 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 <string>
#include <vector>
#include "arrow/api.h"
#include "arrow/c/abi.h"
#include "arrow/c/bridge.h"
#include "arrow/ipc/json_simple.h"
#include "gtest/gtest.h"
#include "paimon/common/utils/date_time_utils.h"
#include "paimon/common/utils/path_util.h"
#include "paimon/format/avro/avro_reader_builder.h"
#include "paimon/format/file_format.h"
#include "paimon/format/file_format_factory.h"
#include "paimon/format/format_writer.h"
#include "paimon/format/reader_builder.h"
#include "paimon/format/writer_builder.h"
#include "paimon/fs/file_system.h"
#include "paimon/fs/local/local_file_system.h"
#include "paimon/reader/file_batch_reader.h"
#include "paimon/result.h"
#include "paimon/status.h"
#include "paimon/testing/utils/read_result_collector.h"
#include "paimon/testing/utils/testharness.h"
namespace paimon::avro::test {
class AvroFileFormatTest : public testing::Test, public ::testing::WithParamInterface<std::string> {
public:
void SetUp() override {
ASSERT_OK_AND_ASSIGN(file_format_,
FileFormatFactory::Get("avro", {{Options::FILE_FORMAT, "avro"}}));
fs_ = std::make_shared<LocalFileSystem>();
dir_ = ::paimon::test::UniqueTestDirectory::Create();
ASSERT_TRUE(dir_);
}
void TearDown() override {}
void WriteDataAndCheckResult(const arrow::FieldVector& fields, const std::string& data_str) {
auto schema = arrow::schema(fields);
auto data_type = arrow::struct_(fields);
::ArrowSchema c_schema;
ASSERT_TRUE(arrow::ExportSchema(*schema, &c_schema).ok());
ASSERT_OK_AND_ASSIGN(auto writer_builder,
file_format_->CreateWriterBuilder(&c_schema, 1024));
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<OutputStream> out,
fs_->Create(PathUtil::JoinPath(dir_->Str(), "file.avro"), /*overwrite=*/false));
auto compression = GetParam();
ASSERT_OK_AND_ASSIGN(auto writer, writer_builder->Build(out, compression));
auto input_array =
arrow::ipc::internal::json::ArrayFromJSON(data_type, data_str).ValueOrDie();
ASSERT_TRUE(input_array);
::ArrowArray c_array;
ASSERT_TRUE(arrow::ExportArray(*input_array, &c_array).ok());
ASSERT_OK(writer->AddBatch(&c_array));
ASSERT_OK(writer->Flush());
ASSERT_OK(writer->Finish());
ASSERT_OK(out->Flush());
ASSERT_OK(out->Close());
// read
ASSERT_OK_AND_ASSIGN(auto reader_builder, file_format_->CreateReaderBuilder(1024));
ASSERT_OK_AND_ASSIGN(std::shared_ptr<InputStream> in,
fs_->Open(PathUtil::JoinPath(dir_->Str(), "file.avro")));
ASSERT_OK_AND_ASSIGN(auto batch_reader, reader_builder->Build(in));
ASSERT_TRUE(arrow::ExportSchema(*schema, &c_schema).ok());
ASSERT_OK(batch_reader->SetReadSchema(&c_schema, /*predicate=*/nullptr,
/*selection_bitmap=*/std::nullopt));
ASSERT_OK_AND_ASSIGN(auto output_array, ::paimon::test::ReadResultCollector::CollectResult(
std::move(batch_reader)));
ASSERT_TRUE(output_array->Equals(arrow::ChunkedArray(input_array)))
<< output_array->ToString() << "\n vs \n"
<< input_array->ToString();
}
private:
std::shared_ptr<FileFormat> file_format_;
std::shared_ptr<FileSystem> fs_;
std::unique_ptr<paimon::test::UniqueTestDirectory> dir_;
};
TEST(AvroReaderBuilderTest, RejectsNullMemoryPool) {
AvroReaderBuilder builder(/*batch_size=*/1024);
builder.WithMemoryPool(nullptr);
ASSERT_NOK_WITH_MSG(builder.Build(nullptr), "Avro reader memory pool is nullptr");
}
INSTANTIATE_TEST_SUITE_P(Compression, AvroFileFormatTest,
::testing::ValuesIn(std::vector<std::string>(
{"zstd", "zstandard", "snappy", "null", "deflate"})));
TEST_P(AvroFileFormatTest, TestSimple) {
arrow::FieldVector fields = {
arrow::field("f0", arrow::boolean()), arrow::field("f1", arrow::int8()),
arrow::field("f2", arrow::int16()), arrow::field("f3", arrow::int32()),
arrow::field("field_null", arrow::int32()), arrow::field("f4", arrow::int64()),
arrow::field("f5", arrow::float32()), arrow::field("f6", arrow::float64()),
arrow::field("f7", arrow::utf8()), arrow::field("f8", arrow::binary())};
std::string data_str =
R"([[true, 0, 32767, 2147483647, null, 4294967295, 0.5, 1.141592659, "20250327", "banana"],
[false, 1, 32767, null, null, 4294967296, 1.0, 2.141592658, "20250327", "dog"],
[null, 1, 32767, 2147483647, null, null, 2.0, 3.141592657, null, "lucy"],
[true, -2, -32768, -2147483648, null, -4294967298, 2.0, 3.141592657, "20250326", "mouse"]])";
WriteDataAndCheckResult(fields, data_str);
}
TEST_P(AvroFileFormatTest, TestComplexTypes) {
auto timezone = DateTimeUtils::GetLocalTimezoneName();
arrow::FieldVector fields = {
arrow::field("f0", arrow::timestamp(arrow::TimeUnit::SECOND)),
arrow::field("f1", arrow::timestamp(arrow::TimeUnit::MILLI)),
arrow::field("f2", arrow::timestamp(arrow::TimeUnit::MICRO)),
arrow::field("f3", arrow::timestamp(arrow::TimeUnit::NANO)),
arrow::field("f4", arrow::timestamp(arrow::TimeUnit::SECOND, timezone)),
arrow::field("f5", arrow::timestamp(arrow::TimeUnit::MILLI, timezone)),
arrow::field("f6", arrow::timestamp(arrow::TimeUnit::MICRO, timezone)),
arrow::field("f7", arrow::timestamp(arrow::TimeUnit::NANO, timezone)),
arrow::field("f8", arrow::decimal128(30, 5)),
arrow::field("f9", arrow::decimal128(2, 2)),
arrow::field("f10", arrow::date32()),
arrow::field("f11", arrow::list(arrow::int32())),
arrow::field("f12", arrow::map(arrow::utf8(), arrow::utf8())),
arrow::field("f13", arrow::map(arrow::int32(), arrow::utf8())),
arrow::field("f14", arrow::map(arrow::struct_({arrow::field("f0", arrow::int32())}),
arrow::map(arrow::int32(), arrow::utf8()))),
arrow::field("f15", arrow::struct_({arrow::field("sub1", arrow::int64()),
arrow::field("sub2", arrow::float64()),
arrow::field("sub3", arrow::boolean())})),
};
std::string src_data_str = R"([
["1970-01-02 00:00:00", "1970-01-02 00:00:00.001", "1970-01-02 00:00:00.000001", "1970-01-02 00:00:00.000000001",
"1970-01-02 00:00:00", "1970-01-02 00:00:00.001", "1970-01-02 00:00:00.000001", "1970-01-02 00:00:00.000000001",
"-123456789987654321.45678", "0.78", 12345, [1,2,3], [["test1","a"],["test2","value-3"]], [[1001,"a"],[1002,"value-3"]],
[[[1001],[[1,"a"],[2,"b"]]],[[1002],[[11,"aa"],[22,"bb"]]]], [10,11.2,false]]
])";
WriteDataAndCheckResult(fields, src_data_str);
}
TEST_P(AvroFileFormatTest, TestNestedMap) {
auto timezone = DateTimeUtils::GetLocalTimezoneName();
arrow::FieldVector fields = {
arrow::field("f0", arrow::map(arrow::utf8(), arrow::int16())),
arrow::field("f1", arrow::map(arrow::utf8(), arrow::utf8())),
arrow::field("f2", arrow::map(arrow::utf8(), arrow::timestamp(arrow::TimeUnit::MICRO))),
arrow::field(
"f3", arrow::map(arrow::utf8(), arrow::list(arrow::timestamp(arrow::TimeUnit::MICRO)))),
arrow::field("f4", arrow::map(arrow::utf8(), arrow::list(arrow::utf8()))),
arrow::field(
"f5",
arrow::map(
arrow::int32(),
arrow::struct_(
{arrow::field(
"f5.a",
arrow::struct_(
{arrow::field("f5.a.0", arrow::utf8()),
arrow::field("f5.sub2", arrow::int32()),
arrow::field("f5.a.1", arrow::timestamp(arrow::TimeUnit::MICRO))})),
arrow::field("f5.b", arrow::list(arrow::utf8())),
arrow::field("f5.c", arrow::map(arrow::utf8(), arrow::int32()))}))),
arrow::field(
"f6", arrow::map(arrow::utf8(), arrow::map(arrow::utf8(), arrow::list(arrow::utf8())))),
arrow::field("f7", arrow::map(arrow::int32(), arrow::boolean())),
arrow::field("f8", arrow::map(arrow::int64(), arrow::decimal128(2, 2))),
arrow::field("f9", arrow::map(arrow::date32(), arrow::float32())),
arrow::field("f10", arrow::map(arrow::binary(), arrow::float64())),
arrow::field("f11", arrow::map(arrow::int32(), arrow::list(arrow::int64()))),
arrow::field(
"f12",
arrow::map(arrow::utf8(),
arrow::list(arrow::struct_(
{arrow::field("name", arrow::utf8()),
arrow::field("scores", arrow::list(arrow::float32())),
arrow::field("info", arrow::map(arrow::float32(), arrow::utf8()))}))))};
std::string src_data_str = R"([
[
[["f0_key_0", 1],["f0_key_1", 2]],
[["f1_key_0","val-1"],["f1_key_1","value-2"]],
[["f2_key_0","1970-01-01 00:00:00.000001"],["f2_key_1","1970-01-02 00:00:00.000001"]],
[["f3_key_0",["1970-01-01 00:00:00.000001"]],["f3_key_1",["1970-01-02 00:00:00.000001"]]],
[["f4_key_0",["val-1", "val-2"]],["f4_key_1",["val-3","val-4"]]],
[[500,[["sub1",1,"1970-01-01 00:00:00.000001"],["test-1","test-2"],[["subkey_0",1],["subkey_1", 2]]]],[600,[["sub2",2,"1970-01-02 00:00:00.000001"],["test-3","test-4"],[["subkey_2", 1],["subkey_3", 2]]]]],
[["f6_key_0",[["f6_sub_key_0",["value-0","value-1"]],["f6_sub_key_1",["value-2","value-3"]]]],["f6_key_1",[["f6_sub_key_2",["value-0","value-1"]],["f6_sub_key_3",["value-2","value-3"]]]]],
[[100, true], [200, false]],
[[1000, "0.78"], [2000, "0.91"]],
[[10, 1.5], [20, 2.75]],
[["aGVsbG8=", 3.14159], ["d29ybGQ=", 2.71828]],
[[1, [1000000, 2000000]], [2, [3000000, 4000000, 5000000]]],
[["group1", [["Alice", [95.5, 96.0], [[100.1, "info"]]], ["Bob", [88.0, 89.5],[[200.1,"info"]]]]],["group2", [["Charlie",[92.3, 93.1],[[300.1,"info"]]]]]]
],
[
null,null,null,null,null,null,null,null,null,null,null,null,null
]
])";
WriteDataAndCheckResult(fields, src_data_str);
}
} // namespace paimon::avro::test