1
0
Fork 0
arangodb/tests/Mocks/StorageEngineMock.cpp

1494 lines
48 KiB
C++

//////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2017 EMC Corporation
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is EMC Corporation
///
/// @author Andrey Abramov
/// @author Vasiliy Nabatchikov
////////////////////////////////////////////////////////////////////////////////
#include "StorageEngineMock.h"
#include "Aql/AstNode.h"
#include "Basics/LocalTaskQueue.h"
#include "Basics/Result.h"
#include "Basics/StaticStrings.h"
#include "Basics/VelocyPackHelper.h"
#include "Basics/asio_ns.h"
#include "Cluster/ClusterInfo.h"
#include "IResearch/IResearchCommon.h"
#include "IResearch/IResearchLinkCoordinator.h"
#include "IResearch/IResearchMMFilesLink.h"
#include "IResearch/VelocyPackHelper.h"
#include "Indexes/IndexIterator.h"
#include "Indexes/SimpleAttributeEqualityMatcher.h"
#include "RestServer/FlushFeature.h"
#include "StorageEngine/EngineSelectorFeature.h"
#include "Transaction/Helpers.h"
#include "Transaction/Manager.h"
#include "Transaction/Methods.h"
#include "Transaction/StandaloneContext.h"
#include "Utils/OperationOptions.h"
#include "Utils/SingleCollectionTransaction.h"
#include "VocBase/LogicalCollection.h"
#include "VocBase/LogicalView.h"
#include "VocBase/ManagedDocumentResult.h"
#include "VocBase/ticks.h"
#include <velocypack/Iterator.h>
#include <velocypack/velocypack-aliases.h>
#include <boost/asio/io_context.hpp>
namespace {
/// @brief hard-coded vector of the index attributes
/// note that the attribute names must be hard-coded here to avoid an init-order
/// fiasco with StaticStrings::FromString etc.
std::vector<std::vector<arangodb::basics::AttributeName>> const IndexAttributes{
{arangodb::basics::AttributeName("_from", false)},
{arangodb::basics::AttributeName("_to", false)}};
/// @brief add a single value node to the iterator's keys
void handleValNode(VPackBuilder* keys, arangodb::aql::AstNode const* valNode) {
if (!valNode->isStringValue() || valNode->getStringLength() == 0) {
return;
}
keys->openObject();
keys->add(arangodb::StaticStrings::IndexEq,
VPackValuePair(valNode->getStringValue(),
valNode->getStringLength(), VPackValueType::String));
keys->close();
TRI_IF_FAILURE("EdgeIndex::collectKeys") {
THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
}
}
class EdgeIndexIteratorMock final : public arangodb::IndexIterator {
public:
typedef std::unordered_multimap<std::string, arangodb::LocalDocumentId> Map;
EdgeIndexIteratorMock(arangodb::LogicalCollection* collection,
arangodb::transaction::Methods* trx, arangodb::Index const* index,
Map const& map, std::unique_ptr<VPackBuilder>&& keys)
: IndexIterator(collection, trx),
_map(map),
_begin(_map.begin()),
_end(_map.end()),
_keys(std::move(keys)),
_keysIt(_keys->slice()) {}
char const* typeName() const override { return "edge-index-iterator-mock"; }
bool next(LocalDocumentIdCallback const& cb, size_t limit) override {
while (limit && _begin != _end && _keysIt.valid()) {
auto key = _keysIt.value();
if (key.isObject()) {
key = key.get(arangodb::StaticStrings::IndexEq);
}
std::tie(_begin, _end) = _map.equal_range(key.toString());
while (limit && _begin != _end) {
cb(_begin->second);
++_begin;
--limit;
}
++_keysIt;
}
return _begin != _end || _keysIt.valid();
}
void reset() override {
_keysIt.reset();
_begin = _map.begin();
_end = _map.end();
}
private:
Map const& _map;
Map::const_iterator _begin;
Map::const_iterator _end;
std::unique_ptr<VPackBuilder> _keys;
arangodb::velocypack::ArrayIterator _keysIt;
}; // EdgeIndexIteratorMock
class EdgeIndexMock final : public arangodb::Index {
public:
static std::shared_ptr<arangodb::Index> make(TRI_idx_iid_t iid,
arangodb::LogicalCollection& collection,
arangodb::velocypack::Slice const& definition) {
auto const typeSlice = definition.get("type");
if (typeSlice.isNone()) {
return nullptr;
}
auto const type =
arangodb::basics::VelocyPackHelper::getStringRef(typeSlice,
arangodb::velocypack::StringRef());
if (type.compare("edge") != 0) {
return nullptr;
}
return std::make_shared<EdgeIndexMock>(iid, collection);
}
IndexType type() const override { return Index::TRI_IDX_TYPE_EDGE_INDEX; }
char const* typeName() const override { return "edge"; }
bool isPersistent() const override { return false; }
bool canBeDropped() const override { return false; }
bool isHidden() const override { return false; }
bool isSorted() const override { return false; }
bool hasSelectivityEstimate() const override { return false; }
size_t memory() const override { return sizeof(EdgeIndexMock); }
void load() override {}
void unload() override {}
void afterTruncate(TRI_voc_tick_t) override {
_edgesFrom.clear();
_edgesTo.clear();
}
void toVelocyPack(VPackBuilder& builder,
std::underlying_type<arangodb::Index::Serialize>::type flags) const override {
builder.openObject();
Index::toVelocyPack(builder, flags);
// hard-coded
builder.add("unique", VPackValue(false));
builder.add("sparse", VPackValue(false));
builder.close();
}
void toVelocyPackFigures(VPackBuilder& builder) const override {
Index::toVelocyPackFigures(builder);
builder.add("from", VPackValue(VPackValueType::Object));
//_edgesFrom->appendToVelocyPack(builder);
builder.close();
builder.add("to", VPackValue(VPackValueType::Object));
//_edgesTo->appendToVelocyPack(builder);
builder.close();
}
arangodb::Result insert(arangodb::transaction::Methods& trx,
arangodb::LocalDocumentId const& documentId,
arangodb::velocypack::Slice const& doc, OperationMode) {
if (!doc.isObject()) {
return {TRI_ERROR_INTERNAL};
}
VPackSlice const fromValue(arangodb::transaction::helpers::extractFromFromDocument(doc));
if (!fromValue.isString()) {
return {TRI_ERROR_INTERNAL};
}
VPackSlice const toValue(arangodb::transaction::helpers::extractToFromDocument(doc));
if (!toValue.isString()) {
return {TRI_ERROR_INTERNAL};
}
_edgesFrom.emplace(fromValue.toString(), documentId);
_edgesTo.emplace(toValue.toString(), documentId);
return {}; // ok
}
arangodb::Result remove(arangodb::transaction::Methods& trx,
arangodb::LocalDocumentId const&,
arangodb::velocypack::Slice const& doc, OperationMode) {
if (!doc.isObject()) {
return {TRI_ERROR_INTERNAL};
}
VPackSlice const fromValue(arangodb::transaction::helpers::extractFromFromDocument(doc));
if (!fromValue.isString()) {
return {TRI_ERROR_INTERNAL};
}
VPackSlice const toValue(arangodb::transaction::helpers::extractToFromDocument(doc));
if (!toValue.isString()) {
return {TRI_ERROR_INTERNAL};
}
_edgesFrom.erase(fromValue.toString());
_edgesTo.erase(toValue.toString());
return {}; // ok
}
bool supportsFilterCondition(std::vector<std::shared_ptr<arangodb::Index>> const& /*allIndexes*/,
arangodb::aql::AstNode const* node,
arangodb::aql::Variable const* reference,
size_t itemsInIndex, size_t& estimatedItems,
double& estimatedCost) const override {
arangodb::SimpleAttributeEqualityMatcher matcher(IndexAttributes);
return matcher.matchOne(this, node, reference, itemsInIndex, estimatedItems, estimatedCost);
}
arangodb::IndexIterator* iteratorForCondition(arangodb::transaction::Methods* trx,
arangodb::aql::AstNode const* node,
arangodb::aql::Variable const*,
arangodb::IndexIteratorOptions const&) override {
TRI_ASSERT(node->type == arangodb::aql::NODE_TYPE_OPERATOR_NARY_AND);
TRI_ASSERT(node->numMembers() == 1);
auto comp = node->getMember(0);
// assume a.b == value
auto attrNode = comp->getMember(0);
auto valNode = comp->getMember(1);
if (attrNode->type != arangodb::aql::NODE_TYPE_ATTRIBUTE_ACCESS) {
// got value == a.b -> flip sides
std::swap(attrNode, valNode);
}
TRI_ASSERT(attrNode->type == arangodb::aql::NODE_TYPE_ATTRIBUTE_ACCESS);
if (comp->type == arangodb::aql::NODE_TYPE_OPERATOR_BINARY_EQ) {
// a.b == value
return createEqIterator(trx, attrNode, valNode);
}
if (comp->type == arangodb::aql::NODE_TYPE_OPERATOR_BINARY_IN) {
// a.b IN values
if (!valNode->isArray()) {
// a.b IN non-array
return new arangodb::EmptyIndexIterator(&_collection, trx);
}
return createInIterator(trx, attrNode, valNode);
}
// operator type unsupported
return new arangodb::EmptyIndexIterator(&_collection, trx);
}
arangodb::aql::AstNode* specializeCondition(arangodb::aql::AstNode* node,
arangodb::aql::Variable const* reference) const override {
arangodb::SimpleAttributeEqualityMatcher matcher(IndexAttributes);
return matcher.specializeOne(this, node, reference);
}
EdgeIndexMock(TRI_idx_iid_t iid, arangodb::LogicalCollection& collection)
: arangodb::Index(
iid, collection, arangodb::StaticStrings::IndexNameEdge,
{{arangodb::basics::AttributeName(arangodb::StaticStrings::FromString, false)},
{arangodb::basics::AttributeName(arangodb::StaticStrings::ToString, false)}},
true, false) {}
arangodb::IndexIterator* createEqIterator(arangodb::transaction::Methods* trx,
arangodb::aql::AstNode const* attrNode,
arangodb::aql::AstNode const* valNode) const {
// lease builder, but immediately pass it to the unique_ptr so we don't leak
arangodb::transaction::BuilderLeaser builder(trx);
std::unique_ptr<VPackBuilder> keys(builder.steal());
keys->openArray();
handleValNode(keys.get(), valNode);
TRI_IF_FAILURE("EdgeIndex::noIterator") {
THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
}
keys->close();
// _from or _to?
bool const isFrom = (attrNode->stringEquals(arangodb::StaticStrings::FromString));
return new EdgeIndexIteratorMock(&_collection, trx, this,
isFrom ? _edgesFrom : _edgesTo, std::move(keys));
}
/// @brief create the iterator
arangodb::IndexIterator* createInIterator(arangodb::transaction::Methods* trx,
arangodb::aql::AstNode const* attrNode,
arangodb::aql::AstNode const* valNode) const {
// lease builder, but immediately pass it to the unique_ptr so we don't leak
arangodb::transaction::BuilderLeaser builder(trx);
std::unique_ptr<VPackBuilder> keys(builder.steal());
keys->openArray();
size_t const n = valNode->numMembers();
for (size_t i = 0; i < n; ++i) {
handleValNode(keys.get(), valNode->getMemberUnchecked(i));
TRI_IF_FAILURE("EdgeIndex::iteratorValNodes") {
THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
}
}
TRI_IF_FAILURE("EdgeIndex::noIterator") {
THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
}
keys->close();
// _from or _to?
bool const isFrom = (attrNode->stringEquals(arangodb::StaticStrings::FromString));
return new EdgeIndexIteratorMock(&_collection, trx, this,
isFrom ? _edgesFrom : _edgesTo, std::move(keys));
}
/// @brief the hash table for _from
EdgeIndexIteratorMock::Map _edgesFrom;
/// @brief the hash table for _to
EdgeIndexIteratorMock::Map _edgesTo;
}; // EdgeIndexMock
class ReverseAllIteratorMock final : public arangodb::IndexIterator {
public:
ReverseAllIteratorMock(uint64_t size, arangodb::LogicalCollection* coll,
arangodb::transaction::Methods* trx)
: arangodb::IndexIterator(coll, trx), _end(size), _size(size) {}
virtual char const* typeName() const override {
return "ReverseAllIteratorMock";
}
virtual void reset() override { _end = _size; }
virtual bool next(LocalDocumentIdCallback const& callback, size_t limit) override {
while (_end && limit) { // `_end` always > 0
callback(arangodb::LocalDocumentId(_end--));
--limit;
}
return 0 == limit;
}
private:
uint64_t _end;
uint64_t _size; // the original size
}; // ReverseAllIteratorMock
class AllIteratorMock final : public arangodb::IndexIterator {
public:
AllIteratorMock(uint64_t size, arangodb::LogicalCollection& coll,
arangodb::transaction::Methods* trx)
: arangodb::IndexIterator(&coll, trx), _end(size) {}
virtual char const* typeName() const override { return "AllIteratorMock"; }
virtual void reset() override { _begin = 0; }
virtual bool next(LocalDocumentIdCallback const& callback, size_t limit) override {
while (_begin < _end && limit) {
callback(arangodb::LocalDocumentId(++_begin)); // always > 0
--limit;
}
return 0 == limit;
}
private:
uint64_t _begin{};
uint64_t _end;
}; // AllIteratorMock
struct IndexFactoryMock : arangodb::IndexFactory {
virtual void fillSystemIndexes(arangodb::LogicalCollection& col,
std::vector<std::shared_ptr<arangodb::Index>>& systemIndexes) const override {
// NOOP
}
/// @brief create indexes from a list of index definitions
virtual void prepareIndexes(arangodb::LogicalCollection& col,
arangodb::velocypack::Slice const& indexesSlice,
std::vector<std::shared_ptr<arangodb::Index>>& indexes) const override {
// NOOP
}
};
} // namespace
std::function<void()> PhysicalCollectionMock::before = []() -> void {};
PhysicalCollectionMock::PhysicalCollectionMock(arangodb::LogicalCollection& collection,
arangodb::velocypack::Slice const& info)
: PhysicalCollection(collection, info) {}
arangodb::PhysicalCollection* PhysicalCollectionMock::clone(arangodb::LogicalCollection& collection) const {
before();
TRI_ASSERT(false);
return nullptr;
}
int PhysicalCollectionMock::close() {
for (auto& index : _indexes) {
index->unload();
}
return TRI_ERROR_NO_ERROR; // assume close successful
}
std::shared_ptr<arangodb::Index> PhysicalCollectionMock::createIndex(
arangodb::velocypack::Slice const& info, bool restore, bool& created) {
before();
std::vector<std::pair<arangodb::LocalDocumentId, arangodb::velocypack::Slice>> docs;
for (size_t i = 0, count = documents.size(); i < count; ++i) {
auto& entry = documents[i];
if (entry.second) {
auto revId = arangodb::LocalDocumentId::create(i + 1); // always > 0
docs.emplace_back(revId, entry.first.slice());
}
}
struct IndexFactory : public arangodb::IndexFactory {
using arangodb::IndexFactory::validateSlice;
};
auto id = IndexFactory::validateSlice(info, true, false); // trie+false to ensure id generation if missing
auto const type =
arangodb::basics::VelocyPackHelper::getStringRef(info.get("type"),
arangodb::velocypack::StringRef());
std::shared_ptr<arangodb::Index> index;
if (0 == type.compare("edge")) {
index = EdgeIndexMock::make(id, _logicalCollection, info);
} else if (0 == type.compare(arangodb::iresearch::DATA_SOURCE_TYPE.name())) {
if (arangodb::ServerState::instance()->isCoordinator()) {
arangodb::iresearch::IResearchLinkCoordinator::factory().instantiate(index, _logicalCollection,
info, id, false);
} else {
arangodb::iresearch::IResearchMMFilesLink::factory().instantiate(index, _logicalCollection,
info, id, false);
}
}
if (!index) {
return nullptr;
}
asio_ns::io_context ioContext;
auto poster = [&ioContext](std::function<void()> fn) -> void {
ioContext.post(fn);
};
arangodb::basics::LocalTaskQueue taskQueue(poster);
std::shared_ptr<arangodb::basics::LocalTaskQueue> taskQueuePtr(
&taskQueue, [](arangodb::basics::LocalTaskQueue*) -> void {});
TRI_vocbase_t& vocbase = _logicalCollection.vocbase();
arangodb::SingleCollectionTransaction trx(arangodb::transaction::StandaloneContext::Create(vocbase),
_logicalCollection,
arangodb::AccessMode::Type::WRITE);
auto res = trx.begin();
TRI_ASSERT(res.ok());
if (index->type() == arangodb::Index::TRI_IDX_TYPE_EDGE_INDEX) {
auto* l = dynamic_cast<EdgeIndexMock*>(index.get());
TRI_ASSERT(l != nullptr);
for (auto const& pair : docs) {
l->insert(trx, pair.first, pair.second, arangodb::Index::OperationMode::internal);
}
} else if (index->type() == arangodb::Index::TRI_IDX_TYPE_IRESEARCH_LINK) {
auto* l = dynamic_cast<arangodb::iresearch::IResearchLink*>(index.get());
TRI_ASSERT(l != nullptr);
;
l->batchInsert(trx, docs, taskQueuePtr);
} else {
TRI_ASSERT(false);
}
if (TRI_ERROR_NO_ERROR != taskQueue.status()) {
return nullptr;
}
_indexes.emplace_back(std::move(index));
created = true;
res = trx.commit();
TRI_ASSERT(res.ok());
return _indexes.back();
}
void PhysicalCollectionMock::deferDropCollection(
std::function<bool(arangodb::LogicalCollection&)> const& callback) {
before();
callback(_logicalCollection); // assume noone is using this collection (drop immediately)
}
bool PhysicalCollectionMock::dropIndex(TRI_idx_iid_t iid) {
before();
for (auto itr = _indexes.begin(), end = _indexes.end(); itr != end; ++itr) {
if ((*itr)->id() == iid) {
if ((*itr)->drop().ok()) {
_indexes.erase(itr);
return true;
}
}
}
return false;
}
void PhysicalCollectionMock::figuresSpecific(std::shared_ptr<arangodb::velocypack::Builder>&) {
before();
TRI_ASSERT(false);
}
std::unique_ptr<arangodb::IndexIterator> PhysicalCollectionMock::getAllIterator(
arangodb::transaction::Methods* trx) const {
before();
return std::make_unique<AllIteratorMock>(documents.size(), this->_logicalCollection, trx);
}
std::unique_ptr<arangodb::IndexIterator> PhysicalCollectionMock::getAnyIterator(
arangodb::transaction::Methods* trx) const {
before();
return std::make_unique<AllIteratorMock>(documents.size(), this->_logicalCollection, trx);
}
void PhysicalCollectionMock::getPropertiesVPack(arangodb::velocypack::Builder&) const {
before();
}
arangodb::Result PhysicalCollectionMock::insert(
arangodb::transaction::Methods* trx, arangodb::velocypack::Slice const newSlice,
arangodb::ManagedDocumentResult& result, arangodb::OperationOptions& options,
bool lock, arangodb::KeyLockInfo* /*keyLockInfo*/,
std::function<void()> const& callbackDuringLock) {
TRI_ASSERT(callbackDuringLock == nullptr); // not implemented
before();
arangodb::velocypack::Builder builder;
auto isEdgeCollection = (TRI_COL_TYPE_EDGE == _logicalCollection.type());
TRI_voc_rid_t unused;
auto res = newObjectForInsert(trx, newSlice, isEdgeCollection, builder,
options.isRestore, unused);
if (res.fail()) {
return res;
}
documents.emplace_back(std::move(builder), true);
arangodb::LocalDocumentId docId(documents.size()); // always > 0
result.setUnmanaged(documents.back().first.data());
TRI_ASSERT(result.revisionId() == unused);
for (auto& index : _indexes) {
if (index->type() == arangodb::Index::TRI_IDX_TYPE_EDGE_INDEX) {
auto* l = static_cast<EdgeIndexMock*>(index.get());
if (!l->insert(*trx, docId, arangodb::velocypack::Slice(result.vpack()),
arangodb::Index::OperationMode::normal)
.ok()) {
return arangodb::Result(TRI_ERROR_BAD_PARAMETER);
}
continue;
} else if (index->type() == arangodb::Index::TRI_IDX_TYPE_IRESEARCH_LINK) {
if (arangodb::ServerState::instance()->isCoordinator()) {
auto* l =
static_cast<arangodb::iresearch::IResearchLinkCoordinator*>(index.get());
if (!l->insert(*trx, docId, arangodb::velocypack::Slice(result.vpack()),
arangodb::Index::OperationMode::normal)
.ok()) {
return arangodb::Result(TRI_ERROR_BAD_PARAMETER);
}
} else {
auto* l = static_cast<arangodb::iresearch::IResearchMMFilesLink*>(index.get());
if (!l->insert(*trx, docId, arangodb::velocypack::Slice(result.vpack()),
arangodb::Index::OperationMode::normal)
.ok()) {
return arangodb::Result(TRI_ERROR_BAD_PARAMETER);
}
}
continue;
}
TRI_ASSERT(false);
}
return arangodb::Result();
}
void PhysicalCollectionMock::invokeOnAllElements(
arangodb::transaction::Methods*,
std::function<bool(arangodb::LocalDocumentId const&)> callback) {
before();
for (size_t i = 0, count = documents.size(); i < count; ++i) {
arangodb::LocalDocumentId token(i + 1); // '_data' always > 0
if (documents[i].second && !callback(token)) {
return;
}
}
}
arangodb::LocalDocumentId PhysicalCollectionMock::lookupKey(
arangodb::transaction::Methods*, arangodb::velocypack::Slice const&) const {
before();
TRI_ASSERT(false);
return arangodb::LocalDocumentId();
}
size_t PhysicalCollectionMock::memory() const {
before();
TRI_ASSERT(false);
return 0;
}
uint64_t PhysicalCollectionMock::numberDocuments(arangodb::transaction::Methods*) const {
before();
return documents.size();
}
void PhysicalCollectionMock::open(bool ignoreErrors) {
before();
TRI_ASSERT(false);
}
std::string const& PhysicalCollectionMock::path() const {
before();
return physicalPath;
}
arangodb::Result PhysicalCollectionMock::persistProperties() {
before();
TRI_ASSERT(false);
return arangodb::Result(TRI_ERROR_INTERNAL);
}
bool PhysicalCollectionMock::addIndex(std::shared_ptr<arangodb::Index> idx) {
auto const id = idx->id();
for (auto const& it : _indexes) {
if (it->id() == id) {
// already have this particular index. do not add it again
return false;
}
}
TRI_UpdateTickServer(static_cast<TRI_voc_tick_t>(id));
_indexes.emplace_back(idx);
return true;
}
void PhysicalCollectionMock::prepareIndexes(arangodb::velocypack::Slice indexesSlice) {
before();
auto* engine = arangodb::EngineSelectorFeature::ENGINE;
auto& idxFactory = engine->indexFactory();
for (auto const& v : VPackArrayIterator(indexesSlice)) {
if (arangodb::basics::VelocyPackHelper::getBooleanValue(v, "error", false)) {
// We have an error here.
// Do not add index.
continue;
}
auto idx = idxFactory.prepareIndexFromSlice(v, false, _logicalCollection, true);
if (!idx) {
continue;
}
if (!addIndex(idx)) {
return;
}
}
}
arangodb::Result PhysicalCollectionMock::read(arangodb::transaction::Methods*,
arangodb::velocypack::StringRef const& key,
arangodb::ManagedDocumentResult& result, bool) {
before();
for (size_t i = documents.size(); i; --i) {
auto& entry = documents[i - 1];
if (!entry.second) {
continue; // removed document
}
auto& doc = entry.first;
auto const keySlice = doc.slice().get(arangodb::StaticStrings::KeyString);
if (!keySlice.isString()) {
continue;
}
arangodb::velocypack::StringRef const docKey(keySlice);
if (key == docKey) {
result.setUnmanaged(doc.data());
return arangodb::Result(TRI_ERROR_NO_ERROR);
}
}
return arangodb::Result(TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND);
}
arangodb::Result PhysicalCollectionMock::read(arangodb::transaction::Methods*,
arangodb::velocypack::Slice const& key,
arangodb::ManagedDocumentResult& result, bool) {
before();
TRI_ASSERT(false);
return TRI_ERROR_INTERNAL;
}
bool PhysicalCollectionMock::readDocument(arangodb::transaction::Methods* trx,
arangodb::LocalDocumentId const& token,
arangodb::ManagedDocumentResult& result) const {
before();
if (token.id() > documents.size()) {
return false;
}
auto& entry = documents[token.id() - 1]; // '_data' always > 0
if (!entry.second) {
return false; // removed document
}
result.setUnmanaged(entry.first.data());
return true;
}
bool PhysicalCollectionMock::readDocumentWithCallback(
arangodb::transaction::Methods* trx, arangodb::LocalDocumentId const& token,
arangodb::IndexIterator::DocumentCallback const& cb) const {
before();
if (token.id() > documents.size()) {
return false;
}
auto& entry = documents[token.id() - 1]; // '_data' always > 0
if (!entry.second) {
return false; // removed document
}
cb(token, VPackSlice(entry.first.data()));
return true;
}
arangodb::Result PhysicalCollectionMock::remove(
arangodb::transaction::Methods& trx, arangodb::velocypack::Slice slice,
arangodb::ManagedDocumentResult& previous, arangodb::OperationOptions& options,
bool lock, arangodb::KeyLockInfo* /*keyLockInfo*/,
std::function<void()> const& callbackDuringLock) {
TRI_ASSERT(callbackDuringLock == nullptr); // not implemented
before();
auto key = slice.get(arangodb::StaticStrings::KeyString);
for (size_t i = documents.size(); i; --i) {
auto& entry = documents[i - 1];
if (!entry.second) {
continue; // removed document
}
arangodb::velocypack::Builder& doc = entry.first;
if (key == doc.slice().get(arangodb::StaticStrings::KeyString)) {
entry.second = false;
previous.setUnmanaged(doc.data());
TRI_ASSERT(previous.revisionId() == TRI_ExtractRevisionId(doc.slice()));
return arangodb::Result(TRI_ERROR_NO_ERROR); // assume document was removed
}
}
return arangodb::Result(TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND);
}
arangodb::Result PhysicalCollectionMock::replace(
arangodb::transaction::Methods* trx, arangodb::velocypack::Slice const newSlice,
arangodb::ManagedDocumentResult& result,
arangodb::OperationOptions& options,
bool lock, arangodb::ManagedDocumentResult& previous) {
before();
return update(trx, newSlice, result, options, lock, previous);
}
TRI_voc_rid_t PhysicalCollectionMock::revision(arangodb::transaction::Methods*) const {
before();
TRI_ASSERT(false);
return 0;
}
void PhysicalCollectionMock::setPath(std::string const& value) {
before();
physicalPath = value;
}
arangodb::Result PhysicalCollectionMock::truncate(arangodb::transaction::Methods& trx,
arangodb::OperationOptions& options) {
before();
documents.clear();
return arangodb::Result();
}
arangodb::Result PhysicalCollectionMock::compact() {
return arangodb::Result();
}
arangodb::Result PhysicalCollectionMock::update(
arangodb::transaction::Methods* trx, arangodb::velocypack::Slice const newSlice,
arangodb::ManagedDocumentResult& result, arangodb::OperationOptions& options,
bool lock, arangodb::ManagedDocumentResult& previous) {
auto key = newSlice.get(arangodb::StaticStrings::KeyString);
if (key.isNone()) {
return arangodb::Result(TRI_ERROR_ARANGO_DOCUMENT_HANDLE_BAD);
}
before();
for (size_t i = documents.size(); i; --i) {
auto& entry = documents[i - 1];
if (!entry.second) {
continue; // removed document
}
auto& doc = entry.first;
if (key == doc.slice().get(arangodb::StaticStrings::KeyString)) {
if (!options.mergeObjects) {
entry.second = false;
previous.setUnmanaged(doc.data());
TRI_ASSERT(previous.revisionId() == TRI_ExtractRevisionId(doc.slice()));
return insert(trx, newSlice, result, options, lock, nullptr, nullptr);
}
arangodb::velocypack::Builder builder;
builder.openObject();
if (!arangodb::iresearch::mergeSlice(builder, newSlice)) {
return arangodb::Result(TRI_ERROR_BAD_PARAMETER);
}
for (arangodb::velocypack::ObjectIterator itr(doc.slice()); itr.valid(); ++itr) {
auto key = itr.key().copyString();
if (!newSlice.hasKey(key)) {
builder.add(key, itr.value());
}
}
builder.close();
entry.second = false;
previous.setUnmanaged(doc.data());
TRI_ASSERT(previous.revisionId() == TRI_ExtractRevisionId(doc.slice()));
return insert(trx, builder.slice(), result, options, lock, nullptr, nullptr);
}
}
return arangodb::Result(TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND);
}
arangodb::Result PhysicalCollectionMock::updateProperties(arangodb::velocypack::Slice const& slice,
bool doSync) {
before();
return arangodb::Result(TRI_ERROR_NO_ERROR); // assume mock collection updated OK
}
std::function<void()> StorageEngineMock::before = []() -> void {};
arangodb::Result StorageEngineMock::flushSubscriptionResult;
bool StorageEngineMock::inRecoveryResult = false;
/*static*/ std::string StorageEngineMock::versionFilenameResult;
StorageEngineMock::StorageEngineMock(arangodb::application_features::ApplicationServer& server)
: StorageEngine(server, "Mock", "",
std::unique_ptr<arangodb::IndexFactory>(new IndexFactoryMock())),
_releasedTick(0) {
arangodb::FlushFeature::_defaultFlushSubscription =
[](std::string const&, TRI_vocbase_t const&,
arangodb::velocypack::Slice const&) -> arangodb::Result {
return flushSubscriptionResult;
};
}
arangodb::WalAccess const* StorageEngineMock::walAccess() const {
TRI_ASSERT(false);
return nullptr;
}
void StorageEngineMock::addOptimizerRules() {
before();
// NOOP
}
void StorageEngineMock::addRestHandlers(arangodb::rest::RestHandlerFactory& handlerFactory) {
TRI_ASSERT(false);
}
void StorageEngineMock::addV8Functions() { TRI_ASSERT(false); }
void StorageEngineMock::changeCollection(TRI_vocbase_t& vocbase,
arangodb::LogicalCollection const& collection,
bool doSync) {
// NOOP, assume physical collection changed OK
}
arangodb::Result StorageEngineMock::changeView(TRI_vocbase_t& vocbase,
arangodb::LogicalView const& view,
bool doSync) {
before();
TRI_ASSERT(views.find(std::make_pair(vocbase.id(), view.id())) != views.end());
arangodb::velocypack::Builder builder;
builder.openObject();
view.properties(builder, true, true);
builder.close();
views[std::make_pair(vocbase.id(), view.id())] = std::move(builder);
return {};
}
std::string StorageEngineMock::collectionPath(TRI_vocbase_t const& vocbase,
TRI_voc_cid_t id) const {
TRI_ASSERT(false);
return "<invalid>";
}
std::string StorageEngineMock::createCollection(TRI_vocbase_t& vocbase,
arangodb::LogicalCollection const& collection) {
return "<invalid>"; // physical path of the new collection
}
std::unique_ptr<TRI_vocbase_t> StorageEngineMock::createDatabase(
TRI_voc_tick_t id, arangodb::velocypack::Slice const& args, int& status) {
if (!args.get("name").isString()) {
status = TRI_ERROR_BAD_PARAMETER;
}
status = TRI_ERROR_NO_ERROR;
std::string cname = args.get("name").copyString();
if (arangodb::ServerState::instance()->isCoordinator()) {
return std::make_unique<TRI_vocbase_t>(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_COORDINATOR,
id, cname);
}
return std::make_unique<TRI_vocbase_t>(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL,
id, cname);
}
arangodb::Result StorageEngineMock::createLoggerState(TRI_vocbase_t*, VPackBuilder&) {
TRI_ASSERT(false);
return arangodb::Result(TRI_ERROR_NOT_IMPLEMENTED);
}
std::unique_ptr<arangodb::PhysicalCollection> StorageEngineMock::createPhysicalCollection(
arangodb::LogicalCollection& collection, arangodb::velocypack::Slice const& info) {
before();
return std::unique_ptr<arangodb::PhysicalCollection>(
new PhysicalCollectionMock(collection, info));
}
arangodb::Result StorageEngineMock::createTickRanges(VPackBuilder&) {
TRI_ASSERT(false);
return arangodb::Result(TRI_ERROR_NOT_IMPLEMENTED);
}
std::unique_ptr<arangodb::TransactionCollection> StorageEngineMock::createTransactionCollection(
arangodb::TransactionState& state, TRI_voc_cid_t cid,
arangodb::AccessMode::Type accessType, int nestingLevel) {
return std::unique_ptr<arangodb::TransactionCollection>(
new TransactionCollectionMock(&state, cid, accessType));
}
std::unique_ptr<arangodb::transaction::ContextData> StorageEngineMock::createTransactionContextData() {
return std::unique_ptr<arangodb::transaction::ContextData>();
}
std::unique_ptr<arangodb::transaction::Manager> StorageEngineMock::createTransactionManager() {
TRI_ASSERT(false);
return nullptr;
}
std::unique_ptr<arangodb::TransactionState> StorageEngineMock::createTransactionState(
TRI_vocbase_t& vocbase,
TRI_voc_tid_t tid,
arangodb::transaction::Options const& options
) {
return std::unique_ptr<arangodb::TransactionState>(
new TransactionStateMock(vocbase, options)
);
}
arangodb::Result StorageEngineMock::createView(TRI_vocbase_t& vocbase, TRI_voc_cid_t id,
arangodb::LogicalView const& view) {
before();
TRI_ASSERT(views.find(std::make_pair(vocbase.id(), view.id())) == views.end()); // called after createView()
arangodb::velocypack::Builder builder;
builder.openObject();
view.properties(builder, true, true);
builder.close();
views[std::make_pair(vocbase.id(), view.id())] = std::move(builder);
return arangodb::Result(TRI_ERROR_NO_ERROR); // assume mock view persisted OK
}
void StorageEngineMock::getViewProperties(TRI_vocbase_t& vocbase,
arangodb::LogicalView const& view,
VPackBuilder& builder) {
before();
// NOOP
}
TRI_voc_tick_t StorageEngineMock::currentTick() const {
return TRI_CurrentTickServer();
}
std::string StorageEngineMock::databasePath(TRI_vocbase_t const* vocbase) const {
before();
return ""; // no valid path filesystem persisted, return empty string
}
void StorageEngineMock::destroyCollection(TRI_vocbase_t& vocbase,
arangodb::LogicalCollection& collection) {
// NOOP, assume physical collection destroyed OK
}
void StorageEngineMock::destroyView(TRI_vocbase_t const& vocbase,
arangodb::LogicalView const& view) noexcept {
before();
// NOOP, assume physical view destroyed OK
}
arangodb::Result StorageEngineMock::dropCollection(TRI_vocbase_t& vocbase,
arangodb::LogicalCollection& collection) {
return arangodb::Result(TRI_ERROR_NO_ERROR); // assume physical collection dropped OK
}
arangodb::Result StorageEngineMock::dropDatabase(TRI_vocbase_t& vocbase) {
TRI_ASSERT(false);
return arangodb::Result();
}
arangodb::Result StorageEngineMock::dropView(TRI_vocbase_t const& vocbase,
arangodb::LogicalView const& view) {
before();
TRI_ASSERT(views.find(std::make_pair(vocbase.id(), view.id())) != views.end());
views.erase(std::make_pair(vocbase.id(), view.id()));
return arangodb::Result(TRI_ERROR_NO_ERROR); // assume mock view dropped OK
}
arangodb::Result StorageEngineMock::firstTick(uint64_t&) {
TRI_ASSERT(false);
return arangodb::Result(TRI_ERROR_NOT_IMPLEMENTED);
}
void StorageEngineMock::getCollectionInfo(TRI_vocbase_t& vocbase, TRI_voc_cid_t cid,
arangodb::velocypack::Builder& result,
bool includeIndexes, TRI_voc_tick_t maxTick) {
arangodb::velocypack::Builder parameters;
parameters.openObject();
parameters.close();
result.openObject();
result.add("parameters", parameters.slice()); // required entry of type object
result.close();
// nothing more required, assume info used for PhysicalCollectionMock
}
int StorageEngineMock::getCollectionsAndIndexes(TRI_vocbase_t& vocbase,
arangodb::velocypack::Builder& result,
bool wasCleanShutdown, bool isUpgrade) {
TRI_ASSERT(false);
return TRI_ERROR_INTERNAL;
}
void StorageEngineMock::getDatabases(arangodb::velocypack::Builder& result) {
before();
arangodb::velocypack::Builder system;
system.openObject();
system.add("name", arangodb::velocypack::Value(TRI_VOC_SYSTEM_DATABASE));
system.close();
// array expected
result.openArray();
result.add(system.slice());
result.close();
}
void StorageEngineMock::cleanupReplicationContexts() {
// nothing to do here
}
arangodb::velocypack::Builder StorageEngineMock::getReplicationApplierConfiguration(
TRI_vocbase_t& vocbase, int& result) {
before();
result = TRI_ERROR_FILE_NOT_FOUND; // assume no ReplicationApplierConfiguration for vocbase
return arangodb::velocypack::Builder();
}
arangodb::velocypack::Builder StorageEngineMock::getReplicationApplierConfiguration(int& result) {
before();
result = TRI_ERROR_FILE_NOT_FOUND;
return arangodb::velocypack::Builder();
}
int StorageEngineMock::getViews(TRI_vocbase_t& vocbase, arangodb::velocypack::Builder& result) {
result.openArray();
for (auto& entry : views) {
result.add(entry.second.slice());
}
result.close();
return TRI_ERROR_NO_ERROR;
}
arangodb::Result StorageEngineMock::handleSyncKeys(arangodb::DatabaseInitialSyncer& syncer,
arangodb::LogicalCollection& col,
std::string const& keysId) {
TRI_ASSERT(false);
return arangodb::Result();
}
bool StorageEngineMock::inRecovery() { return inRecoveryResult; }
arangodb::Result StorageEngineMock::lastLogger(
TRI_vocbase_t& vocbase, std::shared_ptr<arangodb::transaction::Context> transactionContext,
uint64_t tickStart, uint64_t tickEnd, std::shared_ptr<VPackBuilder>& builderSPtr) {
TRI_ASSERT(false);
return arangodb::Result(TRI_ERROR_NOT_IMPLEMENTED);
}
std::unique_ptr<TRI_vocbase_t> StorageEngineMock::openDatabase(
arangodb::velocypack::Slice const& args, bool isUpgrade, int& status) {
before();
if (!args.isObject() || !args.hasKey("name") || !args.get("name").isString()) {
status = TRI_ERROR_ARANGO_DATABASE_NAME_INVALID;
return nullptr;
}
status = TRI_ERROR_NO_ERROR;
return std::make_unique<TRI_vocbase_t>(
TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL,
vocbaseCount++,
args.get("name").copyString()
);
}
arangodb::Result StorageEngineMock::persistCollection(TRI_vocbase_t& vocbase,
arangodb::LogicalCollection const& collection) {
before();
return arangodb::Result(TRI_ERROR_NO_ERROR); // assume mock collection persisted OK
}
void StorageEngineMock::prepareDropDatabase(TRI_vocbase_t& vocbase,
bool useWriteMarker, int& status) {
// NOOP
}
TRI_voc_tick_t StorageEngineMock::releasedTick() const {
before();
return _releasedTick;
}
void StorageEngineMock::releaseTick(TRI_voc_tick_t tick) {
before();
_releasedTick = tick;
}
int StorageEngineMock::removeReplicationApplierConfiguration(TRI_vocbase_t& vocbase) {
TRI_ASSERT(false);
return TRI_ERROR_NO_ERROR;
}
int StorageEngineMock::removeReplicationApplierConfiguration() {
TRI_ASSERT(false);
return TRI_ERROR_NO_ERROR;
}
arangodb::Result StorageEngineMock::renameCollection(TRI_vocbase_t& vocbase,
arangodb::LogicalCollection const& collection,
std::string const& oldName) {
TRI_ASSERT(false);
return arangodb::Result(TRI_ERROR_INTERNAL);
}
int StorageEngineMock::saveReplicationApplierConfiguration(TRI_vocbase_t& vocbase,
arangodb::velocypack::Slice slice,
bool doSync) {
TRI_ASSERT(false);
return TRI_ERROR_NO_ERROR;
}
int StorageEngineMock::saveReplicationApplierConfiguration(arangodb::velocypack::Slice, bool) {
TRI_ASSERT(false);
return TRI_ERROR_NO_ERROR;
}
int StorageEngineMock::shutdownDatabase(TRI_vocbase_t& vocbase) {
before();
return TRI_ERROR_NO_ERROR; // assume shutdown successful
}
void StorageEngineMock::signalCleanup(TRI_vocbase_t& vocbase) {
before();
// NOOP, assume cleanup thread signaled OK
}
bool StorageEngineMock::supportsDfdb() const {
TRI_ASSERT(false);
return false;
}
void StorageEngineMock::unloadCollection(TRI_vocbase_t& vocbase,
arangodb::LogicalCollection& collection) {
before();
// NOOP assume collection unloaded OK
}
std::string StorageEngineMock::versionFilename(TRI_voc_tick_t) const {
return versionFilenameResult;
}
void StorageEngineMock::waitForEstimatorSync(std::chrono::milliseconds) {
TRI_ASSERT(false);
}
void StorageEngineMock::waitForSyncTick(TRI_voc_tick_t tick) {
TRI_ASSERT(false);
}
std::vector<std::string> StorageEngineMock::currentWalFiles() const {
return std::vector<std::string>();
}
arangodb::Result StorageEngineMock::flushWal(bool waitForSync, bool waitForCollector,
bool writeShutdownFile) {
TRI_ASSERT(false);
return arangodb::Result();
}
void StorageEngineMock::waitUntilDeletion(TRI_voc_tick_t id, bool force, int& status) {
// NOOP
}
int StorageEngineMock::writeCreateDatabaseMarker(TRI_voc_tick_t id, VPackSlice const& slice) {
return TRI_ERROR_NO_ERROR;
}
TransactionCollectionMock::TransactionCollectionMock(arangodb::TransactionState* state,
TRI_voc_cid_t cid,
arangodb::AccessMode::Type accessType)
: TransactionCollection(state, cid, accessType) {}
bool TransactionCollectionMock::canAccess(arangodb::AccessMode::Type accessType) const {
return nullptr != _collection; // collection must have be opened previously
}
void TransactionCollectionMock::freeOperations(arangodb::transaction::Methods* activeTrx,
bool mustRollback) {
TRI_ASSERT(false);
}
bool TransactionCollectionMock::hasOperations() const {
TRI_ASSERT(false);
return false;
}
void TransactionCollectionMock::release() {
if (_collection) {
if (!arangodb::ServerState::instance()->isCoordinator()) {
_transaction->vocbase().releaseCollection(_collection.get());
}
_collection = nullptr;
}
}
int TransactionCollectionMock::updateUsage(arangodb::AccessMode::Type accessType,
int nestingLevel) {
if (arangodb::AccessMode::isWriteOrExclusive(accessType) &&
!arangodb::AccessMode::isWriteOrExclusive(_accessType)) {
if (nestingLevel > 0) {
// trying to write access a collection that is only marked with
// read-access
return TRI_ERROR_TRANSACTION_UNREGISTERED_COLLECTION;
}
TRI_ASSERT(nestingLevel == 0);
// upgrade collection type to write-access
_accessType = accessType;
}
// if (nestingLevel < _nestingLevel) {
// _nestingLevel = nestingLevel;
// }
return TRI_ERROR_NO_ERROR;
}
void TransactionCollectionMock::unuse(int nestingLevel) {
// NOOP, assume success
}
int TransactionCollectionMock::use(int nestingLevel) {
TRI_vocbase_col_status_e status;
bool shouldLock = !arangodb::AccessMode::isNone(_accessType);
if (shouldLock && !isLocked()) {
// r/w lock the collection
int res = doLock(_accessType, nestingLevel);
if (res == TRI_ERROR_LOCKED) {
// TRI_ERROR_LOCKED is not an error, but it indicates that the lock
// operation has actually acquired the lock (and that the lock has not
// been held before)
res = TRI_ERROR_NO_ERROR;
} else if (res != TRI_ERROR_NO_ERROR) {
return res;
}
}
if (!_collection) {
if (arangodb::ServerState::instance()->isCoordinator()) {
auto* ci = arangodb::ClusterInfo::instance();
TRI_ASSERT(ci);
_collection = ci->getCollectionNT(_transaction->vocbase().name(), std::to_string(_cid));
} else {
_collection = _transaction->vocbase().useCollection(_cid, status);
}
}
return _collection ? TRI_ERROR_NO_ERROR : TRI_ERROR_INTERNAL;
}
int TransactionCollectionMock::doLock(arangodb::AccessMode::Type type, int nestingLevel) {
if (_lockType > _accessType) {
return TRI_ERROR_INTERNAL;
}
_lockType = type;
return TRI_ERROR_NO_ERROR;
}
int TransactionCollectionMock::doUnlock(arangodb::AccessMode::Type type, int nestingLevel) {
if (_lockType != type) {
return TRI_ERROR_INTERNAL;
}
_lockType = arangodb::AccessMode::Type::NONE;
return TRI_ERROR_NO_ERROR;
}
size_t TransactionStateMock::abortTransactionCount;
size_t TransactionStateMock::beginTransactionCount;
size_t TransactionStateMock::commitTransactionCount;
// ensure each transaction state has a unique ID
TransactionStateMock::TransactionStateMock(TRI_vocbase_t& vocbase,
arangodb::transaction::Options const& options)
: TransactionState(vocbase, 0, options) {}
arangodb::Result TransactionStateMock::abortTransaction(arangodb::transaction::Methods* trx) {
++abortTransactionCount;
updateStatus(arangodb::transaction::Status::ABORTED);
unuseCollections(nestingLevel());
// avoid use of TransactionManagerFeature::manager()->unregisterTransaction(...)
const_cast<TRI_voc_tid_t&>(_id) = 0;
return arangodb::Result();
}
arangodb::Result TransactionStateMock::beginTransaction(arangodb::transaction::Hints hints) {
static std::atomic<TRI_voc_tid_t> lastId(0);
++beginTransactionCount;
_hints = hints;
auto res = useCollections(nestingLevel());
if (!res.ok()) {
updateStatus(arangodb::transaction::Status::ABORTED);
const_cast<TRI_voc_tid_t&>(_id) =
0; // avoid use of TransactionManagerFeature::manager()->unregisterTransaction(...)
return res;
}
const_cast<TRI_voc_tid_t&>(_id) = ++lastId; // ensure each transaction state has a unique ID
updateStatus(arangodb::transaction::Status::RUNNING);
return arangodb::Result();
}
arangodb::Result TransactionStateMock::commitTransaction(arangodb::transaction::Methods* trx) {
++commitTransactionCount;
updateStatus(arangodb::transaction::Status::COMMITTED);
unuseCollections(nestingLevel());
// avoid use of TransactionManagerFeature::manager()->unregisterTransaction(...)
const_cast<TRI_voc_tid_t&>(_id) = 0;
return arangodb::Result();
}
bool TransactionStateMock::hasFailedOperations() const {
return false; // assume no failed operations
}
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------