osb/source/core/StarBTreeDatabase.cpp
Kai Blaschke 431a9c00a5
Fixed a huge amount of Clang warnings
On Linux and macOS, using Clang to compile OpenStarbound produces about 400 MB worth of warnings during the build, making the compiler output unreadable and slowing the build down considerably.

99% of the warnings were unqualified uses of std::move and std::forward, which are now all properly qualified.

Fixed a few other minor warnings about non-virtual destructors and some uses of std::move preventing copy elision on temporary objects.

Most remaining warnings are now unused parameters.
2024-02-19 16:55:19 +01:00

1189 lines
34 KiB
C++

#include "StarBTreeDatabase.hpp"
#include "StarSha256.hpp"
#include "StarVlqEncoding.hpp"
namespace Star {
BTreeDatabase::BTreeDatabase() {
m_impl.parent = this;
m_open = false;
m_deviceSize = 0;
m_blockSize = 2048;
m_headFreeIndexBlock = InvalidBlockIndex;
m_keySize = 0;
m_autoCommit = true;
m_indexCache.setMaxSize(64);
m_root = InvalidBlockIndex;
m_rootIsLeaf = false;
m_usingAltRoot = false;
}
BTreeDatabase::BTreeDatabase(String const& contentIdentifier, size_t keySize)
: BTreeDatabase() {
setContentIdentifier(contentIdentifier);
setKeySize(keySize);
}
BTreeDatabase::~BTreeDatabase() {
close();
}
uint32_t BTreeDatabase::blockSize() const {
ReadLocker readLocker(m_lock);
return m_blockSize;
}
void BTreeDatabase::setBlockSize(uint32_t blockSize) {
WriteLocker writeLocker(m_lock);
checkIfOpen("setBlockSize", false);
m_blockSize = blockSize;
}
uint32_t BTreeDatabase::keySize() const {
ReadLocker readLocker(m_lock);
return m_keySize;
}
void BTreeDatabase::setKeySize(uint32_t keySize) {
WriteLocker writeLocker(m_lock);
checkIfOpen("setKeySize", false);
m_keySize = keySize;
}
String BTreeDatabase::contentIdentifier() const {
ReadLocker readLocker(m_lock);
return m_contentIdentifier;
}
void BTreeDatabase::setContentIdentifier(String contentIdentifier) {
WriteLocker writeLocker(m_lock);
checkIfOpen("setContentIdentifier", false);
m_contentIdentifier = std::move(contentIdentifier);
}
uint32_t BTreeDatabase::indexCacheSize() const {
SpinLocker lock(m_indexCacheSpinLock);
return m_indexCache.maxSize();
}
void BTreeDatabase::setIndexCacheSize(uint32_t indexCacheSize) {
SpinLocker lock(m_indexCacheSpinLock);
m_indexCache.setMaxSize(indexCacheSize);
}
bool BTreeDatabase::autoCommit() const {
ReadLocker readLocker(m_lock);
return m_autoCommit;
}
void BTreeDatabase::setAutoCommit(bool autoCommit) {
WriteLocker writeLocker(m_lock);
m_autoCommit = autoCommit;
if (m_autoCommit)
doCommit();
}
IODevicePtr BTreeDatabase::ioDevice() const {
ReadLocker readLocker(m_lock);
return m_device;
}
void BTreeDatabase::setIODevice(IODevicePtr device) {
WriteLocker writeLocker(m_lock);
checkIfOpen("setIODevice", false);
m_device = std::move(device);
}
bool BTreeDatabase::isOpen() const {
ReadLocker readLocker(m_lock);
return m_open;
}
bool BTreeDatabase::open() {
WriteLocker writeLocker(m_lock);
if (m_open)
return false;
if (!m_device)
throw DBException("BlockStorage::open called with no IODevice set");
if (!m_device->isOpen())
m_device->open(IOMode::ReadWrite);
m_open = true;
if (m_device->size() > 0) {
DataStreamIODevice ds(m_device);
ds.seek(0);
auto magic = ds.readBytes(VersionMagicSize);
if (magic != ByteArray::fromCString(VersionMagic))
throw DBException("Device is not a valid BTreeDatabase file");
m_blockSize = ds.read<uint32_t>();
auto contentIdentifier = ds.readBytes(ContentIdentifierStringSize);
contentIdentifier.appendByte('\0');
m_contentIdentifier = String(contentIdentifier.ptr());
m_keySize = ds.read<uint32_t>();
readRoot();
if (m_device->isWritable())
m_device->resize(m_deviceSize);
return false;
} else {
m_deviceSize = HeaderSize;
m_device->resize(m_deviceSize);
m_headFreeIndexBlock = InvalidBlockIndex;
DataStreamIODevice ds(m_device);
ds.seek(0);
ds.writeData(VersionMagic, VersionMagicSize);
ds.write<uint32_t>(m_blockSize);
if (m_contentIdentifier.empty())
throw DBException("Opening new database and no content identifier set!");
if (m_contentIdentifier.utf8Size() > ContentIdentifierStringSize)
throw DBException("contentIdentifier in BTreeDatabase implementation is greater than maximum identifier length");
if (m_keySize == 0)
throw DBException("key size is not set opening a new BTreeDatabase");
ByteArray contentIdentifier = m_contentIdentifier.utf8Bytes();
contentIdentifier.resize(ContentIdentifierStringSize, 0);
ds.writeBytes(contentIdentifier);
ds.write(m_keySize);
m_impl.createNewRoot();
doCommit();
return true;
}
}
bool BTreeDatabase::contains(ByteArray const& k) {
ReadLocker readLocker(m_lock);
checkKeySize(k);
return m_impl.contains(k);
}
Maybe<ByteArray> BTreeDatabase::find(ByteArray const& k) {
ReadLocker readLocker(m_lock);
checkKeySize(k);
return m_impl.find(k);
}
List<pair<ByteArray, ByteArray>> BTreeDatabase::find(ByteArray const& lower, ByteArray const& upper) {
ReadLocker readLocker(m_lock);
checkKeySize(lower);
checkKeySize(upper);
return m_impl.find(lower, upper);
}
void BTreeDatabase::forEach(ByteArray const& lower, ByteArray const& upper, function<void(ByteArray, ByteArray)> v) {
ReadLocker readLocker(m_lock);
checkKeySize(lower);
checkKeySize(upper);
m_impl.forEach(lower, upper, std::move(v));
}
void BTreeDatabase::forAll(function<void(ByteArray, ByteArray)> v) {
ReadLocker readLocker(m_lock);
m_impl.forAll(std::move(v));
}
bool BTreeDatabase::insert(ByteArray const& k, ByteArray const& data) {
WriteLocker writeLocker(m_lock);
checkKeySize(k);
return m_impl.insert(k, data);
}
bool BTreeDatabase::remove(ByteArray const& k) {
WriteLocker writeLocker(m_lock);
checkKeySize(k);
return m_impl.remove(k);
}
uint64_t BTreeDatabase::recordCount() {
ReadLocker readLocker(m_lock);
return m_impl.recordCount();
}
uint8_t BTreeDatabase::indexLevels() {
ReadLocker readLocker(m_lock);
return m_impl.indexLevels();
}
uint32_t BTreeDatabase::totalBlockCount() {
ReadLocker readLocker(m_lock);
checkIfOpen("totalBlockCount", true);
return (m_device->size() - HeaderSize) / m_blockSize;
}
uint32_t BTreeDatabase::freeBlockCount() {
ReadLocker readLocker(m_lock);
checkIfOpen("freeBlockCount", true);
// Go through every FreeIndexBlock in the chain and count all of the tracked
// free blocks.
BlockIndex count = 0;
BlockIndex indexBlockIndex = m_headFreeIndexBlock;
while (indexBlockIndex != InvalidBlockIndex) {
FreeIndexBlock indexBlock = readFreeIndexBlock(indexBlockIndex);
count += 1 + indexBlock.freeBlocks.size();
indexBlockIndex = indexBlock.nextFreeBlock;
}
count += m_availableBlocks.size() + m_pendingFree.size();
// Include untracked blocks at the end of the file in the free count.
count += (m_device->size() - m_deviceSize) / m_blockSize;
return count;
}
uint32_t BTreeDatabase::indexBlockCount() {
ReadLocker readLocker(m_lock);
checkIfOpen("indexBlockCount", true);
// Indexes are simply one index per block
return m_impl.indexCount();
}
uint32_t BTreeDatabase::leafBlockCount() {
WriteLocker writeLocker(m_lock);
checkIfOpen("leafBlockCount", true);
struct LeafBlocksVisitor {
bool operator()(shared_ptr<IndexNode> const&) {
return true;
}
bool operator()(shared_ptr<LeafNode> const& leaf) {
leafBlockCount += 1 + parent->leafTailBlocks(leaf->self).size();
return true;
}
BTreeDatabase* parent;
BlockIndex leafBlockCount = 0;
};
LeafBlocksVisitor visitor;
visitor.parent = this;
m_impl.forAllNodes(visitor);
return visitor.leafBlockCount;
}
void BTreeDatabase::commit() {
WriteLocker writeLocker(m_lock);
doCommit();
}
void BTreeDatabase::rollback() {
WriteLocker writeLocker(m_lock);
m_availableBlocks.clear();
m_indexCache.clear();
m_uncommitted.clear();
m_pendingFree.clear();
readRoot();
if (m_device->isWritable())
m_device->resize(m_deviceSize);
}
void BTreeDatabase::close(bool closeDevice) {
WriteLocker writeLocker(m_lock);
if (m_open) {
doCommit();
m_indexCache.clear();
m_open = false;
if (closeDevice && m_device && m_device->isOpen())
m_device->close();
}
}
BTreeDatabase::BlockIndex const BTreeDatabase::InvalidBlockIndex;
uint32_t const BTreeDatabase::HeaderSize;
char const* const BTreeDatabase::VersionMagic = "BTreeDB5";
uint32_t const BTreeDatabase::VersionMagicSize;
char const* const BTreeDatabase::IndexMagic = "II";
char const* const BTreeDatabase::LeafMagic = "LL";
char const* const BTreeDatabase::FreeIndexMagic = "FF";
size_t const BTreeDatabase::BTreeRootSelectorBit;
size_t const BTreeDatabase::BTreeRootInfoStart;
size_t const BTreeDatabase::BTreeRootInfoSize;
size_t BTreeDatabase::IndexNode::pointerCount() const {
// If no begin pointer is set then the index is simply uninitialized.
if (!beginPointer)
return 0;
else
return pointers.size() + 1;
}
auto BTreeDatabase::IndexNode::pointer(size_t i) const -> BlockIndex {
if (i == 0)
return *beginPointer;
else
return pointers.at(i - 1).pointer;
}
void BTreeDatabase::IndexNode::updatePointer(size_t i, BlockIndex p) {
if (i == 0)
*beginPointer = p;
else
pointers.at(i - 1).pointer = p;
}
ByteArray const& BTreeDatabase::IndexNode::keyBefore(size_t i) const {
return pointers.at(i - 1).key;
}
void BTreeDatabase::IndexNode::updateKeyBefore(size_t i, ByteArray k) {
pointers.at(i - 1).key = k;
}
void BTreeDatabase::IndexNode::removeBefore(size_t i) {
if (i == 0) {
beginPointer = pointers.at(0).pointer;
pointers.eraseAt(0);
} else {
pointers.eraseAt(i - 1);
}
}
void BTreeDatabase::IndexNode::insertAfter(size_t i, ByteArray k, BlockIndex p) {
pointers.insertAt(i, Element{k, p});
}
uint8_t BTreeDatabase::IndexNode::indexLevel() const {
return level;
}
void BTreeDatabase::IndexNode::setIndexLevel(uint8_t indexLevel) {
level = indexLevel;
}
void BTreeDatabase::IndexNode::shiftLeft(ByteArray const& mid, IndexNode& right, size_t count) {
count = std::min(right.pointerCount(), count);
if (count == 0)
return;
pointers.append(Element{mid, *right.beginPointer});
ElementList::iterator s = right.pointers.begin();
std::advance(s, count - 1);
pointers.insert(pointers.end(), right.pointers.begin(), s);
right.pointers.erase(right.pointers.begin(), s);
if (right.pointers.size() != 0) {
right.beginPointer = right.pointers.at(0).pointer;
right.pointers.eraseAt(0);
} else {
right.beginPointer.reset();
}
}
void BTreeDatabase::IndexNode::shiftRight(ByteArray const& mid, IndexNode& left, size_t count) {
count = std::min(left.pointerCount(), count);
if (count == 0)
return;
--count;
pointers.insert(pointers.begin(), Element{mid, *beginPointer});
ElementList::iterator s = left.pointers.begin();
std::advance(s, left.pointers.size() - count);
pointers.insert(pointers.begin(), s, left.pointers.end());
left.pointers.erase(s, left.pointers.end());
if (left.pointers.size() != 0) {
beginPointer = left.pointers.at(left.pointers.size() - 1).pointer;
left.pointers.eraseAt(left.pointers.size() - 1);
} else {
beginPointer = left.beginPointer.take();
}
}
ByteArray BTreeDatabase::IndexNode::split(IndexNode& right, size_t i) {
ElementList::iterator s = pointers.begin();
std::advance(s, i - 1);
right.beginPointer = s->pointer;
ByteArray midKey = s->key;
right.level = level;
++s;
right.pointers.insert(right.pointers.begin(), s, pointers.end());
--s;
pointers.erase(s, pointers.end());
return midKey;
}
size_t BTreeDatabase::LeafNode::count() const {
return elements.size();
}
ByteArray const& BTreeDatabase::LeafNode::key(size_t i) const {
return elements.at(i).key;
}
ByteArray const& BTreeDatabase::LeafNode::data(size_t i) const {
return elements.at(i).data;
}
void BTreeDatabase::LeafNode::insert(size_t i, ByteArray k, ByteArray d) {
elements.insertAt(i, Element{std::move(k), std::move(d)});
}
void BTreeDatabase::LeafNode::remove(size_t i) {
elements.eraseAt(i);
}
void BTreeDatabase::LeafNode::shiftLeft(LeafNode& right, size_t count) {
count = std::min(right.count(), count);
if (count == 0)
return;
ElementList::iterator s = right.elements.begin();
std::advance(s, count);
elements.insert(elements.end(), right.elements.begin(), s);
right.elements.erase(right.elements.begin(), s);
}
void BTreeDatabase::LeafNode::shiftRight(LeafNode& left, size_t count) {
count = std::min(left.count(), count);
if (count == 0)
return;
ElementList::iterator s = left.elements.begin();
std::advance(s, left.elements.size() - count);
elements.insert(elements.begin(), s, left.elements.end());
left.elements.erase(s, left.elements.end());
}
void BTreeDatabase::LeafNode::split(LeafNode& right, size_t i) {
ElementList::iterator s = elements.begin();
std::advance(s, i);
right.elements.insert(right.elements.begin(), s, elements.end());
elements.erase(s, elements.end());
}
auto BTreeDatabase::BTreeImpl::rootPointer() -> Pointer {
return parent->m_root;
}
bool BTreeDatabase::BTreeImpl::rootIsLeaf() {
return parent->m_rootIsLeaf;
}
void BTreeDatabase::BTreeImpl::setNewRoot(Pointer pointer, bool isLeaf) {
parent->m_root = pointer;
parent->m_rootIsLeaf = isLeaf;
if (parent->m_autoCommit)
parent->doCommit();
}
auto BTreeDatabase::BTreeImpl::createIndex(Pointer beginPointer) -> Index {
auto index = make_shared<IndexNode>();
index->self = InvalidBlockIndex;
index->level = 0;
index->beginPointer = beginPointer;
return index;
}
auto BTreeDatabase::BTreeImpl::loadIndex(Pointer pointer) -> Index {
SpinLocker lock(parent->m_indexCacheSpinLock);
if (auto index = parent->m_indexCache.ptr(pointer))
return *index;
lock.unlock();
auto index = make_shared<IndexNode>();
DataStreamBuffer buffer(parent->readBlock(pointer));
if (buffer.readBytes(2) != ByteArray(IndexMagic, 2))
throw DBException("Error, incorrect index block signature.");
index->self = pointer;
index->level = buffer.read<uint8_t>();
uint32_t s = buffer.read<uint32_t>();
index->beginPointer = buffer.read<BlockIndex>();
index->pointers.resize(s);
for (uint32_t i = 0; i < s; ++i) {
auto& e = index->pointers[i];
e.key =buffer.readBytes(parent->m_keySize);
e.pointer = buffer.read<BlockIndex>();
}
lock.lock();
parent->m_indexCache.set(pointer, index);
return index;
}
bool BTreeDatabase::BTreeImpl::indexNeedsShift(Index const& index) {
return index->pointerCount() < (parent->maxIndexPointers() + 1) / 2;
}
bool BTreeDatabase::BTreeImpl::indexShift(Index const& left, Key const& mid, Index const& right) {
if (left->pointerCount() + right->pointerCount() <= parent->maxIndexPointers()) {
left->shiftLeft(mid, *right, right->pointerCount());
return true;
} else {
if (indexNeedsShift(right)) {
right->shiftRight(mid, *left, 1);
return true;
} else if (indexNeedsShift(left)) {
left->shiftLeft(mid, *right, 1);
return true;
} else {
return false;
}
}
}
auto BTreeDatabase::BTreeImpl::indexSplit(Index const& index) -> Maybe<pair<Key, Index>> {
if (index->pointerCount() <= parent->maxIndexPointers())
return {};
auto right = make_shared<IndexNode>();
right->self = InvalidBlockIndex;
Key k = index->split(*right, (index->pointerCount() + 1) / 2);
return make_pair(k, right);
}
auto BTreeDatabase::BTreeImpl::storeIndex(Index index) -> Pointer {
if (index->self != InvalidBlockIndex) {
if (!parent->m_uncommitted.contains(index->self)) {
parent->freeBlock(index->self);
parent->m_indexCache.remove(index->self);
index->self = InvalidBlockIndex;
}
}
if (index->self == InvalidBlockIndex)
index->self = parent->reserveBlock();
DataStreamBuffer buffer(parent->m_blockSize);
buffer.writeData(IndexMagic, 2);
buffer.write<uint8_t>(index->level);
buffer.write<uint32_t>(index->pointers.size());
buffer.write<BlockIndex>(*index->beginPointer);
for (auto i = index->pointers.begin(); i != index->pointers.end(); ++i) {
starAssert(i->key.size() == parent->m_keySize);
buffer.writeBytes(i->key);
buffer.write<BlockIndex>(i->pointer);
}
parent->updateBlock(index->self, buffer.data());
parent->m_indexCache.set(index->self, index);
return index->self;
}
void BTreeDatabase::BTreeImpl::deleteIndex(Index index) {
parent->m_indexCache.remove(index->self);
parent->freeBlock(index->self);
}
auto BTreeDatabase::BTreeImpl::createLeaf() -> Leaf {
auto leaf = make_shared<LeafNode>();
leaf->self = InvalidBlockIndex;
return leaf;
}
auto BTreeDatabase::BTreeImpl::loadLeaf(Pointer pointer) -> Leaf {
auto leaf = make_shared<LeafNode>();
leaf->self = pointer;
BlockIndex currentLeafBlock = leaf->self;
DataStreamBuffer leafBuffer;
leafBuffer.reset(parent->m_blockSize);
parent->readBlock(currentLeafBlock, 0, leafBuffer.ptr(), parent->m_blockSize);
if (leafBuffer.readBytes(2) != ByteArray(LeafMagic, 2))
throw DBException("Error, incorrect leaf block signature.");
DataStreamFunctions leafInput([&](char* data, size_t len) -> size_t {
size_t pos = 0;
size_t left = len;
while (left > 0) {
if (leafBuffer.pos() + left < parent->m_blockSize - sizeof(BlockIndex)) {
leafBuffer.readData(data + pos, left);
pos += left;
left = 0;
} else {
size_t toRead = parent->m_blockSize - sizeof(BlockIndex) - leafBuffer.pos();
leafBuffer.readData(data + pos, toRead);
pos += toRead;
left -= toRead;
}
if (leafBuffer.pos() == (parent->m_blockSize - sizeof(BlockIndex)) && left > 0) {
currentLeafBlock = leafBuffer.read<BlockIndex>();
if (currentLeafBlock != InvalidBlockIndex) {
leafBuffer.reset(parent->m_blockSize);
parent->readBlock(currentLeafBlock, 0, leafBuffer.ptr(), parent->m_blockSize);
if (leafBuffer.readBytes(2) != ByteArray(LeafMagic, 2))
throw DBException("Error, incorrect leaf block signature.");
} else {
throw DBException("Leaf read off end of Leaf list.");
}
}
}
return len;
}, {});
uint32_t count = leafInput.read<uint32_t>();
leaf->elements.resize(count);
for (uint32_t i = 0; i < count; ++i) {
auto& element = leaf->elements[i];
element.key = leafInput.readBytes(parent->m_keySize);
element.data = leafInput.read<ByteArray>();
}
return leaf;
}
bool BTreeDatabase::BTreeImpl::leafNeedsShift(Leaf const& l) {
return parent->leafSize(l) < parent->m_blockSize / 2;
}
bool BTreeDatabase::BTreeImpl::leafShift(Leaf& left, Leaf& right) {
if (left->count() == 0) {
left->shiftLeft(*right, right->count());
return true;
}
if (right->count() == 0)
return true;
uint32_t leftSize = parent->leafSize(left);
uint32_t rightSize = parent->leafSize(right);
if (leftSize + rightSize < parent->m_blockSize) {
left->shiftLeft(*right, right->count());
return true;
}
// TODO: Shifting algorithm is bad, could potentially want to shift more
// than one element here.
uint32_t rightBeginSize = parent->m_keySize + parent->dataSize(right->elements[0].data);
uint32_t leftEndSize = parent->m_keySize + parent->dataSize(left->elements[left->elements.size() - 1].data);
if (leftSize < rightSize - rightBeginSize && leftSize + rightBeginSize < parent->m_blockSize) {
left->shiftLeft(*right, 1);
return true;
} else if (rightSize < leftSize - leftEndSize && rightSize + leftEndSize < parent->m_blockSize) {
right->shiftRight(*left, 1);
return true;
}
return false;
}
auto BTreeDatabase::BTreeImpl::leafSplit(Leaf& leaf) -> Maybe<Leaf> {
if (leaf->elements.size() < 2)
return {};
uint32_t size = 6;
bool boundaryFound = false;
uint32_t boundary = 0;
for (uint32_t i = 0; i < leaf->elements.size(); ++i) {
size += parent->m_keySize;
size += parent->dataSize(leaf->elements[i].data);
if (size > parent->m_blockSize - sizeof(BlockIndex) && !boundaryFound) {
boundary = i;
boundaryFound = true;
}
}
if (boundary == 0)
boundary = 1;
if (size < parent->m_blockSize * 2 - 2 * sizeof(BlockIndex) - 4) {
return {};
} else {
auto right = make_shared<LeafNode>();
right->self = InvalidBlockIndex;
leaf->split(*right, boundary);
return right;
}
}
auto BTreeDatabase::BTreeImpl::storeLeaf(Leaf leaf) -> Pointer {
if (leaf->self != InvalidBlockIndex) {
List<BlockIndex> tailBlocks = parent->leafTailBlocks(leaf->self);
for (uint32_t i = 0; i < tailBlocks.size(); ++i)
parent->freeBlock(tailBlocks[i]);
if (!parent->m_uncommitted.contains(leaf->self)) {
parent->freeBlock(leaf->self);
leaf->self = InvalidBlockIndex;
}
}
if (leaf->self == InvalidBlockIndex)
leaf->self = parent->reserveBlock();
BlockIndex currentLeafBlock = leaf->self;
DataStreamBuffer leafBuffer;
leafBuffer.reset(parent->m_blockSize);
leafBuffer.writeData(LeafMagic, 2);
DataStreamFunctions leafOutput({}, [&](char const* data, size_t len) -> size_t {
size_t pos = 0;
size_t left = len;
while (true) {
size_t toWrite = left;
if (toWrite > parent->m_blockSize - leafBuffer.pos() - sizeof(BlockIndex))
toWrite = parent->m_blockSize - leafBuffer.pos() - sizeof(BlockIndex);
if (toWrite != 0) {
leafBuffer.writeData(data + pos, toWrite);
left -= toWrite;
pos += toWrite;
}
if (left == 0)
break;
if (leafBuffer.pos() == (parent->m_blockSize - sizeof(BlockIndex))) {
BlockIndex nextBlock = parent->reserveBlock();
leafBuffer.write<BlockIndex>(nextBlock);
parent->updateBlock(currentLeafBlock, leafBuffer.data());
currentLeafBlock = nextBlock;
leafBuffer.reset(parent->m_blockSize);
leafBuffer.writeData(LeafMagic, 2);
}
}
return len;
});
leafOutput.write<uint32_t>(leaf->elements.size());
for (LeafNode::ElementList::iterator i = leaf->elements.begin(); i != leaf->elements.end(); ++i) {
starAssert(i->key.size() == parent->m_keySize);
leafOutput.writeBytes(i->key);
leafOutput.write(i->data);
}
leafBuffer.seek(parent->m_blockSize - sizeof(BlockIndex));
leafBuffer.write<BlockIndex>(InvalidBlockIndex);
parent->updateBlock(currentLeafBlock, leafBuffer.data());
return leaf->self;
}
void BTreeDatabase::BTreeImpl::deleteLeaf(Leaf leaf) {
List<BlockIndex> tailBlocks = parent->leafTailBlocks(leaf->self);
for (uint32_t i = 0; i < tailBlocks.size(); ++i)
parent->freeBlock(tailBlocks[i]);
parent->freeBlock(leaf->self);
}
size_t BTreeDatabase::BTreeImpl::indexPointerCount(Index const& index) {
return index->pointerCount();
}
auto BTreeDatabase::BTreeImpl::indexPointer(Index const& index, size_t i) -> Pointer {
return index->pointer(i);
}
void BTreeDatabase::BTreeImpl::indexUpdatePointer(Index& index, size_t i, Pointer p) {
index->updatePointer(i, p);
}
auto BTreeDatabase::BTreeImpl::indexKeyBefore(Index const& index, size_t i) -> Key {
return index->keyBefore(i);
}
void BTreeDatabase::BTreeImpl::indexUpdateKeyBefore(Index& index, size_t i, Key k) {
index->updateKeyBefore(i, k);
}
void BTreeDatabase::BTreeImpl::indexRemoveBefore(Index& index, size_t i) {
index->removeBefore(i);
}
void BTreeDatabase::BTreeImpl::indexInsertAfter(Index& index, size_t i, Key k, Pointer p) {
index->insertAfter(i, k, p);
}
size_t BTreeDatabase::BTreeImpl::indexLevel(Index const& index) {
return index->indexLevel();
}
void BTreeDatabase::BTreeImpl::setIndexLevel(Index& index, size_t indexLevel) {
index->setIndexLevel(indexLevel);
}
size_t BTreeDatabase::BTreeImpl::leafElementCount(Leaf const& leaf) {
return leaf->count();
}
auto BTreeDatabase::BTreeImpl::leafKey(Leaf const& leaf, size_t i) -> Key {
return leaf->key(i);
}
auto BTreeDatabase::BTreeImpl::leafData(Leaf const& leaf, size_t i) -> Data {
return leaf->data(i);
}
void BTreeDatabase::BTreeImpl::leafInsert(Leaf& leaf, size_t i, Key k, Data d) {
leaf->insert(i, std::move(k), std::move(d));
}
void BTreeDatabase::BTreeImpl::leafRemove(Leaf& leaf, size_t i) {
leaf->remove(i);
}
auto BTreeDatabase::BTreeImpl::nextLeaf(Leaf const&) -> Maybe<Pointer> {
return {};
}
void BTreeDatabase::BTreeImpl::setNextLeaf(Leaf&, Maybe<Pointer>) {}
void BTreeDatabase::readBlock(BlockIndex blockIndex, size_t blockOffset, char* block, size_t size) const {
checkBlockIndex(blockIndex);
rawReadBlock(blockIndex, blockOffset, block, size);
}
ByteArray BTreeDatabase::readBlock(BlockIndex blockIndex) const {
ByteArray block(m_blockSize, 0);
readBlock(blockIndex, 0, block.ptr(), m_blockSize);
return block;
}
void BTreeDatabase::updateBlock(BlockIndex blockIndex, ByteArray const& block) {
checkBlockIndex(blockIndex);
rawWriteBlock(blockIndex, 0, block.ptr(), block.size());
}
void BTreeDatabase::rawReadBlock(BlockIndex blockIndex, size_t blockOffset, char* block, size_t size) const {
if (blockOffset > m_blockSize || size > m_blockSize - blockOffset)
throw DBException::format("Read past end of block, offset: {} size {}", blockOffset, size);
if (size <= 0)
return;
m_device->readFullAbsolute(HeaderSize + blockIndex * (StreamOffset)m_blockSize + blockOffset, block, size);
}
void BTreeDatabase::rawWriteBlock(BlockIndex blockIndex, size_t blockOffset, char const* block, size_t size) const {
if (blockOffset > m_blockSize || size > m_blockSize - blockOffset)
throw DBException::format("Write past end of block, offset: {} size {}", blockOffset, size);
if (size <= 0)
return;
m_device->writeFullAbsolute(HeaderSize + blockIndex * (StreamOffset)m_blockSize + blockOffset, block, size);
}
auto BTreeDatabase::readFreeIndexBlock(BlockIndex blockIndex) -> FreeIndexBlock {
checkBlockIndex(blockIndex);
ByteArray magic(2, 0);
rawReadBlock(blockIndex, 0, magic.ptr(), 2);
if (magic != ByteArray(FreeIndexMagic, 2))
throw DBException::format("Internal exception! block {} missing free index block marker!", blockIndex);
FreeIndexBlock freeIndexBlock;
DataStreamBuffer buffer(max(sizeof(BlockIndex), (size_t)4));
rawReadBlock(blockIndex, 2, buffer.ptr(), sizeof(BlockIndex));
buffer.seek(0);
freeIndexBlock.nextFreeBlock = buffer.read<BlockIndex>();
rawReadBlock(blockIndex, 2 + sizeof(BlockIndex), buffer.ptr(), 4);
buffer.seek(0);
size_t numFree = buffer.read<uint32_t>();
for (size_t i = 0; i < numFree; ++i) {
rawReadBlock(blockIndex, 6 + sizeof(BlockIndex) + sizeof(BlockIndex) * i, buffer.ptr(), sizeof(BlockIndex));
buffer.seek(0);
freeIndexBlock.freeBlocks.append(buffer.read<BlockIndex>());
}
return freeIndexBlock;
}
void BTreeDatabase::writeFreeIndexBlock(BlockIndex blockIndex, FreeIndexBlock indexBlock) {
checkBlockIndex(blockIndex);
rawWriteBlock(blockIndex, 0, FreeIndexMagic, 2);
DataStreamBuffer buffer(max(sizeof(BlockIndex), (size_t)4));
buffer.seek(0);
buffer.write<BlockIndex>(indexBlock.nextFreeBlock);
rawWriteBlock(blockIndex, 2, buffer.ptr(), sizeof(BlockIndex));
buffer.seek(0);
buffer.write<uint32_t>(indexBlock.freeBlocks.size());
rawWriteBlock(blockIndex, 2 + sizeof(BlockIndex), buffer.ptr(), 4);
for (size_t i = 0; i < indexBlock.freeBlocks.size(); ++i) {
buffer.seek(0);
buffer.write<BlockIndex>(indexBlock.freeBlocks[i]);
rawWriteBlock(blockIndex, 6 + sizeof(BlockIndex) + sizeof(BlockIndex) * i, buffer.ptr(), sizeof(BlockIndex));
}
}
uint32_t BTreeDatabase::leafSize(shared_ptr<LeafNode> const& leaf) const {
size_t s = 6;
for (LeafNode::ElementList::iterator i = leaf->elements.begin(); i != leaf->elements.end(); ++i) {
s += m_keySize;
s += dataSize(i->data);
}
return s;
}
uint32_t BTreeDatabase::maxIndexPointers() const {
// 2 for magic, 1 byte for level, sizeof(BlockIndex) for beginPointer, 4
// for size.
return (m_blockSize - 2 - 1 - sizeof(BlockIndex) - 4) / (m_keySize + sizeof(BlockIndex)) + 1;
}
uint32_t BTreeDatabase::dataSize(ByteArray const& d) const {
return vlqUSize(d.size()) + d.size();
}
auto BTreeDatabase::leafTailBlocks(BlockIndex leafPointer) -> List<BlockIndex> {
List<BlockIndex> tailBlocks;
DataStreamBuffer pointerBuffer(sizeof(BlockIndex));
while (leafPointer != InvalidBlockIndex) {
readBlock(leafPointer, m_blockSize - sizeof(BlockIndex), pointerBuffer.ptr(), sizeof(BlockIndex));
pointerBuffer.seek(0);
leafPointer = pointerBuffer.read<BlockIndex>();
if (leafPointer != InvalidBlockIndex)
tailBlocks.append(leafPointer);
}
return tailBlocks;
}
void BTreeDatabase::freeBlock(BlockIndex b) {
if (m_uncommitted.contains(b)) {
m_uncommitted.remove(b);
m_availableBlocks.add(b);
} else {
m_pendingFree.append(b);
}
}
auto BTreeDatabase::reserveBlock() -> BlockIndex {
if (m_availableBlocks.empty()) {
if (m_headFreeIndexBlock != InvalidBlockIndex) {
// If available, make available all the blocks in the first free index
// block.
FreeIndexBlock indexBlock = readFreeIndexBlock(m_headFreeIndexBlock);
for (auto const& b : indexBlock.freeBlocks)
m_availableBlocks.add(b);
// We cannot make available the block itself, because we must maintain
// atomic consistency. We will need to free this block later and commit
// the new free index block chain.
m_pendingFree.append(m_headFreeIndexBlock);
m_headFreeIndexBlock = indexBlock.nextFreeBlock;
}
if (m_availableBlocks.empty()) {
// If we still don't have any available blocks, just add a block to the
// end of the file.
m_availableBlocks.add(makeEndBlock());
}
}
BlockIndex block = m_availableBlocks.takeFirst();
m_uncommitted.add(block);
return block;
}
auto BTreeDatabase::makeEndBlock() -> BlockIndex {
BlockIndex blockCount = (m_deviceSize - HeaderSize) / m_blockSize;
m_deviceSize += m_blockSize;
m_device->resize(m_deviceSize);
return blockCount;
}
void BTreeDatabase::writeRoot() {
DataStreamIODevice ds(m_device);
// First write the root info to whichever section we are not currently using
ds.seek(BTreeRootInfoStart + (m_usingAltRoot ? 0 : BTreeRootInfoSize));
ds.write<BlockIndex>(m_headFreeIndexBlock);
ds.write<StreamOffset>(m_deviceSize);
ds.write<BlockIndex>(m_root);
ds.write<bool>(m_rootIsLeaf);
// Then flush all the pending changes.
m_device->sync();
// Then switch headers by writing the single bit that switches them
m_usingAltRoot = !m_usingAltRoot;
ds.seek(BTreeRootSelectorBit);
ds.write(m_usingAltRoot);
// Then flush this single bit write to make sure it happens before anything
// else.
m_device->sync();
}
void BTreeDatabase::readRoot() {
DataStreamIODevice ds(m_device);
ds.seek(BTreeRootSelectorBit);
ds.read(m_usingAltRoot);
ds.seek(BTreeRootInfoStart + (m_usingAltRoot ? BTreeRootInfoSize : 0));
m_headFreeIndexBlock = ds.read<BlockIndex>();
m_deviceSize = ds.read<StreamOffset>();
m_root = ds.read<BlockIndex>();
m_rootIsLeaf = ds.read<bool>();
}
void BTreeDatabase::doCommit() {
if (m_availableBlocks.empty() && m_pendingFree.empty() && m_uncommitted.empty())
return;
if (!m_availableBlocks.empty() || !m_pendingFree.empty()) {
// First, read the existing head FreeIndexBlock, if it exists
FreeIndexBlock indexBlock = FreeIndexBlock{InvalidBlockIndex, {}};
if (m_headFreeIndexBlock != InvalidBlockIndex) {
indexBlock = readFreeIndexBlock(m_headFreeIndexBlock);
if (indexBlock.freeBlocks.size() >= maxFreeIndexLength()) {
// If the existing head free index block is full, then we should start a
// new one and leave it alone
indexBlock.nextFreeBlock = m_headFreeIndexBlock;
indexBlock.freeBlocks.clear();
} else {
// If we are copying an existing free index block, the old free index
// block will be a newly freed block
indexBlock.freeBlocks.append(m_headFreeIndexBlock);
}
}
// Then, we need to write all the available blocks, which are safe to write
// to, and the pending free blocks, which are NOT safe to write to, to the
// FreeIndexBlock chain.
while (true) {
if (indexBlock.freeBlocks.size() < maxFreeIndexLength() && (!m_availableBlocks.empty() || !m_pendingFree.empty())) {
// If we have room on our current FreeIndexblock, just add a block to
// it. Prioritize the pending free blocks, because we cannot use those
// to write to.
BlockIndex toAdd;
if (m_pendingFree.empty())
toAdd = m_availableBlocks.takeFirst();
else
toAdd = m_pendingFree.takeFirst();
indexBlock.freeBlocks.append(toAdd);
} else {
// If our index block is full OR we are out of blocks to free, then
// need to write a new head free index block.
if (m_availableBlocks.empty())
m_headFreeIndexBlock = makeEndBlock();
else
m_headFreeIndexBlock = m_availableBlocks.takeFirst();
writeFreeIndexBlock(m_headFreeIndexBlock, indexBlock);
// If we're out of blocks to free, then we're done
if (m_availableBlocks.empty() && m_pendingFree.empty())
break;
indexBlock.nextFreeBlock = m_headFreeIndexBlock;
indexBlock.freeBlocks.clear();
}
}
}
writeRoot();
m_uncommitted.clear();
}
void BTreeDatabase::checkIfOpen(char const* methodName, bool shouldBeOpen) const {
if (shouldBeOpen && !m_open)
throw DBException::format("BTreeDatabase method '{}' called when not open, must be open.", methodName);
else if (!shouldBeOpen && m_open)
throw DBException::format("BTreeDatabase method '{}' called when open, cannot call when open.", methodName);
}
void BTreeDatabase::checkBlockIndex(size_t blockIndex) const {
BlockIndex blockCount = (m_deviceSize - HeaderSize) / m_blockSize;
if (blockIndex >= blockCount)
throw DBException::format("blockIndex: {} out of block range", blockIndex);
}
void BTreeDatabase::checkKeySize(ByteArray const& k) const {
if (k.size() != m_keySize)
throw DBException::format("Wrong key size {}", k.size());
}
uint32_t BTreeDatabase::maxFreeIndexLength() const {
return (m_blockSize - 2 - sizeof(BlockIndex) - 4) / sizeof(BlockIndex);
}
BTreeSha256Database::BTreeSha256Database() {
setKeySize(32);
}
BTreeSha256Database::BTreeSha256Database(String const& contentIdentifier) {
setKeySize(32);
setContentIdentifier(contentIdentifier);
}
bool BTreeSha256Database::contains(ByteArray const& key) {
return BTreeDatabase::contains(sha256(key));
}
Maybe<ByteArray> BTreeSha256Database::find(ByteArray const& key) {
return BTreeDatabase::find(sha256(key));
}
bool BTreeSha256Database::insert(ByteArray const& key, ByteArray const& value) {
return BTreeDatabase::insert(sha256(key), value);
}
bool BTreeSha256Database::remove(ByteArray const& key) {
return BTreeDatabase::remove(sha256(key));
}
bool BTreeSha256Database::contains(String const& key) {
return BTreeDatabase::contains(sha256(key));
}
Maybe<ByteArray> BTreeSha256Database::find(String const& key) {
return BTreeDatabase::find(sha256(key));
}
bool BTreeSha256Database::insert(String const& key, ByteArray const& value) {
return BTreeDatabase::insert(sha256(key), value);
}
bool BTreeSha256Database::remove(String const& key) {
return BTreeDatabase::remove(sha256(key));
}
}