diff options
author | Gregory Noma <gregory.noma@gmail.com> | 2022-04-26 10:20:27 -0400 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-04-26 22:48:08 +0000 |
commit | e1f267e15c92f48580bd77a6138fc4cc09be6913 (patch) | |
tree | b9d429adc687e6aef122a5d53eb22d1938f48ab6 /src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store.h | |
parent | f6134fa0277a7c73580735b42c69f43ee7b0d11c (diff) | |
download | mongo-e1f267e15c92f48580bd77a6138fc4cc09be6913.tar.gz |
SERVER-65151 Remove `ephemeralForTest`
Diffstat (limited to 'src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store.h')
-rw-r--r-- | src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store.h | 2507 |
1 files changed, 0 insertions, 2507 deletions
diff --git a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store.h b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store.h deleted file mode 100644 index c78498393b2..00000000000 --- a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store.h +++ /dev/null @@ -1,2507 +0,0 @@ -/** - * Copyright (C) 2018-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#pragma once - -#include <array> -#include <boost/intrusive_ptr.hpp> -#include <boost/optional.hpp> -#include <cstring> -#include <exception> -#include <iostream> -#include <memory> -#include <string.h> -#include <vector> - -#include "mongo/platform/atomic_word.h" -#include "mongo/util/assert_util.h" - -namespace mongo { -namespace ephemeral_for_test { - - -class merge_conflict_exception : std::exception { - virtual const char* what() const noexcept { - return "conflicting changes prevent successful merge"; - } -}; - -struct Metrics { - AtomicWord<int64_t> totalMemory{0}; - AtomicWord<int32_t> totalNodes{0}; - AtomicWord<int32_t> totalChildren{0}; - - void addMemory(size_t size) { - totalMemory.fetchAndAdd(size); - } - - void subtractMemory(size_t size) { - totalMemory.fetchAndSubtract(size); - } -}; -enum class NodeType : uint8_t { LEAF, NODE4, NODE16, NODE48, NODE256 }; - -/** - * 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 RadixStore { - class Node; - class Head; - - friend class RadixStoreTest; - -public: - using mapped_type = T; - using value_type = std::pair<const Key, mapped_type>; - using allocator_type = std::allocator<value_type>; - 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 difference_type = std::ptrdiff_t; - using uint8_t = std::uint8_t; - using node_ptr = boost::intrusive_ptr<Node>; - using head_ptr = boost::intrusive_ptr<Head>; - - static constexpr uint8_t maxByte = 255; - - template <class pointer_type, class reference_type> - class radix_iterator { - friend class RadixStore; - - public: - using iterator_category = std::forward_iterator_tag; - using value_type = typename RadixStore::value_type; - using difference_type = std::ptrdiff_t; - using pointer = pointer_type; - using reference = reference_type; - - radix_iterator() : _root(nullptr), _current(nullptr) {} - - ~radix_iterator() { - updateTreeView(/*stopIfMultipleCursors=*/true); - } - - radix_iterator(const radix_iterator& originalIterator) { - _root = originalIterator._root; - _current = originalIterator._current; - } - - radix_iterator& operator=(const radix_iterator& originalIterator) { - if (this != &originalIterator) { - _root = originalIterator._root; - _current = originalIterator._current; - } - return *this; - } - - radix_iterator& operator=(radix_iterator&& originalIterator) { - if (this != &originalIterator) { - _root = std::move(originalIterator._root); - _current = std::move(originalIterator._current); - } - return *this; - } - - radix_iterator& operator++() { - repositionIfChanged(); - _findNext(); - return *this; - } - - radix_iterator operator++(int) { - repositionIfChanged(); - radix_iterator old = *this; - ++*this; - return old; - } - - bool operator==(const radix_iterator& other) const { - repositionIfChanged(); - return _current == other._current; - } - - bool operator!=(const radix_iterator& other) const { - repositionIfChanged(); - return _current != other._current; - } - - reference operator*() { - repositionIfChanged(); - return *(_current->_data); - } - - const_pointer operator->() { - repositionIfChanged(); - return &*(_current->_data); - } - - /** - * Attempts to restore the iterator on its former position in the updated tree if the tree - * has changed. - * - * If the former position has been erased, the iterator finds the next node. It is - * possible that no next node is available, so at that point the cursor is exhausted and - * points to the end. - */ - void repositionIfChanged() const { - if (!_current || !_root->_nextVersion) - return; - - invariant(_current->_data); - - // Copy the key from _current before we move our _root reference. - auto key = _current->_data->first; - - updateTreeView(); - RadixStore store(*_root); - - // Find the same or next node in the updated tree. - _current = store.lower_bound(key)._current; - } - - /* - * Returns a reference to the current key without checking whether the current position has - * been erased. - */ - pointer currentRaw() const { - if (!_current) { - return nullptr; - } - return &(*_current->_data); - } - - private: - radix_iterator(const head_ptr& root) : _root(root), _current(nullptr) {} - - radix_iterator(const head_ptr& 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. - const 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 its 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.front(); - 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. - bool res = _forEachChild(node, oldKey + 1, false, [this](node_ptr child) { - // If the current node has data, return it and exit. If not, find the - // left-most node with data in this sub-tree. - if (child->_data) { - _current = child.get(); - return false; - } - - _current = child.get(); - _traverseLeftSubtree(); - return false; - }); - if (!res) { - 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 { - _forEachChild(_current, 0, false, [this](node_ptr child) { - _current = child.get(); - return false; - }); - } while (!_current->_data); - } - - void updateTreeView(bool stopIfMultipleCursors = false) const { - while (_root && _root->_nextVersion) { - if (stopIfMultipleCursors && _root->refCount() > 1) - return; - - bool clearPreviousFlag = _root->refCount() == 1; - _root = _root->_nextVersion; - if (clearPreviousFlag) - _root->_hasPreviousVersion = false; - } - } - - // "_root" is a pointer to the root of the tree over which this is iterating. - mutable head_ptr _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. - mutable 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 = typename 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; - _traverseRightSubtree(); - } else { - _findNextReverse(); - } - } - - ~reverse_radix_iterator() { - updateTreeView(/*stopIfMultipleCursors=*/true); - } - - reverse_radix_iterator(const reverse_radix_iterator& originalIterator) { - _root = originalIterator._root; - _current = originalIterator._current; - } - - reverse_radix_iterator& operator=(const reverse_radix_iterator& originalIterator) { - if (this != &originalIterator) { - _root = originalIterator._root; - _current = originalIterator._current; - } - return *this; - } - - reverse_radix_iterator& operator=(reverse_radix_iterator&& originalIterator) { - if (this != &originalIterator) { - _root = std::move(originalIterator._root); - _current = std::move(originalIterator._current); - } - return *this; - } - - reverse_radix_iterator& operator++() { - repositionIfChanged(); - _findNextReverse(); - return *this; - } - - reverse_radix_iterator operator++(int) { - repositionIfChanged(); - reverse_radix_iterator old = *this; - ++*this; - return old; - } - - bool operator==(const reverse_radix_iterator& other) const { - repositionIfChanged(); - return _current == other._current; - } - - bool operator!=(const reverse_radix_iterator& other) const { - repositionIfChanged(); - return _current != other._current; - } - - reference operator*() { - repositionIfChanged(); - return *(_current->_data); - } - - const_pointer operator->() { - repositionIfChanged(); - return &*(_current->_data); - } - - /** - * Attempts to restore the iterator on its former position in the updated tree if the tree - * has changed. - * - * If the former position has been erased, the iterator finds the next node. It is - * possible that no next node is available, so at that point the cursor is exhausted and - * points to the end. - */ - void repositionIfChanged() const { - if (!_current || !_root->_nextVersion) - return; - - invariant(_current->_data); - - // Copy the key from _current before we move our _root reference. - auto key = _current->_data->first; - - updateTreeView(); - RadixStore store(*_root); - - // Find the same or next node in the updated tree. - const_iterator it = store.lower_bound(key); - - // Couldn't find any nodes with key greater than currentKey in lower_bound(). - // So make _current point to the beginning, since rbegin() will point to the - // previous node before key. - if (!it._current) - _current = store.rbegin()._current; - else { - _current = it._current; - // lower_bound(), moved us one up in a forwards direction since the currentKey - // didn't exist anymore, move one back. - if (_current->_data->first > key) - _findNextReverse(); - } - } - - /* - * Returns a reference to the current key without checking whether the current position has - * been erased. - */ - pointer currentRaw() const { - if (!_current) { - return nullptr; - } - return &(*_current->_data); - } - - private: - reverse_radix_iterator(const head_ptr& root) : _root(root), _current(nullptr) {} - - reverse_radix_iterator(const head_ptr& root, Node* current) - : _root(root), _current(current) {} - - void _findNextReverse() const { - // 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; - - const 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.front(); - 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. - bool res = _forEachChild(node, oldKey - 1, true, [this](node_ptr child) { - // If there is a sub-tree found, it must have data, therefore it's necessary - // to traverse to the right most node. - _current = child.get(); - _traverseRightSubtree(); - return false; - }); - if (!res) { - 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) { - _current = node; - return; - } - } - } - - void _traverseRightSubtree() const { - // This function traverses the given tree to the right most leaf of the subtree where - // 'current' is the root. - do { - _forEachChild(_current, maxByte, true, [this](node_ptr child) { - _current = child.get(); - return false; - }); - } while (!_current->isLeaf()); - } - - void updateTreeView(bool stopIfMultipleCursors = false) const { - while (_root && _root->_nextVersion) { - if (stopIfMultipleCursors && _root->refCount() > 1) - return; - - bool clearPreviousFlag = _root->refCount() == 1; - _root = _root->_nextVersion; - if (clearPreviousFlag) - _root->_hasPreviousVersion = false; - } - } - - // "_root" is a pointer to the root of the tree over which this is iterating. - mutable head_ptr _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. - mutable Node* _current; - }; - - using reverse_iterator = reverse_radix_iterator<pointer, value_type&>; - using const_reverse_iterator = reverse_radix_iterator<const_pointer, const value_type&>; - - // Constructors - RadixStore() : _root(make_intrusive_node<Head>()) {} - RadixStore(const RadixStore& other) : _root(make_intrusive_node<Head>(*(other._root))) {} - RadixStore(const Head& other) : _root(make_intrusive_node<Head>(other)) {} - - RadixStore(RadixStore&& other) { - _root = std::move(other._root); - } - - RadixStore& operator=(const RadixStore& other) { - if (this != &other) { - _root = make_intrusive_node<Head>(*(other._root)); - } - return *this; - } - - RadixStore& operator=(RadixStore&& other) { - if (this != &other) { - _root = std::move(other._root); - } - return *this; - } - - // Equality - bool operator==(const RadixStore& other) const { - if (_root->_count != other._root->_count || _root->_dataSize != other._root->_dataSize) - return false; - - RadixStore::const_iterator iter = this->begin(); - RadixStore::const_iterator other_iter = other.begin(); - - while (iter != this->end()) { - if (other_iter == other.end() || *iter != *other_iter) { - return false; - } - - iter++; - other_iter++; - } - - return other_iter == other.end(); - } - - bool operator!=(const RadixStore& other) const { - return !(*this == other); - } - - // Capacity - bool empty() const { - // Not relying on size() internally, as it may be updated late. - return _root->isLeaf() && !_root->_data; - } - - size_type size() const { - return _root->_count; - } - - size_type dataSize() const { - return _root->_dataSize; - } - - bool hasBranch() const { - return _root->_nextVersion ? true : false; - } - - // Metrics - static int64_t totalMemory() { - return _metrics.totalMemory.load(); - } - - static int32_t totalNodes() { - return _metrics.totalNodes.load(); - } - - static float averageChildren() { - auto totalNodes = _metrics.totalNodes.load(); - return totalNodes ? _metrics.totalChildren.load() / static_cast<float>(totalNodes) : 0; - } - - // Modifiers - void clear() noexcept { - _root = make_intrusive_node<Head>(); - } - - std::pair<const_iterator, bool> insert(value_type&& value) { - const Key& key = value.first; - - Node* node = _findNode(key); - if (node != nullptr || key.size() == 0) - return std::make_pair(end(), false); - - return _upsertWithCopyOnSharedNodes(key, std::move(value)); - } - - std::pair<const_iterator, bool> update(value_type&& value) { - const Key& key = value.first; - - // Ensure that the item to be updated exists. - auto item = RadixStore::find(key); - if (item == RadixStore::end()) - return std::make_pair(item, false); - - // Setting the same value is a no-op - if (item->second == value.second) - return std::make_pair(item, false); - - return _upsertWithCopyOnSharedNodes(key, std::move(value)); - } - - /** - * Returns whether the key was removed. - */ - bool erase(const Key& key) { - std::vector<std::pair<Node*, bool>> context; - - Node* prev = _root.get(); - int rootUseCount = _root->_hasPreviousVersion ? 2 : 1; - bool isUniquelyOwned = _root->refCount() == rootUseCount; - context.push_back(std::make_pair(prev, isUniquelyOwned)); - - Node* node = nullptr; - - const uint8_t* charKey = reinterpret_cast<const uint8_t*>(key.data()); - size_t depth = prev->_depth + prev->_trieKey.size(); - while (depth < key.size()) { - auto nodePtr = _findChild(prev, charKey[depth]); - node = nodePtr.get(); - if (node == nullptr) { - return false; - } - - // If the prefixes mismatch, this key cannot exist in the tree. - size_t p = _comparePrefix(node->_trieKey, charKey + depth, key.size() - depth); - if (p != node->_trieKey.size()) { - return false; - } - - isUniquelyOwned = isUniquelyOwned && nodePtr->refCount() - 1 == 1; - context.push_back(std::make_pair(node, isUniquelyOwned)); - depth = node->_depth + node->_trieKey.size(); - prev = node; - } - - // Found the node, now remove it. - - Node* deleted = context.back().first; - context.pop_back(); - - // If the to-be deleted node is an internal node without data it is hidden from the user and - // should not be deleted - if (!deleted->_data) - return false; - - if (!deleted->isLeaf()) { - // 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); - return true; - } - - Node* parent = context.at(0).first; - isUniquelyOwned = context.at(0).second; - - if (!isUniquelyOwned) { - invariant(!_root->_nextVersion); - invariant(_root->refCount() > rootUseCount); - _root->_nextVersion = make_intrusive_node<Head>(*_root); - _root = _root->_nextVersion; - _root->_hasPreviousVersion = true; - parent = _root.get(); - } - - size_t sizeOfRemovedData = node->_data->second.size(); - _root->_dataSize -= sizeOfRemovedData; - _root->_count--; - Node* prevParent = nullptr; - for (size_t depth = 1; depth < context.size(); depth++) { - Node* child = context.at(depth).first; - isUniquelyOwned = context.at(depth).second; - - uint8_t childFirstChar = child->_trieKey.front(); - if (!isUniquelyOwned) { - auto childCopy = _copyNode(child); - _setChildPtr(parent, childFirstChar, childCopy); - child = childCopy.get(); - } - - prevParent = parent; - parent = child; - } - - // Handle the deleted node, as it is a leaf. - _setChildPtr(parent, deleted->_trieKey.front(), nullptr); - - // 'parent' may only have one child, in which case we need to evaluate whether or not - // this node is redundant. - if (auto newParent = _compressOnlyChild(prevParent, parent)) { - parent = newParent; - } - - // Don't shrink the root node. - if (prevParent && parent->needShrink()) { - parent = _shrink(prevParent, parent).get(); - } - - return true; - } - - void merge3(const RadixStore& base, const RadixStore& other) { - std::vector<Node*> context; - std::vector<uint8_t> trieKeyIndex; - difference_type deltaCount = _root->_count - base._root->_count; - difference_type deltaDataSize = _root->_dataSize - base._root->_dataSize; - - invariant(this->_root->_trieKey.size() == 0 && base._root->_trieKey.size() == 0 && - other._root->_trieKey.size() == 0); - _merge3Helper( - this->_root.get(), base._root.get(), other._root.get(), context, trieKeyIndex); - _root->_count = other._root->_count + deltaCount; - _root->_dataSize = other._root->_dataSize + deltaDataSize; - } - - // Iterators - const_iterator begin() const noexcept { - if (_root->isLeaf() && !_root->_data) - return end(); - - Node* node = _begin(_root.get()); - return RadixStore::const_iterator(_root, node); - } - - const_reverse_iterator rbegin() const noexcept { - return const_reverse_iterator(end()); - } - - const_iterator end() const noexcept { - return const_iterator(_root); - } - - const_reverse_iterator rend() const noexcept { - return const_reverse_iterator(_root); - } - - const_iterator find(const Key& key) const { - const_iterator it = RadixStore::end(); - - Node* node = _findNode(key); - if (node == nullptr) - return it; - else - return const_iterator(_root, node); - } - - const_iterator lower_bound(const Key& key) const { - Node* node = _root.get(); - const uint8_t* charKey = reinterpret_cast<const uint8_t*>(key.data()); - std::vector<std::pair<Node*, uint8_t>> context; - size_t depth = 0; - - // Traverse the path given the key to see if the node exists. - while (depth < key.size()) { - uint8_t idx = charKey[depth]; - - // When we go back up the tree to search for the lower bound of key, always search to - // the right of 'idx' so that we never search anything less than what the lower bound - // would be. - if (idx != UINT8_MAX) - context.push_back(std::make_pair(node, idx + 1)); - - auto nodePtr = _findChild(node, idx); - if (!nodePtr) - break; - - node = nodePtr.get(); - size_t mismatchIdx = - _comparePrefix(node->_trieKey, charKey + depth, key.size() - depth); - - // There is a prefix mismatch, so we don't need to traverse anymore. - if (mismatchIdx < node->_trieKey.size()) { - // Check if the current key in the tree is greater than the one we are looking - // for since it can't be equal at this point. It can be greater in two ways: - // It can be longer or it can have a larger character at the mismatch index. - uint8_t mismatchChar = charKey[mismatchIdx + depth]; - if (mismatchIdx == key.size() - depth || - node->_trieKey[mismatchIdx] > mismatchChar) { - // If the current key is greater and has a value it is the lower bound. - if (node->_data) - return const_iterator(_root, node); - - // If the current key has no value, place it in the context - // so that we can search its children. - context.push_back(std::make_pair(node, 0)); - } - break; - } - - depth = node->_depth + node->_trieKey.size(); - } - - if (depth == key.size()) { - // If the node exists, then we can just return an iterator to that node. - if (node->_data) - return const_iterator(_root, node); - - // The search key is an exact prefix, so we need to search all of this node's - // children. - context.back() = std::make_pair(node, 0); - } - - // The node with the provided key did not exist. Now we must find the next largest node, if - // it exists. - while (!context.empty()) { - uint8_t idx = 0; - std::tie(node, idx) = context.back(); - context.pop_back(); - - bool exhausted = _forEachChild(node, idx, false, [&node, &context](node_ptr child) { - node = child.get(); - if (!node->_data) { - // Need to search this node's children for the next largest node. - context.push_back(std::make_pair(node, 0)); - } - return false; - }); - if (!exhausted && node->_data) { - // There exists a node with a key larger than the one given. - return const_iterator(_root, node); - } - - if (node->_trieKey.empty() && context.empty()) { - // We have searched the root. There's nothing left to search. - return end(); - } - } - - // There was no node key at least as large as the one given. - return end(); - } - - const_iterator upper_bound(const Key& key) const { - const_iterator it = lower_bound(key); - if (it == end()) - return it; - - if (it->first == key) - return ++it; - - return it; - } - - typename RadixStore::iterator::difference_type distance(iterator iter1, iterator iter2) { - return std::distance(iter1, iter2); - } - - typename RadixStore::iterator::difference_type distance(const_iterator iter1, - const_iterator iter2) { - return std::distance(iter1, iter2); - } - - std::string to_string_for_test() { - return _walkTree(_root.get(), 0); - } - -private: - /* - * The base class of all other node types. - */ - class Node { - friend class RadixStore; - friend class RadixStoreTest; - - public: - Node() { - addNodeMemory(); - } - - Node(NodeType type, std::vector<uint8_t> key) { - _nodeType = type; - _trieKey = key; - addNodeMemory(); - } - - Node(const Node& other) { - _nodeType = other._nodeType; - _numChildren = other._numChildren; - _depth = other._depth; - _trieKey = other._trieKey; - if (other._data) { - addData(other._data.value()); - } - // _refCount is initialized to 0 and not copied from other. - - addNodeMemory(); - } - - Node(Node&& other) - : _nodeType(other._nodeType), - _numChildren(other._numChildren), - _depth(other._depth), - _trieKey(std::move(other._trieKey)), - _data(std::move(other._data)) { - // _refCount is initialized to 0 and not copied from other. - // The move constructor transfers the dynamic memory so only the static memory of Node - // should be added. - addNodeMemory(-static_cast<int64_t>(_trieKey.capacity()) * - static_cast<int64_t>(sizeof(uint8_t))); - } - - Node& operator=(const Node& rhs) = delete; - Node& operator=(Node&& rhs) = delete; - - virtual ~Node() { - subtractNodeMemory(); - } - - bool isLeaf() const { - return !_numChildren; - } - - uint16_t numChildren() const { - return _numChildren; - } - - int refCount() const { - return _refCount.load(); - } - - friend void intrusive_ptr_add_ref(Node* ptr) { - ptr->_refCount.fetchAndAdd(1); - } - - friend void intrusive_ptr_release(Node* ptr) { - if (ptr->_refCount.fetchAndSubtract(1) == 1) { - delete ptr; - } - } - - protected: - NodeType _nodeType = NodeType::LEAF; - uint16_t _numChildren = 0; - unsigned int _depth = 0; - std::vector<uint8_t> _trieKey; - boost::optional<value_type> _data; - AtomicWord<uint32_t> _refCount{0}; - - private: - bool needGrow() const { - switch (_nodeType) { - case NodeType::LEAF: - return true; - case NodeType::NODE4: - return numChildren() == 4; - case NodeType::NODE16: - return numChildren() == 16; - case NodeType::NODE48: - return numChildren() == 48; - case NodeType::NODE256: - return false; - } - MONGO_UNREACHABLE; - } - - bool needShrink() const { - switch (_nodeType) { - case NodeType::LEAF: - return false; - case NodeType::NODE4: - return numChildren() < 1; - case NodeType::NODE16: - return numChildren() < 5; - case NodeType::NODE48: - return numChildren() < 17; - case NodeType::NODE256: - return numChildren() < 49; - } - MONGO_UNREACHABLE; - } - - /* - * Only adds non-empty value and handle the increment on memory metrics. - */ - void addData(value_type value) { - _data.emplace(value.first, value.second); - _metrics.addMemory(value.first.capacity() + value.second.capacity()); - } - - void addNodeMemory(difference_type offset = 0) { - _metrics.totalMemory.fetchAndAdd(sizeof(Node) + _trieKey.capacity() * sizeof(uint8_t) + - offset); - _metrics.totalNodes.fetchAndAdd(1); - } - - void subtractNodeMemory() { - size_t memUsage = sizeof(Node) + _trieKey.capacity() * sizeof(uint8_t); - if (_data) { - memUsage += _data->first.capacity() + _data->second.capacity(); - } - _metrics.totalMemory.fetchAndSubtract(memUsage); - _metrics.totalNodes.fetchAndSubtract(1); - } - }; - - class Node4; - class Node16; - class Node48; - class Node256; - - class NodeLeaf : public Node { - friend class RadixStore; - - public: - NodeLeaf(std::vector<uint8_t> key) : Node(NodeType::LEAF, key) {} - NodeLeaf(const NodeLeaf& other) : Node(other) {} - - NodeLeaf(Node4&& other) : Node(std::move(other)) { - this->_nodeType = NodeType::LEAF; - } - }; - - /* - * Node4 is used when the number of children is in the range of [1, 4]. - */ - class Node4 : public Node { - friend class RadixStore; - - public: - Node4(std::vector<uint8_t> key) : Node(NodeType::NODE4, key) { - std::fill(_childKey.begin(), _childKey.end(), 0); - addNodeMemory(); - } - - Node4(const Node4& other) - : Node(other), _childKey(other._childKey), _children(other._children) { - addNodeMemory(); - } - - /* - * This move constructor should only be used when growing NodeLeaf to Node4. - */ - Node4(NodeLeaf&& other) : Node(std::move(other)) { - this->_nodeType = NodeType::NODE4; - std::fill(_childKey.begin(), _childKey.end(), 0); - addNodeMemory(); - } - - /* - * This move constructor should only be used when shrinking Node16 to Node4. - */ - Node4(Node16&& other) : Node(std::move(other)) { - invariant(other.needShrink()); - this->_nodeType = NodeType::NODE4; - std::fill(_childKey.begin(), _childKey.end(), 0); - for (size_t i = 0; i < other.numChildren(); ++i) { - _childKey[i] = other._childKey[i]; - _children[i] = std::move(other._children[i]); - } - addNodeMemory(); - } - - ~Node4() { - _metrics.subtractMemory(sizeof(Node4) - sizeof(Node)); - _metrics.totalChildren.fetchAndSubtract(_children.size()); - } - - private: - void addNodeMemory() { - _metrics.totalMemory.fetchAndAdd(sizeof(Node4) - sizeof(Node)); - _metrics.totalChildren.fetchAndAdd(_children.size()); - } - - // The first bytes of each child's key is stored in a sorted order. - std::array<uint8_t, 4> _childKey; - std::array<node_ptr, 4> _children; - }; - - /* - * Node16 is used when the number of children is in the range of [5, 16]. - */ - class Node16 : public Node { - friend class RadixStore; - - public: - Node16(std::vector<uint8_t> key) : Node(NodeType::NODE16, key) { - std::fill(_childKey.begin(), _childKey.end(), 0); - addNodeMemory(); - } - - Node16(const Node16& other) - : Node(other), _childKey(other._childKey), _children(other._children) { - addNodeMemory(); - } - - /* - * This move constructor should only be used when growing Node4 to Node16. - */ - Node16(Node4&& other) : Node(std::move(other)) { - invariant(other.needGrow()); - this->_nodeType = NodeType::NODE16; - auto n = other._childKey.size(); - for (size_t i = 0; i < n; ++i) { - _childKey[i] = other._childKey[i]; - _children[i] = std::move(other._children[i]); - } - std::fill(_childKey.begin() + n, _childKey.end(), 0); - addNodeMemory(); - } - - /* - * This move constructor should only be used when shrinking Node48 to Node16. - */ - Node16(Node48&& other) : Node(std::move(other)) { - invariant(other.needShrink()); - this->_nodeType = NodeType::NODE16; - std::fill(_childKey.begin(), _childKey.end(), 0); - size_t cur = 0; - for (size_t i = 0; i < other._childIndex.size(); ++i) { - auto index = other._childIndex[i]; - if (index != maxByte) { - _childKey[cur] = i; - _children[cur++] = std::move(other._children[index]); - } - } - addNodeMemory(); - } - - ~Node16() { - _metrics.subtractMemory(sizeof(Node16) - sizeof(Node)); - _metrics.totalChildren.fetchAndSubtract(_children.size()); - } - - private: - void addNodeMemory() { - _metrics.totalMemory.fetchAndAdd(sizeof(Node16) - sizeof(Node)); - _metrics.totalChildren.fetchAndAdd(_children.size()); - } - - // _childKey is sorted ascendingly. - std::array<uint8_t, 16> _childKey; - std::array<node_ptr, 16> _children; - }; - - /* - * Node48 is used when the number of children is in the range of [17, 48]. - */ - class Node48 : public Node { - friend class RadixStore; - - public: - Node48(std::vector<uint8_t> key) : Node(NodeType::NODE48, key) { - std::fill(_childIndex.begin(), _childIndex.end(), maxByte); - addNodeMemory(); - } - - Node48(const Node48& other) - : Node(other), _childIndex(other._childIndex), _children(other._children) { - addNodeMemory(); - } - - /* - * This move constructor should only be used when growing Node16 to Node48. - */ - Node48(Node16&& other) : Node(std::move(other)) { - invariant(other.needGrow()); - this->_nodeType = NodeType::NODE48; - std::fill(_childIndex.begin(), _childIndex.end(), maxByte); - for (uint8_t i = 0; i < other._children.size(); ++i) { - _childIndex[other._childKey[i]] = i; - _children[i] = std::move(other._children[i]); - } - addNodeMemory(); - } - - /* - * This move constructor should only be used when shrinking Node256 to Node48. - */ - Node48(Node256&& other) : Node(std::move(other)) { - invariant(other.needShrink()); - this->_nodeType = NodeType::NODE48; - std::fill(_childIndex.begin(), _childIndex.end(), maxByte); - size_t cur = 0; - for (size_t i = 0; i < other._children.size(); ++i) { - auto child = other._children[i]; - if (child) { - _childIndex[i] = cur; - _children[cur++] = child; - } - } - addNodeMemory(); - } - - ~Node48() { - _metrics.subtractMemory(sizeof(Node48) - sizeof(Node)); - _metrics.totalChildren.fetchAndSubtract(_children.size()); - } - - private: - void addNodeMemory() { - _metrics.totalMemory.fetchAndAdd(sizeof(Node48) - sizeof(Node)); - _metrics.totalChildren.fetchAndAdd(_children.size()); - } - - // A lookup table for child pointers. It has values from 0 to 48, where 0 - // represents empty, and all other index i maps to index i - 1 in _children. - std::array<uint8_t, 256> _childIndex; - std::array<node_ptr, 48> _children; - }; - - /* - * Node256 is used when the number of children is in the range of [49, 256]. - */ - class Node256 : public Node { - friend class RadixStore; - - public: - Node256() { - this->_nodeType = NodeType::NODE256; - addNodeMemory(); - } - - Node256(std::vector<uint8_t> key) : Node(NodeType::NODE256, key) { - addNodeMemory(); - } - - Node256(const NodeLeaf& other) : Node(other) { - addNodeMemory(); - } - - Node256(const Node4& other) : Node(other) { - this->_nodeType = NodeType::NODE256; - for (size_t i = 0; i < other.numChildren(); ++i) { - this->_children[other._childKey[i]] = other._children[i]; - } - addNodeMemory(); - } - - Node256(const Node16& other) : Node(other) { - this->_nodeType = NodeType::NODE256; - for (size_t i = 0; i < other.numChildren(); ++i) { - this->_children[other._childKey[i]] = other._children[i]; - } - addNodeMemory(); - } - - Node256(const Node48& other) : Node(other) { - this->_nodeType = NodeType::NODE256; - for (size_t i = 0; i < other._childIndex.size(); ++i) { - auto index = other._childIndex[i]; - if (index != maxByte) { - this->_children[i] = other._children[index]; - } - } - addNodeMemory(); - } - - Node256(const Node256& other) : Node(other), _children(other._children) { - addNodeMemory(); - } - - /* - * This move constructor should only be used when growing Node48 to Node256. - */ - Node256(Node48&& other) : Node(std::move(other)) { - invariant(other.needGrow()); - this->_nodeType = NodeType::NODE256; - for (size_t i = 0; i < other._childIndex.size(); ++i) { - auto index = other._childIndex[i]; - if (index != maxByte) { - _children[i] = std::move(other._children[index]); - } - } - addNodeMemory(); - } - - ~Node256() { - _metrics.subtractMemory(sizeof(Node256) - sizeof(Node)); - _metrics.totalChildren.fetchAndSubtract(_children.size()); - } - - private: - void addNodeMemory() { - _metrics.totalMemory.fetchAndAdd(sizeof(Node256) - sizeof(Node)); - _metrics.totalChildren.fetchAndAdd(_children.size()); - } - - std::array<node_ptr, 256> _children; - }; - - template <typename Node, typename... Args> - boost::intrusive_ptr<Node> make_intrusive_node(Args&&... args) { - auto ptr = new Node(std::forward<Args>(args)...); - return boost::intrusive_ptr<Node>(ptr, true); - } - - /** - * Head is the root node of every RadixStore, it contains extra information used by cursors to - * be able to see when the tree is modified and to respond to these changes by ensuring they are - * not iterating over stale trees. - */ - class Head : public Node256 { - friend class RadixStore; - - public: - Head() { - addNodeMemory(); - } - - Head(std::vector<uint8_t> key) : Node256(key) { - addNodeMemory(); - } - - Head(const Head& other) : Node256(other), _count(other._count), _dataSize(other._dataSize) { - addNodeMemory(); - } - - // Copy constructor template for the five node types. - template <typename NodeT> - Head(const NodeT& other) : Node256(other) { - addNodeMemory(); - } - - ~Head() { - if (_nextVersion) - _nextVersion->_hasPreviousVersion = false; - subtractNodeMemory(); - } - - Head(Head&& other) - : Node256(std::move(other)), _count(other._count), _dataSize(other._dataSize) { - addNodeMemory(); - } - - Head& operator=(const Head& rhs) = delete; - Head& operator=(Head&& rhs) = delete; - - bool hasPreviousVersion() const { - return _hasPreviousVersion; - } - - protected: - // Forms a singly linked list of versions that is needed to reposition cursors after - // modifications have been made. - head_ptr _nextVersion; - - // While we have cursors that haven't been repositioned to the latest tree, this will be - // true to help us understand when to copy on modifications due to the extra shared pointer - // _nextVersion. - bool _hasPreviousVersion = false; - - private: - void addNodeMemory() { - _metrics.addMemory(sizeof(Head) - sizeof(Node256)); - } - void subtractNodeMemory() { - _metrics.subtractMemory(sizeof(Head) - sizeof(Node256)); - } - - size_type _count = 0; - size_type _dataSize = 0; - }; - - /** - * Return a string representation of all the nodes in this tree. - * The string will look like: - * - * food - * s - * bar - * - * The number of spaces in front of each node indicates the depth - * at which the node lies. - */ - std::string _walkTree(Node* node, int depth) { - std::string ret; - for (int i = 0; i < depth; i++) { - ret.push_back(' '); - } - - for (uint8_t ch : node->_trieKey) { - ret.push_back(ch); - } - if (node->_data) { - ret.push_back('*'); - } - ret.push_back('\n'); - - _forEachChild(node, 0, false, [this, &ret, depth](node_ptr child) { - ret.append(_walkTree(child.get(), depth + 1)); - return true; - }); - return ret; - } - - /* - * Helper function to iterate through _children array for different node types and execute the - * given function on each child. The given function returns false when it intends to break the - * iteration. - * Returns false when the given function returns false on an element. Returns true - * when the end of the array is reached. - */ - static bool _forEachChild(Node* node, - uint16_t startKey, - bool reverse, - const std::function<bool(node_ptr)>& func) { - if (startKey > maxByte || !node->_numChildren) { - return true; - } - // Sets the step depending on the direction of iteration. - int16_t step = reverse ? -1 : 1; - switch (node->_nodeType) { - case NodeType::LEAF: - return true; - case NodeType::NODE4: { - Node4* node4 = static_cast<Node4*>(node); - // Locates the actual starting position first. - auto first = node4->_childKey.begin(); - auto numChildren = node4->numChildren(); - auto pos = std::find_if(first, first + numChildren, [&startKey](uint8_t keyByte) { - return keyByte >= startKey; - }); - if (reverse) { - if (pos == first && *pos != startKey) { - return true; - } - if (pos == first + numChildren || *pos != startKey) { - --pos; - } - } - - // No qualified elements. - if (pos == first + numChildren) { - return true; - } - - auto end = reverse ? -1 : numChildren; - for (auto cur = pos - first; cur != end; cur += step) { - // All children within the range will not be null since they are stored - // consecutively. - if (!func(node4->_children[cur])) { - // Breaks the loop if the given function decides to. - return false; - } - } - return true; - } - case NodeType::NODE16: { - Node16* node16 = static_cast<Node16*>(node); - // Locates the actual starting position first. - auto first = node16->_childKey.begin(); - auto numChildren = node16->numChildren(); - auto pos = std::find_if(first, first + numChildren, [&startKey](uint8_t keyByte) { - return keyByte >= startKey; - }); - if (reverse) { - if (pos == first && *pos != startKey) { - return true; - } - if (pos == first + numChildren || *pos != startKey) { - --pos; - } - } - - // No qualified elements. - if (pos == first + numChildren) { - return true; - } - - int16_t end = reverse ? -1 : numChildren; - for (auto cur = pos - first; cur != end; cur += step) { - // All children within the range will not be null since they are stored - // consecutively. - if (!func(node16->_children[cur])) { - return false; - } - } - return true; - } - case NodeType::NODE48: { - Node48* node48 = static_cast<Node48*>(node); - size_t end = reverse ? -1 : node48->_childIndex.size(); - for (size_t cur = startKey; cur != end; cur += step) { - auto index = node48->_childIndex[cur]; - if (index != maxByte && !func(node48->_children[index])) { - return false; - } - } - return true; - } - case NodeType::NODE256: { - Node256* node256 = static_cast<Node256*>(node); - size_t end = reverse ? -1 : node256->_children.size(); - for (size_t cur = startKey; cur != end; cur += step) { - auto child = node256->_children[cur]; - if (child && !func(child)) { - return false; - } - } - return true; - } - } - MONGO_UNREACHABLE; - } - - /* - * Gets the child mapped by the key for different node types. Node4 just does a simple linear - * search. Node16 uses binary search. Node48 does one extra lookup with the index table. Node256 - * has direct mapping. - */ - static node_ptr _findChild(const Node* node, uint8_t key) { - switch (node->_nodeType) { - case NodeType::LEAF: - return node_ptr(nullptr); - case NodeType::NODE4: { - const Node4* node4 = static_cast<const Node4*>(node); - auto start = node4->_childKey.begin(); - auto end = start + node4->numChildren(); - auto pos = std::find(start, end, key); - return pos != end ? node4->_children[pos - start] : node_ptr(nullptr); - } - case NodeType::NODE16: { - const Node16* node16 = static_cast<const Node16*>(node); - auto start = node16->_childKey.begin(); - auto end = start + node16->numChildren(); - auto pos = std::find(start, end, key); - return pos != end ? node16->_children[pos - start] : node_ptr(nullptr); - } - case NodeType::NODE48: { - const Node48* node48 = static_cast<const Node48*>(node); - auto index = node48->_childIndex[key]; - if (index != maxByte) { - return node48->_children[index]; - } - return node_ptr(nullptr); - } - case NodeType::NODE256: { - const Node256* node256 = static_cast<const Node256*>(node); - return node256->_children[key]; - } - } - MONGO_UNREACHABLE; - } - - Node* _findNode(const Key& key) const { - const uint8_t* charKey = reinterpret_cast<const uint8_t*>(key.data()); - - unsigned int depth = _root->_depth; - unsigned int initialDepthOffset = depth; - - // If the root node's triekey is not empty then the tree is a subtree, and so we examine it. - for (unsigned int i = 0; i < _root->_trieKey.size(); i++) { - if (charKey[i + initialDepthOffset] != _root->_trieKey[i]) { - return nullptr; - } - depth++; - - // Return node if entire trieKey matches. - if (depth == key.size() && _root->_data && - (key.size() - initialDepthOffset) == _root->_trieKey.size()) { - return _root.get(); - } - } - - depth = _root->_depth + _root->_trieKey.size(); - uint8_t childFirstChar = charKey[depth]; - auto node = _findChild(_root.get(), childFirstChar); - - while (node != nullptr) { - - depth = node->_depth; - - size_t mismatchIdx = - _comparePrefix(node->_trieKey, charKey + depth, key.size() - depth); - if (mismatchIdx != node->_trieKey.size()) { - return nullptr; - } else if (mismatchIdx == key.size() - depth && node->_data) { - return node.get(); - } - - depth = node->_depth + node->_trieKey.size(); - - childFirstChar = charKey[depth]; - node = _findChild(node.get(), childFirstChar); - } - - return nullptr; - } - - /** - * Makes a copy of the _root node if it isn't uniquely owned during an operation that will - * modify the tree. - * - * The _root node wouldn't be uniquely owned only when there are cursors positioned on the - * latest version of the tree. Cursors that are not yet repositioned onto the latest version of - * the tree are not considered to be sharing the _root for modifying operations. - */ - void _makeRootUnique() { - int rootUseCount = _root->_hasPreviousVersion ? 2 : 1; - - if (_root->refCount() == rootUseCount) - return; - - invariant(_root->refCount() > rootUseCount); - // Copy the node on a modifying operation when the root isn't unique. - - // There should not be any _nextVersion set in the _root otherwise our tree would have - // multiple HEADs. - invariant(!_root->_nextVersion); - _root->_nextVersion = make_intrusive_node<Head>(*_root); - _root = _root->_nextVersion; - _root->_hasPreviousVersion = true; - } - - /* - * Moves a smaller node's contents to a larger one. The method should only be called when the - * node is full. - */ - node_ptr _grow(Node* parent, Node* node) { - invariant(node->_nodeType != NodeType::NODE256); - auto key = node->_trieKey.front(); - switch (node->_nodeType) { - case NodeType::NODE256: - return nullptr; - case NodeType::LEAF: { - NodeLeaf* leaf = static_cast<NodeLeaf*>(node); - auto newNode = make_intrusive_node<Node4>(std::move(*leaf)); - _setChildPtr(parent, key, newNode); - return newNode; - } - case NodeType::NODE4: { - Node4* node4 = static_cast<Node4*>(node); - auto newNode = make_intrusive_node<Node16>(std::move(*node4)); - _setChildPtr(parent, key, newNode); - return newNode; - } - case NodeType::NODE16: { - Node16* node16 = static_cast<Node16*>(node); - auto newNode = make_intrusive_node<Node48>(std::move(*node16)); - _setChildPtr(parent, key, newNode); - return newNode; - } - case NodeType::NODE48: { - Node48* node48 = static_cast<Node48*>(node); - auto newNode = make_intrusive_node<Node256>(std::move(*node48)); - _setChildPtr(parent, key, newNode); - return newNode; - } - } - MONGO_UNREACHABLE; - } - - node_ptr _shrink(Node* parent, Node* node) { - invariant(node->_nodeType != NodeType::LEAF); - auto key = node->_trieKey.front(); - switch (node->_nodeType) { - case NodeType::LEAF: - return nullptr; - case NodeType::NODE4: { - Node4* node4 = static_cast<Node4*>(node); - auto newNode = make_intrusive_node<NodeLeaf>(std::move(*node4)); - _setChildPtr(parent, key, newNode); - return newNode; - } - case NodeType::NODE16: { - Node16* node16 = static_cast<Node16*>(node); - auto newNode = make_intrusive_node<Node4>(std::move(*node16)); - _setChildPtr(parent, key, newNode); - return newNode; - } - case NodeType::NODE48: { - Node48* node48 = static_cast<Node48*>(node); - auto newNode = make_intrusive_node<Node16>(std::move(*node48)); - _setChildPtr(parent, key, newNode); - return newNode; - } - case NodeType::NODE256: { - Node256* node256 = static_cast<Node256*>(node); - auto newNode = make_intrusive_node<Node48>(std::move(*node256)); - _setChildPtr(parent, key, newNode); - return newNode; - } - } - MONGO_UNREACHABLE; - } - - /** - * _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( - const Key& key, boost::optional<value_type> value) { - - const uint8_t* charKey = reinterpret_cast<const uint8_t*>(key.data()); - - int depth = _root->_depth + _root->_trieKey.size(); - uint8_t childFirstChar = charKey[depth]; - - _makeRootUnique(); - - Node* prevParent = nullptr; - Node* prev = _root.get(); - node_ptr node = _findChild(prev, childFirstChar); - while (node != nullptr) { - if (node->refCount() - 1 > 1) { - // Copy node on a modifying operation when it isn't owned uniquely. - node = _copyNode(node.get()); - _setChildPtr(prev, childFirstChar, node); - } - - // 'node' is uniquely owned at this point, so we are free to modify it. - // Get the index at which node->_trieKey and the new key differ. - size_t mismatchIdx = - _comparePrefix(node->_trieKey, charKey + depth, key.size() - depth); - - // The keys mismatch, so we need to split this node. - if (mismatchIdx != node->_trieKey.size()) { - - // Make a new node with whatever prefix is shared between node->_trieKey - // and the new key. This will replace the current node in the tree. - std::vector<uint8_t> newKey = _makeKey(node->_trieKey, 0, mismatchIdx); - Node* newNode = _addChild(prev, newKey, boost::none, NodeType::NODE4); - depth += mismatchIdx; - const_iterator it(_root, newNode); - if (key.size() - depth != 0) { - // Make a child with whatever is left of the new key. - newKey = _makeKey(charKey + depth, key.size() - depth); - Node* newChild = _addChild(newNode, newKey, value); - it = const_iterator(_root, newChild); - } else { - // The new key is a prefix of an existing key, and has its own node, so we don't - // need to add any new nodes. - newNode->addData(value.value()); - } - _root->_count++; - _root->_dataSize += value->second.size(); - - // Change the current node's trieKey and make a child of the new node. - newKey = _makeKey(node->_trieKey, mismatchIdx, node->_trieKey.size() - mismatchIdx); - _setChildPtr(newNode, newKey.front(), node); - - // Handle key size change to the new key. - _metrics.subtractMemory(sizeof(uint8_t) * - (node->_trieKey.capacity() - newKey.capacity())); - node->_trieKey = newKey; - node->_depth = newNode->_depth + newNode->_trieKey.size(); - - return std::pair<const_iterator, bool>(it, true); - } else if (mismatchIdx == key.size() - depth) { - auto& data = node->_data; - // The key already exists. If there's an element as well, account for its removal. - if (data) { - _root->_count--; - _root->_dataSize -= data->second.size(); - _metrics.subtractMemory(data->first.capacity() + data->second.capacity()); - } - - // Update an internal node. - if (!value) { - data = boost::none; - auto keyByte = node->_trieKey.front(); - if (_compressOnlyChild(prev, node.get())) { - node = _findChild(prev, keyByte); - } - } else { - _root->_count++; - _root->_dataSize += value->second.size(); - node->addData(value.value()); - } - const_iterator it(_root, node.get()); - - return std::pair<const_iterator, bool>(it, true); - } - - depth = node->_depth + node->_trieKey.size(); - childFirstChar = charKey[depth]; - - prevParent = prev; - prev = node.get(); - node = _findChild(node.get(), childFirstChar); - } - - // Add a completely new child to a node. The new key at this depth does not - // share a prefix with any existing keys. - std::vector<uint8_t> newKey = _makeKey(charKey + depth, key.size() - depth); - if (prev->needGrow()) { - prev = _grow(prevParent, prev).get(); - } - Node* newNode = _addChild(prev, newKey, value); - _root->_count++; - _root->_dataSize += value->second.size(); - const_iterator it(_root, newNode); - - return std::pair<const_iterator, bool>(it, true); - } - - /** - * Return a uint8_t vector with the first 'count' characters of - * 'old'. - */ - std::vector<uint8_t> _makeKey(const uint8_t* old, size_t count) { - std::vector<uint8_t> key; - for (size_t i = 0; i < count; ++i) { - uint8_t c = old[i]; - key.push_back(c); - } - return key; - } - - /** - * Return a uint8_t vector with the [pos, pos+count) characters from old. - */ - std::vector<uint8_t> _makeKey(std::vector<uint8_t> old, size_t pos, size_t count) { - std::vector<uint8_t> key; - for (size_t i = pos; i < pos + count; ++i) { - key.push_back(old[i]); - } - return key; - } - - /* - * Wraps around the copy constructor for different node types. - */ - node_ptr _copyNode(Node* node) { - switch (node->_nodeType) { - case NodeType::LEAF: { - NodeLeaf* leaf = static_cast<NodeLeaf*>(node); - return make_intrusive_node<NodeLeaf>(*leaf); - } - case NodeType::NODE4: { - Node4* node4 = static_cast<Node4*>(node); - return make_intrusive_node<Node4>(*node4); - } - case NodeType::NODE16: { - Node16* node16 = static_cast<Node16*>(node); - return make_intrusive_node<Node16>(*node16); - } - case NodeType::NODE48: { - Node48* node48 = static_cast<Node48*>(node); - return make_intrusive_node<Node48>(*node48); - } - case NodeType::NODE256: { - Node256* node256 = static_cast<Node256*>(node); - return make_intrusive_node<Node256>(*node256); - } - } - MONGO_UNREACHABLE; - } - - head_ptr _makeHead(Node* node) { - switch (node->_nodeType) { - case NodeType::LEAF: { - NodeLeaf* leaf = static_cast<NodeLeaf*>(node); - return make_intrusive_node<Head>(*leaf); - } - case NodeType::NODE4: { - Node4* node4 = static_cast<Node4*>(node); - return make_intrusive_node<Head>(*node4); - } - case NodeType::NODE16: { - Node16* node16 = static_cast<Node16*>(node); - return make_intrusive_node<Head>(*node16); - } - case NodeType::NODE48: { - Node48* node48 = static_cast<Node48*>(node); - return make_intrusive_node<Head>(*node48); - } - case NodeType::NODE256: { - Node256* node256 = static_cast<Node256*>(node); - return make_intrusive_node<Head>(*node256); - } - } - MONGO_UNREACHABLE; - } - - /** - * Add a child with trieKey 'key' and value 'value' to 'node'. The new child node created should - * only be the type NodeLeaf or Node4. - */ - Node* _addChild(Node* node, - std::vector<uint8_t> key, - boost::optional<value_type> value, - NodeType childType = NodeType::LEAF) { - invariant(childType == NodeType::LEAF || childType == NodeType::NODE4); - node_ptr newNode; - if (childType == NodeType::LEAF) { - newNode = make_intrusive_node<NodeLeaf>(key); - } else { - newNode = make_intrusive_node<Node4>(key); - } - newNode->_depth = node->_depth + node->_trieKey.size(); - if (value) { - newNode->addData(value.value()); - } - auto newNodeRaw = newNode.get(); - _setChildPtr(node, key.front(), std::move(newNode)); - return newNodeRaw; - } - - /* - * Inserts, updates, or deletes a child node at index and then maintains the key and children - * array for Node4 and Node16 in _setChildPtr(). - */ - template <class Node4Or16> - bool _setChildAtIndex(Node4Or16* node, node_ptr child, uint8_t key, size_t index) { - // Adds to the end of the array. - if (!node->_childKey[index] && !node->_children[index]) { - node->_childKey[index] = key; - node->_children[index] = child; - ++node->_numChildren; - return true; - } - if (node->_childKey[index] == key) { - if (!child) { - // Deletes a child and shifts - auto n = node->numChildren(); - for (int j = index; j < n - 1; ++j) { - node->_childKey[j] = node->_childKey[j + 1]; - node->_children[j] = node->_children[j + 1]; - } - node->_childKey[n - 1] = 0; - node->_children[n - 1] = nullptr; - --node->_numChildren; - return true; - } - node->_children[index] = child; - return true; - } - if (node->_childKey[index] > key) { - // _children is guaranteed not to be full. - // Inserts to 'index' and shift larger keys to the right. - for (size_t j = node->numChildren(); j > index; --j) { - node->_childKey[j] = node->_childKey[j - 1]; - node->_children[j] = node->_children[j - 1]; - } - node->_childKey[index] = key; - node->_children[index] = child; - ++node->_numChildren; - return true; - } - return false; - } - - /* - * Sets the child pointer of 'key' to the 'newNode' in 'node'. - */ - void _setChildPtr(Node* node, uint8_t key, node_ptr newNode) { - switch (node->_nodeType) { - case NodeType::LEAF: - return; - case NodeType::NODE4: { - Node4* node4 = static_cast<Node4*>(node); - auto start = node4->_childKey.begin(); - // Find the position of the first larger or equal key to insert. - auto pos = std::find_if(start, - start + node4->numChildren(), - [&key](uint8_t keyByte) { return keyByte >= key; }); - _setChildAtIndex(node4, newNode, key, pos - start); - return; - } - case NodeType::NODE16: { - Node16* node16 = static_cast<Node16*>(node); - auto start = node16->_childKey.begin(); - auto pos = std::find_if(start, - start + node16->numChildren(), - [&key](uint8_t keyByte) { return keyByte >= key; }); - _setChildAtIndex(node16, newNode, key, pos - start); - return; - } - case NodeType::NODE48: { - Node48* node48 = static_cast<Node48*>(node); - auto index = node48->_childIndex[key]; - if (index != maxByte) { - // Pointer already exists. Delete or update it. - if (!newNode) { - node48->_childIndex[key] = maxByte; - --node48->_numChildren; - } - node48->_children[index] = newNode; - return; - } else { - // Finds the first empty slot to insert the newNode. - for (size_t i = 0; i < node48->_children.size(); ++i) { - auto& child = node48->_children[i]; - if (!child) { - child = newNode; - node48->_childIndex[key] = i; - ++node48->_numChildren; - return; - } - } - } - return; - } - case NodeType::NODE256: { - Node256* node256 = static_cast<Node256*>(node); - auto& child = node256->_children[key]; - if (!child) { - ++node256->_numChildren; - } else if (!newNode) { - --node256->_numChildren; - } - child = newNode; - return; - } - } - MONGO_UNREACHABLE; - } - - /** - * 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. - * - * This assumes that the key is present in the tree. - */ - static std::vector<Node*> _buildContext(const Key& key, Node* node) { - std::vector<Node*> context; - context.push_back(node); - - const uint8_t* charKey = reinterpret_cast<const uint8_t*>(key.data()); - size_t depth = node->_depth + node->_trieKey.size(); - - while (depth < key.size()) { - node = _findChild(node, charKey[depth]).get(); - context.push_back(node); - depth = node->_depth + node->_trieKey.size(); - } - return context; - } - - /** - * Return the index at which 'key1' and 'key2' differ. - * This function will interpret the bytes in 'key2' as unsigned values. - */ - size_t _comparePrefix(std::vector<uint8_t> key1, const uint8_t* key2, size_t len2) const { - size_t smaller = std::min(key1.size(), len2); - - size_t i = 0; - for (; i < smaller; ++i) { - uint8_t c = key2[i]; - if (key1[i] != c) { - return i; - } - } - return i; - } - - /** - * Compresses a child node into its parent if necessary. This is required when an erase results - * in a node with no value and only one child. - * Returns true if compression occurred and false otherwise. - */ - Node* _compressOnlyChild(Node* parent, Node* node) { - // Don't compress if this node is not of type Node4, has an actual value associated with it, - // or doesn't have only one child. - if (node->_nodeType != NodeType::NODE4 || node->_data || node->_trieKey.empty() || - node->numChildren() != 1) { - return nullptr; - } - - Node4* node4 = static_cast<Node4*>(node); - node_ptr onlyChild = std::move(node4->_children[0]); - node4->_childKey[0] = 0; - node4->_numChildren = 0; - auto oldCapacity = node4->_trieKey.capacity(); - - for (char item : onlyChild->_trieKey) { - node4->_trieKey.push_back(item); - } - _metrics.addMemory(sizeof(uint8_t) * (node4->_trieKey.capacity() - oldCapacity)); - if (onlyChild->_data) { - node4->addData(onlyChild->_data.value()); - } - _forEachChild(onlyChild.get(), 0, false, [this, &parent, &node](node_ptr child) { - if (node->needGrow()) { - node = _grow(parent, node).get(); - } - auto keyByte = child->_trieKey.front(); - _setChildPtr(node, keyByte, std::move(child)); - return true; - }); - - if (node->needShrink()) { - node = _shrink(parent, node).get(); - } - - return node; - } - - /** - * Rebuilds the context by replacing stale raw pointers with the new pointers. The pointers - * can become stale when running an operation that copies the node on modification, like - * insert or erase. - */ - void _rebuildContext(std::vector<Node*>& context, std::vector<uint8_t>& trieKeyIndex) { - Node* replaceNode = _root.get(); - context[0] = replaceNode; - - for (size_t node = 1; node < context.size(); node++) { - replaceNode = _findChild(replaceNode, trieKeyIndex[node - 1]).get(); - context[node] = replaceNode; - } - } - - Node* _makeBranchUnique(std::vector<Node*>& context) { - - if (context.empty()) - return nullptr; - - // The first node should always be the root node. - _makeRootUnique(); - context[0] = _root.get(); - - // If the context only contains the root, and it was copied, return the new root. - if (context.size() == 1) - return _root.get(); - - Node* node = nullptr; - Node* prev = _root.get(); - - // Create copies of the nodes until the leaf node. - for (size_t idx = 1; idx < context.size(); idx++) { - node = context[idx]; - - auto next = _findChild(prev, node->_trieKey.front()); - if (next->refCount() - 1 > 1) { - node_ptr nodeCopy = _copyNode(node); - _setChildPtr(prev, nodeCopy->_trieKey.front(), nodeCopy); - context[idx] = nodeCopy.get(); - prev = nodeCopy.get(); - } else { - prev = next.get(); - } - } - - return context.back(); - } - - /** - * Resolves conflicts within subtrees due to the complicated structure of path-compressed radix - * tries. - */ - void _mergeResolveConflict(Node* current, Node* baseNode, Node* otherNode) { - - // Merges all differences between this and other, using base to determine whether operations - // are allowed or should throw a merge conflict. - RadixStore base, other, node; - node._root = _makeHead(current); - base._root = _makeHead(baseNode); - other._root = _makeHead(otherNode); - - // Merges insertions and updates from the master tree into the working tree, if possible. - for (const value_type& otherVal : other) { - RadixStore::const_iterator baseIter = base.find(otherVal.first); - RadixStore::const_iterator thisIter = node.find(otherVal.first); - - if (thisIter != node.end() && baseIter != base.end()) { - // All three trees have a record of the node with the same key. - if (thisIter->second == baseIter->second && baseIter->second != otherVal.second) { - // No changes occurred in the working tree, so the value in the master tree can - // be merged in cleanly. - this->update(RadixStore::value_type(otherVal)); - } else if (thisIter->second != baseIter->second && - baseIter->second != otherVal.second) { - // Both the working copy and master nodes changed the same value at the same - // key. This results in a merge conflict. - throw merge_conflict_exception(); - } else if (thisIter->second != baseIter->second && - thisIter->second == otherVal.second) { - // Both the working copy and master nodes are inserting the same value at the - // same key. But this is a merge conflict because if that operation was an - // increment, it's no different than a race condition on an unguarded variable. - throw merge_conflict_exception(); - } - } else if (baseIter != base.end() && baseIter->second != otherVal.second) { - // The working tree removed this node while the master updated the node, this - // results in a merge conflict. - throw merge_conflict_exception(); - } else if (thisIter != node.end()) { - // Both the working copy and master tree are either inserting the same value or - // different values at the same node, resulting in a merge conflict. - throw merge_conflict_exception(); - } else if (thisIter == node.end() && baseIter == base.end()) { - // The working tree and merge base do not have any record of this node. The node can - // be merged in cleanly from the master tree. - this->insert(RadixStore::value_type(otherVal)); - } - } - - // Perform deletions from the master tree in the working tree, if possible. - for (const value_type& baseVal : base) { - RadixStore::const_iterator otherIter = other.find(baseVal.first); - RadixStore::const_iterator thisIter = node.find(baseVal.first); - - if (otherIter == other.end()) { - if (thisIter != node.end() && thisIter->second == baseVal.second) { - // Nothing changed between the working tree and merge base, so it is safe to - // perform the deletion that occurred in the master tree. - this->erase(baseVal.first); - } else if (thisIter != node.end() && thisIter->second != baseVal.second) { - // The working tree made a change to the node while the master tree removed the - // node, resulting in a merge conflict. - throw merge_conflict_exception(); - } - } - } - } - - /** - * Merges elements from the master tree into the working copy if they have no presence in the - * working copy, otherwise we throw a merge conflict. - */ - void _mergeTwoBranches(Node* current, Node* otherNode) { - - RadixStore other, node; - node._root = _makeHead(current); - other._root = _makeHead(otherNode); - - for (const value_type& otherVal : other) { - RadixStore::const_iterator thisIter = node.find(otherVal.first); - - if (thisIter != node.end()) - throw merge_conflict_exception(); - this->insert(RadixStore::value_type(otherVal)); - } - } - - /** - * Merges changes from base to other into current. Throws merge_conflict_exception if there are - * merge conflicts. - * It returns the updated current node and a boolean indicating that conflict resolution is - * required after recursion - */ - std::pair<Node*, bool> _merge3Helper(Node* current, - const Node* base, - const Node* other, - std::vector<Node*>& context, - std::vector<uint8_t>& trieKeyIndex) { - context.push_back(current); - - // Root doesn't have a trie key. - if (!current->_trieKey.empty()) - trieKeyIndex.push_back(current->_trieKey.at(0)); - - auto hasParent = [](std::vector<Node*>& context) { return context.size() >= 2; }; - - auto getParent = [&](std::vector<Node*>& context) { - // We should never get here unless we are at sufficient depth already, so the invariant - // should indeed actually hold. If coverity complains, treat as a false alarm. - invariant(hasParent(context)); - return context[context.size() - 2]; - }; - - auto currentHasBeenCompressed = [&]() { - // This can only happen when conflict resolution erases nodes that causes compression on - // the current node. - return current->_trieKey.size() != other->_trieKey.size(); - }; - - auto splitCurrentBeforeWriteIfNeeded = [&](Node* child) { - // If current has not been compressed there's nothing to do - if (!currentHasBeenCompressed()) - return child; - - // This can only happen if we've done previous writes to current so it should already be - // unique and safe to write to. - size_t mismatchIdx = - _comparePrefix(current->_trieKey, other->_trieKey.data(), other->_trieKey.size()); - - auto parent = getParent(context); - auto key = current->_trieKey.front(); - auto newTrieKeyBegin = current->_trieKey.begin(); - auto newTrieKeyEnd = current->_trieKey.begin() + mismatchIdx; - auto shared_current = std::move(_findChild(parent, key)); - auto newTrieKey = std::vector<uint8_t>(newTrieKeyBegin, newTrieKeyEnd); - - // Replace current with a new node with no data - auto newNode = _addChild(parent, newTrieKey, boost::none, NodeType::NODE4); - - // Remove the part of the trieKey that is used by the new node. - current->_trieKey.erase(current->_trieKey.begin(), - current->_trieKey.begin() + mismatchIdx); - _metrics.subtractMemory(sizeof(uint8_t) * mismatchIdx); - current->_depth += mismatchIdx; - - // Add what was the current node as a child to the new internal node - key = current->_trieKey.front(); - _setChildPtr(newNode, key, std::move(shared_current)); - - // Update current pointer and context - child = current; - current = newNode; - context.back() = current; - return child; - }; - - bool resolveConflictNeeded = false; - for (size_t key = 0; key < 256; ++key) { - // Since _makeBranchUnique may make changes to the pointer addresses in recursive calls. - current = context.back(); - - Node* node = _findChild(current, key).get(); - Node* baseNode = _findChild(base, key).get(); - Node* otherNode = _findChild(other, key).get(); - - if (!node && !baseNode && !otherNode) - continue; - - bool unique = node != otherNode && node != baseNode; - - // If the current tree does not have this node, check if the other trees do. - if (!node) { - if (!baseNode && otherNode) { - splitCurrentBeforeWriteIfNeeded(nullptr); - // If base and node do NOT have this branch, but other does, then - // merge in the other's branch. - current = _makeBranchUnique(context); - - if (current->needGrow() && hasParent(context)) { - current = _grow(getParent(context), current).get(); - } - - // Need to rebuild our context to have updated pointers due to the - // modifications that go on in _makeBranchUnique. - _rebuildContext(context, trieKeyIndex); - _setChildPtr(current, key, _findChild(other, key)); - } else if (!otherNode || (baseNode && baseNode != otherNode)) { - // Either the master tree and working tree remove the same branch, or the master - // tree updated the branch while the working tree removed the branch, resulting - // in a merge conflict. - throw merge_conflict_exception(); - } - } else if (!unique) { - if (baseNode && !otherNode && baseNode == node) { - node = splitCurrentBeforeWriteIfNeeded(node); - - // Other has a deleted branch that must also be removed from current tree. - current = _makeBranchUnique(context); - _setChildPtr(current, key, nullptr); - if (current->needShrink() && hasParent(context)) { - current = _shrink(getParent(context), current).get(); - } - _rebuildContext(context, trieKeyIndex); - } else if (baseNode && otherNode && baseNode == node) { - node = splitCurrentBeforeWriteIfNeeded(node); - - // If base and current point to the same node, then master changed. - current = _makeBranchUnique(context); - if (current->needGrow() && hasParent(context)) { - current = _grow(getParent(context), current).get(); - } - _rebuildContext(context, trieKeyIndex); - _setChildPtr(current, key, _findChild(other, key)); - } - } else if (baseNode && otherNode && baseNode != otherNode) { - // If all three are unique and leaf nodes with different data, then it is a merge - // conflict. - if (node->isLeaf() && baseNode->isLeaf() && otherNode->isLeaf()) { - bool dataChanged = node->_data != baseNode->_data; - bool otherDataChanged = baseNode->_data != otherNode->_data; - if (dataChanged && otherDataChanged) { - // All three nodes have different data, that is a merge conflict - throw merge_conflict_exception(); - } - if (otherDataChanged) { - // Only other changed the data. Take that node - current = _makeBranchUnique(context); - _rebuildContext(context, trieKeyIndex); - _setChildPtr(current, key, _findChild(other, key)); - } - continue; - } - - if (currentHasBeenCompressed()) { - resolveConflictNeeded = true; - break; - } - - // If the keys and data are all the exact same, then we can keep recursing. - // Otherwise, we manually resolve the differences element by element. The - // structure of compressed radix tries makes it difficult to compare the - // trees node by node, hence the reason for resolving these differences - // element by element. - bool resolveConflict = - !(node->_trieKey == baseNode->_trieKey && - baseNode->_trieKey == otherNode->_trieKey && node->_data == baseNode->_data && - baseNode->_data == otherNode->_data); - if (!resolveConflict) { - std::tie(node, resolveConflict) = - _merge3Helper(node, baseNode, otherNode, context, trieKeyIndex); - if (node && !node->_data) { - // Drop if leaf node without data, that is not valid. Otherwise we might - // need to compress if we have only one child. - if (node->isLeaf()) { - _setChildPtr(current, key, nullptr); - // Don't shrink the root node. - if (current->needShrink() && hasParent(context)) { - current = _shrink(getParent(context), current).get(); - } - _rebuildContext(context, trieKeyIndex); - } else { - if (auto compressedNode = _compressOnlyChild(current, node)) { - node = compressedNode; - } - } - } - } - if (resolveConflict) { - Node* nodeToResolve = node; - if (hasParent(context)) { - if (auto compressed = _compressOnlyChild(getParent(context), current)) { - current = compressed; - nodeToResolve = current; - } - } - _mergeResolveConflict(nodeToResolve, baseNode, otherNode); - _rebuildContext(context, trieKeyIndex); - // If we compressed above, resolving the conflict can result in erasing current. - // Break out of the recursion as there is nothing more to do. - if (!context.back()) - break; - } - } else if (baseNode && !otherNode) { - // Throw a write conflict since current has modified a branch but master has - // removed it. - throw merge_conflict_exception(); - } else if (!baseNode && otherNode) { - // Both the working tree and master added branches that were nonexistent in base. - // This requires us to resolve these differences element by element since the - // changes may not be conflicting. - if (currentHasBeenCompressed()) { - resolveConflictNeeded = true; - break; - } - - _mergeTwoBranches(node, otherNode); - _rebuildContext(context, trieKeyIndex); - } - } - - current = context.back(); - context.pop_back(); - if (!trieKeyIndex.empty()) - trieKeyIndex.pop_back(); - - return std::make_pair(current, resolveConflictNeeded); - } - - Node* _begin(Node* root) const noexcept { - Node* node = root; - while (!node->_data) { - _forEachChild(node, 0, false, [&node](node_ptr child) { - node = child.get(); - return false; - }); - } - return node; - } - - head_ptr _root = nullptr; - static Metrics _metrics; -}; - -template <class Key, class T> -Metrics RadixStore<Key, T>::_metrics; - -using StringStore = RadixStore<std::string, std::string>; -} // namespace ephemeral_for_test -} // namespace mongo |