diff --git a/arangod/Aql/Functions.cpp b/arangod/Aql/Functions.cpp index d8825eb314..4b214a4564 100644 --- a/arangod/Aql/Functions.cpp +++ b/arangod/Aql/Functions.cpp @@ -1582,7 +1582,7 @@ static AqlValue VertexIdsToAqlValue (triagens::arango::AqlTransaction* trx, CollectionNameResolver const* resolver, std::unordered_set& ids, bool includeData = false) { - Json* result = new Json(Json::Array); + Json* result = new Json(Json::Array, ids.size()); if (includeData) { for (auto& it : ids) { @@ -1609,7 +1609,7 @@ AqlValue Functions::Neighbors (triagens::aql::Query* query, basics::traverser::NeighborsOptions opts; if (n < 4 || n > 6) { - THROW_ARANGO_EXCEPTION_PARAMS(TRI_ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH, "NEIGHBORS"); + THROW_ARANGO_EXCEPTION_PARAMS(TRI_ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH, "NEIGHBORS", (int) 4, (int) 6); } auto resolver = trx->resolver(); @@ -1654,9 +1654,30 @@ AqlValue Functions::Neighbors (triagens::aql::Query* query, opts.start = v; } } + else if (vertexInfo.isObject()) { + if (!vertexInfo.has("_id")) { + THROW_ARANGO_EXCEPTION_PARAMS(TRI_ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, "NEIGHBORS"); + } + vertexId = basics::JsonHelper::getStringValue(vertexInfo.get("_id").json(), ""); + // TODO tmp can be replaced by Traversal::IdStringToVertexId + size_t split; + char const* str = vertexId.c_str(); + + if (! TRI_ValidateDocumentIdKeyGenerator(str, &split)) { + THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_DOCUMENT_KEY_BAD); + } + + std::string const collectionName = vertexId.substr(0, split); + auto coli = resolver->getCollectionStruct(collectionName); + if (coli == nullptr || collectionName.compare(vColName) != 0) { + THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND); + } + VertexId v(coli->_cid, const_cast(str + split + 1)); + opts.start = v; + } else { // TODO FIXME - THROW_ARANGO_EXCEPTION_PARAMS(TRI_ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH, "NEIGHBORS"); + THROW_ARANGO_EXCEPTION_PARAMS(TRI_ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, "NEIGHBORS"); } Json direction = ExtractFunctionParameter(trx, parameters, 3, false); @@ -1681,9 +1702,6 @@ AqlValue Functions::Neighbors (triagens::aql::Query* query, THROW_ARANGO_EXCEPTION_PARAMS(TRI_ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH, "NEIGHBORS"); } - /* Ignored for now - auto examples = ExtractFunctionParameter(trx, parameters, 4, false); - */ bool includeData = false; @@ -1700,26 +1718,8 @@ AqlValue Functions::Neighbors (triagens::aql::Query* query, } else { opts.maxDepth = basics::JsonHelper::getNumericValue(options.json(), "maxDepth", opts.minDepth); } - /* - if (options.has("includeData")) { - Json incl = options.get("includeData"); - if (! incl.isBoolean() ) { - //TODO Correct Error Message - THROW_ARANGO_EXCEPTION_PARAMS(TRI_ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH, "NEIGHBORS"); - } - includeData = incl.json()->_value._boolean; - } - if (options.has("minDepth")) { - Json min = options.get("minDepth"); - } - if (options.has("maxDepth")) { - } - */ } - // TODO Parse VertexInfo {_id},"v/1","1" are all valid - - std::unordered_set neighbors; std::vector edgeCollectionInfos; @@ -1733,12 +1733,24 @@ AqlValue Functions::Neighbors (triagens::aql::Query* query, }; TRI_voc_cid_t eCid = resolver->getCollectionId(eColName); + + + // Function to return constant distance auto wc = [](TRI_doc_mptr_copy_t& edge) -> double { return 1; }; - edgeCollectionInfos.emplace_back(new EdgeCollectionInfo( + EdgeCollectionInfo* eci = new EdgeCollectionInfo( eCid, trx->documentCollection(eCid), wc - )); + ); + + edgeCollectionInfos.emplace_back(eci); + + if (n > 4) { + auto edgeExamples = ExtractFunctionParameter(trx, parameters, 4, false); + if (! (edgeExamples.isArray() && edgeExamples.size() == 0) ) { + opts.addEdgeFilter(edgeExamples, eci->getShaper(), eCid); + } + } TRI_RunNeighborsSearch( edgeCollectionInfos, diff --git a/arangod/V8Server/V8Traverser.cpp b/arangod/V8Server/V8Traverser.cpp index 6c177d66b4..b8d5af6d42 100644 --- a/arangod/V8Server/V8Traverser.cpp +++ b/arangod/V8Server/V8Traverser.cpp @@ -293,6 +293,7 @@ void BasicOptions::addEdgeFilter (v8::Isolate* isolate, TRI_shaper_t* shaper, TRI_voc_cid_t const& cid, string& errorMessage) { + useEdgeFilter = true; auto it = _edgeFilter.find(cid); if (example->IsArray()) { @@ -308,6 +309,20 @@ void BasicOptions::addEdgeFilter (v8::Isolate* isolate, } } +//////////////////////////////////////////////////////////////////////////////// +/// @brief Insert a new edge matcher object +//////////////////////////////////////////////////////////////////////////////// + +void BasicOptions::addEdgeFilter (Json const& example, + TRI_shaper_t* shaper, + TRI_voc_cid_t const& cid) { + useEdgeFilter = true; + auto it = _edgeFilter.find(cid); + if (it == _edgeFilter.end()) { + _edgeFilter.emplace(cid, new ExampleMatcher(example.json(), shaper)); + } +} + //////////////////////////////////////////////////////////////////////////////// /// @brief Checks if an edge matches to given examples //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/V8Server/V8Traverser.h b/arangod/V8Server/V8Traverser.h index 6d7853562f..3b97a4a4fc 100644 --- a/arangod/V8Server/V8Traverser.h +++ b/arangod/V8Server/V8Traverser.h @@ -171,6 +171,10 @@ namespace triagens { TRI_voc_cid_t const& cid, std::string& errorMessage); + void addEdgeFilter (Json const& example, + TRI_shaper_t* shaper, + TRI_voc_cid_t const& cid); + void addVertexFilter (v8::Isolate* isolate, v8::Handle const& example, triagens::arango::ExplicitTransaction* trx, diff --git a/arangod/VocBase/ExampleMatcher.h b/arangod/VocBase/ExampleMatcher.h index 26949a2302..a4825e7c26 100644 --- a/arangod/VocBase/ExampleMatcher.h +++ b/arangod/VocBase/ExampleMatcher.h @@ -76,6 +76,9 @@ namespace triagens { TRI_shaper_t* _shaper; std::vector definitions; + void fillExampleDefinition (TRI_json_t const* example, + ExampleDefinition& def); + void fillExampleDefinition (v8::Isolate* isolate, v8::Handle const& example, v8::Handle const& names, @@ -95,7 +98,7 @@ namespace triagens { TRI_shaper_t* shaper, std::string& errorMessage); - ExampleMatcher (TRI_json_t* example, + ExampleMatcher (TRI_json_t const* example, TRI_shaper_t* shaper); ~ExampleMatcher () { diff --git a/js/server/tests/aql-graph.js b/js/server/tests/aql-graph.js index 1b0952ce15..dc2b0f597a 100644 --- a/js/server/tests/aql-graph.js +++ b/js/server/tests/aql-graph.js @@ -172,6 +172,58 @@ function ahuacatlQueryEdgesTestSuite () { assertEqual(actual, [ ]); assertQueryError(errors.ERROR_ARANGO_COLLECTION_NOT_FOUND.code, "FOR e IN EDGES(UnitTestsAhuacatlEdge, 'thefox/thefox', 'outbound') SORT e.what RETURN e.what"); + } + + }; +} + +function ahuacatlQueryNeighborsTestSuite () { + var vertex = null; + var edge = null; + + return { + +//////////////////////////////////////////////////////////////////////////////// +/// @brief set up +//////////////////////////////////////////////////////////////////////////////// + + setUp : function () { + db._drop("UnitTestsAhuacatlVertex"); + db._drop("UnitTestsAhuacatlEdge"); + + vertex = db._create("UnitTestsAhuacatlVertex"); + edge = db._createEdgeCollection("UnitTestsAhuacatlEdge"); + + vertex.save({ _key: "v1", name: "v1" }); + vertex.save({ _key: "v2", name: "v2" }); + vertex.save({ _key: "v3", name: "v3" }); + vertex.save({ _key: "v4", name: "v4" }); + vertex.save({ _key: "v5", name: "v5" }); + vertex.save({ _key: "v6", name: "v6" }); + vertex.save({ _key: "v7", name: "v7" }); + + function makeEdge (from, to) { + edge.save("UnitTestsAhuacatlVertex/" + from, "UnitTestsAhuacatlVertex/" + to, { what: from + "->" + to, _key: from + "_" + to }); + } + + makeEdge("v1", "v2"); + makeEdge("v1", "v3"); + makeEdge("v2", "v3"); + makeEdge("v3", "v4"); + makeEdge("v3", "v6"); + makeEdge("v3", "v7"); + makeEdge("v4", "v2"); + makeEdge("v7", "v3"); + makeEdge("v6", "v3"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief tear down +//////////////////////////////////////////////////////////////////////////////// + + tearDown : function () { + db._drop("UnitTestsAhuacatlVertex"); + db._drop("UnitTestsAhuacatlEdge"); }, //////////////////////////////////////////////////////////////////////////////// @@ -327,6 +379,44 @@ function ahuacatlQueryEdgesTestSuite () { return x.name; }); assertEqual(actual, ["v4", "v6", "v7"]); + }, + + testNeighborsEdgeExamples : function () { + var actual; + var v3 = "UnitTestsAhuacatlVertex/v3"; + var v4 = "UnitTestsAhuacatlVertex/v4"; + var v6 = "UnitTestsAhuacatlVertex/v6"; + var v7 = "UnitTestsAhuacatlVertex/v7"; + var query = "FOR n IN NEIGHBORS(UnitTestsAhuacatlVertex, UnitTestsAhuacatlEdge, @startId" + + ", 'outbound', @examples) SORT n RETURN n"; + + // An empty array should let all edges through + actual = getQueryResults(query, {startId: v3, examples: []}); + assertEqual(actual, [ v4, v6, v7 ]); + + // Should be able to handle exactly one object + actual = getQueryResults(query, {startId: v3, examples: {what: "v3->v4"}}); + assertEqual(actual, [ v4 ]); + + // Should be able to handle a list of a single object + actual = getQueryResults(query, {startId: v3, examples: [{what: "v3->v4"}]}); + assertEqual(actual, [ v4 ]); + + // Should be able to handle a list of objects + actual = getQueryResults(query, {startId: v3, examples: [{what: "v3->v4"}, {what: "v3->v6"}]}); + assertEqual(actual, [ v4, v6 ]); + + // Should be able to handle an id as string + actual = getQueryResults(query, {startId: v3, examples: "UnitTestAhuacatlEdge/v3_v6"}); + assertEqual(actual, [ v6 ]); + + // Should be able to handle a mix of id and objects + actual = getQueryResults(query, {startId: v3, examples: ["UnitTestAhuacatlEdge/v3_v6", {what: "v3->v4"}]}); + assertEqual(actual, [ v4, v6 ]); + + // Should be able to handle internal attributes + actual = getQueryResults(query, {startId: v3, examples: {_to: v4}}); + assertEqual(actual, [ v4 ]); } }; @@ -1862,6 +1952,7 @@ function ahuacatlQueryShortestpathErrorsSuite () { //////////////////////////////////////////////////////////////////////////////// jsunity.run(ahuacatlQueryEdgesTestSuite); +jsunity.run(ahuacatlQueryNeighborsTestSuite); jsunity.run(ahuacatlQueryPathsTestSuite); jsunity.run(ahuacatlQueryShortestPathTestSuite); jsunity.run(ahuacatlQueryTraversalFilterTestSuite);