diff --git a/src/include/utils/slotted_page.h b/src/include/utils/slotted_page.h new file mode 100644 index 0000000..c8d09ee --- /dev/null +++ b/src/include/utils/slotted_page.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +namespace buzzdb { + +struct SlottedPage { + struct Header { + Header(uint32_t page_size); + + uint16_t slot_count; + uint16_t next_free_slot; + uint32_t page_size; + }; + + struct Slot { + Slot() = default; + + uint32_t stored_value; + }; + + SlottedPage(uint32_t page_size); + + uint16_t addSlot(uint32_t stored_value); + + // Completely remove a slot at a particular index. + uint16_t removeSlot(int index); + + // Removes the first instance of a value found in a slot without removing the + // slot completely. + uint16_t removeSlot(uint32_t value_to_remove); + + Header slot_header; + std::vector data; + std::vector slots; +}; + +struct SlotReference { + SlotReference(uint64_t page, uint16_t slot); + + // 48 bytes page id and 16 bytes for slot id = 64 byte slot id value + uint64_t slot_id_value; +}; + +} \ No newline at end of file diff --git a/src/slotted_page.cc b/src/slotted_page.cc new file mode 100644 index 0000000..2c8bcda --- /dev/null +++ b/src/slotted_page.cc @@ -0,0 +1,74 @@ +#include "utils/slotted_page.h" +#include + +using SlottedPage = buzzdb::SlottedPage; +using SlotReference = buzzdb::SlotReference; + +SlotReference::SlotReference(uint64_t page, uint16_t slot) { + this -> slot_id_value = (page << 16) | slot; +} + +SlottedPage::Header::Header(uint32_t page_size) { + this -> slot_count = 0; + // Header data takes up around 4 bytes of space. + this -> next_free_slot = page_size - 4; + this -> page_size = page_size; +} + +SlottedPage::SlottedPage(uint32_t page_size) : slot_header(page_size) { + this -> data.resize(page_size - 4); +} + +uint16_t SlottedPage::addSlot(uint32_t stored_value) { + // Case where we have a free slot somewhere and therefore it would be + // wasteful to add a new slot. + if (this -> slot_header.next_free_slot != this -> slot_header.page_size - 4) { + this -> slots[this -> slot_header.next_free_slot].stored_value = stored_value; + uint16_t old_free_slot = this -> slot_header.next_free_slot; + int index = 0; + + for (auto slot : slots) { + if (slot.stored_value == (uint32_t) -1) { + this -> slot_header.next_free_slot = index; + break; + } + index++; + } + + if (this -> slot_header.next_free_slot == old_free_slot) { + this -> slot_header.next_free_slot = -1; + } + + return old_free_slot; + } else { + Slot new_slot; + new_slot.stored_value = stored_value; + this -> slot_header.slot_count++; + this -> slots.push_back(new_slot); + return this -> slot_header.slot_count; + } +} + +uint16_t SlottedPage::removeSlot(int index) { + this -> slots.erase(this -> slots.begin() + index); + this -> slot_header.slot_count--; + return this -> slot_header.slot_count; +} + +uint16_t SlottedPage::removeSlot(uint32_t value_to_remove) { + int index = 0; + + for (auto slot : this -> slots) { + if (slot.stored_value == value_to_remove) { + this -> slots[index].stored_value = -1; + if (this -> slot_header.next_free_slot == (uint16_t) -1 || + (this -> slot_header.next_free_slot != (uint16_t) -1 && index < this -> slot_header.next_free_slot)) { + this -> slot_header.next_free_slot = index; + } + break; + } + index++; + } + + return index; +} \ No newline at end of file diff --git a/test/unit/utils/slotted_page_test.cc b/test/unit/utils/slotted_page_test.cc new file mode 100644 index 0000000..b38d829 --- /dev/null +++ b/test/unit/utils/slotted_page_test.cc @@ -0,0 +1,95 @@ +#include + +#include "utils/slotted_page.h" + +using SlottedPage = buzzdb::SlottedPage; + +namespace { + +TEST(SlottedPageTest, AddSlot) { + SlottedPage new_page(32); + new_page.addSlot(45); + new_page.addSlot(69); + new_page.addSlot(67); + + EXPECT_EQ(45, new_page.slots[0].stored_value); + EXPECT_EQ(69, new_page.slots[1].stored_value); + EXPECT_EQ(67, new_page.slots[2].stored_value); +} + +TEST(SlottedPageTest, RemoveWholeSlotBasic) { + int removeIndex = 1; + SlottedPage new_page(32); + new_page.addSlot(45); + new_page.addSlot(69); + new_page.addSlot(67); + new_page.removeSlot(removeIndex); + + EXPECT_EQ(45, new_page.slots[0].stored_value); + EXPECT_EQ(67, new_page.slots[1].stored_value); +} + +TEST(SlottedPageTest, RemoveValueBasic) { + uint32_t removeValue = 67; + SlottedPage new_page(32); + new_page.addSlot(45); + new_page.addSlot(69); + new_page.addSlot(67); + new_page.removeSlot(removeValue); + + EXPECT_EQ(45, new_page.slots[0].stored_value); + EXPECT_EQ(69, new_page.slots[1].stored_value); + EXPECT_EQ(-1, new_page.slots[2].stored_value); + EXPECT_EQ(2, new_page.slot_header.next_free_slot); +} + +TEST(SlottedPageTest, RemoveValueThenAddNoFreeSlots) { + uint32_t removeValue = 67; + SlottedPage new_page(32); + new_page.addSlot(45); + new_page.addSlot(69); + new_page.addSlot(67); + new_page.removeSlot(removeValue); + + EXPECT_EQ(45, new_page.slots[0].stored_value); + EXPECT_EQ(69, new_page.slots[1].stored_value); + EXPECT_EQ(-1, new_page.slots[2].stored_value); + EXPECT_EQ(2, new_page.slot_header.next_free_slot); + + new_page.addSlot(42); + + EXPECT_EQ(45, new_page.slots[0].stored_value); + EXPECT_EQ(69, new_page.slots[1].stored_value); + EXPECT_EQ(42, new_page.slots[2].stored_value); + EXPECT_EQ((uint16_t) -1, new_page.slot_header.next_free_slot); +} + +TEST(SlottedPageTest, RemoveValueThenAddWithFreeSlot) { + uint32_t removeValue1 = 67; + uint32_t removeValue2 = 69; + SlottedPage new_page(32); + new_page.addSlot(45); + new_page.addSlot(69); + new_page.addSlot(67); + new_page.removeSlot(removeValue1); + new_page.removeSlot(removeValue2); + + EXPECT_EQ(45, new_page.slots[0].stored_value); + EXPECT_EQ(-1, new_page.slots[1].stored_value); + EXPECT_EQ(-1, new_page.slots[2].stored_value); + EXPECT_EQ(1, new_page.slot_header.next_free_slot); + + new_page.addSlot(42); + + EXPECT_EQ(45, new_page.slots[0].stored_value); + EXPECT_EQ(42, new_page.slots[1].stored_value); + EXPECT_EQ(-1, new_page.slots[2].stored_value); + EXPECT_EQ(2, new_page.slot_header.next_free_slot); +} + +} + +int main(int argc, char *argv[]) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file