1
0
Fork 0

Added the implementation and more tests for EDGES in CXX only

This commit is contained in:
Michael Hackstein 2015-10-29 15:52:46 +01:00
parent 69d9cce680
commit cc35fdb7b0
5 changed files with 301 additions and 9 deletions

View File

@ -192,7 +192,7 @@ std::unordered_map<std::string, Function const> const Executor::FunctionNames{
{ "VALUES", Function("VALUES", "AQL_VALUES", "a|b", true, true, false, true, true, &Functions::Values) },
{ "MERGE", Function("MERGE", "AQL_MERGE", "la|+", true, true, false, true, true, &Functions::Merge) },
{ "MERGE_RECURSIVE", Function("MERGE_RECURSIVE", "AQL_MERGE_RECURSIVE", "a,a|+", true, true, false, true, true) },
{ "DOCUMENT", Function("DOCUMENT", "AQL_DOCUMENT", "h.|.", false, false, true, false, true, &Functions::Document) },
{ "DOCUMENT", Function("DOCUMENT", "AQL_DOCUMENT", "h.|.", false, false, true, false, true, &Functions::Document, NotInCluster) },
{ "MATCHES", Function("MATCHES", "AQL_MATCHES", ".,l|b", true, true, false, true, true) },
{ "UNSET", Function("UNSET", "AQL_UNSET", "a,sl|+", true, true, false, true, true, &Functions::Unset) },
{ "KEEP", Function("KEEP", "AQL_KEEP", "a,sl|+", true, true, false, true, true, &Functions::Keep) },
@ -218,7 +218,7 @@ std::unordered_map<std::string, Function const> const Executor::FunctionNames{
{ "GRAPH_TRAVERSAL", Function("GRAPH_TRAVERSAL", "AQL_GRAPH_TRAVERSAL", "s,als,s|a", false, false, true, false, false) },
{ "TRAVERSAL_TREE", Function("TRAVERSAL_TREE", "AQL_TRAVERSAL_TREE", "h,h,s,s,s|a", false, false, true, false, false) },
{ "GRAPH_TRAVERSAL_TREE", Function("GRAPH_TRAVERSAL_TREE", "AQL_GRAPH_TRAVERSAL_TREE", "s,als,s,s|a", false, false, true, false, false) },
{ "EDGES", Function("EDGES", "AQL_EDGES", "h,s,s|l,o", true, false, true, false, false) },
{ "EDGES", Function("EDGES", "AQL_EDGES", "h,s,s|l,o", true, false, true, false, false, &Functions::Edges, NotInCluster) },
{ "GRAPH_EDGES", Function("GRAPH_EDGES", "AQL_GRAPH_EDGES", "s,als|a", false, false, true, false, false) },
{ "GRAPH_VERTICES", Function("GRAPH_VERTICES", "AQL_GRAPH_VERTICES", "s,als|a", false, false, true, false, false) },
{ "NEIGHBORS", Function("NEIGHBORS", "AQL_NEIGHBORS", "h,h,s,s|l,a", true, false, true, false, false, &Functions::Neighbors, NotInCluster) },

View File

@ -1799,6 +1799,51 @@ static inline Json ExpandShapedJson (VocShaper* shaper,
return json;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief Reads a document by cid and key
/// Also lazy locks the collection.
/// Returns null if the document does not exist
////////////////////////////////////////////////////////////////////////////////
static Json readDocument (triagens::arango::AqlTransaction* trx,
CollectionNameResolver const* resolver,
TRI_voc_cid_t cid,
char const* key) {
auto collection = trx->trxCollection(cid);
if (collection == nullptr) {
int res = TRI_AddCollectionTransaction(trx->getInternals(),
cid,
TRI_TRANSACTION_READ,
trx->nestingLevel(),
true,
true);
if (res != TRI_ERROR_NO_ERROR) {
THROW_ARANGO_EXCEPTION(res);
}
TRI_EnsureCollectionsTransaction(trx->getInternals());
collection = trx->trxCollection(cid);
if (collection == nullptr) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "collection is a nullptr");
}
}
TRI_doc_mptr_copy_t mptr;
int res = trx->readSingle(collection, &mptr, key);
if (res != TRI_ERROR_NO_ERROR) {
return Json(Json::Null);
}
return ExpandShapedJson(
collection->_collection->_collection->getShaper(),
resolver,
cid,
&mptr
);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief Transforms VertexId to Json
////////////////////////////////////////////////////////////////////////////////
@ -2780,6 +2825,208 @@ AqlValue Functions::Document (triagens::aql::Query* query,
// Id has invalid format
return AqlValue(new Json(Json::Null));
}
////////////////////////////////////////////////////////////////////////////////
/// @brief function Edges
////////////////////////////////////////////////////////////////////////////////
AqlValue Functions::Edges (triagens::aql::Query* query,
triagens::arango::AqlTransaction* trx,
FunctionParameters const& parameters) {
size_t const n = parameters.size();
if (n < 3 || 5 < n) {
THROW_ARANGO_EXCEPTION_PARAMS(TRI_ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH, "EDGES", (int) 3, (int) 5);
}
Json collectionJson = ExtractFunctionParameter(trx, parameters, 0, false);
if (! collectionJson.isString()) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_INTERNAL);
}
std::string collectionName = triagens::basics::JsonHelper::getStringValue(collectionJson.json(), "");
TRI_transaction_collection_t* collection = nullptr;
TRI_voc_cid_t cid;
RegisterCollectionInTransaction(trx, collectionName, cid, collection);
if (collection->_collection->_type != TRI_COL_TYPE_EDGE) {
RegisterWarning(query, "EDGES", TRI_ERROR_ARANGO_COLLECTION_TYPE_INVALID);
return AqlValue(new Json(Json::Null));
}
Json vertexJson = ExtractFunctionParameter(trx, parameters, 1, false);
if (! vertexJson.isString()) {
// Invalid Start vertex
return AqlValue(new Json(Json::Null));
}
std::string vertexId = basics::JsonHelper::getStringValue(vertexJson.json(), "");
std::vector<std::string> parts = triagens::basics::StringUtils::split(vertexId, "/");
if (parts.size() != 2) {
// Invalid Start vertex
return AqlValue(new Json(Json::Null));
}
Json directionJson = ExtractFunctionParameter(trx, parameters, 2, false);
if (! directionJson.isString()) {
RegisterWarning(query, "EDGES", TRI_ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return AqlValue(new Json(Json::Null));
}
std::string dirString = basics::JsonHelper::getStringValue(directionJson.json(), "");
// transform String to lower case
std::transform(dirString.begin(), dirString.end(), dirString.begin(), ::tolower);
TRI_edge_direction_e direction;
if (dirString == "inbound") {
direction = TRI_EDGE_IN;
}
else if (dirString == "outbound") {
direction = TRI_EDGE_OUT;
}
else if (dirString == "any") {
direction = TRI_EDGE_ANY;
}
else {
RegisterWarning(query, "EDGES", TRI_ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return AqlValue(new Json(Json::Null));
}
auto resolver = trx->resolver();
TRI_voc_cid_t startCid = resolver->getCollectionId(parts[0]);
if (startCid == 0) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND);
}
char* key = const_cast<char*>(parts[1].c_str());
std::vector<TRI_doc_mptr_copy_t> edges = TRI_LookupEdgesDocumentCollection(
collection->_collection->_collection,
direction,
startCid,
key
);
size_t resultCount = edges.size();
auto shaper = collection->_collection->_collection->getShaper();
if (n > 3) {
// We might have examples
Json exampleJson = ExtractFunctionParameter(trx, parameters, 3, false);
if (! exampleJson.isNull()) {
bool buildMatcher = false;
if (exampleJson.isArray()) {
size_t exampleCount = exampleJson.size();
// We only support objects here so validate
for (size_t k = 0; k < exampleCount; ++k) {
if (! exampleJson.at(k).isObject()) {
RegisterWarning(query, "EDGES", TRI_ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
if (TRI_DeleteArrayJson(TRI_UNKNOWN_MEM_ZONE, exampleJson.json(), k)) {
--k;
--exampleCount;
}
else {
// Should never occur.
// If it occurs in production it will simply fall through
// it can only retern more results than expected and not do any harm.
TRI_ASSERT(false);
}
}
}
if (exampleCount > 0) {
buildMatcher = true;
}
// else we do not filter at all
}
else if (exampleJson.isObject()) {
buildMatcher = true;
}
else {
RegisterWarning(query, "EDGES", TRI_ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
}
if (buildMatcher) {
try {
triagens::arango::ExampleMatcher matcher(exampleJson.json(), shaper, resolver);
for (size_t i = 0; i < resultCount; ++i) {
if (! matcher.matches(cid, &edges[i])) {
edges.erase(edges.begin() + i);
--i;
--resultCount;
}
}
}
catch (triagens::basics::Exception& e) {
if (e.code() != TRI_RESULT_ELEMENT_NOT_FOUND) {
throw e;
}
// Illegal match, we cannot filter anything
edges.clear();
resultCount = 0;
}
}
}
}
bool includeVertices = false;
if (n == 5) {
// We have options
Json options = ExtractFunctionParameter(trx, parameters, 4, false);
if (options.isObject() && options.has("includeVertices")) {
includeVertices = triagens::basics::JsonHelper::getBooleanValue(options.json(), "includeVertices", false);
}
}
if (includeVertices) {
Json result(Json::Array, resultCount);
for (size_t i = 0; i < resultCount; ++i) {
Json resultPair(Json::Object, 2);
resultPair.set("edge", ExpandShapedJson(
shaper,
resolver,
cid,
&(edges[i])
));
char const* targetKey;
TRI_voc_cid_t targetCid;
switch (direction) {
case TRI_EDGE_OUT:
targetKey = TRI_EXTRACT_MARKER_TO_KEY(&edges[i]);
targetCid = TRI_EXTRACT_MARKER_TO_CID(&edges[i]);
break;
case TRI_EDGE_IN:
targetKey = TRI_EXTRACT_MARKER_FROM_KEY(&edges[i]);
targetCid = TRI_EXTRACT_MARKER_FROM_CID(&edges[i]);
break;
case TRI_EDGE_ANY:
targetKey = TRI_EXTRACT_MARKER_TO_KEY(&edges[i]);
targetCid = TRI_EXTRACT_MARKER_TO_CID(&edges[i]);
if (targetCid == startCid && strcmp(targetKey, key) == 0) {
targetKey = TRI_EXTRACT_MARKER_FROM_KEY(&edges[i]);
targetCid = TRI_EXTRACT_MARKER_FROM_CID(&edges[i]);
};
break;
}
resultPair.set("vertex", readDocument(trx, resolver, targetCid, targetKey));
result.add(resultPair);
}
return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, result.steal()));
}
Json result(Json::Array, resultCount);
for (size_t i = 0; i < resultCount; ++i) {
result.add(ExpandShapedJson(
shaper,
resolver,
cid,
&(edges[i])
));
}
return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, result.steal()));
}
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------

View File

@ -118,6 +118,7 @@ namespace triagens {
static AqlValue ParseIdentifier (triagens::aql::Query*, triagens::arango::AqlTransaction*, FunctionParameters const&);
static AqlValue Minus (triagens::aql::Query*, triagens::arango::AqlTransaction*, FunctionParameters const&);
static AqlValue Document (triagens::aql::Query*, triagens::arango::AqlTransaction*, FunctionParameters const&);
static AqlValue Edges (triagens::aql::Query*, triagens::arango::AqlTransaction*, FunctionParameters const&);
};
}

View File

@ -203,7 +203,6 @@ void ExampleMatcher::fillExampleDefinition (TRI_json_t const* example,
}
else {
// no attribute path found. this means the result will be empty
ExampleMatcher::cleanup();
THROW_ARANGO_EXCEPTION(TRI_RESULT_ELEMENT_NOT_FOUND);
}
}
@ -213,7 +212,6 @@ void ExampleMatcher::fillExampleDefinition (TRI_json_t const* example,
auto value = TRI_ShapedJsonJson(_shaper, jsonValue, false);
if (value == nullptr) {
ExampleMatcher::cleanup();
THROW_ARANGO_EXCEPTION(TRI_RESULT_ELEMENT_NOT_FOUND);
}
@ -313,12 +311,21 @@ ExampleMatcher::ExampleMatcher (TRI_json_t const* example,
ExampleDefinition def;
try {
ExampleMatcher::fillExampleDefinition(TRI_LookupArrayJson(example, i), resolver, def);
definitions.emplace_back(move(def));
}
catch (...) {
catch (triagens::basics::Exception& e) {
if (e.code() != TRI_RESULT_ELEMENT_NOT_FOUND) {
ExampleMatcher::cleanup();
throw;
}
definitions.emplace_back(move(def));
// Result not found might happen. Ignore here because all other elemens
// might be matched.
}
}
if (definitions.size() == 0) {
// None of the given examples could ever match.
// Throw result not found so client can short circuit.
THROW_ARANGO_EXCEPTION(TRI_RESULT_ELEMENT_NOT_FOUND);
}
}
}

View File

@ -67,7 +67,7 @@ function ahuacatlQueryEdgesTestSuite () {
vertex.save({ _key: "v7", name: "v7" });
function makeEdge (from, to) {
edge.save("UnitTestsAhuacatlVertex/" + from, "UnitTestsAhuacatlVertex/" + to, { what: from + "->" + to });
edge.save("UnitTestsAhuacatlVertex/" + from, "UnitTestsAhuacatlVertex/" + to, { _key: from + "" + to, what: from + "->" + to });
}
makeEdge("v1", "v2");
@ -323,7 +323,44 @@ function ahuacatlQueryEdgesTestSuite () {
});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief checks EDGES()
////////////////////////////////////////////////////////////////////////////////
testEdgesFilterExample : function () {
var queries = [
"FOR e IN EDGES(UnitTestsAhuacatlEdge, @start, 'any', @examples) SORT e.what RETURN e.what",
"FOR e IN NOOPT(EDGES(UnitTestsAhuacatlEdge, @start, 'any', @examples)) SORT e.what RETURN e.what",
"FOR e IN NOOPT(V8(EDGES(UnitTestsAhuacatlEdge, @start, 'any', @examples))) SORT e.what RETURN e.what"
];
queries.forEach(function (q) {
var actual;
actual = getQueryResults(q, {start: "UnitTestsAhuacatlVertex/v3", examples: {what: "v1->v3"} });
assertEqual(actual, [ "v1->v3" ]);
actual = getQueryResults(q, {start: "UnitTestsAhuacatlVertex/v3", examples: [{what: "v1->v3"}, {what: "v3->v6"}] });
assertEqual(actual, [ "v1->v3", "v3->v6"]);
actual = getQueryResults(q, {start: "UnitTestsAhuacatlVertex/v3", examples: [] });
assertEqual(actual, [ "v1->v3", "v2->v3", "v3->v4", "v3->v6", "v3->v7", "v6->v3", "v7->v3" ]);
actual = getQueryResults(q, {start: "UnitTestsAhuacatlVertex/v3", examples: null });
assertEqual(actual, [ "v1->v3", "v2->v3", "v3->v4", "v3->v6", "v3->v7", "v6->v3", "v7->v3" ]);
actual = getQueryResults(q, {start: "UnitTestsAhuacatlVertex/v3", examples: {non: "matchable"} });
assertEqual(actual, [ ]);
actual = getQueryResults(q, {start: "UnitTestsAhuacatlVertex/v3", examples: [{what: "v1->v3"}, {non: "matchable"}] });
assertEqual(actual, [ "v1->v3" ]);
actual = getQueryResults(q, {start: "UnitTestsAhuacatlVertex/v3", examples: [{what: "v1->v3"}, {non: "matchable"}] });
assertEqual(actual, [ "v1->v3" ]);
actual = getQueryResults(q, {start: "UnitTestsAhuacatlVertex/v3", examples: ["UnitTestsAhuacatlEdge/v1v3", {what: "v3->v6" } ] });
assertEqual(actual, [ "v3->v6" ]);
});
}
};
}