//////////////////////////////////////////////////////////////////////////////// /// 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(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(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; }