blob: 7d6e03c034fe706d3cd9aceda7166953db2afde4 [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.
*/
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed 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 "compute/delta/DeltaDeletionVectorReader.h"
#include "compute/delta/RoaringBitmapArray.h"
#include "velox/common/base/tests/GTestUtils.h"
#include <gtest/gtest.h>
using namespace facebook::velox;
using namespace gluten::delta;
class DeltaDeletionVectorReaderTest : public ::testing::Test {
protected:
static void SetUpTestSuite() {
memory::MemoryManager::testingSetInstance(memory::MemoryManager::Options{});
}
void SetUp() override {
pool_ = memory::memoryManager()->addLeafPool();
}
std::string createSerializedPayload(const std::vector<uint64_t>& deletedRows) {
RoaringBitmapArray bitmap;
for (auto row : deletedRows) {
bitmap.addSafe(row);
}
const auto serializedSize = bitmap.serializedSizeInBytes();
auto buffer = AlignedBuffer::allocate<char>(serializedSize, pool_.get());
bitmap.serialize(buffer->asMutable<char>());
return std::string(buffer->as<char>(), serializedSize);
}
std::shared_ptr<memory::MemoryPool> pool_;
};
TEST_F(DeltaDeletionVectorReaderTest, LoadSerializedPayload) {
auto payload = createSerializedPayload({2, 7, 12});
DeltaDeletionVectorReader reader;
reader.loadSerializedDeletionVector(payload, 3);
EXPECT_TRUE(reader.isRowDeleted(2));
EXPECT_TRUE(reader.isRowDeleted(7));
EXPECT_TRUE(reader.isRowDeleted(12));
EXPECT_FALSE(reader.isRowDeleted(0));
EXPECT_FALSE(reader.isRowDeleted(3));
EXPECT_FALSE(reader.isRowDeleted(20));
}
TEST_F(DeltaDeletionVectorReaderTest, LoadPortablePayload) {
// Captured from a Delta 3.3.2 table after `DELETE WHERE id < 10`.
const std::vector<uint8_t> payloadBytes = {0xd1, 0xd3, 0x39, 0x64, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x30, 0x00, 0x00, 0x01, 0x00,
0x00, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00};
DeltaDeletionVectorReader reader;
reader.loadSerializedDeletionVector(
std::string_view(reinterpret_cast<const char*>(payloadBytes.data()), payloadBytes.size()), 10);
for (uint64_t deleted = 0; deleted < 10; ++deleted) {
EXPECT_TRUE(reader.isRowDeleted(deleted));
}
EXPECT_FALSE(reader.isRowDeleted(10));
EXPECT_FALSE(reader.isRowDeleted(100));
}
TEST_F(DeltaDeletionVectorReaderTest, ApplyDeletionFilter) {
auto payload = createSerializedPayload({2, 5, 8});
DeltaDeletionVectorReader reader;
reader.loadSerializedDeletionVector(payload);
auto deleteBitmap = AlignedBuffer::allocate<uint64_t>(bits::nwords(10), pool_.get());
reader.applyDeletionFilter(0, 10, deleteBitmap);
auto* rawBitmap = deleteBitmap->as<uint64_t>();
EXPECT_TRUE(bits::isBitSet(rawBitmap, 2));
EXPECT_TRUE(bits::isBitSet(rawBitmap, 5));
EXPECT_TRUE(bits::isBitSet(rawBitmap, 8));
EXPECT_FALSE(bits::isBitSet(rawBitmap, 1));
EXPECT_FALSE(bits::isBitSet(rawBitmap, 7));
}
TEST_F(DeltaDeletionVectorReaderTest, ApplyDeletionFilterWithOffset) {
auto payload = createSerializedPayload({10, 15, 20});
DeltaDeletionVectorReader reader;
reader.loadSerializedDeletionVector(payload);
auto deleteBitmap = AlignedBuffer::allocate<uint64_t>(bits::nwords(15), pool_.get());
reader.applyDeletionFilter(10, 15, deleteBitmap);
auto* rawBitmap = deleteBitmap->as<uint64_t>();
EXPECT_TRUE(bits::isBitSet(rawBitmap, 0));
EXPECT_TRUE(bits::isBitSet(rawBitmap, 5));
EXPECT_TRUE(bits::isBitSet(rawBitmap, 10));
EXPECT_FALSE(bits::isBitSet(rawBitmap, 1));
EXPECT_FALSE(bits::isBitSet(rawBitmap, 14));
}
TEST_F(DeltaDeletionVectorReaderTest, EmptyReader) {
DeltaDeletionVectorReader reader;
EXPECT_TRUE(reader.empty());
EXPECT_FALSE(reader.isRowDeleted(0));
auto deleteBitmap = AlignedBuffer::allocate<uint64_t>(bits::nwords(10), pool_.get());
reader.applyDeletionFilter(0, 10, deleteBitmap);
auto* rawBitmap = deleteBitmap->as<uint64_t>();
for (int i = 0; i < 10; ++i) {
EXPECT_FALSE(bits::isBitSet(rawBitmap, i));
}
}
TEST_F(DeltaDeletionVectorReaderTest, MultipleLoadsOverwrite) {
DeltaDeletionVectorReader reader;
reader.loadSerializedDeletionVector(createSerializedPayload({1, 2, 3}));
EXPECT_TRUE(reader.isRowDeleted(1));
EXPECT_FALSE(reader.isRowDeleted(10));
reader.loadSerializedDeletionVector(createSerializedPayload({10, 20, 30}));
EXPECT_FALSE(reader.isRowDeleted(1));
EXPECT_TRUE(reader.isRowDeleted(10));
}
TEST_F(DeltaDeletionVectorReaderTest, EmptyPayloadThrows) {
DeltaDeletionVectorReader reader;
VELOX_ASSERT_THROW(reader.loadSerializedDeletionVector(std::string_view()), "Serialized deletion vector is empty");
}
TEST_F(DeltaDeletionVectorReaderTest, CorruptedMagicNumberThrows) {
const uint32_t wrongMagic = 12345678;
const std::string payload(reinterpret_cast<const char*>(&wrongMagic), sizeof(wrongMagic));
DeltaDeletionVectorReader reader;
VELOX_ASSERT_THROW(reader.loadSerializedDeletionVector(payload), "Unexpected Delta bitmap array magic number");
}
TEST_F(DeltaDeletionVectorReaderTest, CardinalityValidationSuccess) {
auto payload = createSerializedPayload({1, 2, 3, 4, 5});
DeltaDeletionVectorReader reader;
EXPECT_NO_THROW(reader.loadSerializedDeletionVector(payload, 5));
EXPECT_EQ(reader.estimatedDeletedRowCount(), 5);
}
TEST_F(DeltaDeletionVectorReaderTest, CardinalityValidationMismatchThrows) {
auto payload = createSerializedPayload({1, 2, 3, 4, 5});
DeltaDeletionVectorReader reader;
EXPECT_THROW(reader.loadSerializedDeletionVector(payload, 3), VeloxUserError);
}
TEST_F(DeltaDeletionVectorReaderTest, LargeCardinalityValidation) {
std::vector<uint64_t> deletedRows;
for (uint64_t i = 0; i < 10000; i += 10) {
deletedRows.push_back(i);
}
DeltaDeletionVectorReader reader;
reader.loadSerializedDeletionVector(createSerializedPayload(deletedRows), 1000);
EXPECT_EQ(reader.estimatedDeletedRowCount(), 1000);
}
TEST_F(DeltaDeletionVectorReaderTest, BatchFilteringPartialOverlap) {
std::vector<uint64_t> deletedRows;
for (uint64_t i = 45; i <= 55; ++i) {
deletedRows.push_back(i);
}
DeltaDeletionVectorReader reader;
reader.loadSerializedDeletionVector(createSerializedPayload(deletedRows));
auto deleteBitmap = AlignedBuffer::allocate<uint64_t>(bits::nwords(20), pool_.get());
reader.applyDeletionFilter(40, 20, deleteBitmap);
auto* rawBitmap = deleteBitmap->as<uint64_t>();
for (int i = 0; i < 5; ++i) {
EXPECT_FALSE(bits::isBitSet(rawBitmap, i));
}
for (int i = 5; i <= 15; ++i) {
EXPECT_TRUE(bits::isBitSet(rawBitmap, i));
}
for (int i = 16; i < 20; ++i) {
EXPECT_FALSE(bits::isBitSet(rawBitmap, i));
}
}
// --- Corner-case tests for iterator-based applyDeletionFilter ---
TEST_F(DeltaDeletionVectorReaderTest, ApplyDeletionFilterAllRowsDeleted) {
// Every row in the batch is deleted.
std::vector<uint64_t> deletedRows;
for (uint64_t i = 0; i < 100; ++i) {
deletedRows.push_back(i);
}
DeltaDeletionVectorReader reader;
reader.loadSerializedDeletionVector(createSerializedPayload(deletedRows));
auto deleteBitmap = AlignedBuffer::allocate<uint64_t>(bits::nwords(100), pool_.get());
reader.applyDeletionFilter(0, 100, deleteBitmap);
auto* rawBitmap = deleteBitmap->as<uint64_t>();
for (int i = 0; i < 100; ++i) {
EXPECT_TRUE(bits::isBitSet(rawBitmap, i)) << "Row " << i << " should be deleted";
}
EXPECT_GT(deleteBitmap->size(), 0);
}
TEST_F(DeltaDeletionVectorReaderTest, ApplyDeletionFilterNoRowsDeleted) {
// The DV has deletions but none overlap with the batch range.
auto payload = createSerializedPayload({1000, 2000, 3000});
DeltaDeletionVectorReader reader;
reader.loadSerializedDeletionVector(payload);
auto deleteBitmap = AlignedBuffer::allocate<uint64_t>(bits::nwords(100), pool_.get());
reader.applyDeletionFilter(0, 100, deleteBitmap);
auto* rawBitmap = deleteBitmap->as<uint64_t>();
for (int i = 0; i < 100; ++i) {
EXPECT_FALSE(bits::isBitSet(rawBitmap, i));
}
EXPECT_EQ(deleteBitmap->size(), 0);
}
TEST_F(DeltaDeletionVectorReaderTest, ApplyDeletionFilterFirstAndLastRow) {
// Only the first and last row of the batch are deleted.
auto payload = createSerializedPayload({100, 199});
DeltaDeletionVectorReader reader;
reader.loadSerializedDeletionVector(payload);
auto deleteBitmap = AlignedBuffer::allocate<uint64_t>(bits::nwords(100), pool_.get());
reader.applyDeletionFilter(100, 100, deleteBitmap);
auto* rawBitmap = deleteBitmap->as<uint64_t>();
EXPECT_TRUE(bits::isBitSet(rawBitmap, 0));
EXPECT_TRUE(bits::isBitSet(rawBitmap, 99));
for (int i = 1; i < 99; ++i) {
EXPECT_FALSE(bits::isBitSet(rawBitmap, i));
}
}
TEST_F(DeltaDeletionVectorReaderTest, ApplyDeletionFilterLargeOffset) {
// Test with large row offsets (beyond 32-bit boundary) to exercise
// multi-bucket Roaring64Map behavior.
const uint64_t largeBase = static_cast<uint64_t>(3) << 32;
std::vector<uint64_t> deletedRows = {largeBase + 10, largeBase + 50, largeBase + 99};
DeltaDeletionVectorReader reader;
reader.loadSerializedDeletionVector(createSerializedPayload(deletedRows));
auto deleteBitmap = AlignedBuffer::allocate<uint64_t>(bits::nwords(100), pool_.get());
reader.applyDeletionFilter(largeBase, 100, deleteBitmap);
auto* rawBitmap = deleteBitmap->as<uint64_t>();
EXPECT_TRUE(bits::isBitSet(rawBitmap, 10));
EXPECT_TRUE(bits::isBitSet(rawBitmap, 50));
EXPECT_TRUE(bits::isBitSet(rawBitmap, 99));
EXPECT_FALSE(bits::isBitSet(rawBitmap, 0));
EXPECT_FALSE(bits::isBitSet(rawBitmap, 11));
EXPECT_FALSE(bits::isBitSet(rawBitmap, 49));
}
TEST_F(DeltaDeletionVectorReaderTest, ApplyDeletionFilterSingleRowBatch) {
// Batch of size 1 where the single row is deleted.
auto payload = createSerializedPayload({42});
DeltaDeletionVectorReader reader;
reader.loadSerializedDeletionVector(payload);
auto deleteBitmap = AlignedBuffer::allocate<uint64_t>(bits::nwords(1), pool_.get());
reader.applyDeletionFilter(42, 1, deleteBitmap);
auto* rawBitmap = deleteBitmap->as<uint64_t>();
EXPECT_TRUE(bits::isBitSet(rawBitmap, 0));
}
TEST_F(DeltaDeletionVectorReaderTest, ApplyDeletionFilterSingleRowBatchNotDeleted) {
// Batch of size 1 where the single row is NOT deleted.
auto payload = createSerializedPayload({42});
DeltaDeletionVectorReader reader;
reader.loadSerializedDeletionVector(payload);
auto deleteBitmap = AlignedBuffer::allocate<uint64_t>(bits::nwords(1), pool_.get());
reader.applyDeletionFilter(43, 1, deleteBitmap);
EXPECT_EQ(deleteBitmap->size(), 0);
}
TEST_F(DeltaDeletionVectorReaderTest, ApplyDeletionFilterDenseSparseMixed) {
// Dense cluster at start, sparse in middle, dense at end.
std::vector<uint64_t> deletedRows;
// Dense: rows 0-49
for (uint64_t i = 0; i < 50; ++i) {
deletedRows.push_back(i);
}
// Sparse: every 100th row from 100-900
for (uint64_t i = 100; i < 1000; i += 100) {
deletedRows.push_back(i);
}
// Dense: rows 950-999
for (uint64_t i = 950; i < 1000; ++i) {
deletedRows.push_back(i);
}
DeltaDeletionVectorReader reader;
reader.loadSerializedDeletionVector(createSerializedPayload(deletedRows));
// Test middle sparse region
auto deleteBitmap = AlignedBuffer::allocate<uint64_t>(bits::nwords(200), pool_.get());
reader.applyDeletionFilter(100, 200, deleteBitmap);
auto* rawBitmap = deleteBitmap->as<uint64_t>();
// Should have rows 100 and 200 marked (every 100th from our sparse set)
EXPECT_TRUE(bits::isBitSet(rawBitmap, 0)); // row 100
EXPECT_TRUE(bits::isBitSet(rawBitmap, 100)); // row 200
EXPECT_FALSE(bits::isBitSet(rawBitmap, 1)); // row 101
EXPECT_FALSE(bits::isBitSet(rawBitmap, 99)); // row 199
}
TEST_F(DeltaDeletionVectorReaderTest, ApplyDeletionFilterBatchBeyondAllDeletions) {
// Batch starts after all deletions.
auto payload = createSerializedPayload({1, 2, 3, 4, 5});
DeltaDeletionVectorReader reader;
reader.loadSerializedDeletionVector(payload);
auto deleteBitmap = AlignedBuffer::allocate<uint64_t>(bits::nwords(100), pool_.get());
reader.applyDeletionFilter(1000, 100, deleteBitmap);
EXPECT_EQ(deleteBitmap->size(), 0);
}
TEST_F(DeltaDeletionVectorReaderTest, ApplyDeletionFilterBatchBeforeAllDeletions) {
// Batch ends before any deletions start.
auto payload = createSerializedPayload({500, 600, 700});
DeltaDeletionVectorReader reader;
reader.loadSerializedDeletionVector(payload);
auto deleteBitmap = AlignedBuffer::allocate<uint64_t>(bits::nwords(100), pool_.get());
reader.applyDeletionFilter(0, 100, deleteBitmap);
EXPECT_EQ(deleteBitmap->size(), 0);
}
TEST_F(DeltaDeletionVectorReaderTest, ApplyDeletionFilterOverflowProtection) {
// Verify that baseReadOffset near UINT64_MAX does not overflow rangeEnd.
// RoaringBitmapArray only supports row indices up to kMaxRepresentableValue,
// so we test with low-value bitmap entries but call applyDeletionFilter with
// a baseReadOffset that would cause baseReadOffset + size to overflow.
auto payload = createSerializedPayload({10, 20, 30});
DeltaDeletionVectorReader reader;
reader.loadSerializedDeletionVector(payload);
// Request a batch of 100 starting near UINT64_MAX — this would overflow
// without the saturation guard (nearMax + 100 > UINT64_MAX).
// No deletions exist in this range, so we just verify no crash and empty
// result.
const uint64_t nearMax = UINT64_MAX - 50;
auto deleteBitmap = AlignedBuffer::allocate<uint64_t>(bits::nwords(100), pool_.get());
reader.applyDeletionFilter(nearMax, 100, deleteBitmap);
// No rows in bitmap are near UINT64_MAX, so result should be empty.
EXPECT_EQ(deleteBitmap->size(), 0);
}