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

222 lines
7.1 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 "BreadthFirstEnumerator.h"
#include "VocBase/Traverser.h"
#include "VocBase/TraverserCache.h"
#include "VocBase/TraverserOptions.h"
#include <velocypack/Slice.h>
#include <velocypack/velocypack-aliases.h>
using namespace arangodb;
using namespace arangodb::traverser;
using BreadthFirstEnumerator = arangodb::graph::BreadthFirstEnumerator;
BreadthFirstEnumerator::PathStep::PathStep(StringRef const vertex)
: sourceIdx(0),
vertex(vertex) {
}
BreadthFirstEnumerator::PathStep::PathStep(size_t sourceIdx,
StringRef const edge,
StringRef const vertex) :
sourceIdx(sourceIdx), edge(edge), vertex(vertex) {
}
BreadthFirstEnumerator::BreadthFirstEnumerator(Traverser* traverser,
VPackSlice startVertex,
TraverserOptions* opts)
: PathEnumerator(traverser, startVertex.copyString(), opts),
_schreierIndex(1),
_lastReturned(0),
_currentDepth(0),
_toSearchPos(0) {
_schreier.reserve(32);
StringRef startVId = _opts->cache()->persistString(StringRef(startVertex));
_schreier.emplace_back(startVId);
_toSearch.emplace_back(NextStep(0));
}
bool BreadthFirstEnumerator::next() {
if (_isFirst) {
_isFirst = false;
if (_opts->minDepth == 0) {
return true;
}
}
_lastReturned++;
if (_lastReturned < _schreierIndex) {
// We still have something on our stack.
// Paths have been read but not returned.
return true;
}
if (_opts->maxDepth == 0) {
// Short circuit.
// We cannot find any path of length 0 or less
return false;
}
// Avoid large call stacks.
// Loop will be left if we are either finished
// with searching.
// Or we found vertices in the next depth for
// a vertex.
while (true) {
if (_toSearchPos >= _toSearch.size()) {
// This depth is done. GoTo next
if (_nextDepth.empty()) {
// That's it. we are done.
return false;
}
// Save copies:
// We clear current
// we swap current and next.
// So now current is filled
// and next is empty.
_toSearch.clear();
_toSearchPos = 0;
_toSearch.swap(_nextDepth);
_currentDepth++;
TRI_ASSERT(_toSearchPos < _toSearch.size());
TRI_ASSERT(_nextDepth.empty());
TRI_ASSERT(_currentDepth < _opts->maxDepth);
}
// This access is always safe.
// If not it should have bailed out before.
TRI_ASSERT(_toSearchPos < _toSearch.size());
_tmpEdges.clear();
auto const nextIdx = _toSearch[_toSearchPos++].sourceIdx;
auto const nextVertex = _schreier[nextIdx].vertex;
StringRef vId;
std::unique_ptr<arangodb::traverser::EdgeCursor> cursor(_opts->nextCursor(_traverser->mmdr(), nextVertex, _currentDepth));
if (cursor != nullptr) {
bool shouldReturnPath = _currentDepth + 1 >= _opts->minDepth;
bool didInsert = false;
auto callback = [&] (arangodb::StringRef const& eid, VPackSlice e, size_t cursorIdx) -> void {
if (_opts->uniqueEdges ==
TraverserOptions::UniquenessLevel::GLOBAL) {
if (_returnedEdges.find(eid) == _returnedEdges.end()) {
// Edge not yet visited. Mark and continue.
// TODO FIXME the edge will run out of scope
_returnedEdges.emplace(eid);
} else {
// Edge filtered due to unique_constraint
_traverser->_filteredPaths++;
return;
}
}
if (!_traverser->edgeMatchesConditions(e, nextVertex,
_currentDepth,
cursorIdx)) {
return;
}
if (_traverser->getSingleVertex(e, nextVertex, _currentDepth, vId)) {
_schreier.emplace_back(nextIdx, eid, vId);
if (_currentDepth < _opts->maxDepth - 1) {
_nextDepth.emplace_back(NextStep(_schreierIndex));
}
_schreierIndex++;
didInsert = true;
}
};
cursor->readAll(callback);
if (!shouldReturnPath) {
_lastReturned = _schreierIndex;
didInsert = false;
}
if (didInsert) {
// We exit the loop here.
// _schreierIndex is moved forward
break;
}
}
// Nothing found for this vertex.
// _toSearchPos is increased so
// we are not stuck in an endless loop
}
// _lastReturned points to the last used
// entry. We compute the path to it.
return true;
}
arangodb::aql::AqlValue BreadthFirstEnumerator::lastVertexToAqlValue() {
TRI_ASSERT(_lastReturned < _schreier.size());
PathStep const& current = _schreier[_lastReturned];
return _traverser->fetchVertexData(StringRef(current.vertex));
}
arangodb::aql::AqlValue BreadthFirstEnumerator::lastEdgeToAqlValue() {
TRI_ASSERT(_lastReturned < _schreier.size());
if (_lastReturned == 0) {
// This is the first Vertex. No Edge Pointing to it
return arangodb::aql::AqlValue(arangodb::basics::VelocyPackHelper::NullValue());
}
PathStep const& current = _schreier[_lastReturned];
return _traverser->fetchEdgeData(StringRef(current.edge));
}
arangodb::aql::AqlValue BreadthFirstEnumerator::pathToAqlValue(
arangodb::velocypack::Builder& result) {
// TODO make deque class variable
std::deque<size_t> fullPath;
size_t cur = _lastReturned;
while (cur != 0) {
// Walk backwards through the path and push everything found on the local stack
fullPath.emplace_front(cur);
cur = _schreier[cur].sourceIdx;
}
result.clear();
result.openObject();
result.add(VPackValue("edges"));
result.openArray();
for (auto const& idx : fullPath) {
_traverser->addEdgeToVelocyPack(StringRef(_schreier[idx].edge), result);
}
result.close(); // edges
result.add(VPackValue("vertices"));
result.openArray();
// Always add the start vertex
_traverser->addVertexToVelocyPack(_schreier[0].vertex, result);
for (auto const& idx : fullPath) {
_traverser->addVertexToVelocyPack(_schreier[idx].vertex, result);
}
result.close(); // vertices
result.close();
return arangodb::aql::AqlValue(result.slice());
}