blob: e677b0e2e2d0918a58fca1cf4b56195d59b3e91b [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/util/data_file_set.h"
#include <gtest/gtest.h>
#include "iceberg/file_format.h"
#include "iceberg/manifest/manifest_entry.h"
#include "iceberg/row/partition_values.h"
namespace iceberg {
class DataFileSetTest : public ::testing::Test {
protected:
std::shared_ptr<DataFile> CreateDataFile(const std::string& path, int64_t size = 100) {
auto file = std::make_shared<DataFile>();
file->file_path = path;
file->file_format = FileFormatType::kParquet;
file->file_size_in_bytes = size;
file->record_count = 10;
file->content = DataFile::Content::kData;
return file;
}
std::shared_ptr<DataFile> CreateDeleteFile(const std::string& path) {
auto file = CreateDataFile(path);
file->content = DataFile::Content::kPositionDeletes;
return file;
}
std::shared_ptr<DataFile> CreateDV(const std::string& path, int64_t offset,
int64_t size) {
auto file = CreateDeleteFile(path);
file->file_format = FileFormatType::kPuffin;
file->content_offset = offset;
file->content_size_in_bytes = size;
return file;
}
};
TEST_F(DataFileSetTest, EmptySet) {
DataFileSet set;
EXPECT_TRUE(set.empty());
EXPECT_EQ(set.size(), 0);
EXPECT_EQ(set.begin(), set.end());
EXPECT_TRUE(set.as_span().empty());
}
TEST_F(DataFileSetTest, InsertSingleFile) {
DataFileSet set;
auto file = CreateDataFile("/path/to/file.parquet");
auto [iter, inserted] = set.insert(file);
EXPECT_TRUE(inserted);
EXPECT_EQ(*iter, file);
EXPECT_FALSE(set.empty());
EXPECT_EQ(set.size(), 1);
}
TEST_F(DataFileSetTest, InsertDuplicateFile) {
DataFileSet set;
auto file1 = CreateDataFile("/path/to/file.parquet");
auto file2 = CreateDataFile("/path/to/file.parquet"); // Same path
auto [iter1, inserted1] = set.insert(file1);
EXPECT_TRUE(inserted1);
auto [iter2, inserted2] = set.insert(file2);
EXPECT_FALSE(inserted2);
EXPECT_EQ(iter1, iter2); // Should point to the same element
EXPECT_EQ(set.size(), 1); // Should still be size 1
}
TEST_F(DataFileSetTest, InsertDifferentFiles) {
DataFileSet set;
auto file1 = CreateDataFile("/path/to/file1.parquet");
auto file2 = CreateDataFile("/path/to/file2.parquet");
auto file3 = CreateDataFile("/path/to/file3.parquet");
set.insert(file1);
set.insert(file2);
set.insert(file3);
EXPECT_EQ(set.size(), 3);
EXPECT_FALSE(set.empty());
}
TEST_F(DataFileSetTest, InsertionOrderPreserved) {
DataFileSet set;
auto file1 = CreateDataFile("/path/to/file1.parquet");
auto file2 = CreateDataFile("/path/to/file2.parquet");
auto file3 = CreateDataFile("/path/to/file3.parquet");
set.insert(file1);
set.insert(file2);
set.insert(file3);
// Iterate and verify order
std::vector<std::string> paths;
for (const auto& file : set) {
paths.push_back(file->file_path);
}
EXPECT_EQ(paths.size(), 3);
EXPECT_EQ(paths[0], "/path/to/file1.parquet");
EXPECT_EQ(paths[1], "/path/to/file2.parquet");
EXPECT_EQ(paths[2], "/path/to/file3.parquet");
}
TEST_F(DataFileSetTest, AsSpan) {
DataFileSet set;
EXPECT_TRUE(set.as_span().empty());
// Single element
auto file0 = CreateDataFile("/path/to/file0.parquet");
set.insert(file0);
{
auto span = set.as_span();
EXPECT_EQ(span.size(), 1);
EXPECT_EQ(span[0]->file_path, "/path/to/file0.parquet");
EXPECT_EQ(span[0], file0); // Same pointer, span is a view
}
// Multiple elements
auto file1 = CreateDataFile("/path/to/file1.parquet");
auto file2 = CreateDataFile("/path/to/file2.parquet");
set.insert(file1);
set.insert(file2);
auto span = set.as_span();
EXPECT_EQ(span.size(), 3);
EXPECT_EQ(span[0]->file_path, "/path/to/file0.parquet");
EXPECT_EQ(span[1]->file_path, "/path/to/file1.parquet");
EXPECT_EQ(span[2]->file_path, "/path/to/file2.parquet");
// Span matches set iteration order and identity
size_t i = 0;
for (const auto& file : set) {
EXPECT_EQ(span[i], file) << "Span element " << i << " should match set iterator";
++i;
}
EXPECT_EQ(i, span.size());
// Span works with range-for
i = 0;
for (const auto& file : span) {
EXPECT_EQ(file->file_path, span[i]->file_path);
++i;
}
EXPECT_EQ(i, 3);
set.clear();
EXPECT_TRUE(set.as_span().empty());
}
TEST_F(DataFileSetTest, InsertDuplicatePreservesOrder) {
DataFileSet set;
auto file1 = CreateDataFile("/path/to/file1.parquet");
auto file2 = CreateDataFile("/path/to/file2.parquet");
auto file3 = CreateDataFile("/path/to/file1.parquet"); // Duplicate of file1
set.insert(file1);
set.insert(file2);
set.insert(file3); // Should not insert, but order should be preserved
EXPECT_EQ(set.size(), 2);
std::vector<std::string> paths;
for (const auto& file : set) {
paths.push_back(file->file_path);
}
EXPECT_EQ(paths[0], "/path/to/file1.parquet");
EXPECT_EQ(paths[1], "/path/to/file2.parquet");
}
TEST_F(DataFileSetTest, InsertNullFile) {
DataFileSet set;
std::shared_ptr<DataFile> null_file = nullptr;
auto [iter, inserted] = set.insert(null_file);
EXPECT_FALSE(inserted);
EXPECT_EQ(iter, set.end());
EXPECT_TRUE(set.empty());
EXPECT_EQ(set.size(), 0);
}
TEST_F(DataFileSetTest, InsertMoveSemantics) {
DataFileSet set;
auto file1 = CreateDataFile("/path/to/file1.parquet");
auto file2 = CreateDataFile("/path/to/file2.parquet");
// Insert using move
auto [iter1, inserted1] = set.insert(std::move(file1));
EXPECT_TRUE(inserted1);
EXPECT_EQ(file1, nullptr); // Should be moved
// Insert using copy
auto [iter2, inserted2] = set.insert(file2);
EXPECT_TRUE(inserted2);
EXPECT_NE(file2, nullptr); // Should still be valid
EXPECT_EQ(set.size(), 2);
}
TEST_F(DataFileSetTest, Clear) {
DataFileSet set;
set.insert(CreateDataFile("/path/to/file1.parquet"));
set.insert(CreateDataFile("/path/to/file2.parquet"));
EXPECT_EQ(set.size(), 2);
set.clear();
EXPECT_TRUE(set.empty());
EXPECT_EQ(set.size(), 0);
EXPECT_EQ(set.begin(), set.end());
}
TEST_F(DataFileSetTest, IteratorOperations) {
DataFileSet set;
auto file1 = CreateDataFile("/path/to/file1.parquet");
auto file2 = CreateDataFile("/path/to/file2.parquet");
auto file3 = CreateDataFile("/path/to/file3.parquet");
set.insert(file1);
set.insert(file2);
set.insert(file3);
// Test const iterators
const auto& const_set = set;
EXPECT_NE(const_set.begin(), const_set.end());
EXPECT_NE(const_set.cbegin(), const_set.cend());
// Test iterator increment
auto it = set.begin();
EXPECT_EQ((*it)->file_path, "/path/to/file1.parquet");
++it;
EXPECT_EQ((*it)->file_path, "/path/to/file2.parquet");
++it;
EXPECT_EQ((*it)->file_path, "/path/to/file3.parquet");
++it;
EXPECT_EQ(it, set.end());
}
TEST_F(DataFileSetTest, RangeBasedForLoop) {
DataFileSet set;
set.insert(CreateDataFile("/path/to/file1.parquet"));
set.insert(CreateDataFile("/path/to/file2.parquet"));
set.insert(CreateDataFile("/path/to/file3.parquet"));
int count = 0;
for (const auto& file : set) {
EXPECT_NE(file, nullptr);
++count;
}
EXPECT_EQ(count, 3);
}
TEST_F(DataFileSetTest, CaseSensitivePaths) {
DataFileSet set;
auto file1 = CreateDataFile("/path/to/file.parquet");
auto file2 = CreateDataFile("/path/to/FILE.parquet");
set.insert(file1);
set.insert(file2);
EXPECT_EQ(set.size(), 2);
}
TEST_F(DataFileSetTest, MultipleInsertsSameFile) {
DataFileSet set;
auto file = CreateDataFile("/path/to/file.parquet");
set.insert(file);
set.insert(file);
set.insert(file);
EXPECT_EQ(set.size(), 1);
}
TEST_F(DataFileSetTest, CopyRebuildsDataFileIndex) {
DataFileSet original;
original.insert(CreateDataFile("/path/to/file1.parquet"));
original.insert(CreateDataFile("/path/to/file2.parquet"));
DataFileSet copy = original;
auto duplicate = CreateDataFile("/path/to/file1.parquet");
auto [iter, inserted] = copy.insert(duplicate);
EXPECT_FALSE(inserted);
ASSERT_NE(iter, copy.end());
EXPECT_EQ((*iter)->file_path, "/path/to/file1.parquet");
EXPECT_EQ(copy.size(), 2U);
}
TEST_F(DataFileSetTest, DeleteFileSetDeduplicatesByPathForRegularDeletes) {
DeleteFileSet set;
auto first = CreateDeleteFile("/path/to/delete.parquet");
auto duplicate = CreateDeleteFile("/path/to/delete.parquet");
auto [first_iter, first_inserted] = set.insert(first);
EXPECT_TRUE(first_inserted);
EXPECT_EQ(*first_iter, first);
auto [duplicate_iter, duplicate_inserted] = set.insert(duplicate);
EXPECT_FALSE(duplicate_inserted);
EXPECT_EQ(*duplicate_iter, first);
EXPECT_EQ(set.size(), 1U);
EXPECT_TRUE(set.contains(*duplicate));
}
TEST_F(DataFileSetTest, DeleteFileSetDistinguishesDeletionVectorContentRanges) {
DeleteFileSet set;
auto first = CreateDV("/path/to/dv.puffin", /*offset=*/0, /*size=*/10);
auto same_range = CreateDV("/path/to/dv.puffin", /*offset=*/0, /*size=*/10);
auto different_offset = CreateDV("/path/to/dv.puffin", /*offset=*/10, /*size=*/10);
auto different_size = CreateDV("/path/to/dv.puffin", /*offset=*/0, /*size=*/20);
EXPECT_TRUE(set.insert(first).second);
EXPECT_FALSE(set.insert(same_range).second);
EXPECT_TRUE(set.insert(different_offset).second);
EXPECT_TRUE(set.insert(different_size).second);
EXPECT_EQ(set.size(), 3U);
}
TEST_F(DataFileSetTest, DeleteFileSetCopyRebuildsIndex) {
DeleteFileSet original;
original.insert(CreateDV("/path/to/dv.puffin", /*offset=*/0, /*size=*/10));
original.insert(CreateDV("/path/to/dv.puffin", /*offset=*/10, /*size=*/10));
DeleteFileSet copy = original;
auto duplicate = CreateDV("/path/to/dv.puffin", /*offset=*/0, /*size=*/10);
auto [iter, inserted] = copy.insert(duplicate);
EXPECT_FALSE(inserted);
ASSERT_NE(iter, copy.end());
EXPECT_EQ((*iter)->content_offset, 0);
EXPECT_EQ(copy.size(), 2U);
}
} // namespace iceberg