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

256 lines
8.5 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 "PathEnumerator.h"
#include "Aql/AqlValue.h"
#include "Aql/PruneExpressionEvaluator.h"
#include "Basics/VelocyPackHelper.h"
#include "Graph/EdgeCursor.h"
#include "Graph/Traverser.h"
#include "Graph/TraverserCache.h"
#include "Graph/TraverserOptions.h"
using PathEnumerator = arangodb::traverser::PathEnumerator;
using DepthFirstEnumerator = arangodb::traverser::DepthFirstEnumerator;
using Traverser = arangodb::traverser::Traverser;
using TraverserOptions = arangodb::traverser::TraverserOptions;
PathEnumerator::PathEnumerator(Traverser* traverser, std::string const& startVertex,
TraverserOptions* opts)
: _traverser(traverser),
_isFirst(true),
_opts(opts),
_httpRequests(0) {
arangodb::velocypack::StringRef svId =
_opts->cache()->persistString(arangodb::velocypack::StringRef(startVertex));
// Guarantee that this vertex _id does not run away
_enumeratedPath.vertices.push_back(svId);
TRI_ASSERT(_enumeratedPath.vertices.size() == 1);
}
DepthFirstEnumerator::DepthFirstEnumerator(Traverser* traverser, std::string const& startVertex,
TraverserOptions* opts)
: PathEnumerator(traverser, startVertex, opts), _pruneNext(false) {}
DepthFirstEnumerator::~DepthFirstEnumerator() {}
bool DepthFirstEnumerator::next() {
if (_isFirst) {
_isFirst = false;
if (shouldPrune()) {
_pruneNext = true;
}
if (_opts->minDepth == 0) {
return true;
}
}
if (_enumeratedPath.vertices.empty()) {
// We are done;
return false;
}
while (true) {
if (_enumeratedPath.edges.size() < _opts->maxDepth && !_pruneNext) {
// We are not done with this path, so
// we reserve the cursor for next depth
auto cursor = _opts->nextCursor(arangodb::velocypack::StringRef(
_enumeratedPath.vertices.back()),
_enumeratedPath.edges.size());
if (cursor != nullptr) {
incHttpRequests(cursor->httpRequests());
_edgeCursors.emplace(cursor);
}
} else {
if (!_enumeratedPath.edges.empty()) {
// This path is at the end. cut the last step
_enumeratedPath.vertices.pop_back();
_enumeratedPath.edges.pop_back();
}
}
_pruneNext = false;
bool foundPath = false;
auto callback = [&](graph::EdgeDocumentToken&& eid, VPackSlice const& edge, size_t cursorId) {
if (_opts->hasEdgeFilter(_enumeratedPath.edges.size(), cursorId)) {
VPackSlice e = edge;
if (edge.isString()) {
e = _opts->cache()->lookupToken(eid);
}
if (!_traverser->edgeMatchesConditions(
e, arangodb::velocypack::StringRef(_enumeratedPath.vertices.back()),
_enumeratedPath.edges.size(), cursorId)) {
// This edge does not pass the filtering
return;
}
}
if (_opts->uniqueEdges == TraverserOptions::UniquenessLevel::PATH) {
if (ServerState::instance()->isCoordinator()) {
for (auto const& it : _enumeratedPath.edges) {
// We might already have this edge on the path.
if (it.equalsCoordinator(eid)) {
return;
}
}
} else {
for (auto const& it : _enumeratedPath.edges) {
if (it.equalsLocal(eid)) {
return;
}
}
}
}
// We have to check if edge and vertex is valid
if (_traverser->getVertex(edge, _enumeratedPath.vertices)) {
// case both are valid.
if (_opts->uniqueVertices == TraverserOptions::UniquenessLevel::PATH) {
auto& e = _enumeratedPath.vertices.back();
bool foundOnce = false;
for (auto const& it : _enumeratedPath.vertices) {
if (foundOnce) {
foundOnce = false; // if we leave with foundOnce == false we
// found the vertex earlier
break;
}
if (it == e) {
foundOnce = true;
}
}
if (!foundOnce) {
// We found it and it was not the last element (expected)
// This vertex is allready on the path
_enumeratedPath.vertices.pop_back();
return;
}
}
_enumeratedPath.edges.push_back(std::move(eid));
foundPath = true;
return;
}
// Vertex Invalid. Do neither insert edge nor vertex
};
while (!_edgeCursors.empty()) {
TRI_ASSERT(_edgeCursors.size() == _enumeratedPath.edges.size() + 1);
auto& cursor = _edgeCursors.top();
if (cursor->next(callback)) {
if (foundPath) {
if (shouldPrune()) {
_pruneNext = true;
}
if (_enumeratedPath.edges.size() < _opts->minDepth) {
// We have a valid prefix, but do NOT return this path
break;
}
return true;
}
} else {
// cursor is empty.
_edgeCursors.pop();
if (!_enumeratedPath.edges.empty()) {
_enumeratedPath.edges.pop_back();
_enumeratedPath.vertices.pop_back();
}
}
}
if (_edgeCursors.empty()) {
// If we get here all cursors are exhausted.
_enumeratedPath.edges.clear();
_enumeratedPath.vertices.clear();
return false;
}
} // while (true)
}
arangodb::aql::AqlValue DepthFirstEnumerator::lastVertexToAqlValue() {
return _traverser->fetchVertexData(
arangodb::velocypack::StringRef(_enumeratedPath.vertices.back()));
}
arangodb::aql::AqlValue DepthFirstEnumerator::lastEdgeToAqlValue() {
if (_enumeratedPath.edges.empty()) {
return arangodb::aql::AqlValue(arangodb::aql::AqlValueHintNull());
}
// FIXME: add some asserts back into this
// TRI_ASSERT(_enumeratedPath.edges.back() != nullptr);
return _opts->cache()->fetchEdgeAqlResult(_enumeratedPath.edges.back());
}
VPackSlice DepthFirstEnumerator::pathToSlice(VPackBuilder& result) {
result.clear();
result.openObject();
result.add(VPackValue("edges"));
result.openArray();
for (auto const& it : _enumeratedPath.edges) {
// TRI_ASSERT(it != nullptr);
_opts->cache()->insertEdgeIntoResult(it, result);
}
result.close();
result.add(VPackValue("vertices"));
result.openArray();
for (auto const& it : _enumeratedPath.vertices) {
_traverser->addVertexToVelocyPack(VPackStringRef(it), result);
}
result.close();
result.close();
TRI_ASSERT(result.isClosed());
return result.slice();
}
arangodb::aql::AqlValue DepthFirstEnumerator::pathToAqlValue(VPackBuilder& result) {
return arangodb::aql::AqlValue(pathToSlice(result));
}
bool DepthFirstEnumerator::shouldPrune() {
// We need to call prune here
if (_opts->usesPrune()) {
// evaluator->evaluate() might access these, so they have to live long
// enough. To make that perfectly clear, I added a scope.
transaction::BuilderLeaser pathBuilder(_opts->trx());
aql::AqlValue vertex, edge;
aql::AqlValueGuard vertexGuard{vertex, true}, edgeGuard{edge, true};
{
aql::PruneExpressionEvaluator* evaluator = _opts->getPruneEvaluator();
if (evaluator->needsVertex()) {
vertex = lastVertexToAqlValue();
evaluator->injectVertex(vertex.slice());
}
if (evaluator->needsEdge()) {
edge = lastEdgeToAqlValue();
evaluator->injectEdge(edge.slice());
}
if (evaluator->needsPath()) {
VPackSlice path = pathToSlice(*pathBuilder.get());
evaluator->injectPath(path);
}
return evaluator->evaluate();
}
}
return false;
}