diff options
author | Dewal Gupta <dewal.gupta@10gen.com> | 2018-08-03 13:11:28 -0400 |
---|---|---|
committer | Dewal Gupta <dewal.gupta@10gen.com> | 2018-08-08 09:07:32 -0400 |
commit | e02b9388ce31740cb2daf7ed86ead382ed66b3b5 (patch) | |
tree | 9d70a60d2995e5d848c1b5627dd88402a240b738 | |
parent | 4b6a5591cf912f6cadaf2f1ede5d58ad2bb694c6 (diff) | |
download | mongo-e02b9388ce31740cb2daf7ed86ead382ed66b3b5.tar.gz |
SERVER-35820 Implements radix trie for biggie storage engine allowing more efficient copying on modification
-rw-r--r-- | src/mongo/db/storage/biggie/biggie_record_store.cpp | 34 | ||||
-rw-r--r-- | src/mongo/db/storage/biggie/biggie_record_store.h | 4 | ||||
-rw-r--r-- | src/mongo/db/storage/biggie/biggie_sorted_impl.cpp | 25 | ||||
-rw-r--r-- | src/mongo/db/storage/biggie/biggie_sorted_impl.h | 12 | ||||
-rw-r--r-- | src/mongo/db/storage/biggie/store.h | 848 | ||||
-rw-r--r-- | src/mongo/db/storage/biggie/store_test.cpp | 1120 |
6 files changed, 1745 insertions, 298 deletions
diff --git a/src/mongo/db/storage/biggie/biggie_record_store.cpp b/src/mongo/db/storage/biggie/biggie_record_store.cpp index 6168c706d6e..d7afd580edc 100644 --- a/src/mongo/db/storage/biggie/biggie_record_store.cpp +++ b/src/mongo/db/storage/biggie/biggie_record_store.cpp @@ -196,14 +196,18 @@ Status RecordStore::updateRecord(OperationContext* opCtx, UpdateNotifier* notifier) { StringStore* workingCopy = getRecoveryUnitBranch_forking(opCtx); std::string key = createKey(_ident, oldLocation.repr()); - StringStore::iterator it = workingCopy->find(key); + StringStore::const_iterator it = workingCopy->find(key); invariant(it != workingCopy->end()); - it->second = std::string(data, len); + + StringStore::value_type vt{key, std::string(data, len)}; + workingCopy->update(std::move(vt)); + // it->second = std::string(data, len); return Status::OK(); } bool RecordStore::updateWithDamagesSupported() const { - return true; + // TODO: enable updateWithDamages. + return false; } StatusWith<RecordData> RecordStore::updateWithDamages(OperationContext* opCtx, @@ -211,17 +215,7 @@ StatusWith<RecordData> RecordStore::updateWithDamages(OperationContext* opCtx, const RecordData& oldRec, const char* damageSource, const mutablebson::DamageVector& damages) { - StringStore* workingCopy = getRecoveryUnitBranch_forking(opCtx); - std::string key = createKey(_ident, loc.repr()); - StringStore::iterator doc = workingCopy->find(key); - invariant(doc != workingCopy->end()); // Only update existing records. - for (const auto& d : damages) { - const char* source = damageSource + d.sourceOffset; - char* target = (&doc->second[0]) + d.targetOffset; - std::memcpy(target, source, d.size); - } - RecordData updatedRecord(doc->second.c_str(), doc->second.length()); - return updatedRecord; // Data is un-owned. + return Status::OK(); } std::unique_ptr<SeekableRecordCursor> RecordStore::getCursor(OperationContext* opCtx, @@ -233,8 +227,8 @@ std::unique_ptr<SeekableRecordCursor> RecordStore::getCursor(OperationContext* o Status RecordStore::truncate(OperationContext* opCtx) { StringStore* str = getRecoveryUnitBranch_forking(opCtx); - StringStore::iterator it = str->lower_bound(_prefix); - StringStore::iterator end = str->upper_bound(_postfix); + StringStore::const_iterator it = str->lower_bound(_prefix); + StringStore::const_iterator end = str->upper_bound(_postfix); std::vector<std::string> keysToErase; while (it != end) { keysToErase.push_back(it->first); @@ -382,7 +376,7 @@ boost::optional<Record> RecordStore::ReverseCursor::next() { StringStore* workingCopy = getRecoveryUnitBranch_forking(opCtx); if (_needFirstSeek) { _needFirstSeek = false; - it = StringStore::reverse_iterator(workingCopy->upper_bound(_postfix)); + it = StringStore::const_reverse_iterator(workingCopy->upper_bound(_postfix)); } else if (it != workingCopy->rend()) { ++it; } else { @@ -404,12 +398,12 @@ boost::optional<Record> RecordStore::ReverseCursor::seekExact(const RecordId& id _savedPosition = boost::none; StringStore* workingCopy = getRecoveryUnitBranch_forking(opCtx); std::string key = createKey(_ident, id.repr()); - StringStore::iterator canFind = workingCopy->find(key); + StringStore::const_iterator canFind = workingCopy->find(key); if (canFind == workingCopy->end() || !inPrefix(canFind->first)) { it = workingCopy->rend(); return boost::none; } - it = StringStore::reverse_iterator(++canFind); // reverse iterator returns item 1 before + it = StringStore::const_reverse_iterator(++canFind); // reverse iterator returns item 1 before _savedPosition = it->first; return Record{id, RecordData(it->second.c_str(), it->second.length())}; } @@ -421,7 +415,7 @@ void RecordStore::ReverseCursor::saveUnpositioned() {} bool RecordStore::ReverseCursor::restore() { StringStore* workingCopy = getRecoveryUnitBranch_forking(opCtx); it = _savedPosition - ? StringStore::reverse_iterator(workingCopy->upper_bound(_savedPosition.value())) + ? StringStore::const_reverse_iterator(workingCopy->upper_bound(_savedPosition.value())) : workingCopy->rend(); return true; } diff --git a/src/mongo/db/storage/biggie/biggie_record_store.h b/src/mongo/db/storage/biggie/biggie_record_store.h index b7207fe721c..9310a875c24 100644 --- a/src/mongo/db/storage/biggie/biggie_record_store.h +++ b/src/mongo/db/storage/biggie/biggie_record_store.h @@ -144,7 +144,7 @@ private: StringData _ident; std::string _prefix; std::string _postfix; - StringStore::iterator it; + StringStore::const_iterator it; boost::optional<std::string> _savedPosition; bool _needFirstSeek{true}; @@ -166,7 +166,7 @@ private: StringData _ident; std::string _prefix; std::string _postfix; - StringStore::reverse_iterator it; + StringStore::const_reverse_iterator it; boost::optional<std::string> _savedPosition; bool _needFirstSeek{true}; diff --git a/src/mongo/db/storage/biggie/biggie_sorted_impl.cpp b/src/mongo/db/storage/biggie/biggie_sorted_impl.cpp index eb0492d4e00..66beebd5aba 100644 --- a/src/mongo/db/storage/biggie/biggie_sorted_impl.cpp +++ b/src/mongo/db/storage/biggie/biggie_sorted_impl.cpp @@ -336,7 +336,8 @@ Status SortedDataInterface::insert(OperationContext* opCtx, if (!dupsAllowed) { std::string workingCopyLowerBound = combineKeyAndRID(key, RecordId::min(), _prefix, _order); std::string workingCopyUpperBound = combineKeyAndRID(key, RecordId::max(), _prefix, _order); - StringStore::iterator lowerBoundIterator = workingCopy->lower_bound(workingCopyLowerBound); + StringStore::const_iterator lowerBoundIterator = + workingCopy->lower_bound(workingCopyLowerBound); if (lowerBoundIterator != workingCopy->end() && lowerBoundIterator->first.compare(_KSForIdentEnd) < 0) { @@ -377,7 +378,11 @@ Status SortedDataInterface::truncate(OperationContext* opCtx) { StringStore* workingCopy = getRecoveryUnitBranch_forking(opCtx); auto workingCopyLowerBound = workingCopy->lower_bound(_KSForIdentStart); auto workingCopyUpperBound = workingCopy->upper_bound(_KSForIdentEnd); - workingCopy->erase(workingCopyLowerBound, workingCopyUpperBound); + // workingCopy->erase(workingCopyLowerBound, workingCopyUpperBound); + while (workingCopyLowerBound != workingCopyUpperBound) { + workingCopy->erase(workingCopyLowerBound->first); + ++workingCopyLowerBound; + } return Status::OK(); } @@ -428,8 +433,8 @@ bool SortedDataInterface::appendCustomStats(OperationContext* opCtx, long long SortedDataInterface::getSpaceUsedBytes(OperationContext* opCtx) const { StringStore* str = getRecoveryUnitBranch_forking(opCtx); size_t totalSize = 0; - StringStore::iterator it = str->lower_bound(_KSForIdentStart); - StringStore::iterator end = str->upper_bound(_KSForIdentEnd); + StringStore::const_iterator it = str->lower_bound(_KSForIdentStart); + StringStore::const_iterator end = str->upper_bound(_KSForIdentEnd); int64_t numElements = str->distance(it, end); for (int i = 0; i < numElements; i++) { totalSize += it->first.length(); @@ -547,7 +552,8 @@ void SortedDataInterface::Cursor::setEndPosition(const BSONObj& key, bool inclus // Reverse iterators work with upper bound since upper bound will return the first element // past the argument, so when it becomes a reverse iterator, it goes backwards one, // (according to the C++ standard) and we end up in the right place. - _endPosReverse = StringStore::reverse_iterator(workingCopy->upper_bound(_endPosBound)); + _endPosReverse = + StringStore::const_reverse_iterator(workingCopy->upper_bound(_endPosBound)); } } @@ -614,8 +620,8 @@ boost::optional<IndexKeyEntry> SortedDataInterface::Cursor::seekAfterProcessing( // Reverse iterators work with upper bound since upper bound will return the first // element past the argument, so when it becomes a reverse iterator, it goes // backwards one, (according to the C++ standard) and we end up in the right place. - _reverseIt = - StringStore::reverse_iterator(_workingCopy->upper_bound(workingCopyBound)); + _reverseIt = StringStore::const_reverse_iterator( + _workingCopy->upper_bound(workingCopyBound)); } // Here, we check to make sure the iterator doesn't fall off the data structure and is // in the ident. We also check to make sure it is on the correct side of the end @@ -633,7 +639,8 @@ boost::optional<IndexKeyEntry> SortedDataInterface::Cursor::seekAfterProcessing( // Reverse iterators work with upper bound since upper bound will return the first // element past the argument, so when it becomes a reverse iterator, it goes // backwards one, (according to the C++ standard) and we end up in the right place. - _reverseIt = StringStore::reverse_iterator(_workingCopy->upper_bound(workingCopyBound)); + _reverseIt = + StringStore::const_reverse_iterator(_workingCopy->upper_bound(workingCopyBound)); } // Once again, we check to make sure the iterator didn't fall off the data structure and // still is in the ident. @@ -725,7 +732,7 @@ void SortedDataInterface::Cursor::restore() { if (_saveKey.length() == 0) { _reverseIt = workingCopy->rend(); } else { - _reverseIt = StringStore::reverse_iterator(workingCopy->upper_bound(_saveKey)); + _reverseIt = StringStore::const_reverse_iterator(workingCopy->upper_bound(_saveKey)); } if (!checkCursorValid()) { _atEOF = true; diff --git a/src/mongo/db/storage/biggie/biggie_sorted_impl.h b/src/mongo/db/storage/biggie/biggie_sorted_impl.h index 35a719207f1..468cb27ab9e 100644 --- a/src/mongo/db/storage/biggie/biggie_sorted_impl.h +++ b/src/mongo/db/storage/biggie/biggie_sorted_impl.h @@ -133,8 +133,8 @@ public: // This is the "working copy" of the master "branch" in the git analogy. StringStore* _workingCopy; // These store the end positions. - boost::optional<StringStore::iterator> _endPos; - boost::optional<StringStore::reverse_iterator> _endPosReverse; + boost::optional<StringStore::const_iterator> _endPos; + boost::optional<StringStore::const_reverse_iterator> _endPosReverse; // This means if the cursor is a forward or reverse cursor. bool _forward; // This means whether the cursor has reached the last EOF (with regard to this index). @@ -146,10 +146,10 @@ public: // These are the same as before. std::string _prefix; std::string _identEnd; - // These two store the iterator, which is the data structure for cursors. The one we use - // depends on _forward. - StringStore::iterator _forwardIt; - StringStore::reverse_iterator _reverseIt; + // These two store the const_iterator, which is the data structure for cursors. The one we + // use depends on _forward. + StringStore::const_iterator _forwardIt; + StringStore::const_reverse_iterator _reverseIt; // This is the ordering for the key's values for multi-field keys. Ordering _order; // This stores whether or not the end position is inclusive for restore. diff --git a/src/mongo/db/storage/biggie/store.h b/src/mongo/db/storage/biggie/store.h index 7b2fd7038af..8fad8fd5248 100644 --- a/src/mongo/db/storage/biggie/store.h +++ b/src/mongo/db/storage/biggie/store.h @@ -28,8 +28,13 @@ #pragma once +#include <array> +#include <boost/optional.hpp> #include <exception> -#include <map> +#include <iostream> +#include <memory> +#include <string.h> +#include <vector> namespace mongo { namespace biggie { @@ -40,10 +45,15 @@ class merge_conflict_exception : std::exception { } }; +/** + * RadixStore is a Trie data structure with the ability to share nodes among copies of trees to + * minimize data duplication. Each node has a notion of ownership and if modifications are made to + * non-uniquely owned nodes, they are copied to prevent dirtying the data for the other owners of + * the node. + */ template <class Key, class T> -class Store { -private: - std::map<Key, T> map; +class RadixStore { + class Node; public: using mapped_type = T; @@ -52,135 +62,492 @@ public: using pointer = typename std::allocator_traits<allocator_type>::pointer; using const_pointer = typename std::allocator_traits<allocator_type>::const_pointer; using size_type = std::size_t; + using uint8_t = std::uint8_t; - template <class pointer_type, class reference_type, class iter_type> - class store_iterator { - private: - friend class Store; - iter_type iter; - store_iterator(iter_type iter) : iter(iter) {} + template <class pointer_type, class reference_type> + class radix_iterator { + friend class RadixStore; public: - using iterator_category = std::bidirectional_iterator_tag; - using value_type = typename Store::value_type; + using iterator_category = std::forward_iterator_tag; + using value_type = RadixStore::value_type; using difference_type = std::ptrdiff_t; using pointer = pointer_type; using reference = reference_type; - store_iterator() = default; + radix_iterator() : _root(nullptr), _current(nullptr) {} + + radix_iterator(const radix_iterator& other) + : _root(other._root), _current(other._current) {} + + ~radix_iterator() = default; - store_iterator& operator++() { - ++this->iter; + radix_iterator& operator++() { + _findNext(); return *this; } - store_iterator operator++(int) { - store_iterator old = *this; - ++this->iter; + radix_iterator operator++(int) { + radix_iterator old = *this; + ++*this; return old; } - store_iterator& operator--() { - --this->iter; + radix_iterator& operator=(const radix_iterator& other) = default; + + bool operator==(const radix_iterator& other) const { + return this->_current == other._current; + } + + bool operator!=(const radix_iterator& other) const { + return this->_current != other._current; + } + + reference operator*() const { + return *(_current->data); + } + + const_pointer operator->() { + return &*(_current->data); + } + + private: + radix_iterator(const std::shared_ptr<Node>& root) : _root(root), _current(nullptr) {} + + radix_iterator(const std::shared_ptr<Node>& root, Node* current) + : _root(root), _current(current){}; + + /** + * This function traverses the tree to find the next left-most node with data. Modifies + * '_current' to point to this node. It uses a pre-order traversal ('visit' the current + * node itself then 'visit' the child subtrees from left to right ). + */ + void _findNext() { + // If 'current' is a nullptr there is no next node to go to. + if (_current == nullptr) + return; + + // If 'current' is not a leaf, continue moving down and left in the tree until the next + // node. + if (!_current->isLeaf()) { + _traverseLeftSubtree(); + return; + } + + // Get path from root to '_current' since it is required to traverse up the tree. + Key key = _current->data->first; + std::vector<Node*> context = RadixStore::_buildContext(key, _root.get()); + + // 'node' should equal '_current' because that should be the last element in the stack. + // Pop back once more to get access to it's parent node. The parent node will enable + // traversal through the neighboring nodes, and if there are none, the iterator will + // move up the tree to continue searching for the next node with data. + Node* node = context.back(); + context.pop_back(); + + // In case there is no next node, set _current to be 'nullptr' which will mark the end + // of the traversal. + _current = nullptr; + while (!context.empty()) { + uint8_t oldKey = node->trieKey; + node = context.back(); + context.pop_back(); + + // Check the children right of the node that the iterator was at already. This way, + // there will be no backtracking in the traversal. + for (auto iter = oldKey + 1 + node->children.begin(); iter != node->children.end(); + ++iter) { + + // If the node has a child, then the sub-tree must have a node with data that + // has not yet been visited. + if (*iter != nullptr) { + + // If the current node has data, return it and exit. If not, continue + // following the nodes to find the next one with data. It is necessary to go + // to the left-most node in this sub-tree. + if ((*iter)->data != boost::none) { + _current = iter->get(); + return; + } + _current = iter->get(); + _traverseLeftSubtree(); + return; + } + } + } + return; + } + + void _traverseLeftSubtree() { + // This function finds the next left-most node with data under the sub-tree where + // '_current' is root. However, it cannot return the root, and hence at least 1 + // iteration of the while loop is required. + do { + for (auto child : _current->children) { + if (child != nullptr) { + _current = child.get(); + break; + } + } + } while (_current->data == boost::none); + } + + // "_root" is a copy of the root of the tree over which this is iterating. + std::shared_ptr<Node> _root; + + // "_current" is the node that the iterator is currently on. _current->data will never be + // boost::none (unless it is within the process of tree traversal), and _current will be + // become a nullptr once there are no more nodes left to iterate. + Node* _current; + }; + + using iterator = radix_iterator<pointer, value_type&>; + using const_iterator = radix_iterator<const_pointer, const value_type&>; + + template <class pointer_type, class reference_type> + class reverse_radix_iterator { + friend class RadixStore; + friend class radix_iterator<pointer_type, reference_type&>; + + public: + using value_type = RadixStore::value_type; + using difference_type = std::ptrdiff_t; + using pointer = pointer_type; + using reference = reference_type; + + reverse_radix_iterator() : _root(nullptr), _current(nullptr) {} + + reverse_radix_iterator(const const_iterator& it) : _root(it._root), _current(it._current) { + // If the iterator passed in is at the end(), then set _current to root which is + // equivalent to rbegin(). Otherwise, move the iterator back one node, due to the fact + // that the relationship &*r == &*(i-1) must be maintained for any reverse iterator 'r' + // and forward iterator 'i'. + if (_current == nullptr) { + // If the tree is empty, then leave '_current' as nullptr. + if (_root->isLeaf()) + return; + + _current = _root.get(); + _traverseRightSubtree(); + } else { + _findNextReverse(); + } + } + + reverse_radix_iterator(const iterator& it) : _root(it._root), _current(it._current) { + if (_current == nullptr) { + _current = _root.get(); + _traverseRightSubtree(); + } else { + _findNextReverse(); + } + } + + reverse_radix_iterator(const reverse_radix_iterator& other) + : _root(other._root), _current(other._current) {} + + reverse_radix_iterator& operator=(const reverse_radix_iterator& other) = default; + + ~reverse_radix_iterator() = default; + + reverse_radix_iterator& operator++() { + _findNextReverse(); return *this; } - store_iterator operator--(int) { - store_iterator old = *this; - --this->iter; + reverse_radix_iterator operator++(int) { + reverse_radix_iterator old = *this; + ++*this; return old; } - // Non member equality - - friend bool operator==(const store_iterator& lhs, const store_iterator& rhs) { - return lhs.iter == rhs.iter; + bool operator==(const reverse_radix_iterator& other) const { + return this->_current == other._current; } - friend bool operator!=(const store_iterator& lhs, const store_iterator& rhs) { - return !(lhs.iter == rhs.iter); + bool operator!=(const reverse_radix_iterator& other) const { + return this->_current != other._current; } reference operator*() const { - return *this->iter; + return *(_current->data); + } + + const_pointer operator->() { + return &*(_current->data); + } + + + private: + reverse_radix_iterator(const std::shared_ptr<Node>& root) + : _root(root), _current(nullptr) {} + reverse_radix_iterator(const std::shared_ptr<Node>& root, Node* current) + : _root(root), _current(current) {} + + void _findNextReverse() { + // Reverse find iterates through the tree to find the "next" node containing data, + // searching from right to left. Normally a pre-order traversal is used, but for + // reverse, the ordering is to visit child nodes from right to left, then 'visit' + // current node. + if (_current == nullptr) + return; + + Key key = _current->data->first; + + std::vector<Node*> context = RadixStore::_buildContext(key, _root.get()); + Node* node = context.back(); + context.pop_back(); + + // Due to the nature of the traversal, it will always be necessary to move up the tree + // first because when the 'current' node was visited, it meant all its children had been + // visited as well. + uint8_t oldKey; + _current = nullptr; + while (!context.empty()) { + oldKey = node->trieKey; + node = context.back(); + context.pop_back(); + + // After moving up in the tree, continue searching for neighboring nodes to see if + // they have data, moving from right to left. + for (int i = oldKey - 1; i >= 0; i--) { + if (node->children[i] != nullptr) { + // If there is a sub-tree found, it must have data, therefore it's necessary + // to traverse to the right most node. + _current = node->children[i].get(); + _traverseRightSubtree(); + return; + } + } + + // If there were no sub-trees that contained data, and the 'current' node has data, + // it can now finally be 'visited'. + if (node->data != boost::none) { + _current = node; + return; + } + } } - pointer operator->() const { - return &(*this->iter); + void _traverseRightSubtree() { + // This function traverses the given tree to the right most leaf of the subtree where + // 'current' is the root. + do { + for (auto iter = _current->children.rbegin(); iter != _current->children.rend(); + ++iter) { + if (*iter != nullptr) { + _current = iter->get(); + break; + } + } + } while (!_current->isLeaf()); } + + // "_root" is a copy of the root of the tree over which this is iterating. + std::shared_ptr<Node> _root; + + // "_current" is a the node that the iterator is currently on. _current->data will never be + // boost::none, and _current will be become a nullptr once there are no more nodes left to + // iterate. + Node* _current; }; - using iterator = store_iterator<pointer, value_type&, typename std::map<Key, T>::iterator>; - using const_iterator = - store_iterator<const_pointer, const value_type&, typename std::map<Key, T>::const_iterator>; - using reverse_iterator = std::reverse_iterator<iterator>; - using reverse_const_iterator = std::reverse_iterator<const_iterator>; + using reverse_iterator = reverse_radix_iterator<pointer, value_type&>; + using const_reverse_iterator = reverse_radix_iterator<const_pointer, const value_type&>; + + // Constructor + RadixStore(const RadixStore& other) { + _root = other._root; + _numElems = other._numElems; + _sizeElems = other._sizeElems; + } + + RadixStore() { + _root = std::make_shared<RadixStore::Node>('\0'); + _numElems = 0; + _sizeElems = 0; + } - // Constructors + ~RadixStore() = default; - Store(const Store& other) = default; - Store() = default; + // Equality + bool operator==(const RadixStore& other) const { + RadixStore::const_iterator iter = this->begin(); + RadixStore::const_iterator other_iter = other.begin(); - ~Store() = default; + while (iter != this->end()) { + if (*iter != *other_iter) { + return false; + } - // Non member equality + iter++; + other_iter++; + } - friend bool operator==(const Store& lhs, const Store& rhs) { - return lhs.map == rhs.map; + return other_iter == other.end(); } // Capacity - bool empty() const { - return map.empty(); + return _numElems == 0; } - // Number of nodes size_type size() const { - return map.size(); + return _numElems; } - // Size of mapped data in store size_type dataSize() const { - size_type s = size_type(0); - for (const value_type val : *this) { - s += val.second.size(); - } - - return s; + return _sizeElems; } // Modifiers - void clear() noexcept { - map.clear(); + _root = std::make_shared<Node>('\0'); + _numElems = 0; + _sizeElems = 0; + } + + std::pair<const_iterator, bool> insert(value_type&& value) { + Key key = value.first; + mapped_type m = value.second; + + Node* item = _findNode(key); + if (item != nullptr || key.size() == 0) + return std::make_pair(end(), false); + + auto result = _upsertWithCopyOnSharedNodes(key, std::move(value)); + if (result.second) { + _numElems++; + _sizeElems += m.size(); + } + + return result; } - std::pair<iterator, bool> insert(value_type&& value) { - auto res = this->map.insert(value); - iterator iter = iterator(res.first); - return std::pair<iterator, bool>(iter, res.second); + std::pair<const_iterator, bool> update(value_type&& value) { + Key key = value.first; + mapped_type m = value.second; + + // Ensure that the item to be updated exists. + auto item = RadixStore::find(key); + if (item == RadixStore::end()) + return std::make_pair(item, false); + + size_t sizeOfRemovedNode = item->second.size(); + auto result = _upsertWithCopyOnSharedNodes(key, std::move(value)); + if (result.second) { + _sizeElems -= sizeOfRemovedNode; + _sizeElems += m.size(); + } + + return result; } size_type erase(const Key& key) { - return map.erase(key); - } + std::vector<std::pair<Node*, bool>> context; + + std::shared_ptr<Node> node = _root; + bool isUniquelyOwned = _root.use_count() - 1 == 1; + context.push_back(std::make_pair(node.get(), isUniquelyOwned)); - void erase(iterator first, iterator last) { - map.erase(first.iter, last.iter); + for (const char* charKey = key.data(); charKey != key.data() + key.size(); ++charKey) { + const uint8_t c = static_cast<const uint8_t>(*charKey); + node = node->children[c]; + if (node == nullptr) + return false; + + isUniquelyOwned = isUniquelyOwned && node.use_count() - 1 == 1; + context.push_back(std::make_pair(node.get(), isUniquelyOwned)); + } + + size_t sizeOfRemovedNode = node->data->second.size(); + Node* last = context.back().first; + isUniquelyOwned = context.back().second; + context.pop_back(); + if (last->isLeaf()) { + // If the node to be deleted is a leaf node, might need to prune the branch. + uint8_t trieKey; + while (!context.empty()) { + trieKey = last->trieKey; + last = context.back().first; + isUniquelyOwned = context.back().second; + context.pop_back(); + + // If a node on the branch has data, stop pruning. + if (last->data != boost::none) + break; + + // If a node has children other than the one leading to the to-be deleted node, stop + // pruning. + bool hasOtherChildren = false; + for (auto iter = last->children.begin(); iter != last->children.end(); ++iter) { + if (*iter != nullptr && (*iter)->trieKey != trieKey) { + hasOtherChildren = true; + break; + } + } + + if (hasOtherChildren) + break; + } + + if (isUniquelyOwned) { + // If this node is uniquely owned, simply set that child node to null and + // "cut" off that branch of our tree + last->children[trieKey] = nullptr; + } else { + // If its not uniquely owned, copy the branch so we can preserve it for the other + // owner(s). + std::shared_ptr<Node> child = std::make_shared<Node>(last->trieKey); + auto lastIter = last->children.begin(); + for (auto iter = child->children.begin(); iter != child->children.end(); + ++iter, ++lastIter) { + if (*lastIter != nullptr && (*lastIter)->trieKey == trieKey) + continue; + + *iter = *lastIter; + } + + std::shared_ptr<Node> node = child; + while (!context.empty()) { + trieKey = last->trieKey; + last = context.back().first; + context.pop_back(); + node = std::make_shared<Node>(last->trieKey); + + auto lastIter = last->children.begin(); + for (auto iter = node->children.begin(); iter != node->children.end(); + ++iter, ++lastIter) { + *iter = *lastIter; + } + + node->children[trieKey] = child; + child = node; + } + _root = node; + } + } else { + // The to-be deleted node is an internal node, and therefore updating its data to be + // boost::none will "delete" it + _upsertWithCopyOnSharedNodes(key, boost::none); + } + + _numElems--; + _sizeElems -= sizeOfRemovedNode; + return true; } - /** - * Returns a Store that has all changes from both 'this' and 'other' compared to base. - * Throws merge_conflict_exception if there are merge conflicts. - */ - Store merge3(const Store& base, const Store& other) const { - Store store; + // Returns a Store that has all changes from both 'this' and 'other' compared to base. + // Throws merge_conflict_exception if there are merge conflicts. + RadixStore merge3(const RadixStore& base, const RadixStore& other) const { + RadixStore store; // Merges all differences between this and base, along with modifications from other. - for (const Store::value_type val : *this) { - Store::const_iterator baseIter = base.find(val.first); - Store::const_iterator otherIter = other.find(val.first); + RadixStore::const_iterator iter = this->begin(); + while (iter != this->end()) { + const value_type val = *iter; + RadixStore::const_iterator baseIter = base.find(val.first); + RadixStore::const_iterator otherIter = other.find(val.first); if (baseIter != base.end() && otherIter != other.end()) { if (val.second != baseIter->second && otherIter->second != baseIter->second) { @@ -190,10 +557,10 @@ public: if (val.second != baseIter->second) { // Merges non-conflicting insertions from this. - store.insert(Store::value_type(val)); + store.insert(RadixStore::value_type(val)); } else { // Merges non-conflicting modifications from other or no modifications. - store.insert(Store::value_type(*otherIter)); + store.insert(RadixStore::value_type(*otherIter)); } } else if (baseIter != base.end() && otherIter == other.end()) { if (val.second != baseIter->second) { @@ -208,18 +575,21 @@ public: } // Merges insertions from this. - store.insert(Store::value_type(val)); + store.insert(RadixStore::value_type(val)); } + iter++; } // Merges insertions and deletions from other. - for (const Store::value_type otherVal : other) { - Store::const_iterator baseIter = base.find(otherVal.first); - Store::const_iterator thisIter = this->find(otherVal.first); + RadixStore::const_iterator other_iter = other.begin(); + for (; other_iter != other.end(); other_iter++) { + const value_type otherVal = *other_iter; + RadixStore::const_iterator baseIter = base.find(otherVal.first); + RadixStore::const_iterator thisIter = this->find(otherVal.first); if (baseIter == base.end()) { // Merges insertions from other. - store.insert(Store::value_type(otherVal)); + store.insert(RadixStore::value_type(otherVal)); } else if (thisIter == this->end() && otherVal.second != baseIter->second) { // Throws exception if modifications from this conflict with deletions from other. throw merge_conflict_exception(); @@ -229,77 +599,305 @@ public: return store; } - // Iterators + // iterators + const_iterator begin() const noexcept { + if (_numElems == 0) { + return RadixStore::end(); + } - iterator begin() noexcept { - return iterator(this->map.begin()); + auto node = _root; + while (node->data == boost::none) { + for (auto child : node->children) { + if (child != nullptr) { + node = child; + break; + } + } + } + return RadixStore::const_iterator(_root, node.get()); } - iterator end() noexcept { - return iterator(this->map.end()); - } + const_reverse_iterator rbegin() const noexcept { + if (_numElems == 0) + return RadixStore::rend(); - reverse_iterator rbegin() noexcept { - return reverse_iterator(end()); + auto node = _root; + while (!node->isLeaf()) { + for (auto iter = node->children.rbegin(); iter != node->children.rend(); ++iter) { + if (*iter != nullptr) { + node = *iter; + break; + } + } + } + return RadixStore::const_reverse_iterator(_root, node.get()); } - reverse_iterator rend() noexcept { - return reverse_iterator(begin()); + const_iterator end() const noexcept { + return RadixStore::const_iterator(_root); } - const_iterator begin() const noexcept { - return const_iterator(this->map.begin()); + const_reverse_iterator rend() const noexcept { + return RadixStore::const_reverse_iterator(_root); } - const_iterator end() const noexcept { - return const_iterator(this->map.end()); - } + const_iterator find(const Key& key) const { + RadixStore::const_iterator it = RadixStore::end(); - reverse_const_iterator rbegin() const noexcept { - return reverse_const_iterator(end()); + Node* node = _findNode(key); + if (node == nullptr) + return it; + else + return RadixStore::const_iterator(_root, node); } - reverse_const_iterator rend() const noexcept { - return reverse_const_iterator(begin()); - } + const_iterator lower_bound(const Key& key) const { + Node* node = _root.get(); + std::vector<Node*> context; + context.push_back(node); + + const char* charKey = key.data(); + uint8_t idx = '\0'; + + // Traverse the path given the key to see if the node exists. + for (; charKey != key.data() + key.size(); ++charKey) { + idx = static_cast<uint8_t>(*charKey); + if (node->children[idx] != nullptr) { + node = node->children[idx].get(); + context.push_back(node); + } else { + break; + } + } - // Look up + // If the node existed, then can just return an iterator to that node. + if (charKey == key.data() + key.size()) + return const_iterator(_root, node); + + // The node did not exist, so must find an node with the next largest key (if it exists). + // Use the context stack to move up the tree and keep searching for the next node with data + // if need be. + while (!context.empty()) { + node = context.back(); + context.pop_back(); + + for (auto iter = idx + 1 + node->children.begin(); iter != node->children.end(); + ++iter) { + + if (*iter != nullptr) { + // There exists a node with a key larger than the one given, traverse to this + // node which will be the left-most node in this sub-tree. + node = iter->get(); + while (node->data == boost::none) { + for (auto iter = node->children.begin(); iter != node->children.end(); + ++iter) { + if (*iter != nullptr) { + node = iter->get(); + break; + } + } + } + return const_iterator(_root, node); + } + } + idx = node->trieKey; + } - iterator find(const Key& key) { - return iterator(this->map.find(key)); + // If there was no node with a larger key than the one given, return end(). + return end(); } - const_iterator find(const Key& key) const { - return const_iterator(this->map.find(key)); - } + const_iterator upper_bound(const Key& key) const { + const_iterator it = lower_bound(key); + if (it == end()) + return it; - iterator lower_bound(const Key& key) { - return iterator(this->map.lower_bound(key)); - } + if (it->first == key) + return ++it; - const_iterator lower_bound(const Key& key) const { - return const_iterator(this->map.lower_bound(key)); + return it; } - iterator upper_bound(const Key& key) { - return iterator(this->map.upper_bound(key)); + typename RadixStore::iterator::difference_type distance(iterator iter1, iterator iter2) { + return std::distance(iter1, iter2); } - const_iterator upper_bound(const Key& key) const { - return const_iterator(this->map.upper_bound(key)); + typename RadixStore::iterator::difference_type distance(const_iterator iter1, + const_iterator iter2) { + return std::distance(iter1, iter2); } - // std::distance +private: + class Node { + friend class RadixStore; - typename iterator::difference_type distance(iterator iter1, iterator iter2) { - return typename iterator::difference_type(std::distance(iter1.iter, iter2.iter)); - }; + public: + Node(uint8_t key) : trieKey(key) { + children.fill(nullptr); + } - typename iterator::difference_type distance(const_iterator iter1, const_iterator iter2) const { - return typename iterator::difference_type(std::distance(iter1.iter, iter2.iter)); + bool isLeaf() { + for (auto child : children) { + if (child != nullptr) + return false; + } + return true; + } + + uint8_t trieKey; + boost::optional<value_type> data; + std::array<std::shared_ptr<Node>, 256> children; }; + + + Node* _findNode(const Key& key) const { + auto node = _root; + for (const char* it = key.data(); it != key.data() + key.size(); ++it) { + const uint8_t k = static_cast<const uint8_t>(*it); + if (node->children[k] != nullptr) + node = node->children[k]; + else + return nullptr; + } + + if (node->data == boost::none) + return nullptr; + + return node.get(); + } + + /** + * _upsertWithCopyOnSharedNodes is a helper function to help manage copy on modification for the + * tree. This function follows the path for the to-be modified node using the keystring. If at + * any point, the path is no longer uniquely owned, the following nodes are copied to prevent + * modification to other owner's data. + * + * 'key' is the key which can be followed to find the data. + * 'value' is the data to be inserted or updated. It can be an empty value in which case it is + * equivalent to removing that data from the tree. + */ + std::pair<const_iterator, bool> _upsertWithCopyOnSharedNodes( + Key key, boost::optional<value_type> value) { + + auto node = _root; + std::shared_ptr<Node> parent = nullptr; + const char* keyString = key.data(); + size_t i = 0; + + // Follow the path in the tree as defined by the key string until a non-uniquely owned node. + // This loop would exit at the root if the root itself was shared, or exit at the end in the + // event the entire path was uniquely owned. + for (; i < key.size(); i++) { + + // The current node in the traversal, if unique, will always have two pointers to it, + // not one. This is because the parent node holds it as a child node, but now also we + // have 'node' pointing to it. The loop will exit if the tree node is no longer uniquely + // owned. + if (node.use_count() > 2) + break; + + uint8_t c = static_cast<uint8_t>(keyString[i]); + + if (node->children[c] != nullptr) { + parent = node; + node = node->children[c]; + } else { + node->children[c] = std::make_shared<Node>(c); + parent = node; + node = node->children[c]; + } + } + + std::shared_ptr<Node> old; + + if (i == 0) { + // If the _root node is shared to begin with, copy the _root. This is necessary since + // '_root' is a member variable and must be updated if changed, unlike other inner nodes + // of the tree. + old = _root; + _root = std::make_shared<Node>('\0'); + node = _root; + + } else if (i < key.size()) { + // If there is a shared node in the middle of the tree, backtrack and create a new node + // that is singly owned by this tree. It is necessary to copy all following nodes as + // well. + old = node; + uint8_t c = static_cast<uint8_t>(keyString[i - 1]); + parent->children[c] = std::make_shared<Node>(c); + node = parent->children[c]; + + } else if (i >= key.size()) { + // If all nodes prior to the last node in the traversal are uniquely owned, then set + // 'old' to nullptr to prevent reassigning the node's children. + old = nullptr; + + if (node.use_count() > 2) { + // In the special case in which the to-be modified node (the last node in our + // traversal) is itself the first non-uniquely owned node - copy it and reassign its + // parents and children. + old = node; + uint8_t c = static_cast<uint8_t>(keyString[i - 1]); + parent->children[c] = std::make_shared<Node>(c); + node = parent->children[c]; + } + } + + for (; i < key.size(); i++) { + uint8_t c = static_cast<uint8_t>(keyString[i]); + + if (old != nullptr) { + node->children = old->children; + + if (old->data != boost::none) + node->data.emplace(old->data->first, old->data->second); + + old = old->children[c]; + } + + node->children[c] = std::make_shared<Node>(c); + node = node->children[c]; + } + + if (value != boost::none) { + node->data.emplace(value->first, value->second); + } else { + node->data = boost::none; + } + + // If 'old' isn't a nullptr, add the children since the modified node need not be a leaf in + // the tree. Will only have to do this if the modified node was not uniquely owned, and a + // copy was created. + if (old != nullptr) { + node->children = old->children; + } + + const_iterator it(_root, node.get()); + return std::pair<const_iterator, bool>(it, true); + } + + /** + * This function traverses the tree starting at the provided node using the provided the + * key. It returns the stack which is used in tree traversals for both the forward and + * reverse iterators. Since both iterator classes use this function, it is declared + * statically under RadixStore. + */ + static std::vector<Node*> _buildContext(Key key, Node* node) { + std::vector<Node*> context; + context.push_back(node); + for (const char* it = key.data(); it != key.data() + key.size(); ++it) { + uint8_t c = static_cast<uint8_t>(*it); + node = node->children[c].get(); + context.push_back(node); + } + return context; + } + + std::shared_ptr<Node> _root; + size_type _numElems; + size_type _sizeElems; }; -using StringStore = Store<std::string, std::string>; -} -} +using StringStore = RadixStore<std::string, std::string>; +} // namespace biggie +} // namespace mongo diff --git a/src/mongo/db/storage/biggie/store_test.cpp b/src/mongo/db/storage/biggie/store_test.cpp index 00833e6d517..d194e196a2f 100644 --- a/src/mongo/db/storage/biggie/store_test.cpp +++ b/src/mongo/db/storage/biggie/store_test.cpp @@ -1,5 +1,5 @@ /** - * Copyright 2018 MongoDB Inc. + * Copyright 2017 MongoDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, @@ -26,20 +26,22 @@ * it in the license file. */ -#include "mongo/platform/basic.h" +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault #include "mongo/db/storage/biggie/store.h" - +#include "mongo/platform/basic.h" #include "mongo/unittest/unittest.h" +#include "mongo/util/log.h" +#include <iostream> namespace mongo { namespace biggie { namespace { -using StringStore = Store<std::string, std::string>; +using StringStore = RadixStore<std::string, std::string>; using value_type = StringStore::value_type; -class StoreTest : public unittest::Test { +class RadixStoreTest : public unittest::Test { protected: StringStore thisStore; StringStore otherStore; @@ -47,75 +49,758 @@ protected: StringStore expected; }; -TEST_F(StoreTest, InsertTest) { - value_type value1 = std::make_pair("1", "foo"); - std::pair<StringStore::iterator, bool> res = thisStore.insert(value_type(value1)); +TEST_F(RadixStoreTest, SimpleInsertTest) { + value_type value1 = std::make_pair("food", "1"); + value_type value2 = std::make_pair("foo", "2"); + value_type value3 = std::make_pair("bar", "3"); + + std::pair<StringStore::const_iterator, bool> res = thisStore.insert(value_type(value1)); + ASSERT_EQ(thisStore.size(), StringStore::size_type(1)); ASSERT_TRUE(res.second); ASSERT_TRUE(*res.first == value1); + + res = thisStore.insert(value_type(value2)); + ASSERT_EQ(thisStore.size(), StringStore::size_type(2)); + ASSERT_TRUE(res.second); + ASSERT_TRUE(*res.first == value2); + + res = thisStore.insert(value_type(value3)); + ASSERT_EQ(thisStore.size(), StringStore::size_type(3)); + ASSERT_TRUE(res.second); + ASSERT_TRUE(*res.first == value3); } -TEST_F(StoreTest, EmptyTest) { - value_type value1 = std::make_pair("1", "foo"); - ASSERT_TRUE(thisStore.empty()); +TEST_F(RadixStoreTest, SimpleIteratorAssignmentTest) { + value_type value1 = std::make_pair("food", "1"); + value_type value2 = std::make_pair("foo", "2"); + value_type value3 = std::make_pair("bar", "3"); thisStore.insert(value_type(value1)); - ASSERT_FALSE(thisStore.empty()); + thisStore.insert(value_type(value2)); + thisStore.insert(value_type(value3)); + + otherStore = thisStore; + + StringStore::const_iterator thisIter = thisStore.begin(); + StringStore::const_iterator otherIter = otherStore.begin(); + + ASSERT_TRUE(thisIter == otherIter); + + thisIter = otherStore.begin(); + ASSERT_TRUE(thisIter == otherIter); } -TEST_F(StoreTest, SizeTest) { - value_type value1 = std::make_pair("1", "foo"); - auto expected1 = StringStore::size_type(0); - ASSERT_EQ(thisStore.size(), expected1); +TEST_F(RadixStoreTest, InsertInCopyFromRootTest) { + value_type value1 = std::make_pair("foo", "1"); + value_type value2 = std::make_pair("fod", "2"); + value_type value3 = std::make_pair("fee", "3"); + value_type value4 = std::make_pair("fed", "4"); thisStore.insert(value_type(value1)); - auto expected2 = StringStore::size_type(1); - ASSERT_EQ(thisStore.size(), expected2); + thisStore.insert(value_type(value2)); + thisStore.insert(value_type(value3)); + + otherStore = thisStore; + + std::pair<StringStore::const_iterator, bool> res = otherStore.insert(value_type(value4)); + ASSERT_TRUE(res.second); + + StringStore::const_iterator it1 = thisStore.find(value4.first); + StringStore::const_iterator it2 = otherStore.find(value4.first); + + ASSERT_TRUE(it1 == thisStore.end()); + ASSERT_TRUE(it2 != otherStore.end()); + + StringStore::const_iterator check_this = thisStore.begin(); + StringStore::const_iterator check_other = otherStore.begin(); + + // Only 'otherStore' should have the 'fed' object, whereas thisStore should point to the 'fee' + // node + ASSERT_TRUE(check_other->first == value4.first); + ASSERT_TRUE(check_this->first == value3.first); + check_other++; + + // Both should point to the same "fee" node. + ASSERT_EQUALS(&*check_this, &*check_other); + check_this++; + check_other++; + + // Now both should point to the same "fod" node. + ASSERT_EQUALS(&*check_this, &*check_other); + check_this++; + check_other++; + + // Both should point to the same "foo" node. + ASSERT_EQUALS(&*check_this, &*check_other); + check_this++; + check_other++; + + ASSERT_TRUE(check_this == thisStore.end()); + ASSERT_TRUE(check_other == otherStore.end()); } -TEST_F(StoreTest, ClearTest) { - value_type value1 = std::make_pair("1", "foo"); +TEST_F(RadixStoreTest, InsertInBothCopiesTest) { + value_type value1 = std::make_pair("foo", "1"); + value_type value2 = std::make_pair("fod", "2"); + value_type value3 = std::make_pair("fee", "3"); + value_type value4 = std::make_pair("fed", "4"); thisStore.insert(value_type(value1)); - ASSERT_FALSE(thisStore.empty()); + thisStore.insert(value_type(value2)); - thisStore.clear(); - ASSERT_TRUE(thisStore.empty()); + otherStore = thisStore; + + thisStore.insert(value_type(value3)); + otherStore.insert(value_type(value4)); + + StringStore::const_iterator check_this = thisStore.find(value4.first); + StringStore::const_iterator check_other = otherStore.find(value3.first); + + // 'thisStore' should not have value4 and 'otherStore' should not have value3. + ASSERT_TRUE(check_this == thisStore.end()); + ASSERT_TRUE(check_other == otherStore.end()); + + check_this = thisStore.begin(); + check_other = otherStore.begin(); + + // Only 'otherStore' should have the 'fed' object, whereas thisStore should point to the 'fee' + // node + ASSERT_TRUE(check_this->first == value3.first); + ASSERT_TRUE(check_other->first == value4.first); + check_other++; + check_this++; + + // Now both should point to the same "fod" node. + ASSERT_EQUALS(&*check_this, &*check_other); + check_this++; + check_other++; + + // Both should point to the same "foo" node. + ASSERT_EQUALS(&*check_this, &*check_other); + check_this++; + check_other++; + + ASSERT_TRUE(check_this == thisStore.end()); + ASSERT_TRUE(check_other == otherStore.end()); } -TEST_F(StoreTest, EraseTest) { - value_type value1 = std::make_pair("1", "foo"); - value_type value2 = std::make_pair("2", "bar"); +TEST_F(RadixStoreTest, InsertTwiceInCopyTest) { + value_type value1 = std::make_pair("foo", "1"); + value_type value2 = std::make_pair("fod", "2"); + value_type value3 = std::make_pair("fee", "3"); + value_type value4 = std::make_pair("fed", "4"); + value_type value5 = std::make_pair("food", "5"); thisStore.insert(value_type(value1)); thisStore.insert(value_type(value2)); - auto expected1 = StringStore::size_type(2); - ASSERT_EQ(thisStore.size(), expected1); + thisStore.insert(value_type(value3)); - thisStore.erase(value1.first); - auto expected2 = StringStore::size_type(1); - ASSERT_EQ(thisStore.size(), expected2); + otherStore = thisStore; - thisStore.erase("3"); - ASSERT_EQ(thisStore.size(), expected2); + otherStore.insert(value_type(value4)); + otherStore.insert(value_type(value5)); + + StringStore::const_iterator check_this = thisStore.begin(); + StringStore::const_iterator check_other = otherStore.begin(); + + // Only 'otherStore' should have the 'fed' object, whereas thisStore should point to the 'fee' + // node + ASSERT_TRUE(check_other->first == value4.first); + ASSERT_TRUE(check_this->first == value3.first); + check_other++; + + // Both should point to the same "fee" node. + ASSERT_EQUALS(&*check_this, &*check_other); + check_this++; + check_other++; + + // Now both should point to the same "fod" node. + ASSERT_EQUALS(&*check_this, &*check_other); + check_this++; + check_other++; + + // Both should point to "foo", but they should be different objects + ASSERT_TRUE(check_this->first == value1.first); + ASSERT_TRUE(check_other->first == value1.first); + ASSERT_TRUE(&*check_this != &*check_other); + check_this++; + check_other++; + + ASSERT_TRUE(check_this == thisStore.end()); + ASSERT_TRUE(check_other->first == value5.first); + check_other++; + + ASSERT_TRUE(check_other == otherStore.end()); } -TEST_F(StoreTest, FindTest) { - value_type value1 = std::make_pair("1", "foo"); - value_type value2 = std::make_pair("2", "bar"); +TEST_F(RadixStoreTest, InsertInBranchWithSharedChildrenTest) { + value_type value1 = std::make_pair("foo", "1"); + value_type value2 = std::make_pair("fod", "2"); + value_type value3 = std::make_pair("fee", "3"); + value_type value4 = std::make_pair("fed", "4"); + value_type value5 = std::make_pair("feed", "5"); + + thisStore.insert(value_type(value1)); + thisStore.insert(value_type(value2)); + thisStore.insert(value_type(value3)); + + otherStore = thisStore; + + otherStore.insert(value_type(value4)); + otherStore.insert(value_type(value5)); + + StringStore::const_iterator check_this = thisStore.begin(); + StringStore::const_iterator check_other = otherStore.begin(); + + // Only 'otherStore' should have the 'fed' object, whereas thisStore should point to the 'fee' + // node + ASSERT_TRUE(check_other->first == value4.first); + ASSERT_TRUE(check_this->first == value3.first); + check_other++; + + // Both should point to "fee", but they should be different objects + ASSERT_TRUE(check_this->first == value3.first); + ASSERT_TRUE(check_other->first == value3.first); + ASSERT_TRUE(&*check_this != &*check_other); + check_this++; + check_other++; + + // Only 'otherStore' should have the 'feed' object + ASSERT_TRUE(check_other->first == value5.first); + check_other++; + + // Now both should point to the same "fod" node. + ASSERT_EQUALS(&*check_this, &*check_other); + check_this++; + check_other++; + + // Now both should point to the same "foo" node. + ASSERT_EQUALS(&*check_this, &*check_other); + check_this++; + check_other++; + + ASSERT_TRUE(check_this == thisStore.end()); + ASSERT_TRUE(check_other == otherStore.end()); +} + +TEST_F(RadixStoreTest, InsertNonLeafNodeInBranchWithSharedChildrenTest) { + value_type value1 = std::make_pair("fed", "1"); + value_type value2 = std::make_pair("fee", "2"); + value_type value3 = std::make_pair("feed", "3"); + value_type value4 = std::make_pair("fod", "4"); + value_type value5 = std::make_pair("foo", "5"); + + thisStore.insert(value_type(value3)); + thisStore.insert(value_type(value4)); + thisStore.insert(value_type(value5)); + + otherStore = thisStore; + + otherStore.insert(value_type(value1)); + otherStore.insert(value_type(value2)); + + StringStore::const_iterator check_this = thisStore.begin(); + StringStore::const_iterator check_other = otherStore.begin(); + + // Only 'otherStore' should have the 'fed' object, whereas thisStore should point to the 'feed' + // node + ASSERT_TRUE(check_other->first == value1.first); + ASSERT_TRUE(check_this->first == value3.first); + check_other++; + + // Only 'otherStore' should have the 'fee' object + ASSERT_TRUE(check_other->first == value2.first); + check_other++; + + // Now both should point to the same "feed" node. + ASSERT_EQUALS(&*check_this, &*check_other); + check_this++; + check_other++; + + // Now both should point to the same "fod" node. + ASSERT_EQUALS(&*check_this, &*check_other); + check_this++; + check_other++; + + // Now both should point to the same "foo" node. + ASSERT_EQUALS(&*check_this, &*check_other); + check_this++; + check_other++; + + ASSERT_TRUE(check_this == thisStore.end()); + ASSERT_TRUE(check_other == otherStore.end()); +} + +TEST_F(RadixStoreTest, FindTest) { + value_type value1 = std::make_pair("foo", "1"); + value_type value2 = std::make_pair("bar", "2"); + value_type value3 = std::make_pair("foozeball", "3"); thisStore.insert(value_type(value1)); thisStore.insert(value_type(value2)); - auto expected = StringStore::size_type(2); - ASSERT_EQ(thisStore.size(), expected); + thisStore.insert(value_type(value3)); + ASSERT_EQ(thisStore.size(), StringStore::size_type(3)); - StringStore::iterator iter1 = thisStore.find(value1.first); + StringStore::const_iterator iter1 = thisStore.find(value1.first); + ASSERT_FALSE(iter1 == thisStore.end()); ASSERT_TRUE(*iter1 == value1); - StringStore::iterator iter2 = thisStore.find("3"); + StringStore::const_iterator iter2 = thisStore.find("fooze"); ASSERT_TRUE(iter2 == thisStore.end()); } -TEST_F(StoreTest, DataSizeTest) { +TEST_F(RadixStoreTest, UpdateTest) { + value_type value1 = std::make_pair("foo", "1"); + value_type value2 = std::make_pair("bar", "2"); + value_type value3 = std::make_pair("foz", "3"); + value_type update = std::make_pair("foo", "test"); + + thisStore.insert(value_type(value1)); + thisStore.insert(value_type(value2)); + thisStore.insert(value_type(value3)); + + StringStore copy(thisStore); + thisStore.update(value_type(update)); + + StringStore::const_iterator it2 = thisStore.begin(); + StringStore::const_iterator copy_it2 = copy.begin(); + + // both should point to the same 'bar' object + ASSERT_EQ(&*it2, &*copy_it2); + it2++; + copy_it2++; + + // the 'foo' object should be different + ASSERT_TRUE(it2->second == "test"); + ASSERT_TRUE(copy_it2->second != "test"); + ASSERT_TRUE(&*copy_it2 != &*it2); + it2++; + copy_it2++; + + ASSERT_EQ(&*it2, &*copy_it2); + it2++; + copy_it2++; + + ASSERT_TRUE(copy_it2 == copy.end()); + ASSERT_TRUE(it2 == thisStore.end()); +} + +TEST_F(RadixStoreTest, UpdateLeafOnSharedNodeTest) { + value_type value1 = std::make_pair("foo", "1"); + value_type value2 = std::make_pair("bar", "2"); + value_type value3 = std::make_pair("fool", "3"); + value_type upd = std::make_pair("fool", "test"); + + thisStore.insert(value_type(value1)); + thisStore.insert(value_type(value2)); + thisStore.insert(value_type(value3)); + + StringStore copy(thisStore); + thisStore.update(value_type(upd)); + + StringStore::const_iterator it2 = thisStore.begin(); + StringStore::const_iterator copy_it2 = copy.begin(); + + // both should point to the same 'bar' object + ASSERT_EQ(&*it2, &*copy_it2); + it2++; + copy_it2++; + + // the 'foo' object should be different but have the same value. This is due to the node being + // copied since 'fool' was updated + ASSERT_TRUE(it2->second == "1"); + ASSERT_TRUE(copy_it2->second == "1"); + ASSERT_TRUE(&*copy_it2 != &*it2); + it2++; + copy_it2++; + + // the 'fool' object should be different + ASSERT_TRUE(it2->second == "test"); + ASSERT_TRUE(copy_it2->second != "test"); + ASSERT_TRUE(&*copy_it2 != &*it2); + it2++; + copy_it2++; + + ASSERT_TRUE(copy_it2 == copy.end()); + ASSERT_TRUE(it2 == thisStore.end()); +} + +TEST_F(RadixStoreTest, UpdateSharedBranchNonLeafNodeTest) { + value_type value1 = std::make_pair("fee", "1"); + value_type value2 = std::make_pair("fed", "2"); + value_type value3 = std::make_pair("feed", "3"); + value_type value4 = std::make_pair("foo", "4"); + value_type value5 = std::make_pair("fod", "5"); + value_type upd_val = std::make_pair("fee", "6"); + + thisStore.insert(value_type(value4)); + thisStore.insert(value_type(value5)); + thisStore.insert(value_type(value1)); + thisStore.insert(value_type(value3)); + + otherStore = thisStore; + + otherStore.insert(value_type(value2)); + otherStore.update(value_type(upd_val)); + + StringStore::const_iterator check_this = thisStore.begin(); + StringStore::const_iterator check_other = otherStore.begin(); + + // Only 'otherStore' should have the 'fed' object, whereas thisStore should point to the 'fee' + // node + ASSERT_TRUE(check_this->first == value1.first); + ASSERT_TRUE(check_other->first == value2.first); + check_other++; + + // 'thisStore' should point to the old 'fee' object whereas 'otherStore' should point to the + // updated object + ASSERT_TRUE(check_this->first == value1.first); + ASSERT_TRUE(check_this->second == value1.second); + ASSERT_TRUE(check_other->first == value1.first); + ASSERT_TRUE(check_other->second == upd_val.second); + ASSERT_TRUE(&*check_this != &*check_other); + check_this++; + check_other++; + + // Now both should point to the same "feed" node. + ASSERT_EQUALS(&*check_this, &*check_other); + check_this++; + check_other++; + + // Now both should point to the same "fod" node. + ASSERT_EQUALS(&*check_this, &*check_other); + check_this++; + check_other++; + + // Now both should point to the same "foo" node. + ASSERT_EQUALS(&*check_this, &*check_other); + check_this++; + check_other++; + + ASSERT_TRUE(check_this == thisStore.end()); + ASSERT_TRUE(check_other == otherStore.end()); +} + +TEST_F(RadixStoreTest, SimpleEraseTest) { + value_type value1 = std::make_pair("abc", "1"); + value_type value2 = std::make_pair("def", "4"); + value_type value3 = std::make_pair("ghi", "5"); + thisStore.insert(value_type(value1)); + thisStore.insert(value_type(value2)); + thisStore.insert(value_type(value3)); + + ASSERT_EQ(thisStore.size(), StringStore::size_type(3)); + + StringStore::size_type success = thisStore.erase(value1.first); + ASSERT_TRUE(success); + ASSERT_EQ(thisStore.size(), StringStore::size_type(2)); + + auto iter = thisStore.begin(); + ASSERT_TRUE(*iter == value2); + ++iter; + ASSERT_TRUE(*iter == value3); + ++iter; + ASSERT_TRUE(iter == thisStore.end()); + + ASSERT_FALSE(thisStore.erase("jkl")); +} + +TEST_F(RadixStoreTest, EraseNodeWithUniquelyOwnedParent) { + value_type value1 = std::make_pair("foo", "1"); + value_type value2 = std::make_pair("fod", "2"); + value_type value3 = std::make_pair("fed", "3"); + value_type value4 = std::make_pair("feed", "4"); + + thisStore.insert(value_type(value1)); + thisStore.insert(value_type(value2)); + thisStore.insert(value_type(value4)); + + otherStore = thisStore; + otherStore.insert(value_type(value3)); + + StringStore::size_type success = otherStore.erase(value4.first); + ASSERT_TRUE(success); + ASSERT_EQ(thisStore.size(), StringStore::size_type(3)); + ASSERT_EQ(otherStore.size(), StringStore::size_type(3)); + + auto this_it = thisStore.begin(); + auto other_it = otherStore.begin(); + + // 'thisStore' should still have the 'feed' object whereas 'otherStore' should point to the + // 'fed' object. + ASSERT_TRUE(this_it->first == value4.first); + ASSERT_TRUE(other_it->first == value3.first); + this_it++; + other_it++; + + // 'thisStore' and 'otherStore' should point to the same 'fod' object. + ASSERT_TRUE(&*this_it == &*other_it); + this_it++; + other_it++; + + // 'thisStore' and 'otherStore' should point to the same 'foo' object. + ASSERT_TRUE(&*this_it == &*other_it); + this_it++; + other_it++; + + ASSERT_TRUE(this_it == thisStore.end()); + ASSERT_TRUE(other_it == otherStore.end()); +} + +TEST_F(RadixStoreTest, EraseNodeWithSharedParent) { + value_type value1 = std::make_pair("foo", "1"); + value_type value2 = std::make_pair("fod", "2"); + value_type value5 = std::make_pair("feed", "5"); + + thisStore.insert(value_type(value1)); + thisStore.insert(value_type(value2)); + thisStore.insert(value_type(value5)); + + otherStore = thisStore; + + StringStore::size_type success = otherStore.erase(value5.first); + ASSERT_TRUE(success); + ASSERT_EQ(thisStore.size(), StringStore::size_type(3)); + ASSERT_EQ(otherStore.size(), StringStore::size_type(2)); + + auto this_it = thisStore.begin(); + auto other_it = otherStore.begin(); + + // 'thisStore' should still have the 'feed' object whereas 'otherStore' should point to the + // 'fod' object. + ASSERT_TRUE(this_it->first == value5.first); + ASSERT_TRUE(other_it->first == value2.first); + this_it++; + + // Both iterators should point to the same 'fod' object. + ASSERT_TRUE(&*this_it == &*other_it); + this_it++; + other_it++; + + // 'thisStore' and 'otherStore' should point to the same 'foo' object. + ASSERT_TRUE(&*this_it == &*other_it); + this_it++; + other_it++; + + ASSERT_TRUE(this_it == thisStore.end()); + ASSERT_TRUE(other_it == otherStore.end()); +} + +TEST_F(RadixStoreTest, EraseNonLeafNodeWithSharedParent) { + value_type value1 = std::make_pair("foo", "1"); + value_type value2 = std::make_pair("fod", "2"); + value_type value3 = std::make_pair("fee", "3"); + value_type value5 = std::make_pair("feed", "5"); + + thisStore.insert(value_type(value1)); + thisStore.insert(value_type(value2)); + thisStore.insert(value_type(value3)); + thisStore.insert(value_type(value5)); + + otherStore = thisStore; + + StringStore::size_type success = otherStore.erase(value3.first); + + ASSERT_TRUE(success); + ASSERT_EQ(thisStore.size(), StringStore::size_type(4)); + ASSERT_EQ(otherStore.size(), StringStore::size_type(3)); + + auto this_it = thisStore.begin(); + auto other_it = otherStore.begin(); + + // 'thisStore' should still have the 'fee' object whereas 'otherStore' should point to the + // 'feed' object. + ASSERT_TRUE(this_it->first == value3.first); + ASSERT_TRUE(other_it->first == value5.first); + this_it++; + + // Now both iterators should point to the same 'feed' object. + ASSERT_TRUE(&*this_it == &*other_it); + this_it++; + other_it++; + + // 'thisStore' and 'otherStore' should point to the same 'fod' object. + ASSERT_TRUE(&*this_it == &*other_it); + this_it++; + other_it++; + + // 'thisStore' and 'otherStore' should point to the same 'foo' object. + ASSERT_TRUE(&*this_it == &*other_it); + this_it++; + other_it++; + + ASSERT_TRUE(this_it == thisStore.end()); + ASSERT_TRUE(other_it == otherStore.end()); +} + +TEST_F(RadixStoreTest, ErasePrefixOfAnotherKeyOfCopiedStoreTest) { + std::string prefix = "bar"; + std::string prefix2 = "barrista"; + value_type value1 = std::make_pair(prefix, "1"); + value_type value2 = std::make_pair(prefix2, "2"); + baseStore.insert(value_type(value1)); + baseStore.insert(value_type(value2)); + + thisStore = baseStore; + StringStore::size_type success = thisStore.erase(prefix); + + ASSERT_TRUE(success); + ASSERT_EQ(thisStore.size(), StringStore::size_type(1)); + ASSERT_EQ(baseStore.size(), StringStore::size_type(2)); + StringStore::const_iterator iter = thisStore.find(prefix2); + ASSERT_TRUE(iter != thisStore.end()); + ASSERT_EQ(iter->first, prefix2); +} + +TEST_F(RadixStoreTest, ErasePrefixOfAnotherKeyTest) { + std::string prefix = "bar"; + std::string otherKey = "barrista"; + value_type value1 = std::make_pair(prefix, "2"); + value_type value2 = std::make_pair(otherKey, "3"); + value_type value3 = std::make_pair("foz", "4"); + thisStore.insert(value_type(value1)); + thisStore.insert(value_type(value2)); + thisStore.insert(value_type(value3)); + + ASSERT_EQ(thisStore.size(), StringStore::size_type(3)); + + StringStore::size_type success = thisStore.erase(prefix); + ASSERT_TRUE(success); + ASSERT_EQ(thisStore.size(), StringStore::size_type(2)); + StringStore::const_iterator iter = thisStore.find(otherKey); + ASSERT_TRUE(iter != thisStore.end()); + ASSERT_EQ(iter->first, otherKey); +} + +TEST_F(RadixStoreTest, EraseKeyWithPrefixStillInStoreTest) { + std::string key = "barrista"; + std::string prefix = "bar"; + value_type value1 = std::make_pair(prefix, "2"); + value_type value2 = std::make_pair(key, "3"); + value_type value3 = std::make_pair("foz", "4"); + thisStore.insert(value_type(value1)); + thisStore.insert(value_type(value2)); + thisStore.insert(value_type(value3)); + + ASSERT_EQ(thisStore.size(), StringStore::size_type(3)); + + StringStore::size_type success = thisStore.erase(key); + ASSERT_TRUE(success); + ASSERT_EQ(thisStore.size(), StringStore::size_type(2)); + StringStore::const_iterator iter = thisStore.find(prefix); + ASSERT_FALSE(iter == thisStore.end()); + ASSERT_EQ(iter->first, prefix); +} + +TEST_F(RadixStoreTest, EraseKeyThatOverlapsAnotherKeyTest) { + std::string key = "foo"; + std::string otherKey = "foz"; + value_type value1 = std::make_pair(key, "1"); + value_type value2 = std::make_pair(otherKey, "4"); + value_type value3 = std::make_pair("bar", "5"); + thisStore.insert(value_type(value1)); + thisStore.insert(value_type(value2)); + thisStore.insert(value_type(value3)); + + ASSERT_EQ(thisStore.size(), StringStore::size_type(3)); + + StringStore::size_type success = thisStore.erase(key); + ASSERT_TRUE(success); + ASSERT_EQ(thisStore.size(), StringStore::size_type(2)); + StringStore::const_iterator iter = thisStore.find(otherKey); + ASSERT_FALSE(iter == thisStore.end()); + ASSERT_EQ(iter->first, otherKey); +} + +TEST_F(RadixStoreTest, CopyTest) { + value_type value1 = std::make_pair("foo", "1"); + value_type value2 = std::make_pair("bar", "2"); + value_type value3 = std::make_pair("foz", "3"); + value_type value4 = std::make_pair("baz", "4"); + thisStore.insert(value_type(value1)); + thisStore.insert(value_type(value2)); + thisStore.insert(value_type(value3)); + + StringStore copy(thisStore); + + std::pair<StringStore::const_iterator, bool> ins = copy.insert(value_type(value4)); + StringStore::const_iterator find1 = copy.find(value4.first); + ASSERT_EQ(&*find1, &*ins.first); + + StringStore::const_iterator find2 = thisStore.find(value4.first); + ASSERT_TRUE(find2 == thisStore.end()); + + StringStore::const_iterator iter = thisStore.begin(); + StringStore::const_iterator copy_iter = copy.begin(); + + ASSERT_EQ(&*iter, &*copy_iter); + + iter++; + copy_iter++; + + ASSERT_TRUE(copy_iter->first == "baz"); + + // Node 'baz' should not be in 'thisStore' + ASSERT_FALSE(iter->first == "baz"); + copy_iter++; + + ASSERT_EQ(&*iter, &*copy_iter); + + iter++; + copy_iter++; + ASSERT_EQ(&*iter, &*copy_iter); + + iter++; + copy_iter++; + ASSERT_TRUE(iter == thisStore.end()); + ASSERT_TRUE(copy_iter == copy.end()); +} + +TEST_F(RadixStoreTest, EmptyTest) { + value_type value1 = std::make_pair("1", "foo"); + ASSERT_TRUE(thisStore.empty()); + + thisStore.insert(value_type(value1)); + ASSERT_FALSE(thisStore.empty()); +} + +TEST_F(RadixStoreTest, NumElementsTest) { + value_type value1 = std::make_pair("1", "foo"); + auto expected1 = StringStore::size_type(0); + ASSERT_EQ(thisStore.size(), expected1); + + thisStore.insert(value_type(value1)); + auto expected2 = StringStore::size_type(1); + ASSERT_EQ(thisStore.size(), expected2); +} + +TEST_F(RadixStoreTest, SimpleStoreEqualityTest) { + value_type value1 = std::make_pair("food", "1"); + value_type value2 = std::make_pair("foo", "2"); + value_type value3 = std::make_pair("bar", "3"); + + otherStore.insert(value_type(value1)); + otherStore.insert(value_type(value2)); + otherStore.insert(value_type(value3)); + + thisStore.insert(value_type(value1)); + thisStore.insert(value_type(value2)); + thisStore.insert(value_type(value3)); + + ASSERT_TRUE(otherStore == thisStore); +} + +TEST_F(RadixStoreTest, ClearTest) { + value_type value1 = std::make_pair("1", "foo"); + + thisStore.insert(value_type(value1)); + ASSERT_FALSE(thisStore.empty()); + + thisStore.clear(); + ASSERT_TRUE(thisStore.empty()); +} + +TEST_F(RadixStoreTest, DataSizeTest) { std::string str1 = "foo"; std::string str2 = "bar65"; @@ -126,60 +811,61 @@ TEST_F(StoreTest, DataSizeTest) { ASSERT_EQ(thisStore.dataSize(), str1.size() + str2.size()); } -TEST_F(StoreTest, DistanceTest) { - value_type value1 = std::make_pair("1", "foo"); - value_type value2 = std::make_pair("2", "bar"); - value_type value3 = std::make_pair("3", "foo"); - value_type value4 = std::make_pair("4", "bar"); +TEST_F(RadixStoreTest, DistanceTest) { + value_type value1 = std::make_pair("foo", "1"); + value_type value2 = std::make_pair("bar", "2"); + value_type value3 = std::make_pair("faz", "3"); + value_type value4 = std::make_pair("baz", "4"); thisStore.insert(value_type(value1)); thisStore.insert(value_type(value2)); thisStore.insert(value_type(value3)); thisStore.insert(value_type(value4)); - StringStore::iterator begin = thisStore.begin(); - StringStore::iterator second = thisStore.begin(); + StringStore::const_iterator begin = thisStore.begin(); + StringStore::const_iterator second = thisStore.begin(); ++second; - StringStore::iterator end = thisStore.end(); + StringStore::const_iterator end = thisStore.end(); ASSERT_EQ(thisStore.distance(begin, end), 4); ASSERT_EQ(thisStore.distance(second, end), 3); } -TEST_F(StoreTest, MergeNoModifications) { +TEST_F(RadixStoreTest, MergeNoModifications) { value_type value1 = std::make_pair("1", "foo"); value_type value2 = std::make_pair("2", "bar"); - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - - otherStore.insert(value_type(value1)); - otherStore.insert(value_type(value2)); - baseStore.insert(value_type(value1)); baseStore.insert(value_type(value2)); + thisStore = baseStore; + otherStore = baseStore; + + expected.insert(value_type(value1)); + expected.insert(value_type(value2)); + StringStore merged = thisStore.merge3(baseStore, otherStore); - ASSERT_TRUE(merged == thisStore); + ASSERT_TRUE(merged == expected); } -TEST_F(StoreTest, MergeModifications) { +TEST_F(RadixStoreTest, MergeModifications) { value_type value1 = std::make_pair("1", "foo"); value_type value2 = std::make_pair("1", "bar"); value_type value3 = std::make_pair("3", "baz"); value_type value4 = std::make_pair("3", "faz"); - thisStore.insert(value_type(value2)); - thisStore.insert(value_type(value3)); - - otherStore.insert(value_type(value1)); - otherStore.insert(value_type(value4)); - baseStore.insert(value_type(value1)); baseStore.insert(value_type(value3)); + thisStore = baseStore; + otherStore = baseStore; + + thisStore.update(value_type(value2)); + + otherStore.update(value_type(value4)); + expected.insert(value_type(value2)); expected.insert(value_type(value4)); @@ -188,25 +874,22 @@ TEST_F(StoreTest, MergeModifications) { ASSERT_TRUE(merged == expected); } -TEST_F(StoreTest, MergeDeletions) { +TEST_F(RadixStoreTest, MergeDeletions) { value_type value1 = std::make_pair("1", "foo"); value_type value2 = std::make_pair("2", "moo"); value_type value3 = std::make_pair("3", "bar"); value_type value4 = std::make_pair("4", "baz"); - - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value3)); - thisStore.insert(value_type(value4)); - - otherStore.insert(value_type(value1)); - otherStore.insert(value_type(value2)); - otherStore.insert(value_type(value3)); - baseStore.insert(value_type(value1)); baseStore.insert(value_type(value2)); baseStore.insert(value_type(value3)); baseStore.insert(value_type(value4)); + thisStore = baseStore; + otherStore = baseStore; + + thisStore.erase(value2.first); + otherStore.erase(value4.first); + expected.insert(value_type(value1)); expected.insert(value_type(value3)); @@ -215,23 +898,21 @@ TEST_F(StoreTest, MergeDeletions) { ASSERT_TRUE(merged == expected); } -TEST_F(StoreTest, MergeInsertions) { +TEST_F(RadixStoreTest, MergeInsertions) { value_type value1 = std::make_pair("1", "foo"); value_type value2 = std::make_pair("2", "foo"); value_type value3 = std::make_pair("3", "bar"); value_type value4 = std::make_pair("4", "faz"); - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - thisStore.insert(value_type(value4)); - - otherStore.insert(value_type(value1)); - otherStore.insert(value_type(value2)); - otherStore.insert(value_type(value3)); - baseStore.insert(value_type(value1)); baseStore.insert(value_type(value2)); + thisStore = baseStore; + otherStore = baseStore; + + thisStore.insert(value_type(value4)); + otherStore.insert(value_type(value3)); + expected.insert(value_type(value1)); expected.insert(value_type(value2)); expected.insert(value_type(value3)); @@ -242,9 +923,12 @@ TEST_F(StoreTest, MergeInsertions) { ASSERT_TRUE(merged == expected); } -TEST_F(StoreTest, MergeEmptyInsertionOther) { +TEST_F(RadixStoreTest, MergeEmptyInsertionOther) { value_type value1 = std::make_pair("1", "foo"); + thisStore = baseStore; + otherStore = baseStore; + otherStore.insert(value_type(value1)); StringStore merged = thisStore.merge3(baseStore, otherStore); @@ -252,10 +936,12 @@ TEST_F(StoreTest, MergeEmptyInsertionOther) { ASSERT_TRUE(merged == otherStore); } -TEST_F(StoreTest, MergeEmptyInsertionThis) { +TEST_F(RadixStoreTest, MergeEmptyInsertionThis) { value_type value1 = std::make_pair("1", "foo"); - StringStore thisStore; + thisStore = baseStore; + otherStore = baseStore; + thisStore.insert(value_type(value1)); StringStore merged = thisStore.merge3(baseStore, otherStore); @@ -263,7 +949,7 @@ TEST_F(StoreTest, MergeEmptyInsertionThis) { ASSERT_TRUE(merged == thisStore); } -TEST_F(StoreTest, MergeInsertionDeletionModification) { +TEST_F(RadixStoreTest, MergeInsertionDeletionModification) { value_type value1 = std::make_pair("1", "foo"); value_type value2 = std::make_pair("2", "baz"); value_type value3 = std::make_pair("3", "bar"); @@ -273,21 +959,22 @@ TEST_F(StoreTest, MergeInsertionDeletionModification) { value_type value7 = std::make_pair("1", "modified"); value_type value8 = std::make_pair("2", "modified2"); - thisStore.insert(value_type(value7)); - thisStore.insert(value_type(value2)); - thisStore.insert(value_type(value3)); - thisStore.insert(value_type(value5)); - - otherStore.insert(value_type(value1)); - otherStore.insert(value_type(value8)); - otherStore.insert(value_type(value4)); - otherStore.insert(value_type(value6)); - baseStore.insert(value_type(value1)); baseStore.insert(value_type(value2)); baseStore.insert(value_type(value3)); baseStore.insert(value_type(value4)); + thisStore = baseStore; + otherStore = baseStore; + + thisStore.update(value_type(value7)); + thisStore.erase(value4.first); + thisStore.insert(value_type(value5)); + + otherStore.update(value_type(value8)); + otherStore.erase(value3.first); + otherStore.insert(value_type(value6)); + expected.insert(value_type(value7)); expected.insert(value_type(value8)); expected.insert(value_type(value5)); @@ -298,46 +985,59 @@ TEST_F(StoreTest, MergeInsertionDeletionModification) { ASSERT_TRUE(merged == expected); } -TEST_F(StoreTest, MergeConflictingModifications) { +TEST_F(RadixStoreTest, MergeConflictingModifications) { value_type value1 = std::make_pair("1", "foo"); value_type value2 = std::make_pair("1", "bar"); value_type value3 = std::make_pair("1", "baz"); - thisStore.insert(value_type(value2)); + baseStore.insert(value_type(value1)); - otherStore.insert(value_type(value3)); + thisStore = baseStore; + otherStore = baseStore; - baseStore.insert(value_type(value1)); + thisStore.update(value_type(value2)); + + otherStore.update(value_type(value3)); ASSERT_THROWS(thisStore.merge3(baseStore, otherStore), merge_conflict_exception); } -TEST_F(StoreTest, MergeConflictingModifictionOtherAndDeletionThis) { +TEST_F(RadixStoreTest, MergeConflictingModifictionOtherAndDeletionThis) { value_type value1 = std::make_pair("1", "foo"); value_type value2 = std::make_pair("1", "bar"); - otherStore.insert(value_type(value2)); - baseStore.insert(value_type(value1)); + thisStore = baseStore; + otherStore = baseStore; + thisStore.erase(value1.first); + otherStore.update(value_type(value2)); ASSERT_THROWS(thisStore.merge3(baseStore, otherStore), merge_conflict_exception); } -TEST_F(StoreTest, MergeConflictingModifictionThisAndDeletionOther) { +TEST_F(RadixStoreTest, MergeConflictingModifictionThisAndDeletionOther) { value_type value1 = std::make_pair("1", "foo"); value_type value2 = std::make_pair("1", "bar"); - thisStore.insert(value_type(value2)); - baseStore.insert(value_type(value1)); + thisStore = baseStore; + otherStore = baseStore; + + thisStore.update(value_type(value2)); + + otherStore.erase(value1.first); + ASSERT_THROWS(thisStore.merge3(baseStore, otherStore), merge_conflict_exception); } -TEST_F(StoreTest, MergeConflictingInsertions) { +TEST_F(RadixStoreTest, MergeConflictingInsertions) { value_type value1 = std::make_pair("1", "foo"); value_type value2 = std::make_pair("1", "foo"); + thisStore = baseStore; + otherStore = baseStore; + thisStore.insert(value_type(value2)); otherStore.insert(value_type(value1)); @@ -345,58 +1045,206 @@ TEST_F(StoreTest, MergeConflictingInsertions) { ASSERT_THROWS(thisStore.merge3(baseStore, otherStore), merge_conflict_exception); } -TEST_F(StoreTest, UpperBoundTest) { - value_type value1 = std::make_pair("1", "foo"); - value_type value2 = std::make_pair("2", "bar"); - value_type value3 = std::make_pair("3", "foo"); - value_type value4 = std::make_pair("5", "bar"); +TEST_F(RadixStoreTest, UpperBoundTest) { + value_type value1 = std::make_pair("foo", "1"); + value_type value2 = std::make_pair("bar", "2"); + value_type value3 = std::make_pair("baz", "3"); + value_type value4 = std::make_pair("fools", "4"); thisStore.insert(value_type(value1)); thisStore.insert(value_type(value2)); thisStore.insert(value_type(value3)); thisStore.insert(value_type(value4)); - StringStore::iterator iter1 = thisStore.upper_bound(value2.first); - ASSERT_EQ(iter1->first, "3"); - StringStore::iterator iter2 = thisStore.upper_bound(value4.first); - ASSERT_TRUE(iter2 == thisStore.end()); + StringStore::const_iterator iter = thisStore.upper_bound(value2.first); + ASSERT_EQ(iter->first, "baz"); + + iter = thisStore.upper_bound(value4.first); + ASSERT_TRUE(iter == thisStore.end()); + + iter = thisStore.upper_bound("baa"); + ASSERT_EQ(iter->first, "bar"); } -TEST_F(StoreTest, LowerBoundTest) { - value_type value1 = std::make_pair("1", "foo"); - value_type value2 = std::make_pair("2", "bar"); - value_type value3 = std::make_pair("3", "foo"); - value_type value4 = std::make_pair("5", "bar"); +TEST_F(RadixStoreTest, LowerBoundTest) { + value_type value1 = std::make_pair("baa", "1"); + value_type value2 = std::make_pair("bad", "2"); + value_type value3 = std::make_pair("foo", "3"); + value_type value4 = std::make_pair("fools", "4"); + thisStore.insert(value_type(value3)); thisStore.insert(value_type(value1)); thisStore.insert(value_type(value2)); - thisStore.insert(value_type(value3)); thisStore.insert(value_type(value4)); - StringStore::iterator iter1 = thisStore.lower_bound(value2.first); - ASSERT_EQ(iter1->first, "2"); - StringStore::iterator iter2 = thisStore.lower_bound("7"); - ASSERT_TRUE(iter2 == thisStore.end()); + StringStore::const_iterator iter = thisStore.lower_bound(value1.first); + ASSERT_EQ(iter->first, value1.first); + + ++iter; + ASSERT_EQ(iter->first, value2.first); + + iter = thisStore.lower_bound("baz"); + ASSERT_TRUE(iter == thisStore.find("foo")); } -TEST_F(StoreTest, ReverseIteratorTest) { - value_type value1 = std::make_pair("1", "foo"); - value_type value2 = std::make_pair("2", "bar"); - value_type value3 = std::make_pair("3", "foo"); - value_type value4 = std::make_pair("4", "bar"); +TEST_F(RadixStoreTest, ReverseIteratorTest) { + value_type value1 = std::make_pair("foo", "3"); + value_type value2 = std::make_pair("bar", "1"); + value_type value3 = std::make_pair("baz", "2"); + value_type value4 = std::make_pair("fools", "5"); + value_type value5 = std::make_pair("foods", "4"); thisStore.insert(value_type(value4)); + thisStore.insert(value_type(value5)); thisStore.insert(value_type(value1)); thisStore.insert(value_type(value3)); thisStore.insert(value_type(value2)); - int cur = 4; + int cur = 5; for (auto iter = thisStore.rbegin(); iter != thisStore.rend(); ++iter) { - ASSERT_EQ(iter->first, std::to_string(cur)); + ASSERT_EQ(iter->second, std::to_string(cur)); --cur; } ASSERT_EQ(cur, 0); } + +TEST_F(RadixStoreTest, ReverseIteratorFromForwardIteratorTest) { + value_type value1 = std::make_pair("foo", "3"); + value_type value2 = std::make_pair("bar", "1"); + value_type value3 = std::make_pair("baz", "2"); + value_type value4 = std::make_pair("fools", "5"); + value_type value5 = std::make_pair("foods", "4"); + + thisStore.insert(value_type(value4)); + thisStore.insert(value_type(value5)); + thisStore.insert(value_type(value1)); + thisStore.insert(value_type(value3)); + thisStore.insert(value_type(value2)); + + int cur = 1; + auto iter = thisStore.begin(); + while (iter != thisStore.end()) { + ASSERT_EQ(iter->second, std::to_string(cur)); + ++cur; + ++iter; + } + + ASSERT_EQ(cur, 6); + ASSERT_TRUE(iter == thisStore.end()); + + --cur; + // This should create a reverse iterator that starts at the very beginning (for the reverse + // iterator). + StringStore::const_reverse_iterator riter(iter); + while (riter != thisStore.rend()) { + ASSERT_EQ(riter->second, std::to_string(cur)); + --cur; + ++riter; + } + + ASSERT_EQ(cur, 0); +} + +TEST_F(RadixStoreTest, ReverseIteratorFromMiddleOfForwardIteratorTest) { + value_type value1 = std::make_pair("foo", "3"); + value_type value2 = std::make_pair("bar", "1"); + value_type value3 = std::make_pair("baz", "2"); + value_type value4 = std::make_pair("fools", "5"); + value_type value5 = std::make_pair("foods", "4"); + + thisStore.insert(value_type(value4)); + thisStore.insert(value_type(value5)); + thisStore.insert(value_type(value1)); + thisStore.insert(value_type(value3)); + thisStore.insert(value_type(value2)); + + int cur = 3; + auto iter = thisStore.begin(); + ++iter; + ++iter; + ++iter; + + // This should create a reverse iterator that starts at the node 'foo' + StringStore::const_reverse_iterator riter(iter); + while (riter != thisStore.rend()) { + ASSERT_EQ(riter->second, std::to_string(cur)); + --cur; + ++riter; + } + + ASSERT_EQ(cur, 0); +} + +TEST_F(RadixStoreTest, ReverseIteratorCopyConstructorTest) { + value_type value1 = std::make_pair("foo", "3"); + value_type value2 = std::make_pair("bar", "1"); + value_type value3 = std::make_pair("baz", "2"); + value_type value4 = std::make_pair("fools", "5"); + value_type value5 = std::make_pair("foods", "4"); + + thisStore.insert(value_type(value4)); + thisStore.insert(value_type(value5)); + thisStore.insert(value_type(value1)); + thisStore.insert(value_type(value3)); + thisStore.insert(value_type(value2)); + + int cur = 3; + auto iter = thisStore.begin(); + auto iter2(iter); + ASSERT_EQ(&*iter, &*iter2); + + ++iter; + ++iter2; + ASSERT_EQ(&*iter, &*iter2); + + ++iter; + ++iter2; + ASSERT_EQ(&*iter, &*iter2); + + ++iter; + ++iter2; + ASSERT_EQ(&*iter, &*iter2); + + // This should create a reverse iterator that starts at the node 'foo' + StringStore::const_reverse_iterator riter(iter); + StringStore::const_reverse_iterator riter2(riter); + while (riter != thisStore.rend()) { + ASSERT_EQ(&*riter, &*riter2); + --cur; + ++riter; + ++riter2; + } + + ASSERT_EQ(cur, 0); +} + +TEST_F(RadixStoreTest, ReverseIteratorAssignmentOpTest) { + value_type value1 = std::make_pair("foo", "3"); + value_type value2 = std::make_pair("bar", "1"); + value_type value3 = std::make_pair("baz", "2"); + value_type value4 = std::make_pair("fools", "5"); + value_type value5 = std::make_pair("foods", "4"); + + thisStore.insert(value_type(value4)); + thisStore.insert(value_type(value5)); + thisStore.insert(value_type(value1)); + thisStore.insert(value_type(value3)); + thisStore.insert(value_type(value2)); + + int cur = 5; + auto iter = thisStore.begin(); + + StringStore::const_reverse_iterator riter(iter); + riter = thisStore.rbegin(); + while (riter != thisStore.rend()) { + ASSERT_EQ(riter->second, std::to_string(cur)); + --cur; + ++riter; + } + + ASSERT_EQ(cur, 0); +} + } // namespace -} // namespace biggie -} // namespace mongo +} // mongo namespace +} // biggie namespace |