-
Notifications
You must be signed in to change notification settings - Fork 124
fix: track batch position delete references #842
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f7857c5
08ae1d9
7a0e768
ce7184a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,7 @@ | |
| #include <gtest/gtest.h> | ||
|
|
||
| #include "iceberg/arrow/arrow_io_internal.h" | ||
| #include "iceberg/arrow_c_data_guard_internal.h" | ||
| #include "iceberg/avro/avro_register.h" | ||
| #include "iceberg/data/equality_delete_writer.h" | ||
| #include "iceberg/data/position_delete_writer.h" | ||
|
|
@@ -45,7 +46,9 @@ | |
|
|
||
| namespace iceberg { | ||
|
|
||
| using ::testing::ElementsAre; | ||
| using ::testing::HasSubstr; | ||
| using ::testing::UnorderedElementsAre; | ||
|
|
||
| class DataWriterTest : public ::testing::Test { | ||
| protected: | ||
|
|
@@ -344,18 +347,12 @@ class PositionDeleteWriterTest : public DataWriterTest { | |
| }; | ||
| } | ||
|
|
||
| std::shared_ptr<::arrow::Array> CreatePositionDeleteData() { | ||
| std::shared_ptr<::arrow::Array> CreatePositionDeleteData( | ||
| std::string_view json = | ||
| R"([["data_file_1.parquet", 0], ["data_file_1.parquet", 5], ["data_file_1.parquet", 10]])") { | ||
| auto delete_schema = std::make_shared<Schema>(std::vector<SchemaField>{ | ||
| MetadataColumns::kDeleteFilePath, MetadataColumns::kDeleteFilePos}); | ||
|
|
||
| ArrowSchema arrow_c_schema; | ||
| ICEBERG_THROW_NOT_OK(ToArrowSchema(*delete_schema, &arrow_c_schema)); | ||
| auto arrow_type = ::arrow::ImportType(&arrow_c_schema).ValueOrDie(); | ||
|
|
||
| return ::arrow::json::ArrayFromJSONString( | ||
| ::arrow::struct_(arrow_type->fields()), | ||
| R"([["data_file_1.parquet", 0], ["data_file_1.parquet", 5], ["data_file_1.parquet", 10]])") | ||
| .ValueOrDie(); | ||
| return CreateArray(*delete_schema, json); | ||
| } | ||
| }; | ||
|
|
||
|
|
@@ -458,6 +455,200 @@ TEST_F(PositionDeleteWriterTest, WriteBatchData) { | |
| const auto& data_file = metadata_result.value().data_files[0]; | ||
| EXPECT_EQ(data_file->content, DataFile::Content::kPositionDeletes); | ||
| EXPECT_GT(data_file->file_size_in_bytes, 0); | ||
| ASSERT_TRUE(data_file->referenced_data_file.has_value()); | ||
| EXPECT_EQ(data_file->referenced_data_file.value(), "data_file_1.parquet"); | ||
| // Bounds for delete metadata columns are kept when referencing a single file. | ||
| EXPECT_TRUE(data_file->lower_bounds.contains(MetadataColumns::kDeleteFilePathColumnId)); | ||
| EXPECT_TRUE(data_file->lower_bounds.contains(MetadataColumns::kDeleteFilePosColumnId)); | ||
| EXPECT_TRUE(data_file->upper_bounds.contains(MetadataColumns::kDeleteFilePathColumnId)); | ||
| EXPECT_TRUE(data_file->upper_bounds.contains(MetadataColumns::kDeleteFilePosColumnId)); | ||
| } | ||
|
|
||
| TEST_F(PositionDeleteWriterTest, WriteBatchRejectsSlicedData) { | ||
| auto writer_result = PositionDeleteWriter::Make(MakeDeleteOptions()); | ||
| ASSERT_THAT(writer_result, IsOk()); | ||
| auto writer = std::move(writer_result.value()); | ||
|
|
||
| auto test_data = CreatePositionDeleteData( | ||
| R"([["data_file_1.parquet", 0], ["data_file_1.parquet", 5]])"); | ||
| auto sliced = test_data->Slice(1, 1); | ||
| ArrowArray arrow_array; | ||
| ASSERT_TRUE(::arrow::ExportArray(*sliced, &arrow_array).ok()); | ||
|
|
||
| auto result = writer->Write(&arrow_array); | ||
| EXPECT_EQ(arrow_array.release, nullptr); | ||
| internal::ArrowArrayGuard array_guard(&arrow_array); | ||
| ASSERT_THAT(result, IsError(ErrorKind::kInvalidArgument)); | ||
| EXPECT_THAT( | ||
| result, | ||
| HasErrorMessage("Position delete data with a non-zero offset is not supported")); | ||
| } | ||
|
|
||
| TEST_F(PositionDeleteWriterTest, FailedBatchWriteDoesNotTrackReferencedFiles) { | ||
| auto writer_result = PositionDeleteWriter::Make(MakeDeleteOptions()); | ||
| ASSERT_THAT(writer_result, IsOk()); | ||
| auto writer = std::move(writer_result.value()); | ||
|
|
||
| auto good_data = CreatePositionDeleteData(R"([["data_file_1.parquet", 0]])"); | ||
| ArrowArray good_array; | ||
| ASSERT_TRUE(::arrow::ExportArray(*good_data, &good_array).ok()); | ||
| ASSERT_THAT(writer->Write(&good_array), IsOk()); | ||
|
|
||
| // The batch references a valid path before the null path rejects it, and none of | ||
| // its paths may end up in the metadata. | ||
| auto bad_data = | ||
| CreatePositionDeleteData(R"([["data_file_bad.parquet", 1], [null, 2]])"); | ||
| ArrowArray bad_array; | ||
| ASSERT_TRUE(::arrow::ExportArray(*bad_data, &bad_array).ok()); | ||
| internal::ArrowArrayGuard bad_array_guard(&bad_array); | ||
| ASSERT_THAT(writer->Write(&bad_array), IsError(ErrorKind::kInvalidArrowData)); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks odd to me because we continue to use a failed writer which should not happen in production. And this does actually verify the case name |
||
|
|
||
| ASSERT_THAT(writer->Close(), IsOk()); | ||
|
|
||
| auto metadata_result = writer->Metadata(); | ||
| ASSERT_THAT(metadata_result, IsOk()); | ||
|
|
||
| const auto& write_result = metadata_result.value(); | ||
| const auto& data_file = write_result.data_files[0]; | ||
| ASSERT_TRUE(data_file->referenced_data_file.has_value()); | ||
| EXPECT_EQ(data_file->referenced_data_file.value(), "data_file_1.parquet"); | ||
| EXPECT_THAT(write_result.referenced_data_files, ElementsAre("data_file_1.parquet")); | ||
| } | ||
|
|
||
| TEST_F(PositionDeleteWriterTest, WriteBatchDataForMultipleFiles) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is one batch containing two paths, not multiple successful Write calls. A bug that replaces referenced_paths_ instead of unioning across batches would still pass. Add two successful batches with disjoint paths. |
||
| auto writer_result = PositionDeleteWriter::Make(MakeDeleteOptions()); | ||
| ASSERT_THAT(writer_result, IsOk()); | ||
| auto writer = std::move(writer_result.value()); | ||
|
|
||
| // Disjoint paths across two successful batches must be unioned, not replaced. | ||
| auto first_data = CreatePositionDeleteData(R"([["data_file_1.parquet", 0]])"); | ||
| ArrowArray first_array; | ||
| ASSERT_TRUE(::arrow::ExportArray(*first_data, &first_array).ok()); | ||
| ASSERT_THAT(writer->Write(&first_array), IsOk()); | ||
|
|
||
| auto second_data = CreatePositionDeleteData(R"([["data_file_2.parquet", 5]])"); | ||
| ArrowArray second_array; | ||
| ASSERT_TRUE(::arrow::ExportArray(*second_data, &second_array).ok()); | ||
| ASSERT_THAT(writer->Write(&second_array), IsOk()); | ||
|
|
||
| ASSERT_THAT(writer->Close(), IsOk()); | ||
|
|
||
| auto metadata_result = writer->Metadata(); | ||
| ASSERT_THAT(metadata_result, IsOk()); | ||
|
|
||
| const auto& write_result = metadata_result.value(); | ||
| const auto& data_file = write_result.data_files[0]; | ||
| EXPECT_FALSE(data_file->referenced_data_file.has_value()); | ||
| EXPECT_THAT(write_result.referenced_data_files, | ||
| UnorderedElementsAre("data_file_1.parquet", "data_file_2.parquet")); | ||
| EXPECT_FALSE( | ||
| data_file->lower_bounds.contains(MetadataColumns::kDeleteFilePathColumnId)); | ||
| EXPECT_FALSE(data_file->lower_bounds.contains(MetadataColumns::kDeleteFilePosColumnId)); | ||
| EXPECT_FALSE( | ||
| data_file->upper_bounds.contains(MetadataColumns::kDeleteFilePathColumnId)); | ||
| EXPECT_FALSE(data_file->upper_bounds.contains(MetadataColumns::kDeleteFilePosColumnId)); | ||
| } | ||
|
|
||
| TEST_F(PositionDeleteWriterTest, WriteBatchThenDeleteTracksAllReferencedFiles) { | ||
| auto writer_result = PositionDeleteWriter::Make(MakeDeleteOptions()); | ||
| ASSERT_THAT(writer_result, IsOk()); | ||
| auto writer = std::move(writer_result.value()); | ||
|
|
||
| auto test_data = CreatePositionDeleteData(R"([["data_file_1.parquet", 0]])"); | ||
| ArrowArray arrow_array; | ||
| ASSERT_TRUE(::arrow::ExportArray(*test_data, &arrow_array).ok()); | ||
| ASSERT_THAT(writer->Write(&arrow_array), IsOk()); | ||
| ASSERT_THAT(writer->WriteDelete("data_file_2.parquet", 5), IsOk()); | ||
| ASSERT_THAT(writer->Close(), IsOk()); | ||
|
|
||
| auto metadata_result = writer->Metadata(); | ||
| ASSERT_THAT(metadata_result, IsOk()); | ||
| const auto& write_result = metadata_result.value(); | ||
| EXPECT_FALSE(write_result.data_files[0]->referenced_data_file.has_value()); | ||
| EXPECT_THAT(write_result.referenced_data_files, | ||
| UnorderedElementsAre("data_file_1.parquet", "data_file_2.parquet")); | ||
| } | ||
|
|
||
| TEST_F(PositionDeleteWriterTest, WriteBatchRejectsInvalidInput) { | ||
| // A null array. | ||
| { | ||
| auto writer_result = PositionDeleteWriter::Make(MakeDeleteOptions()); | ||
| ASSERT_THAT(writer_result, IsOk()); | ||
| auto writer = std::move(writer_result.value()); | ||
|
|
||
| auto result = writer->Write(nullptr); | ||
| ASSERT_THAT(result, IsError(ErrorKind::kInvalidArgument)); | ||
| EXPECT_THAT(result, HasErrorMessage("Position delete data must not be null")); | ||
| } | ||
|
|
||
| // A null file path. | ||
| { | ||
| auto writer_result = PositionDeleteWriter::Make(MakeDeleteOptions()); | ||
| ASSERT_THAT(writer_result, IsOk()); | ||
| auto writer = std::move(writer_result.value()); | ||
|
|
||
| auto test_data = CreatePositionDeleteData(R"([[null, 0]])"); | ||
| ArrowArray arrow_array; | ||
| ASSERT_TRUE(::arrow::ExportArray(*test_data, &arrow_array).ok()); | ||
|
|
||
| auto result = writer->Write(&arrow_array); | ||
| EXPECT_EQ(arrow_array.release, nullptr); | ||
| internal::ArrowArrayGuard array_guard(&arrow_array); | ||
| ASSERT_THAT(result, IsError(ErrorKind::kInvalidArrowData)); | ||
| EXPECT_THAT(result, HasErrorMessage( | ||
| "Position delete file paths must not contain null values")); | ||
| } | ||
|
|
||
| // A null position. | ||
| { | ||
| auto writer_result = PositionDeleteWriter::Make(MakeDeleteOptions()); | ||
| ASSERT_THAT(writer_result, IsOk()); | ||
| auto writer = std::move(writer_result.value()); | ||
|
|
||
| auto test_data = CreatePositionDeleteData(R"([["data_file_1.parquet", null]])"); | ||
| ArrowArray arrow_array; | ||
| ASSERT_TRUE(::arrow::ExportArray(*test_data, &arrow_array).ok()); | ||
|
|
||
| auto result = writer->Write(&arrow_array); | ||
| EXPECT_EQ(arrow_array.release, nullptr); | ||
| internal::ArrowArrayGuard array_guard(&arrow_array); | ||
| ASSERT_THAT(result, IsError(ErrorKind::kInvalidArrowData)); | ||
| EXPECT_THAT(result, HasErrorMessage( | ||
| "Position delete positions must not contain null values")); | ||
| } | ||
|
|
||
| // An empty file path. | ||
| { | ||
| auto writer_result = PositionDeleteWriter::Make(MakeDeleteOptions()); | ||
| ASSERT_THAT(writer_result, IsOk()); | ||
| auto writer = std::move(writer_result.value()); | ||
|
|
||
| auto test_data = CreatePositionDeleteData(R"([["", 0]])"); | ||
| ArrowArray arrow_array; | ||
| ASSERT_TRUE(::arrow::ExportArray(*test_data, &arrow_array).ok()); | ||
|
|
||
| auto result = writer->Write(&arrow_array); | ||
| EXPECT_EQ(arrow_array.release, nullptr); | ||
| internal::ArrowArrayGuard array_guard(&arrow_array); | ||
| ASSERT_THAT(result, IsError(ErrorKind::kInvalidArrowData)); | ||
| EXPECT_THAT(result, HasErrorMessage("Position delete file paths must not be empty")); | ||
| } | ||
| } | ||
|
|
||
| TEST_F(PositionDeleteWriterTest, WriteEmptyBatchDoesNotAddReferencedFiles) { | ||
| auto writer_result = PositionDeleteWriter::Make(MakeDeleteOptions()); | ||
| ASSERT_THAT(writer_result, IsOk()); | ||
| auto writer = std::move(writer_result.value()); | ||
|
|
||
| auto test_data = CreatePositionDeleteData("[]"); | ||
| ArrowArray arrow_array; | ||
| ASSERT_TRUE(::arrow::ExportArray(*test_data, &arrow_array).ok()); | ||
| ASSERT_THAT(writer->Write(&arrow_array), IsOk()); | ||
| ASSERT_THAT(writer->Close(), IsOk()); | ||
|
|
||
| auto metadata_result = writer->Metadata(); | ||
| ASSERT_THAT(metadata_result, IsOk()); | ||
| EXPECT_FALSE(metadata_result.value().data_files[0]->referenced_data_file.has_value()); | ||
| } | ||
|
|
||
| TEST_F(PositionDeleteWriterTest, AutoFlushOnThreshold) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we error out in this case?