diff --git a/CHANGELOG b/CHANGELOG index 17841a8b09..7479adf810 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,14 @@ devel ----- +* AQL: during a traversal if a vertex is not found. It will not print an ERROR to the log and continue + with a NULL value, but will register a warning at the query and continue with a NULL value. + The situation is not desired as an ERROR as ArangoDB can store edges pointing to non-existing + vertex which is perfectly valid, but it may be a n issue on the data model, so users + can directly see it on the query now and do not "by accident" have to check the LOG output. + +* UI: fixed event cleanup in cluster shards view + * fixed issue #3618: Inconsistent behavior of OR statement with object bind parameters * potential fix for issue #3562: Document WITHIN_RECTANGLE not found diff --git a/arangod/Aql/ExecutionPlan.cpp b/arangod/Aql/ExecutionPlan.cpp index ea580f23ce..088e9f9823 100644 --- a/arangod/Aql/ExecutionPlan.cpp +++ b/arangod/Aql/ExecutionPlan.cpp @@ -70,9 +70,9 @@ static uint64_t checkTraversalDepthValue(AstNode const* node) { } static std::unique_ptr CreateTraversalOptions( - transaction::Methods* trx, AstNode const* direction, + aql::Query* query, AstNode const* direction, AstNode const* optionsNode) { - auto options = std::make_unique(trx); + auto options = std::make_unique(query); TRI_ASSERT(direction != nullptr); TRI_ASSERT(direction->type == NODE_TYPE_DIRECTION); @@ -150,8 +150,8 @@ static std::unique_ptr CreateTraversalOptions( } static std::unique_ptr CreateShortestPathOptions( - arangodb::transaction::Methods* trx, AstNode const* node) { - auto options = std::make_unique(trx); + arangodb::aql::Query* query, AstNode const* node) { + auto options = std::make_unique(query); if (node != nullptr && node->type == NODE_TYPE_OBJECT) { size_t n = node->numMembers(); @@ -727,7 +727,7 @@ ExecutionNode* ExecutionPlan::fromNodeTraversal(ExecutionNode* previous, previous = calc; } - auto options = CreateTraversalOptions(getAst()->query()->trx(), direction, + auto options = CreateTraversalOptions(getAst()->query(), direction, node->getMember(3)); TRI_ASSERT(direction->type == NODE_TYPE_DIRECTION); @@ -809,7 +809,7 @@ ExecutionNode* ExecutionPlan::fromNodeShortestPath(ExecutionNode* previous, parseTraversalVertexNode(previous, node->getMember(2)); AstNode const* graph = node->getMember(3); - auto options = CreateShortestPathOptions(getAst()->query()->trx(), node->getMember(4)); + auto options = CreateShortestPathOptions(getAst()->query(), node->getMember(4)); // First create the node auto spNode = new ShortestPathNode(this, nextId(), _ast->query()->vocbase(), diff --git a/arangod/Aql/GraphNode.cpp b/arangod/Aql/GraphNode.cpp index 2c0d66f8f6..f971de06aa 100644 --- a/arangod/Aql/GraphNode.cpp +++ b/arangod/Aql/GraphNode.cpp @@ -362,7 +362,7 @@ GraphNode::GraphNode(ExecutionPlan* plan, "graph options have to be a json-object."); } _options = BaseOptions::createOptionsFromSlice( - _plan->getAst()->query()->trx(), opts); + _plan->getAst()->query(), opts); } /// @brief Internal constructor to clone the node. diff --git a/arangod/Aql/Query.h b/arangod/Aql/Query.h index 604106b65a..d2545c2ed2 100644 --- a/arangod/Aql/Query.h +++ b/arangod/Aql/Query.h @@ -85,7 +85,7 @@ class Query { std::shared_ptr const& queryStruct, std::shared_ptr const& options, QueryPart); - ~Query(); + virtual ~Query(); /// @brief clone a query /// note: as a side-effect, this will also create and start a transaction for @@ -176,7 +176,7 @@ class Query { void registerErrorCustom(int, char const*); /// @brief register a warning - void registerWarning(int, char const* = nullptr); + virtual void registerWarning(int, char const* = nullptr); void prepare(QueryRegistry*, uint64_t queryHash); @@ -208,7 +208,7 @@ class Query { void releaseEngine(); /// @brief return the transaction, if prepared - inline transaction::Methods* trx() { return _trx; } + virtual transaction::Methods* trx() { return _trx; } /// @brief get the plan for the query ExecutionPlan* plan() const { return _plan.get(); } diff --git a/arangod/Graph/BaseOptions.cpp b/arangod/Graph/BaseOptions.cpp index a50871cc1d..d76371572a 100644 --- a/arangod/Graph/BaseOptions.cpp +++ b/arangod/Graph/BaseOptions.cpp @@ -160,23 +160,25 @@ double BaseOptions::LookupInfo::estimateCost(size_t& nrItems) const { } std::unique_ptr BaseOptions::createOptionsFromSlice( - transaction::Methods* trx, VPackSlice const& definition) { + arangodb::aql::Query* query, VPackSlice const& definition) { VPackSlice type = definition.get("type"); if (type.isString() && type.isEqualString("shortestPath")) { - return std::make_unique(trx, definition); + return std::make_unique(query, definition); } - return std::make_unique(trx, definition); + return std::make_unique(query, definition); } -BaseOptions::BaseOptions(transaction::Methods* trx) +BaseOptions::BaseOptions(arangodb::aql::Query* query) : _ctx(new aql::FixedVarExpressionContext()), - _trx(trx), + _query(query), + _trx(query->trx()), _tmpVar(nullptr), _isCoordinator(arangodb::ServerState::instance()->isCoordinator()), _cache(nullptr) {} BaseOptions::BaseOptions(BaseOptions const& other) : _ctx(new aql::FixedVarExpressionContext()), + _query(other._query), _trx(other._trx), _tmpVar(nullptr), _isCoordinator(arangodb::ServerState::instance()->isCoordinator()), @@ -188,6 +190,7 @@ BaseOptions::BaseOptions(BaseOptions const& other) BaseOptions::BaseOptions(arangodb::aql::Query* query, VPackSlice info, VPackSlice collections) : _ctx(new aql::FixedVarExpressionContext()), + _query(query), _trx(query->trx()), _tmpVar(nullptr), _isCoordinator(arangodb::ServerState::instance()->isCoordinator()), @@ -443,5 +446,5 @@ void BaseOptions::activateCache( std::unordered_map const* engines) { // Do not call this twice. TRI_ASSERT(_cache == nullptr); - _cache.reset(cacheFactory::CreateCache(_trx, enableDocumentCache, engines)); + _cache.reset(cacheFactory::CreateCache(_query, enableDocumentCache, engines)); } diff --git a/arangod/Graph/BaseOptions.h b/arangod/Graph/BaseOptions.h index cf415e5b24..75dcb0f547 100644 --- a/arangod/Graph/BaseOptions.h +++ b/arangod/Graph/BaseOptions.h @@ -80,9 +80,9 @@ struct BaseOptions { public: static std::unique_ptr createOptionsFromSlice( - transaction::Methods* trx, arangodb::velocypack::Slice const& definition); + arangodb::aql::Query* query, arangodb::velocypack::Slice const& definition); - explicit BaseOptions(transaction::Methods* trx); + explicit BaseOptions(arangodb::aql::Query* query); /// @brief This copy constructor is only working during planning phase. /// After planning this node should not be copied anywhere. @@ -161,6 +161,8 @@ struct BaseOptions { std::vector&); protected: + aql::Query* _query; + transaction::Methods* _trx; /// @brief Lookup info to find all edges fulfilling the base conditions diff --git a/arangod/Graph/ClusterTraverserCache.cpp b/arangod/Graph/ClusterTraverserCache.cpp index b0045029c2..2d1d36ddb1 100644 --- a/arangod/Graph/ClusterTraverserCache.cpp +++ b/arangod/Graph/ClusterTraverserCache.cpp @@ -23,6 +23,7 @@ #include "ClusterTraverserCache.h" #include "Aql/AqlValue.h" +#include "Aql/Query.h" #include "Basics/StringRef.h" #include "Basics/VelocyPackHelper.h" #include "Cluster/ServerState.h" @@ -38,9 +39,9 @@ using namespace arangodb::basics; using namespace arangodb::graph; ClusterTraverserCache::ClusterTraverserCache( - transaction::Methods* trx, + aql::Query* query, std::unordered_map const* engines) - : TraverserCache(trx), _engines(engines) {} + : TraverserCache(query), _engines(engines) {} VPackSlice ClusterTraverserCache::lookupToken(EdgeDocumentToken const& token) { return VPackSlice(token.vpack()); @@ -59,7 +60,10 @@ aql::AqlValue ClusterTraverserCache::fetchVertexAqlResult(StringRef id) { TRI_ASSERT(ServerState::instance()->isCoordinator()); auto it = _cache.find(id); if (it == _cache.end()) { - LOG_TOPIC(ERR, Logger::GRAPHS) << __FUNCTION__ << " vertex '" << id.toString() << "' not found"; + // Register a warning. It is okay though but helps the user + std::string msg = "vertex '" + id.toString() + "' not found"; + _query->registerWarning(TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND, msg.c_str()); + // Document not found return NULL return aql::AqlValue(aql::AqlValueHintNull()); } @@ -78,7 +82,9 @@ void ClusterTraverserCache::insertVertexIntoResult(StringRef id, VPackBuilder& result) { auto it = _cache.find(id); if (it == _cache.end()) { - LOG_TOPIC(ERR, Logger::GRAPHS) << __FUNCTION__ << " vertex '" << id.toString() << "' not found"; + // Register a warning. It is okay though but helps the user + std::string msg = "vertex '" + id.toString() + "' not found"; + _query->registerWarning(TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND, msg.c_str()); // Document not found append NULL result.add(VelocyPackHelper::NullValue()); } else { diff --git a/arangod/Graph/ClusterTraverserCache.h b/arangod/Graph/ClusterTraverserCache.h index 7e7381ab96..5ebf4013df 100644 --- a/arangod/Graph/ClusterTraverserCache.h +++ b/arangod/Graph/ClusterTraverserCache.h @@ -49,7 +49,7 @@ namespace graph { class ClusterTraverserCache : public TraverserCache { public: ClusterTraverserCache( - transaction::Methods* trx, + aql::Query* query, std::unordered_map const* engines); @@ -100,6 +100,7 @@ class ClusterTraverserCache : public TraverserCache { } private: + /// @brief link by _id into our data dump std::unordered_map _cache; /// @brief dump for our edge and vertex documents diff --git a/arangod/Graph/ShortestPathOptions.cpp b/arangod/Graph/ShortestPathOptions.cpp index 9d53abebb4..93bdffbcb2 100644 --- a/arangod/Graph/ShortestPathOptions.cpp +++ b/arangod/Graph/ShortestPathOptions.cpp @@ -39,17 +39,17 @@ using namespace arangodb::basics; using namespace arangodb::graph; using namespace arangodb::traverser; -ShortestPathOptions::ShortestPathOptions(transaction::Methods* trx) - : BaseOptions(trx), +ShortestPathOptions::ShortestPathOptions(aql::Query* query) + : BaseOptions(query), direction("outbound"), weightAttribute(""), defaultWeight(1), bidirectional(true), multiThreaded(true) {} -ShortestPathOptions::ShortestPathOptions(transaction::Methods* trx, +ShortestPathOptions::ShortestPathOptions(aql::Query* query, VPackSlice const& info) - : BaseOptions(trx), + : BaseOptions(query), direction("outbound"), weightAttribute(""), defaultWeight(1), diff --git a/arangod/Graph/ShortestPathOptions.h b/arangod/Graph/ShortestPathOptions.h index 3b6a5b5ab4..5b4d18bf19 100644 --- a/arangod/Graph/ShortestPathOptions.h +++ b/arangod/Graph/ShortestPathOptions.h @@ -33,10 +33,6 @@ class ExecutionPlan; class Query; } -namespace transaction { -class Methods; -} - namespace velocypack { class Builder; class Slice; @@ -55,9 +51,9 @@ struct ShortestPathOptions : public BaseOptions { arangodb::velocypack::Builder startBuilder; arangodb::velocypack::Builder endBuilder; - explicit ShortestPathOptions(transaction::Methods* trx); + explicit ShortestPathOptions(aql::Query* query); - ShortestPathOptions(transaction::Methods* trx, + ShortestPathOptions(aql::Query* query, arangodb::velocypack::Slice const& info); // @brief DBServer-constructor used by TraverserEngines diff --git a/arangod/Graph/TraverserCache.cpp b/arangod/Graph/TraverserCache.cpp index 5d76b7dee6..09a6c74ea0 100644 --- a/arangod/Graph/TraverserCache.cpp +++ b/arangod/Graph/TraverserCache.cpp @@ -27,6 +27,7 @@ #include "Basics/VelocyPackHelper.h" #include "Aql/AqlValue.h" +#include "Aql/Query.h" #include "Cluster/ServerState.h" #include "Graph/EdgeDocumentToken.h" #include "Logger/Logger.h" @@ -41,9 +42,10 @@ using namespace arangodb; using namespace arangodb::graph; -TraverserCache::TraverserCache(transaction::Methods* trx) +TraverserCache::TraverserCache(aql::Query* query) : _mmdr(new ManagedDocumentResult{}), - _trx(trx), _insertedDocuments(0), + _query(query), + _trx(query->trx()), _insertedDocuments(0), _filteredDocuments(0), _stringHeap(new StringHeap{4096}) /* arbitrary block-size may be adjusted for performance */ { } @@ -90,6 +92,11 @@ VPackSlice TraverserCache::lookupInCollection(StringRef id) { return VPackSlice(_mmdr->vpack()); } else if (res.is(TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND)) { ++_insertedDocuments; + + // Register a warning. It is okay though but helps the user + std::string msg = "vertex '" + id.toString() + "' not found"; + _query->registerWarning(TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND, msg.c_str()); + // This is expected, we may have dangling edges. Interpret as NULL return basics::VelocyPackHelper::NullValue(); } else { diff --git a/arangod/Graph/TraverserCache.h b/arangod/Graph/TraverserCache.h index 9cfd3be880..13d3908a5e 100644 --- a/arangod/Graph/TraverserCache.h +++ b/arangod/Graph/TraverserCache.h @@ -41,6 +41,7 @@ class Slice; namespace aql { struct AqlValue; + class Query; } namespace graph { @@ -55,7 +56,7 @@ struct EdgeDocumentToken; class TraverserCache { public: - explicit TraverserCache(transaction::Methods* trx); + explicit TraverserCache(aql::Query* query); virtual ~TraverserCache(); @@ -130,6 +131,11 @@ class TraverserCache { ////////////////////////////////////////////////////////////////////////////// std::unique_ptr _mmdr; + ////////////////////////////////////////////////////////////////////////////// + /// @brief Query used to register warnings to. + ////////////////////////////////////////////////////////////////////////////// + arangodb::aql::Query* _query; + ////////////////////////////////////////////////////////////////////////////// /// @brief Transaction to access data, This class is NOT responsible for it. ////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Graph/TraverserCacheFactory.cpp b/arangod/Graph/TraverserCacheFactory.cpp index 8416d18ac6..dacb6b8082 100644 --- a/arangod/Graph/TraverserCacheFactory.cpp +++ b/arangod/Graph/TraverserCacheFactory.cpp @@ -35,13 +35,13 @@ using namespace arangodb::traverser; using namespace arangodb::graph::cacheFactory; TraverserCache* cacheFactory::CreateCache( - arangodb::transaction::Methods* trx, bool activateDocumentCache, + arangodb::aql::Query* query, bool activateDocumentCache, std::unordered_map const* engines) { if (ServerState::instance()->isCoordinator()) { - return new ClusterTraverserCache(trx, engines); + return new ClusterTraverserCache(query, engines); } if (activateDocumentCache) { - return new TraverserDocumentCache(trx); + return new TraverserDocumentCache(query); } - return new TraverserCache(trx); + return new TraverserCache(query); } diff --git a/arangod/Graph/TraverserCacheFactory.h b/arangod/Graph/TraverserCacheFactory.h index a20013f806..cb03e4f50a 100644 --- a/arangod/Graph/TraverserCacheFactory.h +++ b/arangod/Graph/TraverserCacheFactory.h @@ -28,15 +28,15 @@ #include "Cluster/TraverserEngineRegistry.h" namespace arangodb { -namespace transaction { -class Methods; +namespace aql { +class Query; } namespace graph { class TraverserCache; namespace cacheFactory { TraverserCache* CreateCache( - arangodb::transaction::Methods* trx, bool activateDocumentCache, + arangodb::aql::Query* query, bool activateDocumentCache, std::unordered_map const* engines); } // namespace cacheFactory diff --git a/arangod/Graph/TraverserDocumentCache.cpp b/arangod/Graph/TraverserDocumentCache.cpp index ba55017d41..364c725915 100644 --- a/arangod/Graph/TraverserDocumentCache.cpp +++ b/arangod/Graph/TraverserDocumentCache.cpp @@ -41,8 +41,8 @@ using namespace arangodb; using namespace arangodb::graph; -TraverserDocumentCache::TraverserDocumentCache(transaction::Methods* trx) - : TraverserCache(trx), _cache(nullptr) { +TraverserDocumentCache::TraverserDocumentCache(aql::Query* query) + : TraverserCache(query), _cache(nullptr) { auto cacheManager = CacheManagerFeature::MANAGER; TRI_ASSERT(cacheManager != nullptr); _cache = cacheManager->createCache(cache::CacheType::Plain); diff --git a/arangod/Graph/TraverserDocumentCache.h b/arangod/Graph/TraverserDocumentCache.h index f9293d21a5..af3071baf4 100644 --- a/arangod/Graph/TraverserDocumentCache.h +++ b/arangod/Graph/TraverserDocumentCache.h @@ -37,7 +37,7 @@ namespace graph { class TraverserDocumentCache : public TraverserCache { public: - explicit TraverserDocumentCache(transaction::Methods* trx); + explicit TraverserDocumentCache(aql::Query* query); ~TraverserDocumentCache(); diff --git a/arangod/Graph/TraverserOptions.cpp b/arangod/Graph/TraverserOptions.cpp index 3e45efb382..41a34fbe0a 100644 --- a/arangod/Graph/TraverserOptions.cpp +++ b/arangod/Graph/TraverserOptions.cpp @@ -40,8 +40,8 @@ using namespace arangodb::transaction; using namespace arangodb::traverser; using VPackHelper = arangodb::basics::VelocyPackHelper; -TraverserOptions::TraverserOptions(transaction::Methods* trx) - : BaseOptions(trx), +TraverserOptions::TraverserOptions(aql::Query* query) + : BaseOptions(query), _baseVertexExpression(nullptr), _traverser(nullptr), minDepth(1), @@ -50,9 +50,9 @@ TraverserOptions::TraverserOptions(transaction::Methods* trx) uniqueVertices(UniquenessLevel::NONE), uniqueEdges(UniquenessLevel::PATH) {} -TraverserOptions::TraverserOptions(transaction::Methods* trx, +TraverserOptions::TraverserOptions(aql::Query* query, VPackSlice const& obj) - : BaseOptions(trx), + : BaseOptions(query), _baseVertexExpression(nullptr), _traverser(nullptr), minDepth(1), @@ -238,7 +238,7 @@ arangodb::traverser::TraverserOptions::TraverserOptions( arangodb::traverser::TraverserOptions::TraverserOptions( TraverserOptions const& other) - : BaseOptions(other.trx()), + : BaseOptions(other._query), _baseVertexExpression(nullptr), _traverser(nullptr), minDepth(other.minDepth), diff --git a/arangod/Graph/TraverserOptions.h b/arangod/Graph/TraverserOptions.h index cae24d468b..3a893f971d 100644 --- a/arangod/Graph/TraverserOptions.h +++ b/arangod/Graph/TraverserOptions.h @@ -82,9 +82,9 @@ struct TraverserOptions : public graph::BaseOptions { UniquenessLevel uniqueEdges; - explicit TraverserOptions(transaction::Methods* trx); + explicit TraverserOptions(aql::Query* query); - TraverserOptions(transaction::Methods* trx, arangodb::velocypack::Slice const& definition); + TraverserOptions(aql::Query* query, arangodb::velocypack::Slice const& definition); TraverserOptions(arangodb::aql::Query*, arangodb::velocypack::Slice, arangodb::velocypack::Slice); diff --git a/js/server/tests/aql/aql-general-graph.js b/js/server/tests/aql/aql-general-graph.js index 42685ecbfe..fa80b301d4 100644 --- a/js/server/tests/aql/aql-general-graph.js +++ b/js/server/tests/aql/aql-general-graph.js @@ -1,5 +1,5 @@ /*jshint globalstrict:false, strict:false, maxlen: 500 */ -/*global assertEqual, assertTrue */ +/*global assertEqual, assertTrue, AQL_EXECUTE */ //////////////////////////////////////////////////////////////////////////////// /// @brief tests for query language, graph functions @@ -30,6 +30,7 @@ var jsunity = require("jsunity"); var db = require("@arangodb").db; +var errors = require("internal").errors; var graph = require("@arangodb/general-graph"); var helper = require("@arangodb/aql-helper"); var getQueryResults = helper.getQueryResults; @@ -1935,7 +1936,8 @@ function ahuacatlQueryShortestPathTestSuite() { LET source = "${v1}/A" LET target = "${v1}/F" FOR v, e IN OUTBOUND SHORTEST_PATH source TO target GRAPH "${graphName}" RETURN {v, e}`; - var actual = getQueryResults(query); + var full = AQL_EXECUTE(query); + var actual = full.json; assertEqual(actual.length, 4); assertEqual(actual[0].v._key, "A"); assertEqual(actual[0].e, null); @@ -1946,6 +1948,9 @@ function ahuacatlQueryShortestPathTestSuite() { assertEqual(actual[2].e._to, v2 + "/D"); assertEqual(actual[3].v._key, "F"); assertEqual(actual[3].e.entfernung, 11); + // We expect one warning + assertEqual(full.warnings.length, 1); + assertEqual(full.warnings[0].code, errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code); } }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3e62e4d9d4..943b878d3e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -53,6 +53,7 @@ add_executable( Cache/TransactionsWithBackingStore.cpp Cluster/ClusterHelpersTest.cpp Geo/georeg.cpp + Graph/ClusterTraverserCacheTest.cpp Pregel/typedbuffer.cpp RocksDBEngine/KeyTest.cpp RocksDBEngine/IndexEstimatorTest.cpp diff --git a/tests/Graph/ClusterTraverserCacheTest.cpp b/tests/Graph/ClusterTraverserCacheTest.cpp new file mode 100644 index 0000000000..ecf1fb1f5a --- /dev/null +++ b/tests/Graph/ClusterTraverserCacheTest.cpp @@ -0,0 +1,107 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2017-2017 ArangoDB 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 "catch.hpp" +#include "fakeit.hpp" + +#include "Aql/AqlValue.h" +#include "Aql/Query.h" +#include "Cluster/ServerState.h" +#include "Graph/ClusterTraverserCache.h" +#include "Transaction/Methods.h" + +#include +#include +#include + +using namespace arangodb; +using namespace arangodb::aql; +using namespace arangodb::graph; +using namespace fakeit; + +namespace arangodb { +namespace tests { +namespace cluster_traverser_cache_test { + +TEST_CASE("ClusterTraverserCache", "[aql][cluster]") { + + auto ss = ServerState::instance(); + ss->setRole(ServerState::ROLE_COORDINATOR); + + SECTION("it should return a NULL AQLValue if vertex not cached") { + std::unordered_map engines; + std::string vertexId = "UnitTest/Vertex"; + std::string expectedMessage = "vertex '" + vertexId + "' not found"; + + Mock trxMock; + transaction::Methods& trx = trxMock.get(); + + Mock queryMock; + Query& query = queryMock.get(); + When(Method(queryMock, trx)).AlwaysReturn(&trx); + When(Method(queryMock, registerWarning)).Do([&] (int code, char const* message) { + REQUIRE(code == TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND); + REQUIRE(strcmp(message, expectedMessage.c_str()) == 0); + }); + + ClusterTraverserCache testee(&query, &engines); + + // NOTE: we do not put anything into the cache, so we get null for any vertex + AqlValue val = testee.fetchVertexAqlResult(StringRef(vertexId)); + REQUIRE(val.isNull(false)); + Verify(Method(queryMock, registerWarning)).Exactly(1); + } + + SECTION("it should insert a NULL VPack if vertex not cached") { + std::unordered_map engines; + std::string vertexId = "UnitTest/Vertex"; + std::string expectedMessage = "vertex '" + vertexId + "' not found"; + + Mock trxMock; + transaction::Methods& trx = trxMock.get(); + + Mock queryMock; + Query& query = queryMock.get(); + When(Method(queryMock, trx)).AlwaysReturn(&trx); + When(Method(queryMock, registerWarning)).Do([&] (int code, char const* message) { + REQUIRE(code == TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND); + REQUIRE(strcmp(message, expectedMessage.c_str()) == 0); + }); + + VPackBuilder result; + + ClusterTraverserCache testee(&query, &engines); + + // NOTE: we do not put anything into the cache, so we get null for any vertex + testee.insertVertexIntoResult(StringRef(vertexId), result); + + VPackSlice sl = result.slice(); + REQUIRE(sl.isNull()); + + Verify(Method(queryMock, registerWarning)).Exactly(1); + } + +} + +} // cluster_traveser_cache_test +} // tests +} // arangodb