1
0
Fork 0
arangodb/arangod/Graph/TraverserOptions.cpp

565 lines
19 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2014-2016 ArangoDB GmbH, Cologne, Germany
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// 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 ArangoDB GmbH, Cologne, Germany
///
/// @author Michael Hackstein
////////////////////////////////////////////////////////////////////////////////
#include "TraverserOptions.h"
#include "Aql/Ast.h"
#include "Aql/Expression.h"
#include "Aql/PruneExpressionEvaluator.h"
#include "Aql/Query.h"
#include "Basics/StringUtils.h"
#include "Basics/tryEmplaceHelper.h"
#include "Basics/VelocyPackHelper.h"
#include "Cluster/ClusterEdgeCursor.h"
#include "Graph/SingleServerTraverser.h"
#include "Indexes/Index.h"
#include <velocypack/Iterator.h>
#include <velocypack/StringRef.h>
#include <velocypack/velocypack-aliases.h>
using namespace arangodb;
using namespace arangodb::graph;
using namespace arangodb::transaction;
using namespace arangodb::traverser;
using VPackHelper = arangodb::basics::VelocyPackHelper;
TraverserOptions::TraverserOptions(aql::Query* query)
: BaseOptions(query),
_baseVertexExpression(nullptr),
_traverser(nullptr),
minDepth(1),
maxDepth(1),
useBreadthFirst(false),
uniqueVertices(UniquenessLevel::NONE),
uniqueEdges(UniquenessLevel::PATH) {}
TraverserOptions::TraverserOptions(aql::Query* query, VPackSlice const& obj)
: BaseOptions(query),
_baseVertexExpression(nullptr),
_traverser(nullptr),
minDepth(1),
maxDepth(1),
useBreadthFirst(false),
uniqueVertices(UniquenessLevel::NONE),
uniqueEdges(UniquenessLevel::PATH) {
TRI_ASSERT(obj.isObject());
#ifdef ARANGODB_ENABLE_MAINTAINER_MODE
VPackSlice type = obj.get("type");
TRI_ASSERT(type.isString());
TRI_ASSERT(type.isEqualString("traversal"));
#endif
minDepth = VPackHelper::getNumericValue<uint64_t>(obj, "minDepth", 1);
maxDepth = VPackHelper::getNumericValue<uint64_t>(obj, "maxDepth", 1);
TRI_ASSERT(minDepth <= maxDepth);
useBreadthFirst = VPackHelper::getBooleanValue(obj, "bfs", false);
std::string tmp = VPackHelper::getStringValue(obj, "uniqueVertices", "");
if (tmp == "path") {
uniqueVertices = TraverserOptions::UniquenessLevel::PATH;
} else if (tmp == "global") {
if (!useBreadthFirst) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER,
"uniqueVertices: 'global' is only "
"supported, with bfs: true due to "
"unpredictable results.");
}
uniqueVertices = TraverserOptions::UniquenessLevel::GLOBAL;
} else {
uniqueVertices = TraverserOptions::UniquenessLevel::NONE;
}
tmp = VPackHelper::getStringValue(obj, "uniqueEdges", "");
if (tmp == "none") {
uniqueEdges = TraverserOptions::UniquenessLevel::NONE;
} else if (tmp == "global") {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER,
"uniqueEdges: 'global' is not supported, "
"due to unpredictable results. Use 'path' "
"or 'none' instead");
} else {
uniqueEdges = TraverserOptions::UniquenessLevel::PATH;
}
}
arangodb::traverser::TraverserOptions::TraverserOptions(arangodb::aql::Query* query,
VPackSlice info, VPackSlice collections)
: BaseOptions(query, info, collections),
_baseVertexExpression(nullptr),
_traverser(nullptr),
minDepth(1),
maxDepth(1),
useBreadthFirst(false),
uniqueVertices(UniquenessLevel::NONE),
uniqueEdges(UniquenessLevel::PATH) {
#ifdef ARANGODB_ENABLE_MAINTAINER_MODE
VPackSlice type = info.get("type");
TRI_ASSERT(type.isString());
TRI_ASSERT(type.isEqualString("traversal"));
#endif
// NOTE collections is an array of arrays of strings
VPackSlice read = info.get("minDepth");
if (!read.isInteger()) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER,
"The options require a minDepth");
}
minDepth = read.getNumber<uint64_t>();
read = info.get("maxDepth");
if (!read.isInteger()) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER,
"The options require a maxDepth");
}
maxDepth = read.getNumber<uint64_t>();
read = info.get("bfs");
if (!read.isBoolean()) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER,
"The options require a bfs");
}
useBreadthFirst = read.getBool();
read = info.get("uniqueVertices");
if (!read.isInteger()) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER,
"The options require a uniqueVertices");
}
size_t i = read.getNumber<size_t>();
switch (i) {
case 0:
uniqueVertices = UniquenessLevel::NONE;
break;
case 1:
uniqueVertices = UniquenessLevel::PATH;
break;
case 2:
uniqueVertices = UniquenessLevel::GLOBAL;
break;
default:
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER,
"The options require a uniqueVertices");
}
read = info.get("uniqueEdges");
if (!read.isInteger()) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER,
"The options require a uniqueEdges");
}
i = read.getNumber<size_t>();
switch (i) {
case 0:
uniqueEdges = UniquenessLevel::NONE;
break;
case 1:
uniqueEdges = UniquenessLevel::PATH;
break;
default:
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER,
"The options require a uniqueEdges");
}
read = info.get("depthLookupInfo");
if (!read.isNone()) {
if (!read.isObject()) {
THROW_ARANGO_EXCEPTION_MESSAGE(
TRI_ERROR_BAD_PARAMETER,
"The options require depthLookupInfo to be an object");
}
_depthLookupInfo.reserve(read.length());
size_t length = collections.length();
for (auto const& depth : VPackObjectIterator(read)) {
uint64_t d = basics::StringUtils::uint64(depth.key.copyString());
auto [it, emplaced] = _depthLookupInfo.try_emplace(d, std::vector<LookupInfo>());
TRI_ASSERT(emplaced);
VPackSlice list = depth.value;
TRI_ASSERT(length == list.length());
it->second.reserve(length);
for (size_t j = 0; j < length; ++j) {
it->second.emplace_back(query, list.at(j), collections.at(j));
}
}
}
read = info.get("vertexExpressions");
if (!read.isNone()) {
if (!read.isObject()) {
THROW_ARANGO_EXCEPTION_MESSAGE(
TRI_ERROR_BAD_PARAMETER,
"The options require vertexExpressions to be an object");
}
_vertexExpressions.reserve(read.length());
for (auto const& info : VPackObjectIterator(read)) {
uint64_t d = basics::StringUtils::uint64(info.key.copyString());
#ifdef ARANGODB_ENABLE_MAINTAINER_MODE
bool emplaced = false;
std::tie(std::ignore, emplaced) = _vertexExpressions.try_emplace(d, new aql::Expression(query->plan(),
query->ast(), info.value));
TRI_ASSERT(emplaced);
#else
_vertexExpressions.try_emplace(
d,
arangodb::lazyConstruct([&]{
return new aql::Expression(query->plan(), query->ast(), info.value);
})
);
#endif
}
}
read = info.get("baseVertexExpression");
if (!read.isNone()) {
if (!read.isObject()) {
THROW_ARANGO_EXCEPTION_MESSAGE(
TRI_ERROR_BAD_PARAMETER,
"The options require vertexExpressions to be an object");
}
_baseVertexExpression = new aql::Expression(query->plan(), query->ast(), read);
}
// Check for illegal option combination:
TRI_ASSERT(uniqueEdges != TraverserOptions::UniquenessLevel::GLOBAL);
TRI_ASSERT(uniqueVertices != TraverserOptions::UniquenessLevel::GLOBAL || useBreadthFirst);
}
arangodb::traverser::TraverserOptions::TraverserOptions(TraverserOptions const& other)
: BaseOptions(other._query),
_baseVertexExpression(nullptr),
_traverser(nullptr),
minDepth(other.minDepth),
maxDepth(other.maxDepth),
useBreadthFirst(other.useBreadthFirst),
uniqueVertices(other.uniqueVertices),
uniqueEdges(other.uniqueEdges) {
TRI_ASSERT(other._baseLookupInfos.empty());
TRI_ASSERT(other._depthLookupInfo.empty());
TRI_ASSERT(other._vertexExpressions.empty());
TRI_ASSERT(other._tmpVar == nullptr);
TRI_ASSERT(other._baseVertexExpression == nullptr);
// Check for illegal option combination:
TRI_ASSERT(uniqueEdges != TraverserOptions::UniquenessLevel::GLOBAL);
TRI_ASSERT(uniqueVertices != TraverserOptions::UniquenessLevel::GLOBAL || useBreadthFirst);
}
TraverserOptions::~TraverserOptions() {
for (auto& pair : _vertexExpressions) {
delete pair.second;
}
delete _baseVertexExpression;
}
void TraverserOptions::toVelocyPack(VPackBuilder& builder) const {
VPackObjectBuilder guard(&builder);
builder.add("minDepth", VPackValue(minDepth));
builder.add("maxDepth", VPackValue(maxDepth));
builder.add("bfs", VPackValue(useBreadthFirst));
switch (uniqueVertices) {
case TraverserOptions::UniquenessLevel::NONE:
builder.add("uniqueVertices", VPackValue("none"));
break;
case TraverserOptions::UniquenessLevel::PATH:
builder.add("uniqueVertices", VPackValue("path"));
break;
case TraverserOptions::UniquenessLevel::GLOBAL:
builder.add("uniqueVertices", VPackValue("global"));
break;
}
switch (uniqueEdges) {
case TraverserOptions::UniquenessLevel::NONE:
builder.add("uniqueEdges", VPackValue("none"));
break;
case TraverserOptions::UniquenessLevel::PATH:
builder.add("uniqueEdges", VPackValue("path"));
break;
case TraverserOptions::UniquenessLevel::GLOBAL:
builder.add("uniqueEdges", VPackValue("global"));
break;
}
builder.add("type", VPackValue("traversal"));
}
void TraverserOptions::toVelocyPackIndexes(VPackBuilder& builder) const {
VPackObjectBuilder guard(&builder);
// base indexes
builder.add("base", VPackValue(VPackValueType::Array));
for (auto const& it : _baseLookupInfos) {
for (auto const& it2 : it.idxHandles) {
it2.getIndex()->toVelocyPack(builder, Index::makeFlags(Index::Serialize::Basics, Index::Serialize::Estimates));
}
}
builder.close();
// depth lookup indexes
builder.add("levels", VPackValue(VPackValueType::Object));
for (auto const& it : _depthLookupInfo) {
builder.add(VPackValue(std::to_string(it.first)));
builder.add(VPackValue(VPackValueType::Array));
for (auto const& it2 : it.second) {
for (auto const& it3 : it2.idxHandles) {
it3.getIndex()->toVelocyPack(builder, Index::makeFlags(Index::Serialize::Basics, Index::Serialize::Estimates));
}
}
builder.close();
}
builder.close();
}
void TraverserOptions::buildEngineInfo(VPackBuilder& result) const {
result.openObject();
injectEngineInfo(result);
result.add("type", VPackValue("traversal"));
result.add("minDepth", VPackValue(minDepth));
result.add("maxDepth", VPackValue(maxDepth));
result.add("bfs", VPackValue(useBreadthFirst));
result.add(VPackValue("uniqueVertices"));
switch (uniqueVertices) {
case UniquenessLevel::NONE:
result.add(VPackValue(0));
break;
case UniquenessLevel::PATH:
result.add(VPackValue(1));
break;
case UniquenessLevel::GLOBAL:
result.add(VPackValue(2));
break;
}
result.add(VPackValue("uniqueEdges"));
switch (uniqueEdges) {
case UniquenessLevel::NONE:
result.add(VPackValue(0));
break;
case UniquenessLevel::PATH:
result.add(VPackValue(1));
break;
case UniquenessLevel::GLOBAL:
result.add(VPackValue(2));
break;
}
if (!_depthLookupInfo.empty()) {
result.add(VPackValue("depthLookupInfo"));
result.openObject();
for (auto const& pair : _depthLookupInfo) {
result.add(VPackValue(basics::StringUtils::itoa(pair.first)));
result.openArray();
for (auto const& it : pair.second) {
it.buildEngineInfo(result);
}
result.close();
}
result.close();
}
if (!_vertexExpressions.empty()) {
result.add(VPackValue("vertexExpressions"));
result.openObject();
for (auto const& pair : _vertexExpressions) {
result.add(VPackValue(basics::StringUtils::itoa(pair.first)));
result.openObject();
result.add(VPackValue("expression"));
pair.second->toVelocyPack(result, true);
result.close();
}
result.close();
}
if (_baseVertexExpression != nullptr) {
result.add(VPackValue("baseVertexExpression"));
result.openObject();
result.add(VPackValue("expression"));
_baseVertexExpression->toVelocyPack(result, true);
result.close();
}
result.close();
}
void TraverserOptions::addDepthLookupInfo(aql::ExecutionPlan* plan,
std::string const& collectionName,
std::string const& attributeName,
aql::AstNode* condition, uint64_t depth) {
auto& list = _depthLookupInfo[depth];
injectLookupInfoInList(list, plan, collectionName, attributeName, condition);
}
bool TraverserOptions::vertexHasFilter(uint64_t depth) const {
if (_baseVertexExpression != nullptr) {
return true;
}
return _vertexExpressions.find(depth) != _vertexExpressions.end();
}
bool TraverserOptions::hasEdgeFilter(int64_t depth, size_t cursorId) const {
if (_isCoordinator) {
// The Coordinator never checks conditions. The DBServer is responsible!
return false;
}
arangodb::aql::Expression* expression = nullptr;
auto specific = _depthLookupInfo.find(depth);
if (specific != _depthLookupInfo.end()) {
TRI_ASSERT(!specific->second.empty());
TRI_ASSERT(specific->second.size() > cursorId);
expression = specific->second[cursorId].expression.get();
} else {
bool unused;
expression = getEdgeExpression(cursorId, unused);
}
return expression != nullptr;
}
bool TraverserOptions::evaluateEdgeExpression(arangodb::velocypack::Slice edge,
arangodb::velocypack::StringRef vertexId,
uint64_t depth, size_t cursorId) const {
arangodb::aql::Expression* expression = nullptr;
auto specific = _depthLookupInfo.find(depth);
auto needToInjectVertex = false;
if (specific != _depthLookupInfo.end()) {
TRI_ASSERT(!specific->second.empty());
TRI_ASSERT(specific->second.size() > cursorId);
expression = specific->second[cursorId].expression.get();
needToInjectVertex = !specific->second[cursorId].conditionNeedUpdate;
} else {
expression = getEdgeExpression(cursorId, needToInjectVertex);
}
if (expression == nullptr) {
return true;
}
if (needToInjectVertex) {
// If we have to inject the vertex value it has to be within
// the last member of the condition.
// We only get into this case iff the index used does
// not cover _from resp. _to.
// inject _from/_to value
auto node = expression->nodeForModification();
TRI_ASSERT(node->numMembers() > 0);
auto dirCmp = node->getMemberUnchecked(node->numMembers() - 1);
TRI_ASSERT(dirCmp->type == aql::NODE_TYPE_OPERATOR_BINARY_EQ);
TRI_ASSERT(dirCmp->numMembers() == 2);
auto idNode = dirCmp->getMemberUnchecked(1);
TRI_ASSERT(idNode->type == aql::NODE_TYPE_VALUE);
TRI_ASSERT(idNode->isValueType(aql::VALUE_TYPE_STRING));
idNode->stealComputedValue();
idNode->setStringValue(vertexId.data(), vertexId.length());
}
if (edge.isExternal()) {
edge = edge.resolveExternal();
}
return evaluateExpression(expression, edge);
}
bool TraverserOptions::evaluateVertexExpression(arangodb::velocypack::Slice vertex,
uint64_t depth) const {
arangodb::aql::Expression* expression = nullptr;
auto specific = _vertexExpressions.find(depth);
if (specific != _vertexExpressions.end()) {
expression = specific->second;
} else {
expression = _baseVertexExpression;
}
if (vertex.isExternal()) {
vertex = vertex.resolveExternal();
}
return evaluateExpression(expression, vertex);
}
EdgeCursor* arangodb::traverser::TraverserOptions::nextCursor(
arangodb::velocypack::StringRef vid, uint64_t depth) {
if (_isCoordinator) {
return nextCursorCoordinator(vid, depth);
}
auto specific = _depthLookupInfo.find(depth);
if (specific != _depthLookupInfo.end()) {
return nextCursorLocal(vid, specific->second);
}
return nextCursorLocal(vid, _baseLookupInfos);
}
EdgeCursor* TraverserOptions::nextCursorCoordinator(arangodb::velocypack::StringRef vid,
uint64_t depth) {
TRI_ASSERT(_traverser != nullptr);
auto cursor = std::make_unique<ClusterEdgeCursor>(vid, depth, this);
return cursor.release();
}
void TraverserOptions::linkTraverser(ClusterTraverser* trav) {
_traverser = trav;
}
double TraverserOptions::estimateCost(size_t& nrItems) const {
size_t count = 1;
double cost = 0;
size_t baseCreateItems = 0;
double baseCost = costForLookupInfoList(_baseLookupInfos, baseCreateItems);
for (uint64_t depth = 0; depth < maxDepth && depth < 10; ++depth) {
auto liList = _depthLookupInfo.find(depth);
if (liList == _depthLookupInfo.end()) {
// No LookupInfo for this depth use base
cost += baseCost * count;
count *= baseCreateItems;
} else {
size_t createItems = 0;
double depthCost = costForLookupInfoList(liList->second, createItems);
cost += depthCost * count;
count *= createItems;
}
}
if (maxDepth > 10) {
// We have a too high depth this cost will be pruned anyway
cost *= (maxDepth - 10) * 10;
count *= (maxDepth - 10) * 10;
}
nrItems = count;
return cost;
}
void TraverserOptions::activatePrune(std::vector<aql::Variable const*> const&& vars,
std::vector<aql::RegisterId> const&& regs,
size_t vertexVarIdx, size_t edgeVarIdx,
size_t pathVarIdx, aql::Expression* expr) {
_pruneExpression =
std::make_unique<aql::PruneExpressionEvaluator>(_trx, _query, std::move(vars),
std::move(regs), vertexVarIdx,
edgeVarIdx, pathVarIdx, expr);
}