1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-20 18:05:07 +00:00
serenity/Tests/LibSQL/TestSqlHeap.cpp
Jelle Raaijmakers a6abc1697f LibSQL: Keep track of free heap blocks when trimming storage
When overwriting existing heap storage that requires fewer blocks, make
sure to free all remaining blocks so they can be reused in the future.
2023-05-25 06:19:16 -07:00

129 lines
5.1 KiB
C++

/*
* Copyright (c) 2023, Jelle Raaijmakers <jelle@gmta.nl>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ScopeGuard.h>
#include <AK/StringBuilder.h>
#include <LibCore/System.h>
#include <LibSQL/Heap.h>
#include <LibTest/TestCase.h>
static constexpr auto db_path = "/tmp/test.db"sv;
static NonnullRefPtr<SQL::Heap> create_heap()
{
auto heap = MUST(SQL::Heap::try_create(db_path));
MUST(heap->open());
return heap;
}
TEST_CASE(heap_write_large_storage_without_flush)
{
ScopeGuard guard([]() { MUST(Core::System::unlink(db_path)); });
auto heap = create_heap();
auto storage_block_id = heap->request_new_block_index();
// Write large storage spanning multiple blocks
StringBuilder builder;
MUST(builder.try_append_repeated('x', SQL::Block::DATA_SIZE * 4));
auto long_string = builder.string_view();
TRY_OR_FAIL(heap->write_storage(storage_block_id, long_string.bytes()));
// Read back
auto stored_long_string = TRY_OR_FAIL(heap->read_storage(storage_block_id));
EXPECT_EQ(long_string.bytes(), stored_long_string.bytes());
}
TEST_CASE(heap_write_large_storage_with_flush)
{
ScopeGuard guard([]() { MUST(Core::System::unlink(db_path)); });
auto heap = create_heap();
auto storage_block_id = heap->request_new_block_index();
// Write large storage spanning multiple blocks
StringBuilder builder;
MUST(builder.try_append_repeated('x', SQL::Block::DATA_SIZE * 4));
auto long_string = builder.string_view();
TRY_OR_FAIL(heap->write_storage(storage_block_id, long_string.bytes()));
MUST(heap->flush());
// Read back
auto stored_long_string = TRY_OR_FAIL(heap->read_storage(storage_block_id));
EXPECT_EQ(long_string.bytes(), stored_long_string.bytes());
}
TEST_CASE(heap_overwrite_large_storage)
{
ScopeGuard guard([]() { MUST(Core::System::unlink(db_path)); });
auto heap = create_heap();
auto storage_block_id = heap->request_new_block_index();
// Write large storage spanning multiple blocks
StringBuilder builder;
MUST(builder.try_append_repeated('x', SQL::Block::DATA_SIZE * 4));
auto long_string = builder.string_view();
TRY_OR_FAIL(heap->write_storage(storage_block_id, long_string.bytes()));
MUST(heap->flush());
auto heap_size = MUST(heap->file_size_in_bytes());
// Let's write it again and check whether the Heap reused the same extended blocks
TRY_OR_FAIL(heap->write_storage(storage_block_id, long_string.bytes()));
MUST(heap->flush());
auto new_heap_size = MUST(heap->file_size_in_bytes());
EXPECT_EQ(heap_size, new_heap_size);
// Write a smaller string and read back - heap size should be at most the previous size
builder.clear();
MUST(builder.try_append_repeated('y', SQL::Block::DATA_SIZE * 2));
auto shorter_string = builder.string_view();
TRY_OR_FAIL(heap->write_storage(storage_block_id, shorter_string.bytes()));
MUST(heap->flush());
new_heap_size = MUST(heap->file_size_in_bytes());
EXPECT(new_heap_size <= heap_size);
auto stored_shorter_string = TRY_OR_FAIL(heap->read_storage(storage_block_id));
EXPECT_EQ(shorter_string.bytes(), stored_shorter_string.bytes());
// Write a longer string and read back - heap size is expected to grow
builder.clear();
MUST(builder.try_append_repeated('z', SQL::Block::DATA_SIZE * 6));
auto longest_string = builder.string_view();
TRY_OR_FAIL(heap->write_storage(storage_block_id, longest_string.bytes()));
MUST(heap->flush());
new_heap_size = MUST(heap->file_size_in_bytes());
EXPECT(new_heap_size > heap_size);
auto stored_longest_string = TRY_OR_FAIL(heap->read_storage(storage_block_id));
EXPECT_EQ(longest_string.bytes(), stored_longest_string.bytes());
}
TEST_CASE(heap_reuse_freed_blocks_after_storage_trim)
{
ScopeGuard guard([]() { MUST(Core::System::unlink(db_path)); });
auto heap = create_heap();
// First, write storage spanning 4 blocks
auto first_index = heap->request_new_block_index();
StringBuilder builder;
MUST(builder.try_append_repeated('x', SQL::Block::DATA_SIZE * 4));
auto long_string = builder.string_view();
TRY_OR_FAIL(heap->write_storage(first_index, long_string.bytes()));
MUST(heap->flush());
auto original_heap_size = MUST(heap->file_size_in_bytes());
// Then, overwrite the first storage and reduce it to 2 blocks
builder.clear();
MUST(builder.try_append_repeated('x', SQL::Block::DATA_SIZE * 2));
long_string = builder.string_view();
TRY_OR_FAIL(heap->write_storage(first_index, long_string.bytes()));
MUST(heap->flush());
auto heap_size_after_reduction = MUST(heap->file_size_in_bytes());
EXPECT(heap_size_after_reduction <= original_heap_size);
// Now add the second storage spanning 2 blocks - heap should not have grown compared to the original storage
auto second_index = heap->request_new_block_index();
TRY_OR_FAIL(heap->write_storage(second_index, long_string.bytes()));
MUST(heap->flush());
auto heap_size_after_second_storage = MUST(heap->file_size_in_bytes());
EXPECT(heap_size_after_second_storage <= original_heap_size);
}