%define api.pure %name-prefix "Aql" %locations %defines %parse-param { arangodb::aql::Parser* parser } %lex-param { void* scanner } %error-verbose %{ // we are using alloca here explicitly because we may // otherwise leak error messages that are generated by Bison. // Bison reports all its errors via the function `Aqlerror`, which // will receive the error message as a constant string. So we // must not free the string inside `Aqlerror`, and we cannot even // tell if the error message is a dynamically allocated error // message or a hard-coded error message that resides in some // static part of the program. // Even worse, `Aqlerror` does not return control to Bison but throws // an exception... So the best thing we can do here is to not use // dynamically memory allocation by Bison, but make it use alloca. #define YYSTACK_USE_ALLOCA 1 #include "Aql/Aggregator.h" #include "Aql/AstNode.h" #include "Aql/Function.h" #include "Aql/Parser.h" #include "Aql/Quantifier.h" #include "Aql/Query.h" #include "Aql/types.h" #include "Basics/tri-strings.h" #include "Transaction/Context.h" #include "VocBase/AccessMode.h" %} %union { arangodb::aql::AstNode* node; struct { char* value; size_t length; } strval; bool boolval; int64_t intval; } %{ using namespace arangodb::aql; /// @brief shortcut macro for signaling out of memory #define ABORT_OOM \ parser->registerError(TRI_ERROR_OUT_OF_MEMORY); \ YYABORT; #define scanner parser->scanner() /// @brief forward for lexer function defined in Aql/tokens.ll int Aqllex(YYSTYPE*, YYLTYPE*, void*); /// @brief register parse error (this will also abort the currently running query) void Aqlerror(YYLTYPE* locp, arangodb::aql::Parser* parser, char const* message) { parser->registerParseError(TRI_ERROR_QUERY_PARSE, message, locp->first_line, locp->first_column); } /// @brief check if any of the variables used in the INTO expression were /// introduced by the COLLECT itself, in which case it would fail static void CheckIntoVariables(Parser* parser, AstNode const* expression, int line, int column, arangodb::HashSet const& variablesIntroduced) { if (expression == nullptr) { return; } arangodb::HashSet varsInAssignment; Ast::getReferencedVariables(expression, varsInAssignment); for (auto const& it : varsInAssignment) { if (variablesIntroduced.find(it) != variablesIntroduced.end()) { std::string msg("use of COLLECT variable '" + it->name + "' inside same COLLECT's INTO expression"); parser->registerParseError(TRI_ERROR_QUERY_VARIABLE_NAME_UNKNOWN, msg.c_str(), it->name.c_str(), line, column); return; } } } /// @brief register variables in the scope static void RegisterAssignVariables(Parser* parser, arangodb::aql::Scopes* scopes, int line, int column, arangodb::HashSet& variablesIntroduced, AstNode const* vars) { arangodb::HashSet varsInAssignment; size_t const n = vars->numMembers(); for (size_t i = 0; i < n; ++i) { auto member = vars->getMemberUnchecked(i); if (member != nullptr) { TRI_ASSERT(member->type == NODE_TYPE_ASSIGN); // check if any of the assignment refers to a variable introduced by this very // same COLLECT, e.g. COLLECT aggregate x = .., y = x varsInAssignment.clear(); Ast::getReferencedVariables(member->getMember(1), varsInAssignment); for (auto const& it : varsInAssignment) { if (variablesIntroduced.find(it) != variablesIntroduced.end()) { std::string msg("use of COLLECT variable '" + it->name + "' inside same COLLECT"); parser->registerParseError(TRI_ERROR_QUERY_VARIABLE_NAME_UNKNOWN, msg.c_str(), it->name.c_str(), line, column); return; } } // keep track of the variable for our assignment auto v = static_cast(member->getMember(0)->getData()); scopes->addVariable(v); variablesIntroduced.emplace(v); } } } /// @brief validate the aggregate variables expressions static bool ValidateAggregates(Parser* parser, AstNode const* aggregates) { size_t const n = aggregates->numMembers(); for (size_t i = 0; i < n; ++i) { auto member = aggregates->getMemberUnchecked(i); if (member != nullptr) { TRI_ASSERT(member->type == NODE_TYPE_ASSIGN); auto func = member->getMember(1); bool isValid = true; if (func->type != NODE_TYPE_FCALL) { // aggregate expression must be a function call isValid = false; } else { auto f = static_cast(func->getData()); if (!Aggregator::isValid(f->name)) { // aggregate expression must be a call to MIN|MAX|LENGTH... isValid = false; } } if (!isValid) { parser->registerError(TRI_ERROR_QUERY_INVALID_AGGREGATE_EXPRESSION); return false; } } } return true; } /// @brief start a new scope for the collect static bool StartCollectScope(arangodb::aql::Scopes* scopes) { // check if we are in the main scope if (scopes->type() == arangodb::aql::AQL_SCOPE_MAIN) { return false; } // end the active scopes scopes->endNested(); // start a new scope scopes->start(arangodb::aql::AQL_SCOPE_COLLECT); return true; } /// @brief get the INTO variable stored in a node (may not exist) static AstNode const* GetIntoVariable(Parser* parser, AstNode const* node) { if (node == nullptr) { return nullptr; } if (node->type == NODE_TYPE_VALUE) { // node is a string containing the variable name return parser->ast()->createNodeVariable(node->getStringValue(), node->getStringLength(), true); } // node is an array with the variable name as the first member TRI_ASSERT(node->type == NODE_TYPE_ARRAY); TRI_ASSERT(node->numMembers() == 2); auto v = node->getMember(0); TRI_ASSERT(v->type == NODE_TYPE_VALUE); return parser->ast()->createNodeVariable(v->getStringValue(), v->getStringLength(), true); } /// @brief get the INTO variable = expression stored in a node (may not exist) static AstNode const* GetIntoExpression(AstNode const* node) { if (node == nullptr || node->type == NODE_TYPE_VALUE) { return nullptr; } // node is an array with the expression as the second member TRI_ASSERT(node->type == NODE_TYPE_ARRAY); TRI_ASSERT(node->numMembers() == 2); return node->getMember(1); } static AstNode* TransformOutputVariables(Parser* parser, AstNode const* names) { auto wrapperNode = parser->ast()->createNodeArray(); for (size_t i = 0; i < names->numMembers(); ++i) { AstNode* variableNameNode = names->getMemberUnchecked(i); TRI_ASSERT(variableNameNode->isStringValue()); AstNode* variableNode = parser->ast()->createNodeVariable(variableNameNode->getStringValue(), variableNameNode->getStringLength(), true); wrapperNode->addMember(variableNode); } return wrapperNode; } %} /* define tokens and "nice" token names */ %token T_FOR "FOR declaration" %token T_LET "LET declaration" %token T_FILTER "FILTER declaration" %token T_RETURN "RETURN declaration" %token T_COLLECT "COLLECT declaration" %token T_SORT "SORT declaration" %token T_LIMIT "LIMIT declaration" %token T_ASC "ASC keyword" %token T_DESC "DESC keyword" %token T_IN "IN keyword" %token T_WITH "WITH keyword" %token T_INTO "INTO keyword" %token T_AGGREGATE "AGGREGATE keyword" %token T_GRAPH "GRAPH keyword" %token T_SHORTEST_PATH "SHORTEST_PATH keyword" %token T_K_SHORTEST_PATHS "K_SHORTEST_PATHS keyword" %token T_DISTINCT "DISTINCT modifier" %token T_REMOVE "REMOVE command" %token T_INSERT "INSERT command" %token T_UPDATE "UPDATE command" %token T_REPLACE "REPLACE command" %token T_UPSERT "UPSERT command" %token T_NULL "null" %token T_TRUE "true" %token T_FALSE "false" %token T_STRING "identifier" %token T_QUOTED_STRING "quoted string" %token T_INTEGER "integer number" %token T_DOUBLE "number" %token T_PARAMETER "bind parameter" %token T_DATA_SOURCE_PARAMETER "bind data source parameter" %token T_ASSIGN "assignment" %token T_NOT "not operator" %token T_AND "and operator" %token T_OR "or operator" %token T_REGEX_MATCH "~= operator" %token T_REGEX_NON_MATCH "~! operator" %token T_EQ "== operator" %token T_NE "!= operator" %token T_LT "< operator" %token T_GT "> operator" %token T_LE "<= operator" %token T_GE ">= operator" %token T_LIKE "like operator" %token T_PLUS "+ operator" %token T_MINUS "- operator" %token T_TIMES "* operator" %token T_DIV "/ operator" %token T_MOD "% operator" %token T_QUESTION "?" %token T_COLON ":" %token T_SCOPE "::" %token T_RANGE ".." %token T_COMMA "," %token T_OPEN "(" %token T_CLOSE ")" %token T_OBJECT_OPEN "{" %token T_OBJECT_CLOSE "}" %token T_ARRAY_OPEN "[" %token T_ARRAY_CLOSE "]" %token T_END 0 "end of query string" %token T_OUTBOUND "outbound modifier" %token T_INBOUND "inbound modifier" %token T_ANY "any modifier" %token T_ALL "all modifier" %token T_NONE "none modifier" /* define operator precedence */ %left T_COMMA %left T_DISTINCT %right T_QUESTION T_COLON %right T_ASSIGN %left T_WITH %nonassoc T_INTO %left T_OR %left T_AND %nonassoc T_OUTBOUND T_INBOUND T_ANY T_ALL T_NONE %left T_EQ T_NE T_LIKE T_REGEX_MATCH T_REGEX_NON_MATCH %left T_IN T_NOT %left T_LT T_GT T_LE T_GE %left T_RANGE %left T_PLUS T_MINUS %left T_TIMES T_DIV T_MOD %right UMINUS UPLUS UNEGATION %left FUNCCALL %left REFERENCE %left INDEXED %left EXPANSION %left T_SCOPE /* define token return types */ %type T_STRING %type T_QUOTED_STRING %type T_INTEGER %type T_DOUBLE %type T_PARAMETER; %type T_DATA_SOURCE_PARAMETER; %type with_collection; %type sort_list; %type sort_element; %type sort_direction; %type collect_list; %type collect_element; %type collect_variable_list; %type keep; %type aggregate; %type collect_optional_into; %type count_into; %type expression; %type expression_or_query; %type distinct_expression; %type operator_unary; %type operator_binary; %type operator_ternary; %type function_call; %type function_name; %type optional_function_call_arguments; %type function_arguments_list; %type compound_value; %type array; %type for_output_variables; %type traversal_graph_info; %type shortest_path_graph_info; %type k_shortest_paths_graph_info; %type optional_array_elements; %type array_elements_list; %type for_options; %type object; %type options; %type optional_object_elements; %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 graph_subject; %type graph_direction; %type graph_direction_steps; %type graph_collection; %type reference; %type simple_value; %type value_literal; %type in_or_into_collection; %type in_or_into_collection_name; %type bind_parameter; %type variable_name; %type numeric_value; %type update_or_replace; %type quantifier; /* define start token of language */ %start queryStart %% with_collection: T_STRING { $$ = parser->ast()->createNodeValueString($1.value, $1.length); } | bind_parameter { char const* p = $1->getStringValue(); size_t const len = $1->getStringLength(); if (len < 2 || *p != '@') { parser->registerParseError(TRI_ERROR_QUERY_BIND_PARAMETER_TYPE, TRI_errno_string(TRI_ERROR_QUERY_BIND_PARAMETER_TYPE), p, yylloc.first_line, yylloc.first_column); } $$ = $1; } ; with_collection_list: with_collection { auto node = static_cast(parser->peekStack()); node->addMember($1); } | with_collection_list T_COMMA with_collection { auto node = static_cast(parser->peekStack()); node->addMember($3); } | with_collection_list with_collection { auto node = static_cast(parser->peekStack()); node->addMember($2); } ; optional_with: /* empty */ { } | T_WITH { auto node = parser->ast()->createNodeArray(); parser->pushStack(node); } with_collection_list { auto node = static_cast(parser->popStack()); auto const& resolver = parser->query()->resolver(); auto withNode = parser->ast()->createNodeWithCollections(node, resolver); parser->ast()->addOperation(withNode); } ; queryStart: optional_with query { } ; query: optional_statement_block_statements final_statement { } ; final_statement: return_statement { } | remove_statement { parser->ast()->scopes()->endNested(); } | insert_statement { parser->ast()->scopes()->endNested(); } | update_statement { parser->ast()->scopes()->endNested(); } | replace_statement { parser->ast()->scopes()->endNested(); } | upsert_statement { parser->ast()->scopes()->endNested(); } ; optional_statement_block_statements: /* empty */ { } | optional_statement_block_statements statement_block_statement { } ; statement_block_statement: for_statement { } | let_statement { } | filter_statement { } | collect_statement { } | sort_statement { } | limit_statement { } | remove_statement { } | insert_statement { } | update_statement { } | replace_statement { } | upsert_statement { } ; more_output_variables: variable_name { auto wrapperNode = parser->ast()->createNodeArray(); parser->pushArray(wrapperNode); // This is guaranteed to be called on the first variable AstNode* node = parser->ast()->createNodeValueString($1.value, $1.length); parser->pushArrayElement(node); } | more_output_variables T_COMMA variable_name { AstNode* node = parser->ast()->createNodeValueString($3.value, $3.length); parser->pushArrayElement(node); } ; for_output_variables: more_output_variables { $$ = parser->popArray(); } ; prune_and_options: /* empty no prune, no options, add two NOPS */ { auto node = static_cast(parser->peekStack()); // Prune node->addMember(parser->ast()->createNodeNop()); // Options node->addMember(parser->ast()->createNodeNop()); } | T_STRING expression { auto node = static_cast(parser->peekStack()); if (TRI_CaseEqualString($1.value, "PRUNE")) { /* Only Prune */ if ($2 == nullptr) { ABORT_OOM } // Prune node->addMember($2); // Options node->addMember(parser->ast()->createNodeNop()); } else if (TRI_CaseEqualString($1.value, "OPTIONS")) { /* Only Options */ if ($2 == nullptr) { ABORT_OOM } if (!$2->isObject()) { parser->registerParseError(TRI_ERROR_QUERY_PARSE, "traversal 'OPTIONS' have to be an object", $1.value, yylloc.first_line, yylloc.first_column); } // Prune node->addMember(parser->ast()->createNodeNop()); // Options node->addMember($2); } else { parser->registerParseError(TRI_ERROR_QUERY_PARSE, "unexpected qualifier '%s', expecting 'PRUNE' or 'OPTIONS'", $1.value, yylloc.first_line, yylloc.first_column); } } | T_STRING expression T_STRING object { /* prune and options */ auto node = static_cast(parser->peekStack()); if (!TRI_CaseEqualString($1.value, "PRUNE")) { parser->registerParseError(TRI_ERROR_QUERY_PARSE, "unexpected qualifier '%s', expecting 'PRUNE'", $1.value, yylloc.first_line, yylloc.first_column); } if ($2 == nullptr) { ABORT_OOM } if (!TRI_CaseEqualString($3.value, "OPTIONS")) { parser->registerParseError(TRI_ERROR_QUERY_PARSE, "unexpected qualifier '%s', expecting 'OPTIONS'", $3.value, yylloc.first_line, yylloc.first_column); } if ($4 == nullptr) { ABORT_OOM } // Prune node->addMember($2); // Options node->addMember($4); } ; traversal_graph_info: graph_direction_steps expression graph_subject { auto infoNode = parser->ast()->createNodeArray(); // Direction infoNode->addMember($1); // Source infoNode->addMember($2); // Graph infoNode->addMember($3); $$ = infoNode; } ; shortest_path_graph_info: graph_direction T_SHORTEST_PATH expression T_STRING expression graph_subject options { if (!TRI_CaseEqualString($4.value, "TO")) { parser->registerParseError(TRI_ERROR_QUERY_PARSE, "unexpected qualifier '%s', expecting 'TO'", $4.value, yylloc.first_line, yylloc.first_column); } auto infoNode = parser->ast()->createNodeArray(); auto dirNode = parser->ast()->createNodeValueInt($1); // Direction infoNode->addMember(dirNode); // Source infoNode->addMember($3); // Target infoNode->addMember($5); // Graph infoNode->addMember($6); // Opts auto opts = parser->ast()->createNodeOptions($7); TRI_ASSERT(opts != nullptr); infoNode->addMember(opts); $$ = infoNode; } ; k_shortest_paths_graph_info: graph_direction T_K_SHORTEST_PATHS expression T_STRING expression graph_subject options { if (!TRI_CaseEqualString($4.value, "TO")) { parser->registerParseError(TRI_ERROR_QUERY_PARSE, "unexpected qualifier '%s', expecting 'TO'", $4.value, yylloc.first_line, yylloc.first_column); } auto infoNode = parser->ast()->createNodeArray(); auto dirNode = parser->ast()->createNodeValueInt($1); // Direction infoNode->addMember(dirNode); // Source infoNode->addMember($3); // Target infoNode->addMember($5); // Graph infoNode->addMember($6); // Opts auto opts = parser->ast()->createNodeOptions($7); TRI_ASSERT(opts != nullptr); infoNode->addMember(opts); $$ = infoNode; } ; for_statement: T_FOR for_output_variables T_IN expression { // first open a new scope (after expression is evaluated) parser->ast()->scopes()->start(arangodb::aql::AQL_SCOPE_FOR); AstNode* variablesNode = static_cast($2); TRI_ASSERT(variablesNode != nullptr); TRI_ASSERT(variablesNode->type == NODE_TYPE_ARRAY); if (variablesNode->numMembers() != 1) { parser->registerParseError(TRI_ERROR_QUERY_PARSE, "Collections and Views only have one return variable", yylloc.first_line, yylloc.first_column); } // now create an out variable for the FOR statement // this prepares us to handle the optional SEARCH condition, which may // or may not refer to the FOR's variable AstNode* variableNameNode = variablesNode->getMemberUnchecked(0); TRI_ASSERT(variableNameNode->isStringValue()); AstNode* variableNode = parser->ast()->createNodeVariable(variableNameNode->getStringValue(), variableNameNode->getStringLength(), true); parser->pushStack(variableNode); } for_options { // now we can handle the optional SEARCH condition and OPTIONS. AstNode* variableNode = static_cast(parser->popStack()); Variable* variable = static_cast(variableNode->getData()); AstNode* node = nullptr; AstNode* search = nullptr; AstNode* options = nullptr; if ($6 != nullptr) { // we got a SEARCH and/or OPTIONS clause TRI_ASSERT($6->type == NODE_TYPE_ARRAY); TRI_ASSERT($6->numMembers() == 2); search = $6->getMemberUnchecked(0); if (search->type == NODE_TYPE_NOP) { search = nullptr; } options = $6->getMemberUnchecked(1); if (options->type == NODE_TYPE_NOP) { options = nullptr; } } if (search != nullptr) { // we got a SEARCH clause. this is always a view. node = parser->ast()->createNodeForView(variable, $4, search, options); if ($4->type != NODE_TYPE_PARAMETER_DATASOURCE && $4->type != NODE_TYPE_VIEW && $4->type != NODE_TYPE_COLLECTION) { parser->registerParseError(TRI_ERROR_QUERY_PARSE, "SEARCH condition used on non-view", yylloc.first_line, yylloc.first_column); } } else { node = parser->ast()->createNodeFor(variable, $4, options); } parser->ast()->addOperation(node); } | T_FOR for_output_variables T_IN traversal_graph_info { // first open a new scope (after expression is evaluated) parser->ast()->scopes()->start(arangodb::aql::AQL_SCOPE_FOR); // Traversal auto variableNamesNode = static_cast($2); TRI_ASSERT(variableNamesNode != nullptr); TRI_ASSERT(variableNamesNode->type == NODE_TYPE_ARRAY); if (variableNamesNode->numMembers() > 3) { parser->registerParseError(TRI_ERROR_QUERY_PARSE, "Traversals only have one, two or three return variables", yylloc.first_line, yylloc.first_column); } auto variablesNode = TransformOutputVariables(parser, variableNamesNode); auto graphInfoNode = static_cast($4); TRI_ASSERT(graphInfoNode != nullptr); TRI_ASSERT(graphInfoNode->type == NODE_TYPE_ARRAY); parser->pushStack(variablesNode); parser->pushStack(graphInfoNode); // This stack push/pop magic is necessary to allow v, e, and p in the prune condition } prune_and_options { auto graphInfoNode = static_cast(parser->popStack()); auto variablesNode = static_cast(parser->popStack()); auto prune = graphInfoNode->getMember(3); if (prune != nullptr) { Ast::traverseReadOnly(prune, [&](AstNode const* node) { if (node->type == NODE_TYPE_REFERENCE && node->hasFlag(AstNodeFlagType::FLAG_SUBQUERY_REFERENCE)) { parser->registerParseError(TRI_ERROR_QUERY_PARSE, "prune condition must not use a subquery", yylloc.first_line, yylloc.first_column); } }); } auto node = parser->ast()->createNodeTraversal(variablesNode, graphInfoNode); parser->ast()->addOperation(node); } | T_FOR for_output_variables T_IN shortest_path_graph_info { // first open a new scope (after expression is evaluated) parser->ast()->scopes()->start(arangodb::aql::AQL_SCOPE_FOR); // Shortest Path auto variableNamesNode = static_cast($2); TRI_ASSERT(variableNamesNode != nullptr); TRI_ASSERT(variableNamesNode->type == NODE_TYPE_ARRAY); if (variableNamesNode->numMembers() > 2) { parser->registerParseError(TRI_ERROR_QUERY_PARSE, "ShortestPath only has one or two return variables", yylloc.first_line, yylloc.first_column); } auto variablesNode = TransformOutputVariables(parser, variableNamesNode); auto graphInfoNode = static_cast($4); TRI_ASSERT(graphInfoNode != nullptr); TRI_ASSERT(graphInfoNode->type == NODE_TYPE_ARRAY); auto node = parser->ast()->createNodeShortestPath(variablesNode, graphInfoNode); parser->ast()->addOperation(node); } | T_FOR for_output_variables T_IN k_shortest_paths_graph_info { // first open a new scope (after expression is evaluated) parser->ast()->scopes()->start(arangodb::aql::AQL_SCOPE_FOR); // K Shortest Paths auto variableNamesNode = static_cast($2); TRI_ASSERT(variableNamesNode != nullptr); TRI_ASSERT(variableNamesNode->type == NODE_TYPE_ARRAY); if (variableNamesNode->numMembers() > 1) { parser->registerParseError(TRI_ERROR_QUERY_PARSE, "k Shortest Paths only has one return variable", yylloc.first_line, yylloc.first_column); } auto variablesNode = TransformOutputVariables(parser, variableNamesNode); auto graphInfoNode = static_cast($4); TRI_ASSERT(graphInfoNode != nullptr); TRI_ASSERT(graphInfoNode->type == NODE_TYPE_ARRAY); auto node = parser->ast()->createNodeKShortestPaths(variablesNode, graphInfoNode); parser->ast()->addOperation(node); } ; filter_statement: T_FILTER expression { // operand is a reference. can use it directly auto node = parser->ast()->createNodeFilter($2); parser->ast()->addOperation(node); } ; let_statement: T_LET let_list { } ; let_list: let_element { } | let_list T_COMMA let_element { } ; let_element: variable_name T_ASSIGN expression { auto node = parser->ast()->createNodeLet($1.value, $1.length, $3, true); parser->ast()->addOperation(node); } ; count_into: T_WITH T_STRING T_INTO variable_name { if (!TRI_CaseEqualString($2.value, "COUNT")) { parser->registerParseError(TRI_ERROR_QUERY_PARSE, "unexpected qualifier '%s', expecting 'COUNT'", $2.value, yylloc.first_line, yylloc.first_column); } $$ = $4; } ; collect_variable_list: T_COLLECT { auto node = parser->ast()->createNodeArray(); parser->pushStack(node); } collect_list { auto list = static_cast(parser->popStack()); if (list == nullptr) { ABORT_OOM } $$ = list; } ; collect_statement: T_COLLECT count_into options { /* COLLECT WITH COUNT INTO var OPTIONS ... */ auto scopes = parser->ast()->scopes(); StartCollectScope(scopes); auto node = parser->ast()->createNodeCollectCount(parser->ast()->createNodeArray(), $2.value, $2.length, $3); parser->ast()->addOperation(node); } | collect_variable_list count_into options { /* COLLECT var = expr WITH COUNT INTO var OPTIONS ... */ auto scopes = parser->ast()->scopes(); if (StartCollectScope(scopes)) { arangodb::HashSet variables; RegisterAssignVariables(parser, scopes, yylloc.first_line, yylloc.first_column, variables, $1); } auto node = parser->ast()->createNodeCollectCount($1, $2.value, $2.length, $3); parser->ast()->addOperation(node); } | T_COLLECT aggregate collect_optional_into options { /* AGGREGATE var = expr OPTIONS ... */ arangodb::HashSet variablesIntroduced; auto scopes = parser->ast()->scopes(); if (StartCollectScope(scopes)) { RegisterAssignVariables(parser, scopes, yylloc.first_line, yylloc.first_column, variablesIntroduced, $2); } // validate aggregates if (!ValidateAggregates(parser, $2)) { YYABORT; } if ($3 != nullptr && $3->type == NODE_TYPE_ARRAY) { CheckIntoVariables(parser, $3->getMember(1), yylloc.first_line, yylloc.first_column, variablesIntroduced); } AstNode const* into = GetIntoVariable(parser, $3); AstNode const* intoExpression = GetIntoExpression($3); auto node = parser->ast()->createNodeCollect(parser->ast()->createNodeArray(), $2, into, intoExpression, nullptr, $3); parser->ast()->addOperation(node); } | collect_variable_list aggregate collect_optional_into options { /* COLLECT var = expr AGGREGATE var = expr OPTIONS ... */ arangodb::HashSet variablesIntroduced; auto scopes = parser->ast()->scopes(); if (StartCollectScope(scopes)) { RegisterAssignVariables(parser, scopes, yylloc.first_line, yylloc.first_column, variablesIntroduced, $1); RegisterAssignVariables(parser, scopes, yylloc.first_line, yylloc.first_column, variablesIntroduced, $2); } if (! ValidateAggregates(parser, $2)) { YYABORT; } if ($3 != nullptr && $3->type == NODE_TYPE_ARRAY) { CheckIntoVariables(parser, $3->getMember(1), yylloc.first_line, yylloc.first_column, variablesIntroduced); } // note all group variables arangodb::HashSet groupVars; size_t n = $1->numMembers(); for (size_t i = 0; i < n; ++i) { auto member = $1->getMember(i); if (member != nullptr) { TRI_ASSERT(member->type == NODE_TYPE_ASSIGN); groupVars.emplace(static_cast(member->getMember(0)->getData())); } } // now validate if any aggregate refers to one of the group variables n = $2->numMembers(); for (size_t i = 0; i < n; ++i) { auto member = $2->getMember(i); if (member != nullptr) { TRI_ASSERT(member->type == NODE_TYPE_ASSIGN); arangodb::HashSet variablesUsed; Ast::getReferencedVariables(member->getMember(1), variablesUsed); for (auto& it : groupVars) { if (variablesUsed.find(it) != variablesUsed.end()) { parser->registerParseError(TRI_ERROR_QUERY_VARIABLE_NAME_UNKNOWN, "use of unknown variable '%s' in AGGREGATE expression", it->name.c_str(), yylloc.first_line, yylloc.first_column); break; } } } } AstNode const* into = GetIntoVariable(parser, $3); AstNode const* intoExpression = GetIntoExpression($3); auto node = parser->ast()->createNodeCollect($1, $2, into, intoExpression, nullptr, $4); parser->ast()->addOperation(node); } | collect_variable_list collect_optional_into options { /* COLLECT var = expr INTO var OPTIONS ... */ arangodb::HashSet variablesIntroduced; auto scopes = parser->ast()->scopes(); if (StartCollectScope(scopes)) { RegisterAssignVariables(parser, scopes, yylloc.first_line, yylloc.first_column, variablesIntroduced, $1); } if ($2 != nullptr && $2->type == NODE_TYPE_ARRAY) { CheckIntoVariables(parser, $2->getMember(1), yylloc.first_line, yylloc.first_column, variablesIntroduced); } AstNode const* into = GetIntoVariable(parser, $2); AstNode const* intoExpression = GetIntoExpression($2); auto node = parser->ast()->createNodeCollect($1, parser->ast()->createNodeArray(), into, intoExpression, nullptr, $3); parser->ast()->addOperation(node); } | collect_variable_list collect_optional_into keep options { /* COLLECT var = expr INTO var KEEP ... OPTIONS ... */ arangodb::HashSet variablesIntroduced; auto scopes = parser->ast()->scopes(); if (StartCollectScope(scopes)) { RegisterAssignVariables(parser, scopes, yylloc.first_line, yylloc.first_column, variablesIntroduced, $1); } if ($2 == nullptr && $3 != nullptr) { parser->registerParseError(TRI_ERROR_QUERY_PARSE, "use of 'KEEP' without 'INTO'", yylloc.first_line, yylloc.first_column); } if ($2 != nullptr && $2->type == NODE_TYPE_ARRAY) { CheckIntoVariables(parser, $2->getMember(1), yylloc.first_line, yylloc.first_column, variablesIntroduced); } AstNode const* into = GetIntoVariable(parser, $2); AstNode const* intoExpression = GetIntoExpression($2); auto node = parser->ast()->createNodeCollect($1, parser->ast()->createNodeArray(), into, intoExpression, $3, $4); parser->ast()->addOperation(node); } ; collect_list: collect_element { } | collect_list T_COMMA collect_element { } ; collect_element: variable_name T_ASSIGN expression { auto node = parser->ast()->createNodeAssign($1.value, $1.length, $3); parser->pushArrayElement(node); } ; collect_optional_into: /* empty */ { $$ = nullptr; } | T_INTO variable_name { $$ = parser->ast()->createNodeValueString($2.value, $2.length); } | T_INTO variable_name T_ASSIGN expression { auto node = parser->ast()->createNodeArray(); node->addMember(parser->ast()->createNodeValueString($2.value, $2.length)); node->addMember($4); $$ = node; } ; variable_list: variable_name { if (! parser->ast()->scopes()->existsVariable($1.value, $1.length)) { parser->registerParseError(TRI_ERROR_QUERY_PARSE, "use of unknown variable '%s' for KEEP", $1.value, yylloc.first_line, yylloc.first_column); } auto node = parser->ast()->createNodeReference($1.value, $1.length); if (node == nullptr) { ABORT_OOM } // indicate the this node is a reference to the variable name, not the variable value node->setFlag(FLAG_KEEP_VARIABLENAME); parser->pushArrayElement(node); } | variable_list T_COMMA variable_name { if (! parser->ast()->scopes()->existsVariable($3.value, $3.length)) { parser->registerParseError(TRI_ERROR_QUERY_PARSE, "use of unknown variable '%s' for KEEP", $3.value, yylloc.first_line, yylloc.first_column); } auto node = parser->ast()->createNodeReference($3.value, $3.length); if (node == nullptr) { ABORT_OOM } // indicate the this node is a reference to the variable name, not the variable value node->setFlag(FLAG_KEEP_VARIABLENAME); parser->pushArrayElement(node); } ; keep: T_STRING { if (!TRI_CaseEqualString($1.value, "KEEP")) { parser->registerParseError(TRI_ERROR_QUERY_PARSE, "unexpected qualifier '%s', expecting 'KEEP'", $1.value, yylloc.first_line, yylloc.first_column); } auto node = parser->ast()->createNodeArray(); parser->pushStack(node); } variable_list { auto list = static_cast(parser->popStack()); $$ = list; } ; aggregate: T_AGGREGATE { auto node = parser->ast()->createNodeArray(); parser->pushStack(node); } collect_list { auto list = static_cast(parser->popStack()); $$ = list; } ; sort_statement: T_SORT { auto node = parser->ast()->createNodeArray(); parser->pushStack(node); } sort_list { auto list = static_cast(parser->popStack()); auto node = parser->ast()->createNodeSort(list); parser->ast()->addOperation(node); } ; sort_list: sort_element { parser->pushArrayElement($1); } | sort_list T_COMMA sort_element { parser->pushArrayElement($3); } ; sort_element: expression sort_direction { $$ = parser->ast()->createNodeSortElement($1, $2); } ; sort_direction: /* empty */ { $$ = parser->ast()->createNodeValueBool(true); } | T_ASC { $$ = parser->ast()->createNodeValueBool(true); } | T_DESC { $$ = parser->ast()->createNodeValueBool(false); } | simple_value { $$ = $1; } ; limit_statement: T_LIMIT expression { auto offset = parser->ast()->createNodeValueInt(0); auto node = parser->ast()->createNodeLimit(offset, $2); parser->ast()->addOperation(node); } | T_LIMIT expression T_COMMA expression { auto node = parser->ast()->createNodeLimit($2, $4); parser->ast()->addOperation(node); } ; return_statement: T_RETURN distinct_expression { auto node = parser->ast()->createNodeReturn($2); parser->ast()->addOperation(node); parser->ast()->scopes()->endNested(); } ; in_or_into_collection: T_IN in_or_into_collection_name { $$ = $2; } | T_INTO in_or_into_collection_name { $$ = $2; } ; remove_statement: T_REMOVE expression in_or_into_collection options { if (!parser->configureWriteQuery($3, $4)) { YYABORT; } auto node = parser->ast()->createNodeRemove($2, $3, $4); parser->ast()->addOperation(node); } ; insert_statement: T_INSERT expression in_or_into_collection options { if (!parser->configureWriteQuery($3, $4)) { YYABORT; } auto node = parser->ast()->createNodeInsert($2, $3, $4); parser->ast()->addOperation(node); } ; update_parameters: expression in_or_into_collection options { if (!parser->configureWriteQuery($2, $3)) { YYABORT; } AstNode* node = parser->ast()->createNodeUpdate(nullptr, $1, $2, $3); parser->ast()->addOperation(node); } | expression T_WITH expression in_or_into_collection options { if (!parser->configureWriteQuery($4, $5)) { YYABORT; } AstNode* node = parser->ast()->createNodeUpdate($1, $3, $4, $5); parser->ast()->addOperation(node); } ; update_statement: T_UPDATE update_parameters { } ; replace_parameters: expression in_or_into_collection options { if (!parser->configureWriteQuery($2, $3)) { YYABORT; } AstNode* node = parser->ast()->createNodeReplace(nullptr, $1, $2, $3); parser->ast()->addOperation(node); } | expression T_WITH expression in_or_into_collection options { if (!parser->configureWriteQuery($4, $5)) { YYABORT; } AstNode* node = parser->ast()->createNodeReplace($1, $3, $4, $5); parser->ast()->addOperation(node); } ; replace_statement: T_REPLACE replace_parameters { } ; update_or_replace: T_UPDATE { $$ = static_cast(NODE_TYPE_UPDATE); } | T_REPLACE { $$ = static_cast(NODE_TYPE_REPLACE); } ; upsert_statement: T_UPSERT { // reserve a variable named "$OLD", we might need it in the update expression // and in a later return thing parser->pushStack(parser->ast()->createNodeVariable(TRI_CHAR_LENGTH_PAIR(Variable::NAME_OLD), true)); } expression { AstNode* variableNode = static_cast(parser->popStack()); auto scopes = parser->ast()->scopes(); scopes->start(arangodb::aql::AQL_SCOPE_SUBQUERY); parser->ast()->startSubQuery(); scopes->start(arangodb::aql::AQL_SCOPE_FOR); std::string const variableName = parser->ast()->variables()->nextName(); auto forNode = parser->ast()->createNodeFor(variableName.c_str(), variableName.size(), parser->ast()->createNodeArray(), false); parser->ast()->addOperation(forNode); auto filterNode = parser->ast()->createNodeUpsertFilter(parser->ast()->createNodeReference(variableName), $3); parser->ast()->addOperation(filterNode); auto offsetValue = parser->ast()->createNodeValueInt(0); auto limitValue = parser->ast()->createNodeValueInt(1); auto limitNode = parser->ast()->createNodeLimit(offsetValue, limitValue); parser->ast()->addOperation(limitNode); auto refNode = parser->ast()->createNodeReference(variableName); auto returnNode = parser->ast()->createNodeReturn(refNode); parser->ast()->addOperation(returnNode); scopes->endNested(); AstNode* subqueryNode = parser->ast()->endSubQuery(); scopes->endCurrent(); std::string const subqueryName = parser->ast()->variables()->nextName(); auto subQuery = parser->ast()->createNodeLet(subqueryName.c_str(), subqueryName.size(), subqueryNode, false); parser->ast()->addOperation(subQuery); auto index = parser->ast()->createNodeValueInt(0); auto firstDoc = parser->ast()->createNodeLet(variableNode, parser->ast()->createNodeIndexedAccess(parser->ast()->createNodeReference(subqueryName), index)); parser->ast()->addOperation(firstDoc); parser->pushStack(forNode); } T_INSERT expression update_or_replace expression in_or_into_collection options { AstNode* forNode = static_cast(parser->popStack()); forNode->changeMember(1, $9); if (!parser->configureWriteQuery($9, $10)) { YYABORT; } auto node = parser->ast()->createNodeUpsert(static_cast($7), parser->ast()->createNodeReference(TRI_CHAR_LENGTH_PAIR(Variable::NAME_OLD)), $6, $8, $9, $10); parser->ast()->addOperation(node); } ; quantifier: T_ALL { $$ = parser->ast()->createNodeQuantifier(Quantifier::ALL); } | T_ANY { $$ = parser->ast()->createNodeQuantifier(Quantifier::ANY); } | T_NONE { $$ = parser->ast()->createNodeQuantifier(Quantifier::NONE); } ; distinct_expression: T_DISTINCT { auto const scopeType = parser->ast()->scopes()->type(); if (scopeType == AQL_SCOPE_MAIN || scopeType == AQL_SCOPE_SUBQUERY) { parser->registerParseError(TRI_ERROR_QUERY_PARSE, "cannot use DISTINCT modifier on top-level query element", yylloc.first_line, yylloc.first_column); } } expression { $$ = parser->ast()->createNodeDistinct($3); } | expression { $$ = $1; } ; expression: operator_unary { $$ = $1; } | operator_binary { $$ = $1; } | operator_ternary { $$ = $1; } | value_literal { $$ = $1; } | reference { $$ = $1; } | expression T_RANGE expression { $$ = parser->ast()->createNodeRange($1, $3); } ; function_name: T_STRING { $$ = $1; } | function_name T_SCOPE T_STRING { std::string temp($1.value, $1.length); temp.append("::"); temp.append($3.value, $3.length); auto p = parser->query()->registerString(temp); if (p == nullptr) { ABORT_OOM } $$.value = p; $$.length = temp.size(); } ; function_call: function_name T_OPEN { parser->pushStack($1.value); auto node = parser->ast()->createNodeArray(); parser->pushStack(node); } optional_function_call_arguments T_CLOSE %prec FUNCCALL { auto list = static_cast(parser->popStack()); $$ = parser->ast()->createNodeFunctionCall(static_cast(parser->popStack()), list); } | T_LIKE T_OPEN { auto node = parser->ast()->createNodeArray(); parser->pushStack(node); } optional_function_call_arguments T_CLOSE %prec FUNCCALL { auto list = static_cast(parser->popStack()); $$ = parser->ast()->createNodeFunctionCall(TRI_CHAR_LENGTH_PAIR("LIKE"), list); } ; operator_unary: T_PLUS expression %prec UPLUS { $$ = parser->ast()->createNodeUnaryOperator(NODE_TYPE_OPERATOR_UNARY_PLUS, $2); } | T_MINUS expression %prec UMINUS { $$ = parser->ast()->createNodeUnaryOperator(NODE_TYPE_OPERATOR_UNARY_MINUS, $2); } | T_NOT expression %prec UNEGATION { $$ = parser->ast()->createNodeUnaryOperator(NODE_TYPE_OPERATOR_UNARY_NOT, $2); } ; operator_binary: expression T_OR expression { $$ = parser->ast()->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_OR, $1, $3); } | expression T_AND expression { $$ = parser->ast()->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_AND, $1, $3); } | expression T_PLUS expression { $$ = parser->ast()->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_PLUS, $1, $3); } | expression T_MINUS expression { $$ = parser->ast()->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_MINUS, $1, $3); } | expression T_TIMES expression { $$ = parser->ast()->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_TIMES, $1, $3); } | expression T_DIV expression { $$ = parser->ast()->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_DIV, $1, $3); } | expression T_MOD expression { $$ = parser->ast()->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_MOD, $1, $3); } | expression T_EQ expression { $$ = parser->ast()->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_EQ, $1, $3); } | expression T_NE expression { $$ = parser->ast()->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_NE, $1, $3); } | expression T_LT expression { $$ = parser->ast()->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_LT, $1, $3); } | expression T_GT expression { $$ = parser->ast()->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_GT, $1, $3); } | expression T_LE expression { $$ = parser->ast()->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_LE, $1, $3); } | expression T_GE expression { $$ = parser->ast()->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_GE, $1, $3); } | expression T_IN expression { $$ = parser->ast()->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_IN, $1, $3); } | expression T_NOT T_IN expression { $$ = parser->ast()->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_NIN, $1, $4); } | expression T_NOT T_LIKE expression { AstNode* arguments = parser->ast()->createNodeArray(2); arguments->addMember($1); arguments->addMember($4); AstNode* expression = parser->ast()->createNodeFunctionCall(TRI_CHAR_LENGTH_PAIR("LIKE"), arguments); $$ = parser->ast()->createNodeUnaryOperator(NODE_TYPE_OPERATOR_UNARY_NOT, expression); } | expression T_NOT T_REGEX_MATCH expression { AstNode* arguments = parser->ast()->createNodeArray(2); arguments->addMember($1); arguments->addMember($4); AstNode* expression = parser->ast()->createNodeFunctionCall(TRI_CHAR_LENGTH_PAIR("REGEX_TEST"), arguments); $$ = parser->ast()->createNodeUnaryOperator(NODE_TYPE_OPERATOR_UNARY_NOT, expression); } | expression T_NOT T_REGEX_NON_MATCH expression { AstNode* arguments = parser->ast()->createNodeArray(2); arguments->addMember($1); arguments->addMember($4); $$ = parser->ast()->createNodeFunctionCall(TRI_CHAR_LENGTH_PAIR("REGEX_TEST"), arguments); } | expression T_LIKE expression { AstNode* arguments = parser->ast()->createNodeArray(2); arguments->addMember($1); arguments->addMember($3); $$ = parser->ast()->createNodeFunctionCall(TRI_CHAR_LENGTH_PAIR("LIKE"), arguments); } | expression T_REGEX_MATCH expression { AstNode* arguments = parser->ast()->createNodeArray(2); arguments->addMember($1); arguments->addMember($3); $$ = parser->ast()->createNodeFunctionCall(TRI_CHAR_LENGTH_PAIR("REGEX_TEST"), arguments); } | expression T_REGEX_NON_MATCH expression { AstNode* arguments = parser->ast()->createNodeArray(2); arguments->addMember($1); arguments->addMember($3); AstNode* node = parser->ast()->createNodeFunctionCall(TRI_CHAR_LENGTH_PAIR("REGEX_TEST"), arguments); $$ = parser->ast()->createNodeUnaryOperator(NODE_TYPE_OPERATOR_UNARY_NOT, node); } | expression quantifier T_EQ expression { $$ = parser->ast()->createNodeBinaryArrayOperator(NODE_TYPE_OPERATOR_BINARY_ARRAY_EQ, $1, $4, $2); } | expression quantifier T_NE expression { $$ = parser->ast()->createNodeBinaryArrayOperator(NODE_TYPE_OPERATOR_BINARY_ARRAY_NE, $1, $4, $2); } | expression quantifier T_LT expression { $$ = parser->ast()->createNodeBinaryArrayOperator(NODE_TYPE_OPERATOR_BINARY_ARRAY_LT, $1, $4, $2); } | expression quantifier T_GT expression { $$ = parser->ast()->createNodeBinaryArrayOperator(NODE_TYPE_OPERATOR_BINARY_ARRAY_GT, $1, $4, $2); } | expression quantifier T_LE expression { $$ = parser->ast()->createNodeBinaryArrayOperator(NODE_TYPE_OPERATOR_BINARY_ARRAY_LE, $1, $4, $2); } | expression quantifier T_GE expression { $$ = parser->ast()->createNodeBinaryArrayOperator(NODE_TYPE_OPERATOR_BINARY_ARRAY_GE, $1, $4, $2); } | expression quantifier T_IN expression { $$ = parser->ast()->createNodeBinaryArrayOperator(NODE_TYPE_OPERATOR_BINARY_ARRAY_IN, $1, $4, $2); } | expression T_ALL T_NOT T_IN expression { auto quantifier = parser->ast()->createNodeQuantifier(Quantifier::ALL); $$ = parser->ast()->createNodeBinaryArrayOperator(NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN, $1, $5, quantifier); } | expression T_ANY T_NOT T_IN expression { auto quantifier = parser->ast()->createNodeQuantifier(Quantifier::ANY); $$ = parser->ast()->createNodeBinaryArrayOperator(NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN, $1, $5, quantifier); } | expression T_NONE T_NOT T_IN expression { auto quantifier = parser->ast()->createNodeQuantifier(Quantifier::NONE); $$ = parser->ast()->createNodeBinaryArrayOperator(NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN, $1, $5, quantifier); } ; operator_ternary: expression T_QUESTION expression T_COLON expression { $$ = parser->ast()->createNodeTernaryOperator($1, $3, $5); } | expression T_QUESTION T_COLON expression { $$ = parser->ast()->createNodeTernaryOperator($1, $1, $4); } ; optional_function_call_arguments: /* empty */ { } | function_arguments_list { } ; expression_or_query: expression { $$ = $1; } | { parser->ast()->scopes()->start(arangodb::aql::AQL_SCOPE_SUBQUERY); parser->ast()->startSubQuery(); } query { AstNode* node = parser->ast()->endSubQuery(); parser->ast()->scopes()->endCurrent(); std::string const variableName = parser->ast()->variables()->nextName(); auto subQuery = parser->ast()->createNodeLet(variableName.c_str(), variableName.size(), node, false); parser->ast()->addOperation(subQuery); $$ = parser->ast()->createNodeSubqueryReference(variableName); } ; function_arguments_list: expression_or_query { parser->pushArrayElement($1); } | function_arguments_list T_COMMA expression_or_query { parser->pushArrayElement($3); } ; compound_value: array { $$ = $1; } | object { $$ = $1; } ; array: T_ARRAY_OPEN { auto node = parser->ast()->createNodeArray(); parser->pushArray(node); } optional_array_elements T_ARRAY_CLOSE { $$ = parser->popArray(); } ; optional_array_elements: /* empty */ { } | array_elements_list { } ; array_elements_list: expression { parser->pushArrayElement($1); } | array_elements_list T_COMMA expression { parser->pushArrayElement($3); } ; for_options: /* empty */ { $$ = nullptr; } | T_STRING expression { if ($2 == nullptr) { ABORT_OOM } // we always return an array with two values: SEARCH and OPTIONS // as only one of these values will be set here, the other value is NOP auto node = parser->ast()->createNodeArray(2); // only one extra qualifier. now we need to check if it is SEARCH or OPTIONS if (TRI_CaseEqualString($1.value, "SEARCH")) { // found SEARCH node->addMember($2); node->addMember(parser->ast()->createNodeNop()); } else { // everything else must be OPTIONS if (!TRI_CaseEqualString($1.value, "OPTIONS")) { parser->registerParseError(TRI_ERROR_QUERY_PARSE, "unexpected qualifier '%s', expecting 'SEARCH' or 'OPTIONS'", $1.value, yylloc.first_line, yylloc.first_column); } node->addMember(parser->ast()->createNodeNop()); node->addMember($2); } $$ = node; } | T_STRING expression T_STRING expression { if ($2 == nullptr) { ABORT_OOM } // two extra qualifiers. we expect them in the order: SEARCH, then OPTIONS if (!TRI_CaseEqualString($1.value, "SEARCH") || !TRI_CaseEqualString($3.value, "OPTIONS")) { parser->registerParseError(TRI_ERROR_QUERY_PARSE, "unexpected qualifier '%s', expecting 'SEARCH' and 'OPTIONS'", $1.value, yylloc.first_line, yylloc.first_column); } auto node = parser->ast()->createNodeArray(2); node->addMember($2); node->addMember($4); $$ = node; } ; options: /* empty */ { $$ = nullptr; } | T_STRING object { if ($2 == nullptr) { ABORT_OOM } if (!TRI_CaseEqualString($1.value, "OPTIONS")) { parser->registerParseError(TRI_ERROR_QUERY_PARSE, "unexpected qualifier '%s', expecting 'OPTIONS'", $1.value, yylloc.first_line, yylloc.first_column); } $$ = $2; } ; object: T_OBJECT_OPEN { auto node = parser->ast()->createNodeObject(); parser->pushStack(node); } optional_object_elements T_OBJECT_CLOSE { $$ = static_cast(parser->popStack()); } ; optional_object_elements: /* empty */ { } | object_elements_list { } ; object_elements_list: object_element { } | object_elements_list T_COMMA object_element { } ; object_element: T_STRING { // attribute-name-only (comparable to JS enhanced object literals, e.g. { foo, bar }) auto ast = parser->ast(); auto variable = ast->scopes()->getVariable($1.value, $1.length, true); if (variable == nullptr) { // variable does not exist parser->registerParseError(TRI_ERROR_QUERY_VARIABLE_NAME_UNKNOWN, "use of unknown variable '%s' in object literal", $1.value, yylloc.first_line, yylloc.first_column); } // create a reference to the variable auto node = ast->createNodeReference(variable); parser->pushObjectElement($1.value, $1.length, node); } | object_element_name T_COLON expression { // attribute-name : attribute-value parser->pushObjectElement($1.value, $1.length, $3); } | T_PARAMETER T_COLON expression { // bind-parameter : attribute-value if ($1.length < 1 || $1.value[0] == '@') { parser->registerParseError(TRI_ERROR_QUERY_BIND_PARAMETER_TYPE, TRI_errno_string(TRI_ERROR_QUERY_BIND_PARAMETER_TYPE), $1.value, yylloc.first_line, yylloc.first_column); } auto param = parser->ast()->createNodeParameter($1.value, $1.length); parser->pushObjectElement(param, $3); } | T_ARRAY_OPEN expression T_ARRAY_CLOSE T_COLON expression { // [ attribute-name-expression ] : attribute-value parser->pushObjectElement($2, $5); } ; 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; } ; graph_collection: T_STRING { $$ = parser->ast()->createNodeValueString($1.value, $1.length); } | bind_parameter { $$ = $1; } | graph_direction T_STRING { auto tmp = parser->ast()->createNodeValueString($2.value, $2.length); $$ = parser->ast()->createNodeCollectionDirection($1, tmp); } | graph_direction bind_parameter { $$ = parser->ast()->createNodeCollectionDirection($1, $2); } ; graph_collection_list: graph_collection { auto node = static_cast(parser->peekStack()); node->addMember($1); } | graph_collection_list T_COMMA graph_collection { auto node = static_cast(parser->peekStack()); node->addMember($3); } ; graph_subject: graph_collection { auto node = parser->ast()->createNodeArray(); node->addMember($1); auto const& resolver = parser->query()->resolver(); $$ = parser->ast()->createNodeCollectionList(node, resolver); } | graph_collection T_COMMA { auto node = parser->ast()->createNodeArray(); parser->pushStack(node); node->addMember($1); } graph_collection_list { auto node = static_cast(parser->popStack()); auto const& resolver = parser->query()->resolver(); $$ = parser->ast()->createNodeCollectionList(node, resolver); } | T_GRAPH bind_parameter { // graph name $$ = $2; } | T_GRAPH T_QUOTED_STRING { // graph name $$ = parser->ast()->createNodeValueString($2.value, $2.length); } ; graph_direction: // Returns the edge direction number. // Identical order as TRI_edge_direction_e T_OUTBOUND { $$ = 2; } | T_INBOUND { $$ = 1; } | T_ANY { $$ = 0; } ; graph_direction_steps: graph_direction { $$ = parser->ast()->createNodeDirection($1, 1); } | expression graph_direction %prec T_OUTBOUND { $$ = parser->ast()->createNodeDirection($2, $1); } ; reference: T_STRING { // variable or collection or view auto ast = parser->ast(); AstNode* node = nullptr; auto variable = ast->scopes()->getVariable($1.value, $1.length, true); if (variable == nullptr) { // variable does not exist // now try special variables if (ast->scopes()->canUseCurrentVariable() && strcmp($1.value, "CURRENT") == 0) { variable = ast->scopes()->getCurrentVariable(); } else if (strcmp($1.value, Variable::NAME_CURRENT) == 0) { variable = ast->scopes()->getCurrentVariable(); } } if (variable != nullptr) { // variable alias exists, now use it node = ast->createNodeReference(variable); } if (node == nullptr) { // variable not found. so it must have been a collection or view auto const& resolver = parser->query()->resolver(); node = ast->createNodeDataSource(resolver, $1.value, $1.length, arangodb::AccessMode::Type::READ, true, false); } TRI_ASSERT(node != nullptr); $$ = node; } | compound_value { $$ = $1; } | bind_parameter { $$ = $1; } | function_call { $$ = $1; if ($$ == nullptr) { ABORT_OOM } } | T_OPEN expression T_CLOSE { 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 { parser->ast()->scopes()->start(arangodb::aql::AQL_SCOPE_SUBQUERY); parser->ast()->startSubQuery(); } query T_CLOSE { AstNode* node = parser->ast()->endSubQuery(); parser->ast()->scopes()->endCurrent(); std::string const variableName = parser->ast()->variables()->nextName(); auto subQuery = parser->ast()->createNodeLet(variableName.c_str(), variableName.size(), node, false); parser->ast()->addOperation(subQuery); $$ = parser->ast()->createNodeReference(variableName); } | reference '.' T_STRING %prec REFERENCE { // named variable access, e.g. variable.reference if ($1->type == NODE_TYPE_EXPANSION) { // if left operand is an expansion already... // 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.value, $3.length)); $$ = $1; } else { $$ = parser->ast()->createNodeAttributeAccess($1, $3.value, $3.length); } } | reference '.' bind_parameter %prec 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 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 { $$ = parser->ast()->createNodeBoundAttributeAccess($1, $3); } } | reference T_ARRAY_OPEN expression T_ARRAY_CLOSE %prec INDEXED { // indexed variable access, e.g. variable[index] if ($1->type == NODE_TYPE_EXPANSION) { // if left operand is an expansion already... // patch the existing expansion 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 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() + "_"; if ($1->type == NODE_TYPE_EXPANSION) { auto iterator = parser->ast()->createNodeIterator(nextName.c_str(), nextName.size(), $1->getMember(1)); parser->pushStack(iterator); } else { auto iterator = parser->ast()->createNodeIterator(nextName.c_str(), nextName.size(), $1); parser->pushStack(iterator); } auto scopes = parser->ast()->scopes(); scopes->stackCurrentVariable(scopes->getVariable(nextName)); } 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 variableNode = iterator->getMember(0); TRI_ASSERT(variableNode->type == NODE_TYPE_VARIABLE); auto variable = static_cast(variableNode->getData()); if ($1->type == NODE_TYPE_EXPANSION) { auto expand = parser->ast()->createNodeExpansion($3, iterator, parser->ast()->createNodeReference(variable->name), $5, $6, $7); $1->changeMember(1, expand); $$ = $1; } else { $$ = parser->ast()->createNodeExpansion($3, iterator, parser->ast()->createNodeReference(variable->name), $5, $6, $7); } } ; simple_value: value_literal { $$ = $1; } | bind_parameter { $$ = $1; } ; numeric_value: T_INTEGER { if ($1 == nullptr) { ABORT_OOM } $$ = $1; } | T_DOUBLE { if ($1 == nullptr) { ABORT_OOM } $$ = $1; } ; value_literal: T_QUOTED_STRING { $$ = parser->ast()->createNodeValueString($1.value, $1.length); } | numeric_value { $$ = $1; } | T_NULL { $$ = parser->ast()->createNodeValueNull(); } | T_TRUE { $$ = parser->ast()->createNodeValueBool(true); } | T_FALSE { $$ = parser->ast()->createNodeValueBool(false); } ; in_or_into_collection_name: T_STRING { auto const& resolver = parser->query()->resolver(); $$ = parser->ast()->createNodeCollection(resolver, $1.value, $1.length, arangodb::AccessMode::Type::WRITE); } | T_QUOTED_STRING { auto const& resolver = parser->query()->resolver(); $$ = parser->ast()->createNodeCollection(resolver, $1.value, $1.length, arangodb::AccessMode::Type::WRITE); } | T_DATA_SOURCE_PARAMETER { if ($1.length < 2 || $1.value[0] != '@') { parser->registerParseError(TRI_ERROR_QUERY_BIND_PARAMETER_TYPE, TRI_errno_string(TRI_ERROR_QUERY_BIND_PARAMETER_TYPE), $1.value, yylloc.first_line, yylloc.first_column); } $$ = parser->ast()->createNodeParameterDatasource($1.value, $1.length); } ; bind_parameter: T_DATA_SOURCE_PARAMETER { if ($1.length < 2 || $1.value[0] != '@') { parser->registerParseError(TRI_ERROR_QUERY_BIND_PARAMETER_TYPE, TRI_errno_string(TRI_ERROR_QUERY_BIND_PARAMETER_TYPE), $1.value, yylloc.first_line, yylloc.first_column); } $$ = parser->ast()->createNodeParameterDatasource($1.value, $1.length); } | T_PARAMETER { $$ = parser->ast()->createNodeParameter($1.value, $1.length); } ; object_element_name: T_STRING { $$ = $1; } | T_QUOTED_STRING { $$ = $1; } variable_name: T_STRING { $$ = $1; } ;