blob: e2bb867ed425782ae9918d109876f5c2b2d3d49b [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 <gtest/gtest.h>
#include <memory>
#include <numeric>
#include <vector>
#include "arrow/api.h"
#include "arrow/c/bridge.h"
#include "paimon/common/factories/io_hook.h"
#include "paimon/common/file_index/rangebitmap/range_bitmap_file_index.h"
#include "paimon/common/utils/arrow/status_utils.h"
#include "paimon/common/utils/scope_guard.h"
#include "paimon/file_index/bitmap_index_result.h"
#include "paimon/fs/file_system.h"
#include "paimon/fs/local/local_file_system.h"
#include "paimon/memory/memory_pool.h"
#include "paimon/predicate/literal.h"
#include "paimon/testing/utils/io_exception_helper.h"
#include "paimon/testing/utils/testharness.h"
#include "paimon/utils/roaring_bitmap32.h"
namespace paimon::test {
class RangeBitmapIoTest : public ::testing::Test {
public:
void SetUp() override {
dir_ = paimon::test::UniqueTestDirectory::Create();
fs_ = dir_->GetFileSystem();
pool_ = GetDefaultPool();
}
void TearDown() override {
ASSERT_OK(fs_->Delete(dir_->Str()));
}
// Helper function to create writer, write data and return serialized bytes
template <typename ArrowBuilder, typename ValueType>
Result<PAIMON_UNIQUE_PTR<Bytes>> CreateIndexData(
const std::shared_ptr<arrow::DataType>& arrow_type,
const std::vector<ValueType>& test_data) {
// Create Arrow array from test data
auto builder = std::make_shared<ArrowBuilder>();
for (const auto& value : test_data) {
PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Append(value));
}
std::shared_ptr<arrow::Array> arrow_array;
PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Finish(&arrow_array));
// Wrap in StructArray
arrow::FieldVector fields = {arrow::field("test_field", arrow_type)};
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::StructArray> struct_array,
arrow::StructArray::Make({arrow_array}, fields));
auto c_array = std::make_unique<::ArrowArray>();
PAIMON_RETURN_NOT_OK_FROM_ARROW(arrow::ExportArray(*struct_array, c_array.get()));
// Create schema
const auto schema = arrow::schema({arrow::field("test_field", arrow_type)});
// Create writer and write data
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<RangeBitmapFileIndexWriter> writer,
RangeBitmapFileIndexWriter::Create(schema, "test_field", {}, pool_));
PAIMON_RETURN_NOT_OK(writer->AddBatch(c_array.get()));
return writer->SerializedBytes();
}
protected:
std::unique_ptr<paimon::test::UniqueTestDirectory> dir_;
std::shared_ptr<paimon::FileSystem> fs_;
std::shared_ptr<MemoryPool> pool_;
};
// Test normal read/write operations
TEST_F(RangeBitmapIoTest, TestSimple) {
// Prepare test data
std::vector<int32_t> test_data = {10, 20, 30, 40, 50};
const auto& arrow_type = arrow::int32();
// Create index data
ASSERT_OK_AND_ASSIGN(PAIMON_UNIQUE_PTR<Bytes> serialized_bytes,
(CreateIndexData<arrow::Int32Builder, int32_t>(arrow_type, test_data)));
ASSERT_TRUE(serialized_bytes);
ASSERT_GT(serialized_bytes->size(), 0);
// Write to file
std::string file_path = dir_->Str() + "/range_bitmap_test.data";
ASSERT_OK_AND_ASSIGN(std::shared_ptr<OutputStream> out,
fs_->Create(file_path, /*overwrite=*/false));
ASSERT_OK_AND_ASSIGN(
int64_t write_len,
out->Write(reinterpret_cast<char*>(serialized_bytes->data()), serialized_bytes->size()));
ASSERT_EQ(write_len, serialized_bytes->size());
ASSERT_OK(out->Flush());
ASSERT_OK(out->Close());
// Read from file and create reader
ASSERT_OK_AND_ASSIGN(std::shared_ptr<InputStream> in, fs_->Open(file_path));
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<RangeBitmapFileIndexReader> reader,
RangeBitmapFileIndexReader::Create(arrow_type, 0, serialized_bytes->size(), in, pool_));
// Test equality query
ASSERT_OK_AND_ASSIGN(auto eq_result, reader->VisitEqual(Literal(static_cast<int32_t>(30))));
auto bitmap_typed_result = std::dynamic_pointer_cast<BitmapIndexResult>(eq_result);
ASSERT_TRUE(bitmap_typed_result);
ASSERT_OK_AND_ASSIGN(const RoaringBitmap32* bitmap_result, bitmap_typed_result->GetBitmap());
ASSERT_TRUE(bitmap_result);
// Value 30 is at position 2
ASSERT_TRUE(bitmap_result->Contains(2));
ASSERT_FALSE(bitmap_result->Contains(0));
ASSERT_FALSE(bitmap_result->Contains(1));
// Test range query
ASSERT_OK_AND_ASSIGN(auto gt_result,
reader->VisitGreaterThan(Literal(static_cast<int32_t>(25))));
bitmap_typed_result = std::dynamic_pointer_cast<BitmapIndexResult>(gt_result);
ASSERT_TRUE(bitmap_typed_result);
ASSERT_OK_AND_ASSIGN(bitmap_result, bitmap_typed_result->GetBitmap());
// Values > 25: 30, 40, 50 at positions 2, 3, 4
ASSERT_TRUE(bitmap_result->Contains(2));
ASSERT_TRUE(bitmap_result->Contains(3));
ASSERT_TRUE(bitmap_result->Contains(4));
ASSERT_FALSE(bitmap_result->Contains(0));
ASSERT_FALSE(bitmap_result->Contains(1));
ASSERT_OK(in->Close());
}
// Test I/O exceptions using IOHook
TEST_F(RangeBitmapIoTest, TestIOException) {
bool run_complete = false;
auto io_hook = paimon::IOHook::GetInstance();
for (size_t i = 0; i < 200; i++) {
auto test_dir = paimon::test::UniqueTestDirectory::Create();
ASSERT_TRUE(test_dir);
paimon::ScopeGuard guard([&io_hook]() { io_hook->Clear(); });
io_hook->Reset(i, paimon::IOHook::Mode::RETURN_ERROR);
// Prepare test data
std::vector<int32_t> test_data = {10, 20, 30, 40, 50};
const auto& arrow_type = arrow::int32();
// Create index data
auto serialized_bytes_result =
CreateIndexData<arrow::Int32Builder, int32_t>(arrow_type, test_data);
CHECK_HOOK_STATUS(serialized_bytes_result.status(), i);
PAIMON_UNIQUE_PTR<Bytes> serialized_bytes = std::move(serialized_bytes_result).value();
// Write to file
std::string file_path = test_dir->Str() + "/range_bitmap_io_exception_test.data";
auto out_result = fs_->Create(file_path, /*overwrite=*/false);
CHECK_HOOK_STATUS(out_result.status(), i);
std::shared_ptr<OutputStream> out = std::move(out_result).value();
auto write_result =
out->Write(reinterpret_cast<char*>(serialized_bytes->data()), serialized_bytes->size());
CHECK_HOOK_STATUS(write_result.status(), i);
CHECK_HOOK_STATUS(out->Flush(), i);
CHECK_HOOK_STATUS(out->Close(), i);
// Read from file and create reader
auto in_result = fs_->Open(file_path);
CHECK_HOOK_STATUS(in_result.status(), i);
std::shared_ptr<InputStream> in = std::move(in_result).value();
auto reader_result =
RangeBitmapFileIndexReader::Create(arrow_type, 0, serialized_bytes->size(), in, pool_);
CHECK_HOOK_STATUS(reader_result.status(), i);
std::shared_ptr<RangeBitmapFileIndexReader> reader = std::move(reader_result).value();
// Test query
auto eq_result = reader->VisitEqual(Literal(static_cast<int32_t>(30)));
CHECK_HOOK_STATUS(eq_result.status(), i);
auto bitmap_typed_result = std::dynamic_pointer_cast<BitmapIndexResult>(eq_result.value());
ASSERT_TRUE(bitmap_typed_result);
auto bitmap_result = bitmap_typed_result->GetBitmap();
CHECK_HOOK_STATUS(bitmap_result.status(), i);
ASSERT_TRUE(bitmap_result.value()->Contains(2)); // Value 30 is at position 2
run_complete = true;
break;
}
ASSERT_TRUE(run_complete);
}
// Test Java compatibility - read java-generated index file
// data: [1, 3, 5, 7, 9, null, null, 10]
TEST_F(RangeBitmapIoTest, TestJavaCompatibility) {
// Load pre-generated range bitmap index file
std::string index_file = GetDataDir() + "/file_index/rangebitmap/rangebitmap.index";
ASSERT_OK_AND_ASSIGN(std::shared_ptr<InputStream> in, fs_->Open(index_file));
ASSERT_OK_AND_ASSIGN(int64_t file_size, in->Length());
ASSERT_GT(file_size, 0);
// Create reader from the index file (int32 type)
const auto& arrow_type = arrow::int32();
ASSERT_OK_AND_ASSIGN(std::shared_ptr<RangeBitmapFileIndexReader> reader,
RangeBitmapFileIndexReader::Create(
arrow_type, 0, static_cast<int32_t>(file_size), in, pool_));
// Test equality query - value 1 at position 0
ASSERT_OK_AND_ASSIGN(auto eq_result, reader->VisitEqual(Literal(static_cast<int32_t>(1))));
auto bitmap_typed_result = std::dynamic_pointer_cast<BitmapIndexResult>(eq_result);
ASSERT_TRUE(bitmap_typed_result);
ASSERT_OK_AND_ASSIGN(const RoaringBitmap32* bitmap, bitmap_typed_result->GetBitmap());
ASSERT_TRUE(bitmap);
ASSERT_TRUE(bitmap->Contains(0)); // value 1 at position 0
ASSERT_FALSE(bitmap->Contains(1));
// Test equality query - value 5 at position 2
ASSERT_OK_AND_ASSIGN(eq_result, reader->VisitEqual(Literal(static_cast<int32_t>(5))));
bitmap_typed_result = std::dynamic_pointer_cast<BitmapIndexResult>(eq_result);
ASSERT_TRUE(bitmap_typed_result);
ASSERT_OK_AND_ASSIGN(bitmap, bitmap_typed_result->GetBitmap());
ASSERT_TRUE(bitmap);
ASSERT_TRUE(bitmap->Contains(2)); // value 5 at position 2
// Test equality query - value 10 at position 7
ASSERT_OK_AND_ASSIGN(eq_result, reader->VisitEqual(Literal(static_cast<int32_t>(10))));
bitmap_typed_result = std::dynamic_pointer_cast<BitmapIndexResult>(eq_result);
ASSERT_TRUE(bitmap_typed_result);
ASSERT_OK_AND_ASSIGN(bitmap, bitmap_typed_result->GetBitmap());
ASSERT_TRUE(bitmap);
ASSERT_TRUE(bitmap->Contains(7)); // value 10 at position 7
// Test greater than query - values > 5: 7, 9, 10 at positions 3, 4, 7
ASSERT_OK_AND_ASSIGN(auto gt_result,
reader->VisitGreaterThan(Literal(static_cast<int32_t>(5))));
bitmap_typed_result = std::dynamic_pointer_cast<BitmapIndexResult>(gt_result);
ASSERT_TRUE(bitmap_typed_result);
ASSERT_OK_AND_ASSIGN(bitmap, bitmap_typed_result->GetBitmap());
ASSERT_TRUE(bitmap);
ASSERT_TRUE(bitmap->Contains(3)); // 7
ASSERT_TRUE(bitmap->Contains(4)); // 9
ASSERT_TRUE(bitmap->Contains(7)); // 10
ASSERT_FALSE(bitmap->Contains(0)); // 1
ASSERT_FALSE(bitmap->Contains(2)); // 5
// Test less than query - values < 5: 1, 3 at positions 0, 1
ASSERT_OK_AND_ASSIGN(auto lt_result, reader->VisitLessThan(Literal(static_cast<int32_t>(5))));
bitmap_typed_result = std::dynamic_pointer_cast<BitmapIndexResult>(lt_result);
ASSERT_TRUE(bitmap_typed_result);
ASSERT_OK_AND_ASSIGN(bitmap, bitmap_typed_result->GetBitmap());
ASSERT_TRUE(bitmap);
ASSERT_TRUE(bitmap->Contains(0)); // 1
ASSERT_TRUE(bitmap->Contains(1)); // 3
ASSERT_FALSE(bitmap->Contains(2)); // 5
ASSERT_FALSE(bitmap->Contains(7)); // 10
// Test is_not_null query - non-null positions: 0, 1, 2, 3, 4, 7
ASSERT_OK_AND_ASSIGN(auto not_null_result, reader->VisitIsNotNull());
bitmap_typed_result = std::dynamic_pointer_cast<BitmapIndexResult>(not_null_result);
ASSERT_TRUE(bitmap_typed_result);
ASSERT_OK_AND_ASSIGN(bitmap, bitmap_typed_result->GetBitmap());
ASSERT_TRUE(bitmap);
ASSERT_TRUE(bitmap->Contains(0));
ASSERT_TRUE(bitmap->Contains(1));
ASSERT_TRUE(bitmap->Contains(2));
ASSERT_TRUE(bitmap->Contains(3));
ASSERT_TRUE(bitmap->Contains(4));
ASSERT_TRUE(bitmap->Contains(7));
ASSERT_FALSE(bitmap->Contains(5)); // null
ASSERT_FALSE(bitmap->Contains(6)); // null
// Test is_null query - null positions: 5, 6
ASSERT_OK_AND_ASSIGN(auto null_result, reader->VisitIsNull());
bitmap_typed_result = std::dynamic_pointer_cast<BitmapIndexResult>(null_result);
ASSERT_TRUE(bitmap_typed_result);
ASSERT_OK_AND_ASSIGN(bitmap, bitmap_typed_result->GetBitmap());
ASSERT_TRUE(bitmap);
ASSERT_TRUE(bitmap->Contains(5)); // null at position 5
ASSERT_TRUE(bitmap->Contains(6)); // null at position 6
ASSERT_FALSE(bitmap->Contains(0));
ASSERT_FALSE(bitmap->Contains(7));
ASSERT_OK(in->Close());
}
} // namespace paimon::test