blob: 65d521a940a77bf36b2b1f0fccc66505392aa9e7 [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 <limits>
#include <memory>
#include <numeric>
#include <vector>
#include <arrow/array.h>
#include <arrow/buffer.h>
#include <arrow/memory_pool.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <parquet/metadata.h>
#include <parquet/properties.h>
#include <parquet/schema.h>
#include "iceberg/parquet/row_number_reader_internal.h"
#include "iceberg/test/matchers.h"
namespace iceberg::parquet {
namespace {
std::unique_ptr<::parquet::FileMetaData> MakeMetadata(
const std::vector<int64_t>& row_group_sizes) {
// Only row counts are needed; no column data or file I/O is involved.
::parquet::SchemaDescriptor schema;
schema.Init(
::parquet::schema::GroupNode::Make("schema", ::parquet::Repetition::REQUIRED, {}));
auto builder = ::parquet::FileMetaDataBuilder::Make(
&schema, ::parquet::default_writer_properties());
for (int64_t size : row_group_sizes) {
auto* row_group = builder->AppendRowGroup();
row_group->set_num_rows(size);
row_group->Finish(0);
}
return builder->Finish();
}
void VerifyNextBatch(RowNumberReader& reader, int64_t batch_size,
const std::vector<int64_t>& expected) {
ICEBERG_UNWRAP_OR_FAIL(auto array, reader.NextBatch(batch_size));
ASSERT_TRUE(array->ValidateFull().ok());
ASSERT_EQ(array->null_count(), 0);
ASSERT_EQ(array->length(), expected.size());
for (size_t i = 0; i < expected.size(); ++i) {
EXPECT_EQ(array->Value(i), expected[i]) << "Row " << i;
}
}
} // namespace
TEST(RowNumberReaderTest, ReadsAllRowGroups) {
auto metadata = MakeMetadata({2, 3, 4});
ICEBERG_UNWRAP_OR_FAIL(
auto reader,
RowNumberReader::Make(*metadata, {0, 1, 2}, ::arrow::default_memory_pool()));
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 100, {0, 1, 2, 3, 4, 5, 6, 7, 8}));
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 1, {}));
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 1, {}));
}
TEST(RowNumberReaderTest, SkippedRowGroupKeepsFilePositions) {
auto metadata = MakeMetadata({3, 3, 3});
ICEBERG_UNWRAP_OR_FAIL(
auto reader,
RowNumberReader::Make(*metadata, {0, 2}, ::arrow::default_memory_pool()));
// The first batch spans the gap left by the skipped row group.
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 5, {0, 1, 2, 6, 7}));
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 5, {8}));
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 1, {}));
}
TEST(RowNumberReaderTest, ReorderedAndRepeatedRowGroups) {
auto metadata = MakeMetadata({2, 2, 2});
ICEBERG_UNWRAP_OR_FAIL(
auto reader,
RowNumberReader::Make(*metadata, {2, 0, 2}, ::arrow::default_memory_pool()));
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 3, {4, 5, 0}));
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 3, {1, 4, 5}));
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 1, {}));
}
TEST(RowNumberReaderTest, BatchesNotAlignedWithRowGroups) {
auto metadata = MakeMetadata({3, 3, 3});
ICEBERG_UNWRAP_OR_FAIL(
auto reader,
RowNumberReader::Make(*metadata, {0, 1, 2}, ::arrow::default_memory_pool()));
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 2, {0, 1}));
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 4, {2, 3, 4, 5}));
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 4, {6, 7, 8}));
}
TEST(RowNumberReaderTest, EmptyRowGroups) {
auto metadata = MakeMetadata({0, 2, 0, 1, 0});
ICEBERG_UNWRAP_OR_FAIL(
auto reader,
RowNumberReader::Make(*metadata, {0, 1, 2, 3, 4}, ::arrow::default_memory_pool()));
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 2, {0, 1}));
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 2, {2}));
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 2, {}));
}
TEST(RowNumberReaderTest, NoRowGroups) {
for (const auto& sizes : {std::vector<int64_t>{}, std::vector<int64_t>{5}}) {
auto metadata = MakeMetadata(sizes);
ICEBERG_UNWRAP_OR_FAIL(
auto reader,
RowNumberReader::Make(*metadata, {}, ::arrow::default_memory_pool()));
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 10, {}));
}
}
TEST(RowNumberReaderTest, ZeroAndNegativeBatchSizesDoNotAdvance) {
auto metadata = MakeMetadata({2});
ICEBERG_UNWRAP_OR_FAIL(
auto reader, RowNumberReader::Make(*metadata, {0}, ::arrow::default_memory_pool()));
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 0, {}));
EXPECT_THAT(reader->NextBatch(-1), IsError(ErrorKind::kInvalidArgument));
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 1, {0}));
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 0, {}));
ASSERT_NO_FATAL_FAILURE(
VerifyNextBatch(*reader, std::numeric_limits<int64_t>::max(), {1}));
}
TEST(RowNumberReaderTest, RowGroupIndexOutOfBounds) {
auto metadata = MakeMetadata({5});
for (int index : {-1, 1}) {
EXPECT_THAT(RowNumberReader::Make(*metadata, {index}, ::arrow::default_memory_pool()),
IsError(ErrorKind::kInvalidArgument));
}
}
TEST(RowNumberReaderTest, NegativeRowCount) {
auto metadata = MakeMetadata({-1});
EXPECT_THAT(RowNumberReader::Make(*metadata, {0}, ::arrow::default_memory_pool()),
IsError(ErrorKind::kInvalidArgument));
}
TEST(RowNumberReaderTest, OversizedBatchDoesNotAdvance) {
auto metadata = MakeMetadata({std::numeric_limits<int64_t>::max()});
ICEBERG_UNWRAP_OR_FAIL(
auto reader, RowNumberReader::Make(*metadata, {0}, ::arrow::default_memory_pool()));
EXPECT_THAT(reader->NextBatch(std::numeric_limits<int64_t>::max()),
IsError(ErrorKind::kInvalidArgument));
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 2, {0, 1}));
}
TEST(RowNumberReaderTest, AllocationFailureDoesNotAdvance) {
auto metadata = MakeMetadata({64, 64, 64, 64});
::arrow::ProxyMemoryPool tracked_pool(::arrow::default_memory_pool());
::arrow::CappedMemoryPool pool(&tracked_pool, 2048);
ICEBERG_UNWRAP_OR_FAIL(auto reader, RowNumberReader::Make(*metadata, {0, 2, 3}, &pool));
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 1, {0}));
// Leave room for the values buffer, but not the validity bitmap.
auto held_buffer = ::arrow::AllocateBuffer(1024, &pool).ValueOrDie();
EXPECT_FALSE(reader->NextBatch(128).has_value());
EXPECT_EQ(pool.bytes_allocated(), 1024);
EXPECT_FALSE(reader->NextBatch(128).has_value());
EXPECT_EQ(pool.bytes_allocated(), 1024);
// Retry the same request after freeing memory, including the row group gap.
held_buffer.reset();
EXPECT_EQ(pool.bytes_allocated(), 0);
std::vector<int64_t> expected(128);
std::iota(expected.begin(), expected.begin() + 63, 1);
std::iota(expected.begin() + 63, expected.end(), 128);
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 128, expected));
// The last row group was only partially consumed by the successful retry.
expected.resize(63);
std::ranges::iota(expected, 193);
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 128, expected));
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 1, {}));
}
TEST(RowNumberReaderTest, BatchesOwnBuffersFromMemoryPool) {
auto metadata = MakeMetadata({4});
::arrow::ProxyMemoryPool pool(::arrow::default_memory_pool());
ICEBERG_UNWRAP_OR_FAIL(auto reader, RowNumberReader::Make(*metadata, {0}, &pool));
ICEBERG_UNWRAP_OR_FAIL(auto first_batch, reader->NextBatch(2));
EXPECT_GT(pool.bytes_allocated(), 0);
ASSERT_NO_FATAL_FAILURE(VerifyNextBatch(*reader, 2, {2, 3}));
reader.reset();
EXPECT_EQ(first_batch->Value(0), 0);
EXPECT_EQ(first_batch->Value(1), 1);
}
} // namespace iceberg::parquet