BadgerDB
|
00001 00008 #include <cassert> 00009 00010 #include "exceptions/insufficient_space_exception.h" 00011 #include "exceptions/invalid_record_exception.h" 00012 #include "exceptions/invalid_slot_exception.h" 00013 #include "exceptions/slot_in_use_exception.h" 00014 #include "page_iterator.h" 00015 #include "page.h" 00016 00017 namespace badgerdb { 00018 00019 Page::Page() { 00020 initialize(); 00021 } 00022 00023 void Page::initialize() { 00024 header_.free_space_lower_bound = 0; 00025 header_.free_space_upper_bound = DATA_SIZE; 00026 header_.num_slots = 0; 00027 header_.num_free_slots = 0; 00028 header_.current_page_number = INVALID_NUMBER; 00029 header_.next_page_number = INVALID_NUMBER; 00030 data_.assign(DATA_SIZE, char()); 00031 } 00032 00033 RecordId Page::insertRecord(const std::string& record_data) { 00034 if (!hasSpaceForRecord(record_data)) { 00035 throw InsufficientSpaceException( 00036 page_number(), record_data.length(), getFreeSpace()); 00037 } 00038 const SlotId slot_number = getAvailableSlot(); 00039 insertRecordInSlot(slot_number, record_data); 00040 return {page_number(), slot_number}; 00041 } 00042 00043 std::string Page::getRecord(const RecordId& record_id) const { 00044 validateRecordId(record_id); 00045 const PageSlot& slot = getSlot(record_id.slot_number); 00046 return data_.substr(slot.item_offset, slot.item_length); 00047 } 00048 00049 void Page::updateRecord(const RecordId& record_id, 00050 const std::string& record_data) { 00051 validateRecordId(record_id); 00052 const PageSlot* slot = getSlot(record_id.slot_number); 00053 const std::size_t free_space_after_delete = 00054 getFreeSpace() + slot->item_length; 00055 if (record_data.length() > free_space_after_delete) { 00056 throw InsufficientSpaceException( 00057 page_number(), record_data.length(), free_space_after_delete); 00058 } 00059 // We have to disallow slot compaction here because we're going to place the 00060 // record data in the same slot, and compaction might delete the slot if we 00061 // permit it. 00062 deleteRecord(record_id, false /* allow_slot_compaction */); 00063 insertRecordInSlot(record_id.slot_number, record_data); 00064 } 00065 00066 void Page::deleteRecord(const RecordId& record_id) { 00067 deleteRecord(record_id, true /* allow_slot_compaction */); 00068 } 00069 00070 void Page::deleteRecord(const RecordId& record_id, 00071 const bool allow_slot_compaction) { 00072 validateRecordId(record_id); 00073 PageSlot* slot = getSlot(record_id.slot_number); 00074 data_.replace(slot->item_offset, slot->item_length, slot->item_length, '\0'); 00075 00076 // Compact the data by removing the hole left by this record (if necessary). 00077 std::uint16_t move_offset = slot->item_offset; 00078 std::size_t move_bytes = 0; 00079 for (SlotId i = 1; i <= header_.num_slots; ++i) { 00080 PageSlot* other_slot = getSlot(i); 00081 if (other_slot->used && other_slot->item_offset < slot->item_offset) { 00082 if (other_slot->item_offset < move_offset) { 00083 move_offset = other_slot->item_offset; 00084 } 00085 move_bytes += other_slot->item_length; 00086 // Update the slot for the other data to reflect the soon-to-be-new 00087 // location. 00088 other_slot->item_offset += slot->item_length; 00089 } 00090 } 00091 // If we have data to move, shift it to the right. 00092 if (move_bytes > 0) { 00093 const std::string& data_to_move = data_.substr(move_offset, move_bytes); 00094 data_.replace(move_offset + slot->item_length, move_bytes, data_to_move); 00095 } 00096 header_.free_space_upper_bound += slot->item_length; 00097 00098 // Mark slot as unused. 00099 slot->used = false; 00100 slot->item_offset = 0; 00101 slot->item_length = 0; 00102 ++header_.num_free_slots; 00103 00104 if (allow_slot_compaction && record_id.slot_number == header_.num_slots) { 00105 // Last slot in the list, so we need to free any unused slots that are at 00106 // the end of the slot list. 00107 int num_slots_to_delete = 1; 00108 for (SlotId i = 1; i < header_.num_slots; ++i) { 00109 // Traverse list backwards, looking for unused slots. 00110 const PageSlot* other_slot = getSlot(header_.num_slots - i); 00111 if (!other_slot->used) { 00112 ++num_slots_to_delete; 00113 } else { 00114 // Stop at the first used slot we find, since we can't move used slots 00115 // without affecting record IDs. 00116 break; 00117 } 00118 } 00119 header_.num_slots -= num_slots_to_delete; 00120 header_.num_free_slots -= num_slots_to_delete; 00121 header_.free_space_lower_bound -= sizeof(PageSlot) * num_slots_to_delete; 00122 } 00123 } 00124 00125 bool Page::hasSpaceForRecord(const std::string& record_data) const { 00126 std::size_t record_size = record_data.length(); 00127 if (header_.num_free_slots == 0) { 00128 record_size += sizeof(PageSlot); 00129 } 00130 return record_size <= getFreeSpace(); 00131 } 00132 00133 PageSlot* Page::getSlot(const SlotId slot_number) { 00134 return reinterpret_cast<PageSlot*>( 00135 &data_[(slot_number - 1) * sizeof(PageSlot)]); 00136 } 00137 00138 const PageSlot& Page::getSlot(const SlotId slot_number) const { 00139 return *reinterpret_cast<const PageSlot*>( 00140 &data_[(slot_number - 1) * sizeof(PageSlot)]); 00141 } 00142 00143 SlotId Page::getAvailableSlot() { 00144 SlotId slot_number = INVALID_SLOT; 00145 if (header_.num_free_slots > 0) { 00146 // Have an allocated but unused slot that we can reuse. 00147 for (SlotId i = 1; i <= header_.num_slots; ++i) { 00148 const PageSlot* slot = getSlot(i); 00149 if (!slot->used) { 00150 // We don't decrement the number of free slots until someone actually 00151 // puts data in the slot. 00152 slot_number = i; 00153 break; 00154 } 00155 } 00156 } else { 00157 // Have to allocate a new slot. 00158 slot_number = header_.num_slots + 1; 00159 ++header_.num_slots; 00160 ++header_.num_free_slots; 00161 header_.free_space_lower_bound = sizeof(PageSlot) * header_.num_slots; 00162 } 00163 assert(slot_number != INVALID_SLOT); 00164 return static_cast<SlotId>(slot_number); 00165 } 00166 00167 void Page::insertRecordInSlot(const SlotId slot_number, 00168 const std::string& record_data) { 00169 if (slot_number > header_.num_slots || 00170 slot_number == INVALID_SLOT) { 00171 throw InvalidSlotException(page_number(), slot_number); 00172 } 00173 PageSlot* slot = getSlot(slot_number); 00174 if (slot->used) { 00175 throw SlotInUseException(page_number(), slot_number); 00176 } 00177 const int record_length = record_data.length(); 00178 slot->used = true; 00179 slot->item_length = record_length; 00180 slot->item_offset = header_.free_space_upper_bound - record_length; 00181 header_.free_space_upper_bound = slot->item_offset; 00182 --header_.num_free_slots; 00183 data_.replace(slot->item_offset, slot->item_length, record_data); 00184 } 00185 00186 void Page::validateRecordId(const RecordId& record_id) const { 00187 if (record_id.page_number != page_number()) { 00188 throw InvalidRecordException(record_id, page_number()); 00189 } 00190 const PageSlot& slot = getSlot(record_id.slot_number); 00191 if (!slot.used) { 00192 throw InvalidRecordException(record_id, page_number()); 00193 } 00194 } 00195 00196 PageIterator Page::begin() { 00197 return PageIterator(this); 00198 } 00199 00200 PageIterator Page::end() { 00201 const RecordId& end_record_id = {page_number(), Page::INVALID_SLOT}; 00202 return PageIterator(this, end_record_id); 00203 } 00204 00205 }