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

1938 lines
66 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/ClusterFeature.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 "Indexes/SortedIndexAttributeMatcher.h"
#include "RestServer/FlushFeature.h"
#include "StorageEngine/EngineSelectorFeature.h"
#include "Transaction/Helpers.h"
#include "Transaction/Manager.h"
#include "Transaction/ManagerFeature.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/Collection.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
}
Index::FilterCosts supportsFilterCondition(
std::vector<std::shared_ptr<arangodb::Index>> const& /*allIndexes*/,
arangodb::aql::AstNode const* node, arangodb::aql::Variable const* reference,
size_t itemsInIndex) const override {
arangodb::SimpleAttributeEqualityMatcher matcher(IndexAttributes);
return matcher.matchOne(this, node, reference, itemsInIndex);
}
std::unique_ptr<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 std::make_unique<arangodb::EmptyIndexIterator>(&_collection, trx);
}
return createInIterator(trx, attrNode, valNode);
}
// operator type unsupported
return std::make_unique<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) {}
std::unique_ptr<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 std::make_unique<EdgeIndexIteratorMock>(&_collection, trx, this,
isFrom ? _edgesFrom : _edgesTo,
std::move(keys));
}
/// @brief create the iterator
std::unique_ptr<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 std::make_unique<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(std::unordered_map<arangodb::velocypack::StringRef, PhysicalCollectionMock::DocElement> const& data,
arangodb::LogicalCollection& coll, arangodb::transaction::Methods* trx)
: arangodb::IndexIterator(&coll, trx), _data(data), _it{_data.begin()} {}
virtual char const* typeName() const override { return "AllIteratorMock"; }
virtual void reset() override { _it = _data.begin(); }
virtual bool next(LocalDocumentIdCallback const& callback, size_t limit) override {
while (_it != _data.end() && limit != 0) {
callback(_it->second.docId());
++_it;
--limit;
}
return 0 == limit;
}
private:
std::unordered_map<arangodb::velocypack::StringRef, PhysicalCollectionMock::DocElement> const& _data;
std::unordered_map<arangodb::velocypack::StringRef, PhysicalCollectionMock::DocElement>::const_iterator _it;
}; // 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
}
};
class HashIndexMap {
struct VPackBuilderHasher {
std::size_t operator()(VPackBuilder const& builder) const {
return std::hash<VPackSlice>()(builder.slice());
}
};
struct VPackBuilderComparator {
bool operator()(VPackBuilder const& builder1, VPackBuilder const& builder2) const {
return ::arangodb::basics::VelocyPackHelper::compare(builder1.slice(), builder2.slice(), true) == 0;
}
};
using ValueMap = std::unordered_multimap<VPackBuilder, arangodb::LocalDocumentId, VPackBuilderHasher, VPackBuilderComparator>;
using DocumentsIndexMap = std::unordered_map<arangodb::LocalDocumentId, VPackBuilder>;
arangodb::velocypack::Slice getSliceByField(arangodb::velocypack::Slice const& doc, size_t i) {
TRI_ASSERT(i < _fields.size());
TRI_ASSERT(!doc.isNone());
auto slice = doc;
for (auto const& f : _fields[i]) {
slice = slice.get(f.name);
if (slice.isNone() || slice.isNull()) {
break;
}
}
return slice;
}
void insertSlice(arangodb::LocalDocumentId const& documentId, arangodb::velocypack::Slice const& slice, size_t i) {
VPackBuilder builder;
if (slice.isNone() || slice.isNull()) {
builder.add(VPackSlice::nullSlice());
} else {
builder.add(slice);
}
_valueMaps[i].emplace(std::move(builder), documentId);
}
public:
HashIndexMap(std::vector<std::vector<arangodb::basics::AttributeName>> const& fields) : _fields(fields), _valueMaps(fields.size()) {
TRI_ASSERT(!_fields.empty());
}
void insert(arangodb::LocalDocumentId const& documentId, arangodb::velocypack::Slice const& doc) {
VPackBuilder builder;
builder.openArray();
auto toClose = true;
// find fields for the index
for (size_t i = 0; i < _fields.size(); ++i) {
auto slice = doc;
auto isExpansion = false;
for (auto fieldIt = _fields[i].begin(); fieldIt != _fields[i].end(); ++fieldIt) {
TRI_ASSERT(slice.isObject() || slice.isArray());
if (slice.isObject()) {
slice = slice.get(fieldIt->name);
if ((fieldIt->shouldExpand && slice.isObject()) ||
(!fieldIt->shouldExpand && slice.isArray())) {
slice = VPackSlice::nullSlice();
break;
}
if (slice.isNone() || slice.isNull()) {
break;
}
} else { // expansion
isExpansion = slice.isArray();
TRI_ASSERT(isExpansion);
auto found = false;
for (auto sliceIt = arangodb::velocypack::ArrayIterator(slice); sliceIt != sliceIt.end(); ++sliceIt) {
auto subSlice = sliceIt.value();
if (!(subSlice.isNone() || subSlice.isNull())) {
for (auto fieldItForArray = fieldIt; fieldItForArray != _fields[i].end(); ++fieldItForArray) {
TRI_ASSERT(subSlice.isObject());
subSlice = subSlice.get(fieldItForArray->name);
if (subSlice.isNone() || subSlice.isNull()) {
break;
}
}
if (!(subSlice.isNone() || subSlice.isNull())) {
insertSlice(documentId, subSlice, i);
builder.add(subSlice);
found = true;
break;
}
}
}
if (!found) {
insertSlice(documentId, VPackSlice::nullSlice(), i);
builder.add(VPackSlice::nullSlice());
}
break;
}
}
if (!isExpansion) {
// if the last expansion (at the end) leave the array open
if (slice.isArray() && i == _fields.size() - 1) {
auto found = false;
auto wasNull = false;
for (auto sliceIt = arangodb::velocypack::ArrayIterator(slice); sliceIt != sliceIt.end(); ++sliceIt) {
auto subSlice = sliceIt.value();
if (!(subSlice.isNone() || subSlice.isNull())) {
insertSlice(documentId, subSlice, i);
found = true;
} else {
wasNull = true;
}
}
if (!found || wasNull) {
insertSlice(documentId, VPackSlice::nullSlice(), i);
}
toClose = false;
} else { // object
insertSlice(documentId, slice, i);
builder.add(slice);
}
}
}
if (toClose) {
builder.close();
}
_docIndexMap.try_emplace(documentId, std::move(builder));
}
bool remove(arangodb::LocalDocumentId const& documentId, arangodb::velocypack::Slice const& doc) {
size_t i = 0;
auto documentRemoved = false;
for (auto& map : _valueMaps) {
auto slice = getSliceByField(doc, i++);
auto [begin, end] = map.equal_range(VPackBuilder(slice));
for (; begin != end; ++begin) {
if (begin->second == documentId) {
map.erase(begin);
documentRemoved = true;
// not break because of expansions
}
}
}
_docIndexMap.erase(documentId);
return documentRemoved;
}
void clear() {
_valueMaps.clear();
_docIndexMap.clear();
}
std::unordered_map<arangodb::LocalDocumentId, VPackBuilder> find(std::unique_ptr<VPackBuilder>&& keys) const {
std::unordered_map<arangodb::LocalDocumentId, VPackBuilder const*> found;
TRI_ASSERT(keys->slice().isArray());
auto sliceIt = arangodb::velocypack::ArrayIterator(keys->slice());
if (!sliceIt.valid()) {
return std::unordered_map<arangodb::LocalDocumentId, VPackBuilder>();
}
for (auto const& map : _valueMaps) {
auto [begin, end] = map.equal_range(VPackBuilder(sliceIt.value()));
if (begin == end) {
return std::unordered_map<arangodb::LocalDocumentId, VPackBuilder>();
}
if (found.empty()) {
std::transform(begin, end, std::inserter(found, found.end()), [] (auto const& item) {
return std::make_pair(item.second, &item.first);
});
} else {
std::unordered_map<arangodb::LocalDocumentId, VPackBuilder const*> tmpFound;
for (; begin != end; ++begin) {
if (found.find(begin->second) != found.cend()) {
tmpFound.try_emplace(begin->second, &begin->first);
}
}
if (tmpFound.empty()) {
return std::unordered_map<arangodb::LocalDocumentId, VPackBuilder>();
}
found.swap(tmpFound);
}
if (!(++sliceIt).valid()) {
break;
}
}
std::unordered_map<arangodb::LocalDocumentId, VPackBuilder> foundWithCovering;
for (auto const& d : found) {
auto doc = _docIndexMap.find(d.first);
TRI_ASSERT(doc != _docIndexMap.cend());
auto builder = doc->second;
// the array was left open for the last expansion (at the end)
if (doc->second.isOpenArray()) {
builder.add(d.second->slice());
builder.close();
}
foundWithCovering.try_emplace(doc->first, std::move(builder));
}
return foundWithCovering;
}
private:
std::vector<std::vector<arangodb::basics::AttributeName>> const& _fields;
std::vector<ValueMap> _valueMaps;
DocumentsIndexMap _docIndexMap;
};
class HashIndexIteratorMock final : public arangodb::IndexIterator {
public:
HashIndexIteratorMock(arangodb::LogicalCollection* collection,
arangodb::transaction::Methods* trx, arangodb::Index const* index,
HashIndexMap const& map, std::unique_ptr<VPackBuilder>&& keys)
: IndexIterator(collection, trx), _map(map) {
_documents = _map.find(std::move(keys));
_begin = _documents.begin();
_end = _documents.end();
}
char const* typeName() const override { return "hash-index-iterator-mock"; }
bool nextCovering(DocumentCallback const& cb, size_t limit) override {
while (limit && _begin != _end) {
cb(_begin->first, _begin->second.slice());
++_begin;
--limit;
}
return _begin != _end;
}
bool next(LocalDocumentIdCallback const& cb, size_t limit) override {
while (limit && _begin != _end) {
cb(_begin->first);
++_begin;
--limit;
}
return _begin != _end;
}
void reset() override {
_documents.clear();
_begin = _documents.begin();
_end = _documents.end();
}
bool hasCovering() const override {
return true;
}
private:
HashIndexMap const& _map;
std::unordered_map<arangodb::LocalDocumentId, VPackBuilder> _documents;
std::unordered_map<arangodb::LocalDocumentId, VPackBuilder>::const_iterator _begin;
std::unordered_map<arangodb::LocalDocumentId, VPackBuilder>::const_iterator _end;
}; // HashIndexIteratorMock
class HashIndexMock 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("hash") != 0) {
return nullptr;
}
return std::make_shared<HashIndexMock>(iid, collection, definition);
}
IndexType type() const override { return Index::TRI_IDX_TYPE_HASH_INDEX; }
char const* typeName() const override { return "hash"; }
bool isPersistent() const override { return false; }
bool canBeDropped() const override { return false; }
bool hasCoveringIterator() const override { return true; }
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(HashIndexMock); }
void load() override {}
void unload() override {}
void afterTruncate(TRI_voc_tick_t) override {
_hashData.clear();
}
void toVelocyPack(VPackBuilder& builder,
std::underlying_type<arangodb::Index::Serialize>::type flags) const override {
builder.openObject();
Index::toVelocyPack(builder, flags);
builder.add("sparse", VPackValue(sparse()));
builder.add("unique", VPackValue(unique()));
builder.close();
}
void toVelocyPackFigures(VPackBuilder& builder) const override {
Index::toVelocyPackFigures(builder);
}
arangodb::Result insert(arangodb::transaction::Methods&,
arangodb::LocalDocumentId const& documentId,
arangodb::velocypack::Slice const& doc, OperationMode) {
if (!doc.isObject()) {
return {TRI_ERROR_INTERNAL};
}
_hashData.insert(documentId, doc);
return {}; // ok
}
arangodb::Result remove(arangodb::transaction::Methods&,
arangodb::LocalDocumentId const& documentId,
arangodb::velocypack::Slice const& doc, OperationMode) {
if (!doc.isObject()) {
return {TRI_ERROR_INTERNAL};
}
_hashData.remove(documentId, doc);
return {}; // ok
}
Index::FilterCosts supportsFilterCondition(
std::vector<std::shared_ptr<arangodb::Index>> const& allIndexes,
arangodb::aql::AstNode const* node, arangodb::aql::Variable const* reference,
size_t itemsInIndex) const override {
return arangodb::SortedIndexAttributeMatcher::supportsFilterCondition(allIndexes, this, node, reference, itemsInIndex);
}
Index::SortCosts supportsSortCondition(arangodb::aql::SortCondition const* sortCondition,
arangodb::aql::Variable const* reference,
size_t itemsInIndex) const override {
return arangodb::SortedIndexAttributeMatcher::supportsSortCondition(this, sortCondition, reference, itemsInIndex);
}
arangodb::aql::AstNode* specializeCondition(
arangodb::aql::AstNode* node, arangodb::aql::Variable const* reference) const override {
return arangodb::SortedIndexAttributeMatcher::specializeCondition(this, node, reference);
}
std::unique_ptr<arangodb::IndexIterator> iteratorForCondition(
arangodb::transaction::Methods* trx, arangodb::aql::AstNode const* node,
arangodb::aql::Variable const*, arangodb::IndexIteratorOptions const&) override {
arangodb::transaction::BuilderLeaser builder(trx);
std::unique_ptr<VPackBuilder> keys(builder.steal());
keys->openArray();
if (nullptr == node) {
keys->close();
return std::make_unique<HashIndexIteratorMock>(&_collection, trx, this,
_hashData,
std::move(keys));
}
TRI_ASSERT(node->type == arangodb::aql::NODE_TYPE_OPERATOR_NARY_AND);
std::vector<std::pair<std::vector<arangodb::basics::AttributeName>, arangodb::aql::AstNode*>> allAttributes;
for (size_t i = 0; i < node->numMembers(); ++i) {
auto comp = node->getMember(i);
// a.b == value
if (!(comp->type == arangodb::aql::NODE_TYPE_OPERATOR_BINARY_EQ ||
comp->type == arangodb::aql::NODE_TYPE_OPERATOR_BINARY_IN)) {
// operator type unsupported
return std::make_unique<arangodb::EmptyIndexIterator>(&_collection, trx);
}
// assume a.b == value
auto attrNode = comp->getMember(0);
auto valNode = comp->getMember(1);
if (!(attrNode->type == arangodb::aql::NODE_TYPE_ATTRIBUTE_ACCESS ||
attrNode->type == arangodb::aql::NODE_TYPE_EXPANSION)) {
// got value == a.b -> flip sides
std::swap(attrNode, valNode);
}
TRI_ASSERT(attrNode->type == arangodb::aql::NODE_TYPE_ATTRIBUTE_ACCESS ||
attrNode->type == arangodb::aql::NODE_TYPE_EXPANSION);
std::vector<arangodb::basics::AttributeName> attributes;
if (attrNode->type == arangodb::aql::NODE_TYPE_ATTRIBUTE_ACCESS) {
do {
attributes.emplace_back(std::string(attrNode->getStringValue(), attrNode->getStringLength()), false);
attrNode = attrNode->getMember(0);
} while (attrNode->type == arangodb::aql::NODE_TYPE_ATTRIBUTE_ACCESS);
std::reverse(attributes.begin(), attributes.end());
} else { // expansion
TRI_ASSERT(attrNode->type == arangodb::aql::NODE_TYPE_EXPANSION);
auto expNode = attrNode;
TRI_ASSERT(expNode->numMembers() >= 2);
auto left = expNode->getMember(0);
TRI_ASSERT(left->type == arangodb::aql::NODE_TYPE_ITERATOR);
attrNode = left->getMember(1);
TRI_ASSERT(attrNode->type == arangodb::aql::NODE_TYPE_ATTRIBUTE_ACCESS);
do {
attributes.emplace_back(std::string(attrNode->getStringValue(), attrNode->getStringLength()), false);
attrNode = attrNode->getMember(0);
} while (attrNode->type == arangodb::aql::NODE_TYPE_ATTRIBUTE_ACCESS);
attributes.front().shouldExpand = true;
std::reverse(attributes.begin(), attributes.end());
std::vector<arangodb::basics::AttributeName> attributesRight;
attrNode = expNode->getMember(1);
TRI_ASSERT(attrNode->type == arangodb::aql::NODE_TYPE_ATTRIBUTE_ACCESS ||
attrNode->type == arangodb::aql::NODE_TYPE_REFERENCE);
while (attrNode->type == arangodb::aql::NODE_TYPE_ATTRIBUTE_ACCESS) {
attributesRight.emplace_back(std::string(attrNode->getStringValue(), attrNode->getStringLength()), false);
attrNode = attrNode->getMember(0);
}
attributes.insert(attributes.end(), attributesRight.crbegin(), attributesRight.crend());
}
allAttributes.emplace_back(std::move(attributes), valNode);
}
size_t nullsCount = 0;
for (auto const& f : _fields) {
auto it = std::find_if(allAttributes.cbegin(), allAttributes.cend(), [&f] (auto const& attrs) {
return arangodb::basics::AttributeName::isIdentical(attrs.first, f, true);
});
if (it != allAttributes.cend()) {
while (nullsCount > 0) {
keys->add(VPackSlice::nullSlice());
--nullsCount;
}
it->second->toVelocyPackValue(*keys);
} else {
++nullsCount;
}
}
keys->close();
return std::make_unique<HashIndexIteratorMock>(&_collection, trx, this,
_hashData,
std::move(keys));
}
HashIndexMock(TRI_idx_iid_t iid, arangodb::LogicalCollection& collection, VPackSlice const& slice)
: arangodb::Index(iid, collection, slice), _hashData(_fields) {}
/// @brief the hash table for data
HashIndexMap _hashData;
}; // HashIndexMock
} // namespace
PhysicalCollectionMock::DocElement::DocElement(
std::shared_ptr<arangodb::velocypack::Buffer<uint8_t>> data, uint64_t docId)
: _data(data), _docId(docId) {}
arangodb::velocypack::Slice PhysicalCollectionMock::DocElement::data() const {
return arangodb::velocypack::Slice(_data->data());
}
std::shared_ptr<arangodb::velocypack::Buffer<uint8_t>> PhysicalCollectionMock::DocElement::rawData() const {
return _data;
}
void PhysicalCollectionMock::DocElement::swapBuffer(
std::shared_ptr<arangodb::velocypack::Buffer<uint8_t>>& newData) {
_data.swap(newData);
}
arangodb::LocalDocumentId PhysicalCollectionMock::DocElement::docId() const {
return arangodb::LocalDocumentId::create(_docId);
}
uint8_t const* PhysicalCollectionMock::DocElement::vptr() const {
return _data->data();
}
std::function<void()> PhysicalCollectionMock::before = []() -> void {};
PhysicalCollectionMock::PhysicalCollectionMock(arangodb::LogicalCollection& collection,
arangodb::velocypack::Slice const& info)
: PhysicalCollection(collection, info), _lastDocumentId{0} {}
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;
docs.reserve(_documents.size());
for (auto const& [key, doc] : _documents) {
docs.emplace_back(doc.docId(), doc.data());
}
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("hash")) {
index = HashIndexMock::make(id, _logicalCollection, info);
} else if (0 == type.compare(arangodb::iresearch::DATA_SOURCE_TYPE.name())) {
try {
if (arangodb::ServerState::instance()->isCoordinator()) {
index = arangodb::iresearch::IResearchLinkCoordinator::factory().instantiate(
_logicalCollection, info, id, false);
} else {
index = arangodb::iresearch::IResearchMMFilesLink::factory().instantiate(
_logicalCollection, info, id, false);
}
} catch (std::exception const& ex) {
// ignore the details of all errors here
LOG_DEVEL << "caught: " << ex.what();
}
}
if (!index) {
return nullptr;
}
asio_ns::io_context ioContext;
auto poster = [&ioContext](std::function<void()> fn) -> bool {
ioContext.post(fn);
return true;
};
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_HASH_INDEX) {
auto* l = dynamic_cast<HashIndexMock*>(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(index);
created = true;
res = trx.commit();
TRI_ASSERT(res.ok());
return index;
}
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(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, this->_logicalCollection, trx);
}
std::unique_ptr<arangodb::IndexIterator> PhysicalCollectionMock::getAnyIterator(
arangodb::transaction::Methods* trx) const {
before();
return std::make_unique<AllIteratorMock>(_documents, 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();
TRI_ASSERT(newSlice.isObject());
VPackSlice newKey = newSlice.get(arangodb::StaticStrings::KeyString);
if (newKey.isString()) {
if (_documents.find(arangodb::velocypack::StringRef{newKey}) != _documents.end()) {
return TRI_ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED;
}
}
arangodb::velocypack::Builder builder;
auto isEdgeCollection = (TRI_COL_TYPE_EDGE == _logicalCollection.type());
TRI_voc_rid_t revisionId;
auto res = newObjectForInsert(trx, newSlice, isEdgeCollection, builder,
options.isRestore, revisionId);
if (res.fail()) {
return res;
}
TRI_ASSERT(builder.slice().get(arangodb::StaticStrings::KeyString).isString());
arangodb::velocypack::StringRef key{builder.slice().get(arangodb::StaticStrings::KeyString)};
auto const& [ref, didInsert] =
_documents.emplace(key, DocElement{builder.steal(), ++_lastDocumentId});
TRI_ASSERT(didInsert);
result.setUnmanaged(ref->second.vptr());
TRI_ASSERT(result.revisionId() == revisionId);
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, ref->second.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_HASH_INDEX) {
auto* l = static_cast<HashIndexMock*>(index.get());
if (!l->insert(*trx, ref->second.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, ref->second.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, ref->second.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();
}
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(idx);
return true;
}
void PhysicalCollectionMock::prepareIndexes(arangodb::velocypack::Slice indexesSlice) {
before();
auto* engine = arangodb::EngineSelectorFeature::ENGINE;
auto& idxFactory = engine->indexFactory();
for (VPackSlice v : VPackArrayIterator(indexesSlice)) {
if (arangodb::basics::VelocyPackHelper::getBooleanValue(v, "error", false)) {
// We have an error here.
// Do not add index.
continue;
}
try {
auto idx = idxFactory.prepareIndexFromSlice(v, false, _logicalCollection, true);
if (!idx) {
continue;
}
if (!addIndex(idx)) {
return;
}
} catch (std::exception const&) {
// error is just ignored here
}
}
}
arangodb::Result PhysicalCollectionMock::read(arangodb::transaction::Methods*,
arangodb::velocypack::StringRef const& key,
arangodb::ManagedDocumentResult& result, bool) {
before();
auto it = _documents.find(key);
if (it != _documents.end()) {
result.setUnmanaged(it->second.vptr());
return arangodb::Result(TRI_ERROR_NO_ERROR);
}
return arangodb::Result(TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND);
}
arangodb::Result PhysicalCollectionMock::read(arangodb::transaction::Methods* trx,
arangodb::velocypack::Slice const& key,
arangodb::ManagedDocumentResult& result,
bool unusedFlag) {
return read(trx, arangodb::velocypack::StringRef(key), result, unusedFlag);
}
bool PhysicalCollectionMock::readDocument(arangodb::transaction::Methods* trx,
arangodb::LocalDocumentId const& token,
arangodb::ManagedDocumentResult& result) const {
before();
for (auto const& [key, doc] : _documents) {
if (doc.docId() == token) {
result.setUnmanaged(doc.vptr());
return true;
}
}
return false;
}
bool PhysicalCollectionMock::readDocumentWithCallback(
arangodb::transaction::Methods* trx, arangodb::LocalDocumentId const& token,
arangodb::IndexIterator::DocumentCallback const& cb) const {
before();
for (auto const& [key, doc] : _documents) {
if (doc.docId() == token) {
cb(token, doc.data());
return true;
}
}
return false;
}
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);
TRI_ASSERT(key.isString());
arangodb::velocypack::StringRef keyRef{key};
auto old = _documents.find(keyRef);
if (old != _documents.end()) {
previous.setUnmanaged(old->second.vptr());
_graveyard.emplace_back(old->second.rawData());
TRI_ASSERT(previous.revisionId() == TRI_ExtractRevisionId(old->second.data()));
_documents.erase(keyRef);
return arangodb::Result(TRI_ERROR_NO_ERROR); // assume document was removed
}
return arangodb::Result(TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND);
}
arangodb::Result PhysicalCollectionMock::update(
arangodb::transaction::Methods* trx, arangodb::velocypack::Slice const newSlice,
arangodb::ManagedDocumentResult& result, arangodb::OperationOptions& options,
bool lock, arangodb::ManagedDocumentResult& previous) {
return updateInternal(trx, newSlice, result, options, lock, previous, true);
}
arangodb::Result PhysicalCollectionMock::replace(
arangodb::transaction::Methods* trx, arangodb::velocypack::Slice const newSlice,
arangodb::ManagedDocumentResult& result, arangodb::OperationOptions& options,
bool lock, arangodb::ManagedDocumentResult& previous) {
return updateInternal(trx, newSlice, result, options, lock, previous, false);
}
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::updateInternal(
arangodb::transaction::Methods* trx, arangodb::velocypack::Slice const newSlice,
arangodb::ManagedDocumentResult& result, arangodb::OperationOptions& options,
bool lock, arangodb::ManagedDocumentResult& previous, bool isUpdate) {
auto key = newSlice.get(arangodb::StaticStrings::KeyString);
if (!key.isString()) {
return arangodb::Result(TRI_ERROR_ARANGO_DOCUMENT_HANDLE_BAD);
}
before();
arangodb::velocypack::StringRef keyRef{key};
auto it = _documents.find(keyRef);
if (it != _documents.end()) {
auto doc = it->second.data();
if (!options.ignoreRevs) {
TRI_voc_rid_t expectedRev = 0;
if (newSlice.isObject()) {
expectedRev = TRI_ExtractRevisionId(newSlice);
}
TRI_ASSERT(doc.isObject());
TRI_voc_rid_t oldRev = TRI_ExtractRevisionId(doc);
int res = checkRevision(trx, expectedRev, oldRev);
if (res != TRI_ERROR_NO_ERROR) {
return arangodb::Result(res);
}
}
arangodb::velocypack::Builder builder;
TRI_voc_rid_t revisionId = 0; // unused
auto isEdgeCollection = (TRI_COL_TYPE_EDGE == _logicalCollection.type());
if (isUpdate) {
arangodb::Result res =
mergeObjectsForUpdate(trx, doc, newSlice, isEdgeCollection,
options.mergeObjects, options.keepNull, builder,
options.isRestore, revisionId);
if (res.fail()) {
return res;
}
} else {
arangodb::Result res = newObjectForReplace(trx, doc, newSlice, isEdgeCollection,
builder, options.isRestore, revisionId);
if (res.fail()) {
return res;
}
}
auto nextBuffer = builder.steal();
// Set previous
previous.setUnmanaged(it->second.vptr());
TRI_ASSERT(previous.revisionId() == TRI_ExtractRevisionId(doc));
// swap with new data
// Replace the existing Buffer and nextBuffer
it->second.swapBuffer(nextBuffer);
// Put the now old buffer into the graveyour for previous to stay valid
_graveyard.emplace_back(nextBuffer);
result.setUnmanaged(it->second.vptr());
TRI_ASSERT(result.revisionId() != previous.revisionId());
return TRI_ERROR_NO_ERROR;
}
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::RecoveryState StorageEngineMock::recoveryStateResult =
arangodb::RecoveryState::DONE;
TRI_voc_tick_t StorageEngineMock::recoveryTickResult = 0;
std::function<void()> StorageEngineMock::recoveryTickCallback = []() -> void {};
/*static*/ std::string StorageEngineMock::versionFilenameResult;
StorageEngineMock::StorageEngineMock(arangodb::application_features::ApplicationServer& server)
: StorageEngine(server, "Mock", "",
std::unique_ptr<arangodb::IndexFactory>(new IndexFactoryMock())),
vocbaseCount(1),
_releasedTick(0) {}
arangodb::WalAccess const* StorageEngineMock::walAccess() const {
TRI_ASSERT(false);
return nullptr;
}
void StorageEngineMock::addOptimizerRules(arangodb::aql::OptimizerRulesFeature& /*feature*/) {
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();
auto res = view.properties(builder, arangodb::LogicalDataSource::Serialization::Persistence);
if (!res.ok()) {
return res;
}
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(arangodb::CreateDatabaseInfo&& info,
int& status) {
if (arangodb::ServerState::instance()->isCoordinator()) {
return std::make_unique<TRI_vocbase_t>(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_COORDINATOR,
std::move(info));
}
return std::make_unique<TRI_vocbase_t>(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL,
std::move(info));
}
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(
arangodb::transaction::ManagerFeature& feature) {
return std::make_unique<arangodb::transaction::Manager>(feature, /*keepData*/ false);
}
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, tid, 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();
auto res = view.properties(builder, arangodb::LogicalDataSource::Serialization::Persistence);
if (!res.ok()) {
return res;
}
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::dataPath() const {
before();
return ""; // no valid path filesystem persisted, return empty string
}
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();
}
arangodb::RecoveryState StorageEngineMock::recoveryState() {
return recoveryStateResult;
}
TRI_voc_tick_t StorageEngineMock::recoveryTick() {
if (recoveryTickCallback) {
recoveryTickCallback();
}
return recoveryTickResult;
}
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::CreateDatabaseInfo&& info,
bool isUpgrade) {
before();
auto new_info = info;
new_info.setId(++vocbaseCount);
return std::make_unique<TRI_vocbase_t>(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL,
std::move(new_info));
}
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) {
// NOOP
}
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, 0) {}
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;
}
}
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 = _transaction->vocbase()
.server()
.getFeature<arangodb::ClusterFeature>()
.clusterInfo();
_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_ARANGO_DATA_SOURCE_NOT_FOUND;
}
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, TRI_voc_tid_t tid,
arangodb::transaction::Options const& options)
: TransactionState(vocbase, tid, 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) {
++beginTransactionCount;
_hints = hints;
auto res = useCollections(nestingLevel());
if (!res.ok()) {
updateStatus(arangodb::transaction::Status::ABORTED);
// avoid use of TransactionManagerFeature::manager()->unregisterTransaction(...)
const_cast<TRI_voc_tid_t&>(_id) = 0;
return res;
}
if (nestingLevel() == 0) {
updateStatus(arangodb::transaction::Status::RUNNING);
}
return arangodb::Result();
}
arangodb::Result TransactionStateMock::commitTransaction(arangodb::transaction::Methods* trx) {
++commitTransactionCount;
if (nestingLevel() == 0) {
updateStatus(arangodb::transaction::Status::COMMITTED);
// avoid use of TransactionManagerFeature::manager()->unregisterTransaction(...)
const_cast<TRI_voc_tid_t&>(_id) = 0;
}
unuseCollections(nestingLevel());
return arangodb::Result();
}
bool TransactionStateMock::hasFailedOperations() const {
return false; // assume no failed operations
}
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------