From 7313b194313c15deffb19fc7a0fd8260d3541f4d Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Sat, 20 Jun 2015 14:21:58 +0200 Subject: [PATCH] implemented FILTER, LIMIT and multi-star features --- arangod/Aql/Ast.cpp | 1 + arangod/Aql/Executor.cpp | 2 +- arangod/Aql/Expression.cpp | 152 +++++++++++++++++++++++--- js/server/modules/org/arangodb/aql.js | 6 +- 4 files changed, 146 insertions(+), 15 deletions(-) diff --git a/arangod/Aql/Ast.cpp b/arangod/Aql/Ast.cpp index 27de40528e..787824cc2e 100644 --- a/arangod/Aql/Ast.cpp +++ b/arangod/Aql/Ast.cpp @@ -1406,6 +1406,7 @@ void Ast::validateAndOptimize () { if (static_cast(data)->hasSeenWriteNode) { THROW_ARANGO_EXCEPTION(TRI_ERROR_QUERY_ACCESS_AFTER_MODIFICATION); } + return node; } // example diff --git a/arangod/Aql/Executor.cpp b/arangod/Aql/Executor.cpp index 165dc097fb..22d27d7fdf 100644 --- a/arangod/Aql/Executor.cpp +++ b/arangod/Aql/Executor.cpp @@ -1050,7 +1050,7 @@ void Executor::generateCodeExpansion (AstNode const* node) { generateCodeNode(limitNode->getMember(0)); _buffer->appendChar(','); generateCodeNode(limitNode->getMember(1)); - _buffer->appendChar(')'); + _buffer->appendText(",true)"); } // RETURN diff --git a/arangod/Aql/Expression.cpp b/arangod/Aql/Expression.cpp index 121f36cb57..1a5ca3682f 100644 --- a/arangod/Aql/Expression.cpp +++ b/arangod/Aql/Expression.cpp @@ -833,40 +833,166 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node, else if (node->type == NODE_TYPE_EXPANSION) { TRI_ASSERT(node->numMembers() == 5); + + // LIMIT + int64_t offset = 0; + int64_t count = INT64_MAX; + + auto limitNode = node->getMember(3); + + if (limitNode->type != NODE_TYPE_NOP) { + TRI_document_collection_t const* subCollection = nullptr; + AqlValue sub = executeSimpleExpression(limitNode->getMember(0), &subCollection, trx, argv, startPos, vars, regs, false); + offset = sub.toInt64(); + sub.destroy(); + + subCollection = nullptr; + sub = executeSimpleExpression(limitNode->getMember(1), &subCollection, trx, argv, startPos, vars, regs, false); + count = sub.toInt64(); + sub.destroy(); + } + + if (offset < 0 || count <= 0) { + // no items to return... can already stop here + return AqlValue(new triagens::basics::Json(triagens::basics::Json::Array)); + } + + // FILTER + AstNode const* filterNode = node->getMember(2); + + if (filterNode->type == NODE_TYPE_NOP) { + filterNode = nullptr; + } + else if (filterNode->isConstant()) { + if (filterNode->isTrue()) { + // filter expression is always true + filterNode = nullptr; + } + else { + // filter expression is always false + return AqlValue(new triagens::basics::Json(triagens::basics::Json::Array)); + } + } + auto iterator = node->getMember(0); auto variable = static_cast(iterator->getMember(0)->getData()); - // TODO: implement flatten! auto levels = node->getIntValue(true); - TRI_document_collection_t const* myCollection = nullptr; - AqlValue value = executeSimpleExpression(node->getMember(0), &myCollection, trx, argv, startPos, vars, regs, false); + AqlValue value; - if (! value.isArray()) { + if (levels > 1) { + // flatten value... + + // generate a new temporary for the flattened array + std::unique_ptr flattened(new Json(Json::Array)); + + TRI_document_collection_t const* myCollection = nullptr; + value = executeSimpleExpression(node->getMember(0), &myCollection, trx, argv, startPos, vars, regs, false); + + if (! value.isArray()) { + // must cast value to array first + FunctionParameters parameters{ std::make_pair(value, myCollection) }; + auto res = Functions::ToArray(_ast->query(), trx, parameters); + + // destroy old value and swap with function call result + value.destroy(); + value = res; + } + + std::function flatten = [&] (TRI_json_t const* json, int64_t level) { + if (! TRI_IsArrayJson(json)) { + return; + } + + size_t const n = TRI_LengthArrayJson(json); + + for (size_t i = 0; i < n; ++i) { + auto item = static_cast(TRI_AtVector(&json->_value._objects, i)); + + bool const isArray = TRI_IsArrayJson(item); + + if (! isArray || level == levels) { + flattened->add(TRI_CopyJson(TRI_UNKNOWN_MEM_ZONE, item)); + } + else if (isArray && level < levels) { + flatten(item, level + 1); + } + } + }; + + auto subJson = value.toJson(trx, myCollection, false); + flatten(subJson.json(), 1); value.destroy(); - return AqlValue(new triagens::basics::Json(triagens::basics::Json::Array)); + + value = AqlValue(flattened.release()); + } + else { + TRI_document_collection_t const* myCollection = nullptr; + value = executeSimpleExpression(node->getMember(0), &myCollection, trx, argv, startPos, vars, regs, false); + + if (! value.isArray()) { + // must cast value to array first + FunctionParameters parameters{ std::make_pair(value, myCollection) }; + auto res = Functions::ToArray(_ast->query(), trx, parameters); + + // destroy old value and swap with function call result + value.destroy(); + value = res; + } + } + + // RETURN + // the default is to return array member unmodified + AstNode const* projectionNode = node->getMember(1); + + if (node->getMember(4)->type != NODE_TYPE_NOP) { + // return projection + projectionNode = node->getMember(4); } size_t const n = value.arraySize(); std::unique_ptr array(new Json(Json::Array, n)); - - size_t projectionNode = 1; - if (node->getMember(4)->type != NODE_TYPE_NOP) { - projectionNode = 4; - } for (size_t i = 0; i < n; ++i) { // TODO: check why we must copy the array member. will crash without copying! + TRI_document_collection_t const* myCollection = nullptr; auto arrayItem = value.extractArrayMember(trx, myCollection, i, true); setVariable(variable, arrayItem.json()); - TRI_document_collection_t const* subCollection = nullptr; - AqlValue sub = executeSimpleExpression(node->getMember(projectionNode), &subCollection, trx, argv, startPos, vars, regs, true); - array->add(sub.toJson(trx, subCollection, true)); + bool takeItem = true; + + if (filterNode != nullptr) { + // have a filter + TRI_document_collection_t const* subCollection = nullptr; + AqlValue sub = executeSimpleExpression(filterNode, &subCollection, trx, argv, startPos, vars, regs, false); + takeItem = sub.isTrue(); + sub.destroy(); + } + + if (takeItem && offset > 0) { + // there is an offset in place + --offset; + takeItem = false; + } + + if (takeItem) { + TRI_document_collection_t const* subCollection = nullptr; + AqlValue sub = executeSimpleExpression(projectionNode, &subCollection, trx, argv, startPos, vars, regs, true); + array->add(sub.toJson(trx, subCollection, true)); + } clearVariable(variable); arrayItem.destroy(); + + if (takeItem && count > 0) { + // number of items to pick was restricted + if (--count == 0) { + // done + break; + } + } } value.destroy(); diff --git a/js/server/modules/org/arangodb/aql.js b/js/server/modules/org/arangodb/aql.js index 6779db5f4b..9cd6760124 100644 --- a/js/server/modules/org/arangodb/aql.js +++ b/js/server/modules/org/arangodb/aql.js @@ -3111,7 +3111,7 @@ function AQL_SHIFT (list) { /// @brief extract a slice from an array //////////////////////////////////////////////////////////////////////////////// -function AQL_SLICE (value, from, to) { +function AQL_SLICE (value, from, to, nonNegative) { 'use strict'; if (TYPEWEIGHT(value) !== TYPEWEIGHT_ARRAY) { @@ -3122,6 +3122,10 @@ function AQL_SLICE (value, from, to) { from = AQL_TO_NUMBER(from); to = AQL_TO_NUMBER(to); + if (nonNegative && (from < 0 || to < 0)) { + return [ ]; + } + if (TYPEWEIGHT(to) === TYPEWEIGHT_NULL) { to = undefined; }