blob: db1adb872e4936a2ef71abe0fe769aaa129cf9ff [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 "paimon/core/append/append_only_writer.h"
#include <cstddef>
#include <deque>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "arrow/api.h"
#include "arrow/array/builder_binary.h"
#include "arrow/array/builder_nested.h"
#include "arrow/c/abi.h"
#include "arrow/c/bridge.h"
#include "arrow/c/helpers.h"
#include "arrow/type.h"
#include "gtest/gtest.h"
#include "paimon/common/data/blob_utils.h"
#include "paimon/common/fs/external_path_provider.h"
#include "paimon/core/compact/compact_deletion_file.h"
#include "paimon/core/compact/compact_result.h"
#include "paimon/core/compact/noop_compact_manager.h"
#include "paimon/core/core_options.h"
#include "paimon/core/io/compact_increment.h"
#include "paimon/core/io/data_file_path_factory.h"
#include "paimon/core/io/data_increment.h"
#include "paimon/core/manifest/file_source.h"
#include "paimon/core/stats/simple_stats.h"
#include "paimon/core/utils/commit_increment.h"
#include "paimon/defs.h"
#include "paimon/fs/file_system.h"
#include "paimon/fs/local/local_file_system.h"
#include "paimon/memory/memory_pool.h"
#include "paimon/record_batch.h"
#include "paimon/testing/utils/testharness.h"
namespace arrow {
class Array;
} // namespace arrow
namespace paimon::test {
namespace {
class FakeCompactDeletionFile : public CompactDeletionFile,
public std::enable_shared_from_this<FakeCompactDeletionFile> {
public:
explicit FakeCompactDeletionFile(std::string id) : id_(std::move(id)) {}
Result<std::optional<std::shared_ptr<IndexFileMeta>>> GetOrCompute() override {
return std::optional<std::shared_ptr<IndexFileMeta>>();
}
Result<std::shared_ptr<CompactDeletionFile>> MergeOldFile(
const std::shared_ptr<CompactDeletionFile>& old) override {
merged_old_ = old;
return shared_from_this();
}
void Clean() override {
cleaned_ = true;
}
const std::string& Id() const {
return id_;
}
bool Cleaned() const {
return cleaned_;
}
std::shared_ptr<CompactDeletionFile> MergedOld() const {
return merged_old_;
}
private:
std::string id_;
bool cleaned_ = false;
std::shared_ptr<CompactDeletionFile> merged_old_;
};
class FakeCompactManager : public CompactManager {
public:
Status AddNewFile(const std::shared_ptr<DataFileMeta>& file) override {
added_files.push_back(file);
return Status::OK();
}
std::vector<std::shared_ptr<DataFileMeta>> AllFiles() const override {
return all_files;
}
Status TriggerCompaction(bool full_compaction) override {
trigger_calls.push_back(full_compaction);
return Status::OK();
}
Result<std::optional<std::shared_ptr<CompactResult>>> GetCompactionResult(
bool blocking) override {
get_result_blocking_calls.push_back(blocking);
if (queued_results.empty()) {
return std::optional<std::shared_ptr<CompactResult>>();
}
auto result = queued_results.front();
queued_results.pop_front();
return result;
}
void RequestCancelCompaction() override {
request_cancel_called = true;
}
void WaitForCompactionToExit() override {
wait_called = true;
}
bool CompactNotCompleted() const override {
return compact_not_completed;
}
bool ShouldWaitForLatestCompaction() const override {
return should_wait_latest;
}
bool ShouldWaitForPreparingCheckpoint() const override {
return should_wait_prepare;
}
Status Close() override {
close_called = true;
return Status::OK();
}
std::vector<std::shared_ptr<DataFileMeta>> added_files;
std::vector<std::shared_ptr<DataFileMeta>> all_files;
std::vector<bool> trigger_calls;
std::vector<bool> get_result_blocking_calls;
std::deque<Result<std::optional<std::shared_ptr<CompactResult>>>> queued_results;
bool compact_not_completed = false;
bool should_wait_latest = false;
bool should_wait_prepare = false;
bool request_cancel_called = false;
bool wait_called = false;
bool close_called = false;
};
} // namespace
class AppendOnlyWriterTest : public testing::Test {
public:
void SetUp() override {
memory_pool_ = GetDefaultPool();
compact_manager_ = std::make_shared<NoopCompactManager>();
}
CoreOptions CreateOptions(const std::map<std::string, std::string>& overrides = {}) const {
std::map<std::string, std::string> raw_options = {
{Options::FILE_SYSTEM, "local"},
{Options::FILE_FORMAT, "mock_format"},
{Options::MANIFEST_FORMAT, "mock_format"},
};
for (const auto& [key, value] : overrides) {
raw_options[key] = value;
}
return CoreOptions::FromMap(raw_options).value();
}
std::shared_ptr<DataFilePathFactory> CreatePathFactory(const std::string& dir,
const std::string& format,
const CoreOptions& options) const {
auto path_factory = std::make_shared<DataFilePathFactory>();
EXPECT_TRUE(path_factory->Init(dir, format, options.DataFilePrefix(), nullptr).ok());
return path_factory;
}
std::shared_ptr<DataFileMeta> NewAppendFile(const std::string& file_name, int64_t row_count,
int64_t min_sequence_number,
int64_t max_sequence_number) const {
return DataFileMeta::ForAppend(file_name, /*file_size=*/row_count, row_count,
SimpleStats::EmptyStats(), min_sequence_number,
max_sequence_number, /*schema_id=*/0, FileSource::Append(),
std::nullopt, std::nullopt, std::nullopt, std::nullopt)
.value();
}
std::unique_ptr<RecordBatch> CreateSingleStringBatch(
const std::vector<std::string>& values,
const std::optional<std::vector<RecordBatch::RowKind>>& row_kinds = std::nullopt) const {
arrow::FieldVector fields = {arrow::field("f0", arrow::utf8())};
auto struct_type = arrow::struct_(fields);
arrow::StructBuilder struct_builder(struct_type, arrow::default_memory_pool(),
{std::make_shared<arrow::StringBuilder>()});
auto string_builder = static_cast<arrow::StringBuilder*>(struct_builder.field_builder(0));
for (const auto& value : values) {
EXPECT_TRUE(struct_builder.Append().ok());
EXPECT_TRUE(string_builder->Append(value).ok());
}
std::shared_ptr<arrow::Array> array;
EXPECT_TRUE(struct_builder.Finish(&array).ok());
::ArrowArray arrow_array;
EXPECT_TRUE(arrow::ExportArray(*array, &arrow_array).ok());
RecordBatchBuilder batch_builder(&arrow_array);
if (row_kinds.has_value()) {
batch_builder.SetRowKinds(row_kinds.value());
}
return batch_builder.Finish().value();
}
std::unique_ptr<RecordBatch> CreateStructBatch(
const std::shared_ptr<arrow::Schema>& schema,
const std::vector<std::shared_ptr<arrow::Array>>& columns) const {
auto raw_struct_array = arrow::StructArray::Make(columns, schema->fields()).ValueOrDie();
::ArrowArray arrow_array;
EXPECT_TRUE(arrow::ExportArray(*raw_struct_array, &arrow_array).ok());
RecordBatchBuilder batch_builder(&arrow_array);
return batch_builder.Finish().value();
}
private:
std::shared_ptr<MemoryPool> memory_pool_;
std::shared_ptr<CompactManager> compact_manager_;
};
TEST_F(AppendOnlyWriterTest, TestEmptyCommits) {
std::map<std::string, std::string> raw_options;
raw_options[Options::FILE_FORMAT] = "mock_format";
raw_options[Options::FILE_SYSTEM] = "local";
raw_options[Options::MANIFEST_FORMAT] = "mock_format";
ASSERT_OK_AND_ASSIGN(CoreOptions options, CoreOptions::FromMap(raw_options));
arrow::FieldVector fields = {
arrow::field("f0", arrow::boolean()), arrow::field("f1", arrow::uint8()),
arrow::field("f10", arrow::float64()), arrow::field("f11", arrow::utf8()),
arrow::field("f12", arrow::binary()), arrow::field("non-partition-field", arrow::int32())};
auto schema = arrow::schema(fields);
auto path_factory = std::make_shared<DataFilePathFactory>();
auto dir = UniqueTestDirectory::Create();
ASSERT_TRUE(dir);
ASSERT_OK(path_factory->Init(dir->Str(), "mock_format", options.DataFilePrefix(), nullptr));
AppendOnlyWriter writer(options, /*schema_id=*/0, schema, /*write_cols=*/std::nullopt,
/*max_sequence_number=*/-1, path_factory, compact_manager_,
memory_pool_);
for (int32_t i = 0; i < 3; i++) {
ASSERT_OK_AND_ASSIGN(CommitIncrement inc, writer.PrepareCommit(true));
ASSERT_TRUE(inc.GetNewFilesIncrement().IsEmpty());
ASSERT_TRUE(inc.GetCompactIncrement().IsEmpty());
}
}
TEST_F(AppendOnlyWriterTest, TestWriteAndPrepareCommit) {
std::map<std::string, std::string> raw_options;
raw_options[Options::FILE_FORMAT] = "mock_format";
raw_options[Options::FILE_SYSTEM] = "local";
raw_options[Options::MANIFEST_FORMAT] = "mock_format";
ASSERT_OK_AND_ASSIGN(CoreOptions options, CoreOptions::FromMap(raw_options));
arrow::FieldVector fields = {
arrow::field("f0", arrow::boolean()), arrow::field("f1", arrow::uint8()),
arrow::field("f10", arrow::float64()), arrow::field("f11", arrow::utf8()),
arrow::field("f12", arrow::binary()), arrow::field("non-partition-field", arrow::int32())};
auto schema = arrow::schema(fields);
auto dir = UniqueTestDirectory::Create();
ASSERT_TRUE(dir);
auto path_factory = std::make_shared<DataFilePathFactory>();
ASSERT_OK(path_factory->Init(dir->Str(), "mock_format", options.DataFilePrefix(), nullptr));
AppendOnlyWriter writer(options, /*schema_id=*/2, schema, /*write_cols=*/std::nullopt,
/*max_sequence_number=*/-1, path_factory, compact_manager_,
memory_pool_);
arrow::StringBuilder builder;
for (size_t j = 0; j < 100; j++) {
ASSERT_TRUE(builder.Append(std::to_string(j)).ok());
}
std::shared_ptr<arrow::Array> array = builder.Finish().ValueOrDie();
::ArrowArray arrow_array;
ASSERT_TRUE(arrow::ExportArray(*array, &arrow_array).ok());
RecordBatchBuilder batch_builder(&arrow_array);
ASSERT_OK_AND_ASSIGN(auto record_batch, batch_builder.Finish());
ASSERT_OK(writer.Write(std::move(record_batch)));
ASSERT_TRUE(ArrowArrayIsReleased(&arrow_array));
ASSERT_OK_AND_ASSIGN(CommitIncrement inc, writer.PrepareCommit(true));
ASSERT_FALSE(inc.GetNewFilesIncrement().IsEmpty());
const auto& data_increment = inc.GetNewFilesIncrement();
const auto& data_file_metas = data_increment.NewFiles();
ASSERT_EQ(1, data_file_metas.size());
ASSERT_EQ(2, data_file_metas[0]->schema_id);
ASSERT_TRUE(inc.GetCompactIncrement().IsEmpty());
std::string path = path_factory->ToPath(inc.GetNewFilesIncrement().NewFiles()[0]->file_name);
ASSERT_OK_AND_ASSIGN(bool exist, options.GetFileSystem()->Exists(path));
ASSERT_TRUE(exist);
ASSERT_OK(writer.Close());
}
TEST_F(AppendOnlyWriterTest, TestWriteAndClose) {
std::map<std::string, std::string> raw_options;
raw_options[Options::FILE_FORMAT] = "orc";
raw_options[Options::FILE_SYSTEM] = "local";
raw_options[Options::MANIFEST_FORMAT] = "orc";
ASSERT_OK_AND_ASSIGN(CoreOptions options, CoreOptions::FromMap(raw_options));
arrow::FieldVector fields = {arrow::field("f0", arrow::utf8())};
auto schema = arrow::schema(fields);
auto dir = UniqueTestDirectory::Create();
ASSERT_TRUE(dir);
auto path_factory = std::make_shared<DataFilePathFactory>();
ASSERT_OK(path_factory->Init(dir->Str(), "orc", options.DataFilePrefix(), nullptr));
AppendOnlyWriter writer(options, /*schema_id=*/1, schema, /*write_cols=*/std::nullopt,
/*max_sequence_number=*/-1, path_factory, compact_manager_,
memory_pool_);
auto struct_type = arrow::struct_(fields);
arrow::StructBuilder struct_builder(struct_type, arrow::default_memory_pool(),
{std::make_shared<arrow::StringBuilder>()});
auto string_builder = static_cast<arrow::StringBuilder*>(struct_builder.field_builder(0));
for (size_t j = 0; j < 100; j++) {
ASSERT_TRUE(struct_builder.Append().ok());
ASSERT_TRUE(string_builder->Append(std::to_string(j)).ok());
}
std::shared_ptr<arrow::Array> array;
ASSERT_TRUE(struct_builder.Finish(&array).ok());
ASSERT_TRUE(array);
::ArrowArray arrow_array;
ASSERT_TRUE(arrow::ExportArray(*array, &arrow_array).ok());
RecordBatchBuilder batch_builder(&arrow_array);
ASSERT_OK_AND_ASSIGN(auto record_batch, batch_builder.Finish());
ASSERT_OK(writer.Write(std::move(record_batch)));
ASSERT_TRUE(ArrowArrayIsReleased(&arrow_array));
ASSERT_OK(writer.Close());
auto file_system = std::make_shared<LocalFileSystem>();
std::vector<std::unique_ptr<BasicFileStatus>> file_status_list;
ASSERT_OK(file_system->ListDir(dir->Str(), &file_status_list));
ASSERT_TRUE(file_status_list.empty());
}
TEST_F(AppendOnlyWriterTest, TestInvalidRowKind) {
std::map<std::string, std::string> raw_options;
raw_options[Options::FILE_FORMAT] = "orc";
raw_options[Options::FILE_SYSTEM] = "local";
raw_options[Options::MANIFEST_FORMAT] = "orc";
ASSERT_OK_AND_ASSIGN(CoreOptions options, CoreOptions::FromMap(raw_options));
arrow::FieldVector fields = {arrow::field("f0", arrow::utf8())};
auto schema = arrow::schema(fields);
auto dir = UniqueTestDirectory::Create();
ASSERT_TRUE(dir);
auto path_factory = std::make_shared<DataFilePathFactory>();
ASSERT_OK(path_factory->Init(dir->Str(), "orc", options.DataFilePrefix(), nullptr));
AppendOnlyWriter writer(options, /*schema_id=*/1, schema, /*write_cols=*/std::nullopt,
/*max_sequence_number=*/-1, path_factory, compact_manager_,
memory_pool_);
auto struct_type = arrow::struct_(fields);
arrow::StructBuilder struct_builder(struct_type, arrow::default_memory_pool(),
{std::make_shared<arrow::StringBuilder>()});
auto string_builder = static_cast<arrow::StringBuilder*>(struct_builder.field_builder(0));
ASSERT_TRUE(struct_builder.Append().ok());
ASSERT_TRUE(string_builder->Append("row0").ok());
std::shared_ptr<arrow::Array> array;
ASSERT_TRUE(struct_builder.Finish(&array).ok());
ASSERT_TRUE(array);
::ArrowArray arrow_array;
ASSERT_TRUE(arrow::ExportArray(*array, &arrow_array).ok());
RecordBatchBuilder batch_builder(&arrow_array);
ASSERT_OK_AND_ASSIGN(auto record_batch,
batch_builder.SetRowKinds({RecordBatch::RowKind::DELETE}).Finish());
ASSERT_NOK_WITH_MSG(writer.Write(std::move(record_batch)),
"Append only writer can not accept record batch with RowKind DELETE");
ASSERT_TRUE(ArrowArrayIsReleased(&arrow_array));
ASSERT_OK(writer.Close());
auto file_system = std::make_shared<LocalFileSystem>();
std::vector<std::unique_ptr<BasicFileStatus>> file_status_list;
ASSERT_OK(file_system->ListDir(dir->Str(), &file_status_list));
ASSERT_TRUE(file_status_list.empty());
}
TEST_F(AppendOnlyWriterTest, TestPrepareCommitWaitCompactionUsesBlockingGetResult) {
auto options = CreateOptions();
auto dir = UniqueTestDirectory::Create();
ASSERT_TRUE(dir);
auto path_factory = CreatePathFactory(dir->Str(), "mock_format", options);
auto compact_manager = std::make_shared<FakeCompactManager>();
arrow::FieldVector fields = {arrow::field("f0", arrow::utf8())};
auto schema = arrow::schema(fields);
AppendOnlyWriter writer(options, /*schema_id=*/0, schema, /*write_cols=*/std::nullopt,
/*max_sequence_number=*/-1, path_factory, compact_manager,
memory_pool_);
ASSERT_OK(writer.Write(CreateSingleStringBatch({"a", "b"})));
ASSERT_OK(writer.PrepareCommit(/*wait_compaction=*/true).status());
ASSERT_EQ(compact_manager->get_result_blocking_calls.size(), 2);
ASSERT_FALSE(compact_manager->get_result_blocking_calls[0]);
ASSERT_TRUE(compact_manager->get_result_blocking_calls[1]);
ASSERT_OK(writer.Close());
}
TEST_F(AppendOnlyWriterTest, TestPrepareCommitForceCompactUsesBlockingGetResult) {
auto options = CreateOptions({{Options::COMMIT_FORCE_COMPACT, "true"}});
auto dir = UniqueTestDirectory::Create();
ASSERT_TRUE(dir);
auto path_factory = CreatePathFactory(dir->Str(), "mock_format", options);
auto compact_manager = std::make_shared<FakeCompactManager>();
arrow::FieldVector fields = {arrow::field("f0", arrow::utf8())};
auto schema = arrow::schema(fields);
AppendOnlyWriter writer(options, /*schema_id=*/0, schema, /*write_cols=*/std::nullopt,
/*max_sequence_number=*/-1, path_factory, compact_manager,
memory_pool_);
ASSERT_OK(writer.Write(CreateSingleStringBatch({"a"})));
ASSERT_OK(writer.PrepareCommit(/*wait_compaction=*/false).status());
ASSERT_EQ(compact_manager->get_result_blocking_calls.size(), 2);
ASSERT_FALSE(compact_manager->get_result_blocking_calls[0]);
ASSERT_TRUE(compact_manager->get_result_blocking_calls[1]);
ASSERT_OK(writer.Close());
}
TEST_F(AppendOnlyWriterTest,
TestSyncAndPrepareCommitConsumeCompactionResultsAndMergeDeletionFiles) {
auto options = CreateOptions();
auto dir = UniqueTestDirectory::Create();
ASSERT_TRUE(dir);
auto path_factory = CreatePathFactory(dir->Str(), "mock_format", options);
auto compact_manager = std::make_shared<FakeCompactManager>();
auto before1 = NewAppendFile("before-1", 10, 0, 9);
auto after1 = NewAppendFile("after-1", 10, 10, 19);
auto before2 = NewAppendFile("before-2", 10, 20, 29);
auto after2 = NewAppendFile("after-2", 10, 30, 39);
auto deletion_file1 = std::make_shared<FakeCompactDeletionFile>("d1");
auto deletion_file2 = std::make_shared<FakeCompactDeletionFile>("d2");
auto result1 =
std::make_shared<CompactResult>(std::vector<std::shared_ptr<DataFileMeta>>{before1},
std::vector<std::shared_ptr<DataFileMeta>>{after1});
result1->SetDeletionFile(deletion_file1);
auto result2 =
std::make_shared<CompactResult>(std::vector<std::shared_ptr<DataFileMeta>>{before2},
std::vector<std::shared_ptr<DataFileMeta>>{after2});
result2->SetDeletionFile(deletion_file2);
compact_manager->queued_results.push_back(
std::optional<std::shared_ptr<CompactResult>>(result1));
compact_manager->queued_results.push_back(
std::optional<std::shared_ptr<CompactResult>>(result2));
arrow::FieldVector fields = {arrow::field("f0", arrow::utf8())};
auto schema = arrow::schema(fields);
AppendOnlyWriter writer(options, /*schema_id=*/0, schema, /*write_cols=*/std::nullopt,
/*max_sequence_number=*/-1, path_factory, compact_manager,
memory_pool_);
ASSERT_OK(writer.Sync());
ASSERT_OK(writer.Sync());
ASSERT_OK_AND_ASSIGN(CommitIncrement inc, writer.PrepareCommit(/*wait_compaction=*/false));
ASSERT_EQ(inc.GetCompactIncrement().CompactBefore().size(), 2);
ASSERT_EQ(inc.GetCompactIncrement().CompactAfter().size(), 2);
ASSERT_EQ(*inc.GetCompactIncrement().CompactBefore()[0], *before1);
ASSERT_EQ(*inc.GetCompactIncrement().CompactBefore()[1], *before2);
ASSERT_EQ(*inc.GetCompactIncrement().CompactAfter()[0], *after1);
ASSERT_EQ(*inc.GetCompactIncrement().CompactAfter()[1], *after2);
auto merged = std::dynamic_pointer_cast<FakeCompactDeletionFile>(inc.GetCompactDeletionFile());
ASSERT_TRUE(merged);
ASSERT_EQ(merged->Id(), "d2");
ASSERT_EQ(merged->MergedOld(), deletion_file1);
ASSERT_OK(writer.Close());
}
TEST_F(AppendOnlyWriterTest, TestCloseDeletesCompactAfterFiles) {
auto options =
CreateOptions({{Options::FILE_FORMAT, "orc"}, {Options::MANIFEST_FORMAT, "orc"}});
auto dir = UniqueTestDirectory::Create();
ASSERT_TRUE(dir);
auto path_factory = CreatePathFactory(dir->Str(), "orc", options);
auto compact_manager = std::make_shared<FakeCompactManager>();
auto compact_after = NewAppendFile("compact-after.orc", 1, 0, 0);
auto compact_after_path = path_factory->ToPath(compact_after->file_name);
ASSERT_OK_AND_ASSIGN(auto output, options.GetFileSystem()->Create(compact_after_path, true));
ASSERT_OK(output->Close());
auto result =
std::make_shared<CompactResult>(std::vector<std::shared_ptr<DataFileMeta>>{},
std::vector<std::shared_ptr<DataFileMeta>>{compact_after});
compact_manager->queued_results.push_back(
std::optional<std::shared_ptr<CompactResult>>(result));
arrow::FieldVector fields = {arrow::field("f0", arrow::utf8())};
auto schema = arrow::schema(fields);
AppendOnlyWriter writer(options, /*schema_id=*/0, schema, /*write_cols=*/std::nullopt,
/*max_sequence_number=*/-1, path_factory, compact_manager,
memory_pool_);
ASSERT_OK(writer.Sync());
ASSERT_TRUE(options.GetFileSystem()->Exists(compact_after_path).value());
ASSERT_OK(writer.Close());
ASSERT_FALSE(options.GetFileSystem()->Exists(compact_after_path).value());
ASSERT_TRUE(compact_manager->request_cancel_called);
ASSERT_TRUE(compact_manager->wait_called);
ASSERT_TRUE(compact_manager->close_called);
}
TEST_F(AppendOnlyWriterTest, TestCloseCleansDeletionFile) {
auto options = CreateOptions();
auto dir = UniqueTestDirectory::Create();
ASSERT_TRUE(dir);
auto path_factory = CreatePathFactory(dir->Str(), "mock_format", options);
auto compact_manager = std::make_shared<FakeCompactManager>();
auto deletion_file = std::make_shared<FakeCompactDeletionFile>("del-close");
auto before = NewAppendFile("before-close", 5, 0, 4);
auto after = NewAppendFile("after-close", 5, 5, 9);
auto result =
std::make_shared<CompactResult>(std::vector<std::shared_ptr<DataFileMeta>>{before},
std::vector<std::shared_ptr<DataFileMeta>>{after});
result->SetDeletionFile(deletion_file);
compact_manager->queued_results.push_back(
std::optional<std::shared_ptr<CompactResult>>(result));
arrow::FieldVector fields = {arrow::field("f0", arrow::utf8())};
auto schema = arrow::schema(fields);
AppendOnlyWriter writer(options, /*schema_id=*/0, schema, /*write_cols=*/std::nullopt,
/*max_sequence_number=*/-1, path_factory, compact_manager,
memory_pool_);
// Sync to consume the compaction result and populate compact_deletion_file_.
ASSERT_OK(writer.Sync());
ASSERT_FALSE(deletion_file->Cleaned());
ASSERT_OK(writer.Close());
ASSERT_TRUE(deletion_file->Cleaned());
}
TEST_F(AppendOnlyWriterTest, TestCompactNotCompletedTriggersCompaction) {
auto options = CreateOptions();
auto dir = UniqueTestDirectory::Create();
ASSERT_TRUE(dir);
auto path_factory = CreatePathFactory(dir->Str(), "mock_format", options);
auto compact_manager = std::make_shared<FakeCompactManager>();
compact_manager->compact_not_completed = true;
arrow::FieldVector fields = {arrow::field("f0", arrow::utf8())};
auto schema = arrow::schema(fields);
AppendOnlyWriter writer(options, /*schema_id=*/0, schema, /*write_cols=*/std::nullopt,
/*max_sequence_number=*/-1, path_factory, compact_manager,
memory_pool_);
ASSERT_OK_AND_ASSIGN(bool not_completed, writer.CompactNotCompleted());
ASSERT_TRUE(not_completed);
ASSERT_EQ(compact_manager->trigger_calls, std::vector<bool>({false}));
ASSERT_OK(writer.Close());
}
TEST_F(AppendOnlyWriterTest, TestCompactPassesFullCompactionFlag) {
auto options = CreateOptions();
auto dir = UniqueTestDirectory::Create();
ASSERT_TRUE(dir);
auto path_factory = CreatePathFactory(dir->Str(), "mock_format", options);
auto compact_manager = std::make_shared<FakeCompactManager>();
arrow::FieldVector fields = {arrow::field("f0", arrow::utf8())};
auto schema = arrow::schema(fields);
AppendOnlyWriter writer(options, /*schema_id=*/0, schema, /*write_cols=*/std::nullopt,
/*max_sequence_number=*/-1, path_factory, compact_manager,
memory_pool_);
ASSERT_OK(writer.Compact(/*full_compaction=*/true));
ASSERT_OK(writer.Compact(/*full_compaction=*/false));
ASSERT_EQ(compact_manager->trigger_calls, std::vector<bool>({true, false}));
ASSERT_OK(writer.Close());
}
TEST_F(AppendOnlyWriterTest, TestWriteWithSingleBlobField) {
auto options =
CreateOptions({{Options::FILE_FORMAT, "orc"}, {Options::MANIFEST_FORMAT, "orc"}});
auto dir = UniqueTestDirectory::Create();
ASSERT_TRUE(dir);
auto path_factory = CreatePathFactory(dir->Str(), "orc", options);
auto int_field = arrow::field("id", arrow::int32());
auto blob_field = BlobUtils::ToArrowField("blob", false);
auto schema = arrow::schema({int_field, blob_field});
AppendOnlyWriter writer(options, /*schema_id=*/0, schema, /*write_cols=*/std::nullopt,
/*max_sequence_number=*/-1, path_factory, compact_manager_,
memory_pool_);
arrow::Int32Builder int_builder;
ASSERT_TRUE(int_builder.AppendValues({1, 2}).ok());
auto int_array = int_builder.Finish().ValueOrDie();
arrow::LargeBinaryBuilder blob_builder;
ASSERT_TRUE(blob_builder.Append("a", 1).ok());
ASSERT_TRUE(blob_builder.Append("bb", 2).ok());
auto blob_array = blob_builder.Finish().ValueOrDie();
ASSERT_OK(writer.Write(CreateStructBatch(schema, {int_array, blob_array})));
ASSERT_OK_AND_ASSIGN(CommitIncrement inc, writer.PrepareCommit(/*wait_compaction=*/true));
ASSERT_EQ(inc.GetNewFilesIncrement().NewFiles().size(), 2);
const auto& main_file = inc.GetNewFilesIncrement().NewFiles()[0];
const auto& blob_file = inc.GetNewFilesIncrement().NewFiles()[1];
ASSERT_TRUE(
options.GetFileSystem()->Exists(path_factory->ToPath(main_file->file_name)).value());
ASSERT_TRUE(
options.GetFileSystem()->Exists(path_factory->ToPath(blob_file->file_name)).value());
ASSERT_OK(writer.Close());
}
TEST_F(AppendOnlyWriterTest, TestWriteWithMultipleBlobFields) {
auto options =
CreateOptions({{Options::FILE_FORMAT, "orc"}, {Options::MANIFEST_FORMAT, "orc"}});
auto dir = UniqueTestDirectory::Create();
ASSERT_TRUE(dir);
auto path_factory = CreatePathFactory(dir->Str(), "orc", options);
auto schema =
arrow::schema({arrow::field("id", arrow::int32()), BlobUtils::ToArrowField("blob1", false),
BlobUtils::ToArrowField("blob2", false)});
AppendOnlyWriter writer(options, /*schema_id=*/0, schema, /*write_cols=*/std::nullopt,
/*max_sequence_number=*/-1, path_factory, compact_manager_,
memory_pool_);
arrow::Int32Builder int_builder;
ASSERT_TRUE(int_builder.AppendValues({1}).ok());
auto int_array = int_builder.Finish().ValueOrDie();
arrow::LargeBinaryBuilder blob_builder1;
ASSERT_TRUE(blob_builder1.Append("a", 1).ok());
auto blob_array1 = blob_builder1.Finish().ValueOrDie();
arrow::LargeBinaryBuilder blob_builder2;
ASSERT_TRUE(blob_builder2.Append("b", 1).ok());
auto blob_array2 = blob_builder2.Finish().ValueOrDie();
ASSERT_OK(writer.Write(CreateStructBatch(schema, {int_array, blob_array1, blob_array2})));
ASSERT_OK_AND_ASSIGN(CommitIncrement inc, writer.PrepareCommit(/*wait_compaction=*/true));
ASSERT_EQ(inc.GetNewFilesIncrement().NewFiles().size(), 3);
const auto& main_file = inc.GetNewFilesIncrement().NewFiles()[0];
const auto& blob_file1 = inc.GetNewFilesIncrement().NewFiles()[1];
const auto& blob_file2 = inc.GetNewFilesIncrement().NewFiles()[2];
ASSERT_TRUE(
options.GetFileSystem()->Exists(path_factory->ToPath(main_file->file_name)).value());
ASSERT_TRUE(
options.GetFileSystem()->Exists(path_factory->ToPath(blob_file1->file_name)).value());
ASSERT_TRUE(
options.GetFileSystem()->Exists(path_factory->ToPath(blob_file2->file_name)).value());
ASSERT_OK(writer.Close());
}
TEST_F(AppendOnlyWriterTest, TestMultiplePrepareCommitSequenceContinuity) {
auto options = CreateOptions();
auto dir = UniqueTestDirectory::Create();
ASSERT_TRUE(dir);
auto path_factory = CreatePathFactory(dir->Str(), "mock_format", options);
arrow::FieldVector fields = {arrow::field("f0", arrow::utf8())};
auto schema = arrow::schema(fields);
AppendOnlyWriter writer(options, /*schema_id=*/0, schema, /*write_cols=*/std::nullopt,
/*max_sequence_number=*/-1, path_factory, compact_manager_,
memory_pool_);
ASSERT_OK(writer.Write(CreateSingleStringBatch({"a", "b", "c"})));
ASSERT_OK_AND_ASSIGN(CommitIncrement first, writer.PrepareCommit(/*wait_compaction=*/false));
ASSERT_OK(writer.Write(CreateSingleStringBatch({"d", "e"})));
ASSERT_OK_AND_ASSIGN(CommitIncrement second, writer.PrepareCommit(/*wait_compaction=*/false));
ASSERT_EQ(first.GetNewFilesIncrement().NewFiles().size(), 1);
ASSERT_EQ(second.GetNewFilesIncrement().NewFiles().size(), 1);
ASSERT_EQ(first.GetNewFilesIncrement().NewFiles()[0]->min_sequence_number, 0);
ASSERT_EQ(first.GetNewFilesIncrement().NewFiles()[0]->max_sequence_number, 2);
ASSERT_EQ(second.GetNewFilesIncrement().NewFiles()[0]->min_sequence_number, 3);
ASSERT_EQ(second.GetNewFilesIncrement().NewFiles()[0]->max_sequence_number, 4);
ASSERT_OK(writer.Close());
}
} // namespace paimon::test