Skip to content

Commit

Permalink
Implement B-tree iterators
Browse files Browse the repository at this point in the history
- [BUG] Bug fix in DataCell's GetSize function.
- [BUG] Bug fix in splitSingleNode, actual data for the StoreData was not being added.
- Give FixedStack more functionality.
  • Loading branch information
nrupprecht committed Mar 16, 2024
1 parent c193a87 commit 0eee854
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 9 deletions.
33 changes: 32 additions & 1 deletion include/NeverSQL/containers/FixedStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

#pragma once

#include <functional>
#include <concepts>

#include "NeverSQL/utility/Defines.h"

namespace neversql {
Expand Down Expand Up @@ -39,7 +42,35 @@ class FixedStack {
}
}

constexpr T& Top() noexcept { return buffer_[size_ - 1]; }
constexpr std::optional<std::reference_wrapper<const T>> Top() const noexcept {
return 0 < size_ ? std::optional(std::cref(buffer_[size_ - 1])) : std::nullopt;
}

constexpr std::optional<std::reference_wrapper<T>> Top() noexcept {
return 0 < size_ ? std::optional(std::ref(buffer_[size_ - 1])) : std::nullopt;
}

//! \brief Check if two FixedStacks are equal.
bool operator==(const FixedStack& other) const noexcept
requires std::equality_comparable<T>
{
if (size_ != other.size_) {
return false;
}
for (std::size_t i = 0; i < size_; ++i) {
if (buffer_[i] != other.buffer_[i]) {
return false;
}
}
return true;
}

//! \brief Check if two FixedStacks are not equal.
bool operator!=(const FixedStack& other) const noexcept
requires std::equality_comparable<T>
{
return !(*this == other);
}

private:
T buffer_[StackSize_v];
Expand Down
8 changes: 8 additions & 0 deletions include/NeverSQL/data/Document.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ class DocumentReader {
}
}

template<typename T>
NO_DISCARD T GetEntryAs(const std::string& field_name) const {
auto it = std::ranges::find_if(
fields_, [&field_name](const auto& field) { return field.field_name == field_name; });
NOSQL_REQUIRE(it != fields_.end(), "field '" << field_name << "' not found");
return GetEntryAs<T>(std::distance(fields_.begin(), it));
}

private:
//! \brief Scan the data and initialize the field descriptors.
void initialize();
Expand Down
33 changes: 33 additions & 0 deletions include/NeverSQL/data/btree/BTree.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,39 @@ class BTreeManager {
//! \brief Get the root page number of the B-tree.
page_number_t GetRootPageNumber() const noexcept { return root_page_; }

class Iterator {
public:
//! \brief Create a begin iterator for the B-tree.
explicit Iterator(const BTreeManager& manager);

//! \brief Create a specific B-tree iterator.
Iterator(const BTreeManager& manager, FixedStack<std::pair<page_number_t, page_size_t>> progress);

//! \brief Create an end B-Tree iterator
Iterator(const BTreeManager& manager, [[maybe_unused]] bool);

Iterator& operator++();
std::span<const std::byte> operator*() const;
bool operator==(const Iterator& other) const;
bool operator!=(const Iterator& other) const;

private:
//! \brief Check if the iterator is at the end.
bool done() const noexcept;

//! \brief Descend to the leftmost node in the tree given the page and pointer cell to start at.
void descend(const BTreeNodeMap& page, page_size_t index);

//! \brief Reference to the B-tree being traversed.
const BTreeManager& manager_;

//! \brief The current progress at every level of the tree.
FixedStack<std::pair<page_number_t, page_size_t>> progress_;
};

Iterator begin() const { return Iterator(*this); }
Iterator end() const { return Iterator(*this, true); }

private:
//! \brief Initialize the B-tree manager object from the data in its root page.
void initialize();
Expand Down
2 changes: 1 addition & 1 deletion include/NeverSQL/data/btree/BTreeNodeMap.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ struct DataNodeCell {
}

NO_DISCARD page_size_t GetSize() const noexcept {
return static_cast<page_size_t>(sizeof(primary_key_t) + sizeof(entry_size_t) + size_of_entry
return static_cast<page_size_t>(key.size() + sizeof(entry_size_t) + size_of_entry
+ (key_size_is_serialized ? 2 : 0));
}
};
Expand Down
7 changes: 7 additions & 0 deletions include/NeverSQL/database/DataManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ class DataManager {
//! \brief Retrieve a value from the database along with data about the retrieval.
RetrievalResult Retrieve(const std::string& collection_name, primary_key_t key) const;

// ========================================
// FOR NOW: Test search and iteration methods.
// ========================================

BTreeManager::Iterator Begin(const std::string& collection_name) const;
BTreeManager::Iterator End(const std::string& collection_name) const;

// ========================================
// Debugging and Diagnostic Functions
// ========================================
Expand Down
112 changes: 105 additions & 7 deletions source/NeverSQL/data/btree/BTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,107 @@

namespace neversql {

// ================================================================================================
// BTreeManager::Iterator.
// ================================================================================================

BTreeManager::Iterator::Iterator(const BTreeManager& manager)
: manager_(manager) {
auto root = manager_.loadNodePage(manager.GetRootPageNumber());
progress_.Push({manager.GetRootPageNumber(), 0});
descend(*root, 0);
}

BTreeManager::Iterator::Iterator(const BTreeManager& manager, FixedStack<std::pair<page_number_t, page_size_t>> progress)
: manager_(manager)
, progress_(std::move(progress)) {}

//! \brief Create an end B-Tree iterator
BTreeManager::Iterator::Iterator(const BTreeManager& manager, [[maybe_unused]] bool)
: manager_(manager) {}

BTreeManager::Iterator& BTreeManager::Iterator::operator++() {
if (done()) {
return *this;
}

auto& [current_page_number, current_index] = progress_.Top()->get();
auto current_page = *manager_.loadNodePage(current_page_number);
current_index++;
// There is no more data in the current data page.
if (current_index == current_page.GetNumPointers()) {
progress_.Pop();

while (!done()) {
auto& [page_number, index] = progress_.Top()->get();
auto page = *manager_.loadNodePage(page_number);
++index;
// Note: index can be == num pointers, since this means go to the "rightmost page."
if (index <= page.GetNumPointers()) {
descend(page, index);
break;
}
progress_.Pop();
}
}
return *this;
}

std::span<const std::byte> BTreeManager::Iterator::operator*() const {
if (done()) {
return {};
}

auto [page_number, cell_index] = progress_.Top()->get();
auto page = *manager_.loadNodePage(page_number);
auto cell = page.getNthCell(cell_index);
// Should be a data cell.
NOSQL_ASSERT(std::holds_alternative<DataNodeCell>(cell), "Cell is not a data cell.");
// TODO / NOTE: This is not truly safe in a multi-threaded environment, but it is safe in the context of
// what I have so far.
// TODO: This could be solved, though, by the iterator pinning its current page. Then the data would at
// least not be dangling, though if another transaction is modifying the data, the actual data
// referenced by the span might change.
return std::get<DataNodeCell>(cell).SpanValue();
}

bool BTreeManager::Iterator::operator==(const Iterator& other) const {
return progress_ == other.progress_ || (done() && other.done());
}

bool BTreeManager::Iterator::operator!=(const Iterator& other) const { return !(*this == other); }

bool BTreeManager::Iterator::done() const noexcept { return progress_.Empty(); }

void BTreeManager::Iterator::descend(const BTreeNodeMap& page, page_size_t index) {
if (!page.IsPointersPage()) {
return;
}

// if the index is == num_pointers, go to the "rightmost" page.
page_number_t next_page_number {};
if (index == page.GetNumPointers()) {
next_page_number = page.getHeader().GetAdditionalData();
}
else {
next_page_number = std::get<PointersNodeCell>(page.getNthCell(index)).page_number;
}

auto descending_page = *manager_.loadNodePage(next_page_number);
index = 0;

progress_.Push({next_page_number, index});
while (descending_page.IsPointersPage()) {
next_page_number = std::get<PointersNodeCell>(descending_page.getNthCell(index)).page_number;
descending_page = *manager_.loadNodePage(next_page_number);
progress_.Push({next_page_number, index});
}
}

// ================================================================================================
// BTreeManager.
// ================================================================================================

BTreeManager::BTreeManager(page_number_t root_page, PageCache& page_cache)
: page_cache_(page_cache)
, root_page_(root_page)
Expand Down Expand Up @@ -161,7 +262,7 @@ primary_key_t BTreeManager::getNextPrimaryKey() const {
auto root = loadNodePage(root_page_);
primary_key_t pk {};

auto counter_offset = root->GetHeader().GetReservedStart();
auto counter_offset = static_cast<page_size_t>(root->GetHeader().GetReservedStart() + 2);
pk = root->GetPage().Read<primary_key_t>(counter_offset);

primary_key_t next_primary_key = pk + 1;
Expand Down Expand Up @@ -325,7 +426,7 @@ void BTreeManager::splitNode(BTreeNodeMap& node, SearchResult& result, std::opti
result.path.Pop();

// Left page is the original page, right page has to be added to the parent.
auto parent_page_number = result.path.Top();
auto parent_page_number = *result.path.Top();
LOG_SEV(Trace) << " * Adding right page " << split_data.right_page << " to parent page "
<< parent_page_number << ".";

Expand Down Expand Up @@ -444,10 +545,8 @@ SplitPage BTreeManager::splitSingleNode(BTreeNodeMap& node, std::optional<StoreD

if (data) {
LOG_SEV(Trace) << "Data requested to be added to a node, pk = " << debugKey(data->key) << ".";
StoreData store_data {
.key = data->key, .serialize_key_size = serialize_key_size_, .serialize_data_size = true};
auto& node_to_add_to = lte(data->key, return_data.split_key) ? new_node : node;
addElementToNode(node_to_add_to, store_data);
addElementToNode(node_to_add_to, *data);
}

// =======================================
Expand Down Expand Up @@ -585,8 +684,7 @@ void BTreeManager::vacuum(BTreeNodeMap& node) const {
<< node.GetDefragmentedFreeSpace() << " bytes of defragmented free space.";

// TODO: More WAL friendly version: take a snapshot of the pointers, sort them (without going through the
// WAL), then
// submit the sorted changes all at once.
// WAL), then submit the sorted changes all at once.

auto&& header = node.GetHeader();

Expand Down
13 changes: 13 additions & 0 deletions source/NeverSQL/database/DataManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,19 @@ RetrievalResult DataManager::Retrieve(const std::string& collection_name, primar
return Retrieve(collection_name, key_span);
}


BTreeManager::Iterator DataManager::Begin(const std::string& collection_name) const {
auto it = collections_.find(collection_name);
auto& manager = *it->second;
return manager.begin();
}

BTreeManager::Iterator DataManager::End(const std::string& collection_name) const {
auto it = collections_.find(collection_name);
auto& manager = *it->second;
return manager.end();
}

bool DataManager::HexDumpPage(page_number_t page_number,
std::ostream& out,
utility::HexDumpOptions options) const {
Expand Down

0 comments on commit 0eee854

Please sign in to comment.