mirror of https://gitee.com/bigwinds/arangodb
256 lines
8.5 KiB
C++
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;
|
|
}
|