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 | |
parent | f6134fa0277a7c73580735b42c69f43ee7b0d11c (diff) | |
download | mongo-e1f267e15c92f48580bd77a6138fc4cc09be6913.tar.gz |
SERVER-65151 Remove `ephemeralForTest`
Diffstat (limited to 'src')
22 files changed, 0 insertions, 11132 deletions
diff --git a/src/mongo/db/storage/ephemeral_for_test/README.md b/src/mongo/db/storage/ephemeral_for_test/README.md deleted file mode 100644 index e794cd1de0b..00000000000 --- a/src/mongo/db/storage/ephemeral_for_test/README.md +++ /dev/null @@ -1,131 +0,0 @@ -# Ephemeral For Test Storage Engine - -The primary goal of this [storage engine](#ephemeral-storage-engine-glossary) is to provide a simple -and efficient in-memory storage engine implementation for use in testing. The lack of I/O, -background threads, complex concurrency primitives or third-party libraries, allows for easy -integration in unit tests, compatibility with advanced sanitizers, such as -[TSAN](https://clang.llvm.org/docs/ThreadSanitizer.html). A secondary goal is to strengthen the -Storage API, its modularity and our understanding of it, by having an alternate implementation that -can be used as testbed for new ideas. - -For more context and information on how this storage engine is used, see the -[Execution Architecture Guide](https://github.com/mongodb/mongo/blob/master/src/mongo/db/catalog/README.md). - -## Architecture - -The architecture of the storage engine has parallels to and is in part inspired by the `git` version -control system and borrows some of its terminology. - -## Radix Store - -The core of this storage engine is a [radix tree](https://en.wikipedia.org/wiki/Radix_tree). Our -`RadixStore` implementation uses [copy on write](https://en.wikipedia.org/wiki/Copy-on-write) to -make the data structure [persistent](https://en.wikipedia.org/wiki/Persistent_data_structure). This -is implemented using reference-counted pointers. - -### Transactions on the Radix Store - -The most recent committed state of the store is called the _master_. Starting a transaction involves -making a [shallow copy](https://en.wikipedia.org/wiki/Object_copying#Shallow_copy) of the master, -where we copy the head node in full, but share the children with the original. This copy is known as -the _base_ of the transaction. Another copy of this base is referred to as the _current_ state. Each -modification of the tree updates this _current_ state. A transaction abort consists of simply -discarding these copies. - -A transaction commit is trivial if the master still compares equal to the base: just swap the -current state into master. In other cases, it is necessary to do a [three-way -merge](https://en.wikipedia.org/wiki/Merge_(version_control)#Three-way_merge) to advance the base to -master. Any merge conflicts result in a `WriteConflictException` and transaction abort and requires -the operation to execute again. As long as there is no merge conflict, the current tree has -incorporated all changes that occurred in the master tree and the -[compare-and-swap](https://en.wikipedia.org/wiki/Compare-and-swap) loop can repeat with an updated -base to reflect this. This scheme ensures forward progress and makes the commit protocol [lock -free](https://en.wikipedia.org/wiki/Non-blocking_algorithm#Lock-freedom). Because merging compares -values using pointer comparison, and both old and new values of any modified key-value pairs still -exist, there is no [ABA problem](https://en.wikipedia.org/wiki/Compare-and-swap#ABA_problem). - -## Changing Nodes - -Modification of a node N requires making a copy N' of that node, as well as all its parents up -to the root R of the tree, as they need to change to point to N'. The tree referenced by R is -immutable, and removing the last reference to R causes that root node to be deallocated, as well -as recursively any nodes unique to that tree. The root node of the tree has some extra information, -namely a `_count` and `_datasize`, which contain the total number of key-value pairs in the tree as -well as the total size in bytes of all key-value pairs. - -### Iterators - -The radix tree is an ordered data structure and traversal of the tree is done with depth-first -search. A `radix_iterator` has a shared reference to the root of the tree as well as a pointer -to the current node. Currently, on successful commit, the new root of the tree automatically adds -itself to the `_nextVersion` link of the previous master thus establishing a linked list of -committed versions. Iterators use this list to move themselves to a newer version when available. -These versions are only versions of the tree made by the same client. This is needed so the client's -iterators can see their own write. - - -## KVEngine implementation - -### _idents_ and prefixed keys - -The storage engine API assumes there are many logical tables containing key-value pairs. These -tables are called [_idents_](#ephemeral-storage-engine-glossary). However, to support transactions -with changes spanning multiple idents, this storage engine uses a single radix store and prefixes -each key with the ident, so that in effect each ident has its own branch in the radix tree. - -### `RecoveryUnit` - -The RecoveryUnit is the abstraction of how transactions are implemented on the storage engine as -described [_above_](#transactions-on-the-radix-store). It is responsible for creating a local fork -of the radix tree and merge in any changes to the master tree on commit. When a transaction is -rolled back the RecoveryUnit can simply release its reference to the forked radix store. This -guarantees -[_atomicity_](https://github.com/mongodb/mongo/blob/master/src/mongo/db/storage/README.md#atomicity) -on the storage engine. - -### `RecordStore` - -The RecordStore is the abstraction on how to read or write data with the storage engine. It is also -responsible for implementing the oplog collection with the correct semantics, this is handled in the -[VisibilityManager](#visibilitymanager). The RecordStore accesses the radix store via the -[_RecoveryUnit_](#recoveryunit), this ensures -[_isolation_](https://github.com/mongodb/mongo/blob/master/src/mongo/db/storage/README.md#isolation) -on the storage engine. - -### `VisibilityManager` - -The visibility manager is a system internal to the storage engine to keep track of uncommitted -inserts and reserved timestamps to implement [_oplog -visibility_](https://github.com/mongodb/mongo/blob/master/src/mongo/db/catalog/README.md#oplog-visibility). - -### `SortedDataInterface` - -The SortedDataInterface is the implementation for indexes. Indexes are implemented on top of the -radix store like any other [_idents_](#ephemeral-storage-engine-glossary) where the data contains -the necessary information to be able to find a record belonging to another ident without searching -for it. There are two types of indexes in the implementation, unique and non-unique. Both index -types may contain duplicate entries but are optimized for their regular use-case. - -# Ephemeral Storage Engine Glossary - -**ident**: Name uniquely identifying a table containing key-value pairs. Idents are not reused. - -**sanitizer**: Testing tool for executing code with extra checks, often through the use of special -run time libraries and/or compiled code generation. - -**storage engine**: Low-level database component providing transactional storage primitives -including create, read, update and delete (CRUD) operations on key-value pairs. - -**master**: The master radix store contains the last committed transaction. - -**base**: The radix store containing the master at the start of the transaction. - -**path compression**: The technique to ensure that each node without value in a radix tree has at -least two children. - -**storage transaction**: A series of CRUD operations performed on behalf of a client that remain -invisible to other clients (_isolation property_), until they are made visible(_committed_) or -undone (_aborted_), all at once (atomicity property). Pre-commit checks ensure the operations do -not violate logical constraints (_consistency property_). Once committed, the changes will not go -away unless the node fails (_durability property_). Together these four transaction properties are -known as the ACID properties. diff --git a/src/mongo/db/storage/ephemeral_for_test/SConscript b/src/mongo/db/storage/ephemeral_for_test/SConscript deleted file mode 100644 index 335f6ca4bf5..00000000000 --- a/src/mongo/db/storage/ephemeral_for_test/SConscript +++ /dev/null @@ -1,77 +0,0 @@ -# -*- mode: python; -*- - -Import("env") - -env = env.Clone() - -env.Library( - target='storage_ephemeral_for_test_core', - source=[ - 'ephemeral_for_test_kv_engine.cpp', - 'ephemeral_for_test_record_store.cpp', - 'ephemeral_for_test_recovery_unit.cpp', - 'ephemeral_for_test_sorted_impl.cpp', - 'ephemeral_for_test_visibility_manager.cpp', - ], - LIBDEPS=[ - '$BUILD_DIR/mongo/base', - '$BUILD_DIR/mongo/db/concurrency/write_conflict_exception', - '$BUILD_DIR/mongo/db/storage/index_entry_comparison', - '$BUILD_DIR/mongo/db/storage/record_store_base', - '$BUILD_DIR/mongo/db/storage/recovery_unit_base', - ], - LIBDEPS_PRIVATE=[ - '$BUILD_DIR/mongo/db/catalog/collection_options', - '$BUILD_DIR/mongo/db/commands/server_status', - '$BUILD_DIR/mongo/db/record_id_helpers', - '$BUILD_DIR/mongo/db/storage/key_string', - '$BUILD_DIR/mongo/db/storage/storage_options', - '$BUILD_DIR/mongo/db/storage/write_unit_of_work', - '$BUILD_DIR/mongo/util/fail_point', - ], -) - -env.Library( - target='storage_ephemeral_for_test', - source=[ - 'ephemeral_for_test_init.cpp', - 'ephemeral_for_test_server_status.cpp', - ], - LIBDEPS=[ - '$BUILD_DIR/mongo/db/storage/durable_catalog_impl', - '$BUILD_DIR/mongo/db/storage/storage_engine_impl', - 'storage_ephemeral_for_test_core', - ], - LIBDEPS_PRIVATE=[ - '$BUILD_DIR/mongo/db/commands/server_status', - '$BUILD_DIR/mongo/db/storage/storage_engine_common', - '$BUILD_DIR/mongo/db/storage/storage_engine_lock_file', - ], -) - -# Testing -env.CppUnitTest( - target='storage_ephemeral_for_test_test', - source=[ - 'ephemeral_for_test_kv_engine_test.cpp', - 'ephemeral_for_test_record_store_test.cpp', - 'ephemeral_for_test_recovery_unit_test.cpp', - 'ephemeral_for_test_radix_store_test.cpp', - 'ephemeral_for_test_radix_store_concurrent_test.cpp', - 'ephemeral_for_test_sorted_impl_test.cpp', - ], - LIBDEPS=[ - '$BUILD_DIR/mongo/db/auth/authmocks', - '$BUILD_DIR/mongo/db/common', - '$BUILD_DIR/mongo/db/index/index_descriptor', - '$BUILD_DIR/mongo/db/multitenancy', - '$BUILD_DIR/mongo/db/repl/repl_coordinator_interface', - '$BUILD_DIR/mongo/db/repl/replmocks', - '$BUILD_DIR/mongo/db/storage/key_string', - '$BUILD_DIR/mongo/db/storage/kv/kv_engine_test_harness', - '$BUILD_DIR/mongo/db/storage/record_store_test_harness', - '$BUILD_DIR/mongo/db/storage/recovery_unit_test_harness', - '$BUILD_DIR/mongo/db/storage/sorted_data_interface_test_harness', - 'storage_ephemeral_for_test_core', - ], -) diff --git a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_init.cpp b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_init.cpp deleted file mode 100644 index 713309f786d..00000000000 --- a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_init.cpp +++ /dev/null @@ -1,100 +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. - */ - -#include "mongo/platform/basic.h" - -#include "mongo/base/init.h" -#include "mongo/db/service_context.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_kv_engine.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_server_status.h" -#include "mongo/db/storage/storage_engine_impl.h" -#include "mongo/db/storage/storage_engine_init.h" -#include "mongo/db/storage/storage_engine_lock_file.h" -#include "mongo/db/storage/storage_options.h" - -#if __has_feature(address_sanitizer) -#include <sanitizer/lsan_interface.h> -#endif - -namespace mongo { -namespace ephemeral_for_test { - -namespace { -class EphemeralForTestStorageEngineFactory : public StorageEngine::Factory { -public: - virtual std::unique_ptr<StorageEngine> create(OperationContext* opCtx, - const StorageGlobalParams& params, - const StorageEngineLockFile* lockFile) const { - auto kv = std::make_unique<KVEngine>(); - // We must only add the server parameters to the global registry once during unit testing. - static int setupCountForUnitTests = 0; - if (setupCountForUnitTests == 0) { - ++setupCountForUnitTests; - - // Intentionally leaked. - [[maybe_unused]] auto leakedSection = new ServerStatusSection(kv.get()); - - // This allows unit tests to run this code without encountering memory leaks -#if __has_feature(address_sanitizer) - __lsan_ignore_object(leakedSection); -#endif - } - - StorageEngineOptions options; - options.directoryPerDB = params.directoryperdb; - options.forRepair = params.repair; - options.forRestore = params.restore; - options.lockFileCreatedByUncleanShutdown = lockFile && lockFile->createdByUncleanShutdown(); - return std::make_unique<StorageEngineImpl>(opCtx, std::move(kv), options); - } - - virtual StringData getCanonicalName() const { - return kEngineName; - } - - virtual Status validateMetadata(const StorageEngineMetadata& metadata, - const StorageGlobalParams& params) const { - return Status::OK(); - } - - virtual BSONObj createMetadataOptions(const StorageGlobalParams& params) const { - return BSONObj(); - } -}; - - -ServiceContext::ConstructorActionRegisterer registerEphemeralForTest( - "RegisterEphemeralForTestEngine", [](ServiceContext* service) { - registerStorageEngine(service, std::make_unique<EphemeralForTestStorageEngineFactory>()); - }); - -} // namespace -} // namespace ephemeral_for_test -} // namespace mongo diff --git a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_kv_engine.cpp b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_kv_engine.cpp deleted file mode 100644 index 3d4a3ce9976..00000000000 --- a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_kv_engine.cpp +++ /dev/null @@ -1,293 +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. - */ - -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kStorage - -#include "mongo/platform/basic.h" - -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_kv_engine.h" - -#include <memory> - -#include "mongo/db/index/index_descriptor.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_recovery_unit.h" -#include "mongo/db/storage/key_string.h" -#include "mongo/db/storage/record_store.h" -#include "mongo/db/storage/sorted_data_interface.h" - -namespace mongo { -namespace ephemeral_for_test { -namespace { -static AtomicWord<bool> shuttingDown{false}; -} // namespace - -bool KVEngine::instanceExists() { - return shuttingDown.load(); -} - -KVEngine::KVEngine() - : mongo::KVEngine(), _visibilityManager(std::make_unique<VisibilityManager>()) { - _master = std::make_shared<StringStore>(); - _availableHistory[Timestamp(_masterVersion++, 0)] = _master; - shuttingDown.store(false); -} - -KVEngine::~KVEngine() { - shuttingDown.store(true); -} - -mongo::RecoveryUnit* KVEngine::newRecoveryUnit() { - return new RecoveryUnit(this, nullptr); -} - -Status KVEngine::createRecordStore(OperationContext* opCtx, - const NamespaceString& nss, - StringData ident, - const CollectionOptions& options, - KeyFormat keyFormat) { - stdx::lock_guard lock(_identsLock); - _idents[ident.toString()] = true; - return Status::OK(); -} - -Status KVEngine::importRecordStore(OperationContext* opCtx, - StringData ident, - const BSONObj& storageMetadata, - const ImportOptions& importOptions) { - stdx::lock_guard lock(_identsLock); - _idents[ident.toString()] = true; - return Status::OK(); -} - -std::unique_ptr<mongo::RecordStore> KVEngine::makeTemporaryRecordStore(OperationContext* opCtx, - StringData ident, - KeyFormat keyFormat) { - std::unique_ptr<mongo::RecordStore> recordStore = - std::make_unique<RecordStore>("", ident, keyFormat, false); - stdx::lock_guard lock(_identsLock); - _idents[ident.toString()] = true; - return recordStore; -}; - - -std::unique_ptr<mongo::RecordStore> KVEngine::getRecordStore(OperationContext* unused, - const NamespaceString& nss, - StringData ident, - const CollectionOptions& options) { - std::unique_ptr<mongo::RecordStore> recordStore; - const auto keyFormat = options.clusteredIndex ? KeyFormat::String : KeyFormat::Long; - if (options.capped) { - recordStore = std::make_unique<RecordStore>(nss.ns(), - ident, - keyFormat, - options.capped, - /*cappedCallback*/ nullptr, - _visibilityManager.get()); - } else { - recordStore = std::make_unique<RecordStore>(nss.ns(), ident, keyFormat, options.capped); - } - stdx::lock_guard lock(_identsLock); - _idents[ident.toString()] = true; - return recordStore; -} - -bool KVEngine::trySwapMaster(StringStore& newMaster, uint64_t version) { - stdx::lock_guard<Latch> lock(_masterLock); - invariant(!newMaster.hasBranch() && !_master->hasBranch()); - if (_masterVersion != version) - return false; - // TODO SERVER-48314: replace _masterVersion with a Timestamp of transaction. - Timestamp commitTimestamp(_masterVersion++, 0); - auto newMasterPtr = std::make_shared<StringStore>(newMaster); - _availableHistory[commitTimestamp] = newMasterPtr; - _master = newMasterPtr; - _cleanHistory(lock); - return true; -} - - -Status KVEngine::createSortedDataInterface(OperationContext* opCtx, - const NamespaceString& nss, - const CollectionOptions& collOptions, - StringData ident, - const IndexDescriptor* desc) { - stdx::lock_guard lock(_identsLock); - _idents[ident.toString()] = false; - return Status::OK(); // I don't think we actually need to do anything here -} - -Status KVEngine::importSortedDataInterface(OperationContext* opCtx, - StringData ident, - const BSONObj& storageMetadata, - const ImportOptions& importOptions) { - stdx::lock_guard lock(_identsLock); - _idents[ident.toString()] = false; - return Status::OK(); -} - -std::unique_ptr<mongo::SortedDataInterface> KVEngine::getSortedDataInterface( - OperationContext* opCtx, - const NamespaceString& nss, - const CollectionOptions& collOptions, - StringData ident, - const IndexDescriptor* desc) { - auto rsKeyFormat = collOptions.clusteredIndex ? KeyFormat::String : KeyFormat::Long; - return getSortedDataInterface(opCtx, nss, rsKeyFormat, ident, desc); -} - -std::unique_ptr<mongo::SortedDataInterface> KVEngine::getSortedDataInterface( - OperationContext* opCtx, - const NamespaceString& nss, - KeyFormat rsKeyFormat, - StringData ident, - const IndexDescriptor* desc) { - { - stdx::lock_guard lock(_identsLock); - _idents[ident.toString()] = false; - } - if (desc->unique()) - return std::make_unique<SortedDataInterfaceUnique>(opCtx, ident, rsKeyFormat, desc); - else - return std::make_unique<SortedDataInterfaceStandard>(opCtx, ident, rsKeyFormat, desc); -} - -Status KVEngine::dropIdent(mongo::RecoveryUnit* ru, - StringData ident, - StorageEngine::DropIdentCallback&& onDrop) { - Status dropStatus = Status::OK(); - stdx::unique_lock lock(_identsLock); - if (_idents.count(ident.toString()) > 0) { - // Check if the ident is a RecordStore or a SortedDataInterface then call the corresponding - // truncate. A true value in the map means it is a RecordStore, false a SortedDataInterface. - bool isRecordStore = _idents[ident.toString()] == true; - lock.unlock(); - if (isRecordStore) { // ident is RecordStore. - CollectionOptions s; - auto rs = getRecordStore(/*opCtx=*/nullptr, NamespaceString(""), ident, s); - dropStatus = - checked_cast<RecordStore*>(rs.get())->truncateWithoutUpdatingCount(ru).getStatus(); - } else { // ident is SortedDataInterface. - auto sdi = - std::make_unique<SortedDataInterfaceUnique>(Ordering::make(BSONObj()), ident); - dropStatus = sdi->truncate(ru); - } - lock.lock(); - _idents.erase(ident.toString()); - } - if (dropStatus.isOK() && onDrop) { - onDrop(); - } - return dropStatus; -} - -std::pair<uint64_t, std::shared_ptr<StringStore>> KVEngine::getMasterInfo( - boost::optional<Timestamp> timestamp) { - stdx::lock_guard<Latch> lock(_masterLock); - if (timestamp && !timestamp->isNull()) { - if (timestamp < _getOldestTimestamp(lock)) { - uasserted(ErrorCodes::SnapshotTooOld, - str::stream() << "Read timestamp " << timestamp->toString() - << " is older than the oldest available timestamp."); - } - auto it = _availableHistory.lower_bound(timestamp.get()); - return std::make_pair(it->first.asULL(), it->second); - } - return std::make_pair(_masterVersion, _master); -} - -void KVEngine::cleanHistory() { - stdx::lock_guard<Latch> lock(_masterLock); - _cleanHistory(lock); -} - -void KVEngine::_cleanHistory(WithLock) { - for (auto it = _availableHistory.cbegin(); it != _availableHistory.cend();) { - if (it->second.use_count() == 1) { - invariant(it->second.get() != _master.get()); - it = _availableHistory.erase(it); - } else { - break; - } - } - - // Check that pointer to master is not deleted. - invariant(_availableHistory.size() >= 1); -} - -Timestamp KVEngine::getOldestTimestamp() const { - stdx::lock_guard<Latch> lock(_masterLock); - return _getOldestTimestamp(lock); -} - -void KVEngine::setOldestTimestamp(Timestamp newOldestTimestamp, bool force) { - stdx::lock_guard<Latch> lock(_masterLock); - if (newOldestTimestamp > _availableHistory.rbegin()->first) { - _availableHistory[newOldestTimestamp] = _master; - // TODO SERVER-48314: Remove when _masterVersion is no longer being used to mock commit - // timestamps. - _masterVersion = newOldestTimestamp.asULL(); - } - for (auto it = _availableHistory.cbegin(); it != _availableHistory.cend();) { - if (it->first < newOldestTimestamp) { - it = _availableHistory.erase(it); - } else { - break; - } - } - - // Check that pointer to master is not deleted. - invariant(_availableHistory.size() >= 1); -} - -std::map<Timestamp, std::shared_ptr<StringStore>> KVEngine::getHistory_forTest() { - stdx::lock_guard<Latch> lock(_masterLock); - return _availableHistory; -} - -class EmptyRecordCursor final : public SeekableRecordCursor { -public: - boost::optional<Record> next() final { - return {}; - } - boost::optional<Record> seekExact(const RecordId& id) final { - return {}; - } - boost::optional<Record> seekNear(const RecordId& id) final { - return {}; - } - void save() final {} - bool restore(bool tolerateCappedRepositioning) final { - return true; - } - void detachFromOperationContext() final {} - void reattachToOperationContext(OperationContext* opCtx) final {} - void setSaveStorageCursorOnDetachFromOperationContext(bool) final {} -}; -} // namespace ephemeral_for_test -} // namespace mongo diff --git a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_kv_engine.h b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_kv_engine.h deleted file mode 100644 index 8844100fbae..00000000000 --- a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_kv_engine.h +++ /dev/null @@ -1,241 +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 <memory> -#include <mutex> -#include <set> - -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_sorted_impl.h" -#include "mongo/db/storage/kv/kv_engine.h" - -namespace mongo { -namespace ephemeral_for_test { - -static constexpr char kEngineName[] = "ephemeralForTest"; - -class JournalListener; -/** - * The ephemeral for test storage engine is intended for unit and performance testing. - */ -class KVEngine : public mongo::KVEngine { -public: - KVEngine(); - virtual ~KVEngine(); - - virtual mongo::RecoveryUnit* newRecoveryUnit(); - - virtual Status createRecordStore(OperationContext* opCtx, - const NamespaceString& nss, - StringData ident, - const CollectionOptions& options, - KeyFormat keyFormat = KeyFormat::Long); - - virtual Status importRecordStore(OperationContext* opCtx, - StringData ident, - const BSONObj& storageMetadata, - const ImportOptions& importOptions) final; - - virtual std::unique_ptr<mongo::RecordStore> getRecordStore(OperationContext* opCtx, - const NamespaceString& nss, - StringData ident, - const CollectionOptions& options); - - virtual std::unique_ptr<mongo::RecordStore> makeTemporaryRecordStore( - OperationContext* opCtx, StringData ident, KeyFormat keyFormat) override; - - virtual Status createSortedDataInterface(OperationContext* opCtx, - const NamespaceString& nss, - const CollectionOptions& collOptions, - StringData ident, - const IndexDescriptor* desc); - - virtual Status importSortedDataInterface(OperationContext* opCtx, - StringData ident, - const BSONObj& storageMetadata, - const ImportOptions& importOptions) final; - - virtual Status dropSortedDataInterface(OperationContext* opCtx, StringData ident) { - return Status::OK(); - } - - virtual std::unique_ptr<mongo::SortedDataInterface> getSortedDataInterface( - OperationContext* opCtx, - const NamespaceString& nss, - KeyFormat rsKeyFormat, - StringData ident, - const IndexDescriptor* desc); - - virtual std::unique_ptr<mongo::SortedDataInterface> getSortedDataInterface( - OperationContext* opCtx, - const NamespaceString& nss, - const CollectionOptions& collOptions, - StringData ident, - const IndexDescriptor* desc); - - virtual Status beginBackup(OperationContext* opCtx) { - return Status::OK(); - } - - virtual void endBackup(OperationContext* opCtx) {} - - virtual Status dropIdent(mongo::RecoveryUnit* ru, - StringData ident, - StorageEngine::DropIdentCallback&& onDrop); - - virtual void dropIdentForImport(OperationContext* opCtx, StringData ident) {} - - virtual bool supportsDirectoryPerDB() const { - return false; // Not persistant so no Directories - } - - virtual bool supportsCappedCollections() const { - return true; - } - - /** - * Ephemeral for test does not write to disk. - */ - virtual bool isDurable() const { - return false; - } - - virtual bool isEphemeral() const { - return true; - } - - virtual int64_t getIdentSize(OperationContext* opCtx, StringData ident) { - return 0; - } - - virtual Status repairIdent(OperationContext* opCtx, StringData ident) { - return Status::OK(); - } - - virtual bool hasIdent(OperationContext* opCtx, StringData ident) const { - return true; - } - - std::vector<std::string> getAllIdents(OperationContext* opCtx) const { - std::vector<std::string> idents; - for (const auto& i : _idents) { - idents.push_back(i.first); - } - return idents; - } - - virtual void cleanShutdown(){}; - - void setJournalListener(mongo::JournalListener* jl) final {} - - virtual Timestamp getAllDurableTimestamp() const override { - RecordId id = _visibilityManager->getAllCommittedRecord(); - return Timestamp(id.getLong()); - } - - boost::optional<Timestamp> getOplogNeededForCrashRecovery() const final { - return boost::none; - } - - // Ephemeral for test Specific - - /** - * Returns a pair of the current version and a shared_ptr of tree of the master at the provided - * timestamp. Null timestamps will return the latest master and timestamps before oldest - * timestamp will throw SnapshotTooOld exception. - */ - std::pair<uint64_t, std::shared_ptr<StringStore>> getMasterInfo( - boost::optional<Timestamp> timestamp = boost::none); - - /** - * Returns true and swaps _master to newMaster if the version passed in is the same as the - * masters current version. - */ - bool trySwapMaster(StringStore& newMaster, uint64_t version); - - VisibilityManager* visibilityManager() { - return _visibilityManager.get(); - } - - /** - * History in the map that is older than the oldest timestamp can be removed. Additionally, if - * the tree at the oldest timestamp is no longer in use by any active transactions it can be - * cleaned up, up until the point where there's an active transaction in the map. That point - * also becomes the new oldest timestamp. - */ - void cleanHistory(); - - Timestamp getOldestTimestamp() const override; - - Timestamp getStableTimestamp() const override { - return Timestamp(); - } - - void setOldestTimestamp(Timestamp newOldestTimestamp, bool force) override; - - std::map<Timestamp, std::shared_ptr<StringStore>> getHistory_forTest(); - - boost::optional<Timestamp> getRecoveryTimestamp() const override { - return boost::none; - } - - static bool instanceExists(); - - void setPinnedOplogTimestamp(const Timestamp& pinnedTimestamp) {} - - void dump() const override {} - -private: - void _cleanHistory(WithLock); - - Timestamp _getOldestTimestamp(WithLock) const { - return _availableHistory.begin()->first; - } - - std::shared_ptr<void> _catalogInfo; - int _cachePressureForTest = 0; - mutable Mutex _identsLock = MONGO_MAKE_LATCH("KVEngine::_identsLock"); - std::map<std::string, bool> _idents; // TODO : replace with a query to _master. - std::unique_ptr<VisibilityManager> _visibilityManager; - - mutable Mutex _masterLock = MONGO_MAKE_LATCH("KVEngine::_masterLock"); - std::shared_ptr<StringStore> _master; - // While write transactions aren't implemented, we use the _masterVersion to generate mock - // commit timestamps. We need to start at 1 to avoid the null timestamp. - uint64_t _masterVersion = 1; - - // This map contains the different versions of the StringStore's referenced by their commit - // timestamps. - std::map<Timestamp, std::shared_ptr<StringStore>> _availableHistory; -}; -} // namespace ephemeral_for_test -} // namespace mongo diff --git a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_kv_engine_test.cpp b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_kv_engine_test.cpp deleted file mode 100644 index f129c04baa2..00000000000 --- a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_kv_engine_test.cpp +++ /dev/null @@ -1,497 +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. - */ - -#include "mongo/platform/basic.h" - -#include "mongo/db/storage/kv/kv_engine_test_harness.h" - -#include <memory> - -#include "mongo/base/init.h" -#include "mongo/db/operation_context_noop.h" -#include "mongo/db/repl/replication_coordinator_mock.h" -#include "mongo/db/service_context.h" -#include "mongo/db/service_context_test_fixture.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_kv_engine.h" -#include "mongo/unittest/unittest.h" - -namespace mongo { -namespace ephemeral_for_test { - -class KVHarnessHelper : public mongo::KVHarnessHelper { -public: - KVHarnessHelper(ServiceContext* svcCtx) : _engine(std::make_unique<KVEngine>()) { - repl::ReplicationCoordinator::set( - svcCtx, - std::unique_ptr<repl::ReplicationCoordinator>(new repl::ReplicationCoordinatorMock( - getGlobalServiceContext(), repl::ReplSettings()))); - } - - virtual KVEngine* getEngine() override { - return _engine.get(); - } - - virtual KVEngine* restartEngine() override { - return _engine.get(); - } - -private: - std::unique_ptr<KVEngine> _engine; -}; - -std::unique_ptr<mongo::KVHarnessHelper> makeHelper(ServiceContext* svcCtx) { - return std::make_unique<KVHarnessHelper>(svcCtx); -} - -MONGO_INITIALIZER(RegisterEphemeralForTestKVHarnessFactory)(InitializerContext*) { - KVHarnessHelper::registerFactory(makeHelper); -} - -class EphemeralForTestKVEngineTest : public ServiceContextTest { -public: - EphemeralForTestKVEngineTest() : _helper(getServiceContext()), _engine(_helper.getEngine()) {} - - ServiceContext::UniqueOperationContext makeOpCtx() { - auto opCtx = makeOperationContext(); - opCtx->setRecoveryUnit(std::unique_ptr<RecoveryUnit>(_engine->newRecoveryUnit()), - WriteUnitOfWork::RecoveryUnitState::kNotInUnitOfWork); - opCtx->swapLockState(std::make_unique<LockerNoop>(), WithLock::withoutLock()); - return opCtx; - } - - std::vector<std::pair<ServiceContext::UniqueClient, ServiceContext::UniqueOperationContext>> - makeOpCtxs(unsigned num) { - std::vector<std::pair<ServiceContext::UniqueClient, ServiceContext::UniqueOperationContext>> - opCtxs; - opCtxs.reserve(num); - - for (unsigned i = 0; i < num; ++i) { - auto client = getServiceContext()->makeClient(std::to_string(i)); - - auto opCtx = client->makeOperationContext(); - opCtx->setRecoveryUnit(std::unique_ptr<RecoveryUnit>(_engine->newRecoveryUnit()), - WriteUnitOfWork::RecoveryUnitState::kNotInUnitOfWork); - opCtx->swapLockState(std::make_unique<LockerNoop>(), WithLock::withoutLock()); - - opCtxs.emplace_back(std::move(client), std::move(opCtx)); - } - - return opCtxs; - } - -protected: - std::unique_ptr<KVHarnessHelper> helper; - KVHarnessHelper _helper; - KVEngine* _engine; -}; - -TEST_F(EphemeralForTestKVEngineTest, AvailableHistoryUpdate) { - NamespaceString nss("a.b"); - std::string ident = "collection-1234"; - std::string record = "abcd"; - CollectionOptions defaultCollectionOptions; - - std::unique_ptr<mongo::RecordStore> rs; - { - auto opCtx = makeOpCtx(); - ASSERT_OK(_engine->createRecordStore(opCtx.get(), nss, ident, defaultCollectionOptions)); - rs = _engine->getRecordStore(opCtx.get(), nss, ident, defaultCollectionOptions); - ASSERT(rs); - } - - Timestamp lastMaster; - Timestamp currentMaster; - - ASSERT_EQ(1, _engine->getHistory_forTest().size()); - currentMaster = _engine->getHistory_forTest().rbegin()->first; - ASSERT_EQ(_engine->getOldestTimestamp(), currentMaster); - - { - auto opCtx = makeOpCtx(); - WriteUnitOfWork uow(opCtx.get()); - StatusWith<RecordId> res = - rs->insertRecord(opCtx.get(), record.c_str(), record.length() + 1, Timestamp()); - ASSERT_OK(res.getStatus()); - uow.commit(); - } - - ASSERT_EQ(1, _engine->getHistory_forTest().size()); - lastMaster = currentMaster; - currentMaster = _engine->getHistory_forTest().rbegin()->first; - ASSERT_GT(currentMaster, lastMaster); - ASSERT_EQ(_engine->getOldestTimestamp(), currentMaster); -} - -TEST_F(EphemeralForTestKVEngineTest, PinningOldestTimestampWithReadTransaction) { - NamespaceString nss("a.b"); - std::string ident = "collection-1234"; - std::string record = "abcd"; - CollectionOptions defaultCollectionOptions; - - std::unique_ptr<mongo::RecordStore> rs; - { - auto opCtx = makeOpCtx(); - ASSERT_OK(_engine->createRecordStore(opCtx.get(), nss, ident, defaultCollectionOptions)); - rs = _engine->getRecordStore(opCtx.get(), nss, ident, defaultCollectionOptions); - ASSERT(rs); - } - - // _availableHistory starts off with master. - ASSERT_EQ(1, _engine->getHistory_forTest().size()); - - RecordId loc; - { - auto opCtx = makeOpCtx(); - WriteUnitOfWork uow(opCtx.get()); - StatusWith<RecordId> res = - rs->insertRecord(opCtx.get(), record.c_str(), record.length() + 1, Timestamp()); - ASSERT_OK(res.getStatus()); - loc = res.getValue(); - uow.commit(); - } - - auto opCtxs = makeOpCtxs(2); - - auto opCtxRead = opCtxs[0].second.get(); - RecordData rd; - ASSERT(rs->findRecord(opCtxRead, loc, &rd)); - - { - auto opCtx = opCtxs[1].second.get(); - WriteUnitOfWork uow(opCtx); - StatusWith<RecordId> res = - rs->insertRecord(opCtx, record.c_str(), record.length() + 1, Timestamp()); - ASSERT_OK(res.getStatus()); - uow.commit(); - } - - // Open read transaction prevents deletion of history. - ASSERT_EQ(2, _engine->getHistory_forTest().size()); - ASSERT_GT(_engine->getHistory_forTest().rbegin()->first, _engine->getOldestTimestamp()); -} - -TEST_F(EphemeralForTestKVEngineTest, SettingOldestTimestampClearsHistory) { - NamespaceString nss("a.b"); - std::string ident = "collection-1234"; - std::string record = "abcd"; - CollectionOptions defaultCollectionOptions; - - std::unique_ptr<mongo::RecordStore> rs; - { - auto opCtx = makeOpCtx(); - ASSERT_OK(_engine->createRecordStore(opCtx.get(), nss, ident, defaultCollectionOptions)); - rs = _engine->getRecordStore(opCtx.get(), nss, ident, defaultCollectionOptions); - ASSERT(rs); - } - - // _availableHistory starts off with master. - ASSERT_EQ(1, _engine->getHistory_forTest().size()); - - RecordId loc; - { - auto opCtx = makeOpCtx(); - WriteUnitOfWork uow(opCtx.get()); - StatusWith<RecordId> res = - rs->insertRecord(opCtx.get(), record.c_str(), record.length() + 1, Timestamp()); - ASSERT_OK(res.getStatus()); - loc = res.getValue(); - uow.commit(); - } - - auto opCtxs = makeOpCtxs(2); - - auto opCtxRead = opCtxs[0].second.get(); - RecordData rd; - ASSERT(rs->findRecord(opCtxRead, loc, &rd)); - - { - auto opCtx = opCtxs[1].second.get(); - WriteUnitOfWork uow(opCtx); - StatusWith<RecordId> res = - rs->insertRecord(opCtx, record.c_str(), record.length() + 1, Timestamp()); - ASSERT_OK(res.getStatus()); - uow.commit(); - } - - ASSERT_EQ(2, _engine->getHistory_forTest().size()); - _engine->setOldestTimestamp(_engine->getHistory_forTest().rbegin()->first, false); - ASSERT_EQ(1, _engine->getHistory_forTest().size()); -} - -TEST_F(EphemeralForTestKVEngineTest, SettingOldestTimestampToMax) { - NamespaceString nss("a.b"); - std::string ident = "collection-1234"; - std::string record = "abcd"; - CollectionOptions defaultCollectionOptions; - - std::unique_ptr<mongo::RecordStore> rs; - { - auto opCtx = makeOpCtx(); - ASSERT_OK(_engine->createRecordStore(opCtx.get(), nss, ident, defaultCollectionOptions)); - rs = _engine->getRecordStore(opCtx.get(), nss, ident, defaultCollectionOptions); - ASSERT(rs); - } - - { - auto opCtx = makeOpCtx(); - WriteUnitOfWork uow(opCtx.get()); - StatusWith<RecordId> res = - rs->insertRecord(opCtx.get(), record.c_str(), record.length() + 1, Timestamp()); - ASSERT_OK(res.getStatus()); - uow.commit(); - } - - // Check that setting oldest to Timestamp::max() does not clear history. - ASSERT_GTE(_engine->getHistory_forTest().size(), 1); - ASSERT_LT(_engine->getHistory_forTest().rbegin()->first, Timestamp::max()); - _engine->setOldestTimestamp(Timestamp::max(), true); - ASSERT_GTE(_engine->getHistory_forTest().size(), 1); - ASSERT_EQ(Timestamp::max(), _engine->getHistory_forTest().rbegin()->first); -} - -TEST_F(EphemeralForTestKVEngineTest, CleanHistoryWithOpenTransaction) { - NamespaceString nss("a.b"); - std::string ident = "collection-1234"; - std::string record = "abcd"; - CollectionOptions defaultCollectionOptions; - - std::unique_ptr<mongo::RecordStore> rs; - { - auto opCtx = makeOpCtx(); - ASSERT_OK(_engine->createRecordStore(opCtx.get(), nss, ident, defaultCollectionOptions)); - rs = _engine->getRecordStore(opCtx.get(), nss, ident, defaultCollectionOptions); - ASSERT(rs); - } - - // _availableHistory starts off with master. - ASSERT_EQ(1, _engine->getHistory_forTest().size()); - - RecordId loc; - { - auto opCtx = makeOpCtx(); - WriteUnitOfWork uow(opCtx.get()); - StatusWith<RecordId> res = - rs->insertRecord(opCtx.get(), record.c_str(), record.length() + 1, Timestamp()); - ASSERT_OK(res.getStatus()); - loc = res.getValue(); - uow.commit(); - } - - auto opCtxs = makeOpCtxs(2); - - auto opCtxRead = opCtxs[0].second.get(); - Timestamp readTime1 = _engine->getHistory_forTest().rbegin()->first; - RecordData rd; - ASSERT(rs->findRecord(opCtxRead, loc, &rd)); - - { - auto opCtx = opCtxs[1].second.get(); - WriteUnitOfWork uow(opCtx); - StatusWith<RecordId> res = - rs->insertRecord(opCtx, record.c_str(), record.length() + 1, Timestamp()); - ASSERT_OK(res.getStatus()); - uow.commit(); - } - - Timestamp readTime2 = _engine->getHistory_forTest().rbegin()->first; - - { - auto opCtx = opCtxs[1].second.get(); - WriteUnitOfWork uow(opCtx); - StatusWith<RecordId> res = - rs->insertRecord(opCtx, record.c_str(), record.length() + 1, Timestamp()); - ASSERT_OK(res.getStatus()); - uow.commit(); - } - - // Destruct the client used for writes prior to checking use_count(). - opCtxs.pop_back(); - - Timestamp readTime3 = _engine->getHistory_forTest().rbegin()->first; - _engine->cleanHistory(); - - // use_count() should be {2, 1, 2} without the copy from getHistory_forTest(). - ASSERT_EQ(3, _engine->getHistory_forTest().size()); - ASSERT_EQ(3, _engine->getHistory_forTest().at(readTime1).use_count()); - ASSERT_EQ(2, _engine->getHistory_forTest().at(readTime2).use_count()); - ASSERT_EQ(3, _engine->getHistory_forTest().at(readTime3).use_count()); -} - -TEST_F(EphemeralForTestKVEngineTest, ReadOlderSnapshotsSimple) { - NamespaceString nss("a.b"); - std::string ident = "collection-1234"; - std::string record = "abcd"; - CollectionOptions defaultCollectionOptions; - - std::unique_ptr<mongo::RecordStore> rs; - { - auto opCtx = makeOpCtx(); - ASSERT_OK(_engine->createRecordStore(opCtx.get(), nss, ident, defaultCollectionOptions)); - rs = _engine->getRecordStore(opCtx.get(), nss, ident, defaultCollectionOptions); - ASSERT(rs); - } - - auto opCtxs = makeOpCtxs(2); - - // Pin oldest timestamp with a read transaction. - auto pinningOldest = opCtxs[0].second.get(); - ASSERT(!rs->findRecord(pinningOldest, RecordId(1), nullptr)); - - // Set readFrom to timestamp with no committed transactions. - Timestamp readFrom = _engine->getHistory_forTest().rbegin()->first; - - auto opCtx = opCtxs[1].second.get(); - WriteUnitOfWork uow1(opCtx); - StatusWith<RecordId> res1 = - rs->insertRecord(opCtx, record.c_str(), record.length() + 1, Timestamp()); - ASSERT_OK(res1.getStatus()); - RecordId loc1 = res1.getValue(); - uow1.commit(); - - WriteUnitOfWork uow2(opCtx); - StatusWith<RecordId> res2 = - rs->insertRecord(opCtx, record.c_str(), record.length() + 1, Timestamp()); - ASSERT_OK(res2.getStatus()); - RecordId loc2 = res2.getValue(); - uow2.commit(); - - RecordData rd; - opCtx->recoveryUnit()->abandonSnapshot(); - opCtx->recoveryUnit()->setTimestampReadSource(RecoveryUnit::ReadSource::kProvided, readFrom); - ASSERT(!rs->findRecord(opCtx, loc1, &rd)); - ASSERT(!rs->findRecord(opCtx, loc2, &rd)); - - opCtx->recoveryUnit()->abandonSnapshot(); - opCtx->recoveryUnit()->setTimestampReadSource(RecoveryUnit::ReadSource::kNoTimestamp); - ASSERT(rs->findRecord(opCtx, loc1, &rd)); - ASSERT(rs->findRecord(opCtx, loc2, &rd)); -} - -TEST_F(EphemeralForTestKVEngineTest, ReadOutdatedSnapshot) { - NamespaceString nss("a.b"); - std::string ident = "collection-1234"; - std::string record = "abcd"; - CollectionOptions defaultCollectionOptions; - - std::unique_ptr<mongo::RecordStore> rs; - { - auto opCtx = makeOpCtx(); - ASSERT_OK(_engine->createRecordStore(opCtx.get(), nss, ident, defaultCollectionOptions)); - rs = _engine->getRecordStore(opCtx.get(), nss, ident, defaultCollectionOptions); - ASSERT(rs); - } - - RecordId loc1; - { - auto opCtx = makeOpCtx(); - WriteUnitOfWork uow(opCtx.get()); - StatusWith<RecordId> res = - rs->insertRecord(opCtx.get(), record.c_str(), record.length() + 1, Timestamp()); - ASSERT_OK(res.getStatus()); - loc1 = res.getValue(); - uow.commit(); - } - - auto opCtxs = makeOpCtxs(2); - - auto opCtxRead = opCtxs[0].second.get(); - RecordData rd; - ASSERT(rs->findRecord(opCtxRead, loc1, &rd)); - Timestamp readFrom = _engine->getHistory_forTest().rbegin()->first; - - RecordId loc2; - { - auto opCtx = opCtxs[1].second.get(); - WriteUnitOfWork uow(opCtx); - StatusWith<RecordId> res = - rs->insertRecord(opCtx, record.c_str(), record.length() + 1, Timestamp()); - ASSERT_OK(res.getStatus()); - loc2 = res.getValue(); - uow.commit(); - } - - // Destruct the client used for writes prior to trying to get a snapshot too old. - opCtxs.pop_back(); - - ASSERT(rs->findRecord(opCtxRead, loc1, &rd)); - opCtxRead->recoveryUnit()->abandonSnapshot(); - opCtxRead->recoveryUnit()->setTimestampReadSource(RecoveryUnit::ReadSource::kProvided, - readFrom); - ASSERT_THROWS_CODE( - rs->findRecord(opCtxRead, loc1, &rd), DBException, ErrorCodes::SnapshotTooOld); -} - -TEST_F(EphemeralForTestKVEngineTest, SetReadTimestampBehindOldestTimestamp) { - NamespaceString nss("a.b"); - std::string ident = "collection-1234"; - std::string record = "abcd"; - CollectionOptions defaultCollectionOptions; - - std::unique_ptr<mongo::RecordStore> rs; - { - auto opCtx = makeOpCtx(); - ASSERT_OK(_engine->createRecordStore(opCtx.get(), nss, ident, defaultCollectionOptions)); - rs = _engine->getRecordStore(opCtx.get(), nss, ident, defaultCollectionOptions); - ASSERT(rs); - } - - RecordId loc1; - { - auto opCtx = makeOpCtx(); - WriteUnitOfWork uow(opCtx.get()); - StatusWith<RecordId> res = - rs->insertRecord(opCtx.get(), record.c_str(), record.length() + 1, Timestamp()); - ASSERT_OK(res.getStatus()); - loc1 = res.getValue(); - uow.commit(); - } - - RecordData rd; - Timestamp readFrom = _engine->getHistory_forTest().begin()->first; - auto opCtx = makeOpCtx(); - WriteUnitOfWork uow(opCtx.get()); - StatusWith<RecordId> res = - rs->insertRecord(opCtx.get(), record.c_str(), record.length() + 1, Timestamp()); - ASSERT_OK(res.getStatus()); - RecordId loc2 = res.getValue(); - uow.commit(); - - opCtx->recoveryUnit()->setTimestampReadSource(RecoveryUnit::ReadSource::kProvided, readFrom); - _engine->setOldestTimestamp(Timestamp::max(), true); - ASSERT_THROWS_CODE( - rs->findRecord(opCtx.get(), loc2, &rd), DBException, ErrorCodes::SnapshotTooOld); - - opCtx->recoveryUnit()->abandonSnapshot(); - opCtx->recoveryUnit()->setTimestampReadSource(RecoveryUnit::ReadSource::kNoTimestamp); - ASSERT(rs->findRecord(opCtx.get(), loc1, &rd)); - ASSERT(rs->findRecord(opCtx.get(), loc2, &rd)); -} - -} // namespace ephemeral_for_test -} // namespace mongo 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 diff --git a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store_concurrent_test.cpp b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store_concurrent_test.cpp deleted file mode 100644 index f63379ff2a5..00000000000 --- a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store_concurrent_test.cpp +++ /dev/null @@ -1,412 +0,0 @@ -/** - * Copyright (C) 2020-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. - */ - -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kTest - -#include "mongo/platform/basic.h" - -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store.h" -#include "mongo/logv2/log.h" -#include "mongo/platform/mutex.h" -#include "mongo/stdx/condition_variable.h" -#include "mongo/stdx/thread.h" -#include "mongo/unittest/unittest.h" - -#include <boost/thread/barrier.hpp> -#include <tuple> - -namespace mongo { -namespace ephemeral_for_test { - -// Helper fixture to run radix tree modifications in parallel on different threads. The result will -// be merged into a master tree and any merge conflicts will be retried. The fixture remembers the -// order of operations that get merged in and replays the jobs on a single thread to validate that -// it produces the same result. -template <std::size_t NumThreads> -class ConcurrentRadixStoreTest : public unittest::Test { -public: - ConcurrentRadixStoreTest() {} - virtual ~ConcurrentRadixStoreTest() { - _checkValid(_store); - } - - template <class SetupFunc> - void setup(SetupFunc func) { - auto [tree, version] = _head(); - func(tree); - ASSERT_TRUE(_commit(tree, version, 0)); - _executionOrder.clear(); - _setupFunc = func; - } - - template <class... WorkFuncs> - void initThreads(WorkFuncs&&... funcs) { - // Create index sequence so we can access the indices of the funcs parameter pack - initThreadsImpl_(std::index_sequence_for<WorkFuncs...>{}, - std::forward<WorkFuncs>(funcs)...); - } - - template <class Rep, class Period> - StringStore runThreads(stdx::chrono::duration<Rep, Period> dur) { - _barrier.count_down_and_wait(); - stdx::this_thread::sleep_for(dur); - _stop.store(true); - for (auto& thread : _threads) - thread.join(); - - auto result = _fork(); - - // Play back the same operations on a single thread in the order they executed to ensure - // that we get the same end result - StringStore playback; - if (_setupFunc) - _setupFunc(playback); - std::array<std::size_t, NumThreads> terms; - terms.fill(0); - - for (auto op : _executionOrder) { - _workFuncs[op](playback, terms[op]++); - } - - if (result != playback) { - LOGV2_ERROR_OPTIONS(4785800, - {logv2::LogTruncation::Disabled}, - "Execution order of failure", - "order"_attr = _executionOrder, - "concurrent"_attr = result.to_string_for_test(), - "serial"_attr = playback.to_string_for_test()); - ASSERT(false); - } - - return result; - } - -private: - // Wrapper for unittest work that handles thread synchronization and merging of radix trees - template <class WorkFunc> - class Worker { - public: - Worker(ConcurrentRadixStoreTest& thisTest, std::size_t threadIndex, WorkFunc func) - : _thisTest(thisTest), _func(std::move(func)), _index(threadIndex) {} - - void operator()() { - // Wait until all threads have reached this point before we begin - _thisTest._barrier.count_down_and_wait(); - - // Term is a counter for the number of successful commits this thread has done - std::size_t term = 0; - while (!_thisTest._stop.load()) { - // Take a copy of the current master tree into base and make a working copy that we - // apply our operations to - StringStore base; - uint64_t baseVersion; - - std::tie(base, baseVersion) = _thisTest._head(); - auto copy = base; - // Perform the actual work. The work must be deterministic for every value of term. - bool change = _func(copy, term); - if (!change) - continue; - - // Try merging our tree with head of master tree, retry as long as we are not - // merging with the latest version - uint64_t version; - bool committed = true; - do { - StringStore head; - std::tie(head, version) = _thisTest._head(); - try { - copy.merge3(base, head); - } catch (const merge_conflict_exception&) { - // Retry this operation in case of merge conflict - committed = false; - break; - } - - // Update base to be latest that we know of - base = std::move(head); - } while (!_thisTest._commit(copy, version, _index)); - if (committed) { - ++term; - _checkValid(copy); - } - } - } - - private: - ConcurrentRadixStoreTest& _thisTest; - WorkFunc _func; - std::size_t _index; - }; - - template <class WorkFunc> - friend class Worker; - - template <std::size_t... Is, class... WorkFuncs> - void initThreadsImpl_(std::index_sequence<Is...>, const WorkFuncs&... funcs) { - // Index sequence as a helper type to retrieve indexes for the WorkFuncs while expanding the - // parameter pack. It is a helper type with template integer template arguments - // 'std::index_sequence<0, 1, ..., N>'. Then we expand both parameter packs simultaneously - // and we can therefore associate each WorkerThread (and its WorkerFunc) with an index. - _threads = {stdx::thread(Worker<WorkFuncs>(*this, Is, funcs))...}; - _workFuncs = {funcs...}; - } - - StringStore _fork() const { - stdx::lock_guard lock(_mutex); - return _store; - } - - std::tuple<StringStore, uint64_t> _head() const { - stdx::lock_guard lock(_mutex); - return std::make_tuple(_store, _version); - } - - bool _commit(StringStore tree, uint64_t checkedOutVersion, std::size_t threadIndex) { - stdx::lock_guard lock(_mutex); - if (checkedOutVersion != _version) - return false; - - _store = std::move(tree); - ++_version; - _executionOrder.push_back(threadIndex); - return true; - } - - static void _checkValid(StringStore& store) { - size_t actualSize = 0; - size_t actualDataSize = 0; - std::string lastKey = ""; - for (auto& item : store) { - ASSERT_GT(item.first, lastKey); - actualDataSize += item.second.size(); - actualSize++; - } - ASSERT_EQ(store.size(), actualSize); - ASSERT_EQ(store.dataSize(), actualDataSize); - } - - mutable Mutex _mutex; - StringStore _store; - uint64_t _version; - - std::array<stdx::thread, NumThreads> _threads; - boost::barrier _barrier{NumThreads + 1}; - AtomicWord<bool> _stop{false}; - - std::vector<int> _executionOrder; - std::function<void(StringStore&)> _setupFunc; - std::array<std::function<bool(StringStore&, std::size_t)>, NumThreads> _workFuncs; -}; - -// Helper to be be able to create a fixture with template parameters -class ConcurrentRadixStoreTestFourThreads : public ConcurrentRadixStoreTest<4> {}; -class ConcurrentRadixStoreTestNineThreads : public ConcurrentRadixStoreTest<9> {}; - -TEST_F(ConcurrentRadixStoreTestFourThreads, UpdateDifferentKeysDifferentBranches) { - setup([](StringStore& tree) { - tree.insert({"a", ""}); - tree.insert({"b", ""}); - tree.insert({"c", ""}); - tree.insert({"d", ""}); - }); - - std::string s1; - std::string s2; - std::string s3; - std::string s4; - initThreads( - [&s1](StringStore& tree, std::size_t term) { - s1 = std::string(term, 'a'); - return tree.update({"a", s1}).second; - }, - [&s2](StringStore& tree, std::size_t term) { - s2 = std::string(term, 'b'); - return tree.update({"b", s2}).second; - }, - [&s3](StringStore& tree, std::size_t term) { - s3 = std::string(term, 'c'); - return tree.update({"c", s3}).second; - }, - [&s4](StringStore& tree, std::size_t term) { - s4 = std::string(term, 'd'); - return tree.update({"d", s4}).second; - }); - auto result = runThreads(stdx::chrono::seconds(3)); - ASSERT_EQ(result.find("a")->second, s1); - ASSERT_EQ(result.find("b")->second, s2); - ASSERT_EQ(result.find("c")->second, s3); - ASSERT_EQ(result.find("d")->second, s4); -} - - -TEST_F(ConcurrentRadixStoreTestFourThreads, UpdateDifferentKeysSameBranch) { - setup([](StringStore& tree) { - tree.insert({"a", ""}); - tree.insert({"aa", ""}); - tree.insert({"aaa", ""}); - tree.insert({"aaaa", ""}); - }); - - std::string s1; - std::string s2; - std::string s3; - std::string s4; - initThreads( - [&s1](StringStore& tree, std::size_t term) { - s1 = std::string(term, 'a'); - return tree.update({"a", s1}).second; - }, - [&s2](StringStore& tree, std::size_t term) { - s2 = std::string(term, 'b'); - return tree.update({"aa", s2}).second; - }, - [&s3](StringStore& tree, std::size_t term) { - s3 = std::string(term, 'c'); - return tree.update({"aaa", s3}).second; - }, - [&s4](StringStore& tree, std::size_t term) { - s4 = std::string(term, 'd'); - return tree.update({"aaaa", s4}).second; - }); - auto result = runThreads(stdx::chrono::seconds(3)); - ASSERT_EQ(result.find("a")->second, s1); - ASSERT_EQ(result.find("aa")->second, s2); - ASSERT_EQ(result.find("aaa")->second, s3); - ASSERT_EQ(result.find("aaaa")->second, s4); -} - -TEST_F(ConcurrentRadixStoreTestFourThreads, UpdateSameKey) { - setup([](StringStore& tree) { tree.insert({"key", ""}); }); - - initThreads( - [](StringStore& tree, std::size_t term) { - return tree.update({"key", "a"}).second; - }, - [](StringStore& tree, std::size_t term) { - return tree.update({"key", "b"}).second; - }, - [](StringStore& tree, std::size_t term) { - return tree.update({"key", "c"}).second; - }, - [](StringStore& tree, std::size_t term) { - return tree.update({"key", "d"}).second; - }); - auto result = runThreads(stdx::chrono::seconds(3)); - auto res = result.find("key")->second; - ASSERT(std::string("abcd").find(res) != std::string::npos); -} - -TEST_F(ConcurrentRadixStoreTestFourThreads, InsertUpdateEraseSameKey) { - initThreads([](StringStore& tree, std::size_t term) { return tree.erase("key"); }, - [](StringStore& tree, std::size_t term) { - return tree.insert({"key", "a"}).second; - }, - [](StringStore& tree, std::size_t term) { - return tree.update({"key", "b"}).second; - }, - [](StringStore& tree, std::size_t term) { - return tree.update({"key", "c"}).second; - }); - auto result = runThreads(stdx::chrono::seconds(3)); - if (auto res = result.find("key"); res != result.end()) { - ASSERT(std::string("abc").find(res->second) != std::string::npos); - } -} - -TEST_F(ConcurrentRadixStoreTestFourThreads, InsertEraseSubtree) { - initThreads( - [](StringStore& tree, std::size_t term) { - return tree.insert({"aaa", "a"}).second; - }, - [](StringStore& tree, std::size_t term) { - return tree.insert({"aaaa", "a"}).second; - }, - [](StringStore& tree, std::size_t term) { - return tree.insert({"aaab", "b"}).second; - }, - [](StringStore& tree, std::size_t term) { return tree.erase("aaa"); }); - auto result = runThreads(stdx::chrono::seconds(3)); - if (auto res = result.find("aaa"); res != result.end()) { - ASSERT_EQ(res->second, "a"); - } - if (auto res = result.find("aaaa"); res != result.end()) { - ASSERT_EQ(res->second, "a"); - } - if (auto res = result.find("aaab"); res != result.end()) { - ASSERT_EQ(res->second, "b"); - } -} - -TEST_F(ConcurrentRadixStoreTestNineThreads, InsertEraseUpdateSameBranch) { - AtomicWord<int> aTerm{0}; - AtomicWord<int> aaTerm{0}; - AtomicWord<int> aaaTerm{0}; - initThreads( - [&](StringStore& tree, std::size_t term) { - aTerm.store(term); - return tree.insert({"a", std::string(term, 'a')}).second; - }, - [&](StringStore& tree, std::size_t term) { - aaTerm.store(term); - return tree.insert({"aa", std::string(term, 'a')}).second; - }, - [&](StringStore& tree, std::size_t term) { - aaaTerm.store(term); - return tree.insert({"aaa", std::string(term, 'a')}).second; - }, - [&](StringStore& tree, std::size_t term) { - aTerm.store(term); - return tree.update({"a", std::string(term, 'a')}).second; - }, - [&](StringStore& tree, std::size_t term) { - aaTerm.store(term); - return tree.update({"aa", std::string(term, 'a')}).second; - }, - [&](StringStore& tree, std::size_t term) { - aaaTerm.store(term); - return tree.update({"aaa", std::string(term, 'a')}).second; - }, - [](StringStore& tree, std::size_t term) { return tree.erase("a"); }, - [](StringStore& tree, std::size_t term) { return tree.erase("aa"); }, - [](StringStore& tree, std::size_t term) { return tree.erase("aaa"); }); - auto result = runThreads(stdx::chrono::seconds(3)); - if (auto res = result.find("a"); res != result.end()) { - ASSERT_EQ(res->second, std::string(aTerm.load(), 'a')); - } - if (auto res = result.find("aa"); res != result.end()) { - ASSERT_EQ(res->second, std::string(aaTerm.load(), 'a')); - } - if (auto res = result.find("aaa"); res != result.end()) { - ASSERT_EQ(res->second, std::string(aaaTerm.load(), 'a')); - } -} - -} // namespace ephemeral_for_test -} // namespace mongo diff --git a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store_test.cpp b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store_test.cpp deleted file mode 100644 index 46b16109d96..00000000000 --- a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store_test.cpp +++ /dev/null @@ -1,3066 +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. - */ - -#include <deque> - -#include "mongo/platform/basic.h" - -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store.h" -#include "mongo/unittest/unittest.h" - -namespace mongo { -namespace ephemeral_for_test { - -using value_type = StringStore::value_type; - -class RadixStoreTest : public unittest::Test { -public: - using node_type = StringStore::Node; - - virtual ~RadixStoreTest() { - checkValid(thisStore); - checkValid(parallelStore); - checkValid(otherStore); - checkValid(baseStore); - checkValid(expected); - } - - StringStore::Head* getRootAddress() const { - return thisStore._root.get(); - } - - int getRootCount() const { - return thisStore._root->refCount(); - } - - bool hasPreviousVersion() const { - return thisStore._root->hasPreviousVersion(); - } - - void checkValid(StringStore& store) const { - size_t actualSize = 0; - size_t actualDataSize = 0; - std::string lastKey = ""; - for (auto& item : store) { - ASSERT_GT(item.first, lastKey); - actualDataSize += item.second.size(); - actualSize++; - } - ASSERT_EQ(store.size(), actualSize); - ASSERT_EQ(store.dataSize(), actualDataSize); - - checkNumChildrenValid(store); - } - - /** - * Returns all nodes in "store" with level order traversal. - */ - std::vector<node_type*> allNodes(StringStore& store) const { - std::deque<node_type*> level(1, store._root.get()); - std::vector<node_type*> result(1, store._root.get()); - while (!level.empty()) { - auto node = level.front(); - StringStore::_forEachChild( - node, 0, false, [&level, &result](boost::intrusive_ptr<node_type> child) { - level.push_back(child.get()); - result.push_back(child.get()); - return true; - }); - level.pop_front(); - } - return result; - } - - /** - * Checks if the number of children and _numChildren are equal in each node of the 'store'. - */ - void checkNumChildrenValid(StringStore& store) const { - auto nodes = allNodes(store); - for (const auto& node : nodes) { - uint16_t numChildren = 0; - StringStore::_forEachChild( - node, 0, false, [&numChildren](boost::intrusive_ptr<node_type> child) { - ++numChildren; - return true; - }); - ASSERT_EQ(numChildren, node->numChildren()); - } - } - - void debug(StringStore& store) const { - std::cout << "Memory: " << store._metrics.totalMemory.load() << std::endl - << "Nodes: " << store._metrics.totalNodes.load() << std::endl - << "Children: " << store._metrics.totalChildren.load() << std::endl; - } - -protected: - StringStore thisStore; - StringStore parallelStore; - StringStore otherStore; - StringStore baseStore; - StringStore expected; -}; - -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(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)); - 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(RadixStoreTest, IteratorRevalidateOneTest) { - 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)); - thisStore.insert(value_type(value2)); - - StringStore::const_iterator thisIter = thisStore.begin(); - ASSERT_EQ((*thisIter).first, "foo"); - thisIter++; - ASSERT_EQ((*thisIter).first, "food"); - - thisStore.insert(value_type(value3)); - ASSERT_TRUE(thisIter != thisStore.end()); - ASSERT_EQ((*thisIter).first, "food"); -} - -TEST_F(RadixStoreTest, IteratorRevalidateTwoTest) { - value_type value1 = std::make_pair("food", "1"); - value_type value2 = std::make_pair("foo", "2"); - value_type value3 = std::make_pair("zebra", "3"); - - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - thisStore.insert(value_type(value3)); - - StringStore::const_iterator thisIter = thisStore.begin(); - ASSERT_EQ((*thisIter).first, "foo"); - thisIter++; - ASSERT_EQ((*thisIter).first, "food"); - thisStore.erase("food"); - - ASSERT_EQ((*thisIter).first, "zebra"); -} - -TEST_F(RadixStoreTest, IteratorRevalidateThreeTest) { - value_type value1 = std::make_pair("food", "1"); - value_type value2 = std::make_pair("foo", "2"); - value_type value3 = std::make_pair("zebra", "3"); - - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - thisStore.insert(value_type(value3)); - - StringStore::const_iterator thisIter = thisStore.begin(); - ASSERT_EQ((*thisIter).first, "foo"); - thisIter++; - ASSERT_EQ((*thisIter).first, "food"); - thisStore.erase("zebra"); - - ASSERT_EQ((*thisIter).first, "food"); -} - -TEST_F(RadixStoreTest, IteratorRevalidateFourTest) { - value_type value1 = std::make_pair("food", "1"); - value_type value2 = std::make_pair("grass", "2"); - value_type value3 = std::make_pair("zebra", "3"); - - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value3)); - - StringStore::const_iterator thisIter = thisStore.begin(); - ASSERT_EQ((*thisIter).first, "food"); - thisStore.erase("food"); - thisStore.insert(value_type(value2)); - - ASSERT_EQ((*thisIter).first, "grass"); -} - -TEST_F(RadixStoreTest, IteratorRevalidateFiveTest) { - value_type value1 = std::make_pair("food", "1"); - value_type value2 = std::make_pair("grass", "2"); - - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - - StringStore::const_iterator thisIter = thisStore.begin(); - ASSERT_EQ((*thisIter).first, "food"); - thisStore.erase("food"); - thisStore.insert(value_type(value1)); - - ASSERT_EQ((*thisIter).first, "food"); -} - -TEST_F(RadixStoreTest, IteratorRevalidateSixTest) { - value_type value1 = std::make_pair("food", "1"); - value_type value2 = std::make_pair("grass", "2"); - value_type value3 = std::make_pair("food", "3"); - - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - - StringStore::const_iterator thisIter = thisStore.begin(); - ASSERT_EQ((*thisIter).first, "food"); - thisStore.update(value_type(value3)); - - ASSERT_EQ((*thisIter).first, "food"); - ASSERT_EQ((*thisIter).second, "3"); -} - -TEST_F(RadixStoreTest, IteratorRevalidateSevenTest) { - value_type value1 = std::make_pair("food", "1"); - value_type value2 = std::make_pair("grass", "2"); - value_type value3 = std::make_pair("zebra", "3"); - - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - thisStore.insert(value_type(value3)); - - StringStore::const_iterator thisIter = thisStore.begin(); - ASSERT_EQ((*thisIter).first, "food"); - thisStore.erase("food"); - thisStore.erase("grass"); - - ASSERT_EQ((*thisIter).first, "zebra"); -} - -TEST_F(RadixStoreTest, IteratorRevalidateEightTest) { - value_type value1 = std::make_pair("food", "1"); - value_type value2 = std::make_pair("grass", "2"); - value_type value3 = std::make_pair("zebra", "3"); - - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - thisStore.insert(value_type(value3)); - - StringStore::const_iterator thisIter = thisStore.begin(); - ASSERT_EQ((*thisIter).first, "food"); - thisStore.erase("food"); - thisStore.erase("grass"); - thisStore.erase("zebra"); - - ASSERT_TRUE(thisIter == thisStore.end()); -} - -TEST_F(RadixStoreTest, ReverseIteratorRevalidateOneTest) { - 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)); - thisStore.insert(value_type(value2)); - - StringStore::const_reverse_iterator thisIter = thisStore.rbegin(); - ASSERT_EQ((*thisIter).first, "food"); - thisIter++; - ASSERT_EQ((*thisIter).first, "foo"); - - thisStore.insert(value_type(value3)); - ASSERT_TRUE(thisIter != thisStore.rend()); - ASSERT_EQ((*thisIter).first, "foo"); -} - -TEST_F(RadixStoreTest, ReverseIteratorRevalidateTwoTest) { - value_type value1 = std::make_pair("food", "1"); - value_type value2 = std::make_pair("foo", "2"); - value_type value3 = std::make_pair("zebra", "3"); - - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - thisStore.insert(value_type(value3)); - - StringStore::const_reverse_iterator thisIter = thisStore.rbegin(); - ASSERT_EQ((*thisIter).first, "zebra"); - thisIter++; - ASSERT_EQ((*thisIter).first, "food"); - thisStore.erase("food"); - - ASSERT_EQ((*thisIter).first, "foo"); -} - -TEST_F(RadixStoreTest, ReverseIteratorRevalidateThreeTest) { - value_type value1 = std::make_pair("food", "1"); - value_type value2 = std::make_pair("foo", "2"); - value_type value3 = std::make_pair("zebra", "3"); - - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - thisStore.insert(value_type(value3)); - - StringStore::const_reverse_iterator thisIter = thisStore.rbegin(); - ASSERT_EQ((*thisIter).first, "zebra"); - thisIter++; - ASSERT_EQ((*thisIter).first, "food"); - thisStore.erase("foo"); - - ASSERT_EQ((*thisIter).first, "food"); -} - -TEST_F(RadixStoreTest, ReverseIteratorRevalidateFourTest) { - value_type value1 = std::make_pair("food", "1"); - value_type value2 = std::make_pair("grass", "2"); - value_type value3 = std::make_pair("zebra", "3"); - - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value3)); - - StringStore::const_reverse_iterator thisIter = thisStore.rbegin(); - ASSERT_EQ((*thisIter).first, "zebra"); - thisStore.erase("zebra"); - thisStore.insert(value_type(value2)); - - ASSERT_EQ((*thisIter).first, "grass"); -} - -TEST_F(RadixStoreTest, ReverseIteratorRevalidateFiveTest) { - value_type value1 = std::make_pair("food", "1"); - value_type value2 = std::make_pair("grass", "2"); - - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - - StringStore::const_reverse_iterator thisIter = thisStore.rbegin(); - ASSERT_EQ((*thisIter).first, "grass"); - thisStore.erase("grass"); - thisStore.insert(value_type(value2)); - - ASSERT_EQ((*thisIter).first, "grass"); -} - -TEST_F(RadixStoreTest, ReverseIteratorRevalidateSixTest) { - value_type value1 = std::make_pair("food", "1"); - value_type value2 = std::make_pair("grass", "2"); - value_type value3 = std::make_pair("grass", "3"); - - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - - StringStore::const_reverse_iterator thisIter = thisStore.rbegin(); - ASSERT_EQ((*thisIter).first, "grass"); - thisStore.update(value_type(value3)); - - ASSERT_EQ((*thisIter).first, "grass"); - ASSERT_EQ((*thisIter).second, "3"); -} - -TEST_F(RadixStoreTest, ReverseIteratorRevalidateSevenTest) { - value_type value1 = std::make_pair("food", "1"); - value_type value2 = std::make_pair("grass", "2"); - value_type value3 = std::make_pair("zebra", "3"); - - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - thisStore.insert(value_type(value3)); - - StringStore::const_reverse_iterator thisIter = thisStore.rbegin(); - ASSERT_EQ((*thisIter).first, "zebra"); - thisStore.erase("food"); - thisStore.erase("grass"); - - ASSERT_EQ((*thisIter).first, "zebra"); -} - -TEST_F(RadixStoreTest, ReverseIteratorRevalidateEightTest) { - value_type value1 = std::make_pair("food", "1"); - value_type value2 = std::make_pair("grass", "2"); - value_type value3 = std::make_pair("zebra", "3"); - - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - thisStore.insert(value_type(value3)); - - StringStore::const_reverse_iterator thisIter = thisStore.rbegin(); - ASSERT_EQ((*thisIter).first, "zebra"); - thisStore.erase("food"); - thisStore.erase("grass"); - thisStore.erase("zebra"); - - ASSERT_TRUE(thisIter == thisStore.rend()); -} - -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)); - 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); - - // 'otherStore' should have a 'fee' object. - check_other++; - ASSERT_TRUE(check_other->first == value3.first); - - // Both should point to different "fee" nodes due to the insertion of 'fed' splitting - // the 'fee' node in other. - ASSERT_NOT_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(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)); - thisStore.insert(value_type(value2)); - - 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(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)); - 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); - - // 'otherStore' should have a 'fee' object. - check_other++; - ASSERT_TRUE(check_other->first == value3.first); - - // Both should point to different "fee" nodes due to the insertion of 'fed' splitting - // the 'fee' node in other. - ASSERT_NOT_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(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 a "feed" node. - ASSERT_TRUE(check_this->first == check_other->first); - - // The 'feed' nodes should be different due to the insertion of 'fee' splitting the 'feed' - // node in 'otherStore' - ASSERT_NOT_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)); - thisStore.insert(value_type(value3)); - ASSERT_EQ(thisStore.size(), StringStore::size_type(3)); - - StringStore::const_iterator iter1 = thisStore.find(value1.first); - ASSERT_FALSE(iter1 == thisStore.end()); - ASSERT_TRUE(*iter1 == value1); - - StringStore::const_iterator iter2 = thisStore.find(value2.first); - ASSERT_FALSE(iter2 == thisStore.end()); - ASSERT_TRUE(*iter2 == value2); - - StringStore::const_iterator iter3 = thisStore.find(value3.first); - ASSERT_FALSE(iter3 == thisStore.end()); - ASSERT_TRUE(*iter3 == value3); - - StringStore::const_iterator iter4 = thisStore.find("fooze"); - ASSERT_TRUE(iter4 == thisStore.end()); -} - -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, UpdateExistingSameDataTest) { - thisStore.insert({"a", "a"}); - ASSERT_FALSE(thisStore.update({"a", "a"}).second); -} - -TEST_F(RadixStoreTest, DuplicateKeyTest) { - std::string msg1 = "Hello, world!"; - std::string msg2 = msg1 + "!!"; - value_type value1 = std::make_pair("msg", msg1); - value_type value2 = std::make_pair("msg", msg2); - - ASSERT(thisStore.insert(value_type(value1)).second); - ASSERT_EQ(thisStore.size(), 1u); - ASSERT_EQ(thisStore.dataSize(), msg1.size()); - - ASSERT(!thisStore.insert(value_type(value2)).second); - ASSERT_EQ(thisStore.size(), 1u); - ASSERT_EQ(thisStore.dataSize(), msg1.size()); -} - -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 value3 = std::make_pair("feed", "3"); - - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - thisStore.insert(value_type(value3)); - - otherStore = thisStore; - - StringStore::size_type success = otherStore.erase(value3.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 == value3.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 value4 = std::make_pair("feed", "4"); - - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - thisStore.insert(value_type(value3)); - thisStore.insert(value_type(value4)); - - otherStore = thisStore; - - bool 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 == value4.first); - - // 'thisStore' should have a 'feed' node. - this_it++; - ASSERT_TRUE(this_it->first == value4.first); - - // Both iterators should point to different 'feed' objects because erasing 'fee' from - // 'otherStore' caused 'feed' to be compressed. - ASSERT_NOT_EQUALS(&*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, EraseInternalNodeShouldFail) { - thisStore.insert({"aaaa", "a"}); - thisStore.insert({"aaab", "b"}); - ASSERT_FALSE(thisStore.erase("aaa")); -} - -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(); - - // Both 'iter' and 'copy_iter' should point to 'bar'. - ASSERT_EQ(iter->first, value2.first); - ASSERT_EQ(copy_iter->first, value2.first); - - // The insertion of 'baz' split the 'bar' node in 'copy_iter', so these - // nodes should be different. - ASSERT_NOT_EQUALS(&*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"; - - value_type value1 = std::make_pair("1", str1); - value_type value2 = std::make_pair("2", str2); - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - ASSERT_EQ(thisStore.dataSize(), str1.size() + str2.size()); -} - -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::const_iterator begin = thisStore.begin(); - StringStore::const_iterator second = thisStore.begin(); - ++second; - StringStore::const_iterator end = thisStore.end(); - - ASSERT_EQ(thisStore.distance(begin, end), 4); - ASSERT_EQ(thisStore.distance(second, end), 3); -} - -TEST_F(RadixStoreTest, MergeNoModifications) { - value_type value1 = std::make_pair("foo", "1"); - value_type value2 = std::make_pair("bar", "2"); - - baseStore.insert(value_type(value1)); - baseStore.insert(value_type(value2)); - - thisStore = baseStore; - otherStore = baseStore; - - expected.insert(value_type(value1)); - expected.insert(value_type(value2)); - - thisStore.merge3(baseStore, otherStore); - - ASSERT_TRUE(thisStore == expected); - - ASSERT_EQ(expected.size(), StringStore::size_type(2)); - ASSERT_EQ(thisStore.size(), StringStore::size_type(2)); - ASSERT_EQ(baseStore.size(), StringStore::size_type(2)); - - ASSERT_EQ(expected.dataSize(), StringStore::size_type(2)); - ASSERT_EQ(thisStore.dataSize(), StringStore::size_type(2)); - ASSERT_EQ(baseStore.dataSize(), StringStore::size_type(2)); - - int itemsVisited = 0; - StringStore::const_iterator thisIter = thisStore.begin(); - while (thisIter != thisStore.end()) { - itemsVisited++; - thisIter++; - } - - ASSERT_EQ(itemsVisited, 2); -} - -TEST_F(RadixStoreTest, MergeNoModificationsSharedKeyPrefix) { - value_type value1 = std::make_pair("foo", "1"); - value_type value2 = std::make_pair("food", "2"); - - baseStore.insert(value_type(value1)); - baseStore.insert(value_type(value2)); - - thisStore = baseStore; - otherStore = baseStore; - - expected.insert(value_type(value1)); - expected.insert(value_type(value2)); - - thisStore.merge3(baseStore, otherStore); - - ASSERT_TRUE(thisStore == expected); - - ASSERT_EQ(expected.size(), StringStore::size_type(2)); - ASSERT_EQ(thisStore.size(), StringStore::size_type(2)); - ASSERT_EQ(baseStore.size(), StringStore::size_type(2)); - - ASSERT_EQ(expected.dataSize(), StringStore::size_type(2)); - ASSERT_EQ(thisStore.dataSize(), StringStore::size_type(2)); - ASSERT_EQ(baseStore.dataSize(), StringStore::size_type(2)); - - int itemsVisited = 0; - StringStore::const_iterator thisIter = thisStore.begin(); - while (thisIter != thisStore.end()) { - itemsVisited++; - thisIter++; - } - - ASSERT_EQ(itemsVisited, 2); -} - -TEST_F(RadixStoreTest, MergeModifications) { - value_type value1 = std::make_pair("foo", "1"); - value_type value2 = std::make_pair("foo", "1234"); - - value_type value3 = std::make_pair("bar", "1"); - value_type value4 = std::make_pair("bar", "1234"); - - baseStore.insert(value_type(value1)); - baseStore.insert(value_type(value3)); - - thisStore = baseStore; - otherStore = baseStore; - - ASSERT_EQ(baseStore.size(), StringStore::size_type(2)); - ASSERT_EQ(thisStore.size(), StringStore::size_type(2)); - ASSERT_EQ(otherStore.size(), StringStore::size_type(2)); - - thisStore.update(value_type(value2)); - ASSERT_EQ(thisStore.dataSize(), StringStore::size_type(5)); - - otherStore.update(value_type(value4)); - ASSERT_EQ(otherStore.dataSize(), StringStore::size_type(5)); - - expected.insert(value_type(value2)); - expected.insert(value_type(value4)); - ASSERT_EQ(expected.dataSize(), StringStore::size_type(8)); - - thisStore.merge3(baseStore, otherStore); - - ASSERT_TRUE(thisStore == expected); - - ASSERT_EQ(expected.dataSize(), StringStore::size_type(8)); - ASSERT_EQ(thisStore.dataSize(), StringStore::size_type(8)); - ASSERT_EQ(baseStore.dataSize(), StringStore::size_type(2)); - - int itemsVisited = 0; - StringStore::const_iterator thisIter = thisStore.begin(); - while (thisIter != thisStore.end()) { - itemsVisited++; - thisIter++; - } - - ASSERT_EQ(itemsVisited, 2); -} - -TEST_F(RadixStoreTest, MergeDeletions) { - value_type value1 = std::make_pair("foo", "1"); - value_type value2 = std::make_pair("moo", "2"); - value_type value3 = std::make_pair("bar", "3"); - value_type value4 = std::make_pair("baz", "4"); - 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)); - - thisStore.merge3(baseStore, otherStore); - - ASSERT_TRUE(thisStore == expected); - - ASSERT_EQ(expected.size(), StringStore::size_type(2)); - ASSERT_EQ(thisStore.size(), StringStore::size_type(2)); - ASSERT_EQ(baseStore.size(), StringStore::size_type(4)); - - ASSERT_EQ(expected.dataSize(), StringStore::size_type(2)); - ASSERT_EQ(thisStore.dataSize(), StringStore::size_type(2)); - ASSERT_EQ(baseStore.dataSize(), StringStore::size_type(4)); - - int itemsVisited = 0; - StringStore::const_iterator thisIter = thisStore.begin(); - while (thisIter != thisStore.end()) { - itemsVisited++; - thisIter++; - } - - ASSERT_EQ(itemsVisited, 2); -} - -TEST_F(RadixStoreTest, MergeInsertions) { - value_type value1 = std::make_pair("foo", "1"); - value_type value2 = std::make_pair("moo", "2"); - value_type value3 = std::make_pair("bar", "3"); - value_type value4 = std::make_pair("cat", "4"); - - 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)); - expected.insert(value_type(value4)); - - thisStore.merge3(baseStore, otherStore); - - ASSERT_TRUE(thisStore == expected); - - ASSERT_EQ(expected.size(), StringStore::size_type(4)); - ASSERT_EQ(thisStore.size(), StringStore::size_type(4)); - ASSERT_EQ(baseStore.size(), StringStore::size_type(2)); - - ASSERT_EQ(expected.dataSize(), StringStore::size_type(4)); - ASSERT_EQ(thisStore.dataSize(), StringStore::size_type(4)); - ASSERT_EQ(baseStore.dataSize(), StringStore::size_type(2)); - - int itemsVisited = 0; - StringStore::const_iterator thisIter = thisStore.begin(); - while (thisIter != thisStore.end()) { - itemsVisited++; - thisIter++; - } - - ASSERT_EQ(itemsVisited, 4); -} - -TEST_F(RadixStoreTest, MergeAllDifferentLeafOnlyOtherChanged) { - baseStore.insert({"aa", "a"}); - - otherStore = baseStore; - otherStore.update({"aa", "b"}); - - thisStore = baseStore; - // Force 'aa' to get a new node but still be a leaf - thisStore.insert({"aaa", "a"}); - thisStore.erase("aaa"); - - // This should not be a merge conflict, only other actually changed 'aa' - thisStore.merge3(baseStore, otherStore); - - expected.insert({"aa", "b"}); - ASSERT_TRUE(thisStore == expected); -} - -TEST_F(RadixStoreTest, MergeConflictingPathCompressedKeys) { - // This test creates a "simple" merge problem where 'otherStore' has an insertion, and - // 'thisStore' has a non-conflicting insertion. However, due to the path compression, the trees - // end up looking slightly different and present a challenging case. - value_type value1 = std::make_pair("fod", "1"); - value_type value2 = std::make_pair("foda", "2"); - value_type value3 = std::make_pair("fol", "3"); - - baseStore.insert(value_type(value1)); - thisStore = baseStore; - otherStore = baseStore; - - otherStore.insert(value_type(value2)); - thisStore.insert(value_type(value3)); - - expected.insert(value_type(value1)); - expected.insert(value_type(value2)); - expected.insert(value_type(value3)); - - thisStore.merge3(baseStore, otherStore); - - ASSERT_TRUE(thisStore == expected); - - ASSERT_EQ(otherStore.size(), StringStore::size_type(2)); - ASSERT_EQ(thisStore.size(), StringStore::size_type(3)); - ASSERT_EQ(baseStore.size(), StringStore::size_type(1)); - - ASSERT_EQ(otherStore.dataSize(), StringStore::size_type(2)); - ASSERT_EQ(thisStore.dataSize(), StringStore::size_type(3)); - ASSERT_EQ(baseStore.dataSize(), StringStore::size_type(1)); - - int itemsVisited = 0; - StringStore::const_iterator thisIter = thisStore.begin(); - while (thisIter != thisStore.end()) { - itemsVisited++; - thisIter++; - } - - ASSERT_EQ(itemsVisited, 3); -} - -TEST_F(RadixStoreTest, MergeConflictingPathCompressedKeys2) { - // This test is similar to the one above with slight different looking trees. - value_type value1 = std::make_pair("foe", "1"); - value_type value2 = std::make_pair("fod", "2"); - value_type value3 = std::make_pair("fol", "3"); - - baseStore.insert(value_type(value1)); - thisStore = baseStore; - otherStore = baseStore; - - otherStore.insert(value_type(value2)); - otherStore.erase(value1.first); - - thisStore.insert(value_type(value3)); - - expected.insert(value_type(value2)); - expected.insert(value_type(value3)); - - thisStore.merge3(baseStore, otherStore); - - ASSERT_TRUE(thisStore == expected); - - ASSERT_EQ(otherStore.size(), StringStore::size_type(1)); - ASSERT_EQ(thisStore.size(), StringStore::size_type(2)); - ASSERT_EQ(baseStore.size(), StringStore::size_type(1)); - ASSERT_EQ(expected.size(), StringStore::size_type(2)); - - ASSERT_EQ(otherStore.dataSize(), StringStore::size_type(1)); - ASSERT_EQ(thisStore.dataSize(), StringStore::size_type(2)); - ASSERT_EQ(baseStore.dataSize(), StringStore::size_type(1)); - ASSERT_EQ(expected.dataSize(), StringStore::size_type(2)); - - int itemsVisited = 0; - StringStore::const_iterator thisIter = thisStore.begin(); - while (thisIter != thisStore.end()) { - itemsVisited++; - thisIter++; - } - - ASSERT_EQ(itemsVisited, 2); -} - -TEST_F(RadixStoreTest, MergeDecompressNodeBeforeMergingChildren) { - // This test have a shared 'aa' internal node it forces recursion on by making sure all tree - // three touches it. It causes a merge conflict on the 'a' child on 'aa' where an erase of - // 'aaaaaa' happens on thisStore causing a node compression. This will lead to current and other - // inside merge3 having different trieKeys. Make sure children merged in during this state gets - // expanded correctly. - baseStore.insert({"aaaaaa", "a"}); - baseStore.insert({"aaaabb", "b"}); - baseStore.insert({"aab", "b"}); - - otherStore = baseStore; - otherStore.erase("aaaaaa"); - otherStore.insert({"aadd", "d"}); - otherStore.insert({"aade", "e"}); - - thisStore = baseStore; - thisStore.erase("aab"); - thisStore.erase("aaaabb"); - thisStore.insert({"aac", "c"}); - - thisStore.merge3(baseStore, otherStore); - ASSERT_EQ(thisStore.find("aadd")->second, "d"); -} - -TEST_F(RadixStoreTest, MergeFallbackToConflictResolutionAfterCompression) { - // This test works similarly to 'MergeDecompressNodeBeforeMergingChildren' above. But triggers - // additional conflict resolution on the compressed node 'aadd' that is erased in other. - baseStore.insert({"aaaaaa", "a"}); - baseStore.insert({"aaaabb", "b"}); - baseStore.insert({"aab", "b"}); - baseStore.insert({"aadd", "d"}); - baseStore.insert({"aaddd", "d"}); - baseStore.insert({"aaddb", "b"}); - - otherStore = baseStore; - otherStore.erase("aaaaaa"); - otherStore.erase("aadd"); - otherStore.erase("aaddd"); - - thisStore = baseStore; - thisStore.erase("aab"); - thisStore.erase("aaaabb"); - thisStore.erase("aaddb"); - - thisStore.merge3(baseStore, otherStore); - - ASSERT_EQ(thisStore.size(), 0); - ASSERT_EQ(std::distance(thisStore.begin(), thisStore.end()), 0); - ASSERT(thisStore == expected); -} - -TEST_F(RadixStoreTest, MergeEmptyInsertionOther) { - value_type value1 = std::make_pair("foo", "bar"); - - thisStore = baseStore; - otherStore = baseStore; - - otherStore.insert(value_type(value1)); - - thisStore.merge3(baseStore, otherStore); - - ASSERT_TRUE(thisStore == otherStore); - - ASSERT_EQ(otherStore.size(), StringStore::size_type(1)); - ASSERT_EQ(thisStore.size(), StringStore::size_type(1)); - ASSERT_EQ(baseStore.size(), StringStore::size_type(0)); - - ASSERT_EQ(otherStore.dataSize(), StringStore::size_type(3)); - ASSERT_EQ(thisStore.dataSize(), StringStore::size_type(3)); - ASSERT_EQ(baseStore.dataSize(), StringStore::size_type(0)); - - int itemsVisited = 0; - StringStore::const_iterator thisIter = thisStore.begin(); - while (thisIter != thisStore.end()) { - itemsVisited++; - thisIter++; - } - - ASSERT_EQ(itemsVisited, 1); -} - -TEST_F(RadixStoreTest, MergeEmptyInsertionThis) { - value_type value1 = std::make_pair("foo", "bar"); - - thisStore = baseStore; - otherStore = baseStore; - - thisStore.insert(value_type(value1)); - - thisStore.merge3(baseStore, otherStore); - - ASSERT_TRUE(thisStore == thisStore); - - ASSERT_EQ(otherStore.size(), StringStore::size_type(0)); - ASSERT_EQ(thisStore.size(), StringStore::size_type(1)); - ASSERT_EQ(baseStore.size(), StringStore::size_type(0)); - - ASSERT_EQ(otherStore.dataSize(), StringStore::size_type(0)); - ASSERT_EQ(thisStore.dataSize(), StringStore::size_type(3)); - ASSERT_EQ(baseStore.dataSize(), StringStore::size_type(0)); - - int itemsVisited = 0; - StringStore::const_iterator thisIter = thisStore.begin(); - while (thisIter != thisStore.end()) { - itemsVisited++; - thisIter++; - } - - ASSERT_EQ(itemsVisited, 1); -} - -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"); - value_type value4 = std::make_pair("4", "faz"); - value_type value5 = std::make_pair("5", "too"); - value_type value6 = std::make_pair("6", "moo"); - value_type value7 = std::make_pair("1", "1234"); - value_type value8 = std::make_pair("2", "12345"); - - 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(value5)); - expected.insert(value_type(value6)); - expected.insert(value_type(value7)); - expected.insert(value_type(value8)); - - thisStore.merge3(baseStore, otherStore); - - ASSERT_TRUE(thisStore == expected); - - ASSERT_EQ(otherStore.size(), StringStore::size_type(4)); - ASSERT_EQ(thisStore.size(), StringStore::size_type(4)); - ASSERT_EQ(baseStore.size(), StringStore::size_type(4)); - ASSERT_EQ(expected.size(), StringStore::size_type(4)); - - ASSERT_EQ(otherStore.dataSize(), StringStore::size_type(14)); - ASSERT_EQ(thisStore.dataSize(), StringStore::size_type(15)); - ASSERT_EQ(baseStore.dataSize(), StringStore::size_type(12)); - ASSERT_EQ(expected.dataSize(), StringStore::size_type(15)); - - int itemsVisited = 0; - StringStore::const_iterator thisIter = thisStore.begin(); - while (thisIter != thisStore.end()) { - itemsVisited++; - thisIter++; - } - - ASSERT_EQ(itemsVisited, 4); -} - -TEST_F(RadixStoreTest, MergeOnlyDataDifferenceInBranch) { - baseStore.insert({"a", ""}); - baseStore.insert({"aa", ""}); - - otherStore = baseStore; - otherStore.update({"a", "a"}); - - thisStore = baseStore; - thisStore.update({"aa", "b"}); - thisStore.merge3(baseStore, otherStore); - - expected.insert({"a", "a"}); - expected.insert({"aa", "b"}); - ASSERT_TRUE(thisStore == expected); -} - -TEST_F(RadixStoreTest, MergeSharedSubKey) { - otherStore = baseStore; - - otherStore.insert({"aaa", "a"}); - otherStore.insert({"aaab", "b"}); - - thisStore = baseStore; - thisStore.insert({"aaaa", "a"}); - thisStore.merge3(baseStore, otherStore); - - expected.insert({"aaa", "a"}); - expected.insert({"aaaa", "a"}); - expected.insert({"aaab", "b"}); - ASSERT_TRUE(thisStore == expected); -} - -TEST_F(RadixStoreTest, MergeInternalNodeTest) { - baseStore.insert({"a", "a"}); - baseStore.insert({"aaaa", "a"}); - baseStore.insert({"aaab", "a"}); - - otherStore = baseStore; - otherStore.insert({"aaa", "a"}); - - thisStore = baseStore; - thisStore.insert({"aa", "a"}); - - thisStore.merge3(baseStore, otherStore); - - expected.insert({"a", "a"}); - expected.insert({"aa", "a"}); - expected.insert({"aaa", "a"}); - expected.insert({"aaaa", "a"}); - expected.insert({"aaab", "a"}); - ASSERT_TRUE(thisStore == expected); -} - -TEST_F(RadixStoreTest, MergeBaseKeyNegativeCharTest) { - baseStore.insert({"aaa\xffq", "q"}); - - otherStore = baseStore; - otherStore.insert({"aab", "b"}); - - thisStore = baseStore; - thisStore.insert({"aac", "c"}); - - thisStore.merge3(baseStore, otherStore); - ASSERT_EQ(thisStore.find("aaa\xffq")->second, "q"); - ASSERT_EQ(thisStore.find("aab")->second, "b"); - ASSERT_EQ(thisStore.find("aac")->second, "c"); -} - -TEST_F(RadixStoreTest, MergeWillRemoveEmptyInternalLeaf) { - baseStore.insert({"aa", "a"}); - baseStore.insert({"ab", "b"}); - baseStore.insert({"ac", "c"}); - baseStore.insert({"ad", "d"}); - - otherStore = baseStore; - otherStore.erase("ac"); - otherStore.erase("ad"); - - thisStore = baseStore; - thisStore.erase("aa"); - thisStore.erase("ab"); - - thisStore.merge3(baseStore, otherStore); - - // The store is in a valid state that is traversable and we should find no nodes - ASSERT_EQ(std::distance(thisStore.begin(), thisStore.end()), 0); -} - -TEST_F(RadixStoreTest, MergeWillRemoveEmptyInternalLeafWithUnrelatedBranch) { - baseStore.insert({"aa", "a"}); - baseStore.insert({"ab", "b"}); - baseStore.insert({"ac", "c"}); - baseStore.insert({"ad", "d"}); - baseStore.insert({"b", "b"}); - - otherStore = baseStore; - otherStore.erase("ac"); - otherStore.erase("ad"); - otherStore.erase("b"); - - thisStore = baseStore; - thisStore.erase("aa"); - thisStore.erase("ab"); - - thisStore.merge3(baseStore, otherStore); - - // The store is in a valid state that is traversable and we should find no nodes - ASSERT_EQ(std::distance(thisStore.begin(), thisStore.end()), 0); -} - -TEST_F(RadixStoreTest, MergeWillCompressNodes) { - baseStore.insert({"aa", "a"}); - baseStore.insert({"ab", "b"}); - baseStore.insert({"ac", "c"}); - baseStore.insert({"ad", "d"}); - - otherStore = baseStore; - otherStore.erase("ab"); - otherStore.erase("ac"); - - thisStore = baseStore; - thisStore.erase("aa"); - - thisStore.merge3(baseStore, otherStore); - - // The store is in a valid state that is traversable and we should find a single node - ASSERT_EQ(thisStore.find("ad")->second, "d"); - ASSERT_EQ(std::distance(thisStore.begin(), thisStore.end()), 1); - - // Removing this node should not result in an internal leaf node without data - thisStore.erase("ad"); - - ASSERT_EQ(std::distance(thisStore.begin(), thisStore.end()), 0); -} - -TEST_F(RadixStoreTest, CompressNodeBeforeResolvingConflict) { - baseStore.insert({"aa", "a"}); - baseStore.insert({"ab", "b"}); - baseStore.insert({"ac", "c"}); - baseStore.insert({"ada", "da"}); - baseStore.insert({"adb", "db"}); - - otherStore = baseStore; - otherStore.erase("ac"); - otherStore.erase("adb"); - - thisStore = baseStore; - thisStore.erase("aa"); - thisStore.erase("ab"); - thisStore.erase("ada"); - - // 'adb' will need to be compressed after 'ac' is erased in the merge. Make sure conflict - // resulution can handle this. - thisStore.merge3(baseStore, otherStore); - - // The store is in a valid state that is traversable and we should find no nodes - ASSERT_EQ(std::distance(thisStore.begin(), thisStore.end()), 0); -} - -TEST_F(RadixStoreTest, MergeConflictingModifications) { - value_type value1 = std::make_pair("foo", "1"); - value_type value2 = std::make_pair("foo", "2"); - value_type value3 = std::make_pair("foo", "3"); - - baseStore.insert(value_type(value1)); - - thisStore = baseStore; - otherStore = baseStore; - - thisStore.update(value_type(value2)); - - otherStore.update(value_type(value3)); - - ASSERT_THROWS(thisStore.merge3(baseStore, otherStore), merge_conflict_exception); -} - -TEST_F(RadixStoreTest, MergeConflictingModifictionOtherAndDeletionThis) { - value_type value1 = std::make_pair("foo", "1"); - value_type value2 = std::make_pair("foo", "2"); - - 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(RadixStoreTest, MergeConflictingModifictionThisAndDeletionOther) { - value_type value1 = std::make_pair("foo", "1"); - value_type value2 = std::make_pair("foo", "2"); - - 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(RadixStoreTest, MergeConflictingInsertions) { - value_type value1 = std::make_pair("foo", "bar"); - value_type value2 = std::make_pair("foo", "bar"); - - thisStore = baseStore; - otherStore = baseStore; - - thisStore.insert(value_type(value2)); - - otherStore.insert(value_type(value1)); - - ASSERT_THROWS(thisStore.merge3(baseStore, otherStore), merge_conflict_exception); -} - -TEST_F(RadixStoreTest, MergeDifferentLeafNodesSameDataTest) { - baseStore.insert({"a", "a"}); - baseStore.insert({"aa", "a"}); - - otherStore = baseStore; - otherStore.insert({"aaa", "a"}); - otherStore.erase("aaa"); - - thisStore = baseStore; - thisStore.insert({"aab", "b"}); - thisStore.erase("aab"); - - thisStore.merge3(baseStore, otherStore); - - expected.insert({"a", "a"}); - expected.insert({"aa", "a"}); - ASSERT_TRUE(thisStore == expected); -} - -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::const_iterator iter = thisStore.upper_bound(value2.first); - ASSERT_EQ(iter->first, "baz"); - - iter++; - ASSERT_EQ(iter->first, "foo"); - - iter++; - ASSERT_EQ(iter->first, "fools"); - - iter++; - ASSERT_TRUE(iter == thisStore.end()); - - iter = thisStore.upper_bound(value4.first); - ASSERT_TRUE(iter == thisStore.end()); - - iter = thisStore.upper_bound("baa"); - ASSERT_EQ(iter->first, "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"); - value_type value5 = std::make_pair("baz", "5"); - - thisStore.insert(value_type(value3)); - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - thisStore.insert(value_type(value4)); - - 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("bac"); - ASSERT_EQ(iter->first, "bad"); - - iter++; - ASSERT_EQ(iter->first, "foo"); - - iter++; - ASSERT_EQ(iter->first, "fools"); - - iter++; - ASSERT_TRUE(iter == thisStore.end()); - - iter = thisStore.lower_bound("baz"); - ASSERT_TRUE(iter == thisStore.find("foo")); - - // Lower bound not found - iter = thisStore.lower_bound("fooze"); - ASSERT_TRUE(iter == thisStore.end()); - - iter = thisStore.lower_bound("fright"); - ASSERT_TRUE(iter == thisStore.end()); - - iter = thisStore.lower_bound("three"); - ASSERT_TRUE(iter == thisStore.end()); - - thisStore.insert(value_type(value5)); - iter = thisStore.lower_bound("bah"); - ASSERT_TRUE(iter == thisStore.find("baz")); -} - -TEST_F(RadixStoreTest, LowerBoundTestSmallerThanExistingPrefix) { - value_type value1 = std::make_pair("abcdef", "1"); - value_type value2 = std::make_pair("abc", "1"); - value_type value3 = std::make_pair("bah", "2"); - - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - thisStore.insert(value_type(value3)); - - // Test the various ways in which the key we are trying to lower - // bound can be smaller than existing keys it shares prefixes with. - - // Search key is smaller than existing key. - StringStore::const_iterator iter = thisStore.lower_bound("abcd"); - ASSERT_TRUE(iter != thisStore.end()); - ASSERT_EQ(iter->first, value1.first); - - // Smaller character at mismatch between search key and existing key. - StringStore::const_iterator iter2 = thisStore.lower_bound("abcda"); - ASSERT_TRUE(iter2 != thisStore.end()); - ASSERT_EQ(iter2->first, value1.first); -} - -TEST_F(RadixStoreTest, LowerBoundTestLargerThanExistingPrefix) { - value_type value1 = std::make_pair("abcdef", "1"); - value_type value2 = std::make_pair("abc", "1"); - value_type value3 = std::make_pair("agh", "1"); - - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - thisStore.insert(value_type(value3)); - - // Test the various ways in which the key we are trying to lower - // bound can be smaller than existing keys it shares prefixes with. - - // Search key is longer than existing key. - StringStore::const_iterator iter = thisStore.lower_bound("abcdefg"); - ASSERT_TRUE(iter != thisStore.end()); - ASSERT_EQ(iter->first, value3.first); - - // Larger character at mismatch between search key and existing key. - StringStore::const_iterator iter2 = thisStore.lower_bound("abcdz"); - ASSERT_TRUE(iter2 != thisStore.end()); - ASSERT_EQ(iter2->first, value3.first); -} - -TEST_F(RadixStoreTest, LowerBoundTestExactPrefixMatch) { - value_type value1 = std::make_pair("aba", "1"); - value_type value2 = std::make_pair("abd", "1"); - - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - - // Search for a string that matches a prefix in the tree with no value. - StringStore::const_iterator iter = thisStore.lower_bound("ab"); - ASSERT_TRUE(iter != thisStore.end()); - ASSERT_EQ(iter->first, value1.first); -} - -TEST_F(RadixStoreTest, LowerBoundTestNullCharacter) { - value_type value1 = std::make_pair(std::string("ab\0", 3), "1"); - value_type value2 = std::make_pair("abd", "1"); - - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - - StringStore::const_iterator iter = thisStore.lower_bound(std::string("ab")); - ASSERT_TRUE(iter != thisStore.end()); - ASSERT_EQ(iter->first, value1.first); -} - -TEST_F(RadixStoreTest, BasicInsertFindDeleteNullCharacter) { - value_type value1 = std::make_pair(std::string("ab\0", 3), "1"); - value_type value2 = std::make_pair("abd", "1"); - - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - - StringStore::const_iterator iter = thisStore.find(std::string("ab\0", 3)); - ASSERT_TRUE(iter != thisStore.end()); - ASSERT_EQ(iter->first, value1.first); - - ASSERT_TRUE(thisStore.erase(std::string("ab\0", 3))); - ASSERT_EQ(thisStore.size(), StringStore::size_type(1)); - - iter = thisStore.find(std::string("ab\0", 3)); - ASSERT_TRUE(iter == thisStore.end()); - - iter = thisStore.find(std::string("abd")); - ASSERT_TRUE(iter != thisStore.end()); - ASSERT_EQ(iter->first, value2.first); -} - -TEST_F(RadixStoreTest, IteratorTest) { - uint8_t keyArr[] = {97, 0, 0}; - int v = 0; - int cur = v; - - // Node4 - for (size_t i = 0; i < 4; ++i) { - ++keyArr[1]; - std::string keyStr(reinterpret_cast<char*>(keyArr)); - value_type value = std::make_pair(keyStr, std::to_string(++v)); - - thisStore.insert(value_type(value)); - } - - cur = 0; - for (auto iter = thisStore.begin(); iter != thisStore.end(); ++iter) { - ++cur; - ASSERT_EQ(iter->second, std::to_string(cur)); - } - ASSERT_EQ(cur, v); - - // Node16 - for (size_t i = 0; i < 12; ++i) { - ++keyArr[1]; - std::string keyStr(reinterpret_cast<char*>(keyArr)); - value_type value = std::make_pair(keyStr, std::to_string(++v)); - - thisStore.insert(value_type(value)); - } - - cur = 0; - for (auto iter = thisStore.begin(); iter != thisStore.end(); ++iter) { - ++cur; - ASSERT_EQ(iter->second, std::to_string(cur)); - } - ASSERT_EQ(cur, v); - - // Node48 - for (size_t i = 0; i < 32; ++i) { - ++keyArr[1]; - std::string keyStr(reinterpret_cast<char*>(keyArr)); - value_type value = std::make_pair(keyStr, std::to_string(++v)); - - thisStore.insert(value_type(value)); - } - - cur = 0; - for (auto iter = thisStore.begin(); iter != thisStore.end(); ++iter) { - ++cur; - ASSERT_EQ(iter->second, std::to_string(cur)); - } - ASSERT_EQ(cur, v); - - // Node256 - for (size_t i = 0; i < 207; ++i) { - ++keyArr[1]; - std::string keyStr(reinterpret_cast<char*>(keyArr)); - value_type value = std::make_pair(keyStr, std::to_string(++v)); - - thisStore.insert(value_type(value)); - } - - cur = 0; - for (auto iter = thisStore.begin(); iter != thisStore.end(); ++iter) { - ++cur; - ASSERT_EQ(iter->second, std::to_string(cur)); - } - ASSERT_EQ(cur, v); -} - -TEST_F(RadixStoreTest, ReverseIteratorTest) { - uint8_t keyArr[] = {97, 0, 0}; - int v = 0; - int cur = v; - - // Node4 - for (size_t i = 0; i < 4; ++i) { - ++keyArr[1]; - std::string keyStr(reinterpret_cast<char*>(keyArr)); - value_type value = std::make_pair(keyStr, std::to_string(++v)); - - thisStore.insert(value_type(value)); - } - - cur = v; - for (auto iter = thisStore.rbegin(); iter != thisStore.rend(); ++iter) { - ASSERT_EQ(iter->second, std::to_string(cur)); - --cur; - } - ASSERT_EQ(cur, 0); - - // Node16 - for (size_t i = 0; i < 12; ++i) { - ++keyArr[1]; - std::string keyStr(reinterpret_cast<char*>(keyArr)); - value_type value = std::make_pair(keyStr, std::to_string(++v)); - - thisStore.insert(value_type(value)); - } - - cur = v; - for (auto iter = thisStore.rbegin(); iter != thisStore.rend(); ++iter) { - ASSERT_EQ(iter->second, std::to_string(cur)); - --cur; - } - ASSERT_EQ(cur, 0); - - // Node48 - for (size_t i = 0; i < 32; ++i) { - ++keyArr[1]; - std::string keyStr(reinterpret_cast<char*>(keyArr)); - value_type value = std::make_pair(keyStr, std::to_string(++v)); - - thisStore.insert(value_type(value)); - } - - cur = v; - for (auto iter = thisStore.rbegin(); iter != thisStore.rend(); ++iter) { - ASSERT_EQ(iter->second, std::to_string(cur)); - --cur; - } - ASSERT_EQ(cur, 0); - - // Node256 - for (size_t i = 0; i < 207; ++i) { - ++keyArr[1]; - std::string keyStr(reinterpret_cast<char*>(keyArr)); - value_type value = std::make_pair(keyStr, std::to_string(++v)); - - thisStore.insert(value_type(value)); - } - - cur = v; - for (auto iter = thisStore.rbegin(); iter != thisStore.rend(); ++iter) { - 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); -} - -TEST_F(RadixStoreTest, PathCompressionTest) { - value_type value1 = std::make_pair("food", "1"); - value_type value2 = std::make_pair("foo", "2"); - value_type value3 = std::make_pair("bar", "3"); - value_type value4 = std::make_pair("batter", "4"); - value_type value5 = std::make_pair("batty", "5"); - value_type value6 = std::make_pair("bats", "6"); - value_type value7 = std::make_pair("foodie", "7"); - - thisStore.insert(value_type(value1)); - ASSERT_EQ(thisStore.to_string_for_test(), "\n food*\n"); - - // Add a key that is a prefix of a key already in the tree - thisStore.insert(value_type(value2)); - ASSERT_EQ(thisStore.to_string_for_test(), - "\n foo*" - "\n d*\n"); - - // Add a key with no prefix already in the tree - thisStore.insert(value_type(value3)); - ASSERT_EQ(thisStore.to_string_for_test(), - "\n bar*" - "\n foo*" - "\n d*\n"); - - // Add a key that shares a prefix with a key in the tree - thisStore.insert(value_type(value4)); - ASSERT_EQ(thisStore.to_string_for_test(), - "\n ba" - "\n r*" - "\n tter*" - "\n foo*" - "\n d*\n"); - - // Add another level to the tree - thisStore.insert(value_type(value5)); - ASSERT_EQ(thisStore.to_string_for_test(), - "\n ba" - "\n r*" - "\n tt" - "\n er*" - "\n y*" - "\n foo*" - "\n d*\n"); - - // Erase a key that causes the path to be compressed - thisStore.erase(value2.first); - ASSERT_EQ(thisStore.to_string_for_test(), - "\n ba" - "\n r*" - "\n tt" - "\n er*" - "\n y*" - "\n food*\n"); - - // Erase a key that causes the path to be compressed - thisStore.erase(value3.first); - ASSERT_EQ(thisStore.to_string_for_test(), - "\n batt" - "\n er*" - "\n y*" - "\n food*\n"); - - // Add a key that causes a node with children to be split - thisStore.insert(value_type(value6)); - ASSERT_EQ(thisStore.to_string_for_test(), - "\n bat" - "\n s*" - "\n t" - "\n er*" - "\n y*" - "\n food*\n"); - - // Add a key that has a prefix already in the tree with a value - thisStore.insert(value_type(value7)); - ASSERT_EQ(thisStore.to_string_for_test(), - "\n bat" - "\n s*" - "\n t" - "\n er*" - "\n y*" - "\n food*" - "\n ie*\n"); -} - -TEST_F(RadixStoreTest, MergeOneTest) { - value_type value1 = std::make_pair("<collection-1-first", "1"); - value_type value2 = std::make_pair("<collection-1-second", "2"); - value_type value3 = std::make_pair("<_catalog", "catalog"); - value_type value4 = std::make_pair("<collection-2-first", "1"); - value_type value5 = std::make_pair("<collection-2-second", "2"); - value_type value6 = std::make_pair("<collection-1-second", "20"); - value_type value7 = std::make_pair("<collection-1-second", "30"); - - baseStore.insert(value_type(value1)); - baseStore.insert(value_type(value2)); - baseStore.insert(value_type(value3)); - - otherStore = baseStore; - thisStore = baseStore; - parallelStore = baseStore; - - thisStore.update(value_type(value6)); - thisStore.update(value_type(value7)); - - parallelStore.insert(value_type(value5)); - parallelStore.insert(value_type(value4)); - - parallelStore.merge3(baseStore, otherStore); - thisStore.merge3(baseStore, parallelStore); - - int itemsVisited = 0; - StringStore::const_iterator thisIter = thisStore.begin(); - while (thisIter != thisStore.end()) { - itemsVisited++; - thisIter++; - } - - ASSERT_EQ(itemsVisited, 5); -} - -TEST_F(RadixStoreTest, MergeTwoTest) { - value_type value1 = std::make_pair("<collection-1-first", "1"); - value_type value2 = std::make_pair("<collection-1-second", "2"); - value_type value3 = std::make_pair("<_catalog", "catalog"); - value_type value4 = std::make_pair("<_index", "index"); - value_type value5 = std::make_pair("<collection-1-third", "1"); - value_type value6 = std::make_pair("<collection-1-forth", "2"); - value_type value7 = std::make_pair("<collection-1-first", "20"); - value_type value8 = std::make_pair("<collection-1-second", "30"); - - baseStore.insert(value_type(value3)); - baseStore.insert(value_type(value4)); - - otherStore = baseStore; - thisStore = baseStore; - parallelStore = baseStore; - - parallelStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - - parallelStore.merge3(baseStore, otherStore); - thisStore.merge3(baseStore, parallelStore); - - int itemsVisited = 0; - StringStore::const_iterator thisIter = thisStore.begin(); - while (thisIter != thisStore.end()) { - itemsVisited++; - thisIter++; - } - - ASSERT_EQ(itemsVisited, 4); -} - -TEST_F(RadixStoreTest, MergeThreeTest) { - value_type value1 = std::make_pair("<collection-1-first", "1"); - value_type value2 = std::make_pair("<collection-1-second", "2"); - value_type value3 = std::make_pair("<_catalog", "catalog"); - value_type value4 = std::make_pair("<_index", "index"); - value_type value5 = std::make_pair("<collection-1-third", "1"); - value_type value6 = std::make_pair("<collection-1-forth", "2"); - value_type value7 = std::make_pair("<collection-1-first", "20"); - value_type value8 = std::make_pair("<collection-1-second", "30"); - - baseStore.insert(value_type(value3)); - baseStore.insert(value_type(value4)); - - otherStore = baseStore; - thisStore = baseStore; - parallelStore = baseStore; - - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - thisStore.update(value_type(value7)); - thisStore.update(value_type(value8)); - thisStore.insert(value_type(value5)); - thisStore.insert(value_type(value6)); - - thisStore.merge3(baseStore, otherStore); - - int itemsVisited = 0; - StringStore::const_iterator thisIter = thisStore.begin(); - while (thisIter != thisStore.end()) { - itemsVisited++; - thisIter++; - } - - ASSERT_EQ(itemsVisited, 6); -} - -TEST_F(RadixStoreTest, MergeFourTest) { - value_type value1 = std::make_pair("<collection-1-first", "1"); - value_type value2 = std::make_pair("<collection-1-second", "2"); - value_type value3 = std::make_pair("<_catalog", "catalog"); - value_type value4 = std::make_pair("<_index", "index"); - value_type value5 = std::make_pair("<collection-1-third", "1"); - value_type value6 = std::make_pair("<collection-1-forth", "2"); - value_type value7 = std::make_pair("<collection-1-first", "20"); - value_type value8 = std::make_pair("<collection-1-second", "30"); - - baseStore.insert(value_type(value1)); - baseStore.insert(value_type(value3)); - baseStore.insert(value_type(value4)); - - otherStore = baseStore; - thisStore = baseStore; - - otherStore.insert(value_type(value2)); - otherStore.update(value_type(value7)); - - thisStore.merge3(baseStore, otherStore); - - int itemsVisited = 0; - StringStore::const_iterator thisIter = thisStore.begin(); - while (thisIter != thisStore.end()) { - itemsVisited++; - thisIter++; - } - - ASSERT_EQ(itemsVisited, 4); -} - -TEST_F(RadixStoreTest, MergeFiveTest) { - value_type value1 = std::make_pair("<collection-1-first", "1"); - value_type value2 = std::make_pair("<collection-1-second", "2"); - value_type value3 = std::make_pair("<_catalog", "catalog"); - value_type value4 = std::make_pair("<_index", "index"); - value_type value5 = std::make_pair("<collection-1-third", "1"); - value_type value6 = std::make_pair("<collection-1-forth", "2"); - value_type value7 = std::make_pair("<collection-1-first", "20"); - value_type value8 = std::make_pair("<collection-1-second", "30"); - - baseStore.insert(value_type(value1)); - baseStore.insert(value_type(value3)); - baseStore.insert(value_type(value4)); - - otherStore = baseStore; - thisStore = baseStore; - - otherStore.update(value_type(value7)); - thisStore.update(value_type(value7)); - - ASSERT_THROWS(thisStore.merge3(baseStore, otherStore), merge_conflict_exception); -} - -TEST_F(RadixStoreTest, MergeSixTest) { - value_type value1 = std::make_pair("<collection-1-first", "1"); - value_type value2 = std::make_pair("<collection-1-second", "2"); - value_type value3 = std::make_pair("<_catalog", "catalog"); - value_type value4 = std::make_pair("<_index", "index"); - value_type value5 = std::make_pair("<collection-1-third", "1"); - value_type value6 = std::make_pair("<collection-1-forth", "2"); - value_type value7 = std::make_pair("<collection-1-first", "20"); - value_type value8 = std::make_pair("<collection-1-second", "30"); - - baseStore.insert(value_type(value1)); - baseStore.insert(value_type(value3)); - baseStore.insert(value_type(value4)); - - otherStore = baseStore; - thisStore = baseStore; - - otherStore.update(value_type(value7)); - otherStore.insert(value_type(value2)); - - thisStore.update(value_type(value7)); - - ASSERT_THROWS(thisStore.merge3(baseStore, otherStore), merge_conflict_exception); -} - -TEST_F(RadixStoreTest, MergeSevenTest) { - value_type value1 = std::make_pair("<collection-1", "1"); - value_type value2 = std::make_pair("<collection-2", "2"); - - baseStore.insert(value_type(value1)); - - otherStore = baseStore; - thisStore = baseStore; - - otherStore.insert(value_type(value2)); - - thisStore.merge3(baseStore, otherStore); - - expected.insert(value_type(value1)); - expected.insert(value_type(value2)); - - ASSERT_TRUE(thisStore == expected); - ASSERT_TRUE(thisStore.size() == 2); - ASSERT_TRUE(thisStore.dataSize() == 2); -} - -TEST_F(RadixStoreTest, SizeTest) { - value_type value1 = std::make_pair("<index", "."); - value_type value2 = std::make_pair("<collection", ".."); - value_type value3 = std::make_pair("<collection-1", "..."); - value_type value4 = std::make_pair("<collection-2", "...."); - - thisStore.insert(value_type(value1)); - ASSERT_TRUE(thisStore.size() == 1); - ASSERT_TRUE(thisStore.dataSize() == 1); - - thisStore.insert(value_type(value2)); - ASSERT_TRUE(thisStore.size() == 2); - ASSERT_TRUE(thisStore.dataSize() == 3); - - thisStore.insert(value_type(value3)); - ASSERT_TRUE(thisStore.size() == 3); - ASSERT_TRUE(thisStore.dataSize() == 6); - - thisStore.insert(value_type(value4)); - ASSERT_TRUE(thisStore.size() == 4); - ASSERT_TRUE(thisStore.dataSize() == 10); - - thisStore.erase(value2.first); - ASSERT_TRUE(thisStore.size() == 3); - ASSERT_TRUE(thisStore.dataSize() == 8); - - thisStore.erase(value4.first); - ASSERT_TRUE(thisStore.size() == 2); - ASSERT_TRUE(thisStore.dataSize() == 4); - - thisStore.erase(value1.first); - ASSERT_TRUE(thisStore.size() == 1); - ASSERT_TRUE(thisStore.dataSize() == 3); - - thisStore.erase(value3.first); - ASSERT_TRUE(thisStore.size() == 0); - ASSERT_TRUE(thisStore.dataSize() == 0); - - thisStore.insert(value_type(value4)); - ASSERT_TRUE(thisStore.size() == 1); - ASSERT_TRUE(thisStore.dataSize() == 4); - - thisStore.insert(value_type(value3)); - ASSERT_TRUE(thisStore.size() == 2); - ASSERT_TRUE(thisStore.dataSize() == 7); - - thisStore.insert(value_type(value2)); - ASSERT_TRUE(thisStore.size() == 3); - ASSERT_TRUE(thisStore.dataSize() == 9); - - thisStore.insert(value_type(value1)); - ASSERT_TRUE(thisStore.size() == 4); - ASSERT_TRUE(thisStore.dataSize() == 10); -} - -TEST_F(RadixStoreTest, CannotRevalidateExhaustedCursor) { - value_type value1 = std::make_pair("a", "1"); - value_type value2 = std::make_pair("b", "2"); - - thisStore.insert(value_type(value1)); - - auto it = thisStore.begin(); - it++; - - // 'it' should be exhausted. - ASSERT_TRUE(it == thisStore.end()); - - thisStore.insert(value_type(value2)); - - // 'it' should still be exhausted even though we have a new tree version available. - ASSERT_TRUE(it == thisStore.end()); -} - -TEST_F(RadixStoreTest, AvoidComparingDifferentTreeVersions) { - value_type value = std::make_pair("a", "1"); - value_type value2 = std::make_pair("b", "2"); - value_type updated = std::make_pair("a", "10"); - - thisStore.insert(value_type(value)); - thisStore.insert(value_type(value2)); - - { - auto it = thisStore.begin(); - - // Updating value1 causes a new tree to be made since it's shared with the cursor. - thisStore.update(value_type(updated)); - - auto it2 = thisStore.begin(); - - it.repositionIfChanged(); - ASSERT_TRUE(it2 == it); - } - - { - auto it = thisStore.begin(); - - // Updating value1 causes a new tree to be made since it's shared with the cursor. - thisStore.erase("a"); - - auto it2 = thisStore.begin(); - - it.repositionIfChanged(); - ASSERT_TRUE(it2->first == "b"); - ASSERT_TRUE(it2 == it); - } -} - -TEST_F(RadixStoreTest, TreeUniqueness) { - value_type value1 = std::make_pair("a", "1"); - value_type value2 = std::make_pair("b", "2"); - value_type value3 = std::make_pair("c", "3"); - value_type value4 = std::make_pair("d", "4"); - - auto rootAddr = getRootAddress(); - thisStore.insert(value_type(value1)); - - // Neither the address or count should change. - ASSERT_EQUALS(rootAddr, getRootAddress()); - ASSERT_EQUALS(1, getRootCount()); - - thisStore.insert(value_type(value2)); - ASSERT_EQUALS(rootAddr, getRootAddress()); - ASSERT_EQUALS(1, getRootCount()); - - { - // Make the tree shared. - auto it = thisStore.begin(); - ASSERT_EQUALS(rootAddr, getRootAddress()); - ASSERT_EQUALS(2, getRootCount()); - - // Inserting should make a copy of the tree. - thisStore.insert(value_type(value3)); - - // The root's address should change. - ASSERT_NOT_EQUALS(rootAddr, getRootAddress()); - rootAddr = getRootAddress(); - - // Count should remain 2 because of _nextVersion - ASSERT_EQUALS(2, getRootCount()); - - // Inserting again shouldn't make a copy because the cursor hasn't been updated - thisStore.insert(value_type(value4)); - ASSERT_EQUALS(rootAddr, getRootAddress()); - ASSERT_EQUALS(2, getRootCount()); - - // Use the pointer to reposition it on the new tree. - *it; - ASSERT_EQUALS(rootAddr, getRootAddress()); - ASSERT_EQUALS(2, getRootCount()); - - thisStore.erase("d"); - ASSERT_NOT_EQUALS(rootAddr, getRootAddress()); - rootAddr = getRootAddress(); - ASSERT_EQUALS(2, getRootCount()); - } - - ASSERT_EQUALS(rootAddr, getRootAddress()); - ASSERT_EQUALS(1, getRootCount()); - - thisStore.erase("c"); - thisStore.erase("b"); - thisStore.erase("a"); - - ASSERT_EQUALS(rootAddr, getRootAddress()); - ASSERT_EQUALS(1, getRootCount()); -} - -TEST_F(RadixStoreTest, HasPreviousVersionFlagTest) { - value_type value1 = std::make_pair("a", "1"); - value_type value2 = std::make_pair("b", "2"); - value_type value3 = std::make_pair("c", "3"); - - ASSERT_FALSE(hasPreviousVersion()); - thisStore.insert(value_type(value1)); - - { - auto it = thisStore.begin(); - ASSERT_FALSE(hasPreviousVersion()); - } - - ASSERT_FALSE(hasPreviousVersion()); - - { - auto it = thisStore.begin(); - ASSERT_FALSE(hasPreviousVersion()); - - thisStore.insert(value_type(value2)); - ASSERT_TRUE(hasPreviousVersion()); - } - - ASSERT_FALSE(hasPreviousVersion()); - thisStore.erase("b"); - - // Use multiple cursors - { - auto it = thisStore.begin(); - auto it2 = thisStore.begin(); - ASSERT_FALSE(hasPreviousVersion()); - - thisStore.insert(value_type(value2)); - ASSERT_TRUE(hasPreviousVersion()); - - *it; // Change to repositionIfChanged when merging (SERVER-38262 in master); - ASSERT_TRUE(hasPreviousVersion()); - - *it2; - ASSERT_FALSE(hasPreviousVersion()); - - thisStore.insert(value_type(value3)); - ASSERT_TRUE(hasPreviousVersion()); - - *it; - } - - ASSERT_FALSE(hasPreviousVersion()); -} - -TEST_F(RadixStoreTest, LowerBoundEndpoint) { - value_type value1 = std::make_pair("AAA", "1"); - value_type value2 = std::make_pair("\xff\xff\xff", "2"); - - thisStore.insert(value_type(value1)); - thisStore.insert(value_type(value2)); - - auto it = thisStore.lower_bound("AAA"); - ASSERT_TRUE(it->first == "AAA"); - - it = thisStore.lower_bound("\xff\xff"); - ASSERT_TRUE(it->first == "\xff\xff\xff"); - - it = thisStore.lower_bound("\xff\xff\xff"); - ASSERT_TRUE(it->first == "\xff\xff\xff"); - - it = thisStore.lower_bound("\xff\xff\xff\xff"); - ASSERT_TRUE(it == thisStore.end()); -} - -TEST_F(RadixStoreTest, SimpleGrowTest) { - std::pair<StringStore::const_iterator, bool> res; - uint8_t keyArr[] = {97, 0, 0}; - - for (size_t i = 0; i < 255; ++i) { - ++keyArr[1]; - std::string keyStr(reinterpret_cast<char*>(keyArr)); - value_type value = std::make_pair(keyStr, "1"); - - res = thisStore.insert(value_type(value)); - ASSERT_TRUE(res.second); - ASSERT_TRUE(*res.first == value); - } - - ASSERT_EQ(thisStore.size(), 255); -} - -TEST_F(RadixStoreTest, SimpleShrinkTest) { - uint8_t keyArr[] = {97, 0, 0}; - - for (size_t i = 0; i < 255; ++i) { - ++keyArr[1]; - std::string keyStr(reinterpret_cast<char*>(keyArr)); - value_type value = std::make_pair(keyStr, "1"); - - thisStore.insert(value_type(value)); - } - - for (size_t i = 0; i < 255; ++i) { - std::string keyStr(reinterpret_cast<char*>(keyArr)); - thisStore.erase(keyStr); - --keyArr[1]; - } - - ASSERT_EQ(thisStore.size(), 0); -} - -TEST_F(RadixStoreTest, UpdateGrowTest) { - std::pair<StringStore::const_iterator, bool> res; - uint8_t keyArr[] = {97, 0, 0}; - - for (size_t i = 0; i < 255; ++i) { - ++keyArr[1]; - std::string keyStr(reinterpret_cast<char*>(keyArr)); - value_type value = std::make_pair(keyStr, "1"); - - thisStore.insert(value_type(value)); - - value_type update = std::make_pair(keyStr, "2"); - res = thisStore.update(value_type(update)); - ASSERT_TRUE(res.second); - ASSERT_TRUE(*res.first == update); - } -} - -TEST_F(RadixStoreTest, MergeGrowOneTest) { - // !baseNode && otherNode - thisStore = baseStore; - - uint8_t keyArr[] = {97, 0, 0}; - for (size_t i = 0; i < 16; ++i) { - ++keyArr[1]; - std::string keyStr(reinterpret_cast<char*>(keyArr)); - value_type value = std::make_pair(keyStr, "1"); - thisStore.insert((value_type(value))); - } - - otherStore = baseStore; - auto keyArrOther = keyArr; - ++keyArrOther[1]; - std::string keyStr1(reinterpret_cast<char*>(keyArrOther)); - value_type value1 = std::make_pair(keyStr1, "1"); - otherStore.insert((value_type(value1))); - - thisStore.merge3(baseStore, otherStore); - - ASSERT_EQ(baseStore.size(), 0); - ASSERT_EQ(otherStore.size(), 1); - ASSERT_EQ(thisStore.size(), 17); - ASSERT_EQ(thisStore.dataSize(), 17); - - for (size_t i = 0; i < 31; ++i) { - ++keyArr[1]; - std::string keyStr(reinterpret_cast<char*>(keyArr)); - value_type value = std::make_pair(keyStr, "1"); - thisStore.insert((value_type(value))); - } - - otherStore = baseStore; - keyArrOther = keyArr; - ++keyArrOther[1]; - std::string keyStr2(reinterpret_cast<char*>(keyArrOther)); - value_type value2 = std::make_pair(keyStr2, "1"); - otherStore.insert((value_type(value2))); - - thisStore.merge3(baseStore, otherStore); - - ASSERT_EQ(baseStore.size(), 0); - ASSERT_EQ(otherStore.size(), 1); - ASSERT_EQ(thisStore.size(), 49); - ASSERT_EQ(thisStore.dataSize(), 49); -} - -TEST_F(RadixStoreTest, MergeGrowTwoTest) { - // !node && !baseNode && otherNode - value_type value1 = std::make_pair("a", "1"); - value_type value2 = std::make_pair("aa", "2"); - - baseStore.insert(value_type(value1)); - - thisStore = baseStore; - otherStore = baseStore; - - otherStore.insert(value_type(value2)); - - thisStore.merge3(baseStore, otherStore); - - ASSERT_EQ(baseStore.size(), 1); - ASSERT_EQ(otherStore.size(), 2); - ASSERT_EQ(thisStore.size(), 2); -} - -TEST_F(RadixStoreTest, MergeCompressTest) { - value_type value1 = std::make_pair("a", "1"); - value_type value2 = std::make_pair("aaa", "2"); - value_type value3 = std::make_pair("aab", "3"); - value_type value4 = std::make_pair("aac", "4"); - - 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("aac"); - otherStore.erase("aab"); - - thisStore.merge3(baseStore, otherStore); - - ASSERT_EQ(baseStore.size(), 4); - ASSERT_EQ(otherStore.size(), 3); - ASSERT_EQ(thisStore.size(), 2); -} - -} // namespace ephemeral_for_test -} // namespace mongo diff --git a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.cpp b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.cpp deleted file mode 100644 index 5b0fc6c002a..00000000000 --- a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.cpp +++ /dev/null @@ -1,671 +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. - */ - -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kStorage - -#include "mongo/platform/basic.h" - -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.h" - -#include <cstring> -#include <memory> -#include <utility> - -#include "mongo/bson/bsonobj.h" -#include "mongo/db/operation_context.h" -#include "mongo/db/record_id_helpers.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_recovery_unit.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_visibility_manager.h" -#include "mongo/db/storage/key_string.h" -#include "mongo/db/storage/write_unit_of_work.h" -#include "mongo/logv2/log.h" -#include "mongo/util/hex.h" - -namespace mongo { -namespace ephemeral_for_test { -namespace { -Ordering allAscending = Ordering::make(BSONObj()); -auto const version = KeyString::Version::kLatestVersion; -BSONObj const sample = BSON("" - << "s" - << "" << (int64_t)0); - -std::string createKey(StringData ident, RecordId recordId) { - KeyString::Builder ks(version, BSON("" << ident), allAscending, recordId); - return std::string(ks.getBuffer(), ks.getSize()); -} - -std::string createPrefix(StringData ident) { - KeyString::Builder ks( - version, BSON("" << ident), allAscending, KeyString::Discriminator::kExclusiveBefore); - return std::string(ks.getBuffer(), ks.getSize()); -} - -std::string createPostfix(StringData ident) { - KeyString::Builder ks( - version, BSON("" << ident), allAscending, KeyString::Discriminator::kExclusiveAfter); - return std::string(ks.getBuffer(), ks.getSize()); -} - -RecordId extractRecordId(const std::string& keyStr, KeyFormat keyFormat) { - if (KeyFormat::Long == keyFormat) { - return KeyString::decodeRecordIdLongAtEnd(keyStr.c_str(), keyStr.size()); - } else { - invariant(KeyFormat::String == keyFormat); - return KeyString::decodeRecordIdStrAtEnd(keyStr.c_str(), keyStr.size()); - } -} -} // namespace - -RecordStore::RecordStore(StringData ns, - StringData ident, - KeyFormat keyFormat, - bool isCapped, - CappedCallback* cappedCallback, - VisibilityManager* visibilityManager) - : mongo::RecordStore(ns, ident), - _keyFormat(keyFormat), - _isCapped(isCapped), - _ident(getIdent().data(), getIdent().size()), - _prefix(createPrefix(_ident)), - _postfix(createPostfix(_ident)), - _cappedCallback(cappedCallback), - _isOplog(NamespaceString::oplog(ns)), - _visibilityManager(visibilityManager) {} - -const char* RecordStore::name() const { - return kEngineName; -} - -long long RecordStore::dataSize(OperationContext* opCtx) const { - return _dataSize.load(); -} - -long long RecordStore::numRecords(OperationContext* opCtx) const { - return static_cast<long long>(_numRecords.load()); -} - -void RecordStore::setCappedCallback(CappedCallback* cb) { - stdx::lock_guard<Latch> cappedCallbackLock(_cappedCallbackMutex); - _cappedCallback = cb; -} - -int64_t RecordStore::storageSize(OperationContext* opCtx, - BSONObjBuilder* extraInfo, - int infoLevel) const { - return dataSize(opCtx); -} - -bool RecordStore::findRecord(OperationContext* opCtx, const RecordId& loc, RecordData* rd) const { - StringStore* workingCopy(RecoveryUnit::get(opCtx)->getHead()); - auto it = workingCopy->find(createKey(_ident, loc)); - if (it == workingCopy->end()) { - return false; - } - *rd = RecordData(it->second.c_str(), it->second.length()).getOwned(); - return true; -} - -void RecordStore::doDeleteRecord(OperationContext* opCtx, const RecordId& dl) { - if (KeyFormat::Long == _keyFormat) { - _initHighestIdIfNeeded(opCtx); - } - auto ru = RecoveryUnit::get(opCtx); - StringStore* workingCopy(ru->getHead()); - SizeAdjuster adjuster(opCtx, this); - invariant(workingCopy->erase(createKey(_ident, dl))); - ru->makeDirty(); -} - -Status RecordStore::doInsertRecords(OperationContext* opCtx, - std::vector<Record>* inOutRecords, - const std::vector<Timestamp>& timestamps) { - auto ru = RecoveryUnit::get(opCtx); - StringStore* workingCopy(ru->getHead()); - { - SizeAdjuster adjuster(opCtx, this); - for (auto& record : *inOutRecords) { - auto thisRecordId = record.id; - if (_isOplog) { - StatusWith<RecordId> status = - record_id_helpers::extractKeyOptime(record.data.data(), record.data.size()); - if (!status.isOK()) - return status.getStatus(); - thisRecordId = status.getValue(); - _visibilityManager->addUncommittedRecord(opCtx, this, thisRecordId); - } else { - // If the record had an id already, keep it. - if (KeyFormat::Long == _keyFormat && thisRecordId.isNull()) { - thisRecordId = RecordId(_nextRecordId(opCtx)); - } - } - auto key = createKey(_ident, thisRecordId); - auto it = workingCopy->find(key); - if (keyFormat() == KeyFormat::String && it != workingCopy->end()) { - // RecordStores with the string KeyFormat implicitly expect enforcement of _id - // uniqueness. Generate a useful error message that is consistent with duplicate key - // error messages on indexes. - BSONObj obj = record_id_helpers::toBSONAs(thisRecordId, ""); - return buildDupKeyErrorStatus(obj, - NamespaceString(ns()), - "" /* indexName */, - BSON("_id" << 1), - BSONObj() /* collation */); - } - - workingCopy->insert( - StringStore::value_type{key, std::string(record.data.data(), record.data.size())}); - record.id = RecordId(thisRecordId); - } - } - ru->makeDirty(); - return Status::OK(); -} - -Status RecordStore::doUpdateRecord(OperationContext* opCtx, - const RecordId& oldLocation, - const char* data, - int len) { - StringStore* workingCopy(RecoveryUnit::get(opCtx)->getHead()); - SizeAdjuster adjuster(opCtx, this); - { - std::string key = createKey(_ident, oldLocation); - StringStore::const_iterator it = workingCopy->find(key); - invariant(it != workingCopy->end()); - workingCopy->update(StringStore::value_type{key, std::string(data, len)}); - } - RecoveryUnit::get(opCtx)->makeDirty(); - - return Status::OK(); -} - -bool RecordStore::updateWithDamagesSupported() const { - // TODO: enable updateWithDamages after writable pointers are complete. - return false; -} - -StatusWith<RecordData> RecordStore::doUpdateWithDamages(OperationContext* opCtx, - const RecordId& loc, - const RecordData& oldRec, - const char* damageSource, - const mutablebson::DamageVector& damages) { - return RecordData(); -} - -std::unique_ptr<SeekableRecordCursor> RecordStore::getCursor(OperationContext* opCtx, - bool forward) const { - if (forward) - return std::make_unique<Cursor>(opCtx, *this, _visibilityManager); - return std::make_unique<ReverseCursor>(opCtx, *this, _visibilityManager); -} - -Status RecordStore::doTruncate(OperationContext* opCtx) { - SizeAdjuster adjuster(opCtx, this); - StatusWith<int64_t> s = truncateWithoutUpdatingCount( - checked_cast<ephemeral_for_test::RecoveryUnit*>(opCtx->recoveryUnit())); - if (!s.isOK()) - return s.getStatus(); - - return Status::OK(); -} - -StatusWith<int64_t> RecordStore::truncateWithoutUpdatingCount(mongo::RecoveryUnit* ru) { - auto bRu = checked_cast<ephemeral_for_test::RecoveryUnit*>(ru); - StringStore* workingCopy(bRu->getHead()); - StringStore::const_iterator end = workingCopy->upper_bound(_postfix); - std::vector<std::string> toDelete; - - for (auto it = workingCopy->lower_bound(_prefix); it != end; ++it) { - toDelete.push_back(it->first); - } - - if (toDelete.empty()) - return 0; - - for (const auto& key : toDelete) - workingCopy->erase(key); - - bRu->makeDirty(); - - return static_cast<int64_t>(toDelete.size()); -} - -void RecordStore::doCappedTruncateAfter(OperationContext* opCtx, RecordId end, bool inclusive) { - auto ru = RecoveryUnit::get(opCtx); - StringStore* workingCopy(ru->getHead()); - WriteUnitOfWork wuow(opCtx); - const auto recordKey = createKey(_ident, end); - auto recordIt = - inclusive ? workingCopy->lower_bound(recordKey) : workingCopy->upper_bound(recordKey); - auto endIt = workingCopy->upper_bound(_postfix); - - while (recordIt != endIt) { - stdx::lock_guard<Latch> cappedCallbackLock(_cappedCallbackMutex); - if (_cappedCallback) { - // Documents are guaranteed to have a RecordId at the end of the KeyString, unlike - // unique indexes. - RecordId rid = extractRecordId(recordIt->first, _keyFormat); - RecordData rd = RecordData(recordIt->second.c_str(), recordIt->second.length()); - uassertStatusOK(_cappedCallback->aboutToDeleteCapped(opCtx, rid, rd)); - } - // Important to scope adjuster until after capped callback, as that changes indexes and - // would result in those changes being reflected in RecordStore count/size. - SizeAdjuster adjuster(opCtx, this); - - // Don't need to increment the iterator because the iterator gets revalidated and placed on - // the next item after the erase. - workingCopy->erase(recordIt->first); - - // Tree modifications are bound to happen here so we need to reposition our end cursor. - endIt.repositionIfChanged(); - ru->makeDirty(); - } - - wuow.commit(); -} - -void RecordStore::updateStatsAfterRepair(OperationContext* opCtx, - long long numRecords, - long long dataSize) { - // SERVER-38883 This storage engine should instead be able to invariant that stats are correct. - _numRecords.store(numRecords); - _dataSize.store(dataSize); -} - -void RecordStore::waitForAllEarlierOplogWritesToBeVisibleImpl(OperationContext* opCtx) const { - _visibilityManager->waitForAllEarlierOplogWritesToBeVisible(opCtx); -} - -Status RecordStore::oplogDiskLocRegisterImpl(OperationContext* opCtx, - const Timestamp& opTime, - bool orderedCommit) { - if (!orderedCommit) { - return opCtx->recoveryUnit()->setTimestamp(opTime); - } - - return Status::OK(); -} - -void RecordStore::_initHighestIdIfNeeded(OperationContext* opCtx) { - // In the normal case, this will already be initialized, so use a weak load. Since this value - // will only change from 0 to a positive integer, the only risk is reading an outdated value, 0, - // and having to take the mutex. - if (_highestRecordId.loadRelaxed() > 0) { - return; - } - - // Only one thread needs to do this. - stdx::lock_guard<Latch> lk(_initHighestIdMutex); - if (_highestRecordId.load() > 0) { - return; - } - - // Need to start at 1 so we are always higher than RecordId::minLong() - int64_t nextId = 1; - - // Find the largest RecordId currently in use. - std::unique_ptr<SeekableRecordCursor> cursor = getCursor(opCtx, /*forward=*/false); - if (auto record = cursor->next()) { - nextId = record->id.getLong() + 1; - } - - _highestRecordId.store(nextId); -}; - -int64_t RecordStore::_nextRecordId(OperationContext* opCtx) { - _initHighestIdIfNeeded(opCtx); - return _highestRecordId.fetchAndAdd(1); -} - -RecordStore::Cursor::Cursor(OperationContext* opCtx, - const RecordStore& rs, - VisibilityManager* visibilityManager) - : opCtx(opCtx), _rs(rs), _visibilityManager(visibilityManager) { - if (_rs._isOplog) { - _oplogVisibility = _visibilityManager->getAllCommittedRecord(); - } -} - -void RecordStore::Cursor::advanceSnapshotIfNeeded() { - if (auto newWorkingCopy = RecoveryUnit::get(opCtx)->getHeadShared(); - newWorkingCopy != _workingCopy) { - if (_savedPosition) { - it = newWorkingCopy->lower_bound(_savedPosition.value()); - } - _workingCopy = std::move(newWorkingCopy); - } -} - -boost::optional<Record> RecordStore::Cursor::next() { - advanceSnapshotIfNeeded(); - // Capped iterators die on invalidation rather than advancing. - if (_rs._isCapped && _lastMoveWasRestore) { - return boost::none; - } - - if (_needFirstSeek) { - _needFirstSeek = false; - it = _workingCopy->lower_bound(_rs._prefix); - } else if (it != _workingCopy->end() && !_lastMoveWasRestore) { - ++it; - } - - _savedPosition = boost::none; - _lastMoveWasRestore = false; - if (it != _workingCopy->end() && inPrefix(it->first)) { - _savedPosition = it->first; - - Record nextRecord; - nextRecord.id = RecordId(extractRecordId(it->first, _rs._keyFormat)); - nextRecord.data = RecordData(it->second.c_str(), it->second.length()); - - if (_rs._isOplog && nextRecord.id > _oplogVisibility) { - return boost::none; - } - - return nextRecord; - } - return boost::none; -} - -boost::optional<Record> RecordStore::Cursor::seekExact(const RecordId& id) { - advanceSnapshotIfNeeded(); - _savedPosition = boost::none; - _lastMoveWasRestore = false; - std::string key = createKey(_rs._ident, id); - it = _workingCopy->find(key); - - if (it == _workingCopy->end() || !inPrefix(it->first)) - return boost::none; - - if (_rs._isOplog && id > _oplogVisibility) { - return boost::none; - } - - _needFirstSeek = false; - _savedPosition = it->first; - return Record{id, RecordData(it->second.c_str(), it->second.length())}; -} - -boost::optional<Record> RecordStore::Cursor::seekNear(const RecordId& id) { - advanceSnapshotIfNeeded(); - _savedPosition = boost::none; - _lastMoveWasRestore = false; - - RecordId search = id; - if (_rs._isOplog && id > _oplogVisibility) { - search = RecordId(_oplogVisibility); - } - - auto numRecords = _rs.numRecords(opCtx); - if (numRecords == 0) - return boost::none; - - std::string key = createKey(_rs._ident, search); - // We may land higher and that is fine per the API contract. - it = _workingCopy->lower_bound(key); - - // If we're at the end of this record store, we didn't find anything >= id. Position on the - // immediately previous record, which must exist. - if (it == _workingCopy->end() || !inPrefix(it->first)) { - // The reverse iterator constructor positions on the next record automatically. - StringStore::const_reverse_iterator revIt(it); - invariant(revIt != _workingCopy->rend()); - it = _workingCopy->lower_bound(revIt->first); - invariant(it != _workingCopy->end()); - invariant(inPrefix(it->first)); - } - - // If we landed one higher, then per the API contract, we need to return the previous record. - RecordId rid = extractRecordId(it->first, _rs._keyFormat); - if (rid > search) { - StringStore::const_reverse_iterator revIt(it); - // The reverse iterator constructor positions on the next record automatically. - if (revIt != _workingCopy->rend() && inPrefix(revIt->first)) { - it = _workingCopy->lower_bound(revIt->first); - rid = RecordId(extractRecordId(it->first, _rs._keyFormat)); - } - // Otherwise, we hit the beginning of this record store, then there is only one record and - // we should return that. - } - - // For forward cursors on the oplog, the oplog visible timestamp is treated as the end of the - // record store. So if we are positioned past this point, then there are no visible records. - if (_rs._isOplog && rid > _oplogVisibility) { - return boost::none; - } - - _needFirstSeek = false; - _savedPosition = it->first; - return Record{rid, RecordData(it->second.c_str(), it->second.length())}; -} - -// Positions are saved as we go. -void RecordStore::Cursor::save() { - _workingCopy = nullptr; -} -void RecordStore::Cursor::saveUnpositioned() { - _workingCopy = nullptr; - _savedPosition = boost::none; -} - -bool RecordStore::Cursor::restore(bool tolerateCappedRepositioning) { - if (!_savedPosition) - return true; - - // Get oplog visibility before forking working tree to guarantee that nothing gets committed - // after we've forked that would update oplog visibility - if (_rs._isOplog) { - _oplogVisibility = _visibilityManager->getAllCommittedRecord(); - } - - _workingCopy = RecoveryUnit::get(opCtx)->getHeadShared(); - it = _workingCopy->lower_bound(_savedPosition.value()); - _lastMoveWasRestore = it == _workingCopy->end() || it->first != _savedPosition.value(); - - // Capped iterators die on invalidation rather than advancing. - return !(_rs._isCapped && _lastMoveWasRestore); -} - -void RecordStore::Cursor::detachFromOperationContext() { - invariant(opCtx != nullptr); - opCtx = nullptr; -} - -void RecordStore::Cursor::reattachToOperationContext(OperationContext* opCtx) { - invariant(opCtx != nullptr); - this->opCtx = opCtx; -} - -bool RecordStore::Cursor::inPrefix(const std::string& key_string) { - return (key_string > _rs._prefix) && (key_string < _rs._postfix); -} - -RecordStore::ReverseCursor::ReverseCursor(OperationContext* opCtx, - const RecordStore& rs, - VisibilityManager* visibilityManager) - : opCtx(opCtx), _rs(rs), _visibilityManager(visibilityManager) { - _savedPosition = boost::none; -} - -void RecordStore::ReverseCursor::advanceSnapshotIfNeeded() { - if (auto newWorkingCopy = RecoveryUnit::get(opCtx)->getHeadShared(); - newWorkingCopy != _workingCopy) { - if (_savedPosition) { - it = StringStore::const_reverse_iterator( - newWorkingCopy->upper_bound(_savedPosition.value())); - } - _workingCopy = std::move(newWorkingCopy); - } -} - -boost::optional<Record> RecordStore::ReverseCursor::next() { - advanceSnapshotIfNeeded(); - - // Capped iterators die on invalidation rather than advancing. - if (_rs._isCapped && _lastMoveWasRestore) { - return boost::none; - } - - if (_needFirstSeek) { - _needFirstSeek = false; - it = StringStore::const_reverse_iterator(_workingCopy->upper_bound(_rs._postfix)); - } else if (it != _workingCopy->rend() && !_lastMoveWasRestore) { - ++it; - } - _savedPosition = boost::none; - _lastMoveWasRestore = false; - - if (it != _workingCopy->rend() && inPrefix(it->first)) { - _savedPosition = it->first; - Record nextRecord; - nextRecord.id = RecordId(extractRecordId(it->first, _rs._keyFormat)); - nextRecord.data = RecordData(it->second.c_str(), it->second.length()); - - return nextRecord; - } - return boost::none; -} - -boost::optional<Record> RecordStore::ReverseCursor::seekExact(const RecordId& id) { - advanceSnapshotIfNeeded(); - _needFirstSeek = false; - _savedPosition = boost::none; - std::string key = createKey(_rs._ident, id); - StringStore::const_iterator canFind = _workingCopy->find(key); - if (canFind == _workingCopy->end() || !inPrefix(canFind->first)) { - it = _workingCopy->rend(); - return boost::none; - } - - 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())}; -} - -boost::optional<Record> RecordStore::ReverseCursor::seekNear(const RecordId& id) { - advanceSnapshotIfNeeded(); - _savedPosition = boost::none; - _lastMoveWasRestore = false; - - auto numRecords = _rs.numRecords(opCtx); - if (numRecords == 0) - return boost::none; - - std::string key = createKey(_rs._ident, id); - it = StringStore::const_reverse_iterator(_workingCopy->upper_bound(key)); - - // Since there is at least 1 record, if we hit the beginning we need to return the only record. - if (it == _workingCopy->rend() || !inPrefix(it->first)) { - // This lands on the next key. - auto fwdIt = _workingCopy->upper_bound(key); - // reverse iterator increments one item before - it = StringStore::const_reverse_iterator(++fwdIt); - invariant(it != _workingCopy->end()); - invariant(inPrefix(it->first)); - } - - // If we landed lower, then per the API contract, we need to return the previous record. - RecordId rid = extractRecordId(it->first, _rs._keyFormat); - if (rid < id) { - // This lands on the next key. - auto fwdIt = _workingCopy->upper_bound(key); - if (fwdIt != _workingCopy->end() && inPrefix(fwdIt->first)) { - it = StringStore::const_reverse_iterator(++fwdIt); - } - // Otherwise, we hit the beginning of this record store, then there is only one record and - // we should return that. - } - - rid = RecordId(extractRecordId(it->first, _rs._keyFormat)); - _needFirstSeek = false; - _savedPosition = it->first; - return Record{rid, RecordData(it->second.c_str(), it->second.length())}; -} - -void RecordStore::ReverseCursor::save() { - _workingCopy = nullptr; -} -void RecordStore::ReverseCursor::saveUnpositioned() { - _workingCopy = nullptr; - _savedPosition = boost::none; -} - -bool RecordStore::ReverseCursor::restore(bool tolerateCappedRepositioning) { - if (!_savedPosition) - return true; - - _workingCopy = RecoveryUnit::get(opCtx)->getHeadShared(); - it = StringStore::const_reverse_iterator(_workingCopy->upper_bound(_savedPosition.value())); - _lastMoveWasRestore = (it == _workingCopy->rend() || it->first != _savedPosition.value()); - - // Capped iterators die on invalidation rather than advancing. - return !(_rs._isCapped && _lastMoveWasRestore); -} - -void RecordStore::ReverseCursor::detachFromOperationContext() { - invariant(opCtx != nullptr); - opCtx = nullptr; -} - -void RecordStore::ReverseCursor::reattachToOperationContext(OperationContext* opCtx) { - invariant(opCtx != nullptr); - this->opCtx = opCtx; -} - -bool RecordStore::ReverseCursor::inPrefix(const std::string& key_string) { - return (key_string > _rs._prefix) && (key_string < _rs._postfix); -} - -RecordStore::SizeAdjuster::SizeAdjuster(OperationContext* opCtx, RecordStore* rs) - : _opCtx(opCtx), - _rs(rs), - _workingCopy(ephemeral_for_test::RecoveryUnit::get(opCtx)->getHead()), - _origNumRecords(_workingCopy->size()), - _origDataSize(_workingCopy->dataSize()) {} - -RecordStore::SizeAdjuster::~SizeAdjuster() { - // SERVER-48981 This implementation of fastcount results in inaccurate values. This storage - // engine emits write conflict exceptions at commit-time leading to the fastcount to be - // inaccurate until the rollback happens. - // If proper local isolation is implemented, SERVER-38883 can also be fulfulled for this storage - // engine where we can invariant for correct fastcount in updateStatsAfterRepair() - int64_t deltaNumRecords = _workingCopy->size() - _origNumRecords; - int64_t deltaDataSize = _workingCopy->dataSize() - _origDataSize; - _rs->_numRecords.fetchAndAdd(deltaNumRecords); - _rs->_dataSize.fetchAndAdd(deltaDataSize); - RecoveryUnit::get(_opCtx)->onRollback([rs = _rs, deltaNumRecords, deltaDataSize]() { - rs->_numRecords.fetchAndSubtract(deltaNumRecords); - rs->_dataSize.fetchAndSubtract(deltaDataSize); - }); -} - -} // namespace ephemeral_for_test -} // namespace mongo diff --git a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.h b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.h deleted file mode 100644 index b6b913c57ce..00000000000 --- a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.h +++ /dev/null @@ -1,249 +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 <atomic> -#include <map> - -#include "mongo/db/concurrency/d_concurrency.h" -#include "mongo/db/storage/capped_callback.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_visibility_manager.h" -#include "mongo/db/storage/record_store.h" -#include "mongo/platform/atomic_word.h" -#include "mongo/platform/mutex.h" - -namespace mongo { -namespace ephemeral_for_test { - -/** - * A RecordStore that stores all data in-memory. - */ -class RecordStore final : public ::mongo::RecordStore { -public: - explicit RecordStore(StringData ns, - StringData ident, - KeyFormat keyFormat, - bool isCapped = false, - CappedCallback* cappedCallback = nullptr, - VisibilityManager* visibilityManager = nullptr); - ~RecordStore() = default; - - virtual const char* name() const; - virtual KeyFormat keyFormat() const { - return _keyFormat; - } - virtual long long dataSize(OperationContext* opCtx) const; - virtual long long numRecords(OperationContext* opCtx) const; - virtual void setCappedCallback(CappedCallback*); - virtual int64_t storageSize(OperationContext* opCtx, - BSONObjBuilder* extraInfo = nullptr, - int infoLevel = 0) const; - - virtual bool findRecord(OperationContext* opCtx, const RecordId& loc, RecordData* rd) const; - - void doDeleteRecord(OperationContext* opCtx, const RecordId& dl) final; - - Status doInsertRecords(OperationContext* opCtx, - std::vector<Record>* inOutRecords, - const std::vector<Timestamp>& timestamps) final; - - Status doUpdateRecord(OperationContext* opCtx, - const RecordId& oldLocation, - const char* data, - int len) final; - - virtual bool updateWithDamagesSupported() const; - - StatusWith<RecordData> doUpdateWithDamages(OperationContext* opCtx, - const RecordId& loc, - const RecordData& oldRec, - const char* damageSource, - const mutablebson::DamageVector& damages) final; - - virtual void printRecordMetadata(OperationContext* opCtx, const RecordId& recordId) const {} - - std::unique_ptr<SeekableRecordCursor> getCursor(OperationContext* opCtx, - bool forward) const final; - - Status doTruncate(OperationContext* opCtx) final; - StatusWith<int64_t> truncateWithoutUpdatingCount(RecoveryUnit* ru); - - void doCappedTruncateAfter(OperationContext* opCtx, RecordId end, bool inclusive) final; - - virtual void appendNumericCustomStats(OperationContext* opCtx, - BSONObjBuilder* result, - double scale) const {} - - virtual void updateStatsAfterRepair(OperationContext* opCtx, - long long numRecords, - long long dataSize); - -protected: - Status oplogDiskLocRegisterImpl(OperationContext* opCtx, - const Timestamp& opTime, - bool orderedCommit) override; - - void waitForAllEarlierOplogWritesToBeVisibleImpl(OperationContext* opCtx) const override; - -private: - friend class VisibilityManagerChange; - - void _initHighestIdIfNeeded(OperationContext* opCtx); - - /** - * This gets the next (guaranteed) unique record id. - */ - int64_t _nextRecordId(OperationContext* opCtx); - - const KeyFormat _keyFormat; - const bool _isCapped; - - StringData _ident; - - std::string _prefix; - std::string _postfix; - - mutable Mutex _cappedCallbackMutex = - MONGO_MAKE_LATCH("RecordStore::_cappedCallbackMutex"); // Guards _cappedCallback - CappedCallback* _cappedCallback; - - mutable Mutex _cappedDeleterMutex = MONGO_MAKE_LATCH("RecordStore::_cappedDeleterMutex"); - - mutable Mutex _initHighestIdMutex = MONGO_MAKE_LATCH("RecordStore::_initHighestIdMutex"); - AtomicWord<long long> _highestRecordId{0}; - AtomicWord<long long> _numRecords{0}; - AtomicWord<long long> _dataSize{0}; - - std::string generateKey(const uint8_t* key, size_t key_len) const; - - bool _isOplog; - VisibilityManager* _visibilityManager; - - /** - * Automatically adjust the record count and data size based on the size in change of the - * underlying radix store during the life time of the SizeAdjuster. - */ - friend class SizeAdjuster; - class SizeAdjuster { - public: - SizeAdjuster(OperationContext* opCtx, RecordStore* rs); - ~SizeAdjuster(); - - private: - OperationContext* const _opCtx; - RecordStore* const _rs; - const StringStore* _workingCopy; - const int64_t _origNumRecords; - const int64_t _origDataSize; - }; - - class Cursor final : public SeekableRecordCursor { - OperationContext* opCtx; - const RecordStore& _rs; - StringStore::const_iterator it; - boost::optional<std::string> _savedPosition; - bool _needFirstSeek = true; - bool _lastMoveWasRestore = false; - VisibilityManager* _visibilityManager; - RecordId _oplogVisibility; - - // Each cursor holds a strong reference to the recovery unit's working copy at the time of - // the last next()/seek() on the cursor. This ensures that in between calls to - // next()/seek(), the data pointed to by the cursor remains valid, even if the client - // finishes a transaction and advances the snapshot. - std::shared_ptr<StringStore> _workingCopy; - - public: - Cursor(OperationContext* opCtx, - const RecordStore& rs, - VisibilityManager* visibilityManager); - boost::optional<Record> next() final; - boost::optional<Record> seekExact(const RecordId& id) final override; - boost::optional<Record> seekNear(const RecordId& id) final override; - void save() final; - void saveUnpositioned() final override; - bool restore(bool tolerateCappedRepositioning = true) final; - void detachFromOperationContext() final; - void reattachToOperationContext(OperationContext* opCtx) final; - void setSaveStorageCursorOnDetachFromOperationContext(bool) override { - // Noop for EFT, since we always keep the cursor's contents valid across save/restore. - } - - private: - bool inPrefix(const std::string& key_string); - - // Updates '_workingCopy' and 'it' to use the snapshot associated with the recovery unit, - // if it has changed. - void advanceSnapshotIfNeeded(); - }; - - class ReverseCursor final : public SeekableRecordCursor { - OperationContext* opCtx; - const RecordStore& _rs; - StringStore::const_reverse_iterator it; - boost::optional<std::string> _savedPosition; - bool _needFirstSeek = true; - bool _lastMoveWasRestore = false; - VisibilityManager* _visibilityManager; - - // Each cursor holds a strong reference to the recovery unit's working copy at the time of - // the last next()/seek() on the cursor. This ensures that in between calls to - // next()/seek(), the data pointed to by the cursor remains valid, even if the client - // finishes a transaction and advances the snapshot. - std::shared_ptr<StringStore> _workingCopy; - - public: - ReverseCursor(OperationContext* opCtx, - const RecordStore& rs, - VisibilityManager* visibilityManager); - boost::optional<Record> next() final; - boost::optional<Record> seekExact(const RecordId& id) final override; - boost::optional<Record> seekNear(const RecordId& id) final override; - void save() final; - void saveUnpositioned() final override; - bool restore(bool tolerateCappedRepositioning = true) final; - void detachFromOperationContext() final; - void reattachToOperationContext(OperationContext* opCtx) final; - void setSaveStorageCursorOnDetachFromOperationContext(bool) override { - // Noop for EFT, since we always keep the cursor's contents valid across save/restore. - } - - private: - bool inPrefix(const std::string& key_string); - - // Updates '_workingCopy' and 'it' to use the snapshot associated with the recovery unit, - // if it has changed. - void advanceSnapshotIfNeeded(); - }; -}; - -} // namespace ephemeral_for_test -} // namespace mongo diff --git a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store_test.cpp b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store_test.cpp deleted file mode 100644 index 15ebe93cad0..00000000000 --- a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store_test.cpp +++ /dev/null @@ -1,107 +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. - */ - -#include "mongo/platform/basic.h" - -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.h" - -#include <memory> - -#include "mongo/base/init.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_kv_engine.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_recovery_unit.h" -#include "mongo/db/storage/record_store_test_harness.h" -#include "mongo/unittest/unittest.h" - -namespace mongo { -namespace ephemeral_for_test { -namespace { - -class RecordStoreHarnessHelper final : public ::mongo::RecordStoreHarnessHelper { - KVEngine _kvEngine{}; - VisibilityManager _visibilityManager; - -public: - RecordStoreHarnessHelper() {} - - virtual std::unique_ptr<mongo::RecordStore> newRecordStore() { - return newRecordStore("a.b", CollectionOptions()); - } - - virtual std::unique_ptr<mongo::RecordStore> newRecordStore( - const std::string& ns, - const CollectionOptions& collOptions, - KeyFormat keyFormat = KeyFormat::Long) { - if (collOptions.clusteredIndex) { - // A clustered collection requires both CollectionOptions.clusteredIndex and - // KeyFormat::String. For a clustered record store that is not associated with a - // clustered collection KeyFormat::String is sufficient. - uassert(6144102, - "RecordStore with CollectionOptions.clusteredIndex requires KeyFormat::String", - keyFormat == KeyFormat::String); - } - - return std::make_unique<RecordStore>(ns, - "ident"_sd /* ident */, - collOptions.clusteredIndex ? KeyFormat::String - : KeyFormat::Long, - false /* isCapped */, - nullptr /* cappedCallback */, - nullptr /* visibilityManager */); - } - - virtual std::unique_ptr<mongo::RecordStore> newOplogRecordStore() final { - return std::make_unique<RecordStore>(NamespaceString::kRsOplogNamespace.toString(), - "ident"_sd, - KeyFormat::Long, - /*isCapped*/ true, - /*cappedCallback*/ nullptr, - &_visibilityManager); - } - - std::unique_ptr<mongo::RecoveryUnit> newRecoveryUnit() final { - return std::make_unique<RecoveryUnit>(&_kvEngine); - } - - KVEngine* getEngine() override final { - return &_kvEngine; - } -}; - -std::unique_ptr<mongo::RecordStoreHarnessHelper> makeRecordStoreHarnessHelper() { - return std::make_unique<RecordStoreHarnessHelper>(); -} - -MONGO_INITIALIZER(RegisterRecordStoreHarnessFactory)(InitializerContext*) { - mongo::registerRecordStoreHarnessHelperFactory(makeRecordStoreHarnessHelper); -} -} // namespace -} // namespace ephemeral_for_test -} // namespace mongo diff --git a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_recovery_unit.cpp b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_recovery_unit.cpp deleted file mode 100644 index e5b3752381d..00000000000 --- a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_recovery_unit.cpp +++ /dev/null @@ -1,219 +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. - */ - -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kStorage - -#include "mongo/platform/basic.h" - -#include <mutex> - -#include "mongo/db/concurrency/write_conflict_exception.h" -#include "mongo/db/record_id_helpers.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_recovery_unit.h" -#include "mongo/util/fail_point.h" - -namespace mongo { -namespace ephemeral_for_test { -namespace { -MONGO_FAIL_POINT_DEFINE(EFTAlwaysThrowWCEOnWrite); -MONGO_FAIL_POINT_DEFINE(EFTThrowWCEOnMerge); -} // namespace - -RecoveryUnit::RecoveryUnit(KVEngine* parentKVEngine, std::function<void()> cb) - : _waitUntilDurableCallback(cb), _KVEngine(parentKVEngine) {} - -RecoveryUnit::~RecoveryUnit() { - invariant(!_inUnitOfWork(), toString(_getState())); - _abort(); -} - -void RecoveryUnit::doBeginUnitOfWork() { - invariant(!_inUnitOfWork(), toString(_getState())); - _setState(_isActive() ? State::kActive : State::kInactiveInUnitOfWork); -} - -void RecoveryUnit::doCommitUnitOfWork() { - invariant(_inUnitOfWork(), toString(_getState())); - - if (_dirty) { - invariant(_forked); - if (MONGO_unlikely(EFTThrowWCEOnMerge.shouldFail())) { - throw WriteConflictException(); - } - - while (true) { - auto masterInfo = _KVEngine->getMasterInfo(_readAtTimestamp); - try { - invariant(_mergeBase); - _workingCopy->merge3(*_mergeBase, *masterInfo.second); - } catch (const merge_conflict_exception&) { - throw WriteConflictException(); - } - - if (_KVEngine->trySwapMaster(*_workingCopy, masterInfo.first)) { - // Merged successfully - break; - } else { - // Retry the merge, but update the mergeBase since some progress was made merging. - _mergeBase = masterInfo.second; - } - } - _forked = false; - _dirty = false; - } else if (_forked) { - if (kDebugBuild) - invariant(*_mergeBase == *_workingCopy); - } - _isTimestamped = false; - - _setState(State::kCommitting); - commitRegisteredChanges(boost::none); - _setState(State::kInactive); -} - -void RecoveryUnit::doAbortUnitOfWork() { - invariant(_inUnitOfWork(), toString(_getState())); - _abort(); -} - -bool RecoveryUnit::waitUntilDurable(OperationContext* opCtx) { - invariant(!_inUnitOfWork(), toString(_getState())); - invariant(!opCtx->lockState()->isLocked() || storageGlobalParams.repair); - return true; // This is an in-memory storage engine. -} - -Status RecoveryUnit::majorityCommittedSnapshotAvailable() const { - return Status::OK(); -} - -void RecoveryUnit::prepareUnitOfWork() { - invariant(_inUnitOfWork()); -} - -void RecoveryUnit::doAbandonSnapshot() { - invariant(!_inUnitOfWork(), toString(_getState())); - if (_abandonSnapshotMode == RecoveryUnit::AbandonSnapshotMode::kCommit) { - invariant(!_dirty); // Cannot commit written data outside WUOW. - } - - _forked = false; - _dirty = false; - _isTimestamped = false; - _setMergeNull(); - _setState(State::kInactive); - _workingCopy = nullptr; -} - -void RecoveryUnit::makeDirty() { - if (MONGO_unlikely(EFTAlwaysThrowWCEOnWrite.shouldFail())) { - throw WriteConflictException(); - } - _dirty = true; - if (!_isActive()) { - _setState(_inUnitOfWork() ? State::kActive : State::kActiveNotInUnitOfWork); - } -} - -bool RecoveryUnit::forkIfNeeded() { - if (_forked) - return false; - - boost::optional<Timestamp> readFrom = boost::none; - switch (_timestampReadSource) { - case ReadSource::kNoTimestamp: - case ReadSource::kMajorityCommitted: - case ReadSource::kNoOverlap: - case ReadSource::kLastApplied: - break; - case ReadSource::kProvided: - readFrom = _readAtTimestamp; - break; - case ReadSource::kAllDurableSnapshot: - readFrom = _KVEngine->getAllDurableTimestamp(); - break; - } - // Update the copies of the trees when not in a WUOW so cursors can retrieve the latest data. - auto masterInfo = _KVEngine->getMasterInfo(readFrom); - _mergeBase = masterInfo.second; - _workingCopy = std::make_shared<StringStore>(*masterInfo.second); - invariant(_mergeBase); - - // Call cleanHistory in case _mergeBase was holding a shared_ptr to an older tree. - _KVEngine->cleanHistory(); - _forked = true; - return true; -} - -Status RecoveryUnit::setTimestamp(Timestamp timestamp) { - auto key = record_id_helpers::keyForOptime(timestamp); - if (!key.isOK()) - return key.getStatus(); - - _KVEngine->visibilityManager()->reserveRecord(this, key.getValue()); - _isTimestamped = true; - return Status::OK(); -} - -void RecoveryUnit::setOrderedCommit(bool orderedCommit) {} - -void RecoveryUnit::_abort() { - _forked = false; - _dirty = false; - _isTimestamped = false; - _setMergeNull(); - _setState(State::kAborting); - abortRegisteredChanges(); - _setState(State::kInactive); -} - -void RecoveryUnit::_setMergeNull() { - _mergeBase = nullptr; - if (!KVEngine::instanceExists()) { - _KVEngine->cleanHistory(); - } -} - -void RecoveryUnit::setTimestampReadSource(ReadSource readSource, - boost::optional<Timestamp> provided) { - invariant(!provided == (readSource != ReadSource::kProvided)); - invariant(!(provided && provided->isNull())); - - _timestampReadSource = readSource; - _readAtTimestamp = (provided) ? *provided : Timestamp(); -} - -RecoveryUnit::ReadSource RecoveryUnit::getTimestampReadSource() const { - return _timestampReadSource; -} - -RecoveryUnit* RecoveryUnit::get(OperationContext* opCtx) { - return checked_cast<ephemeral_for_test::RecoveryUnit*>(opCtx->recoveryUnit()); -} -} // namespace ephemeral_for_test -} // namespace mongo diff --git a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_recovery_unit.h b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_recovery_unit.h deleted file mode 100644 index 3be769ef99b..00000000000 --- a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_recovery_unit.h +++ /dev/null @@ -1,147 +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 <functional> -#include <vector> - -#include "mongo/db/record_id.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_kv_engine.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store.h" -#include "mongo/db/storage/recovery_unit.h" - -namespace mongo { -namespace ephemeral_for_test { - -class RecoveryUnit : public ::mongo::RecoveryUnit { -public: - RecoveryUnit(KVEngine* parentKVEngine, std::function<void()> cb = nullptr); - ~RecoveryUnit(); - - virtual bool waitUntilDurable(OperationContext* opCtx) override; - - virtual void setOrderedCommit(bool orderedCommit) override; - - Status majorityCommittedSnapshotAvailable() const final; - - void prepareUnitOfWork() override; - - virtual void setPrepareTimestamp(Timestamp ts) override { - _prepareTimestamp = ts; - } - - virtual Timestamp getPrepareTimestamp() const override { - return _prepareTimestamp; - } - - virtual void setCommitTimestamp(Timestamp ts) override { - _commitTimestamp = ts; - } - - virtual Timestamp getCommitTimestamp() const override { - return _commitTimestamp; - } - - virtual void clearCommitTimestamp() override { - _commitTimestamp = Timestamp::min(); - } - - Status setTimestamp(Timestamp timestamp) override; - - bool isTimestamped() const override { - return _isTimestamped; - } - - void setTimestampReadSource(ReadSource readSource, - boost::optional<Timestamp> provided) override; - - ReadSource getTimestampReadSource() const override; - - // Ephemeral for test specific function declarations below. - StringStore* getHead() { - forkIfNeeded(); - return _workingCopy.get(); - } - - std::shared_ptr<StringStore> getHeadShared() { - forkIfNeeded(); - invariant(_workingCopy.get()); - return _workingCopy; - } - - void makeDirty(); - - /** - * Checks if there already exists a current working copy and merge base; if not fetches - * one and creates them. - */ - bool forkIfNeeded(); - - static RecoveryUnit* get(OperationContext* opCtx); - -private: - void doBeginUnitOfWork() override final; - - void doCommitUnitOfWork() override final; - - void doAbortUnitOfWork() override final; - - void doAbandonSnapshot() override final; - - void _abort(); - - void _setMergeNull(); - - std::function<void()> _waitUntilDurableCallback; - // Official master is kept by KVEngine - KVEngine* _KVEngine; - // We need _mergeBase to be a shared_ptr to hold references in KVEngine::_availableHistory. - // _mergeBase will be initialized in forkIfNeeded(). - std::shared_ptr<StringStore> _mergeBase; - - // '_workingCopy' is a separate copy of the entire tree, owned by this recovery unit and - // cursors associated with it. It is not shared between recovery units. - std::shared_ptr<StringStore> _workingCopy; - - bool _forked = false; - bool _dirty = false; // Whether or not we have written to this _workingCopy. - - Timestamp _prepareTimestamp = Timestamp::min(); - Timestamp _commitTimestamp = Timestamp::min(); - - bool _isTimestamped = false; - - // Specifies which external source to use when setting read timestamps on transactions. - ReadSource _timestampReadSource = ReadSource::kNoTimestamp; - boost::optional<Timestamp> _readAtTimestamp = boost::none; -}; - -} // namespace ephemeral_for_test -} // namespace mongo diff --git a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_recovery_unit_test.cpp b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_recovery_unit_test.cpp deleted file mode 100644 index 2b638c82c3e..00000000000 --- a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_recovery_unit_test.cpp +++ /dev/null @@ -1,124 +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. - */ - -#include "mongo/platform/basic.h" - -#include <memory> - -#include "mongo/base/init.h" -#include "mongo/db/service_context.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_kv_engine.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_recovery_unit.h" -#include "mongo/db/storage/recovery_unit_test_harness.h" -#include "mongo/unittest/unittest.h" - -namespace mongo { -namespace ephemeral_for_test { -namespace { - -class RecoveryUnitHarnessHelper final : public mongo::RecoveryUnitHarnessHelper { -public: - RecoveryUnitHarnessHelper() = default; - - virtual std::unique_ptr<mongo::RecoveryUnit> newRecoveryUnit() final { - return std::make_unique<RecoveryUnit>(&_kvEngine); - } - - virtual std::unique_ptr<mongo::RecordStore> createRecordStore(OperationContext* opCtx, - const std::string& ns) { - return std::make_unique<RecordStore>(ns, - "ident"_sd /* ident */, - KeyFormat::Long, - false /* isCapped */, - nullptr /* cappedCallback */); - } - -private: - KVEngine _kvEngine{}; -}; - -std::unique_ptr<mongo::RecoveryUnitHarnessHelper> makeRecoveryUnitHarnessHelper() { - return std::make_unique<RecoveryUnitHarnessHelper>(); -} - -MONGO_INITIALIZER(RegisterRecoveryUnitHarnessFactory)(InitializerContext* const) { - mongo::registerRecoveryUnitHarnessHelperFactory(makeRecoveryUnitHarnessHelper); -} - -class EphemeralForTestRecoveryUnitTestHarness : public unittest::Test { -public: - void setUp() override { - harnessHelper = makeRecoveryUnitHarnessHelper(); - opCtx = harnessHelper->newOperationContext(); - ru = opCtx->recoveryUnit(); - } - - std::unique_ptr<mongo::RecoveryUnitHarnessHelper> harnessHelper; - ServiceContext::UniqueOperationContext opCtx; - mongo::RecoveryUnit* ru; -}; - -TEST_F(EphemeralForTestRecoveryUnitTestHarness, AbandonSnapshotAbortMode) { - Lock::GlobalLock globalLk(opCtx.get(), MODE_IX); - - ru->setAbandonSnapshotMode(RecoveryUnit::AbandonSnapshotMode::kAbort); - - const auto rs = harnessHelper->createRecordStore(opCtx.get(), "table1"); - opCtx->lockState()->beginWriteUnitOfWork(); - ru->beginUnitOfWork(opCtx->readOnly()); - StatusWith<RecordId> rid1 = rs->insertRecord(opCtx.get(), "ABC", 3, Timestamp()); - StatusWith<RecordId> rid2 = rs->insertRecord(opCtx.get(), "123", 3, Timestamp()); - ASSERT_TRUE(rid1.isOK()); - ASSERT_TRUE(rid2.isOK()); - ASSERT_EQUALS(2, rs->numRecords(opCtx.get())); - ru->commitUnitOfWork(); - opCtx->lockState()->endWriteUnitOfWork(); - - auto snapshotIdBefore = ru->getSnapshotId(); - - // Now create a cursor. - auto cursor = rs->getCursor(opCtx.get()); - - auto record = cursor->next(); - ASSERT(record); - ASSERT_EQ(record->id, rid1.getValue()); - - // Abandon the snapshot. - ru->abandonSnapshot(); - ASSERT_NE(snapshotIdBefore, ru->getSnapshotId()); - - // After the snapshot is abandoned, calls to next() will simply use a newer snapshot. - // This behavior is specific to EFT, and other engines may behave differently. - auto record2 = cursor->next(); - ASSERT(record2); - ASSERT_EQ(record2->id, rid2.getValue()); -} -} // namespace -} // namespace ephemeral_for_test -} // namespace mongo diff --git a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_server_status.cpp b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_server_status.cpp deleted file mode 100644 index 32939677129..00000000000 --- a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_server_status.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright (C) 2020-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. - */ - -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kFTDC - -#include "mongo/platform/basic.h" - -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_server_status.h" - -#include "mongo/bson/bsonobjbuilder.h" -#include "mongo/db/concurrency/d_concurrency.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_kv_engine.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.h" -#include "mongo/logv2/log.h" - -namespace mongo { -namespace ephemeral_for_test { - -ServerStatusSection::ServerStatusSection(KVEngine* engine) - : mongo::ServerStatusSection(kEngineName), _engine(engine) {} - -bool ServerStatusSection::includeByDefault() const { - return true; -} - -BSONObj ServerStatusSection::generateSection(OperationContext* opCtx, - const BSONElement& configElement) const { - Lock::GlobalLock lk( - opCtx, LockMode::MODE_IS, Date_t::now(), Lock::InterruptBehavior::kLeaveUnlocked); - if (!lk.isLocked()) { - LOGV2_DEBUG(4919800, 2, "Failed to retrieve ephemeralForTest statistics"); - return BSONObj(); - } - - BSONObjBuilder bob; - bob.append("totalMemoryUsage", StringStore::totalMemory()); - bob.append("totalNodes", StringStore::totalNodes()); - bob.append("averageChildren", StringStore::averageChildren()); - - return bob.obj(); -} - -} // namespace ephemeral_for_test -} // namespace mongo diff --git a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_server_status.h b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_server_status.h deleted file mode 100644 index 12868f8a7cc..00000000000 --- a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_server_status.h +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (C) 2020-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 "mongo/db/commands/server_status.h" - -namespace mongo { -namespace ephemeral_for_test { - -class KVEngine; - -/** - * Adds "biggie" to the results of db.serverStatus(). - */ -class ServerStatusSection : public mongo::ServerStatusSection { -public: - ServerStatusSection(KVEngine* engine); - bool includeByDefault() const override; - BSONObj generateSection(OperationContext* opCtx, - const BSONElement& configElement) const override; - -private: - KVEngine* _engine; -}; - -} // namespace ephemeral_for_test -} // namespace mongo diff --git a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_sorted_impl.cpp b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_sorted_impl.cpp deleted file mode 100644 index 519226630c6..00000000000 --- a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_sorted_impl.cpp +++ /dev/null @@ -1,1662 +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. - */ - -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kStorage - -#include "mongo/platform/basic.h" - -#include <boost/iterator/iterator_facade.hpp> -#include <cstring> -#include <memory> -#include <string> - -#include "mongo/bson/bsonobj.h" -#include "mongo/bson/bsonobjbuilder.h" -#include "mongo/db/catalog/collection.h" -#include "mongo/db/catalog/index_catalog_entry.h" -#include "mongo/db/catalog/validate_results.h" -#include "mongo/db/index/index_descriptor.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_recovery_unit.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_sorted_impl.h" -#include "mongo/db/storage/index_entry_comparison.h" -#include "mongo/db/storage/key_string.h" -#include "mongo/util/bufreader.h" -#include "mongo/util/hex.h" -#include "mongo/util/shared_buffer.h" -#include "mongo/util/str.h" - -namespace mongo { -namespace ephemeral_for_test { -namespace { - -// Helper to interpret index data buffer -class IndexDataEntry { -public: - IndexDataEntry() : _buffer(nullptr), _keyFormat(KeyFormat::Long) {} - IndexDataEntry(const uint8_t* buffer, KeyFormat keyFormat); - IndexDataEntry(const std::string& indexDataEntry, KeyFormat keyFormat); - - static std::string create(RecordId loc, const KeyString::TypeBits& typeBits); - - const uint8_t* buffer() const; - size_t size() const; // returns buffer size - RecordId loc() const; - KeyString::TypeBits typeBits() const; - KeyFormat keyFormat() const; - -private: - const uint8_t* _buffer; - KeyFormat _keyFormat; -}; - -// Forward iterator for IndexDataEntry with contigous memory layout -class IndexDataEntryIterator : public boost::iterator_facade<IndexDataEntryIterator, - IndexDataEntry const, - boost::forward_traversal_tag> { -public: - IndexDataEntryIterator() = default; - IndexDataEntryIterator(const uint8_t* entryData, KeyFormat keyFormat); - -private: - friend class boost::iterator_core_access; - - void increment(); - bool equal(IndexDataEntryIterator const& other) const; - const IndexDataEntry& dereference() const; - - IndexDataEntry _entry; -}; - -// Helper to interpret index data buffer for unique index -class UniqueIndexData { -public: - UniqueIndexData() : _size(0), _begin(nullptr), _end(nullptr), _keyFormat(KeyFormat::Long) {} - UniqueIndexData(const std::string& indexData, KeyFormat keyFormat) { - std::memcpy(&_size, indexData.data(), sizeof(uint64_t)); - _begin = reinterpret_cast<const uint8_t*>(indexData.data() + sizeof(uint64_t)); - _end = reinterpret_cast<const uint8_t*>(indexData.data() + indexData.size()); - _keyFormat = keyFormat; - } - - using const_iterator = IndexDataEntryIterator; - - size_t size() const { - return _size; - } - bool empty() const { - return _size == 0; - } - const_iterator begin() const { - return IndexDataEntryIterator(_begin, _keyFormat); - } - const_iterator end() const { - return IndexDataEntryIterator(_end, _keyFormat); - } - const_iterator lower_bound(RecordId loc) const; - const_iterator upper_bound(RecordId loc) const; - - // Creates a new UniqueIndexData buffer containing an additional item. Returns boost::none if - // entry already exists. - boost::optional<std::string> add(RecordId loc, const KeyString::TypeBits& typeBits); - - // Creates a new UniqueIndexData buffer with item with RecordId removed. Returns boost::none if - // entry did not exist. - boost::optional<std::string> remove(RecordId loc); - -private: - size_t _memoryUsage() const; - - size_t _size; - const uint8_t* _begin; - const uint8_t* _end; - KeyFormat _keyFormat; -}; - -IndexDataEntry::IndexDataEntry(const uint8_t* buffer, KeyFormat keyFormat) - : _buffer(buffer), _keyFormat(keyFormat) {} -IndexDataEntry::IndexDataEntry(const std::string& indexDataEntry, KeyFormat keyFormat) - : _buffer(reinterpret_cast<const uint8_t*>(indexDataEntry.data())), _keyFormat(keyFormat) {} - -std::string IndexDataEntry::create(RecordId loc, const KeyString::TypeBits& typeBits) { - // [RecordId size, RecordId, TypeBits size, TypeBits] - std::string entry; - auto writeSizeT = [&entry](size_t val) { - auto data = reinterpret_cast<const char*>(&val); - entry.append(data, data + sizeof(val)); - }; - - if (loc.isLong()) { - writeSizeT(sizeof(int64_t)); - writeSizeT(loc.getLong()); - } else { - auto str = loc.getStr(); - writeSizeT(str.size()); - entry.append(str.rawData(), str.size()); - } - writeSizeT(typeBits.getSize()); - entry.append(typeBits.getBuffer(), typeBits.getSize()); - return entry; -} - -const uint8_t* IndexDataEntry::buffer() const { - return _buffer; -} - -KeyFormat IndexDataEntry::keyFormat() const { - return _keyFormat; -} - -size_t IndexDataEntry::size() const { - // [RecordId size, RecordId, TypeBits size, TypeBits] - size_t ridSize; - std::memcpy(&ridSize, _buffer, sizeof(size_t)); - - int len = sizeof(size_t) + ridSize; - size_t typeBitsSize; - std::memcpy(&typeBitsSize, _buffer + len, sizeof(size_t)); - - len += sizeof(size_t) + typeBitsSize; - return len; -} - -RecordId IndexDataEntry::loc() const { - // [RecordId size, RecordId, TypeBits size, TypeBits] - size_t ridSize; - std::memcpy(&ridSize, _buffer, sizeof(size_t)); - const uint8_t* ridStart = _buffer + sizeof(size_t); - if (KeyFormat::Long == _keyFormat) { - int64_t repr; - std::memcpy(&repr, ridStart, ridSize); - return RecordId(repr); - } else { - return RecordId(reinterpret_cast<const char*>(ridStart), ridSize); - } -} - -KeyString::TypeBits IndexDataEntry::typeBits() const { - // [RecordId size, RecordId, TypeBits size, TypeBits] - size_t ridSize; - std::memcpy(&ridSize, _buffer, sizeof(size_t)); - - int len = sizeof(size_t) + ridSize; - size_t typeBitsSize; - std::memcpy(&typeBitsSize, _buffer + len, sizeof(size_t)); - - len += sizeof(size_t); - - BufReader reader(_buffer + len, typeBitsSize); - return KeyString::TypeBits::fromBuffer(KeyString::Version::kLatestVersion, &reader); -} - -IndexDataEntryIterator::IndexDataEntryIterator(const uint8_t* entryData, KeyFormat keyFormat) - : _entry(IndexDataEntry(entryData, keyFormat)) {} -void IndexDataEntryIterator::increment() { - _entry = IndexDataEntry(_entry.buffer() + _entry.size(), _entry.keyFormat()); -} -bool IndexDataEntryIterator::equal(IndexDataEntryIterator const& other) const { - return _entry.buffer() == other._entry.buffer(); -} - -const IndexDataEntry& IndexDataEntryIterator::dereference() const { - return _entry; -} - -UniqueIndexData::const_iterator UniqueIndexData::lower_bound(RecordId loc) const { - // Linear search to the first item not less than loc - return std::find_if_not( - begin(), end(), [loc](const IndexDataEntry& entry) { return entry.loc() < loc; }); -} -UniqueIndexData::const_iterator UniqueIndexData::upper_bound(RecordId loc) const { - // Linear search to the first item larger than loc - auto lb = lower_bound(loc); - return std::find_if( - lb, end(), [loc](const IndexDataEntry& entry) { return loc < entry.loc(); }); -} - -size_t UniqueIndexData::_memoryUsage() const { - return sizeof(_size) + (_end - _begin); -} - -boost::optional<std::string> UniqueIndexData::add(RecordId loc, - const KeyString::TypeBits& typeBits) { - // If entry already exists then nothing to do - auto it = lower_bound(loc); - if (it != end() && it->loc() == loc) - return boost::none; - - auto itBuffer = it->buffer(); - std::string entry = IndexDataEntry::create(loc, typeBits); - - // Allocate string that fit the new entry - std::string output(_memoryUsage() + entry.size(), '\0'); - auto pos = output.data(); - - // Write number of entries - uint64_t num = size() + 1; - std::memcpy(pos, &num, sizeof(num)); - pos += sizeof(num); - - // Write old entries smaller than the new one - if (auto bytes = itBuffer - _begin) { - std::memcpy(pos, _begin, bytes); - pos += bytes; - } - - // Write new entry - std::memcpy(pos, entry.data(), entry.size()); - pos += entry.size(); - - // Write old entries larger than the new one - if (auto bytes = _end - itBuffer) { - std::memcpy(pos, itBuffer, bytes); - } - - return output; -} -boost::optional<std::string> UniqueIndexData::remove(RecordId loc) { - // If entry doesn't exist then nothing to do - auto it = lower_bound(loc); - if (it == end() || it->loc() != loc) - return boost::none; - - // Allocate string with approrpriate amount of space - std::string output(_memoryUsage() - it->size(), '\0'); - auto pos = output.data(); - - // Write number of entries - uint64_t num = size() - 1; - std::memcpy(pos, &num, sizeof(num)); - pos += sizeof(num); - - // Write entries before entry to remove - std::memcpy(pos, _begin, it->buffer() - _begin); - pos += it->buffer() - _begin; - - // Skip entry to remove and write remaining entries - ++it; - std::memcpy(pos, it->buffer(), _end - it->buffer()); - return output; -} - -const Ordering allAscending = Ordering::make(BSONObj()); - -void prefixKeyStringWithoutLoc(KeyString::Builder* keyString, const std::string& prefixToUse) { - BSONObjBuilder b; - b.append("", prefixToUse); // prefix - b.append("", StringData(keyString->getBuffer(), keyString->getSize())); // key - - keyString->resetToKey(b.obj(), allAscending); -} - -void prefixKeyStringWithLoc(KeyString::Builder* keyString, - RecordId loc, - const std::string& prefixToUse) { - BSONObjBuilder b; - b.append("", prefixToUse); // prefix - b.append("", StringData(keyString->getBuffer(), keyString->getSize())); // key - - keyString->resetToKey(b.obj(), allAscending, loc); -} - -void prefixMinKeyString(KeyString::Builder* keyString, const std::string& prefixToUse) { - BSONObjBuilder b; - b.append("", prefixToUse); // prefix - b.append("", StringData(keyString->getBuffer(), keyString->getSize())); // key - - keyString->resetToKey(b.obj(), allAscending, KeyString::Discriminator::kExclusiveBefore); -} - -void prefixMaxKeyString(KeyString::Builder* keyString, const std::string& prefixToUse) { - BSONObjBuilder b; - b.append("", prefixToUse); // prefix - b.append("", StringData(keyString->getBuffer(), keyString->getSize())); // key - - keyString->resetToKey(b.obj(), allAscending, KeyString::Discriminator::kExclusiveAfter); -} - -std::string createRadixKeyWithoutLocFromObj(const BSONObj& key, - const std::string& prefixToUse, - Ordering order) { - KeyString::Version version = KeyString::Version::kLatestVersion; - KeyString::Builder ks(version, BSONObj::stripFieldNames(key), order); - - prefixKeyStringWithoutLoc(&ks, prefixToUse); - return std::string(ks.getBuffer(), ks.getSize()); -} - -std::string createRadixKeyWithoutLocFromKS(const KeyString::Value& keyString, - const std::string& prefixToUse, - KeyFormat keyFormat) { - KeyString::Builder ks(KeyString::Version::kLatestVersion); - auto ksBuffer = keyString.getBuffer(); - if (ksBuffer) { - if (KeyFormat::Long == keyFormat) { - ks.resetFromBuffer( - ksBuffer, KeyString::sizeWithoutRecordIdLongAtEnd(ksBuffer, keyString.getSize())); - } else { - ks.resetFromBuffer( - ksBuffer, KeyString::sizeWithoutRecordIdStrAtEnd(ksBuffer, keyString.getSize())); - } - } - prefixKeyStringWithoutLoc(&ks, prefixToUse); - return std::string(ks.getBuffer(), ks.getSize()); -} - -std::string createRadixKeyWithoutLocFromKSWithoutRecordId(const KeyString::Value& keyString, - const std::string& prefixToUse) { - KeyString::Builder ks(KeyString::Version::kLatestVersion); - auto ksBuffer = keyString.getBuffer(); - if (ksBuffer) - ks.resetFromBuffer(ksBuffer, keyString.getSize()); - prefixKeyStringWithoutLoc(&ks, prefixToUse); - return std::string(ks.getBuffer(), ks.getSize()); -} - -std::string createMinRadixKeyFromObj(const BSONObj& key, - const std::string& prefixToUse, - Ordering order) { - KeyString::Version version = KeyString::Version::kLatestVersion; - KeyString::Builder ks(version, BSONObj::stripFieldNames(key), order); - - prefixMinKeyString(&ks, prefixToUse); - return std::string(ks.getBuffer(), ks.getSize()); -} - -std::string createMaxRadixKeyFromObj(const BSONObj& key, - const std::string& prefixToUse, - Ordering order) { - KeyString::Version version = KeyString::Version::kLatestVersion; - KeyString::Builder ks(version, BSONObj::stripFieldNames(key), order); - - prefixMaxKeyString(&ks, prefixToUse); - return std::string(ks.getBuffer(), ks.getSize()); -} - - -std::string createRadixKeyWithLocFromKS(const KeyString::Value& keyString, - RecordId loc, - const std::string& prefixToUse) { - KeyString::Builder ks(KeyString::Version::kLatestVersion); - auto ksBuffer = keyString.getBuffer(); - if (ksBuffer) { - if (loc.isLong()) { - ks.resetFromBuffer( - ksBuffer, KeyString::sizeWithoutRecordIdLongAtEnd(ksBuffer, keyString.getSize())); - } else { - ks.resetFromBuffer( - ksBuffer, KeyString::sizeWithoutRecordIdStrAtEnd(ksBuffer, keyString.getSize())); - } - } - prefixKeyStringWithLoc(&ks, loc, prefixToUse); - return std::string(ks.getBuffer(), ks.getSize()); -} - -std::string createMinRadixKeyFromKSWithoutRecordId(const KeyString::Value& keyString, - const std::string& prefixToUse) { - KeyString::Builder ks(KeyString::Version::kLatestVersion); - auto ksBuffer = keyString.getBuffer(); - if (ksBuffer) - ks.resetFromBuffer(ksBuffer, keyString.getSize()); - prefixMinKeyString(&ks, prefixToUse); - return std::string(ks.getBuffer(), ks.getSize()); -} - -std::string createMaxRadixKeyFromKSWithoutRecordId(const KeyString::Value& keyString, - const std::string& prefixToUse) { - KeyString::Builder ks(KeyString::Version::kLatestVersion); - auto ksBuffer = keyString.getBuffer(); - if (ksBuffer) - ks.resetFromBuffer(ksBuffer, keyString.getSize()); - prefixMaxKeyString(&ks, prefixToUse); - return std::string(ks.getBuffer(), ks.getSize()); -} - -BSONObj createObjFromRadixKey(const std::string& radixKey, - const KeyString::TypeBits& typeBits, - const Ordering& order) { - KeyString::Version version = KeyString::Version::kLatestVersion; - KeyString::TypeBits tbOuter = KeyString::TypeBits(version); - BSONObj bsonObj = - KeyString::toBsonSafe(radixKey.data(), radixKey.size(), allAscending, tbOuter); - - SharedBuffer sb; - auto it = BSONObjIterator(bsonObj); - ++it; // We want the second part - KeyString::Builder ks(version); - ks.resetFromBuffer((*it).valueStringDataSafe().rawData(), (*it).valueStringDataSafe().size()); - - return KeyString::toBsonSafe(ks.getBuffer(), ks.getSize(), order, typeBits); -} - -IndexKeyEntry createIndexKeyEntryFromRadixKey(const std::string& radixKey, - RecordId loc, - const KeyString::TypeBits& typeBits, - const Ordering order) { - return IndexKeyEntry(createObjFromRadixKey(radixKey, typeBits, order), loc); -} - -IndexKeyEntry createIndexKeyEntryFromRadixKey(const std::string& radixKey, - const std::string& indexDataEntry, - const Ordering order, - KeyFormat keyFormat) { - IndexDataEntry data(indexDataEntry, keyFormat); - return IndexKeyEntry(createObjFromRadixKey(radixKey, data.typeBits(), order), data.loc()); -} - -boost::optional<KeyStringEntry> createKeyStringEntryFromRadixKey( - const std::string& radixKey, - RecordId loc, - const KeyString::TypeBits& typeBits, - const Ordering& order) { - auto key = createObjFromRadixKey(radixKey, typeBits, order); - KeyString::Builder ksFinal(KeyString::Version::kLatestVersion, key, order); - ksFinal.appendRecordId(loc); - return KeyStringEntry(ksFinal.getValueCopy(), loc); -} - -boost::optional<KeyStringEntry> createKeyStringEntryFromRadixKey(const std::string& radixKey, - const std::string& indexDataEntry, - const Ordering& order, - KeyFormat keyFormat) { - IndexDataEntry data(indexDataEntry, keyFormat); - RecordId loc = data.loc(); - auto key = createObjFromRadixKey(radixKey, data.typeBits(), order); - KeyString::Builder ksFinal(KeyString::Version::kLatestVersion, key, order); - ksFinal.appendRecordId(loc); - return KeyStringEntry(ksFinal.getValueCopy(), loc); -} - -/* - * This is the base cursor class required by the sorted data interface. - * Using CRTP (static inheritance) to reuse shared implementation for cursors over unique and - * standard indexes - */ -template <class CursorImpl> -class CursorBase : public ::mongo::SortedDataInterface::Cursor { -public: - // All the following public functions just implement the interface. - CursorBase(OperationContext* opCtx, - bool isForward, - // This is the ident. - std::string _prefix, - // This is a string immediately after the ident and before other idents. - std::string _identEnd, - std::shared_ptr<StringStore> workingCopy, - Ordering order, - KeyFormat keyFormat, - std::string prefixBSON, - std::string KSForIdentEnd); - virtual void setEndPosition(const BSONObj& key, bool inclusive) override; - virtual boost::optional<IndexKeyEntry> seek(const KeyString::Value& keyString, - RequestedInfo parts = kKeyAndLoc) override; - virtual boost::optional<KeyStringEntry> seekForKeyString( - const KeyString::Value& keyStringValue) override; - virtual void save() override; - virtual void restore() override; - virtual void detachFromOperationContext() override; - virtual void reattachToOperationContext(OperationContext* opCtx) override; - void setSaveStorageCursorOnDetachFromOperationContext(bool) override { - // Noop for EFT, since we always keep the cursor's contents valid across save/restore. - } - bool isRecordIdAtEndOfKeyString() const override { - return true; - } - -private: - // CRTP Interface - boost::optional<KeyStringEntry> finishSeekAfterProcessing() { - return static_cast<CursorImpl*>(this)->finishSeekAfterProcessing(); - } - bool advanceNextInternal() { - return static_cast<CursorImpl*>(this)->advanceNextInternal(); - } - void finishAdvanceNext() { - static_cast<CursorImpl*>(this)->finishAdvanceNext(); - } - bool checkCursorValid() { - return static_cast<CursorImpl*>(this)->checkCursorValid(); - } - void saveForward() { - return static_cast<CursorImpl*>(this)->saveForward(); - } - void saveReverse() { - return static_cast<CursorImpl*>(this)->saveReverse(); - } - void restoreForward() { - return static_cast<CursorImpl*>(this)->restoreForward(); - } - void restoreReverse() { - return static_cast<CursorImpl*>(this)->restoreReverse(); - } - -protected: - // Helper function which changes the cursor to point to data in the latest snapshot from the - // recovery unit. If no transaction was committed or aborted since the last call, this is a - // noop. - void advanceSnapshotIfChanged(); - - bool advanceNext(); - // This is a helper function to check if the cursor was explicitly set by the user or not. - bool endPosSet(); - // This is a helper function for seek. - boost::optional<IndexKeyEntry> seekAfterProcessing(BSONObj finalKey); - boost::optional<KeyStringEntry> seekAfterProcessing(const KeyString::Value& keyString); - OperationContext* _opCtx; - // This is the "working copy" of the master "branch" in the git analogy. - // Its ownership is split across the RecoveryUnit and associated cursors. It is not - // shared _between_ recovery units. - std::shared_ptr<StringStore> _workingCopy; - // These store the end positions. - 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). - bool _atEOF; - // This means whether or not the last move was restore. - bool _lastMoveWasRestore; - // This is the keystring for the saved location. - std::string _saveKey; - RecordId _saveLoc; - // These are the same as before. - std::string _prefix; - std::string _identEnd; - // 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; - KeyFormat _keyFormat; - // This stores whether or not the end position is inclusive for restore. - bool _endPosIncl; - // This stores the key for the end position. - boost::optional<BSONObj> _endPosKey; - // The next two are the same as above. - std::string _KSForIdentStart; - std::string _KSForIdentEnd; -}; - -// Cursor -template <class CursorImpl> -CursorBase<CursorImpl>::CursorBase(OperationContext* opCtx, - bool isForward, - std::string _prefix, - std::string _identEnd, - std::shared_ptr<StringStore> workingCopy, - Ordering order, - KeyFormat keyFormat, - std::string _KSForIdentStart, - std::string identEndBSON) - : _opCtx(opCtx), - _workingCopy(std::move(workingCopy)), - _endPos(boost::none), - _endPosReverse(boost::none), - _forward(isForward), - _atEOF(false), - _lastMoveWasRestore(false), - _prefix(_prefix), - _identEnd(_identEnd), - _forwardIt(_workingCopy->begin()), - _reverseIt(_workingCopy->rbegin()), - _order(order), - _keyFormat(keyFormat), - _endPosIncl(false), - _KSForIdentStart(_KSForIdentStart), - _KSForIdentEnd(identEndBSON) {} - -template <class CursorImpl> -void CursorBase<CursorImpl>::advanceSnapshotIfChanged() { - if (_workingCopy.get() != RecoveryUnit::get(_opCtx)->getHead()) { - save(); - restore(); - } -} - -template <class CursorImpl> -bool CursorBase<CursorImpl>::advanceNext() { - if (!_atEOF) { - // If the last move was restore, then we don't need to advance the cursor, since the user - // never got the value the cursor was pointing to in the first place. However, - // _lastMoveWasRestore will go through extra logic on a unique index, since unique indexes - // are not allowed to return the same key twice. - if (_lastMoveWasRestore) { - _lastMoveWasRestore = false; - } else { - if (advanceNextInternal()) - return true; - - // We basically just check to make sure the cursor is in the ident. - if (_forward && checkCursorValid()) { - ++_forwardIt; - } else if (!_forward && checkCursorValid()) { - ++_reverseIt; - } - // We check here to make sure that we are on the correct side of the end position, and - // that the cursor is still in the ident after advancing. - if (!checkCursorValid()) { - _atEOF = true; - return false; - } - } - } else { - _lastMoveWasRestore = false; - return false; - } - - finishAdvanceNext(); - - return true; -} - -// This function checks whether or not the cursor end position was set by the user or not. -template <class CursorImpl> -bool CursorBase<CursorImpl>::endPosSet() { - return (_forward && _endPos != boost::none) || (!_forward && _endPosReverse != boost::none); -} - -template <class CursorImpl> -void CursorBase<CursorImpl>::setEndPosition(const BSONObj& key, bool inclusive) { - StringStore* workingCopy(RecoveryUnit::get(_opCtx)->getHead()); - if (key.isEmpty()) { - _endPos = boost::none; - _endPosReverse = boost::none; - return; - } - _endPosIncl = inclusive; - _endPosKey = key; - StringStore::const_iterator it; - // If forward and inclusive or reverse and not inclusive, then we use the last element in this - // ident. Otherwise, we use the first as our bound. - if (_forward == inclusive) - it = workingCopy->upper_bound(createMaxRadixKeyFromObj(key, _prefix, _order)); - else - it = workingCopy->lower_bound(createMinRadixKeyFromObj(key, _prefix, _order)); - if (_forward) - _endPos = it; - else - _endPosReverse = StringStore::const_reverse_iterator(it); -} - -template <class CursorImpl> -boost::optional<IndexKeyEntry> CursorBase<CursorImpl>::seekAfterProcessing(BSONObj finalKey) { - std::string workingCopyBound; - - KeyString::Builder ks(KeyString::Version::kLatestVersion, finalKey, _order); - auto ksEntry = seekAfterProcessing(ks.getValueCopy()); - - const BSONObj bson = KeyString::toBson(ksEntry->keyString.getBuffer(), - ksEntry->keyString.getSize(), - _order, - ksEntry->keyString.getTypeBits()); - return IndexKeyEntry(bson, ksEntry->loc); -} - -template <class CursorImpl> -boost::optional<KeyStringEntry> CursorBase<CursorImpl>::seekAfterProcessing( - const KeyString::Value& keyStringVal) { - - KeyString::Discriminator discriminator = KeyString::decodeDiscriminator( - keyStringVal.getBuffer(), keyStringVal.getSize(), _order, keyStringVal.getTypeBits()); - - bool inclusive; - switch (discriminator) { - case KeyString::Discriminator::kInclusive: - inclusive = true; - break; - case KeyString::Discriminator::kExclusiveBefore: - inclusive = _forward; - break; - case KeyString::Discriminator::kExclusiveAfter: - inclusive = !_forward; - break; - } - - // If the key is empty and it's not inclusive, then no elements satisfy this seek. - if (keyStringVal.isEmpty() && !inclusive) { - _atEOF = true; - return boost::none; - } - - StringStore::const_iterator it; - // Forward inclusive seek uses lower_bound and exclusive upper_bound. For reverse iterators this - // is also reversed. - if (_forward == inclusive) - it = _workingCopy->lower_bound( - createMinRadixKeyFromKSWithoutRecordId(keyStringVal, _prefix)); - else - it = _workingCopy->upper_bound( - createMaxRadixKeyFromKSWithoutRecordId(keyStringVal, _prefix)); - if (_forward) - _forwardIt = it; - else - _reverseIt = StringStore::const_reverse_iterator(it); - - // 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 - // position, if it was set. - if (!checkCursorValid()) { - _atEOF = true; - return boost::none; - } - - return finishSeekAfterProcessing(); -} - -template <class CursorImpl> -boost::optional<IndexKeyEntry> CursorBase<CursorImpl>::seek(const KeyString::Value& keyString, - RequestedInfo parts) { - boost::optional<KeyStringEntry> ksValue = seekForKeyString(keyString); - if (ksValue) { - BSONObj bson = KeyString::toBson(ksValue->keyString.getBuffer(), - ksValue->keyString.getSize(), - _order, - ksValue->keyString.getTypeBits()); - return IndexKeyEntry(bson, ksValue->loc); - } - return boost::none; -} - -template <class CursorImpl> -boost::optional<KeyStringEntry> CursorBase<CursorImpl>::seekForKeyString( - const KeyString::Value& keyStringValue) { - advanceSnapshotIfChanged(); - - _lastMoveWasRestore = false; - _atEOF = false; - return seekAfterProcessing(keyStringValue); -} - -template <class CursorImpl> -void CursorBase<CursorImpl>::save() { - _atEOF = false; - if (_lastMoveWasRestore || _workingCopy == nullptr) { - // If we just restored, or have no working copy (in which case we're already in a saved - // state), do nothing. - return; - } - - // Any dereference of the _forwardIt and _reverseIt may result in them getting repositioned if - // the key they were pointing at was removed in our snapshot. Before accessing them - // (checking for validity, dereferencing etc) we save the current key they are pointing at. - if (_forward && _forwardIt.currentRaw()) { - _saveKey = _forwardIt.currentRaw()->first; - } else if (_reverseIt.currentRaw()) { - _saveKey = _reverseIt.currentRaw()->first; - } - - if (_forward && checkCursorValid()) { - saveForward(); - } else if (!_forward && checkCursorValid()) { // reverse - saveReverse(); - } else { - _saveKey = ""; - _saveLoc = RecordId(); - } - _workingCopy = nullptr; -} - -template <class CursorImpl> -void CursorBase<CursorImpl>::restore() { - _workingCopy = RecoveryUnit::get(_opCtx)->getHeadShared(); - - // Here, we have to reset the end position if one was set earlier. - if (endPosSet()) { - setEndPosition(*_endPosKey, _endPosIncl); - } - - // We reset the cursor, and make sure it's within the end position bounds. It doesn't matter if - // the cursor is not in the ident right now, since that will be taken care of upon the call to - // next(). - if (_forward) { - if (_saveKey.length() == 0) { - _forwardIt = _workingCopy->end(); - } else { - _forwardIt = _workingCopy->lower_bound(_saveKey); - } - restoreForward(); - } else { - // Now we are dealing with reverse cursors, and use similar logic. - if (_saveKey.length() == 0) { - _reverseIt = _workingCopy->rend(); - } else { - _reverseIt = StringStore::const_reverse_iterator(_workingCopy->upper_bound(_saveKey)); - } - restoreReverse(); - } -} - -template <class CursorImpl> -void CursorBase<CursorImpl>::detachFromOperationContext() { - _opCtx = nullptr; -} - -template <class CursorImpl> -void CursorBase<CursorImpl>::reattachToOperationContext(OperationContext* opCtx) { - this->_opCtx = opCtx; -} - -/* - * This is the cursor class required by the sorted data interface for unique indexes. - */ -class CursorUnique final : public CursorBase<CursorUnique> { -public: - using CursorBase::CursorBase; - - virtual boost::optional<IndexKeyEntry> next(RequestedInfo parts = kKeyAndLoc) override; - virtual boost::optional<KeyStringEntry> nextKeyString() override; - -private: - // Implementations of CursorBase interface - friend class CursorBase; - - bool advanceNextInternal(); - void finishAdvanceNext(); - boost::optional<KeyStringEntry> finishSeekAfterProcessing(); - - void saveForward(); - void saveReverse(); - void restoreForward(); - void restoreReverse(); - - // This is a helper function to check if the cursor is valid or not. - bool checkCursorValid(); - // Helper function to set index data iterators to reverse position, we cannot use reverse - // iterators because we only have forward iterator support over this data - void initReverseDataIterators(); - // Unpacked data from current position in the radix tree. Needed to iterate over indexes - // containing duplicates - UniqueIndexData _indexData; - UniqueIndexData::const_iterator _indexDataIt; - UniqueIndexData::const_iterator _indexDataEnd; - size_t _reversePos = 0; -}; - -bool CursorUnique::advanceNextInternal() { - // Iterate over duplicates before moving to the next item in the radix tree - if (!_indexData.empty()) { - if (_forward) { - if (++_indexDataIt != _indexDataEnd) - return true; - } else { - if (++_reversePos < _indexData.size()) { - initReverseDataIterators(); - return true; - } - } - } - return false; -} - -void CursorUnique::finishAdvanceNext() { - // We have moved to a new position in the tree, initialize index data for iterating over - // duplicates - if (_forward) { - _indexData = UniqueIndexData(_forwardIt->second, _keyFormat); - _indexDataIt = _indexData.begin(); - _indexDataEnd = _indexData.end(); - } else { - _indexData = UniqueIndexData(_reverseIt->second, _keyFormat); - _reversePos = 0; - initReverseDataIterators(); - } -} - -// This function checks whether or not a cursor is valid. In particular, it checks 1) whether the -// cursor is at end() or rend(), 2) whether the cursor is on the wrong side of the end position -// if it was set, and 3) whether the cursor is still in the ident. -bool CursorUnique::checkCursorValid() { - if (_forward) { - if (_forwardIt == _workingCopy->end()) { - return false; - } - if (endPosSet()) { - // The endPos must be in the ident, at most one past the ident, or end. Therefore, the - // endPos includes the check for being inside the ident - if (_endPosIncl) { - if (*_endPos == _workingCopy->end()) - return true; - - // For unique indexes, we need to check if the cursor moved up a position when it - // was restored. This isn't required for non-unique indexes because we store the - // RecordId in the KeyString and use a "<" comparison instead of "<=" since we know - // that no RecordId will ever reach RecordId::maxLong() so we don't need to - // check the equal side of things. This assumption doesn't hold for unique index - // KeyStrings. - std::string endPosKeyString = - createMaxRadixKeyFromObj(*_endPosKey, _prefix, _order); - - if (_forwardIt->first.compare(endPosKeyString) <= 0) - return true; - return false; - } - - return *_endPos == _workingCopy->end() || - _forwardIt->first.compare((*_endPos)->first) < 0; - } - return _forwardIt->first.compare(_KSForIdentEnd) <= 0; - } else { - // This is a reverse cursor - if (_reverseIt == _workingCopy->rend()) { - return false; - } - if (endPosSet()) { - if (_endPosIncl) { - if (*_endPosReverse == _workingCopy->rend()) - return true; - - std::string endPosKeyString = - createMinRadixKeyFromObj(*_endPosKey, _prefix, _order); - - if (_reverseIt->first.compare(endPosKeyString) >= 0) - return true; - return false; - } - - return *_endPosReverse == _workingCopy->rend() || - _reverseIt->first.compare((*_endPosReverse)->first) > 0; - } - return _reverseIt->first.compare(_KSForIdentStart) >= 0; - } -} - -void CursorUnique::initReverseDataIterators() { - _indexDataIt = _indexData.begin(); - _indexDataEnd = _indexData.end(); - for (size_t i = 1; i < (_indexData.size() - _reversePos); ++i) - ++_indexDataIt; -} - -boost::optional<IndexKeyEntry> CursorUnique::next(RequestedInfo parts) { - advanceSnapshotIfChanged(); - - if (!advanceNext()) { - return {}; - } - - if (_forward) { - return createIndexKeyEntryFromRadixKey( - _forwardIt->first, _indexDataIt->loc(), _indexDataIt->typeBits(), _order); - } - return createIndexKeyEntryFromRadixKey( - _reverseIt->first, _indexDataIt->loc(), _indexDataIt->typeBits(), _order); -} - -boost::optional<KeyStringEntry> CursorUnique::nextKeyString() { - advanceSnapshotIfChanged(); - - if (!advanceNext()) { - return {}; - } - - if (_forward) { - return createKeyStringEntryFromRadixKey( - _forwardIt->first, _indexDataIt->loc(), _indexDataIt->typeBits(), _order); - } - return createKeyStringEntryFromRadixKey( - _reverseIt->first, _indexDataIt->loc(), _indexDataIt->typeBits(), _order); -} - -boost::optional<KeyStringEntry> CursorUnique::finishSeekAfterProcessing() { - // We have seeked to an entry in the tree. Now unpack the data and initialize iterators to point - // to the first entry if this index contains duplicates - if (_forward) { - _indexData = UniqueIndexData(_forwardIt->second, _keyFormat); - _indexDataIt = _indexData.begin(); - _indexDataEnd = _indexData.end(); - return createKeyStringEntryFromRadixKey( - _forwardIt->first, _indexDataIt->loc(), _indexDataIt->typeBits(), _order); - } else { - _indexData = UniqueIndexData(_reverseIt->second, _keyFormat); - _reversePos = 0; - initReverseDataIterators(); - return createKeyStringEntryFromRadixKey( - _reverseIt->first, _indexDataIt->loc(), _indexDataIt->typeBits(), _order); - } -} - -void CursorUnique::saveForward() { - if (!_indexData.empty()) { - _saveLoc = _indexDataIt->loc(); - } -} - -void CursorUnique::saveReverse() { - if (!_indexData.empty()) { - _saveLoc = _indexDataIt->loc(); - } -} - -void CursorUnique::restoreForward() { - _lastMoveWasRestore = true; - if (_saveLoc != RecordId() && _forwardIt != _workingCopy->end() && - _forwardIt->first == _saveKey) { - _indexData = UniqueIndexData(_forwardIt->second, _keyFormat); - _indexDataIt = _indexData.lower_bound(_saveLoc); - _indexDataEnd = _indexData.end(); - if (_indexDataIt == _indexDataEnd) { - // We reached the end of the index data, so we need to go to the next item in the - // radix tree to be positioned on a valid item - ++_forwardIt; - if (checkCursorValid()) { - _indexData = UniqueIndexData(_forwardIt->second, _keyFormat); - _indexDataIt = _indexData.begin(); - _indexDataEnd = _indexData.end(); - } - } else { - // Unique indexes disregard difference in location and forces the cursor to advance - // to guarantee that we never return the same key twice - _lastMoveWasRestore = false; - } - } - if (!checkCursorValid()) { - _atEOF = true; - } -} - -void CursorUnique::restoreReverse() { - _lastMoveWasRestore = true; - if (_saveLoc != RecordId() && _reverseIt != _workingCopy->rend() && - _reverseIt->first == _saveKey) { - _indexData = UniqueIndexData(_reverseIt->second, _keyFormat); - _indexDataIt = _indexData.upper_bound(_saveLoc); - _indexDataEnd = _indexData.end(); - if (_indexDataIt == _indexDataEnd) { - ++_reverseIt; - if (checkCursorValid()) { - _indexData = UniqueIndexData(_reverseIt->second, _keyFormat); - _reversePos = 0; - initReverseDataIterators(); - } - } else { - _reversePos = _indexData.size() - std::distance(_indexData.begin(), _indexDataIt) - 1; - _lastMoveWasRestore = false; - } - } - if (!checkCursorValid()) { - _atEOF = true; - } -} - -/* - * This is the cursor class required by the sorted data interface for standard (non-unique) indexes. - */ -class CursorStandard final : public CursorBase<CursorStandard> { -public: - using CursorBase::CursorBase; - - virtual boost::optional<IndexKeyEntry> next(RequestedInfo parts = kKeyAndLoc) override; - virtual boost::optional<KeyStringEntry> nextKeyString() override; - -protected: - // Implementations of CursorBase interface - friend class CursorBase; - - bool advanceNextInternal() { - return false; - } - void finishAdvanceNext() {} - boost::optional<KeyStringEntry> finishSeekAfterProcessing(); - void saveForward() {} - void saveReverse() {} - void restoreForward(); - void restoreReverse(); - -private: - // This is a helper function to check if the cursor is valid or not. - bool checkCursorValid(); -}; - -// This function checks whether or not a cursor is valid. In particular, it checks 1) whether the -// cursor is at end() or rend(), 2) whether the cursor is on the wrong side of the end position -// if it was set, and 3) whether the cursor is still in the ident. -bool CursorStandard::checkCursorValid() { - if (_forward) { - invariant(_workingCopy); - if (_forwardIt == _workingCopy->end()) { - return false; - } - if (endPosSet()) { - return *_endPos == _workingCopy->end() || - _forwardIt->first.compare((*_endPos)->first) < 0; - } - return _forwardIt->first.compare(_KSForIdentEnd) <= 0; - } else { - // This is a reverse cursor - if (_reverseIt == _workingCopy->rend()) { - return false; - } - if (endPosSet()) { - return *_endPosReverse == _workingCopy->rend() || - _reverseIt->first.compare((*_endPosReverse)->first) > 0; - } - return _reverseIt->first.compare(_KSForIdentStart) >= 0; - } -} - - -boost::optional<IndexKeyEntry> CursorStandard::next(RequestedInfo parts) { - advanceSnapshotIfChanged(); - - if (!advanceNext()) { - return {}; - } - - if (_forward) { - return createIndexKeyEntryFromRadixKey( - _forwardIt->first, _forwardIt->second, _order, _keyFormat); - } - return createIndexKeyEntryFromRadixKey( - _reverseIt->first, _reverseIt->second, _order, _keyFormat); -} - -boost::optional<KeyStringEntry> CursorStandard::nextKeyString() { - advanceSnapshotIfChanged(); - - if (!advanceNext()) { - return {}; - } - - if (_forward) { - return createKeyStringEntryFromRadixKey( - _forwardIt->first, _forwardIt->second, _order, _keyFormat); - } - return createKeyStringEntryFromRadixKey( - _reverseIt->first, _reverseIt->second, _order, _keyFormat); -} - -boost::optional<KeyStringEntry> CursorStandard::finishSeekAfterProcessing() { - // We have seeked to an entry in the tree. - if (_forward) { - return createKeyStringEntryFromRadixKey( - _forwardIt->first, _forwardIt->second, _order, _keyFormat); - } else { - return createKeyStringEntryFromRadixKey( - _reverseIt->first, _reverseIt->second, _order, _keyFormat); - } -} - -void CursorStandard::restoreForward() { - if (!checkCursorValid()) { - _atEOF = true; - _lastMoveWasRestore = true; - return; - } - _lastMoveWasRestore = (_forwardIt->first.compare(_saveKey) != 0); -} -void CursorStandard::restoreReverse() { - if (!checkCursorValid()) { - _atEOF = true; - _lastMoveWasRestore = true; - return; - } - _lastMoveWasRestore = (_reverseIt->first.compare(_saveKey) != 0); -} - -RecordId decodeRecordId(const KeyString::Value& keyString, KeyFormat keyFormat) { - RecordId loc; - if (keyFormat == KeyFormat::Long) { - loc = KeyString::decodeRecordIdLongAtEnd(keyString.getBuffer(), keyString.getSize()); - } else { - loc = KeyString::decodeRecordIdStrAtEnd(keyString.getBuffer(), keyString.getSize()); - } - invariant(loc.isValid(), loc.toString()); - return loc; -} - -} // namespace - -SortedDataBuilderBase::SortedDataBuilderBase(OperationContext* opCtx, - bool dupsAllowed, - Ordering order, - KeyFormat rsKeyFormat, - const std::string& prefix, - const std::string& identEnd, - const IndexDescriptor* desc, - const std::string& indexName, - const BSONObj& keyPattern, - const BSONObj& collation) - : _opCtx(opCtx), - _dupsAllowed(dupsAllowed), - _order(order), - _rsKeyFormat(rsKeyFormat), - _prefix(prefix), - _identEnd(identEnd), - _desc(desc), - _indexName(indexName), - _keyPattern(keyPattern), - _collation(collation) {} - -Status SortedDataBuilderUnique::addKey(const KeyString::Value& keyString) { - RecordId loc = decodeRecordId(keyString, _rsKeyFormat); - StringStore* workingCopy(RecoveryUnit::get(_opCtx)->getHead()); - - std::string key = createRadixKeyWithoutLocFromKS(keyString, _prefix, _rsKeyFormat); - auto it = workingCopy->find(key); - if (it != workingCopy->end()) { - if (!_dupsAllowed) { - // There was an attempt to create an index entry with a different RecordId while dups - // were not allowed. - auto obj = KeyString::toBson(keyString, _order); - return buildDupKeyErrorStatus(_opCtx, keyString, _order, _desc); - } - - UniqueIndexData data(it->second, _rsKeyFormat); - // Bulk builder add keys in ascending order so we should insert at the end - auto added = data.add(loc, keyString.getTypeBits()); - if (!added) { - // Already indexed - return Status::OK(); - } - - workingCopy->update({std::move(key), *added}); - } else { - UniqueIndexData data; - workingCopy->insert({std::move(key), *data.add(loc, keyString.getTypeBits())}); - } - - RecoveryUnit::get(_opCtx)->makeDirty(); - return Status::OK(); -} - -// We append \1 to all idents we get, and therefore the KeyString with ident + \0 will only be -// before elements in this ident, and the KeyString with ident + \2 will only be after elements in -// this ident. -SortedDataInterfaceBase::SortedDataInterfaceBase(OperationContext* opCtx, - StringData ident, - KeyFormat rsKeyFormat, - const IndexDescriptor* desc) - : ::mongo::SortedDataInterface(ident, - KeyString::Version::kLatestVersion, - Ordering::make(desc->keyPattern()), - rsKeyFormat), - // All entries in this ident will have a prefix of ident + \1. - _prefix(ident.toString().append(1, '\1')), - // Therefore, the string ident + \2 will be greater than all elements in this ident. - _identEnd(ident.toString().append(1, '\2')), - _desc(desc), - _indexName(desc->indexName()), - _keyPattern(desc->keyPattern()), - _collation(desc->collation()), - _isPartial(desc->isPartial()) {} - -SortedDataInterfaceBase::SortedDataInterfaceBase(const Ordering& ordering, StringData ident) - : ::mongo::SortedDataInterface( - ident, KeyString::Version::kLatestVersion, ordering, KeyFormat::Long), - _prefix(ident.toString().append(1, '\1')), - _identEnd(ident.toString().append(1, '\2')), - _desc(nullptr), - _isPartial(false) {} - -std::unique_ptr<SortedDataBuilderInterface> SortedDataInterfaceUnique::makeBulkBuilder( - OperationContext* opCtx, bool dupsAllowed) { - return std::make_unique<SortedDataBuilderUnique>(opCtx, - dupsAllowed, - _ordering, - _rsKeyFormat, - _prefix, - _identEnd, - _desc, - _indexName, - _keyPattern, - _collation); -} - -// We append \1 to all idents we get, and therefore the KeyString with ident + \0 will only be -// before elements in this ident, and the KeyString with ident + \2 will only be after elements in -// this ident. -SortedDataInterfaceUnique::SortedDataInterfaceUnique(OperationContext* opCtx, - StringData ident, - KeyFormat rsKeyFormat, - const IndexDescriptor* desc) - : SortedDataInterfaceBase(opCtx, ident, rsKeyFormat, desc) { - // This is the string representation of the KeyString before elements in this ident, which is - // ident + \0. This is before all elements in this ident. - _KSForIdentStart = - createRadixKeyWithoutLocFromObj(BSONObj(), ident.toString().append(1, '\0'), _ordering); - // Similarly, this is the string representation of the KeyString for something greater than - // all other elements in this ident. - _KSForIdentEnd = createRadixKeyWithoutLocFromObj(BSONObj(), _identEnd, _ordering); -} - -SortedDataInterfaceUnique::SortedDataInterfaceUnique(const Ordering& ordering, StringData ident) - : SortedDataInterfaceBase(ordering, ident) { - _KSForIdentStart = - createRadixKeyWithoutLocFromObj(BSONObj(), ident.toString().append(1, '\0'), _ordering); - _KSForIdentEnd = createRadixKeyWithoutLocFromObj(BSONObj(), _identEnd, _ordering); -} - -Status SortedDataInterfaceUnique::insert(OperationContext* opCtx, - const KeyString::Value& keyString, - bool dupsAllowed) { - StringStore* workingCopy(RecoveryUnit::get(opCtx)->getHead()); - RecordId loc = decodeRecordId(keyString, rsKeyFormat()); - - std::string key = createRadixKeyWithoutLocFromKS(keyString, _prefix, _rsKeyFormat); - auto it = workingCopy->find(key); - if (it != workingCopy->end()) { - if (!dupsAllowed) { - UniqueIndexData data{it->second, _rsKeyFormat}; - auto dataIt = data.lower_bound(loc); - if (dataIt != data.end() && dataIt->loc() == loc) { - // This exact key+RecordId was already inserted. - return Status::OK(); - } - - // There was an attempt to create an index entry with a different RecordId while - // dups were not allowed. - return buildDupKeyErrorStatus(opCtx, keyString, _ordering, _desc); - } - - UniqueIndexData data(it->second, _rsKeyFormat); - auto added = data.add(loc, keyString.getTypeBits()); - if (!added) { - // Already indexed - return Status::OK(); - } - - workingCopy->update({std::move(key), *added}); - } else { - UniqueIndexData data; - workingCopy->insert({std::move(key), *data.add(loc, keyString.getTypeBits())}); - } - RecoveryUnit::get(opCtx)->makeDirty(); - return Status::OK(); -} - -void SortedDataInterfaceUnique::unindex(OperationContext* opCtx, - const KeyString::Value& keyString, - bool dupsAllowed) { - StringStore* workingCopy(RecoveryUnit::get(opCtx)->getHead()); - RecordId loc = decodeRecordId(keyString, rsKeyFormat()); - - auto key = createRadixKeyWithoutLocFromKS(keyString, _prefix, _rsKeyFormat); - auto it = workingCopy->find(key); - if (it != workingCopy->end()) { - UniqueIndexData data(it->second, _rsKeyFormat); - auto removed = data.remove(loc); - if (!removed) - return; // loc not found, nothing to unindex - - if (UniqueIndexData(*removed, _rsKeyFormat).empty()) { - workingCopy->erase(key); - } else { - workingCopy->update({std::move(key), *removed}); - } - RecoveryUnit::get(opCtx)->makeDirty(); - } -} - -// This function is, as of now, not in the interface, but there exists a server ticket to add -// truncate to the list of commands able to be used. -Status SortedDataInterfaceBase::truncate(mongo::RecoveryUnit* ru) { - auto bRu = checked_cast<ephemeral_for_test::RecoveryUnit*>(ru); - StringStore* workingCopy(bRu->getHead()); - std::vector<std::string> toDelete; - auto end = workingCopy->upper_bound(_KSForIdentEnd); - for (auto it = workingCopy->lower_bound(_KSForIdentStart); it != end; ++it) { - toDelete.push_back(it->first); - } - if (!toDelete.empty()) { - for (const auto& key : toDelete) - workingCopy->erase(key); - bRu->makeDirty(); - } - - return Status::OK(); -} - -Status SortedDataInterfaceUnique::dupKeyCheck(OperationContext* opCtx, - const KeyString::Value& key) { - StringStore* workingCopy(RecoveryUnit::get(opCtx)->getHead()); - - std::string radixKey = createRadixKeyWithoutLocFromKSWithoutRecordId(key, _prefix); - auto it = workingCopy->find(radixKey); - if (it == workingCopy->end()) - return Status::OK(); - - UniqueIndexData data(it->second, _rsKeyFormat); - if (data.size() > 1) { - return buildDupKeyErrorStatus(opCtx, key, _ordering, _desc); - } - - return Status::OK(); -} - -void SortedDataInterfaceUnique::fullValidate(OperationContext* opCtx, - long long* numKeysOut, - IndexValidateResults* fullResults) const { - StringStore* workingCopy(RecoveryUnit::get(opCtx)->getHead()); - long long numKeys = 0; - auto it = workingCopy->lower_bound(_KSForIdentStart); - while (it != workingCopy->end() && it->first.compare(_KSForIdentEnd) < 0) { - numKeys += UniqueIndexData(it->second, _rsKeyFormat).size(); - ++it; - } - *numKeysOut = numKeys; -} - -bool SortedDataInterfaceBase::appendCustomStats(OperationContext* opCtx, - BSONObjBuilder* output, - double scale) const { - return false; -} - -long long SortedDataInterfaceBase::getSpaceUsedBytes(OperationContext* opCtx) const { - StringStore* workingCopy(RecoveryUnit::get(opCtx)->getHead()); - size_t totalSize = 0; - StringStore::const_iterator it = workingCopy->lower_bound(_KSForIdentStart); - StringStore::const_iterator end = workingCopy->upper_bound(_KSForIdentEnd); - int64_t numElements = workingCopy->distance(it, end); - for (int i = 0; i < numElements; i++) { - totalSize += it->first.length(); - ++it; - } - return (long long)totalSize; -} - -bool SortedDataInterfaceBase::isEmpty(OperationContext* opCtx) { - StringStore* workingCopy(RecoveryUnit::get(opCtx)->getHead()); - return workingCopy->distance(workingCopy->lower_bound(_KSForIdentStart), - workingCopy->upper_bound(_KSForIdentEnd)) == 0; -} - -boost::optional<RecordId> SortedDataInterfaceBase::findLoc( - OperationContext* opCtx, const KeyString::Value& keyStringValue) const { - dassert(KeyString::decodeDiscriminator(keyStringValue.getBuffer(), - keyStringValue.getSize(), - _ordering, - keyStringValue.getTypeBits()) == - KeyString::Discriminator::kInclusive); - auto cursor = newCursor(opCtx); - auto ksEntry = cursor->seekForKeyString(keyStringValue); - if (!ksEntry) { - return boost::none; - } - - auto sizeWithoutRecordId = KeyFormat::Long == _rsKeyFormat - ? KeyString::sizeWithoutRecordIdLongAtEnd(ksEntry->keyString.getBuffer(), - ksEntry->keyString.getSize()) - : KeyString::sizeWithoutRecordIdStrAtEnd(ksEntry->keyString.getBuffer(), - ksEntry->keyString.getSize()); - if (KeyString::compare(ksEntry->keyString.getBuffer(), - keyStringValue.getBuffer(), - sizeWithoutRecordId, - keyStringValue.getSize()) == 0) { - return ksEntry->loc; - } - return boost::none; -} - -std::unique_ptr<mongo::SortedDataInterface::Cursor> SortedDataInterfaceUnique::newCursor( - OperationContext* opCtx, bool isForward) const { - return std::make_unique<CursorUnique>(opCtx, - isForward, - _prefix, - _identEnd, - RecoveryUnit::get(opCtx)->getHeadShared(), - _ordering, - _rsKeyFormat, - _KSForIdentStart, - _KSForIdentEnd); -} - -Status SortedDataInterfaceBase::initAsEmpty(OperationContext* opCtx) { - return Status::OK(); -} - -Status SortedDataBuilderStandard::addKey(const KeyString::Value& keyString) { - StringStore* workingCopy(RecoveryUnit::get(_opCtx)->getHead()); - RecordId loc = decodeRecordId(keyString, _rsKeyFormat); - - std::string key = createRadixKeyWithLocFromKS(keyString, loc, _prefix); - bool inserted = - workingCopy->insert({std::move(key), IndexDataEntry::create(loc, keyString.getTypeBits())}) - .second; - if (inserted) - RecoveryUnit::get(_opCtx)->makeDirty(); - return Status::OK(); -} - -std::unique_ptr<SortedDataBuilderInterface> SortedDataInterfaceStandard::makeBulkBuilder( - OperationContext* opCtx, bool dupsAllowed) { - return std::make_unique<SortedDataBuilderStandard>(opCtx, - dupsAllowed, - _ordering, - _rsKeyFormat, - _prefix, - _identEnd, - _desc, - _indexName, - _keyPattern, - _collation); -} - -// We append \1 to all idents we get, and therefore the KeyString with ident + \0 will only be -// before elements in this ident, and the KeyString with ident + \2 will only be after elements in -// this ident. -SortedDataInterfaceStandard::SortedDataInterfaceStandard(OperationContext* opCtx, - StringData ident, - KeyFormat rsKeyFormat, - const IndexDescriptor* desc) - : SortedDataInterfaceBase(opCtx, ident, rsKeyFormat, desc) { - // This is the string representation of the KeyString before elements in this ident, which is - // ident + \0. This is before all elements in this ident. - _KSForIdentStart = - createMinRadixKeyFromObj(BSONObj(), ident.toString().append(1, '\0'), _ordering); - // Similarly, this is the string representation of the KeyString for something greater than - // all other elements in this ident. - _KSForIdentEnd = createMinRadixKeyFromObj(BSONObj(), _identEnd, _ordering); -} - -SortedDataInterfaceStandard::SortedDataInterfaceStandard(const Ordering& ordering, StringData ident) - : SortedDataInterfaceBase(ordering, ident) { - _KSForIdentStart = - createMinRadixKeyFromObj(BSONObj(), ident.toString().append(1, '\0'), _ordering); - _KSForIdentEnd = createMinRadixKeyFromObj(BSONObj(), _identEnd, _ordering); -} - -Status SortedDataInterfaceStandard::insert(OperationContext* opCtx, - const KeyString::Value& keyString, - bool dupsAllowed) { - StringStore* workingCopy(RecoveryUnit::get(opCtx)->getHead()); - RecordId loc = decodeRecordId(keyString, rsKeyFormat()); - - if (!dupsAllowed) { - std::string prefix_key = createRadixKeyWithoutLocFromKS(keyString, _prefix, _rsKeyFormat); - auto it = workingCopy->lower_bound(prefix_key); - if (it != workingCopy->end()) { - auto keyStringFound = - createKeyStringEntryFromRadixKey(it->first, it->second, _ordering, _rsKeyFormat) - .get() - .keyString; - bool hasSameKey = loc.isLong() - ? keyString.compareWithoutRecordIdLong(keyStringFound) == 0 - : keyString.compareWithoutRecordIdStr(keyStringFound) == 0; - if (hasSameKey) { - return buildDupKeyErrorStatus(opCtx, keyString, _ordering, _desc); - } - } - } - - std::string key = createRadixKeyWithLocFromKS(keyString, loc, _prefix); - bool inserted = - workingCopy->insert({std::move(key), IndexDataEntry::create(loc, keyString.getTypeBits())}) - .second; - if (inserted) - RecoveryUnit::get(opCtx)->makeDirty(); - return Status::OK(); -} - -void SortedDataInterfaceStandard::unindex(OperationContext* opCtx, - const KeyString::Value& keyString, - bool dupsAllowed) { - StringStore* workingCopy(RecoveryUnit::get(opCtx)->getHead()); - RecordId loc = decodeRecordId(keyString, rsKeyFormat()); - - auto key = createRadixKeyWithLocFromKS(keyString, loc, _prefix); - if (workingCopy->erase(key)) - RecoveryUnit::get(opCtx)->makeDirty(); -} - -Status SortedDataInterfaceStandard::dupKeyCheck(OperationContext* opCtx, - const KeyString::Value& key) { - invariant(false); - return Status::OK(); -} - -void SortedDataInterfaceStandard::fullValidate(OperationContext* opCtx, - long long* numKeysOut, - IndexValidateResults* fullResults) const { - StringStore* workingCopy(RecoveryUnit::get(opCtx)->getHead()); - long long numKeys = 0; - auto it = workingCopy->lower_bound(_KSForIdentStart); - while (it != workingCopy->end() && it->first.compare(_KSForIdentEnd) < 0) { - ++numKeys; - ++it; - } - *numKeysOut = numKeys; -} - -std::unique_ptr<mongo::SortedDataInterface::Cursor> SortedDataInterfaceStandard::newCursor( - OperationContext* opCtx, bool isForward) const { - return std::make_unique<CursorStandard>(opCtx, - isForward, - _prefix, - _identEnd, - RecoveryUnit::get(opCtx)->getHeadShared(), - _ordering, - _rsKeyFormat, - _KSForIdentStart, - _KSForIdentEnd); -} - -} // namespace ephemeral_for_test -} // namespace mongo diff --git a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_sorted_impl.h b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_sorted_impl.h deleted file mode 100644 index 3f93c83ece0..00000000000 --- a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_sorted_impl.h +++ /dev/null @@ -1,171 +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 "mongo/db/index/index_descriptor_fwd.h" -#include "mongo/db/operation_context.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store.h" -#include "mongo/db/storage/key_string.h" -#include "mongo/db/storage/sorted_data_interface.h" - -namespace mongo { -namespace ephemeral_for_test { - -class SortedDataBuilderBase : public SortedDataBuilderInterface { -public: - SortedDataBuilderBase(OperationContext* opCtx, - bool dupsAllowed, - Ordering order, - KeyFormat rsKeyFormat, - const std::string& prefix, - const std::string& identEnd, - const IndexDescriptor* desc, - const std::string& indexName, - const BSONObj& keyPattern, - const BSONObj& collation); - -protected: - OperationContext* _opCtx; - bool _dupsAllowed; - // Order of the keys. - Ordering _order; - // RecordId format of the related record store - KeyFormat _rsKeyFormat; - // Prefix and identEnd for the ident. - std::string _prefix; - std::string _identEnd; - // Index metadata. - const IndexDescriptor* _desc; - const std::string _indexName; - const BSONObj _keyPattern; - const BSONObj _collation; -}; - -class SortedDataBuilderUnique : public SortedDataBuilderBase { -public: - using SortedDataBuilderBase::SortedDataBuilderBase; - Status addKey(const KeyString::Value& keyString) override; -}; - -class SortedDataInterfaceBase : public SortedDataInterface { -public: - // Truncate is not required at the time of writing but will be when the truncate command is - // created - Status truncate(RecoveryUnit* ru); - SortedDataInterfaceBase(OperationContext* opCtx, - StringData ident, - KeyFormat rsKeyFormat, - const IndexDescriptor* desc); - SortedDataInterfaceBase(const Ordering& ordering, StringData ident); - bool appendCustomStats(OperationContext* opCtx, - BSONObjBuilder* output, - double scale) const override; - long long getSpaceUsedBytes(OperationContext* opCtx) const override; - long long getFreeStorageBytes(OperationContext* opCtx) const override { - return 0; - } - bool isEmpty(OperationContext* opCtx) override; - Status initAsEmpty(OperationContext* opCtx) override; - boost::optional<RecordId> findLoc(OperationContext* opCtx, - const KeyString::Value& keyString) const override; - void insertWithRecordIdInValue_forTest(OperationContext* opCtx, - const KeyString::Value& keyString, - RecordId rid) override { - MONGO_UNREACHABLE; - } - -protected: - // These two are the same as before. - std::string _prefix; - std::string _identEnd; - // Index metadata. - const IndexDescriptor* _desc; - const std::string _indexName; - const BSONObj _keyPattern; - const BSONObj _collation; - // These are the keystring representations of the _prefix and the _identEnd. - std::string _KSForIdentStart; - std::string _KSForIdentEnd; - // Whether or not the index is partial - bool _isPartial; -}; - -class SortedDataInterfaceUnique : public SortedDataInterfaceBase { -public: - SortedDataInterfaceUnique(OperationContext* opCtx, - StringData ident, - KeyFormat rsKeyFormat, - const IndexDescriptor* desc); - SortedDataInterfaceUnique(const Ordering& ordering, StringData ident); - std::unique_ptr<SortedDataBuilderInterface> makeBulkBuilder(OperationContext* opCtx, - bool dupsAllowed) override; - Status insert(OperationContext* opCtx, - const KeyString::Value& keyString, - bool dupsAllowed) override; - void unindex(OperationContext* opCtx, - const KeyString::Value& keyString, - bool dupsAllowed) override; - Status dupKeyCheck(OperationContext* opCtx, const KeyString::Value& keyString) override; - void fullValidate(OperationContext* opCtx, - long long* numKeysOut, - IndexValidateResults* fullResults) const override; - std::unique_ptr<mongo::SortedDataInterface::Cursor> newCursor( - OperationContext* opCtx, bool isForward = true) const override; -}; - -class SortedDataBuilderStandard : public SortedDataBuilderBase { -public: - using SortedDataBuilderBase::SortedDataBuilderBase; - Status addKey(const KeyString::Value& keyString) override; -}; - -class SortedDataInterfaceStandard : public SortedDataInterfaceBase { -public: - SortedDataInterfaceStandard(OperationContext* opCtx, - StringData ident, - KeyFormat rsKeyFormat, - const IndexDescriptor* desc); - SortedDataInterfaceStandard(const Ordering& ordering, StringData ident); - std::unique_ptr<SortedDataBuilderInterface> makeBulkBuilder(OperationContext* opCtx, - bool dupsAllowed) override; - Status insert(OperationContext* opCtx, - const KeyString::Value& keyString, - bool dupsAllowed) override; - void unindex(OperationContext* opCtx, - const KeyString::Value& keyString, - bool dupsAllowed) override; - Status dupKeyCheck(OperationContext* opCtx, const KeyString::Value& keyString) override; - void fullValidate(OperationContext* opCtx, - long long* numKeysOut, - IndexValidateResults* fullResults) const override; - std::unique_ptr<mongo::SortedDataInterface::Cursor> newCursor( - OperationContext* opCtx, bool isForward = true) const override; -}; -} // namespace ephemeral_for_test -} // namespace mongo diff --git a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_sorted_impl_test.cpp b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_sorted_impl_test.cpp deleted file mode 100644 index a36311aa472..00000000000 --- a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_sorted_impl_test.cpp +++ /dev/null @@ -1,113 +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. - */ - -#include "mongo/platform/basic.h" - -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_sorted_impl.h" - -#include <memory> - -#include "mongo/base/init.h" -#include "mongo/db/catalog/collection_mock.h" -#include "mongo/db/index/index_descriptor.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_kv_engine.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_recovery_unit.h" -#include "mongo/db/storage/sorted_data_interface_test_harness.h" -#include "mongo/unittest/unittest.h" - -namespace mongo { -namespace ephemeral_for_test { -namespace { - -class SortedDataInterfaceTestHarnessHelper final - : public virtual mongo::SortedDataInterfaceHarnessHelper { -public: - SortedDataInterfaceTestHarnessHelper() : _order(Ordering::make(BSONObj())) {} - - std::unique_ptr<mongo::SortedDataInterface> newIdIndexSortedDataInterface() final { - std::string ns = "test.ephemeral_for_test"; - OperationContextNoop opCtx(newRecoveryUnit().release()); - - BSONObj spec = BSON("key" << BSON("_id" << 1) << "name" - << "_id_" - << "v" << static_cast<int>(IndexDescriptor::kLatestIndexVersion) - << "unique" << true); - - auto collection = std::make_unique<CollectionMock>(NamespaceString(ns)); - IndexDescriptor desc("", spec); - invariant(desc.isIdIndex()); - - return _kvEngine.getSortedDataInterface( - &opCtx, NamespaceString(ns), CollectionOptions(), "ident"_sd, &desc); - } - - std::unique_ptr<mongo::SortedDataInterface> newSortedDataInterface(bool unique, - bool partial, - KeyFormat keyFormat) final { - std::string ns = "test.ephemeral_for_test"; - OperationContextNoop opCtx(newRecoveryUnit().release()); - - BSONObj spec = BSON("key" << BSON("a" << 1) << "name" - << "testIndex" - << "v" << static_cast<int>(IndexDescriptor::kLatestIndexVersion) - << "unique" << unique); - if (partial) { - auto partialBSON = - BSON(IndexDescriptor::kPartialFilterExprFieldName.toString() << BSON("" - << "")); - spec = spec.addField(partialBSON.firstElement()); - } - - auto collection = std::make_unique<CollectionMock>(NamespaceString(ns)); - _descs.emplace_back("", spec); - return _kvEngine.getSortedDataInterface( - &opCtx, NamespaceString(ns), keyFormat, "ident"_sd, &_descs.back()); - } - - std::unique_ptr<mongo::RecoveryUnit> newRecoveryUnit() final { - return std::make_unique<RecoveryUnit>(&_kvEngine); - } - -private: - KVEngine _kvEngine{}; - Ordering _order; - std::list<IndexDescriptor> _descs; -}; - -std::unique_ptr<mongo::SortedDataInterfaceHarnessHelper> makeSortedDataInterfaceHarnessHelper() { - return std::make_unique<SortedDataInterfaceTestHarnessHelper>(); -} - -MONGO_INITIALIZER(RegisterSortedDataInterfaceHarnessFactory)(InitializerContext* const) { - mongo::registerSortedDataInterfaceHarnessHelperFactory(makeSortedDataInterfaceHarnessHelper); -} -} // namespace -} // namespace ephemeral_for_test -} // namespace mongo diff --git a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_visibility_manager.cpp b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_visibility_manager.cpp deleted file mode 100644 index 745e87ddf63..00000000000 --- a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_visibility_manager.cpp +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Copyright (C) 2019-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. - */ - -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kStorage - -#include "mongo/platform/basic.h" - -#include <algorithm> - -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.h" -#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_visibility_manager.h" -#include "mongo/db/storage/recovery_unit.h" - -namespace mongo { -namespace ephemeral_for_test { - -/** - * Used by the visibility manager to register changes when the RecoveryUnit either commits or - * rolls back changes. - */ -class VisibilityManagerChange : public RecoveryUnit::Change { -public: - VisibilityManagerChange(VisibilityManager* visibilityManager, RecordStore* rs, RecordId rid) - : _visibilityManager(visibilityManager), _rs(rs), _rid(rid) {} - ~VisibilityManagerChange() = default; - - virtual void commit(boost::optional<Timestamp>) { - _visibilityManager->dealtWithRecord(_rid); - } - - virtual void rollback() { - _visibilityManager->dealtWithRecord(_rid); - if (!_rs) - return; - - stdx::lock_guard<Latch> lk(_rs->_cappedCallbackMutex); - if (_rs->_cappedCallback) - _rs->_cappedCallback->notifyCappedWaitersIfNeeded(); - } - -private: - VisibilityManager* _visibilityManager; - const RecordStore* const _rs; - const RecordId _rid; -}; - -void VisibilityManager::dealtWithRecord(RecordId rid) { - stdx::lock_guard<Latch> lock(_stateLock); - _uncommittedRecords.erase(rid); - _opsBecameVisibleCV.notify_all(); -} - -void VisibilityManager::reserveRecord(RecoveryUnit* recoveryUnit, RecordId rid) { - stdx::lock_guard<Latch> lock(_stateLock); - - // Just register one change even if reserveRecord is called multiple times - auto it = _uncommittedRecords.find(rid); - if (it == _uncommittedRecords.end()) { - _uncommittedRecords.insert(it, rid); - recoveryUnit->registerChange(std::make_unique<VisibilityManagerChange>(this, nullptr, rid)); - } -} - -void VisibilityManager::addUncommittedRecord(OperationContext* opCtx, - RecordStore* rs, - RecordId rid) { - stdx::lock_guard<Latch> lock(_stateLock); - _uncommittedRecords.insert(rid); - opCtx->recoveryUnit()->registerChange(std::make_unique<VisibilityManagerChange>(this, rs, rid)); - - if (rid > _highestSeen) - _highestSeen = rid; -} - -RecordId VisibilityManager::getAllCommittedRecord() { - stdx::lock_guard<Latch> lock(_stateLock); - return _uncommittedRecords.empty() ? _highestSeen - : RecordId(_uncommittedRecords.begin()->getLong() - 1); -} - -bool VisibilityManager::isFirstHidden(RecordId rid) { - stdx::lock_guard<Latch> lock(_stateLock); - if (_uncommittedRecords.empty()) - return false; - return *_uncommittedRecords.begin() == rid; -} - -void VisibilityManager::waitForAllEarlierOplogWritesToBeVisible(OperationContext* opCtx) { - invariant(opCtx->lockState()->isNoop() || !opCtx->lockState()->inAWriteUnitOfWork()); - - stdx::unique_lock<Latch> lock(_stateLock); - const RecordId waitFor = _highestSeen; - opCtx->waitForConditionOrInterrupt(_opsBecameVisibleCV, lock, [&] { - return _uncommittedRecords.empty() || *_uncommittedRecords.begin() > waitFor; - }); -} - -} // namespace ephemeral_for_test -} // namespace mongo diff --git a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_visibility_manager.h b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_visibility_manager.h deleted file mode 100644 index 3380e13adaa..00000000000 --- a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_visibility_manager.h +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Copyright (C) 2019-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 "mongo/db/operation_context.h" -#include "mongo/db/record_id.h" -#include "mongo/stdx/condition_variable.h" -#include "mongo/util/concurrency/mutex.h" - -namespace mongo { -namespace ephemeral_for_test { - -class RecordStore; - -/** - * Manages oplog visibility by keeping track of uncommitted RecordIds and hiding Records from - * cursors while a given Record's RecordId is greater than the uncommitted RecordIds. - */ -class VisibilityManager { -public: - /** - * Removes the RecordId from the uncommitted records and notifies other threads that a chunk of - * the oplog became visible. - */ - void dealtWithRecord(RecordId rid); - - /** - * Reserves a RecordId to be tracked before it is added. Used to ensure we don't skip over oplog - * holes when inserting out-of-order - */ - void reserveRecord(RecoveryUnit* recoveryUnit, RecordId rid); - - /** - * Adds a RecordId to be tracked while its Record is uncommitted. Upon commit or rollback of - * the record, the appropriate actions are taken to change the visibility of the oplog. - */ - void addUncommittedRecord(OperationContext* opCtx, RecordStore* rs, RecordId rid); - - /** - * Returns the highest seen RecordId such that it and all smaller RecordIds are committed or - * rolled back. - */ - RecordId getAllCommittedRecord(); - - /** - * Returns true if the given RecordId is the earliest uncommitted Record being tracked by the - * visibility manager, otherwise it returns false. - */ - bool isFirstHidden(RecordId rid); - - /** - * Uses a condition variable to have all threads wait until all earlier oplog writes are visible - * based on the RecordId they're waiting for to become visible. - */ - void waitForAllEarlierOplogWritesToBeVisible(OperationContext* opCtx); - -private: - mutable Mutex _stateLock = - MONGO_MAKE_LATCH("VisibilityManager::_stateLock"); // Protects the values below. - RecordId _highestSeen = RecordId(); - - // Used to wait for all earlier oplog writes to be visible. - mutable stdx::condition_variable _opsBecameVisibleCV; - std::set<RecordId> _uncommittedRecords; // RecordIds that have yet to be committed/rolled back. -}; - -} // namespace ephemeral_for_test -} // namespace mongo |