blob: bd4527904d89e22c8cffe1b5bacbd22f2e3189e6 [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 "iceberg/update/merge_append.h"
#include <algorithm>
#include <format>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <tuple>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "iceberg/avro/avro_register.h"
#include "iceberg/constants.h"
#include "iceberg/manifest/manifest_entry.h"
#include "iceberg/manifest/manifest_list.h"
#include "iceberg/manifest/manifest_reader.h"
#include "iceberg/manifest/manifest_writer.h"
#include "iceberg/partition_spec.h"
#include "iceberg/schema.h"
#include "iceberg/snapshot.h"
#include "iceberg/table.h"
#include "iceberg/table_metadata.h"
#include "iceberg/table_properties.h"
#include "iceberg/test/matchers.h"
#include "iceberg/test/mock_catalog.h"
#include "iceberg/test/update_test_base.h"
#include "iceberg/transaction.h"
#include "iceberg/update/fast_append.h"
#include "iceberg/update/update_partition_spec.h"
#include "iceberg/update/update_properties.h"
namespace iceberg {
namespace {
// RollingManifestWriter checks whether to roll every 250 rows.
constexpr size_t kManifestFileGroupSizeForTest = 250;
} // namespace
class MergeAppendTestBase : public MinimalUpdateTestBase {
protected:
static void SetUpTestSuite() { avro::RegisterAll(); }
void SetUp() override {
MinimalUpdateTestBase::SetUp();
ICEBERG_UNWRAP_OR_FAIL(spec_, table_->spec());
ICEBERG_UNWRAP_OR_FAIL(schema_, table_->schema());
file_a_ = MakeDataFile("/data/file_a.parquet", /*partition_x=*/1L);
file_b_ = MakeDataFile("/data/file_b.parquet", /*partition_x=*/2L);
file_c_ = MakeDataFile("/data/file_c.parquet", /*partition_x=*/3L);
file_d_ = MakeDataFile("/data/file_d.parquet", /*partition_x=*/4L);
}
virtual std::string branch() const { return std::string(SnapshotRef::kMainBranch); }
std::shared_ptr<DataFile> MakeDataFile(const std::string& path, int64_t partition_x) {
return MakeDataFile(path, spec_, {Literal::Long(partition_x)});
}
std::vector<std::shared_ptr<DataFile>> MakeDataFiles(std::string_view prefix,
size_t count) {
std::vector<std::shared_ptr<DataFile>> files;
files.reserve(count);
for (size_t i = 0; i < count; ++i) {
files.push_back(MakeDataFile(std::format("{}/file_{}.parquet", prefix, i),
static_cast<int64_t>(i % 2)));
}
return files;
}
std::shared_ptr<DataFile> MakeDataFile(const std::string& path,
std::shared_ptr<PartitionSpec> spec,
std::vector<Literal> partition_values) {
auto file = std::make_shared<DataFile>();
file->content = DataFile::Content::kData;
file->file_path = table_location_ + path;
file->file_format = FileFormatType::kParquet;
file->partition = PartitionValues(std::move(partition_values));
file->file_size_in_bytes = 1024;
file->record_count = 100;
file->partition_spec_id = spec->spec_id();
return file;
}
Result<ManifestFile> WriteManifest(
const std::string& path, const std::vector<std::shared_ptr<DataFile>>& files) {
ICEBERG_ASSIGN_OR_RAISE(
auto writer, ManifestWriter::MakeWriter(
format_version(), std::nullopt, path, file_io_, spec_, schema_,
ManifestContent::kData, /*first_row_id=*/std::nullopt));
for (const auto& file : files) {
ManifestEntry entry;
entry.status = ManifestStatus::kAdded;
entry.snapshot_id = std::nullopt;
entry.data_file = file;
ICEBERG_RETURN_UNEXPECTED(writer->WriteAddedEntry(entry));
}
ICEBERG_RETURN_UNEXPECTED(writer->Close());
return writer->ToManifestFile();
}
Result<ManifestFile> WriteManifestEntries(const std::string& path,
const std::vector<ManifestEntry>& entries) {
ICEBERG_ASSIGN_OR_RAISE(
auto writer, ManifestWriter::MakeWriter(
format_version(), std::nullopt, path, file_io_, spec_, schema_,
ManifestContent::kData, /*first_row_id=*/std::nullopt));
for (const auto& entry : entries) {
switch (entry.status) {
case ManifestStatus::kAdded:
ICEBERG_RETURN_UNEXPECTED(writer->WriteAddedEntry(entry));
break;
case ManifestStatus::kExisting:
ICEBERG_RETURN_UNEXPECTED(writer->WriteExistingEntry(entry));
break;
case ManifestStatus::kDeleted:
ICEBERG_RETURN_UNEXPECTED(writer->WriteDeletedEntry(entry));
break;
}
}
ICEBERG_RETURN_UNEXPECTED(writer->Close());
return writer->ToManifestFile();
}
ManifestFile MakeEmptyAppendManifest(std::string path) {
ManifestFile manifest;
manifest.manifest_path = std::move(path);
manifest.content = ManifestContent::kData;
manifest.added_snapshot_id = kInvalidSnapshotId;
manifest.sequence_number = kInvalidSequenceNumber;
manifest.added_files_count = 0;
manifest.existing_files_count = 0;
manifest.deleted_files_count = 0;
return manifest;
}
void SetManifestMinMergeCount(int count) {
ICEBERG_UNWRAP_OR_FAIL(auto props, table_->NewUpdateProperties());
props->Set(std::string(TableProperties::kManifestMinMergeCount.key()),
std::to_string(count));
EXPECT_THAT(props->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
}
void SetManifestTargetSizeBytes(int64_t size_bytes) {
ICEBERG_UNWRAP_OR_FAIL(auto props, table_->NewUpdateProperties());
props->Set(std::string(TableProperties::kManifestTargetSizeBytes.key()),
std::to_string(size_bytes));
EXPECT_THAT(props->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
}
void SetSnapshotIdInheritanceEnabled() {
ICEBERG_UNWRAP_OR_FAIL(auto props, table_->NewUpdateProperties());
props->Set(std::string(TableProperties::kSnapshotIdInheritanceEnabled.key()), "true");
EXPECT_THAT(props->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
}
void SetCommitRetryProperties(int32_t retries) {
ICEBERG_UNWRAP_OR_FAIL(auto props, table_->NewUpdateProperties());
props->Set(std::string(TableProperties::kCommitNumRetries.key()),
std::to_string(retries));
props->Set(std::string(TableProperties::kCommitMinRetryWaitMs.key()), "1");
props->Set(std::string(TableProperties::kCommitMaxRetryWaitMs.key()), "1");
props->Set(std::string(TableProperties::kCommitTotalRetryTimeMs.key()), "1000");
EXPECT_THAT(props->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
}
void BindTableWithFailingCommits(int failures, int* update_call_count = nullptr) {
auto mock_catalog = std::make_shared<::testing::NiceMock<MockCatalog>>();
std::weak_ptr<::testing::NiceMock<MockCatalog>> weak_catalog = mock_catalog;
ON_CALL(*mock_catalog, LoadTable(::testing::_))
.WillByDefault([this, weak_catalog](const TableIdentifier& identifier)
-> Result<std::shared_ptr<Table>> {
ICEBERG_ASSIGN_OR_RAISE(auto loaded, catalog_->LoadTable(identifier));
auto catalog = weak_catalog.lock();
ICEBERG_PRECHECK(catalog != nullptr, "Mock catalog expired");
return Table::Make(loaded->name(), loaded->metadata(),
std::string(loaded->metadata_file_location()), loaded->io(),
catalog);
});
ON_CALL(*mock_catalog, UpdateTable(::testing::_, ::testing::_, ::testing::_))
.WillByDefault(
[this, weak_catalog, failures, update_call_count](
const TableIdentifier& identifier,
const std::vector<std::unique_ptr<TableRequirement>>& requirements,
const std::vector<std::unique_ptr<TableUpdate>>& updates) mutable
-> Result<std::shared_ptr<Table>> {
if (update_call_count != nullptr) {
++*update_call_count;
}
if (failures-- > 0) {
return CommitFailed("Injected failure");
}
ICEBERG_ASSIGN_OR_RAISE(
auto updated, catalog_->UpdateTable(identifier, requirements, updates));
auto catalog = weak_catalog.lock();
ICEBERG_PRECHECK(catalog != nullptr, "Mock catalog expired");
return Table::Make(updated->name(), updated->metadata(),
std::string(updated->metadata_file_location()),
updated->io(), catalog);
});
ICEBERG_UNWRAP_OR_FAIL(auto bound_table,
Table::Make(table_->name(), table_->metadata(),
std::string(table_->metadata_file_location()),
table_->io(), mock_catalog));
table_ = std::move(bound_table);
mock_catalogs_.push_back(std::move(mock_catalog));
}
// Create a MergeAppend already targeting branch(), so each test runs against
// both the main branch and a named branch without per-call boilerplate.
Result<std::shared_ptr<MergeAppend>> NewBranchMergeAppend() {
ICEBERG_ASSIGN_OR_RAISE(auto append, table_->NewMergeAppend());
append->ToBranch(branch());
return append;
}
Result<std::shared_ptr<FastAppend>> NewBranchFastAppend() {
ICEBERG_ASSIGN_OR_RAISE(auto append, table_->NewFastAppend());
append->ToBranch(branch());
return append;
}
// Returns the snapshot referenced by branch(), so tests work uniformly whether
// they commit to the main branch or to a named branch.
Result<std::shared_ptr<Snapshot>> CurrentSnapshot() {
const auto& refs = table_->metadata()->refs;
auto it = refs.find(branch());
if (it == refs.end()) {
return NotFound("No snapshot ref for branch '{}'", branch());
}
return table_->SnapshotById(it->second->snapshot_id);
}
Result<std::vector<ManifestFile>> CurrentDataManifests() {
ICEBERG_ASSIGN_OR_RAISE(auto snapshot, CurrentSnapshot());
return DataManifests(snapshot);
}
Result<std::shared_ptr<Snapshot>> SnapshotForBranch(const TableMetadata& metadata) {
auto it = metadata.refs.find(branch());
if (it == metadata.refs.end()) {
return NotFound("No snapshot ref for branch '{}'", branch());
}
return metadata.SnapshotById(it->second->snapshot_id);
}
Result<std::vector<ManifestFile>> DataManifests(
const std::shared_ptr<Snapshot>& snapshot) {
SnapshotCache snapshot_cache(snapshot.get());
ICEBERG_ASSIGN_OR_RAISE(auto manifests, snapshot_cache.DataManifests(file_io_));
return std::vector<ManifestFile>(manifests.begin(), manifests.end());
}
bool FileExists(const std::string& path) {
auto input_file = file_io_->NewInputFile(path);
if (!input_file.has_value()) {
return false;
}
return input_file.value()->Size().has_value();
}
Result<std::vector<ManifestEntry>> ReadEntries(const ManifestFile& manifest) {
return ReadAllEntries(std::span<const ManifestFile>(&manifest, 1));
}
Result<std::vector<ManifestEntry>> ReadAllEntries(
std::span<const ManifestFile> manifests) {
std::vector<ManifestEntry> result;
for (const auto& manifest : manifests) {
ICEBERG_ASSIGN_OR_RAISE(
auto spec, table_->metadata()->PartitionSpecById(manifest.partition_spec_id));
ICEBERG_ASSIGN_OR_RAISE(auto reader,
ManifestReader::Make(manifest, file_io_, schema_, spec));
ICEBERG_ASSIGN_OR_RAISE(auto entries, reader->Entries());
result.insert(result.end(), entries.begin(), entries.end());
}
return result;
}
std::vector<std::string> ManifestPaths(const std::vector<ManifestFile>& manifests) {
std::vector<std::string> paths;
paths.reserve(manifests.size());
for (const auto& manifest : manifests) {
paths.push_back(manifest.manifest_path);
}
return paths;
}
const ManifestFile* FindManifestByPath(const std::vector<ManifestFile>& manifests,
const std::string& path) {
auto it = std::ranges::find_if(manifests, [&path](const ManifestFile& manifest) {
return manifest.manifest_path == path;
});
return it == manifests.end() ? nullptr : &*it;
}
const ManifestFile* FindManifestForSpec(const std::vector<ManifestFile>& manifests,
int32_t spec_id) {
auto it = std::ranges::find_if(manifests, [spec_id](const ManifestFile& manifest) {
return manifest.partition_spec_id == spec_id;
});
return it == manifests.end() ? nullptr : &*it;
}
void ExpectManifestEntries(const ManifestFile& manifest,
const std::vector<std::shared_ptr<DataFile>>& expected_files,
const std::vector<ManifestStatus>& expected_statuses) {
ASSERT_EQ(expected_files.size(), expected_statuses.size());
ICEBERG_UNWRAP_OR_FAIL(auto entries, ReadEntries(manifest));
ASSERT_EQ(entries.size(), expected_files.size());
for (size_t i = 0; i < entries.size(); ++i) {
EXPECT_EQ(entries[i].status, expected_statuses[i]);
ASSERT_NE(entries[i].data_file, nullptr);
EXPECT_EQ(entries[i].data_file->file_path, expected_files[i]->file_path);
EXPECT_EQ(entries[i].data_file->partition_spec_id,
expected_files[i]->partition_spec_id);
}
}
void ExpectAllEntriesAcrossManifests(
std::span<const ManifestFile> manifests,
const std::vector<std::shared_ptr<DataFile>>& expected_files,
ManifestStatus expected_status) {
ICEBERG_UNWRAP_OR_FAIL(auto entries, ReadAllEntries(manifests));
ASSERT_EQ(entries.size(), expected_files.size());
std::vector<std::string> expected_paths;
expected_paths.reserve(expected_files.size());
for (const auto& file : expected_files) {
expected_paths.push_back(file->file_path);
}
std::vector<std::string> actual_paths;
actual_paths.reserve(entries.size());
for (const auto& entry : entries) {
EXPECT_EQ(entry.status, expected_status);
ASSERT_NE(entry.data_file, nullptr);
actual_paths.push_back(entry.data_file->file_path);
}
EXPECT_THAT(actual_paths, ::testing::UnorderedElementsAreArray(expected_paths));
}
void ExpectAllEntryPathsAcrossManifests(
std::span<const ManifestFile> manifests,
const std::vector<std::shared_ptr<DataFile>>& expected_files) {
ICEBERG_UNWRAP_OR_FAIL(auto entries, ReadAllEntries(manifests));
ASSERT_EQ(entries.size(), expected_files.size());
std::vector<std::string> expected_paths;
expected_paths.reserve(expected_files.size());
for (const auto& file : expected_files) {
expected_paths.push_back(file->file_path);
}
std::vector<std::string> actual_paths;
actual_paths.reserve(entries.size());
for (const auto& entry : entries) {
ASSERT_NE(entry.data_file, nullptr);
actual_paths.push_back(entry.data_file->file_path);
}
EXPECT_THAT(actual_paths, ::testing::UnorderedElementsAreArray(expected_paths));
}
void ExpectManifestEntriesMatchFiles(
const ManifestFile& manifest,
std::span<const std::shared_ptr<DataFile>> expected_files) {
ICEBERG_UNWRAP_OR_FAIL(auto entries, ReadEntries(manifest));
ASSERT_EQ(entries.size(), expected_files.size());
for (size_t i = 0; i < entries.size(); ++i) {
EXPECT_EQ(entries[i].status, ManifestStatus::kAdded);
ASSERT_NE(entries[i].data_file, nullptr);
EXPECT_EQ(entries[i].data_file->file_path, expected_files[i]->file_path);
}
}
void ExpectManifestEntries(const ManifestFile& manifest,
const std::vector<std::shared_ptr<DataFile>>& expected_files,
const std::vector<ManifestStatus>& expected_statuses,
const std::vector<int64_t>& expected_snapshot_ids,
const std::vector<int64_t>& expected_sequence_numbers,
const std::vector<int64_t>& expected_file_sequence_numbers) {
ASSERT_EQ(expected_files.size(), expected_statuses.size());
ASSERT_EQ(expected_files.size(), expected_snapshot_ids.size());
ASSERT_EQ(expected_files.size(), expected_sequence_numbers.size());
ASSERT_EQ(expected_files.size(), expected_file_sequence_numbers.size());
ICEBERG_UNWRAP_OR_FAIL(auto entries, ReadEntries(manifest));
ASSERT_EQ(entries.size(), expected_files.size());
for (size_t i = 0; i < entries.size(); ++i) {
EXPECT_EQ(entries[i].status, expected_statuses[i]);
EXPECT_EQ(entries[i].snapshot_id, expected_snapshot_ids[i]);
EXPECT_EQ(entries[i].sequence_number, expected_sequence_numbers[i]);
EXPECT_EQ(entries[i].file_sequence_number, expected_file_sequence_numbers[i]);
ASSERT_NE(entries[i].data_file, nullptr);
EXPECT_EQ(entries[i].data_file->file_path, expected_files[i]->file_path);
EXPECT_EQ(entries[i].data_file->partition_spec_id,
expected_files[i]->partition_spec_id);
}
}
void ExpectAppendManifestError(ManifestFile manifest,
const std::string& expected_message) {
ICEBERG_UNWRAP_OR_FAIL(auto append, NewBranchMergeAppend());
append->AppendManifest(manifest);
auto result = append->Commit();
EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed));
EXPECT_THAT(result, HasErrorMessage(expected_message));
}
std::shared_ptr<PartitionSpec> spec_;
std::shared_ptr<Schema> schema_;
std::shared_ptr<DataFile> file_a_;
std::shared_ptr<DataFile> file_b_;
std::shared_ptr<DataFile> file_c_;
std::shared_ptr<DataFile> file_d_;
std::vector<std::shared_ptr<::testing::NiceMock<MockCatalog>>> mock_catalogs_;
};
class MergeAppendTest
: public MergeAppendTestBase,
public ::testing::WithParamInterface<std::tuple<int8_t, std::string>> {
protected:
int8_t format_version() const override { return std::get<0>(GetParam()); }
std::string branch() const override { return std::get<1>(GetParam()); }
};
TEST_F(MergeAppendTestBase, AppendNullFile) {
ICEBERG_UNWRAP_OR_FAIL(auto append, NewBranchMergeAppend());
append->AppendFile(nullptr);
auto result = append->Commit();
EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed));
EXPECT_THAT(result, HasErrorMessage("Cannot add a null data file"));
}
TEST_P(MergeAppendTest, AddManyFiles) {
EXPECT_THAT(CurrentSnapshot(), IsError(ErrorKind::kNotFound));
constexpr size_t kManifestCount = 2;
constexpr size_t kFileCount = kManifestFileGroupSizeForTest * kManifestCount;
SetManifestTargetSizeBytes(10);
auto files = MakeDataFiles("/data/many", kFileCount);
ICEBERG_UNWRAP_OR_FAIL(auto append, NewBranchMergeAppend());
for (const auto& file : files) {
append->AppendFile(file);
}
EXPECT_THAT(append->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto snapshot, CurrentSnapshot());
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kAddedDataFiles),
std::to_string(kFileCount));
ICEBERG_UNWRAP_OR_FAIL(auto data_manifests, CurrentDataManifests());
EXPECT_EQ(data_manifests.size(), kManifestCount);
ExpectAllEntriesAcrossManifests(data_manifests, files, ManifestStatus::kAdded);
}
TEST_P(MergeAppendTest, AddManyFilesWithConsistentOrdering) {
constexpr size_t kManifestCount = 3;
constexpr size_t kFileCount = kManifestFileGroupSizeForTest * kManifestCount;
SetManifestTargetSizeBytes(10);
auto files = MakeDataFiles("/data/ordered", kFileCount);
ICEBERG_UNWRAP_OR_FAIL(auto append, NewBranchMergeAppend());
for (const auto& file : files) {
append->AppendFile(file);
}
EXPECT_THAT(append->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto data_manifests, CurrentDataManifests());
ASSERT_EQ(data_manifests.size(), kManifestCount);
for (size_t i = 0; i < kManifestCount; ++i) {
auto first =
files.begin() + static_cast<std::ptrdiff_t>(i * kManifestFileGroupSizeForTest);
auto last = first + static_cast<std::ptrdiff_t>(kManifestFileGroupSizeForTest);
std::vector<std::shared_ptr<DataFile>> expected(first, last);
ExpectManifestEntriesMatchFiles(data_manifests[i], expected);
}
}
TEST_P(MergeAppendTest, EmptyTableAppend) {
ICEBERG_UNWRAP_OR_FAIL(auto append, NewBranchMergeAppend());
append->AppendFile(file_a_).AppendFile(file_b_);
EXPECT_THAT(append->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto snapshot, CurrentSnapshot());
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kAddedDataFiles), "2");
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kAddedRecords), "200");
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kAddedFileSize), "2048");
EXPECT_EQ(table_->metadata()->last_sequence_number, snapshot->sequence_number);
ICEBERG_UNWRAP_OR_FAIL(auto data_manifests, CurrentDataManifests());
ASSERT_EQ(data_manifests.size(), 1U);
EXPECT_EQ(data_manifests[0].added_snapshot_id, snapshot->snapshot_id);
EXPECT_EQ(data_manifests[0].sequence_number, snapshot->sequence_number);
ExpectManifestEntries(data_manifests[0], {file_a_, file_b_},
{ManifestStatus::kAdded, ManifestStatus::kAdded},
{snapshot->snapshot_id, snapshot->snapshot_id},
{snapshot->sequence_number, snapshot->sequence_number},
{snapshot->sequence_number, snapshot->sequence_number});
}
TEST_P(MergeAppendTest, EmptyTableAppendFilesWithDifferentSpecs) {
ICEBERG_UNWRAP_OR_FAIL(auto update_spec, table_->NewUpdatePartitionSpec());
update_spec->AddField("y");
EXPECT_THAT(update_spec->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto new_spec, table_->spec());
auto file_new_spec = MakeDataFile("/data/file_new_spec.parquet", new_spec,
{Literal::Long(5L), Literal::Long(6L)});
ICEBERG_UNWRAP_OR_FAIL(auto append, NewBranchMergeAppend());
append->AppendFile(file_a_).AppendFile(file_new_spec);
EXPECT_THAT(append->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto snapshot, CurrentSnapshot());
SnapshotCache snapshot_cache(snapshot.get());
ICEBERG_UNWRAP_OR_FAIL(auto data_manifests, snapshot_cache.DataManifests(file_io_));
std::vector<ManifestFile> manifest_files(data_manifests.begin(), data_manifests.end());
ASSERT_EQ(manifest_files.size(), 2U);
auto* old_spec_manifest = FindManifestForSpec(manifest_files, spec_->spec_id());
ASSERT_NE(old_spec_manifest, nullptr);
ExpectManifestEntries(*old_spec_manifest, {file_a_}, {ManifestStatus::kAdded});
auto* new_spec_manifest = FindManifestForSpec(manifest_files, new_spec->spec_id());
ASSERT_NE(new_spec_manifest, nullptr);
ExpectManifestEntries(*new_spec_manifest, {file_new_spec}, {ManifestStatus::kAdded});
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kAddedDataFiles), "2");
}
TEST_P(MergeAppendTest, EmptyTableAppendManifest) {
auto path = table_location_ + "/metadata/input.avro";
ICEBERG_UNWRAP_OR_FAIL(auto manifest, WriteManifest(path, {file_a_, file_b_}));
ICEBERG_UNWRAP_OR_FAIL(auto append, NewBranchMergeAppend());
append->AppendManifest(manifest);
EXPECT_THAT(append->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto snapshot, CurrentSnapshot());
ICEBERG_UNWRAP_OR_FAIL(auto data_manifests, CurrentDataManifests());
ASSERT_EQ(data_manifests.size(), 1U);
if (format_version() == 1) {
EXPECT_NE(data_manifests[0].manifest_path, path);
} else {
EXPECT_EQ(data_manifests[0].manifest_path, path);
}
ExpectManifestEntries(data_manifests[0], {file_a_, file_b_},
{ManifestStatus::kAdded, ManifestStatus::kAdded},
{snapshot->snapshot_id, snapshot->snapshot_id},
{snapshot->sequence_number, snapshot->sequence_number},
{snapshot->sequence_number, snapshot->sequence_number});
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kAddedDataFiles), "2");
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kTotalDataFiles), "2");
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kManifestsCreated), "1");
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kManifestsKept), "0");
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kManifestsReplaced), "0");
}
TEST_P(MergeAppendTest, EmptyTableAppendFilesAndManifest) {
auto path = table_location_ + "/metadata/input.avro";
ICEBERG_UNWRAP_OR_FAIL(auto manifest, WriteManifest(path, {file_a_, file_b_}));
ICEBERG_UNWRAP_OR_FAIL(auto append, NewBranchMergeAppend());
append->AppendFile(file_c_).AppendFile(file_d_).AppendManifest(manifest);
EXPECT_THAT(append->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto snapshot, CurrentSnapshot());
ICEBERG_UNWRAP_OR_FAIL(auto data_manifests, CurrentDataManifests());
EXPECT_EQ(data_manifests.size(), 2U);
if (format_version() == 1) {
EXPECT_EQ(FindManifestByPath(data_manifests, path), nullptr);
ExpectAllEntriesAcrossManifests(data_manifests, {file_a_, file_b_, file_c_, file_d_},
ManifestStatus::kAdded);
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kAddedDataFiles), "4");
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kTotalDataFiles), "4");
return;
}
auto* inherited_manifest = FindManifestByPath(data_manifests, path);
ASSERT_NE(inherited_manifest, nullptr);
ExpectManifestEntries(*inherited_manifest, {file_a_, file_b_},
{ManifestStatus::kAdded, ManifestStatus::kAdded});
auto appended_manifest = std::ranges::find_if(
data_manifests,
[&path](const ManifestFile& manifest) { return manifest.manifest_path != path; });
ASSERT_NE(appended_manifest, data_manifests.end());
ExpectManifestEntries(*appended_manifest, {file_c_, file_d_},
{ManifestStatus::kAdded, ManifestStatus::kAdded});
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kAddedDataFiles), "4");
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kTotalDataFiles), "4");
}
TEST_P(MergeAppendTest, MergeWithAppendFilesAndManifest) {
SetManifestMinMergeCount(1);
auto path = table_location_ + "/metadata/input.avro";
ICEBERG_UNWRAP_OR_FAIL(auto manifest, WriteManifest(path, {file_a_, file_b_}));
ICEBERG_UNWRAP_OR_FAIL(auto append, NewBranchMergeAppend());
append->AppendFile(file_c_).AppendFile(file_d_).AppendManifest(manifest);
EXPECT_THAT(append->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto snapshot, CurrentSnapshot());
SnapshotCache snapshot_cache(snapshot.get());
ICEBERG_UNWRAP_OR_FAIL(auto data_manifests, snapshot_cache.DataManifests(file_io_));
ASSERT_EQ(data_manifests.size(), 1U);
EXPECT_NE(data_manifests[0].manifest_path, path);
ExpectManifestEntries(data_manifests[0], {file_c_, file_d_, file_a_, file_b_},
{ManifestStatus::kAdded, ManifestStatus::kAdded,
ManifestStatus::kAdded, ManifestStatus::kAdded},
{snapshot->snapshot_id, snapshot->snapshot_id,
snapshot->snapshot_id, snapshot->snapshot_id},
{snapshot->sequence_number, snapshot->sequence_number,
snapshot->sequence_number, snapshot->sequence_number},
{snapshot->sequence_number, snapshot->sequence_number,
snapshot->sequence_number, snapshot->sequence_number});
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kAddedDataFiles), "4");
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kManifestsCreated), "1");
}
TEST_P(MergeAppendTest, MergeWithExistingManifest) {
SetManifestMinMergeCount(1);
ICEBERG_UNWRAP_OR_FAIL(auto first_append, NewBranchMergeAppend());
first_append->AppendFile(file_a_).AppendFile(file_b_);
EXPECT_THAT(first_append->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto first_snapshot, CurrentSnapshot());
ICEBERG_UNWRAP_OR_FAIL(auto second_append, NewBranchMergeAppend());
second_append->AppendFile(file_c_).AppendFile(file_d_);
EXPECT_THAT(second_append->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto second_snapshot, CurrentSnapshot());
ICEBERG_UNWRAP_OR_FAIL(auto data_manifests, CurrentDataManifests());
ASSERT_EQ(data_manifests.size(), 1U);
ExpectManifestEntries(
data_manifests[0], {file_c_, file_d_, file_a_, file_b_},
{ManifestStatus::kAdded, ManifestStatus::kAdded, ManifestStatus::kExisting,
ManifestStatus::kExisting},
{second_snapshot->snapshot_id, second_snapshot->snapshot_id,
first_snapshot->snapshot_id, first_snapshot->snapshot_id},
{second_snapshot->sequence_number, second_snapshot->sequence_number,
first_snapshot->sequence_number, first_snapshot->sequence_number},
{second_snapshot->sequence_number, second_snapshot->sequence_number,
first_snapshot->sequence_number, first_snapshot->sequence_number});
}
TEST_P(MergeAppendTest, ManifestMergeMinCount) {
if (format_version() >= 3) {
GTEST_SKIP() << "skips format version 3+";
}
auto path_a1 = table_location_ + "/metadata/input-a1.avro";
auto path_c1 = table_location_ + "/metadata/input-c1.avro";
auto path_d1 = table_location_ + "/metadata/input-d1.avro";
ICEBERG_UNWRAP_OR_FAIL(auto manifest_a1, WriteManifest(path_a1, {file_a_}));
ICEBERG_UNWRAP_OR_FAIL(auto manifest_c1, WriteManifest(path_c1, {file_c_}));
ICEBERG_UNWRAP_OR_FAIL(auto manifest_d1, WriteManifest(path_d1, {file_d_}));
SetManifestMinMergeCount(2);
SetManifestTargetSizeBytes(manifest_c1.manifest_length + manifest_d1.manifest_length +
100);
ICEBERG_UNWRAP_OR_FAIL(auto append1, NewBranchMergeAppend());
append1->AppendManifest(manifest_a1)
.AppendManifest(manifest_c1)
.AppendManifest(manifest_d1);
EXPECT_THAT(append1->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto first_snapshot, CurrentSnapshot());
ICEBERG_UNWRAP_OR_FAIL(auto first_manifests, CurrentDataManifests());
ASSERT_EQ(first_manifests.size(), 2U);
ExpectManifestEntries(first_manifests[0], {file_a_}, {ManifestStatus::kAdded},
{first_snapshot->snapshot_id}, {first_snapshot->sequence_number},
{first_snapshot->sequence_number});
ExpectManifestEntries(
first_manifests[1], {file_c_, file_d_},
{ManifestStatus::kAdded, ManifestStatus::kAdded},
{first_snapshot->snapshot_id, first_snapshot->snapshot_id},
{first_snapshot->sequence_number, first_snapshot->sequence_number},
{first_snapshot->sequence_number, first_snapshot->sequence_number});
EXPECT_EQ(first_snapshot->summary.at(SnapshotSummaryFields::kManifestsCreated), "2");
EXPECT_EQ(first_snapshot->summary.at(SnapshotSummaryFields::kManifestsReplaced), "0");
EXPECT_EQ(first_snapshot->summary.at(SnapshotSummaryFields::kManifestsKept), "0");
ICEBERG_UNWRAP_OR_FAIL(
auto manifest_a2,
WriteManifest(table_location_ + "/metadata/input-a2.avro", {file_a_}));
ICEBERG_UNWRAP_OR_FAIL(
auto manifest_c2,
WriteManifest(table_location_ + "/metadata/input-c2.avro", {file_c_}));
ICEBERG_UNWRAP_OR_FAIL(
auto manifest_d2,
WriteManifest(table_location_ + "/metadata/input-d2.avro", {file_d_}));
ICEBERG_UNWRAP_OR_FAIL(auto append2, NewBranchMergeAppend());
append2->AppendManifest(manifest_a2)
.AppendManifest(manifest_c2)
.AppendManifest(manifest_d2);
EXPECT_THAT(append2->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto second_snapshot, CurrentSnapshot());
ICEBERG_UNWRAP_OR_FAIL(auto second_manifests, CurrentDataManifests());
ASSERT_EQ(second_manifests.size(), 3U);
ExpectManifestEntries(second_manifests[0], {file_a_}, {ManifestStatus::kAdded},
{second_snapshot->snapshot_id},
{second_snapshot->sequence_number},
{second_snapshot->sequence_number});
ExpectManifestEntries(
second_manifests[1], {file_c_, file_d_},
{ManifestStatus::kAdded, ManifestStatus::kAdded},
{second_snapshot->snapshot_id, second_snapshot->snapshot_id},
{second_snapshot->sequence_number, second_snapshot->sequence_number},
{second_snapshot->sequence_number, second_snapshot->sequence_number});
ExpectManifestEntries(
second_manifests[2], {file_a_, file_c_, file_d_},
{ManifestStatus::kExisting, ManifestStatus::kExisting, ManifestStatus::kExisting},
{first_snapshot->snapshot_id, first_snapshot->snapshot_id,
first_snapshot->snapshot_id},
{first_snapshot->sequence_number, first_snapshot->sequence_number,
first_snapshot->sequence_number},
{first_snapshot->sequence_number, first_snapshot->sequence_number,
first_snapshot->sequence_number});
EXPECT_EQ(second_snapshot->summary.at(SnapshotSummaryFields::kAddedDataFiles), "3");
EXPECT_EQ(second_snapshot->summary.at(SnapshotSummaryFields::kManifestsCreated), "3");
EXPECT_EQ(second_snapshot->summary.at(SnapshotSummaryFields::kManifestsReplaced), "2");
EXPECT_EQ(second_snapshot->summary.at(SnapshotSummaryFields::kManifestsKept), "0");
}
TEST_P(MergeAppendTest, ManifestsMergeIntoOne) {
ICEBERG_UNWRAP_OR_FAIL(auto first_append, NewBranchMergeAppend());
first_append->AppendFile(file_a_);
EXPECT_THAT(first_append->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto first_snapshot, CurrentSnapshot());
ICEBERG_UNWRAP_OR_FAIL(auto first_manifests, CurrentDataManifests());
ASSERT_EQ(first_manifests.size(), 1U);
ExpectManifestEntries(first_manifests[0], {file_a_}, {ManifestStatus::kAdded},
{first_snapshot->snapshot_id}, {first_snapshot->sequence_number},
{first_snapshot->sequence_number});
ICEBERG_UNWRAP_OR_FAIL(auto second_append, NewBranchMergeAppend());
second_append->AppendFile(file_b_);
EXPECT_THAT(second_append->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto second_snapshot, CurrentSnapshot());
ICEBERG_UNWRAP_OR_FAIL(auto second_manifests, CurrentDataManifests());
ASSERT_EQ(second_manifests.size(), 2U);
ExpectManifestEntries(second_manifests[0], {file_b_}, {ManifestStatus::kAdded},
{second_snapshot->snapshot_id},
{second_snapshot->sequence_number},
{second_snapshot->sequence_number});
ExpectManifestEntries(second_manifests[1], {file_a_}, {ManifestStatus::kAdded},
{first_snapshot->snapshot_id}, {first_snapshot->sequence_number},
{first_snapshot->sequence_number});
auto path_c = table_location_ + "/metadata/input-m0.avro";
ICEBERG_UNWRAP_OR_FAIL(auto manifest_c, WriteManifest(path_c, {file_c_}));
ICEBERG_UNWRAP_OR_FAIL(auto third_append, NewBranchMergeAppend());
third_append->AppendManifest(manifest_c);
EXPECT_THAT(third_append->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto third_snapshot, CurrentSnapshot());
ICEBERG_UNWRAP_OR_FAIL(auto third_manifests, CurrentDataManifests());
ASSERT_EQ(third_manifests.size(), 3U);
ExpectManifestEntries(third_manifests[0], {file_c_}, {ManifestStatus::kAdded},
{third_snapshot->snapshot_id}, {third_snapshot->sequence_number},
{third_snapshot->sequence_number});
ExpectManifestEntries(third_manifests[1], {file_b_}, {ManifestStatus::kAdded},
{second_snapshot->snapshot_id},
{second_snapshot->sequence_number},
{second_snapshot->sequence_number});
ExpectManifestEntries(third_manifests[2], {file_a_}, {ManifestStatus::kAdded},
{first_snapshot->snapshot_id}, {first_snapshot->sequence_number},
{first_snapshot->sequence_number});
SetManifestMinMergeCount(1);
auto path_d = table_location_ + "/metadata/input-m1.avro";
ICEBERG_UNWRAP_OR_FAIL(auto manifest_d, WriteManifest(path_d, {file_d_}));
ICEBERG_UNWRAP_OR_FAIL(auto fourth_append, NewBranchMergeAppend());
fourth_append->AppendManifest(manifest_d);
EXPECT_THAT(fourth_append->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto fourth_snapshot, CurrentSnapshot());
ICEBERG_UNWRAP_OR_FAIL(auto data_manifests, CurrentDataManifests());
ASSERT_EQ(data_manifests.size(), 1U);
ExpectManifestEntries(
data_manifests[0], {file_d_, file_c_, file_b_, file_a_},
{ManifestStatus::kAdded, ManifestStatus::kExisting, ManifestStatus::kExisting,
ManifestStatus::kExisting},
{fourth_snapshot->snapshot_id, third_snapshot->snapshot_id,
second_snapshot->snapshot_id, first_snapshot->snapshot_id},
{fourth_snapshot->sequence_number, third_snapshot->sequence_number,
second_snapshot->sequence_number, first_snapshot->sequence_number},
{fourth_snapshot->sequence_number, third_snapshot->sequence_number,
second_snapshot->sequence_number, first_snapshot->sequence_number});
}
TEST_P(MergeAppendTest, ManifestDoNotMergeMinCount) {
SetManifestMinMergeCount(4);
auto path1 = table_location_ + "/metadata/input1.avro";
auto path2 = table_location_ + "/metadata/input2.avro";
auto path3 = table_location_ + "/metadata/input3.avro";
ICEBERG_UNWRAP_OR_FAIL(auto manifest1, WriteManifest(path1, {file_a_, file_b_}));
ICEBERG_UNWRAP_OR_FAIL(auto manifest2, WriteManifest(path2, {file_c_}));
ICEBERG_UNWRAP_OR_FAIL(auto manifest3, WriteManifest(path3, {file_d_}));
ICEBERG_UNWRAP_OR_FAIL(auto append, NewBranchMergeAppend());
append->AppendManifest(manifest1).AppendManifest(manifest2).AppendManifest(manifest3);
EXPECT_THAT(append->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto snapshot, CurrentSnapshot());
ICEBERG_UNWRAP_OR_FAIL(auto data_manifests, CurrentDataManifests());
EXPECT_EQ(data_manifests.size(), 3U);
if (format_version() == 1) {
EXPECT_EQ(FindManifestByPath(data_manifests, path1), nullptr);
EXPECT_EQ(FindManifestByPath(data_manifests, path2), nullptr);
EXPECT_EQ(FindManifestByPath(data_manifests, path3), nullptr);
ExpectAllEntriesAcrossManifests(data_manifests, {file_a_, file_b_, file_c_, file_d_},
ManifestStatus::kAdded);
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kAddedDataFiles), "4");
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kManifestsCreated), "3");
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kManifestsKept), "0");
return;
}
EXPECT_THAT(ManifestPaths(data_manifests),
::testing::UnorderedElementsAre(path1, path2, path3));
auto* manifest_1 = FindManifestByPath(data_manifests, path1);
ASSERT_NE(manifest_1, nullptr);
ExpectManifestEntries(*manifest_1, {file_a_, file_b_},
{ManifestStatus::kAdded, ManifestStatus::kAdded});
auto* manifest_2 = FindManifestByPath(data_manifests, path2);
ASSERT_NE(manifest_2, nullptr);
ExpectManifestEntries(*manifest_2, {file_c_}, {ManifestStatus::kAdded});
auto* manifest_3 = FindManifestByPath(data_manifests, path3);
ASSERT_NE(manifest_3, nullptr);
ExpectManifestEntries(*manifest_3, {file_d_}, {ManifestStatus::kAdded});
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kAddedDataFiles), "4");
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kManifestsCreated), "3");
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kManifestsKept), "0");
}
TEST_P(MergeAppendTest, MinMergeCount) {
SetManifestMinMergeCount(4);
ICEBERG_UNWRAP_OR_FAIL(auto fast_append_a, NewBranchFastAppend());
fast_append_a->AppendFile(file_a_);
EXPECT_THAT(fast_append_a->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto fast_append_b, NewBranchFastAppend());
fast_append_b->AppendFile(file_b_);
EXPECT_THAT(fast_append_b->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto append_c, NewBranchMergeAppend());
append_c->AppendFile(file_c_);
EXPECT_THAT(append_c->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto snapshot_before_merge, CurrentSnapshot());
SnapshotCache before_cache(snapshot_before_merge.get());
ICEBERG_UNWRAP_OR_FAIL(auto manifests_before_merge,
before_cache.DataManifests(file_io_));
EXPECT_EQ(manifests_before_merge.size(), 3U);
ICEBERG_UNWRAP_OR_FAIL(auto append_d, NewBranchMergeAppend());
append_d->AppendFile(file_d_);
EXPECT_THAT(append_d->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto snapshot, CurrentSnapshot());
ICEBERG_UNWRAP_OR_FAIL(auto data_manifests, CurrentDataManifests());
EXPECT_EQ(data_manifests.size(), 1U);
ExpectManifestEntries(data_manifests[0], {file_d_, file_c_, file_b_, file_a_},
{ManifestStatus::kAdded, ManifestStatus::kExisting,
ManifestStatus::kExisting, ManifestStatus::kExisting});
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kManifestsReplaced), "3");
}
TEST_P(MergeAppendTest, MergeSizeTargetWithExistingManifest) {
SetManifestTargetSizeBytes(10);
ICEBERG_UNWRAP_OR_FAIL(auto first_append, NewBranchMergeAppend());
first_append->AppendFile(file_a_).AppendFile(file_b_);
EXPECT_THAT(first_append->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto first_snapshot, CurrentSnapshot());
ICEBERG_UNWRAP_OR_FAIL(auto initial_manifests, CurrentDataManifests());
ASSERT_EQ(initial_manifests.size(), 1U);
auto initial_manifest = initial_manifests[0];
ICEBERG_UNWRAP_OR_FAIL(auto second_append, NewBranchMergeAppend());
second_append->AppendFile(file_c_).AppendFile(file_d_);
EXPECT_THAT(second_append->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto second_snapshot, CurrentSnapshot());
ICEBERG_UNWRAP_OR_FAIL(auto data_manifests, CurrentDataManifests());
ASSERT_EQ(data_manifests.size(), 2U);
auto* kept_manifest =
FindManifestByPath(data_manifests, initial_manifest.manifest_path);
ASSERT_NE(kept_manifest, nullptr);
ExpectManifestEntries(
*kept_manifest, {file_a_, file_b_},
{ManifestStatus::kAdded, ManifestStatus::kAdded},
{first_snapshot->snapshot_id, first_snapshot->snapshot_id},
{first_snapshot->sequence_number, first_snapshot->sequence_number},
{first_snapshot->sequence_number, first_snapshot->sequence_number});
auto new_manifest = std::ranges::find_if(
data_manifests, [&initial_manifest](const ManifestFile& manifest) {
return manifest.manifest_path != initial_manifest.manifest_path;
});
ASSERT_NE(new_manifest, data_manifests.end());
ExpectManifestEntries(
*new_manifest, {file_c_, file_d_}, {ManifestStatus::kAdded, ManifestStatus::kAdded},
{second_snapshot->snapshot_id, second_snapshot->snapshot_id},
{second_snapshot->sequence_number, second_snapshot->sequence_number},
{second_snapshot->sequence_number, second_snapshot->sequence_number});
EXPECT_EQ(second_snapshot->summary.at(SnapshotSummaryFields::kManifestsCreated), "1");
EXPECT_EQ(second_snapshot->summary.at(SnapshotSummaryFields::kManifestsKept), "1");
EXPECT_EQ(second_snapshot->summary.at(SnapshotSummaryFields::kManifestsReplaced), "0");
}
TEST_P(MergeAppendTest, ChangedPartitionSpec) {
ICEBERG_UNWRAP_OR_FAIL(auto first_append, NewBranchMergeAppend());
first_append->AppendFile(file_a_).AppendFile(file_b_);
EXPECT_THAT(first_append->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto first_snapshot, CurrentSnapshot());
ICEBERG_UNWRAP_OR_FAIL(auto initial_manifests, CurrentDataManifests());
ASSERT_EQ(initial_manifests.size(), 1U);
auto initial_manifest = initial_manifests[0];
ICEBERG_UNWRAP_OR_FAIL(auto update_spec, table_->NewUpdatePartitionSpec());
update_spec->AddField("y");
EXPECT_THAT(update_spec->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto new_spec, table_->spec());
auto file_new_spec = MakeDataFile("/data/file_new_spec.parquet", new_spec,
{Literal::Long(5L), Literal::Long(6L)});
ICEBERG_UNWRAP_OR_FAIL(auto append, NewBranchMergeAppend());
append->AppendFile(file_new_spec);
EXPECT_THAT(append->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto second_snapshot, CurrentSnapshot());
ICEBERG_UNWRAP_OR_FAIL(auto data_manifests, CurrentDataManifests());
ASSERT_EQ(data_manifests.size(), 2U);
auto* old_spec_manifest =
FindManifestByPath(data_manifests, initial_manifest.manifest_path);
ASSERT_NE(old_spec_manifest, nullptr);
ExpectManifestEntries(
*old_spec_manifest, {file_a_, file_b_},
{ManifestStatus::kAdded, ManifestStatus::kAdded},
{first_snapshot->snapshot_id, first_snapshot->snapshot_id},
{first_snapshot->sequence_number, first_snapshot->sequence_number},
{first_snapshot->sequence_number, first_snapshot->sequence_number});
auto* new_spec_manifest = FindManifestForSpec(data_manifests, new_spec->spec_id());
ASSERT_NE(new_spec_manifest, nullptr);
ExpectManifestEntries(*new_spec_manifest, {file_new_spec}, {ManifestStatus::kAdded},
{second_snapshot->snapshot_id},
{second_snapshot->sequence_number},
{second_snapshot->sequence_number});
}
TEST_P(MergeAppendTest, ChangedPartitionSpecMergeExisting) {
ICEBERG_UNWRAP_OR_FAIL(auto append_a, NewBranchMergeAppend());
append_a->AppendFile(file_a_);
EXPECT_THAT(append_a->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto first_snapshot, CurrentSnapshot());
ICEBERG_UNWRAP_OR_FAIL(auto fast_append_b, NewBranchFastAppend());
fast_append_b->AppendFile(file_b_);
EXPECT_THAT(fast_append_b->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto second_snapshot, CurrentSnapshot());
ICEBERG_UNWRAP_OR_FAIL(auto unmerged_manifests, CurrentDataManifests());
ASSERT_EQ(unmerged_manifests.size(), 2U);
auto unmerged_paths = ManifestPaths(unmerged_manifests);
ICEBERG_UNWRAP_OR_FAIL(auto update_spec, table_->NewUpdatePartitionSpec());
update_spec->AddField("y");
EXPECT_THAT(update_spec->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto new_spec, table_->spec());
auto file_new_spec = MakeDataFile("/data/file_new_spec.parquet", new_spec,
{Literal::Long(5L), Literal::Long(6L)});
ICEBERG_UNWRAP_OR_FAIL(auto append_new_spec, NewBranchMergeAppend());
append_new_spec->AppendFile(file_new_spec);
EXPECT_THAT(append_new_spec->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto third_snapshot, CurrentSnapshot());
ICEBERG_UNWRAP_OR_FAIL(auto data_manifests, CurrentDataManifests());
ASSERT_EQ(data_manifests.size(), 2U);
for (const auto& old_path : unmerged_paths) {
EXPECT_EQ(FindManifestByPath(data_manifests, old_path), nullptr);
}
auto* new_spec_manifest = FindManifestForSpec(data_manifests, new_spec->spec_id());
ASSERT_NE(new_spec_manifest, nullptr);
ExpectManifestEntries(*new_spec_manifest, {file_new_spec}, {ManifestStatus::kAdded},
{third_snapshot->snapshot_id}, {third_snapshot->sequence_number},
{third_snapshot->sequence_number});
auto* old_spec_manifest = FindManifestForSpec(data_manifests, spec_->spec_id());
ASSERT_NE(old_spec_manifest, nullptr);
ExpectManifestEntries(
*old_spec_manifest, {file_b_, file_a_},
{ManifestStatus::kExisting, ManifestStatus::kExisting},
{second_snapshot->snapshot_id, first_snapshot->snapshot_id},
{second_snapshot->sequence_number, first_snapshot->sequence_number},
{second_snapshot->sequence_number, first_snapshot->sequence_number});
}
TEST_P(MergeAppendTest, Failure) {
SetManifestMinMergeCount(1);
SetCommitRetryProperties(1);
ICEBERG_UNWRAP_OR_FAIL(auto first_append, NewBranchMergeAppend());
first_append->AppendFile(file_a_);
EXPECT_THAT(first_append->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto first_snapshot, CurrentSnapshot());
ICEBERG_UNWRAP_OR_FAIL(auto initial_manifests, CurrentDataManifests());
ASSERT_EQ(initial_manifests.size(), 1U);
auto initial_manifest = initial_manifests[0];
int update_call_count = 0;
BindTableWithFailingCommits(/*failures=*/2, &update_call_count);
ICEBERG_UNWRAP_OR_FAIL(auto txn, table_->NewTransaction());
ICEBERG_UNWRAP_OR_FAIL(auto append, txn->NewMergeAppend());
append->ToBranch(branch());
append->AppendFile(file_b_);
EXPECT_THAT(append->Commit(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto pending_snapshot, SnapshotForBranch(txn->current()));
ICEBERG_UNWRAP_OR_FAIL(auto pending_manifests, DataManifests(pending_snapshot));
ASSERT_EQ(pending_manifests.size(), 1U);
auto new_manifest = pending_manifests[0];
EXPECT_TRUE(FileExists(new_manifest.manifest_path));
ExpectManifestEntries(new_manifest, {file_b_, file_a_},
{ManifestStatus::kAdded, ManifestStatus::kExisting});
auto result = txn->Commit();
EXPECT_THAT(result, IsError(ErrorKind::kCommitFailed));
EXPECT_THAT(result, HasErrorMessage("Injected failure"));
EXPECT_EQ(update_call_count, 2);
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto current_snapshot, CurrentSnapshot());
EXPECT_EQ(current_snapshot->snapshot_id, first_snapshot->snapshot_id);
ICEBERG_UNWRAP_OR_FAIL(auto current_manifests, CurrentDataManifests());
ASSERT_EQ(current_manifests.size(), 1U);
EXPECT_EQ(current_manifests[0].manifest_path, initial_manifest.manifest_path);
ExpectManifestEntries(current_manifests[0], {file_a_}, {ManifestStatus::kAdded});
EXPECT_FALSE(FileExists(new_manifest.manifest_path));
}
TEST_P(MergeAppendTest, AppendManifestCleanup) {
SetCommitRetryProperties(1);
int update_call_count = 0;
BindTableWithFailingCommits(/*failures=*/2, &update_call_count);
const auto seq_before = table_->metadata()->last_sequence_number;
auto path = table_location_ + "/metadata/input.avro";
ICEBERG_UNWRAP_OR_FAIL(auto manifest, WriteManifest(path, {file_a_, file_b_}));
EXPECT_TRUE(FileExists(manifest.manifest_path));
ICEBERG_UNWRAP_OR_FAIL(auto txn, table_->NewTransaction());
ICEBERG_UNWRAP_OR_FAIL(auto append, txn->NewMergeAppend());
append->ToBranch(branch());
append->AppendManifest(manifest);
EXPECT_THAT(append->Commit(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto pending_snapshot, SnapshotForBranch(txn->current()));
ICEBERG_UNWRAP_OR_FAIL(auto pending_manifests, DataManifests(pending_snapshot));
ASSERT_EQ(pending_manifests.size(), 1U);
auto new_manifest = pending_manifests[0];
EXPECT_TRUE(FileExists(new_manifest.manifest_path));
if (format_version() == 1) {
EXPECT_NE(new_manifest.manifest_path, manifest.manifest_path);
} else {
EXPECT_EQ(new_manifest.manifest_path, manifest.manifest_path);
}
auto result = txn->Commit();
EXPECT_THAT(result, IsError(ErrorKind::kCommitFailed));
EXPECT_THAT(result, HasErrorMessage("Injected failure"));
EXPECT_EQ(update_call_count, 2);
EXPECT_THAT(table_->Refresh(), IsOk());
EXPECT_THAT(CurrentSnapshot(), IsError(ErrorKind::kNotFound));
EXPECT_EQ(table_->metadata()->last_sequence_number, seq_before);
if (format_version() == 1) {
EXPECT_FALSE(FileExists(new_manifest.manifest_path));
EXPECT_TRUE(FileExists(manifest.manifest_path));
} else {
EXPECT_TRUE(FileExists(new_manifest.manifest_path));
}
}
TEST_P(MergeAppendTest, Recovery) {
SetManifestMinMergeCount(1);
SetCommitRetryProperties(3);
ICEBERG_UNWRAP_OR_FAIL(auto first_append, NewBranchMergeAppend());
first_append->AppendFile(file_a_);
EXPECT_THAT(first_append->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto first_snapshot, CurrentSnapshot());
ICEBERG_UNWRAP_OR_FAIL(auto initial_manifests, CurrentDataManifests());
ASSERT_EQ(initial_manifests.size(), 1U);
auto initial_manifest = initial_manifests[0];
int update_call_count = 0;
BindTableWithFailingCommits(/*failures=*/3, &update_call_count);
ICEBERG_UNWRAP_OR_FAIL(auto txn, table_->NewTransaction());
ICEBERG_UNWRAP_OR_FAIL(auto append, txn->NewMergeAppend());
append->ToBranch(branch());
append->AppendFile(file_b_);
EXPECT_THAT(append->Commit(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto pending_snapshot, SnapshotForBranch(txn->current()));
ICEBERG_UNWRAP_OR_FAIL(auto pending_manifests, DataManifests(pending_snapshot));
ASSERT_EQ(pending_manifests.size(), 1U);
auto pending_manifest = pending_manifests[0];
EXPECT_TRUE(FileExists(pending_manifest.manifest_path));
ExpectManifestEntries(pending_manifest, {file_b_, file_a_},
{ManifestStatus::kAdded, ManifestStatus::kExisting});
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto snapshot_before_commit, CurrentSnapshot());
EXPECT_EQ(snapshot_before_commit->snapshot_id, first_snapshot->snapshot_id);
EXPECT_EQ(table_->metadata()->last_sequence_number, first_snapshot->sequence_number);
EXPECT_THAT(txn->Commit(), IsOk());
EXPECT_EQ(update_call_count, 4);
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto second_snapshot, CurrentSnapshot());
ICEBERG_UNWRAP_OR_FAIL(auto data_manifests, CurrentDataManifests());
ASSERT_EQ(data_manifests.size(), 1U);
EXPECT_EQ(data_manifests[0].manifest_path, pending_manifest.manifest_path);
EXPECT_TRUE(FileExists(pending_manifest.manifest_path));
ExpectManifestEntries(
data_manifests[0], {file_b_, file_a_},
{ManifestStatus::kAdded, ManifestStatus::kExisting},
{second_snapshot->snapshot_id, first_snapshot->snapshot_id},
{second_snapshot->sequence_number, first_snapshot->sequence_number},
{second_snapshot->sequence_number, first_snapshot->sequence_number});
EXPECT_NE(data_manifests[0].manifest_path, initial_manifest.manifest_path);
}
TEST_P(MergeAppendTest, AppendManifestWithSnapshotIdInheritance) {
SetSnapshotIdInheritanceEnabled();
auto path = table_location_ + "/metadata/input.avro";
ICEBERG_UNWRAP_OR_FAIL(auto manifest, WriteManifest(path, {file_a_, file_b_}));
ICEBERG_UNWRAP_OR_FAIL(auto append, NewBranchMergeAppend());
append->AppendManifest(manifest);
EXPECT_THAT(append->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto snapshot, CurrentSnapshot());
ICEBERG_UNWRAP_OR_FAIL(auto data_manifests, CurrentDataManifests());
ASSERT_EQ(data_manifests.size(), 1U);
EXPECT_EQ(data_manifests[0].manifest_path, manifest.manifest_path);
ExpectManifestEntries(data_manifests[0], {file_a_, file_b_},
{ManifestStatus::kAdded, ManifestStatus::kAdded},
{snapshot->snapshot_id, snapshot->snapshot_id},
{snapshot->sequence_number, snapshot->sequence_number},
{snapshot->sequence_number, snapshot->sequence_number});
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kAddedDataFiles), "2");
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kAddedRecords), "200");
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kTotalDataFiles), "2");
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kTotalRecords), "200");
}
TEST_P(MergeAppendTest, MergedAppendManifestCleanupWithSnapshotIdInheritance) {
SetSnapshotIdInheritanceEnabled();
SetManifestMinMergeCount(1);
auto path1 = table_location_ + "/metadata/manifest-file-1.avro";
ICEBERG_UNWRAP_OR_FAIL(auto manifest1, WriteManifest(path1, {file_a_, file_b_}));
ICEBERG_UNWRAP_OR_FAIL(auto append1, NewBranchMergeAppend());
append1->AppendManifest(manifest1);
EXPECT_THAT(append1->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto first_snapshot, CurrentSnapshot());
EXPECT_TRUE(FileExists(manifest1.manifest_path));
auto path2 = table_location_ + "/metadata/manifest-file-2.avro";
ICEBERG_UNWRAP_OR_FAIL(auto manifest2, WriteManifest(path2, {file_c_, file_d_}));
ICEBERG_UNWRAP_OR_FAIL(auto append2, NewBranchMergeAppend());
append2->AppendManifest(manifest2);
EXPECT_THAT(append2->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto second_snapshot, CurrentSnapshot());
ICEBERG_UNWRAP_OR_FAIL(auto data_manifests, CurrentDataManifests());
ASSERT_EQ(data_manifests.size(), 1U);
ExpectManifestEntries(
data_manifests[0], {file_c_, file_d_, file_a_, file_b_},
{ManifestStatus::kAdded, ManifestStatus::kAdded, ManifestStatus::kExisting,
ManifestStatus::kExisting},
{second_snapshot->snapshot_id, second_snapshot->snapshot_id,
first_snapshot->snapshot_id, first_snapshot->snapshot_id},
{second_snapshot->sequence_number, second_snapshot->sequence_number,
first_snapshot->sequence_number, first_snapshot->sequence_number},
{second_snapshot->sequence_number, second_snapshot->sequence_number,
first_snapshot->sequence_number, first_snapshot->sequence_number});
EXPECT_FALSE(FileExists(manifest2.manifest_path));
}
TEST_P(MergeAppendTest, AppendManifestFailureWithSnapshotIdInheritance) {
SetSnapshotIdInheritanceEnabled();
SetCommitRetryProperties(1);
BindTableWithFailingCommits(/*failures=*/2);
const auto seq_before = table_->metadata()->last_sequence_number;
auto path = table_location_ + "/metadata/input.avro";
ICEBERG_UNWRAP_OR_FAIL(auto manifest, WriteManifest(path, {file_a_, file_b_}));
ICEBERG_UNWRAP_OR_FAIL(auto append, NewBranchMergeAppend());
append->AppendManifest(manifest);
auto result = append->Commit();
EXPECT_THAT(result, IsError(ErrorKind::kCommitFailed));
EXPECT_THAT(result, HasErrorMessage("Injected failure"));
EXPECT_THAT(table_->Refresh(), IsOk());
EXPECT_THAT(CurrentSnapshot(), IsError(ErrorKind::kNotFound));
EXPECT_EQ(table_->metadata()->last_sequence_number, seq_before);
EXPECT_TRUE(FileExists(manifest.manifest_path));
}
TEST_P(MergeAppendTest, TransactionNewMergeAppendCommits) {
ICEBERG_UNWRAP_OR_FAIL(auto txn, table_->NewTransaction());
ICEBERG_UNWRAP_OR_FAIL(auto append, txn->NewMergeAppend());
append->ToBranch(branch());
append->AppendFile(file_a_);
EXPECT_THAT(append->Commit(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto committed_table, txn->Commit());
const auto& refs = committed_table->metadata()->refs;
auto ref_it = refs.find(branch());
ASSERT_NE(ref_it, refs.end());
ICEBERG_UNWRAP_OR_FAIL(auto snapshot,
committed_table->SnapshotById(ref_it->second->snapshot_id));
EXPECT_EQ(snapshot->summary.at(SnapshotSummaryFields::kAddedDataFiles), "1");
}
TEST_P(MergeAppendTest, InvalidAppendManifestWithExistingEntry) {
ManifestEntry entry;
entry.status = ManifestStatus::kExisting;
entry.snapshot_id = 12345;
entry.sequence_number = 1;
entry.file_sequence_number = 1;
entry.data_file = file_a_;
ICEBERG_UNWRAP_OR_FAIL(
auto manifest,
WriteManifestEntries(table_location_ + "/metadata/existing.avro", {entry}));
ExpectAppendManifestError(manifest, "Cannot append manifest with existing files");
}
TEST_P(MergeAppendTest, InvalidAppendManifestWithDeletedEntry) {
ManifestEntry entry;
entry.status = ManifestStatus::kDeleted;
entry.snapshot_id = 12345;
entry.sequence_number = 1;
entry.file_sequence_number = 1;
entry.data_file = file_a_;
ICEBERG_UNWRAP_OR_FAIL(
auto manifest,
WriteManifestEntries(table_location_ + "/metadata/deleted.avro", {entry}));
ExpectAppendManifestError(manifest, "Cannot append manifest with deleted files");
}
TEST_F(MergeAppendTestBase, InvalidAppendManifestWithExistingFilesSummary) {
auto manifest = MakeEmptyAppendManifest(table_location_ + "/metadata/existing.avro");
manifest.existing_files_count = 1;
ExpectAppendManifestError(manifest, "Cannot append manifest with existing files");
}
TEST_F(MergeAppendTestBase, InvalidAppendManifestWithDeletedFilesSummary) {
auto manifest = MakeEmptyAppendManifest(table_location_ + "/metadata/deleted.avro");
manifest.deleted_files_count = 1;
ExpectAppendManifestError(manifest, "Cannot append manifest with deleted files");
}
TEST_F(MergeAppendTestBase, InvalidAppendManifestWithAssignedSnapshotId) {
auto manifest =
MakeEmptyAppendManifest(table_location_ + "/metadata/assigned-snapshot.avro");
manifest.added_snapshot_id = 12345;
ExpectAppendManifestError(manifest, "Snapshot id must be assigned during commit");
}
TEST_F(MergeAppendTestBase, InvalidAppendManifestWithAssignedSequenceNumber) {
auto manifest =
MakeEmptyAppendManifest(table_location_ + "/metadata/assigned-sequence.avro");
manifest.sequence_number = 7;
ExpectAppendManifestError(manifest, "Sequence number must be assigned during commit");
}
TEST_P(MergeAppendTest, DefaultPartitionSummaries) {
ICEBERG_UNWRAP_OR_FAIL(auto append, NewBranchFastAppend());
append->AppendFile(file_a_);
EXPECT_THAT(append->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto snapshot, CurrentSnapshot());
const auto& summary = snapshot->summary;
size_t partition_summary_count = 0;
for (const auto& [key, value] : summary) {
std::ignore = value;
if (key.starts_with(SnapshotSummaryFields::kChangedPartitionPrefix)) {
++partition_summary_count;
}
}
EXPECT_EQ(partition_summary_count, 0U);
EXPECT_FALSE(summary.contains(SnapshotSummaryFields::kPartitionSummaryProp));
EXPECT_EQ(summary.at(SnapshotSummaryFields::kChangedPartitionCountProp), "1");
EXPECT_EQ(summary.at(SnapshotSummaryFields::kManifestsCreated), "1");
EXPECT_EQ(summary.at(SnapshotSummaryFields::kManifestsReplaced), "0");
EXPECT_EQ(summary.at(SnapshotSummaryFields::kManifestsKept), "0");
}
TEST_P(MergeAppendTest, IncludedPartitionSummaries) {
ICEBERG_UNWRAP_OR_FAIL(auto props, table_->NewUpdateProperties());
props->Set(std::string(TableProperties::kWritePartitionSummaryLimit.key()), "1");
EXPECT_THAT(props->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto append, NewBranchFastAppend());
append->AppendFile(file_a_);
EXPECT_THAT(append->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto snapshot, CurrentSnapshot());
const auto& summary = snapshot->summary;
size_t partition_summary_count = 0;
for (const auto& [key, value] : summary) {
std::ignore = value;
if (key.starts_with(SnapshotSummaryFields::kChangedPartitionPrefix)) {
++partition_summary_count;
}
}
EXPECT_EQ(partition_summary_count, 1U);
EXPECT_EQ(summary.at(SnapshotSummaryFields::kPartitionSummaryProp), "true");
EXPECT_EQ(summary.at(SnapshotSummaryFields::kChangedPartitionCountProp), "1");
ASSERT_TRUE(summary.contains(SnapshotSummaryFields::kChangedPartitionPrefix + "x=1"));
const auto& partition_summary =
summary.at(SnapshotSummaryFields::kChangedPartitionPrefix + "x=1");
EXPECT_THAT(partition_summary,
::testing::HasSubstr(SnapshotSummaryFields::kAddedDataFiles + "=1"));
EXPECT_THAT(partition_summary,
::testing::HasSubstr(SnapshotSummaryFields::kAddedRecords + "=100"));
EXPECT_THAT(partition_summary,
::testing::HasSubstr(SnapshotSummaryFields::kAddedFileSize + "=1024"));
EXPECT_EQ(summary.at(SnapshotSummaryFields::kManifestsCreated), "1");
EXPECT_EQ(summary.at(SnapshotSummaryFields::kManifestsReplaced), "0");
EXPECT_EQ(summary.at(SnapshotSummaryFields::kManifestsKept), "0");
}
TEST_P(MergeAppendTest, IncludedPartitionSummaryLimit) {
ICEBERG_UNWRAP_OR_FAIL(auto props, table_->NewUpdateProperties());
props->Set(std::string(TableProperties::kWritePartitionSummaryLimit.key()), "1");
EXPECT_THAT(props->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto append, NewBranchFastAppend());
append->AppendFile(file_a_).AppendFile(file_b_);
EXPECT_THAT(append->Commit(), IsOk());
EXPECT_THAT(table_->Refresh(), IsOk());
ICEBERG_UNWRAP_OR_FAIL(auto snapshot, CurrentSnapshot());
const auto& summary = snapshot->summary;
size_t partition_summary_count = 0;
for (const auto& [key, value] : summary) {
std::ignore = value;
if (key.starts_with(SnapshotSummaryFields::kChangedPartitionPrefix)) {
++partition_summary_count;
}
}
EXPECT_EQ(partition_summary_count, 0U);
EXPECT_FALSE(summary.contains(SnapshotSummaryFields::kPartitionSummaryProp));
EXPECT_EQ(summary.at(SnapshotSummaryFields::kChangedPartitionCountProp), "2");
EXPECT_EQ(summary.at(SnapshotSummaryFields::kManifestsCreated), "1");
EXPECT_EQ(summary.at(SnapshotSummaryFields::kManifestsReplaced), "0");
EXPECT_EQ(summary.at(SnapshotSummaryFields::kManifestsKept), "0");
}
INSTANTIATE_TEST_SUITE_P(
MergeAppendVersions, MergeAppendTest,
::testing::Combine(::testing::Values<int8_t>(1, 2, 3),
::testing::Values(std::string(SnapshotRef::kMainBranch),
std::string("testBranch"))),
[](const ::testing::TestParamInfo<std::tuple<int8_t, std::string>>& info) {
const auto& branch = std::get<1>(info.param);
return "V" + std::to_string(std::get<0>(info.param)) + "_" +
(branch == SnapshotRef::kMainBranch ? std::string("main") : branch);
});
} // namespace iceberg