mirror of https://gitee.com/bigwinds/arangodb
288 lines
8.9 KiB
C++
288 lines
8.9 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 "VocBase/Traverser.h"
|
|
|
|
using DepthFirstEnumerator = arangodb::traverser::DepthFirstEnumerator;
|
|
using BreadthFirstEnumerator = arangodb::traverser::BreadthFirstEnumerator;
|
|
using Traverser = arangodb::traverser::Traverser;
|
|
using TraverserOptions = arangodb::traverser::TraverserOptions;
|
|
|
|
bool DepthFirstEnumerator::next() {
|
|
if (_isFirst) {
|
|
_isFirst = false;
|
|
// Initialze the first cursor
|
|
_opts->nextCursor(_enumeratedPath.vertices.back(), 0);
|
|
if (_opts->minDepth == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
if (_enumeratedPath.vertices.empty()) {
|
|
// We are done;
|
|
return false;
|
|
}
|
|
if (_enumeratedPath.edges.size() < _opts->maxDepth) {
|
|
// We are not done with this path, so
|
|
// we reserve the cursor for next depth
|
|
auto cursor = _opts->nextCursor(_enumeratedPath.vertices.back(),
|
|
_enumeratedPath.vertices.size());
|
|
if (cursor != nullptr) {
|
|
_edgeCursors.emplace(cursor);
|
|
}
|
|
} else {
|
|
// This path is at the end. cut the last step
|
|
_enumeratedPath.vertices.pop_back();
|
|
_enumeratedPath.edges.pop_back();
|
|
}
|
|
|
|
while (!_edgeCursors.empty()) {
|
|
auto cursor = _edgeCursors.top();
|
|
if (cursor->next(_enumeratedPath.edges)) {
|
|
#warning not yet finished
|
|
// TODO UNIQUE_PATH
|
|
// We have to check if edge and vertex is valid
|
|
if (_traverser->getVertex(_enumeratedPath.edges.back(),
|
|
_enumeratedPath.vertices)) {
|
|
// case both are valid.
|
|
// TODO UNIQUE_PATH
|
|
return true;
|
|
}
|
|
// Vertex Invalid. Revoke edge
|
|
_enumeratedPath.edges.pop_back();
|
|
} else {
|
|
// cursor is empty.
|
|
_edgeCursors.pop();
|
|
}
|
|
}
|
|
// If we get here all cursors are exhausted.
|
|
_enumeratedPath.edges.clear();
|
|
_enumeratedPath.vertices.clear();
|
|
return false;
|
|
}
|
|
|
|
arangodb::aql::AqlValue DepthFirstEnumerator::lastVertexToAqlValue() {
|
|
return _traverser->fetchVertexData(_enumeratedPath.vertices.back());
|
|
}
|
|
|
|
arangodb::aql::AqlValue DepthFirstEnumerator::lastEdgeToAqlValue() {
|
|
if (_enumeratedPath.edges.empty()) {
|
|
return arangodb::aql::AqlValue(arangodb::basics::VelocyPackHelper::NullValue());
|
|
}
|
|
return _traverser->fetchEdgeData(_enumeratedPath.edges.back());
|
|
}
|
|
|
|
arangodb::aql::AqlValue DepthFirstEnumerator::pathToAqlValue(arangodb::velocypack::Builder& result) {
|
|
result.clear();
|
|
result.openObject();
|
|
result.add(VPackValue("edges"));
|
|
result.openArray();
|
|
for (auto const& it : _enumeratedPath.edges) {
|
|
_traverser->addEdgeToVelocyPack(it, result);
|
|
}
|
|
result.close();
|
|
result.add(VPackValue("vertices"));
|
|
result.openArray();
|
|
for (auto const& it : _enumeratedPath.vertices) {
|
|
_traverser->addVertexToVelocyPack(it, result);
|
|
}
|
|
result.close();
|
|
result.close();
|
|
return arangodb::aql::AqlValue(result.slice());
|
|
}
|
|
|
|
BreadthFirstEnumerator::BreadthFirstEnumerator(Traverser* traverser,
|
|
VPackSlice startVertex,
|
|
TraverserOptions const* opts)
|
|
: PathEnumerator(traverser, startVertex, opts),
|
|
_schreierIndex(1),
|
|
_lastReturned(0),
|
|
_currentDepth(0),
|
|
_toSearchPos(0) {
|
|
_schreier.reserve(32);
|
|
auto step = std::make_unique<PathStep>(startVertex);
|
|
_schreier.emplace_back(step.get());
|
|
step.release();
|
|
|
|
_toSearch.emplace_back(NextStep(0));
|
|
}
|
|
|
|
bool BreadthFirstEnumerator::next() {
|
|
if (_isFirst) {
|
|
_isFirst = false;
|
|
if (_opts->minDepth == 0) {
|
|
computeEnumeratedPath(_lastReturned++);
|
|
return true;
|
|
}
|
|
_lastReturned++;
|
|
}
|
|
|
|
if (_lastReturned < _schreierIndex) {
|
|
// We still have something on our stack.
|
|
// Paths have been read but not returned.
|
|
computeEnumeratedPath(_lastReturned++);
|
|
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.
|
|
_enumeratedPath.edges.clear();
|
|
_enumeratedPath.vertices.clear();
|
|
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;
|
|
|
|
_traverser->getAllEdges(nextVertex, _tmpEdges, _currentDepth);
|
|
bool shouldReturnPath = _currentDepth + 1 >= _opts->minDepth;
|
|
if (!_tmpEdges.empty()) {
|
|
bool didInsert = false;
|
|
VPackSlice v;
|
|
for (auto const& e : _tmpEdges) {
|
|
bool valid =
|
|
_traverser->getSingleVertex(e, nextVertex, _currentDepth, v);
|
|
if (valid) {
|
|
auto step = std::make_unique<PathStep>(nextIdx, e, v);
|
|
_schreier.emplace_back(step.get());
|
|
step.release();
|
|
if (_currentDepth < _opts->maxDepth - 1) {
|
|
_nextDepth.emplace_back(NextStep(_schreierIndex));
|
|
}
|
|
_schreierIndex++;
|
|
didInsert = true;
|
|
}
|
|
}
|
|
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
|
|
// and increase the schreierIndex to point
|
|
// to the next free position.
|
|
computeEnumeratedPath(_lastReturned++);
|
|
return true;
|
|
}
|
|
|
|
// TODO Optimize this. Remove enumeratedPath
|
|
// All can be read from schreier vector directly
|
|
arangodb::aql::AqlValue BreadthFirstEnumerator::lastVertexToAqlValue() {
|
|
return _traverser->fetchVertexData(
|
|
_enumeratedPath.vertices.back());
|
|
}
|
|
|
|
arangodb::aql::AqlValue BreadthFirstEnumerator::lastEdgeToAqlValue() {
|
|
if (_enumeratedPath.edges.empty()) {
|
|
return arangodb::aql::AqlValue(arangodb::basics::VelocyPackHelper::NullValue());
|
|
}
|
|
return _traverser->fetchEdgeData(_enumeratedPath.edges.back());
|
|
}
|
|
|
|
arangodb::aql::AqlValue BreadthFirstEnumerator::pathToAqlValue(
|
|
arangodb::velocypack::Builder& result) {
|
|
result.clear();
|
|
result.openObject();
|
|
result.add(VPackValue("edges"));
|
|
result.openArray();
|
|
for (auto const& it : _enumeratedPath.edges) {
|
|
_traverser->addEdgeToVelocyPack(it, result);
|
|
}
|
|
result.close();
|
|
result.add(VPackValue("vertices"));
|
|
result.openArray();
|
|
for (auto const& it : _enumeratedPath.vertices) {
|
|
_traverser->addVertexToVelocyPack(it, result);
|
|
}
|
|
result.close();
|
|
result.close();
|
|
return arangodb::aql::AqlValue(result.slice());
|
|
}
|
|
|
|
void BreadthFirstEnumerator::computeEnumeratedPath(size_t index) {
|
|
TRI_ASSERT(index < _schreier.size());
|
|
|
|
size_t depth = getDepth(index);
|
|
_enumeratedPath.edges.clear();
|
|
_enumeratedPath.vertices.clear();
|
|
_enumeratedPath.edges.resize(depth);
|
|
_enumeratedPath.vertices.resize(depth + 1);
|
|
|
|
// Computed path. Insert it into the path enumerator.
|
|
PathStep* current = nullptr;
|
|
while (index != 0) {
|
|
TRI_ASSERT(depth > 0);
|
|
current = _schreier[index];
|
|
_enumeratedPath.vertices[depth] = current->vertex;
|
|
_enumeratedPath.edges[depth - 1] = current->edge;
|
|
|
|
index = current->sourceIdx;
|
|
--depth;
|
|
}
|
|
|
|
current = _schreier[0];
|
|
_enumeratedPath.vertices[0] = current->vertex;
|
|
}
|
|
|