When traversing a blob out of range, automatically create the leaves up to the traversed point
This commit is contained in:
parent
bb98e9b31d
commit
bee68464dc
@ -33,13 +33,17 @@ void BlobOnBlocks::resize(uint64_t numBytes) {
|
|||||||
|
|
||||||
void BlobOnBlocks::traverseLeaves(uint64_t beginByte, uint64_t sizeBytes, function<void (uint64_t, DataLeafNode *leaf, uint32_t, uint32_t)> func) const {
|
void BlobOnBlocks::traverseLeaves(uint64_t beginByte, uint64_t sizeBytes, function<void (uint64_t, DataLeafNode *leaf, uint32_t, uint32_t)> func) const {
|
||||||
uint64_t endByte = beginByte + sizeBytes;
|
uint64_t endByte = beginByte + sizeBytes;
|
||||||
assert(endByte <= size());
|
|
||||||
uint32_t firstLeaf = beginByte / _datatree->maxBytesPerLeaf();
|
uint32_t firstLeaf = beginByte / _datatree->maxBytesPerLeaf();
|
||||||
uint32_t endLeaf = utils::ceilDivision(endByte, _datatree->maxBytesPerLeaf());
|
uint32_t endLeaf = utils::ceilDivision(endByte, _datatree->maxBytesPerLeaf());
|
||||||
_datatree->traverseLeaves(firstLeaf, endLeaf, [&func, beginByte, endByte](DataLeafNode *leaf, uint32_t leafIndex) {
|
bool traversingOutOfRange = _datatree->numStoredBytes() < endByte; //TODO numBytes() inefficient
|
||||||
|
_datatree->traverseLeaves(firstLeaf, endLeaf, [&func, beginByte, endByte, endLeaf, traversingOutOfRange](DataLeafNode *leaf, uint32_t leafIndex) {
|
||||||
uint64_t indexOfFirstLeafByte = leafIndex * leaf->maxStoreableBytes();
|
uint64_t indexOfFirstLeafByte = leafIndex * leaf->maxStoreableBytes();
|
||||||
uint32_t dataBegin = utils::maxZeroSubtraction(beginByte, indexOfFirstLeafByte);
|
uint32_t dataBegin = utils::maxZeroSubtraction(beginByte, indexOfFirstLeafByte);
|
||||||
uint32_t dataEnd = std::min((uint64_t)leaf->maxStoreableBytes(), endByte - indexOfFirstLeafByte);
|
uint32_t dataEnd = std::min((uint64_t)leaf->maxStoreableBytes(), endByte - indexOfFirstLeafByte);
|
||||||
|
if (leafIndex == endLeaf-1 && traversingOutOfRange) {
|
||||||
|
// If we are traversing an area that didn't exist before, then the last leaf was just created with a wrong size. We have to fix it.
|
||||||
|
leaf->resize(dataEnd);
|
||||||
|
}
|
||||||
func(indexOfFirstLeafByte, leaf, dataBegin, dataEnd-dataBegin);
|
func(indexOfFirstLeafByte, leaf, dataBegin, dataEnd-dataBegin);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -61,7 +65,7 @@ uint64_t BlobOnBlocks::tryRead(void *target, uint64_t offset, uint64_t count) co
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BlobOnBlocks::write(const void *source, uint64_t offset, uint64_t size) {
|
void BlobOnBlocks::write(const void *source, uint64_t offset, uint64_t size) {
|
||||||
resizeIfSmallerThan(offset + size);
|
//resizeIfSmallerThan(offset + size);
|
||||||
traverseLeaves(offset, size, [source, offset] (uint64_t indexOfFirstLeafByte, DataLeafNode *leaf, uint32_t leafDataOffset, uint32_t leafDataSize) {
|
traverseLeaves(offset, size, [source, offset] (uint64_t indexOfFirstLeafByte, DataLeafNode *leaf, uint32_t leafDataOffset, uint32_t leafDataSize) {
|
||||||
//TODO Simplify formula, make it easier to understand
|
//TODO Simplify formula, make it easier to understand
|
||||||
leaf->write((uint8_t*)source + indexOfFirstLeafByte - offset + leafDataOffset, leafDataOffset, leafDataSize);
|
leaf->write((uint8_t*)source + indexOfFirstLeafByte - offset + leafDataOffset, leafDataOffset, leafDataSize);
|
||||||
|
@ -21,14 +21,14 @@ public:
|
|||||||
return _baseTree->maxBytesPerLeaf();
|
return _baseTree->maxBytesPerLeaf();
|
||||||
}
|
}
|
||||||
|
|
||||||
void traverseLeaves(uint32_t beginIndex, uint32_t endIndex, std::function<void (const datanodestore::DataLeafNode*, uint32_t)> func) const {
|
|
||||||
return const_cast<const datatreestore::DataTree*>(_baseTree)->traverseLeaves(beginIndex, endIndex, func);
|
|
||||||
}
|
|
||||||
|
|
||||||
void traverseLeaves(uint32_t beginIndex, uint32_t endIndex, std::function<void (datanodestore::DataLeafNode*, uint32_t)> func) {
|
void traverseLeaves(uint32_t beginIndex, uint32_t endIndex, std::function<void (datanodestore::DataLeafNode*, uint32_t)> func) {
|
||||||
return _baseTree->traverseLeaves(beginIndex, endIndex, func);
|
return _baseTree->traverseLeaves(beginIndex, endIndex, func);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t numLeaves() const {
|
||||||
|
return _baseTree->numLeaves();
|
||||||
|
}
|
||||||
|
|
||||||
void resizeNumBytes(uint64_t newNumBytes) {
|
void resizeNumBytes(uint64_t newNumBytes) {
|
||||||
return _baseTree->resizeNumBytes(newNumBytes);
|
return _baseTree->resizeNumBytes(newNumBytes);
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ using std::function;
|
|||||||
using boost::shared_mutex;
|
using boost::shared_mutex;
|
||||||
using boost::shared_lock;
|
using boost::shared_lock;
|
||||||
using boost::unique_lock;
|
using boost::unique_lock;
|
||||||
|
using std::vector;
|
||||||
|
|
||||||
using cpputils::dynamic_pointer_move;
|
using cpputils::dynamic_pointer_move;
|
||||||
using cpputils::optional_ownership_ptr;
|
using cpputils::optional_ownership_ptr;
|
||||||
@ -82,8 +83,9 @@ unique_ptr<DataLeafNode> DataTree::addDataLeafAt(DataInnerNode *insertPos) {
|
|||||||
return new_leaf;
|
return new_leaf;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional_ownership_ptr<DataNode> DataTree::createChainOfInnerNodes(unsigned int num, DataLeafNode *leaf) {
|
optional_ownership_ptr<DataNode> DataTree::createChainOfInnerNodes(unsigned int num, DataNode *child) {
|
||||||
optional_ownership_ptr<DataNode> chain = cpputils::WithoutOwnership<DataNode>(leaf);
|
//TODO This function is implemented twice, once with optional_ownership_ptr, once with unique_ptr. Redundancy!
|
||||||
|
optional_ownership_ptr<DataNode> chain = cpputils::WithoutOwnership<DataNode>(child);
|
||||||
for(unsigned int i=0; i<num; ++i) {
|
for(unsigned int i=0; i<num; ++i) {
|
||||||
auto newnode = _nodeStore->createNewInnerNode(*chain);
|
auto newnode = _nodeStore->createNewInnerNode(*chain);
|
||||||
chain = cpputils::WithOwnership<DataNode>(std::move(newnode));
|
chain = cpputils::WithOwnership<DataNode>(std::move(newnode));
|
||||||
@ -91,11 +93,27 @@ optional_ownership_ptr<DataNode> DataTree::createChainOfInnerNodes(unsigned int
|
|||||||
return chain;
|
return chain;
|
||||||
}
|
}
|
||||||
|
|
||||||
unique_ptr<DataLeafNode> DataTree::addDataLeafToFullTree() {
|
unique_ptr<DataNode> DataTree::createChainOfInnerNodes(unsigned int num, unique_ptr<DataNode> child) {
|
||||||
|
unique_ptr<DataNode> chain = std::move(child);
|
||||||
|
for(unsigned int i=0; i<num; ++i) {
|
||||||
|
chain = _nodeStore->createNewInnerNode(*chain);
|
||||||
|
}
|
||||||
|
return chain;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataInnerNode* DataTree::increaseTreeDepth(unsigned int levels) {
|
||||||
|
assert(levels >= 1);
|
||||||
auto copyOfOldRoot = _nodeStore->createNewNodeAsCopyFrom(*_rootNode);
|
auto copyOfOldRoot = _nodeStore->createNewNodeAsCopyFrom(*_rootNode);
|
||||||
auto newRootNode = DataNode::convertToNewInnerNode(std::move(_rootNode), *copyOfOldRoot);
|
auto chain = createChainOfInnerNodes(levels-1, copyOfOldRoot.get());
|
||||||
auto newLeaf = addDataLeafAt(newRootNode.get());
|
auto newRootNode = DataNode::convertToNewInnerNode(std::move(_rootNode), *chain);
|
||||||
|
DataInnerNode *result = newRootNode.get();
|
||||||
_rootNode = std::move(newRootNode);
|
_rootNode = std::move(newRootNode);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
unique_ptr<DataLeafNode> DataTree::addDataLeafToFullTree() {
|
||||||
|
DataInnerNode *rootNode = increaseTreeDepth(1);
|
||||||
|
auto newLeaf = addDataLeafAt(rootNode);
|
||||||
return newLeaf;
|
return newLeaf;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,22 +129,46 @@ unique_ptr<DataNode> DataTree::releaseRootNode() {
|
|||||||
return std::move(_rootNode);
|
return std::move(_rootNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO Test numLeaves()
|
||||||
|
uint32_t DataTree::numLeaves() const {
|
||||||
|
//TODO Direct calculating the number of leaves would be faster
|
||||||
|
uint64_t currentNumBytes = _numStoredBytes();
|
||||||
|
if(currentNumBytes == 0) {
|
||||||
|
//We always have at least one leaf
|
||||||
|
currentNumBytes = 1;
|
||||||
|
}
|
||||||
|
return utils::ceilDivision(currentNumBytes, _nodeStore->layout().maxBytesPerLeaf());
|
||||||
|
}
|
||||||
|
|
||||||
void DataTree::traverseLeaves(uint32_t beginIndex, uint32_t endIndex, function<void (DataLeafNode*, uint32_t)> func) {
|
void DataTree::traverseLeaves(uint32_t beginIndex, uint32_t endIndex, function<void (DataLeafNode*, uint32_t)> func) {
|
||||||
const_cast<const DataTree*>(this)->traverseLeaves(beginIndex, endIndex, [func](const DataLeafNode* leaf, uint32_t leafIndex) {
|
unique_lock<shared_mutex> lock(_mutex); //TODO Only lock when resizing
|
||||||
func(const_cast<DataLeafNode*>(leaf), leafIndex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void DataTree::traverseLeaves(uint32_t beginIndex, uint32_t endIndex, function<void (const DataLeafNode*, uint32_t)> func) const {
|
|
||||||
shared_lock<shared_mutex> lock(_mutex);
|
|
||||||
assert(beginIndex <= endIndex);
|
assert(beginIndex <= endIndex);
|
||||||
//TODO assert(beginIndex <= numLeaves());
|
|
||||||
//TODO assert(endIndex <= numLeaves());
|
uint8_t neededTreeDepth = utils::ceilLog(_nodeStore->layout().maxChildrenPerInnerNode(), endIndex);
|
||||||
traverseLeaves(_rootNode.get(), 0, beginIndex, endIndex, func);
|
uint32_t numLeaves = this->numLeaves();
|
||||||
|
if (_rootNode->depth() < neededTreeDepth) {
|
||||||
|
//TODO Test cases that actually increase it here by 0 level / 1 level / more than 1 level
|
||||||
|
increaseTreeDepth(neededTreeDepth - _rootNode->depth());
|
||||||
|
}
|
||||||
|
if (numLeaves < endIndex) {
|
||||||
|
//TODO Can this case be efficiently combined with the traversing?
|
||||||
|
LastLeaf(_rootNode.get())->resize(_nodeStore->layout().maxBytesPerLeaf());
|
||||||
|
}
|
||||||
|
uint32_t lastLeafIndex = std::max(numLeaves, endIndex) - 1;
|
||||||
|
if (numLeaves < beginIndex) {
|
||||||
|
//TODO Test cases with numLeaves < / >= beginIndex
|
||||||
|
return _traverseLeaves(_rootNode.get(), 0, numLeaves, endIndex, [beginIndex, numLeaves, lastLeafIndex, &func](DataLeafNode* node, uint32_t index) {
|
||||||
|
if (index >= beginIndex) {
|
||||||
|
func(node, index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return _traverseLeaves(_rootNode.get(), 0, beginIndex, endIndex, func);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataTree::traverseLeaves(const DataNode *root, uint32_t leafOffset, uint32_t beginIndex, uint32_t endIndex, function<void (const DataLeafNode*, uint32_t)> func) const {
|
void DataTree::_traverseLeaves(DataNode *root, uint32_t leafOffset, uint32_t beginIndex, uint32_t endIndex, function<void (DataLeafNode*, uint32_t)> func) {
|
||||||
const DataLeafNode *leaf = dynamic_cast<const DataLeafNode*>(root);
|
DataLeafNode *leaf = dynamic_cast<DataLeafNode*>(root);
|
||||||
if (leaf != nullptr) {
|
if (leaf != nullptr) {
|
||||||
assert(beginIndex <= 1 && endIndex <= 1);
|
assert(beginIndex <= 1 && endIndex <= 1);
|
||||||
if (beginIndex == 0 && endIndex == 1) {
|
if (beginIndex == 0 && endIndex == 1) {
|
||||||
@ -135,20 +177,42 @@ void DataTree::traverseLeaves(const DataNode *root, uint32_t leafOffset, uint32_
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DataInnerNode *inner = dynamic_cast<const DataInnerNode*>(root);
|
DataInnerNode *inner = dynamic_cast<DataInnerNode*>(root);
|
||||||
uint32_t leavesPerChild = leavesPerFullChild(*inner);
|
uint32_t leavesPerChild = leavesPerFullChild(*inner);
|
||||||
uint32_t beginChild = beginIndex/leavesPerChild;
|
uint32_t beginChild = beginIndex/leavesPerChild;
|
||||||
uint32_t endChild = utils::ceilDivision(endIndex, leavesPerChild);
|
uint32_t endChild = utils::ceilDivision(endIndex, leavesPerChild);
|
||||||
|
vector<unique_ptr<DataNode>> children = getOrCreateChildren(inner, beginChild, endChild);
|
||||||
|
|
||||||
for (uint32_t childIndex = beginChild; childIndex < endChild; ++childIndex) {
|
for (uint32_t childIndex = beginChild; childIndex < endChild; ++childIndex) {
|
||||||
uint32_t childOffset = childIndex * leavesPerChild;
|
uint32_t childOffset = childIndex * leavesPerChild;
|
||||||
uint32_t localBeginIndex = utils::maxZeroSubtraction(beginIndex, childOffset);
|
uint32_t localBeginIndex = utils::maxZeroSubtraction(beginIndex, childOffset);
|
||||||
uint32_t localEndIndex = std::min(leavesPerChild, endIndex - childOffset);
|
uint32_t localEndIndex = std::min(leavesPerChild, endIndex - childOffset);
|
||||||
auto child = _nodeStore->load(inner->getChild(childIndex)->key());
|
auto child = std::move(children[childIndex-beginChild]);
|
||||||
traverseLeaves(child.get(), leafOffset + childOffset, localBeginIndex, localEndIndex, func);
|
_traverseLeaves(child.get(), leafOffset + childOffset, localBeginIndex, localEndIndex, func);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vector<unique_ptr<DataNode>> DataTree::getOrCreateChildren(DataInnerNode *node, uint32_t begin, uint32_t end) {
|
||||||
|
vector<unique_ptr<DataNode>> children;
|
||||||
|
children.reserve(end-begin);
|
||||||
|
for (uint32_t childIndex = begin; childIndex < std::min(node->numChildren(), end); ++childIndex) {
|
||||||
|
children.emplace_back(_nodeStore->load(node->getChild(childIndex)->key()));
|
||||||
|
}
|
||||||
|
for (uint32_t childIndex = node->numChildren(); childIndex < end; ++childIndex) {
|
||||||
|
children.emplace_back(addChildTo(node));
|
||||||
|
}
|
||||||
|
assert(children.size() == end-begin);
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
unique_ptr<DataNode> DataTree::addChildTo(DataInnerNode *node) {
|
||||||
|
auto new_leaf = _nodeStore->createNewLeafNode();
|
||||||
|
new_leaf->resize(_nodeStore->layout().maxBytesPerLeaf());
|
||||||
|
auto chain = createChainOfInnerNodes(node->depth()-1, std::move(new_leaf));
|
||||||
|
node->addChild(*chain);
|
||||||
|
return std::move(chain);
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t DataTree::leavesPerFullChild(const DataInnerNode &root) const {
|
uint32_t DataTree::leavesPerFullChild(const DataInnerNode &root) const {
|
||||||
return utils::intPow(_nodeStore->layout().maxChildrenPerInnerNode(), root.depth()-1);
|
return utils::intPow(_nodeStore->layout().maxChildrenPerInnerNode(), root.depth()-1);
|
||||||
}
|
}
|
||||||
|
@ -29,10 +29,10 @@ public:
|
|||||||
const blockstore::Key &key() const;
|
const blockstore::Key &key() const;
|
||||||
uint32_t maxBytesPerLeaf() const;
|
uint32_t maxBytesPerLeaf() const;
|
||||||
|
|
||||||
void traverseLeaves(uint32_t beginIndex, uint32_t endIndex, std::function<void (const datanodestore::DataLeafNode*, uint32_t)> func) const;
|
|
||||||
void traverseLeaves(uint32_t beginIndex, uint32_t endIndex, std::function<void (datanodestore::DataLeafNode*, uint32_t)> func);
|
void traverseLeaves(uint32_t beginIndex, uint32_t endIndex, std::function<void (datanodestore::DataLeafNode*, uint32_t)> func);
|
||||||
void resizeNumBytes(uint64_t newNumBytes);
|
void resizeNumBytes(uint64_t newNumBytes);
|
||||||
|
|
||||||
|
uint32_t numLeaves() const;
|
||||||
uint64_t numStoredBytes() const;
|
uint64_t numStoredBytes() const;
|
||||||
|
|
||||||
void flush() const;
|
void flush() const;
|
||||||
@ -49,19 +49,23 @@ private:
|
|||||||
friend class DataTreeStore;
|
friend class DataTreeStore;
|
||||||
|
|
||||||
std::unique_ptr<datanodestore::DataLeafNode> addDataLeafAt(datanodestore::DataInnerNode *insertPos);
|
std::unique_ptr<datanodestore::DataLeafNode> addDataLeafAt(datanodestore::DataInnerNode *insertPos);
|
||||||
cpputils::optional_ownership_ptr<datanodestore::DataNode> createChainOfInnerNodes(unsigned int num, datanodestore::DataLeafNode *leaf);
|
cpputils::optional_ownership_ptr<datanodestore::DataNode> createChainOfInnerNodes(unsigned int num, datanodestore::DataNode *child);
|
||||||
|
std::unique_ptr<datanodestore::DataNode> createChainOfInnerNodes(unsigned int num, std::unique_ptr<datanodestore::DataNode> child);
|
||||||
std::unique_ptr<datanodestore::DataLeafNode> addDataLeafToFullTree();
|
std::unique_ptr<datanodestore::DataLeafNode> addDataLeafToFullTree();
|
||||||
|
|
||||||
void deleteLastChildSubtree(datanodestore::DataInnerNode *node);
|
void deleteLastChildSubtree(datanodestore::DataInnerNode *node);
|
||||||
void ifRootHasOnlyOneChildReplaceRootWithItsChild();
|
void ifRootHasOnlyOneChildReplaceRootWithItsChild();
|
||||||
|
|
||||||
//TODO Use underscore for private methods
|
//TODO Use underscore for private methods
|
||||||
void traverseLeaves(const datanodestore::DataNode *root, uint32_t leafOffset, uint32_t beginIndex, uint32_t endIndex, std::function<void (const datanodestore::DataLeafNode*, uint32_t)> func) const;
|
void _traverseLeaves(datanodestore::DataNode *root, uint32_t leafOffset, uint32_t beginIndex, uint32_t endIndex, std::function<void (datanodestore::DataLeafNode*, uint32_t)> func);
|
||||||
uint32_t leavesPerFullChild(const datanodestore::DataInnerNode &root) const;
|
uint32_t leavesPerFullChild(const datanodestore::DataInnerNode &root) const;
|
||||||
uint64_t _numStoredBytes() const;
|
uint64_t _numStoredBytes() const;
|
||||||
uint64_t _numStoredBytes(const datanodestore::DataNode &root) const;
|
uint64_t _numStoredBytes(const datanodestore::DataNode &root) const;
|
||||||
cpputils::optional_ownership_ptr<datanodestore::DataLeafNode> LastLeaf(datanodestore::DataNode *root);
|
cpputils::optional_ownership_ptr<datanodestore::DataLeafNode> LastLeaf(datanodestore::DataNode *root);
|
||||||
std::unique_ptr<datanodestore::DataLeafNode> LastLeaf(std::unique_ptr<datanodestore::DataNode> root);
|
std::unique_ptr<datanodestore::DataLeafNode> LastLeaf(std::unique_ptr<datanodestore::DataNode> root);
|
||||||
|
datanodestore::DataInnerNode* increaseTreeDepth(unsigned int levels);
|
||||||
|
std::vector<std::unique_ptr<datanodestore::DataNode>> getOrCreateChildren(datanodestore::DataInnerNode *node, uint32_t begin, uint32_t end);
|
||||||
|
std::unique_ptr<datanodestore::DataNode> addChildTo(datanodestore::DataInnerNode *node);
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(DataTree);
|
DISALLOW_COPY_AND_ASSIGN(DataTree);
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#include "Math.h"
|
#include "Math.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
namespace blobstore {
|
namespace blobstore {
|
||||||
namespace onblocks {
|
namespace onblocks {
|
||||||
namespace utils {
|
namespace utils {
|
||||||
@ -23,6 +25,10 @@ uint32_t maxZeroSubtraction(uint32_t minuend, uint32_t subtrahend) {
|
|||||||
return minuend-subtrahend;
|
return minuend-subtrahend;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t ceilLog(uint32_t base, uint32_t value) {
|
||||||
|
return std::ceil((long double)std::log(value)/(long double)std::log(base));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ namespace utils {
|
|||||||
uint32_t intPow(uint32_t base, uint32_t exponent);
|
uint32_t intPow(uint32_t base, uint32_t exponent);
|
||||||
uint32_t ceilDivision(uint32_t dividend, uint32_t divisor);
|
uint32_t ceilDivision(uint32_t dividend, uint32_t divisor);
|
||||||
uint32_t maxZeroSubtraction(uint32_t minuend, uint32_t subtrahend);
|
uint32_t maxZeroSubtraction(uint32_t minuend, uint32_t subtrahend);
|
||||||
|
uint32_t ceilLog(uint32_t base, uint32_t value);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,6 +109,12 @@ INSTANTIATE_TEST_CASE_P(BlobReadWriteDataTest, BlobReadWriteDataTest, Values(
|
|||||||
DataRange(BlobReadWriteDataTest::LARGE_SIZE, BlobReadWriteDataTest::LARGE_SIZE*1/3, BlobReadWriteDataTest::LARGE_SIZE*2/3) // access middle to end
|
DataRange(BlobReadWriteDataTest::LARGE_SIZE, BlobReadWriteDataTest::LARGE_SIZE*1/3, BlobReadWriteDataTest::LARGE_SIZE*2/3) // access middle to end
|
||||||
));
|
));
|
||||||
|
|
||||||
|
TEST_P(BlobReadWriteDataTest, WritingDoesntChangeSize) {
|
||||||
|
blob->resize(GetParam().blobsize);
|
||||||
|
blob->write(this->foregroundData.data(), GetParam().offset, GetParam().count);
|
||||||
|
EXPECT_EQ(GetParam().blobsize, blob->size());
|
||||||
|
}
|
||||||
|
|
||||||
TEST_P(BlobReadWriteDataTest, WriteAndReadImmediately) {
|
TEST_P(BlobReadWriteDataTest, WriteAndReadImmediately) {
|
||||||
blob->resize(GetParam().blobsize);
|
blob->resize(GetParam().blobsize);
|
||||||
blob->write(this->foregroundData.data(), GetParam().offset, GetParam().count);
|
blob->write(this->foregroundData.data(), GetParam().offset, GetParam().count);
|
||||||
|
@ -0,0 +1,200 @@
|
|||||||
|
#include "testutils/DataTreeTest.h"
|
||||||
|
#include "testutils/TwoLevelDataFixture.h"
|
||||||
|
#include "../../../../implementations/onblocks/utils/Math.h"
|
||||||
|
#include <messmer/blockstore/utils/Data.h>
|
||||||
|
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
using ::testing::WithParamInterface;
|
||||||
|
using ::testing::Values;
|
||||||
|
using ::testing::Combine;
|
||||||
|
using std::tuple;
|
||||||
|
using std::get;
|
||||||
|
using std::function;
|
||||||
|
using std::mem_fn;
|
||||||
|
using cpputils::dynamic_pointer_move;
|
||||||
|
|
||||||
|
using blobstore::onblocks::datanodestore::DataLeafNode;
|
||||||
|
using blobstore::onblocks::datanodestore::DataInnerNode;
|
||||||
|
using blobstore::onblocks::datanodestore::DataNode;
|
||||||
|
using blobstore::onblocks::datanodestore::DataNodeLayout;
|
||||||
|
using blobstore::onblocks::datatreestore::DataTree;
|
||||||
|
using blobstore::onblocks::utils::ceilDivision;
|
||||||
|
using blockstore::Key;
|
||||||
|
using blockstore::Data;
|
||||||
|
|
||||||
|
using std::unique_ptr;
|
||||||
|
|
||||||
|
class DataTreeTest_ResizeByTraversing: public DataTreeTest {
|
||||||
|
public:
|
||||||
|
static constexpr DataNodeLayout LAYOUT = DataNodeLayout(BLOCKSIZE_BYTES);
|
||||||
|
|
||||||
|
unique_ptr<DataTree> CreateTree(unique_ptr<DataNode> root) {
|
||||||
|
Key key = root->key();
|
||||||
|
root.reset();
|
||||||
|
return treeStore.load(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
unique_ptr<DataTree> CreateLeafTreeWithSize(uint32_t size) {
|
||||||
|
return CreateTree(CreateLeafWithSize(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
unique_ptr<DataTree> CreateTwoLeafTreeWithSecondLeafSize(uint32_t size) {
|
||||||
|
return CreateTree(CreateTwoLeafWithSecondLeafSize(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
unique_ptr<DataTree> CreateFullTwoLevelTreeWithLastLeafSize(uint32_t size) {
|
||||||
|
return CreateTree(CreateFullTwoLevelWithLastLeafSize(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
unique_ptr<DataTree> CreateThreeLevelTreeWithTwoChildrenAndLastLeafSize(uint32_t size) {
|
||||||
|
return CreateTree(CreateThreeLevelWithTwoChildrenAndLastLeafSize(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
unique_ptr<DataTree> CreateThreeLevelTreeWithThreeChildrenAndLastLeafSize(uint32_t size) {
|
||||||
|
return CreateTree(CreateThreeLevelWithThreeChildrenAndLastLeafSize(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
unique_ptr<DataTree> CreateFullThreeLevelTreeWithLastLeafSize(uint32_t size) {
|
||||||
|
return CreateTree(CreateFullThreeLevelWithLastLeafSize(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
unique_ptr<DataTree> CreateFourLevelMinDataTreeWithLastLeafSize(uint32_t size) {
|
||||||
|
return CreateTree(CreateFourLevelMinDataWithLastLeafSize(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EXPECT_IS_LEFTMAXDATA_TREE(const Key &key) {
|
||||||
|
auto root = nodeStore->load(key);
|
||||||
|
DataInnerNode *inner = dynamic_cast<DataInnerNode*>(root.get());
|
||||||
|
if (inner != nullptr) {
|
||||||
|
for (uint32_t i = 0; i < inner->numChildren()-1; ++i) {
|
||||||
|
EXPECT_IS_MAXDATA_TREE(inner->getChild(i)->key());
|
||||||
|
}
|
||||||
|
EXPECT_IS_LEFTMAXDATA_TREE(inner->LastChild()->key());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EXPECT_IS_MAXDATA_TREE(const Key &key) {
|
||||||
|
auto root = nodeStore->load(key);
|
||||||
|
DataInnerNode *inner = dynamic_cast<DataInnerNode*>(root.get());
|
||||||
|
if (inner != nullptr) {
|
||||||
|
for (uint32_t i = 0; i < inner->numChildren(); ++i) {
|
||||||
|
EXPECT_IS_MAXDATA_TREE(inner->getChild(i)->key());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DataLeafNode *leaf = dynamic_cast<DataLeafNode*>(root.get());
|
||||||
|
EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf(), leaf->numBytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
constexpr DataNodeLayout DataTreeTest_ResizeByTraversing::LAYOUT;
|
||||||
|
|
||||||
|
class DataTreeTest_ResizeByTraversing_P: public DataTreeTest_ResizeByTraversing, public WithParamInterface<tuple<function<unique_ptr<DataTree>(DataTreeTest_ResizeByTraversing*, uint32_t)>, uint32_t, uint32_t>> {
|
||||||
|
public:
|
||||||
|
DataTreeTest_ResizeByTraversing_P()
|
||||||
|
: oldLastLeafSize(get<1>(GetParam())),
|
||||||
|
tree(get<0>(GetParam())(this, oldLastLeafSize)),
|
||||||
|
numberOfLeavesToAdd(get<2>(GetParam())),
|
||||||
|
newNumberOfLeaves(tree->numLeaves()+numberOfLeavesToAdd),
|
||||||
|
ZEROES(LAYOUT.maxBytesPerLeaf())
|
||||||
|
{
|
||||||
|
ZEROES.FillWithZeroes();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GrowTree(const Key &key, uint32_t numLeavesToAdd) {
|
||||||
|
auto tree = treeStore.load(key);
|
||||||
|
GrowTree(tree.get(), numLeavesToAdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GrowTree(DataTree *tree, uint32_t numLeavesToAdd) {
|
||||||
|
uint32_t oldNumLeaves = tree->numLeaves();
|
||||||
|
uint32_t newNumLeaves = oldNumLeaves + numLeavesToAdd;
|
||||||
|
//TODO Test cases where beginIndex is inside of the existing leaves
|
||||||
|
tree->traverseLeaves(newNumLeaves-1, newNumLeaves, [] (DataLeafNode*,uint32_t){});
|
||||||
|
tree->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
unique_ptr<DataLeafNode> LastLeaf(const Key &key) {
|
||||||
|
auto root = nodeStore->load(key);
|
||||||
|
auto leaf = dynamic_pointer_move<DataLeafNode>(root);
|
||||||
|
if (leaf.get() != nullptr) {
|
||||||
|
return leaf;
|
||||||
|
}
|
||||||
|
auto inner = dynamic_pointer_move<DataInnerNode>(root);
|
||||||
|
return LastLeaf(inner->LastChild()->key());
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t oldLastLeafSize;
|
||||||
|
unique_ptr<DataTree> tree;
|
||||||
|
uint32_t numberOfLeavesToAdd;
|
||||||
|
uint32_t newNumberOfLeaves;
|
||||||
|
Data ZEROES;
|
||||||
|
};
|
||||||
|
INSTANTIATE_TEST_CASE_P(DataTreeTest_ResizeByTraversing_P, DataTreeTest_ResizeByTraversing_P,
|
||||||
|
Combine(
|
||||||
|
//Tree we're starting with
|
||||||
|
Values<function<unique_ptr<DataTree>(DataTreeTest_ResizeByTraversing*, uint32_t)>>(
|
||||||
|
mem_fn(&DataTreeTest_ResizeByTraversing::CreateLeafTreeWithSize),
|
||||||
|
mem_fn(&DataTreeTest_ResizeByTraversing::CreateTwoLeafTreeWithSecondLeafSize),
|
||||||
|
mem_fn(&DataTreeTest_ResizeByTraversing::CreateFullTwoLevelTreeWithLastLeafSize),
|
||||||
|
mem_fn(&DataTreeTest_ResizeByTraversing::CreateThreeLevelTreeWithTwoChildrenAndLastLeafSize),
|
||||||
|
mem_fn(&DataTreeTest_ResizeByTraversing::CreateThreeLevelTreeWithThreeChildrenAndLastLeafSize),
|
||||||
|
mem_fn(&DataTreeTest_ResizeByTraversing::CreateFullThreeLevelTreeWithLastLeafSize),
|
||||||
|
mem_fn(&DataTreeTest_ResizeByTraversing::CreateFourLevelMinDataTreeWithLastLeafSize)
|
||||||
|
),
|
||||||
|
//Last leaf size of the start tree
|
||||||
|
Values(
|
||||||
|
0u,
|
||||||
|
1u,
|
||||||
|
10u,
|
||||||
|
DataTreeTest_ResizeByTraversing::LAYOUT.maxBytesPerLeaf()
|
||||||
|
),
|
||||||
|
//Number of leaves we're adding
|
||||||
|
Values(
|
||||||
|
1u,
|
||||||
|
2u,
|
||||||
|
DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode(), //Full two level tree
|
||||||
|
2* DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode(), //Three level tree with two children
|
||||||
|
3* DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode(), //Three level tree with three children
|
||||||
|
DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode() * DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode(), //Full three level tree
|
||||||
|
DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode() * DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode() + 1 //Four level mindata tree
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
TEST_P(DataTreeTest_ResizeByTraversing_P, StructureIsValid) {
|
||||||
|
GrowTree(tree.get(), numberOfLeavesToAdd);
|
||||||
|
EXPECT_IS_LEFTMAXDATA_TREE(tree->key());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(DataTreeTest_ResizeByTraversing_P, NumBytesIsCorrect) {
|
||||||
|
GrowTree(tree.get(), numberOfLeavesToAdd);
|
||||||
|
// tree->numLeaves() only goes down the right border nodes and expects the tree to be a left max data tree.
|
||||||
|
// This is what the StructureIsValid test case is for.
|
||||||
|
EXPECT_EQ(newNumberOfLeaves, tree->numLeaves());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(DataTreeTest_ResizeByTraversing_P, DepthFlagsAreCorrect) {
|
||||||
|
GrowTree(tree.get(), numberOfLeavesToAdd);
|
||||||
|
uint32_t depth = ceil(log(newNumberOfLeaves)/log(DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode()));
|
||||||
|
CHECK_DEPTH(depth, tree->key());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(DataTreeTest_ResizeByTraversing_P, KeyDoesntChange) {
|
||||||
|
Key key = tree->key();
|
||||||
|
tree->flush();
|
||||||
|
GrowTree(tree.get(), numberOfLeavesToAdd);
|
||||||
|
EXPECT_EQ(key, tree->key());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(DataTreeTest_ResizeByTraversing_P, DataStaysIntact) {
|
||||||
|
uint32_t oldNumberOfLeaves = std::max(1u, ceilDivision(tree->numStoredBytes(), nodeStore->layout().maxBytesPerLeaf()));
|
||||||
|
TwoLevelDataFixture data(nodeStore, TwoLevelDataFixture::SizePolicy::Unchanged);
|
||||||
|
Key key = tree->key();
|
||||||
|
tree.reset();
|
||||||
|
data.FillInto(nodeStore->load(key).get());
|
||||||
|
|
||||||
|
GrowTree(key, newNumberOfLeaves);
|
||||||
|
|
||||||
|
data.EXPECT_DATA_CORRECT(nodeStore->load(key).get(), oldNumberOfLeaves, oldLastLeafSize);
|
||||||
|
}
|
32
test/implementations/onblocks/utils/CeilLogTest.cpp
Normal file
32
test/implementations/onblocks/utils/CeilLogTest.cpp
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#include <google/gtest/gtest.h>
|
||||||
|
#include "../../../../implementations/onblocks/utils/Math.h"
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
using namespace blobstore::onblocks::utils;
|
||||||
|
using ::testing::Test;
|
||||||
|
using std::numeric_limits;
|
||||||
|
|
||||||
|
class CeilLogTest: public Test {};
|
||||||
|
|
||||||
|
TEST_F(CeilLogTest, Log3_0) {
|
||||||
|
EXPECT_EQ(0, ceilLog(3, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CeilLogTest, Log3_1) {
|
||||||
|
EXPECT_EQ(0, ceilLog(3, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CeilLogTest, Log3_2) {
|
||||||
|
EXPECT_EQ(1, ceilLog(3, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CeilLogTest, Log3_3) {
|
||||||
|
EXPECT_EQ(1, ceilLog(3, 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CeilLogTest, Log3_4) {
|
||||||
|
EXPECT_EQ(2, ceilLog(3, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO ...
|
Loading…
x
Reference in New Issue
Block a user