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