diff --git a/arangod/Aql/Ast.cpp b/arangod/Aql/Ast.cpp index 52226fdb39..27de40528e 100644 --- a/arangod/Aql/Ast.cpp +++ b/arangod/Aql/Ast.cpp @@ -169,6 +169,34 @@ void Ast::addOperation (AstNode* node) { _root->addMember(node); } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief find the bottom-most expansion subnodes (if any) +//////////////////////////////////////////////////////////////////////////////// + +AstNode const* Ast::findExpansionSubNode (AstNode const* current) const { + while (true) { + TRI_ASSERT(current->type == NODE_TYPE_EXPANSION); + + if (current->getMember(1)->type != NODE_TYPE_EXPANSION) { + return current; + } + current = current->getMember(1); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief create an AST passhthru node +/// note: this type of node is only used during parsing and optimized away later +//////////////////////////////////////////////////////////////////////////////// + +AstNode* Ast::createNodePassthru (AstNode const* what) { + AstNode* node = createNode(NODE_TYPE_PASSTHRU); + + node->addMember(what); + + return node; +} //////////////////////////////////////////////////////////////////////////////// /// @brief create an AST example node @@ -776,33 +804,61 @@ AstNode* Ast::createNodeIndexedAccess (AstNode const* accessed, } //////////////////////////////////////////////////////////////////////////////// -/// @brief create an AST expansion node, without filter +/// @brief create an AST array limit node (offset, count) //////////////////////////////////////////////////////////////////////////////// -AstNode* Ast::createNodeExpansion (AstNode const* iterator, - AstNode const* expanded, - bool multiExpand) { - AstNode* node = createNode(NODE_TYPE_EXPANSION); - node->setBoolValue(multiExpand); +AstNode* Ast::createNodeArrayLimit (AstNode const* offset, + AstNode const* count) { + AstNode* node = createNode(NODE_TYPE_ARRAY_LIMIT); - node->addMember(iterator); - node->addMember(expanded); + if (offset == nullptr) { + offset = createNodeValueInt(0); + } + node->addMember(offset); + node->addMember(count); return node; } //////////////////////////////////////////////////////////////////////////////// -/// @brief create an AST expansion node, with a filter +/// @brief create an AST expansion node, with or without a filter //////////////////////////////////////////////////////////////////////////////// -AstNode* Ast::createNodeExpansion (AstNode const* iterator, +AstNode* Ast::createNodeExpansion (int64_t levels, + AstNode const* iterator, AstNode const* expanded, - AstNode const* filter) { + AstNode const* filter, + AstNode const* limit, + AstNode const* projection) { AstNode* node = createNode(NODE_TYPE_EXPANSION); + node->setIntValue(levels); node->addMember(iterator); node->addMember(expanded); - node->addMember(filter); + + if (filter == nullptr) { + node->addMember(createNodeNop()); + } + else { + node->addMember(filter); + } + + if (limit == nullptr) { + node->addMember(createNodeNop()); + } + else { + TRI_ASSERT(limit->type == NODE_TYPE_ARRAY_LIMIT); + node->addMember(limit); + } + + if (projection == nullptr) { + node->addMember(createNodeNop()); + } + else { + node->addMember(projection); + } + + TRI_ASSERT(node->numMembers() == 5); return node; } @@ -1153,6 +1209,7 @@ AstNode* Ast::replaceVariables (AstNode* node, if (variable != nullptr) { auto it = replacements.find(variable->id); + if (it != replacements.end()) { // overwrite the node in place node->setData((*it).second); @@ -1296,6 +1353,12 @@ void Ast::validateAndOptimize () { return this->optimizeTernaryOperator(node); } + // passthru node + if (node->type == NODE_TYPE_PASSTHRU) { + // optimize away passthru node. this type of node is only used during parsing + return node->getMember(0); + } + // call to built-in function if (node->type == NODE_TYPE_FCALL) { auto func = static_cast(node->getData()); diff --git a/arangod/Aql/Ast.h b/arangod/Aql/Ast.h index 058ae98793..04d282443e 100644 --- a/arangod/Aql/Ast.h +++ b/arangod/Aql/Ast.h @@ -214,6 +214,19 @@ namespace triagens { void addOperation (AstNode*); +//////////////////////////////////////////////////////////////////////////////// +/// @brief find the bottom-most expansion subnodes (if any) +//////////////////////////////////////////////////////////////////////////////// + + AstNode const* findExpansionSubNode (AstNode const*) const; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief create an AST passthru node +/// note: this type of node is only used during parsing and optimized away later +//////////////////////////////////////////////////////////////////////////////// + + AstNode* createNodePassthru (AstNode const*); + //////////////////////////////////////////////////////////////////////////////// /// @brief create an AST example node //////////////////////////////////////////////////////////////////////////////// @@ -446,18 +459,20 @@ namespace triagens { AstNode const*); //////////////////////////////////////////////////////////////////////////////// -/// @brief create an AST expansion node, without filter +/// @brief create an AST array limit node (offset, count) //////////////////////////////////////////////////////////////////////////////// - AstNode* createNodeExpansion (AstNode const*, + AstNode* createNodeArrayLimit (AstNode const*, + AstNode const*); + +//////////////////////////////////////////////////////////////////////////////// +/// @brief create an AST expansion node +//////////////////////////////////////////////////////////////////////////////// + + AstNode* createNodeExpansion (int64_t, + AstNode const*, + AstNode const*, AstNode const*, - bool); - -//////////////////////////////////////////////////////////////////////////////// -/// @brief create an AST expansion node, with a filter -//////////////////////////////////////////////////////////////////////////////// - - AstNode* createNodeExpansion (AstNode const*, AstNode const*, AstNode const*); diff --git a/arangod/Aql/AstNode.cpp b/arangod/Aql/AstNode.cpp index 36088216e7..1f04dd1b7c 100644 --- a/arangod/Aql/AstNode.cpp +++ b/arangod/Aql/AstNode.cpp @@ -146,7 +146,9 @@ std::unordered_map const AstNode::TypeNames{ { static_cast(NODE_TYPE_COLLECT_COUNT), "collect count" }, { static_cast(NODE_TYPE_COLLECT_EXPRESSION), "collect expression" }, { static_cast(NODE_TYPE_CALCULATED_OBJECT_ELEMENT),"calculated object element" }, - { static_cast(NODE_TYPE_EXAMPLE), "example" } + { static_cast(NODE_TYPE_EXAMPLE), "example" }, + { static_cast(NODE_TYPE_PASSTHRU), "passthru" }, + { static_cast(NODE_TYPE_ARRAY_LIMIT), "array limit" } }; //////////////////////////////////////////////////////////////////////////////// @@ -515,6 +517,10 @@ AstNode::AstNode (Ast* ast, setStringValue(query->registerString(JsonHelper::getStringValue(json.json(), "name", ""), false)); break; } + case NODE_TYPE_EXPANSION: { + setIntValue(JsonHelper::checkAndGetNumericValue(json.json(), "levels")); + break; + } case NODE_TYPE_OBJECT: case NODE_TYPE_ROOT: case NODE_TYPE_FOR: @@ -555,13 +561,14 @@ AstNode::AstNode (Ast* ast, case NODE_TYPE_SUBQUERY: case NODE_TYPE_BOUND_ATTRIBUTE_ACCESS: case NODE_TYPE_INDEXED_ACCESS: - case NODE_TYPE_EXPANSION: case NODE_TYPE_ITERATOR: case NODE_TYPE_ARRAY: case NODE_TYPE_RANGE: case NODE_TYPE_NOP: case NODE_TYPE_CALCULATED_OBJECT_ELEMENT: case NODE_TYPE_EXAMPLE: + case NODE_TYPE_PASSTHRU: + case NODE_TYPE_ARRAY_LIMIT: break; } @@ -732,7 +739,7 @@ TRI_json_t* AstNode::toJsonValue (TRI_memory_zone_t* zone) const { } for (size_t i = 0; i < n; ++i) { - auto member = getMember(i); + auto member = getMemberUnchecked(i); if (member != nullptr) { TRI_json_t* j = member->toJsonValue(zone); @@ -750,7 +757,7 @@ TRI_json_t* AstNode::toJsonValue (TRI_memory_zone_t* zone) const { TRI_json_t* object = TRI_CreateObjectJson(zone, n); for (size_t i = 0; i < n; ++i) { - auto member = getMember(i); + auto member = getMemberUnchecked(i); if (member != nullptr) { TRI_json_t* j = member->getMember(0)->toJsonValue(zone); @@ -862,9 +869,13 @@ TRI_json_t* AstNode::toJson (TRI_memory_zone_t* zone, TRI_Insert3ObjectJson(zone, node, "name", TRI_CreateStringCopyJson(zone, variable->name.c_str(), variable->name.size())); TRI_Insert3ObjectJson(zone, node, "id", TRI_CreateNumberJson(zone, static_cast(variable->id))); } + + if (type == NODE_TYPE_EXPANSION) { + TRI_Insert3ObjectJson(zone, node, "levels", TRI_CreateNumberJson(zone, static_cast(getIntValue(true)))); + } // dump sub-nodes - size_t const n = members._length; + size_t const n = TRI_LengthVectorPointer(&members); if (n > 0) { TRI_json_t* subNodes = TRI_CreateArrayJson(zone, n); @@ -876,8 +887,8 @@ TRI_json_t* AstNode::toJson (TRI_memory_zone_t* zone, try { for (size_t i = 0; i < n; ++i) { - AstNode* member = getMember(i); - if (member != nullptr && member->type != NODE_TYPE_NOP) { + AstNode* member = getMemberUnchecked(i); + if (member != nullptr) { member->toJson(subNodes, zone, verbose); } } @@ -1266,7 +1277,8 @@ bool AstNode::isSimple () const { type == NODE_TYPE_OPERATOR_BINARY_IN || type == NODE_TYPE_OPERATOR_BINARY_NIN || type == NODE_TYPE_RANGE || - type == NODE_TYPE_INDEXED_ACCESS) { + type == NODE_TYPE_INDEXED_ACCESS || + type == NODE_TYPE_PASSTHRU) { // a logical operator is simple if its operands are simple // a comparison operator is simple if both bounds are simple // a range is simple if both bounds are simple @@ -1687,6 +1699,16 @@ void AstNode::stringify (triagens::basics::StringBuffer* buffer, buffer->appendChar(')'); return; } + + if (type == NODE_TYPE_ARRAY_LIMIT) { + // not used by V8 + buffer->appendText("_LIMIT("); + getMember(0)->stringify(buffer, verbose, failIfLong); + buffer->appendChar(','); + getMember(1)->stringify(buffer, verbose, failIfLong); + buffer->appendChar(')'); + return; + } if (type == NODE_TYPE_EXPANSION) { // not used by V8 @@ -1694,10 +1716,27 @@ void AstNode::stringify (triagens::basics::StringBuffer* buffer, getMember(0)->stringify(buffer, verbose, failIfLong); buffer->appendChar(','); getMember(1)->stringify(buffer, verbose, failIfLong); - if (numMembers() > 2) { - buffer->appendChar(','); - getMember(2)->stringify(buffer, verbose, failIfLong); + // filter + buffer->appendChar(','); + + auto filterNode = getMember(2); + if (filterNode != nullptr) { + buffer->appendText(" FILTER "); + filterNode->getMember(0)->stringify(buffer, verbose, failIfLong); } + auto limitNode = getMember(3); + if (limitNode != nullptr) { + buffer->appendText(" LIMIT "); + limitNode->getMember(0)->stringify(buffer, verbose, failIfLong); + buffer->appendChar(','); + limitNode->getMember(1)->stringify(buffer, verbose, failIfLong); + } + auto returnNode = getMember(4); + if (returnNode != nullptr) { + buffer->appendText(" RETURN "); + returnNode->getMember(0)->stringify(buffer, verbose, failIfLong); + } + buffer->appendChar(')'); return; } diff --git a/arangod/Aql/AstNode.h b/arangod/Aql/AstNode.h index 26380da3b5..52fe13fc9e 100644 --- a/arangod/Aql/AstNode.h +++ b/arangod/Aql/AstNode.h @@ -171,7 +171,9 @@ namespace triagens { NODE_TYPE_COLLECT_EXPRESSION = 52, NODE_TYPE_CALCULATED_OBJECT_ELEMENT = 53, NODE_TYPE_UPSERT = 54, - NODE_TYPE_EXAMPLE = 55 + NODE_TYPE_EXAMPLE = 55, + NODE_TYPE_PASSTHRU = 56, + NODE_TYPE_ARRAY_LIMIT = 57 }; static_assert(NODE_TYPE_VALUE < NODE_TYPE_ARRAY, "incorrect node types"); @@ -509,7 +511,7 @@ namespace triagens { /// @brief return the number of members //////////////////////////////////////////////////////////////////////////////// - inline size_t numMembers () const { + inline size_t numMembers () const throw() { return members._length; } diff --git a/arangod/Aql/Executor.cpp b/arangod/Aql/Executor.cpp index cfd149fb50..f01f4391a0 100644 --- a/arangod/Aql/Executor.cpp +++ b/arangod/Aql/Executor.cpp @@ -262,6 +262,12 @@ std::unordered_map const Executor::FunctionNames{ size_t const Executor::DefaultLiteralSizeThreshold = 32; +//////////////////////////////////////////////////////////////////////////////// +/// @brief maxmium number of array members created from range accesses +//////////////////////////////////////////////////////////////////////////////// + +int64_t const Executor::MaxRangeAccessArraySize = 1024 * 1024 * 32; + // ----------------------------------------------------------------------------- // --SECTION-- constructors / destructors // ----------------------------------------------------------------------------- @@ -305,7 +311,7 @@ V8Expression* Executor::generateExpression (AstNode const* node) { // std::cout << "Executor::generateExpression: " << std::string(_buffer->c_str(), _buffer->length()) << "\n"; v8::Handle constantValues = v8::Object::New(isolate); - for (auto& it : _constantRegisters) { + for (auto const& it : _constantRegisters) { std::string name = "r"; name.append(std::to_string(it.second)); @@ -657,11 +663,56 @@ void Executor::generateCodeArray (AstNode const* node) { _buffer->appendChar(','); } - generateCodeNode(node->getMember(i)); + generateCodeNode(node->getMemberUnchecked(i)); } _buffer->appendChar(']'); } +//////////////////////////////////////////////////////////////////////////////// +/// @brief generate JavaScript code for an array +//////////////////////////////////////////////////////////////////////////////// + +void Executor::generateCodeForcedArray (AstNode const* node, + int64_t levels) { + TRI_ASSERT(node != nullptr); + + if (levels > 1) { + _buffer->appendText("_AQL.AQL_FLATTEN("); + } + + bool castToArray = true; + if (node->type == NODE_TYPE_ARRAY) { + // value is an array already + castToArray = false; + } + else if (node->type == NODE_TYPE_EXPANSION && + node->getMember(0)->type == NODE_TYPE_ARRAY) { + // value is an expansion over an array + castToArray = false; + } + else if (node->type == NODE_TYPE_ITERATOR && + node->getMember(1)->type == NODE_TYPE_ARRAY) { + castToArray = false; + } + + if (castToArray) { + // force the value to be an array + _buffer->appendText("_AQL.AQL_TO_ARRAY("); + generateCodeNode(node); + _buffer->appendChar(')'); + } + else { + // value already is an array + generateCodeNode(node); + } + + if (levels > 1) { + _buffer->appendChar(','); + _buffer->appendInteger(levels - 1); + _buffer->appendChar(')'); + } +} + //////////////////////////////////////////////////////////////////////////////// /// @brief generate JavaScript code for an object //////////////////////////////////////////////////////////////////////////////// @@ -689,7 +740,7 @@ void Executor::generateCodeDynamicObject (AstNode const* node) { _buffer->appendText("(function() { var o={};"); for (size_t i = 0; i < n; ++i) { - auto member = node->getMember(i); + auto member = node->getMemberUnchecked(i); if (member->type == NODE_TYPE_OBJECT_ELEMENT) { _buffer->appendText("o[", 2); @@ -963,47 +1014,66 @@ void Executor::generateCodeUserFunctionCall (AstNode const* node) { void Executor::generateCodeExpansion (AstNode const* node) { TRI_ASSERT(node != nullptr); - TRI_ASSERT(node->numMembers() >= 2); + + TRI_ASSERT(node->numMembers() == 5); + + auto levels = node->getIntValue(true); auto iterator = node->getMember(0); auto variable = static_cast(iterator->getMember(0)->getData()); - _buffer->appendText("(function () { return _AQL.AQL_TO_LIST("); - if (node->getBoolValue()) { - _buffer->appendText("_AQL.COMPACT("); - generateCodeNode(node->getMember(0)); - _buffer->appendText(")"); - } - else { - generateCodeNode(node->getMember(0)); + // start LIMIT + auto limitNode = node->getMember(3); + + if (limitNode->type != NODE_TYPE_NOP) { + _buffer->appendText("_AQL.AQL_SLICE("); } - if (node->numMembers() > 2) { - // use a filter expression - _buffer->appendText(").filter(function (v) { "); + generateCodeForcedArray(node->getMember(0), levels); + + // FILTER + auto filterNode = node->getMember(2); + + if (filterNode->type != NODE_TYPE_NOP) { + _buffer->appendText(".filter(function (v) { "); _buffer->appendText("vars[\""); _buffer->appendText(variable->name); _buffer->appendText("\"]=v; "); _buffer->appendText("return _AQL.AQL_TO_BOOL("); - generateCodeNode(node->getMember(2)); - _buffer->appendText("); }"); + generateCodeNode(filterNode); + _buffer->appendText("); })"); } - _buffer->appendText(").map(function (v) { "); + // finish LIMIT + if (limitNode->type != NODE_TYPE_NOP) { + _buffer->appendChar(','); + generateCodeNode(limitNode->getMember(0)); + _buffer->appendChar(','); + generateCodeNode(limitNode->getMember(1)); + _buffer->appendChar(')'); + } + + // RETURN + _buffer->appendText(".map(function (v) { "); _buffer->appendText("vars[\""); _buffer->appendText(variable->name); _buffer->appendText("\"]=v; "); _buffer->appendText("return "); - generateCodeNode(node->getMember(1)); - _buffer->appendText("; }); })()"); + if (node->getMember(4)->type != NODE_TYPE_NOP) { + generateCodeNode(node->getMember(4)); + } + else { + generateCodeNode(node->getMember(1)); + } + _buffer->appendText("; })"); } //////////////////////////////////////////////////////////////////////////////// /// @brief generate JavaScript code for an expansion iterator //////////////////////////////////////////////////////////////////////////////// -void Executor::generateCodeExpandIterator (AstNode const* node) { +void Executor::generateCodeExpansionIterator (AstNode const* node) { TRI_ASSERT(node != nullptr); TRI_ASSERT(node->numMembers() == 2); @@ -1064,25 +1134,12 @@ void Executor::generateCodeIndexedAccess (AstNode const* node) { TRI_ASSERT(node != nullptr); TRI_ASSERT(node->numMembers() == 2); - auto index = node->getMember(1); - if (index->type == NODE_TYPE_RANGE) { - // range access - _buffer->appendText("_AQL.GET_RANGE("); - generateCodeNode(node->getMember(0)); - _buffer->appendChar(','); - generateCodeNode(index->getMember(0)); - _buffer->appendChar(','); - generateCodeNode(index->getMember(1)); - _buffer->appendChar(')'); - } - else { - // indexed access - _buffer->appendText("_AQL.GET_INDEX("); - generateCodeNode(node->getMember(0)); - _buffer->appendChar(','); - generateCodeNode(index); - _buffer->appendChar(')'); - } + // indexed access + _buffer->appendText("_AQL.GET_INDEX("); + generateCodeNode(node->getMember(0)); + _buffer->appendChar(','); + generateCodeNode(node->getMember(1)); + _buffer->appendChar(')'); } //////////////////////////////////////////////////////////////////////////////// @@ -1148,13 +1205,13 @@ void Executor::generateCodeNode (AstNode const* node) { case NODE_TYPE_FCALL_USER: generateCodeUserFunctionCall(node); break; - + case NODE_TYPE_EXPANSION: generateCodeExpansion(node); break; case NODE_TYPE_ITERATOR: - generateCodeExpandIterator(node); + generateCodeExpansionIterator(node); break; case NODE_TYPE_RANGE: @@ -1175,11 +1232,13 @@ void Executor::generateCodeNode (AstNode const* node) { case NODE_TYPE_VARIABLE: case NODE_TYPE_PARAMETER: + case NODE_TYPE_PASSTHRU: + case NODE_TYPE_ARRAY_LIMIT: // we're not expecting these types here - THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "unexpected node type in code generator"); + THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "unexpected node type in generateCodeNode"); default: - THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_NOT_IMPLEMENTED, "node type not implemented"); + THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_NOT_IMPLEMENTED, "node type not implemented in generateCodeNode"); } } @@ -1195,7 +1254,7 @@ triagens::basics::StringBuffer* Executor::initializeBuffer () { THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); } - _buffer->reserve(256); + _buffer->reserve(512); } else { _buffer->clear(); diff --git a/arangod/Aql/Executor.h b/arangod/Aql/Executor.h index bd77ff6154..ebcc2c63f6 100644 --- a/arangod/Aql/Executor.h +++ b/arangod/Aql/Executor.h @@ -150,6 +150,13 @@ namespace triagens { void generateCodeArray (AstNode const*); +//////////////////////////////////////////////////////////////////////////////// +/// @brief generate JavaScript code for a forced array +//////////////////////////////////////////////////////////////////////////////// + + void generateCodeForcedArray (AstNode const*, + int64_t); + //////////////////////////////////////////////////////////////////////////////// /// @brief generate JavaScript code for an object //////////////////////////////////////////////////////////////////////////////// @@ -228,7 +235,7 @@ namespace triagens { /// @brief generate JavaScript code for an expansion iterator //////////////////////////////////////////////////////////////////////////////// - void generateCodeExpandIterator (AstNode const*); + void generateCodeExpansionIterator (AstNode const*); //////////////////////////////////////////////////////////////////////////////// /// @brief generate JavaScript code for a range (i.e. 1..10) @@ -308,12 +315,24 @@ namespace triagens { static std::unordered_map const FunctionNames; +// ----------------------------------------------------------------------------- +// --SECTION-- public static variables +// ----------------------------------------------------------------------------- + + public: + //////////////////////////////////////////////////////////////////////////////// /// @brief minimum number of array members / object attributes for considering /// an array / object literal "big" and pulling it out of the expression //////////////////////////////////////////////////////////////////////////////// static size_t const DefaultLiteralSizeThreshold; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief maxmium number of array members created from range accesses +//////////////////////////////////////////////////////////////////////////////// + + static int64_t const MaxRangeAccessArraySize; }; } diff --git a/arangod/Aql/Expression.cpp b/arangod/Aql/Expression.cpp index 845e67747e..b20b5f3d1e 100644 --- a/arangod/Aql/Expression.cpp +++ b/arangod/Aql/Expression.cpp @@ -281,78 +281,6 @@ void Expression::invalidate () { // --SECTION-- private methods // ----------------------------------------------------------------------------- -//////////////////////////////////////////////////////////////////////////////// -/// @brief extracts a range from an array -//////////////////////////////////////////////////////////////////////////////// - -AqlValue Expression::extractRange (AqlValue const& result, - AqlValue const& indexResult, - TRI_document_collection_t const* collection, - triagens::arango::AqlTransaction* trx) const { - TRI_ASSERT(result.isArray()); - - size_t const length = result.arraySize(); - - if (length == 0) { - // cannot extract anything from an empty array - return AqlValue(new Json(Json::Array)); - } - - int64_t low = indexResult.getRange()->_low; - int64_t high = indexResult.getRange()->_high; - - // negative positions are translated into their positive counterparts - if (low < 0) { - low = static_cast(length) + low; - } - if (high < 0) { - high = static_cast(length) + high; - } - - std::unique_ptr array(new Json(Json::Array)); - - if (low <= high) { - // forward iteration - ++high; - if (low < high) { - if (low < 0) { - low = 0; - } - if (high >= static_cast(length)) { - high = static_cast(length); - } - if (low < high) { - array->reserve(static_cast(high - low)); - for (int64_t position = low; position < high; ++position) { - auto j = result.extractArrayMember(trx, collection, position, true); - array->add(j); - } - } - } - } - else { - // backward iteration - --high; - if (low >= static_cast(length)) { - low = static_cast(length) - 1; - } - if (high < -1) { - high = -1; - } - - if (low - high > 0) { - array->reserve(static_cast(low - high)); - // array->reserve(static_cast(high - low + 1)); - for (int64_t position = low; position > high; --position) { - auto j = result.extractArrayMember(trx, collection, position, true); - array->add(j); - } - } - } - - return AqlValue(array.release()); -} - //////////////////////////////////////////////////////////////////////////////// /// @brief find a value in an AQL list node /// this performs either a binary search (if the node is sorted) or a @@ -592,19 +520,6 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node, // no number found. } } - else if (indexResult.isRange()) { - try { - auto range = extractRange(result, indexResult, myCollection, trx); - indexResult.destroy(); - result.destroy(); - return range; - } - catch (...) { - indexResult.destroy(); - result.destroy(); - throw; - } - } // fall-through to returning null } else if (result.isObject()) { diff --git a/arangod/Aql/Expression.h b/arangod/Aql/Expression.h index 83a5a8ac89..212776d197 100644 --- a/arangod/Aql/Expression.h +++ b/arangod/Aql/Expression.h @@ -296,15 +296,6 @@ namespace triagens { private: -//////////////////////////////////////////////////////////////////////////////// -/// @brief extracts a range from an array -//////////////////////////////////////////////////////////////////////////////// - - AqlValue extractRange (AqlValue const&, - AqlValue const&, - TRI_document_collection_t const*, - triagens::arango::AqlTransaction*) const; - //////////////////////////////////////////////////////////////////////////////// /// @brief find a value in an array //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/OptimizerRules.cpp b/arangod/Aql/OptimizerRules.cpp index 874bab725c..643de23fad 100644 --- a/arangod/Aql/OptimizerRules.cpp +++ b/arangod/Aql/OptimizerRules.cpp @@ -1611,6 +1611,7 @@ int triagens::aql::removeRedundantCalculationsRule (Optimizer* opt, auto target = outvars[0]; while (target != nullptr) { auto it = replacements.find(target->id); + if (it != replacements.end()) { target = (*it).second; } @@ -1656,7 +1657,6 @@ int triagens::aql::removeRedundantCalculationsRule (Optimizer* opt, if (! replacements.empty()) { // finally replace the variables - RedundantCalculationsReplacer finder(replacements); plan->root()->walk(&finder); plan->findVarUsage(); diff --git a/arangod/Aql/Scopes.cpp b/arangod/Aql/Scopes.cpp index 1fa65fcd0c..0759c33ce2 100644 --- a/arangod/Aql/Scopes.cpp +++ b/arangod/Aql/Scopes.cpp @@ -268,7 +268,9 @@ Variable const* Scopes::getCurrentVariable () const { if (_currentVariables.empty()) { THROW_ARANGO_EXCEPTION_PARAMS(TRI_ERROR_QUERY_VARIABLE_NAME_UNKNOWN, Variable::NAME_CURRENT); } - return _currentVariables.back(); + auto result = _currentVariables.back(); + TRI_ASSERT(result != nullptr); + return result; } //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/grammar.y b/arangod/Aql/grammar.y index a3e5936612..0bf448ae8b 100644 --- a/arangod/Aql/grammar.y +++ b/arangod/Aql/grammar.y @@ -140,10 +140,10 @@ void Aqlerror (YYLTYPE* locp, %left T_PLUS T_MINUS %left T_TIMES T_DIV T_MOD %right UMINUS UPLUS T_NOT -%left EXPANSION %left FUNCCALL %left REFERENCE %left INDEXED +%left EXPANSION %left T_SCOPE /* define token return types */ @@ -180,6 +180,10 @@ void Aqlerror (YYLTYPE* locp, %type object_elements_list; %type object_element; %type object_element_name; +%type array_filter_operator; +%type optional_array_filter; +%type optional_array_limit; +%type optional_array_return; %type reference; %type simple_value; %type value_literal; @@ -971,6 +975,45 @@ object_element: } ; +array_filter_operator: + T_TIMES { + $$ = 1; + } + | array_filter_operator T_TIMES { + $$ = $1 + 1; + } + ; + +optional_array_filter: + /* empty */ { + $$ = nullptr; + } + | T_FILTER expression { + $$ = $2; + } + ; + +optional_array_limit: + /* empty */ { + $$ = nullptr; + } + | T_LIMIT expression { + $$ = parser->ast()->createNodeArrayLimit(nullptr, $2); + } + | T_LIMIT expression T_COMMA expression { + $$ = parser->ast()->createNodeArrayLimit($2, $4); + } + ; + +optional_array_return: + /* empty */ { + $$ = nullptr; + } + | T_RETURN expression { + $$ = $2; + } + ; + reference: T_STRING { // variable or collection @@ -1028,7 +1071,14 @@ reference: } } | T_OPEN expression T_CLOSE { - $$ = $2; + if ($2->type == NODE_TYPE_EXPANSION) { + // create a dummy passthru node that reduces and evaluates the expansion first + // and the expansion on top of the stack won't be chained with any other expansions + $$ = parser->ast()->createNodePassthru($2); + } + else { + $$ = $2; + } } | T_OPEN { if (parser->isModificationQuery()) { @@ -1050,8 +1100,11 @@ reference: // named variable access, e.g. variable.reference if ($1->type == NODE_TYPE_EXPANSION) { // if left operand is an expansion already... - // patch the existing expansion - $1->changeMember(1, parser->ast()->createNodeAttributeAccess($1->getMember(1), $3)); + // dive into the expansion's right-hand child nodes for further expansion and + // patch the bottom-most one + auto current = const_cast(parser->ast()->findExpansionSubNode($1)); + TRI_ASSERT(current->type == NODE_TYPE_EXPANSION); + current->changeMember(1, parser->ast()->createNodeAttributeAccess(current->getMember(1), $3)); $$ = $1; } else { @@ -1063,7 +1116,9 @@ reference: if ($1->type == NODE_TYPE_EXPANSION) { // if left operand is an expansion already... // patch the existing expansion - $1->changeMember(1, parser->ast()->createNodeBoundAttributeAccess($1->getMember(1), $3)); + auto current = const_cast(parser->ast()->findExpansionSubNode($1)); + TRI_ASSERT(current->type == NODE_TYPE_EXPANSION); + current->changeMember(1, parser->ast()->createNodeBoundAttributeAccess(current->getMember(1), $3)); $$ = $1; } else { @@ -1075,48 +1130,62 @@ reference: if ($1->type == NODE_TYPE_EXPANSION) { // if left operand is an expansion already... // patch the existing expansion - $1->changeMember(1, parser->ast()->createNodeIndexedAccess($1->getMember(1), $3)); + auto current = const_cast(parser->ast()->findExpansionSubNode($1)); + TRI_ASSERT(current->type == NODE_TYPE_EXPANSION); + current->changeMember(1, parser->ast()->createNodeIndexedAccess(current->getMember(1), $3)); $$ = $1; } else { $$ = parser->ast()->createNodeIndexedAccess($1, $3); } } - | reference T_ARRAY_OPEN T_TIMES T_ARRAY_CLOSE %prec EXPANSION { - // variable expansion, e.g. variable[*] + | reference T_ARRAY_OPEN array_filter_operator { + // variable expansion, e.g. variable[*], with optional FILTER, LIMIT and RETURN clauses + if ($3 > 1 && $1->type == NODE_TYPE_EXPANSION) { + // create a dummy passthru node that reduces and evaluates the expansion first + // and the expansion on top of the stack won't be chained with any other expansions + $1 = parser->ast()->createNodePassthru($1); + } + // create a temporary iterator variable std::string const nextName = parser->ast()->variables()->nextName() + "_"; char const* iteratorName = nextName.c_str(); - auto iterator = parser->ast()->createNodeIterator(iteratorName, $1); - $$ = parser->ast()->createNodeExpansion(iterator, parser->ast()->createNodeReference(iteratorName), false); - } - | reference T_ARRAY_OPEN T_TIMES T_TIMES T_ARRAY_CLOSE %prec EXPANSION { - // variable expansion, e.g. variable[**] - // create a temporary iterator variable - std::string const nextName = parser->ast()->variables()->nextName() + "_"; - char const* iteratorName = nextName.c_str(); - auto iterator = parser->ast()->createNodeIterator(iteratorName, $1); - $$ = parser->ast()->createNodeExpansion(iterator, parser->ast()->createNodeReference(iteratorName), true); - } - | reference T_ARRAY_OPEN T_FILTER { - // variable expansion, e.g. variable[*] - // create a temporary iterator variable - std::string const nextName = parser->ast()->variables()->nextName() + "_"; - char const* iteratorName = nextName.c_str(); - auto iterator = parser->ast()->createNodeIterator(iteratorName, $1); - parser->pushStack(iterator); + + if ($1->type == NODE_TYPE_EXPANSION) { + auto iterator = parser->ast()->createNodeIterator(iteratorName, $1->getMember(1)); + parser->pushStack(iterator); + } + else { + auto iterator = parser->ast()->createNodeIterator(iteratorName, $1); + parser->pushStack(iterator); + } auto scopes = parser->ast()->scopes(); scopes->stackCurrentVariable(scopes->getVariable(iteratorName)); - } expression T_ARRAY_CLOSE %prec EXPANSION { + } optional_array_filter optional_array_limit optional_array_return T_ARRAY_CLOSE %prec EXPANSION { auto scopes = parser->ast()->scopes(); scopes->unstackCurrentVariable(); - auto iterator = static_cast(parser->popStack()); + auto iterator = static_cast(parser->popStack()); auto variableNode = iterator->getMember(0); TRI_ASSERT(variableNode->type == NODE_TYPE_VARIABLE); auto variable = static_cast(variableNode->getData()); - $$ = parser->ast()->createNodeExpansion(iterator, parser->ast()->createNodeReference(variable->name.c_str()), $5); + + if ($1->type == NODE_TYPE_EXPANSION) { + auto expand = parser->ast()->createNodeExpansion($3, iterator, parser->ast()->createNodeReference(variable->name.c_str()), $5, $6, $7); + $1->changeMember(1, expand); + $$ = $1; +/* + auto current = const_cast(parser->ast()->findExpansionSubNode($1)); + TRI_ASSERT(current->type == NODE_TYPE_EXPANSION); + auto expand = parser->ast()->createNodeExpansion($3, iterator, parser->ast()->createNodeReference(variable->name.c_str()), $5, $6, $7); + current->changeMember(1, expand); + $$ = $1; +*/ + } + else { + $$ = parser->ast()->createNodeExpansion($3, iterator, parser->ast()->createNodeReference(variable->name.c_str()), $5, $6, $7); + } } ; diff --git a/js/server/modules/org/arangodb/aql.js b/js/server/modules/org/arangodb/aql.js index c0897c7723..d948217023 100644 --- a/js/server/modules/org/arangodb/aql.js +++ b/js/server/modules/org/arangodb/aql.js @@ -986,85 +986,6 @@ function GET_INDEX (value, index) { return result; } -//////////////////////////////////////////////////////////////////////////////// -/// @brief get an indexed value from an array or document (e.g. users[3]) -//////////////////////////////////////////////////////////////////////////////// - -function GET_RANGE (value, low, high) { - 'use strict'; - - if (TYPEWEIGHT(value) !== TYPEWEIGHT_ARRAY) { - return null; - } - - low = parseInt(low, 10); - high = parseInt(high, 10); - - var result = [ ], position; - - if (low < 0) { - low = value.length + low; - } - if (high < 0) { - high = value.length + high; - } - - if (low <= high) { - ++high; - if (low < high) { - if (low < 0) { - low = 0; - } - if (high >= value.length) { - high = value.length; - } - if (low < high) { - for (position = low; position < high; ++position) { - result.push(value[position]); - } - } - } - } - else { - --high; - if (low >= value.length) { - low = value.length - 1; - } - if (high < -1) { - high = -1; - } - if (low - high > 0) { - for (position = low; position > high; --position) { - result.push(value[position]); - } - } - } - - return result; -} - -function COMPACT (value) { - 'use strict'; - - if (TYPEWEIGHT(value) !== TYPEWEIGHT_ARRAY) { - return [ ]; - } - - var result = [ ]; - for (var i = 0; i < value.length; ++i) { - var v = value[i]; - if (TYPEWEIGHT(v) === TYPEWEIGHT_ARRAY) { - for (var j = 0; j < v.length; ++j) { - result.push(v[j]); - } - } - else { - result.push(v); - } - } - return result; -} - //////////////////////////////////////////////////////////////////////////////// /// @brief normalize a value for comparison, sorting etc. //////////////////////////////////////////////////////////////////////////////// @@ -3187,7 +3108,7 @@ function AQL_SHIFT (list) { } //////////////////////////////////////////////////////////////////////////////// -/// @brief extract a slice from a list +/// @brief extract a slice from an array //////////////////////////////////////////////////////////////////////////////// function AQL_SLICE (value, from, to) { @@ -8435,7 +8356,6 @@ function AQL_GRAPH_DIAMETER (graphName, options) { exports.FCALL_USER = FCALL_USER; exports.KEYS = KEYS; exports.GET_INDEX = GET_INDEX; -exports.GET_RANGE = GET_RANGE; exports.DOCUMENT_MEMBER = DOCUMENT_MEMBER; exports.GET_DOCUMENTS = GET_DOCUMENTS; exports.TERNARY_OPERATOR = TERNARY_OPERATOR; @@ -8458,7 +8378,6 @@ exports.ARITHMETIC_MINUS = ARITHMETIC_MINUS; exports.ARITHMETIC_TIMES = ARITHMETIC_TIMES; exports.ARITHMETIC_DIVIDE = ARITHMETIC_DIVIDE; exports.ARITHMETIC_MODULUS = ARITHMETIC_MODULUS; -exports.COMPACT = COMPACT; exports.AQL_DOCUMENT = AQL_DOCUMENT; exports.AQL_COLLECTIONS = AQL_COLLECTIONS; diff --git a/js/server/tests/aql-array-access.js b/js/server/tests/aql-array-access.js index 1330bae9d0..b23d526cd3 100644 --- a/js/server/tests/aql-array-access.js +++ b/js/server/tests/aql-array-access.js @@ -35,14 +35,59 @@ var jsunity = require("jsunity"); //////////////////////////////////////////////////////////////////////////////// function arrayAccessTestSuite () { - var values = [ + var persons = [ { name: "sir alfred", age: 60, loves: [ "lettuce", "flowers" ] }, { person: { name: "gadgetto", age: 50, loves: "gadgets" } }, { name: "everybody", loves: "sunshine" }, { name: "judge", loves: [ "order", "policing", "weapons" ] }, - "someone" + "something" + ]; + + var continents = [ + { + name: "Europe", countries: [ + { id: "UK", capital: "London" }, + { id: "FR", capital: "Paris" }, + { id: "IT", capital: "Rome" } + ] + }, + { + name: "Asia", countries: [ + { id: "JP", capital: "Tokyo" }, + { id: "CN", capital: "Beijing" }, + { id: "KR", capital: "Seoul" }, + { id: "IN", capital: "Delhi" } + ] + } + ]; + + var tags = [ + { + values: [ + { name: "javascript", count: 2, payload: [ { name: "foo" }, { name: "bar" } ] }, + { name: "cpp", count: 4, payload: [ { name: "baz" }, { name: "qux" } ] }, + { name: "php", count: 2, payload: [ ] }, + { name: "ruby", count: 5, payload: [ { name: "test" }, { name: 23 } ] }, + { name: "python", count: 3, payload: [ { name: "one" }, { name: "two" }, { name: "three" }, { name: "four" } ] } + ] + } ]; + var arrayOfArrays = [ + [ + [ "one", "two", "three" ], + [ "four", "five" ], + [ "six" ] + ], + [ + [ "seven", "eight" ] + ], + [ + [ "nine", "ten" ], + [ "eleven", "twelve" ] + ] + ]; + return { //////////////////////////////////////////////////////////////////////////////// @@ -154,296 +199,512 @@ function arrayAccessTestSuite () { //////////////////////////////////////////////////////////////////////////////// testSubqueryResult : function () { - for (var i = 0; i < values.length; ++i) { - var result = AQL_EXECUTE("RETURN (FOR value IN @values RETURN value)[" + i + "]", { values: values }).json; - assertEqual([ values[i] ], result); + for (var i = 0; i < persons.length; ++i) { + var result = AQL_EXECUTE("RETURN (FOR value IN @persons RETURN value)[" + i + "]", { persons: persons }).json; + assertEqual([ persons[i] ], result); } }, -//////////////////////////////////////////////////////////////////////////////// -/// @brief test range result -//////////////////////////////////////////////////////////////////////////////// - - testSubqueryResultRangeForward1 : function () { - for (var to = 0; to < 10; ++to) { - var result = AQL_EXECUTE("RETURN (FOR value IN @values RETURN value)[0.." + to + "]", { values: values }).json; - var expected = [ ]; - for (var i = 0; i < Math.min(to + 1, values.length); ++i) { - expected.push(values[i]); - } - assertEqual([ expected ], result); - } + testStarExtractScalar : function () { + var expected = [ "Europe", "Asia" ]; + var result = AQL_EXECUTE("RETURN @value[*].name", { value : continents }).json; + assertEqual([ expected ], result); }, -//////////////////////////////////////////////////////////////////////////////// -/// @brief test range result -//////////////////////////////////////////////////////////////////////////////// - - testSubqueryResultRangeForward2 : function () { - for (var from = 0; from < 10; ++from) { - var result = AQL_EXECUTE("RETURN (FOR value IN @values RETURN value)[" + from + "..99]", { values: values }).json; - var expected = [ ]; - for (var i = Math.min(from, values.length); i < values.length; ++i) { - expected.push(values[i]); - } - assertEqual([ expected ], result); - } + testStarExtractScalarUndefinedAttribute : function () { + var expected = [ null, null ]; + var result = AQL_EXECUTE("RETURN @value[*].foobar", { value : continents }).json; + assertEqual([ expected ], result); }, -//////////////////////////////////////////////////////////////////////////////// -/// @brief test range result -//////////////////////////////////////////////////////////////////////////////// - - testSubqueryResultRangeForwardMisc : function () { - var test = function (from, to, v) { - var result = AQL_EXECUTE("RETURN (FOR value IN @values RETURN value)[" + from + ".." + to + "]", { values: values }).json; - var expected = v.map(function(v) { return values[v]; }); - assertEqual([ expected ], result); - }; - - test(0, 0, [ 0 ]); - test(0, 1, [ 0, 1 ]); - test(0, 2, [ 0, 1, 2 ]); - test(0, 3, [ 0, 1, 2, 3 ]); - test(0, 4, [ 0, 1, 2, 3, 4 ]); - test(0, 5, [ 0, 1, 2, 3, 4 ]); - test(0, 6, [ 0, 1, 2, 3, 4 ]); - test(0, 99, [ 0, 1, 2, 3, 4 ]); - - test(1, 1, [ 1 ]); - test(1, 2, [ 1, 2 ]); - test(1, 3, [ 1, 2, 3 ]); - test(1, 4, [ 1, 2, 3, 4 ]); - test(1, 5, [ 1, 2, 3, 4 ]); - - test(2, 2, [ 2 ]); - test(2, 3, [ 2, 3 ]); - - test(4, 4, [ 4 ]); - test(4, 5, [ 4 ]); - - test(5, 5, [ ]); - test(5, 6, [ ]); - test(1000, 1000, [ ]); - test(1000000, 1000000, [ ]); - - test(0, -1, [ 0, 1, 2, 3, 4 ]); - test(0, -2, [ 0, 1, 2, 3 ]); - test(0, -3, [ 0, 1, 2 ]); - test(0, -4, [ 0, 1 ]); - test(0, -5, [ 0 ]); - test(0, -6, [ 0 ]); - test(0, -7, [ 0 ]); - - test(1, 0, [ 1, 0 ]); - test(1, -1, [ 1, 2, 3, 4 ]); - test(1, -2, [ 1, 2, 3 ]); - test(1, -3, [ 1, 2 ]); - test(1, -4, [ 1 ]); - test(1, -5, [ 1, 0 ]); - test(1, -6, [ 1, 0 ]); - test(1, -7, [ 1, 0 ]); - - test(2, 0, [ 2, 1, 0 ]); - test(2, 1, [ 2, 1 ]); - test(2, -1, [ 2, 3, 4 ]); - test(2, -2, [ 2, 3 ]); - test(2, -3, [ 2 ]); - test(2, -4, [ 2, 1 ]); - test(2, -5, [ 2, 1, 0 ]); - test(2, -6, [ 2, 1, 0 ]); - test(2, -7, [ 2, 1, 0 ]); - - test(3, 0, [ 3, 2, 1, 0 ]); - test(3, 1, [ 3, 2, 1 ]); - test(3, 2, [ 3, 2 ]); - test(3, -1, [ 3, 4 ]); - test(3, -2, [ 3 ]); - test(3, -3, [ 3, 2 ]); - test(3, -4, [ 3, 2, 1 ]); - test(3, -5, [ 3, 2, 1, 0 ]); - test(3, -6, [ 3, 2, 1, 0 ]); - - test(4, 0, [ 4, 3, 2, 1, 0 ]); - test(4, 1, [ 4, 3, 2, 1 ]); - test(4, 2, [ 4, 3, 2 ]); - test(4, 3, [ 4, 3 ]); - test(4, -1, [ 4 ]); - test(4, -2, [ 4, 3 ]); - test(4, -3, [ 4, 3, 2 ]); - test(4, -4, [ 4, 3, 2, 1 ]); - test(4, -5, [ 4, 3, 2, 1, 0 ]); - test(4, -6, [ 4, 3, 2, 1, 0 ]); - - test(5, 0, [ 4, 3, 2, 1, 0 ]); - test(5, 1, [ 4, 3, 2, 1 ]); - test(5, 2, [ 4, 3, 2 ]); - test(5, 3, [ 4, 3 ]); - test(5, -1, [ 4 ]); - test(5, -2, [ 4, 3 ]); - test(5, -3, [ 4, 3, 2 ]); - test(5, -4, [ 4, 3, 2, 1 ]); - test(5, -5, [ 4, 3, 2, 1, 0 ]); - test(5, -6, [ 4, 3, 2, 1, 0 ]); - - test(6, 6, [ ]); - test(6, 7, [ ]); - test(7, 6, [ ]); - test(10, 10, [ ]); - test(100, 100, [ ]); - test(100, 1000, [ ]); - test(1000, 100, [ ]); - - test(-6, -6, [ ]); - test(-6, -7, [ ]); - test(-7, -6, [ ]); - test(-10, -10, [ ]); - test(-100, -100, [ ]); - test(-100, -1000, [ ]); - test(-1000, -100, [ ]); + testStarExtractScalarWithFilter : function () { + var expected = [ "Europe" ]; + var result = AQL_EXECUTE("RETURN @value[* FILTER CURRENT.name == 'Europe'].name", { value : continents }).json; + assertEqual([ expected ], result); }, -//////////////////////////////////////////////////////////////////////////////// -/// @brief test range result -//////////////////////////////////////////////////////////////////////////////// - - testV8SubqueryResultRangeForward1 : function () { - for (var to = 0; to < 10; ++to) { - var result = AQL_EXECUTE("RETURN NOOPT(V8(FOR value IN @values RETURN value))[0.." + to + "]", { values: values }).json; - var expected = [ ]; - for (var i = 0; i < Math.min(to + 1, values.length); ++i) { - expected.push(values[i]); - } - assertEqual([ expected ], result); - } + testStarExtractScalarWithNonMatchingFilter : function () { + var expected = [ ]; + var result = AQL_EXECUTE("RETURN @value[* FILTER CURRENT.name == 'foobar'].name", { value : continents }).json; + assertEqual([ expected ], result); + }, + + testStarExtractArray : function () { + var expected = [ + [ + { id: "UK", capital: "London" }, + { id: "FR", capital: "Paris" }, + { id: "IT", capital: "Rome" } + ], + [ + { id: "JP", capital: "Tokyo" }, + { id: "CN", capital: "Beijing" }, + { id: "KR", capital: "Seoul" }, + { id: "IN", capital: "Delhi" } + ] + ]; + + var result = AQL_EXECUTE("RETURN @value[*].countries", { value : continents }).json; + assertEqual([ expected ], result); }, -//////////////////////////////////////////////////////////////////////////////// -/// @brief test range result -//////////////////////////////////////////////////////////////////////////////// - - testV8SubqueryResultRangeForward2 : function () { - for (var from = 0; from < 10; ++from) { - var result = AQL_EXECUTE("RETURN NOOPT(V8(FOR value IN @values RETURN value))[" + from + "..99]", { values: values }).json; - var expected = [ ]; - for (var i = Math.min(from, values.length); i < values.length; ++i) { - expected.push(values[i]); - } - assertEqual([ expected ], result); - } + testStarExtractArrayWithFilter : function () { + var expected = [ continents[0].countries ]; + var result = AQL_EXECUTE("RETURN @value[* FILTER CURRENT.name == 'Europe'].countries", { value : continents }).json; + assertEqual([ expected ], result); }, -//////////////////////////////////////////////////////////////////////////////// -/// @brief test range result -//////////////////////////////////////////////////////////////////////////////// + testStarExtractSubArrayIndexed1 : function () { + var expected = [ + { id: "UK", capital: "London" }, + { id: "JP", capital: "Tokyo" } + ]; + var result = AQL_EXECUTE("RETURN @value[*].countries[0]", { value : continents }).json; + assertEqual([ expected ], result); + }, - testV8SubqueryResultRangeForwardMisc : function () { - var test = function (from, to, v) { - var result = AQL_EXECUTE("RETURN NOOPT(V8(FOR value IN @values RETURN value))[" + from + ".." + to + "]", { values: values }).json; - var expected = v.map(function(v) { return values[v]; }); - assertEqual([ expected ], result); - }; + testStarExtractSubArrayIndexed2 : function () { + var expected = [ + { id: "IT", capital: "Rome" }, + { id: "IN", capital: "Delhi" } + ]; + var result = AQL_EXECUTE("RETURN @value[*].countries[-1]", { value : continents }).json; + assertEqual([ expected ], result); + }, - test(0, 0, [ 0 ]); - test(0, 1, [ 0, 1 ]); - test(0, 2, [ 0, 1, 2 ]); - test(0, 3, [ 0, 1, 2, 3 ]); - test(0, 4, [ 0, 1, 2, 3, 4 ]); - test(0, 5, [ 0, 1, 2, 3, 4 ]); - test(0, 6, [ 0, 1, 2, 3, 4 ]); - test(0, 99, [ 0, 1, 2, 3, 4 ]); + testStarExtractSubArrayIndexed3 : function () { + var expected = [ + null, + null + ]; + var result = AQL_EXECUTE("RETURN @value[*].countries[99]", { value : continents }).json; + assertEqual([ expected ], result); + }, - test(1, 1, [ 1 ]); - test(1, 2, [ 1, 2 ]); - test(1, 3, [ 1, 2, 3 ]); - test(1, 4, [ 1, 2, 3, 4 ]); - test(1, 5, [ 1, 2, 3, 4 ]); + testStarExtractSubArrayIndexedTotal1 : function () { + var expected = [ + { id: "UK", capital: "London" }, + { id: "FR", capital: "Paris" }, + { id: "IT", capital: "Rome" } + ]; + var result = AQL_EXECUTE("RETURN (@value[*].countries)[0]", { value : continents }).json; + assertEqual([ expected ], result); + }, - test(2, 2, [ 2 ]); - test(2, 3, [ 2, 3 ]); + testStarExtractSubArrayIndexedTotal2 : function () { + var expected = [ + { id: "JP", capital: "Tokyo" }, + { id: "CN", capital: "Beijing" }, + { id: "KR", capital: "Seoul" }, + { id: "IN", capital: "Delhi" } + ]; + var result = AQL_EXECUTE("RETURN (@value[*].countries)[-1]", { value : continents }).json; + assertEqual([ expected ], result); + }, - test(4, 4, [ 4 ]); - test(4, 5, [ 4 ]); + testStarExtractSubAttribute1 : function () { + var expected = [ + [ "UK", "FR", "IT" ], + [ "JP", "CN", "KR", "IN" ] + ]; + var result = AQL_EXECUTE("RETURN @value[*].countries[*].id", { value : continents }).json; + assertEqual([ expected ], result); + }, - test(5, 5, [ ]); - test(5, 6, [ ]); - test(1000, 1000, [ ]); - test(1000000, 1000000, [ ]); - - test(0, -1, [ 0, 1, 2, 3, 4 ]); - test(0, -2, [ 0, 1, 2, 3 ]); - test(0, -3, [ 0, 1, 2 ]); - test(0, -4, [ 0, 1 ]); - test(0, -5, [ 0 ]); - test(0, -6, [ 0 ]); - test(0, -7, [ 0 ]); + testStarExtractSubAttribute2 : function () { + var expected = [ + [ "London", "Paris", "Rome" ], + [ "Tokyo", "Beijing", "Seoul", "Delhi" ] + ]; + var result = AQL_EXECUTE("RETURN @value[*].countries[*].capital", { value : continents }).json; + assertEqual([ expected ], result); + }, - test(1, 0, [ 1, 0 ]); - test(1, -1, [ 1, 2, 3, 4 ]); - test(1, -2, [ 1, 2, 3 ]); - test(1, -3, [ 1, 2 ]); - test(1, -4, [ 1 ]); - test(1, -5, [ 1, 0 ]); - test(1, -6, [ 1, 0 ]); - test(1, -7, [ 1, 0 ]); + testStarExtractSubAttributeExtraOperator1 : function () { + var expected = [ + [ "UK", "FR", "IT" ], + [ "JP", "CN", "KR", "IN" ] + ]; + var result = AQL_EXECUTE("RETURN @value[*].countries[*].id[*]", { value : continents }).json; + assertEqual([ expected ], result); + }, - test(2, 0, [ 2, 1, 0 ]); - test(2, 1, [ 2, 1 ]); - test(2, -1, [ 2, 3, 4 ]); - test(2, -2, [ 2, 3 ]); - test(2, -3, [ 2 ]); - test(2, -4, [ 2, 1 ]); - test(2, -5, [ 2, 1, 0 ]); - test(2, -6, [ 2, 1, 0 ]); - test(2, -7, [ 2, 1, 0 ]); + testStarExtractSubAttributeExtraOperator2 : function () { + var expected = [ + [ "London", "Paris", "Rome" ], + [ "Tokyo", "Beijing", "Seoul", "Delhi" ] + ]; + var result = AQL_EXECUTE("RETURN @value[*].countries[*].capital[*]", { value : continents }).json; + assertEqual([ expected ], result); + }, - test(3, 0, [ 3, 2, 1, 0 ]); - test(3, 1, [ 3, 2, 1 ]); - test(3, 2, [ 3, 2 ]); - test(3, -1, [ 3, 4 ]); - test(3, -2, [ 3 ]); - test(3, -3, [ 3, 2 ]); - test(3, -4, [ 3, 2, 1 ]); - test(3, -5, [ 3, 2, 1, 0 ]); - test(3, -6, [ 3, 2, 1, 0 ]); + testStarExtractSubAttribute1Combine : function () { + var expected = [ + "UK", "FR", "IT", "JP", "CN", "KR", "IN" + ]; + var result = AQL_EXECUTE("RETURN @value[*].countries[**].id", { value : continents }).json; + assertEqual([ expected ], result); + }, - test(4, 0, [ 4, 3, 2, 1, 0 ]); - test(4, 1, [ 4, 3, 2, 1 ]); - test(4, 2, [ 4, 3, 2 ]); - test(4, 3, [ 4, 3 ]); - test(4, -1, [ 4 ]); - test(4, -2, [ 4, 3 ]); - test(4, -3, [ 4, 3, 2 ]); - test(4, -4, [ 4, 3, 2, 1 ]); - test(4, -5, [ 4, 3, 2, 1, 0 ]); - test(4, -6, [ 4, 3, 2, 1, 0 ]); + testStarExtractSubAttribute2Combine : function () { + var expected = [ + "London", "Paris", "Rome", "Tokyo", "Beijing", "Seoul", "Delhi" + ]; + var result = AQL_EXECUTE("RETURN @value[*].countries[**].capital", { value : continents }).json; + assertEqual([ expected ], result); + }, - test(5, 0, [ 4, 3, 2, 1, 0 ]); - test(5, 1, [ 4, 3, 2, 1 ]); - test(5, 2, [ 4, 3, 2 ]); - test(5, 3, [ 4, 3 ]); - test(5, -1, [ 4 ]); - test(5, -2, [ 4, 3 ]); - test(5, -3, [ 4, 3, 2 ]); - test(5, -4, [ 4, 3, 2, 1 ]); - test(5, -5, [ 4, 3, 2, 1, 0 ]); - test(5, -6, [ 4, 3, 2, 1, 0 ]); - - test(6, 6, [ ]); - test(6, 7, [ ]); - test(7, 6, [ ]); - test(10, 10, [ ]); - test(100, 100, [ ]); - test(100, 1000, [ ]); - test(1000, 100, [ ]); + testStarExtractSubAttribute3Combine : function () { + var expected = [ + "London", "Paris", "Rome", "Tokyo", "Beijing", "Seoul", "Delhi" + ]; + // more than 2 asterisks won't change the result + var result = AQL_EXECUTE("RETURN @value[*].countries[****].capital", { value : continents }).json; + assertEqual([ expected ], result); + }, - test(-6, -6, [ ]); - test(-6, -7, [ ]); - test(-7, -6, [ ]); - test(-10, -10, [ ]); - test(-100, -100, [ ]); - test(-100, -1000, [ ]); - test(-1000, -100, [ ]); + testStarExtractSubAttributeNonExisting : function () { + var expected = [ + [ null, null, null ], + [ null, null, null, null ] + ]; + var result = AQL_EXECUTE("RETURN @value[*].countries[*].id.foobar", { value : continents }).json; + assertEqual([ expected ], result); + }, + + testStarExtractSubAttributeIndexed1 : function () { + var expected = [ + [ null, null, null ], + [ null, null, null, null ] + ]; + var result = AQL_EXECUTE("RETURN @value[*].countries[*].id[0]", { value : continents }).json; + assertEqual([ expected ], result); + }, + + testStarExtractSubAttributeIndexed2 : function () { + var expected = [ + [ null, null, null ], + [ null, null, null, null ] + ]; + var result = AQL_EXECUTE("RETURN @value[*].countries[*].id[1]", { value : continents }).json; + assertEqual([ expected ], result); + }, + + testStarExtractSubAttributeIndexedTotal1 : function () { + var expected = [ + "UK", "FR", "IT" + ]; + var result = AQL_EXECUTE("RETURN (@value[*].countries[*].id)[0]", { value : continents }).json; + assertEqual([ expected ], result); + }, + + testStarExtractSubAttributeIndexedTotal2 : function () { + var expected = [ + "JP", "CN", "KR", "IN" + ]; + var result = AQL_EXECUTE("RETURN (@value[*].countries[*].id)[1]", { value : continents }).json; + assertEqual([ expected ], result); + }, + + testStarExtractFiltered1 : function () { + var expected = [ + "javascript", "php" + ]; + var result = AQL_EXECUTE("RETURN @value[0].values[* FILTER CURRENT.count == 2].name", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractFiltered2 : function () { + var expected = [ + "cpp", "ruby", "python" + ]; + var result = AQL_EXECUTE("RETURN @value[0].values[* FILTER CURRENT.count != 2].name", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractFiltered3 : function () { + var expected = [ + "ruby" + ]; + var result = AQL_EXECUTE("RETURN @value[0].values[* FILTER CURRENT.count != 2 && CURRENT.name == 'ruby'].name", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractFiltered4 : function () { + var expected = [ + 3 + ]; + var result = AQL_EXECUTE("RETURN @value[0].values[* FILTER LENGTH(CURRENT.payload) == 4].count", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractFiltered5 : function () { + var expected = [ + "php", "ruby", "python" + ]; + var result = AQL_EXECUTE("RETURN @value[0].values[* FILTER CURRENT.name IN [ 'php', 'python', 'ruby' ]].name", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractFiltered6 : function () { + var expected = [ + [ "javascript", "php" ] + ]; + var result = AQL_EXECUTE("RETURN @value[*].values[* FILTER CURRENT.count == 2].name", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractFiltered7 : function () { + var expected = [ + [ "cpp", "ruby", "python" ] + ]; + var result = AQL_EXECUTE("RETURN @value[*].values[* FILTER CURRENT.count != 2].name", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractFiltered8 : function () { + var expected = [ + [ "ruby" ] + ]; + var result = AQL_EXECUTE("RETURN @value[*].values[* FILTER CURRENT.count != 2 && CURRENT.name == 'ruby'].name", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractFiltered9 : function () { + var expected = [ + [ 3 ] + ]; + var result = AQL_EXECUTE("RETURN @value[*].values[* FILTER LENGTH(CURRENT.payload) == 4].count", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractFiltered10 : function () { + var expected = [ + [ "php", "ruby", "python" ] + ]; + var result = AQL_EXECUTE("RETURN @value[*].values[* FILTER CURRENT.name IN [ 'php', 'python', 'ruby' ]].name", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractProject1 : function () { + var expected = [ + 2, 2, 0, 2, 4 + ]; + var result = AQL_EXECUTE("RETURN @value[0].values[* RETURN LENGTH(CURRENT.payload)][**]", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractProject2 : function () { + var expected = [ + "x-javascript", "x-cpp", "x-php", "x-ruby", "x-python" + ]; + var result = AQL_EXECUTE("RETURN @value[0].values[*].name[* RETURN CONCAT('x-', CURRENT)][**]", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractProjectFilter : function () { + var expected = [ + "x-cpp", "x-ruby", "x-python" + ]; + var result = AQL_EXECUTE("RETURN @value[0].values[* FILTER CURRENT.count >= 3].name[* RETURN CONCAT('x-', CURRENT)][**]", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractLimitEmpty1 : function () { + var expected = [ + ]; + var result = AQL_EXECUTE("RETURN @value[0].values[*].name[** LIMIT 0]", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractLimitEmpty2 : function () { + var expected = [ + ]; + var result = AQL_EXECUTE("RETURN @value[0].values[* LIMIT 0].name", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractLimitEmpty3 : function () { + var expected = [ + ]; + var result = AQL_EXECUTE("RETURN @value[0].values[* LIMIT -10, 1].name", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractLimit0 : function () { + var expected = [ + "javascript" + ]; + var result = AQL_EXECUTE("RETURN @value[0].values[* LIMIT 1].name", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractLimit1 : function () { + var expected = [ + "javascript" + ]; + var result = AQL_EXECUTE("RETURN @value[0].values[*].name[** LIMIT 1]", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractLimit2 : function () { + var expected = [ + "javascript" + ]; + var result = AQL_EXECUTE("RETURN @value[0].values[*].name[** LIMIT 0, 1]", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractLimit3 : function () { + var expected = [ + "javascript", "cpp", "php", "ruby", "python" + ]; + var result = AQL_EXECUTE("RETURN @value[0].values[*].name[** LIMIT 0, 10]", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractLimit4 : function () { + var expected = [ + "javascript", "cpp", "php", "ruby" + ]; + var result = AQL_EXECUTE("RETURN @value[0].values[*].name[** LIMIT 0, 4]", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractLimit5 : function () { + var expected = [ + "javascript", "cpp" + ]; + var result = AQL_EXECUTE("RETURN @value[0].values[*].name[** LIMIT 0, 2]", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractLimit6 : function () { + var expected = [ + "cpp", "php", "ruby", "python" + ]; + var result = AQL_EXECUTE("RETURN @value[0].values[*].name[** LIMIT 1, 4]", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractLimit7 : function () { + var expected = [ + "ruby", "python" + ]; + var result = AQL_EXECUTE("RETURN @value[0].values[*].name[** LIMIT 3, 4]", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractLimit8 : function () { + var expected = [ + "javascript", "cpp" + ]; + var result = AQL_EXECUTE("RETURN @value[0].values[* FILTER CURRENT.name IN [ 'javascript', 'php', 'cpp' ] LIMIT 0, 2].name[**]", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractLimitProject1 : function () { + var expected = [ + "javascript-x", "cpp-x" + ]; + var result = AQL_EXECUTE("RETURN @value[0].values[** LIMIT 0, 2 RETURN CONCAT(CURRENT.name, '-x')]", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testStarExtractLimitProject2 : function () { + var expected = [ + "javascript", "cpp" + ]; + var result = AQL_EXECUTE("RETURN @value[0].values[* FILTER CURRENT.name IN [ 'javascript', 'php', 'cpp' ] LIMIT 0, 2 RETURN CURRENT.name]", { value : tags }).json; + assertEqual([ expected ], result); + }, + + testCollapse1 : function () { + var expected = arrayOfArrays; + var result = AQL_EXECUTE("RETURN @value[*]", { value : arrayOfArrays }).json; + assertEqual([ expected ], result); + }, + + testCollapse2 : function () { + var expected = [ [ "one", "two", "three" ], [ "four", "five" ], [ "six" ], [ "seven", "eight" ], [ "nine", "ten" ], [ "eleven", "twelve" ] ]; + var result = AQL_EXECUTE("RETURN @value[**]", { value : arrayOfArrays }).json; + assertEqual([ expected ], result); + }, + + testCollapse3 : function () { + var expected = [ "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve" ]; + var result = AQL_EXECUTE("RETURN @value[***]", { value : arrayOfArrays }).json; + assertEqual([ expected ], result); + }, + + testCollapseUnmodified : function () { + var data = [ [ [ [ 1, 2 ] ], [ 3, 4 ], 5, [ 6, 7 ] ], [ 8 ] ]; + var expected = data; + var result = AQL_EXECUTE("RETURN @value", { value : data }).json; + assertEqual([ expected ], result); + }, + + testCollapseMixed1 : function () { + var data = [ [ [ [ 1, 2 ] ], [ 3, 4 ], 5, [ 6, 7 ] ], [ 8 ] ]; + var expected = data; + var result = AQL_EXECUTE("RETURN @value[*]", { value : data }).json; + assertEqual([ expected ], result); + }, + + testCollapseMixed2 : function () { + var data = [ [ [ [ 1, 2 ] ], [ 3, 4 ], 5, [ 6, 7 ] ], [ 8 ] ]; + var expected = [ [ [ 1, 2 ] ], [ 3, 4 ], 5, [ 6, 7 ], 8 ]; + var result = AQL_EXECUTE("RETURN @value[**]", { value : data }).json; + assertEqual([ expected ], result); + }, + + testCollapseMixed3 : function () { + var data = [ [ [ [ 1, 2 ] ], [ 3, 4 ], 5, [ 6, 7 ] ], [ 8 ] ]; + var expected = [ [ 1, 2 ], 3, 4, 5, 6, 7, 8 ]; + var result = AQL_EXECUTE("RETURN @value[***]", { value : data }).json; + assertEqual([ expected ], result); + }, + + testCollapseMixed4 : function () { + var data = [ [ [ [ 1, 2 ] ], [ 3, 4 ], 5, [ 6, 7 ] ], [ 8 ] ]; + var expected = [ 1, 2, 3, 4, 5, 6, 7, 8 ]; + var result = AQL_EXECUTE("RETURN @value[****]", { value : data }).json; + assertEqual([ expected ], result); + }, + + testCollapseMixed5 : function () { + var data = [ [ [ [ 1, 2 ] ], [ 3, 4 ], 5, [ 6, 7 ] ], [ 8 ] ]; + var expected = [ 1, 2, 3, 4, 5, 6, 7, 8 ]; + var result = AQL_EXECUTE("RETURN @value[*****]", { value : data }).json; + assertEqual([ expected ], result); + }, + + testCollapseFilter : function () { + var data = [ 1, 2, 3, 4, 5, 6, 7, 8 ]; + var expected = [ 1, 3, 5, 7 ]; + var result = AQL_EXECUTE("RETURN @value[**** FILTER CURRENT % 2 != 0]", { value : data }).json; + assertEqual([ expected ], result); + }, + + testCollapseProject : function () { + var data = [ 1, 2, 3, 4, 5, 6, 7, 8 ]; + var expected = [ 2, 4, 6, 8, 10, 12, 14, 16 ]; + var result = AQL_EXECUTE("RETURN @value[**** RETURN CURRENT * 2]", { value : data }).json; + assertEqual([ expected ], result); + }, + + testCollapseFilterProject : function () { + var data = [ 1, 2, 3, 4, 5, 6, 7, 8 ]; + var expected = [ 4, 8, 12, 16 ]; + var result = AQL_EXECUTE("RETURN @value[**** FILTER CURRENT % 2 == 0 RETURN CURRENT * 2]", { value : data }).json; + assertEqual([ expected ], result); + }, + + testCollapseFilterProjectLimit : function () { + var data = [ 1, 2, 3, 4, 5, 6, 7, 8 ]; + var expected = [ 4, 8, 12 ]; + var result = AQL_EXECUTE("RETURN @value[**** FILTER CURRENT % 2 == 0 LIMIT 3 RETURN CURRENT * 2]", { value : data }).json; + assertEqual([ expected ], result); } }; diff --git a/js/server/tests/aql-optimizer-rule-remove-redundant-calculations.js b/js/server/tests/aql-optimizer-rule-remove-redundant-calculations.js index 85aabd14ba..549cde48d8 100644 --- a/js/server/tests/aql-optimizer-rule-remove-redundant-calculations.js +++ b/js/server/tests/aql-optimizer-rule-remove-redundant-calculations.js @@ -103,7 +103,7 @@ function optimizerRuleTestSuite () { "whether the execution of '" + query + "' this plan gave the wrong results: " + JSON.stringify(allresults.plans[j]) + " Should be: '" + JSON.stringify(allresults.results[0]) + - "' but Is: " + JSON.stringify(allresults.results[j]) + "'" + "' but is: " + JSON.stringify(allresults.results[j]) + "'" ); } } @@ -136,7 +136,7 @@ function optimizerRuleTestSuite () { "whether the execution of '" + query + "' this plan gave the wrong results: " + JSON.stringify(allresults.plans[j]) + " Should be: '" + JSON.stringify(allresults.results[0]) + - "' but Is: " + JSON.stringify(allresults.results[j]) + "'" + "' but is: " + JSON.stringify(allresults.results[j]) + "'" ); } }); @@ -173,7 +173,7 @@ function optimizerRuleTestSuite () { "whether the execution of '" + query + "' this plan gave the wrong results: " + JSON.stringify(allresults.plans[j]) + " Should be: '" + JSON.stringify(allresults.results[0]) + - "' but Is: " + JSON.stringify(allresults.results[j]) + "'" + "' but is: " + JSON.stringify(allresults.results[j]) + "'" ); } }, @@ -215,7 +215,7 @@ function optimizerRuleTestSuite () { "whether the execution of '" + query[0] + "' this plan gave the wrong results: " + JSON.stringify(allresults.plans[j]) + " Should be: '" + JSON.stringify(allresults.results[0]) + - "' but Is: " + JSON.stringify(allresults.results[j]) + "'" + "' but is: " + JSON.stringify(allresults.results[j]) + "'" ); } }); diff --git a/js/server/tests/aql-queries-variables.js b/js/server/tests/aql-queries-variables.js index 0ebeecfca7..33c25df470 100644 --- a/js/server/tests/aql-queries-variables.js +++ b/js/server/tests/aql-queries-variables.js @@ -163,7 +163,7 @@ function ahuacatlQueryVariablesTestSuite () { //////////////////////////////////////////////////////////////////////////////// testListExpansion6 : function () { - var query = "FOR a IN " + JSON.stringify(airports) + " RETURN a.continent.countries[*].airports[*][0].name"; + var query = "FOR a IN " + JSON.stringify(airports) + " RETURN a.continent.countries[*].airports[0].name"; var expected = [["CGN", "LHR", "CDG"], ["LGA", "YTO", "BOG"], ["NRT"]]; var actual = getQueryResults(query); @@ -175,7 +175,7 @@ function ahuacatlQueryVariablesTestSuite () { //////////////////////////////////////////////////////////////////////////////// testListExpansion7 : function () { - var query = "FOR a IN " + JSON.stringify(airports) + " RETURN a.continent.countries[*].airports[*][1].name"; + var query = "FOR a IN " + JSON.stringify(airports) + " RETURN a.continent.countries[*].airports[1].name"; var expected = [["DTM", "LGW", "ORY"], ["JFK", "YVR", null], ["HND"]]; var actual = getQueryResults(query);