summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/README.md131
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/SConscript77
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_init.cpp100
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_kv_engine.cpp293
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_kv_engine.h241
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_kv_engine_test.cpp497
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store.h2507
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store_concurrent_test.cpp412
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_radix_store_test.cpp3066
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.cpp671
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.h249
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store_test.cpp107
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_recovery_unit.cpp219
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_recovery_unit.h147
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_recovery_unit_test.cpp124
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_server_status.cpp71
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_server_status.h54
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_sorted_impl.cpp1662
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_sorted_impl.h171
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_sorted_impl_test.cpp113
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_visibility_manager.cpp125
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_visibility_manager.h95
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