mirror of https://gitee.com/bigwinds/arangodb
2026 lines
64 KiB
Plaintext
2026 lines
64 KiB
Plaintext
%define api.pure
|
|
%name-prefix "Aql"
|
|
%locations
|
|
%defines
|
|
%parse-param { arangodb::aql::Parser* parser }
|
|
%lex-param { void* scanner }
|
|
%define parse.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/StringUtils.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::containers::HashSet<Variable const*> const& variablesIntroduced) {
|
|
if (expression == nullptr) {
|
|
return;
|
|
}
|
|
|
|
::arangodb::containers::HashSet<Variable const*> 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::containers::HashSet<Variable const*>& variablesIntroduced,
|
|
AstNode const* vars) {
|
|
::arangodb::containers::HashSet<Variable const*> 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<Variable*>(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<arangodb::aql::Function*>(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 <strval> T_STRING
|
|
%type <strval> T_QUOTED_STRING
|
|
%type <node> T_INTEGER
|
|
%type <node> T_DOUBLE
|
|
%type <strval> T_PARAMETER;
|
|
%type <strval> T_DATA_SOURCE_PARAMETER;
|
|
%type <node> with_collection;
|
|
%type <node> sort_list;
|
|
%type <node> sort_element;
|
|
%type <node> sort_direction;
|
|
%type <node> collect_list;
|
|
%type <node> collect_element;
|
|
%type <node> collect_variable_list;
|
|
%type <node> keep;
|
|
%type <node> aggregate;
|
|
%type <node> collect_optional_into;
|
|
%type <strval> count_into;
|
|
%type <node> expression;
|
|
%type <node> expression_or_query;
|
|
%type <node> distinct_expression;
|
|
%type <node> operator_unary;
|
|
%type <node> operator_binary;
|
|
%type <node> operator_ternary;
|
|
%type <node> function_call;
|
|
%type <strval> function_name;
|
|
%type <node> optional_function_call_arguments;
|
|
%type <node> function_arguments_list;
|
|
%type <node> compound_value;
|
|
%type <node> array;
|
|
%type <node> for_output_variables;
|
|
%type <node> traversal_graph_info;
|
|
%type <node> shortest_path_graph_info;
|
|
%type <node> k_shortest_paths_graph_info;
|
|
%type <node> optional_array_elements;
|
|
%type <node> array_elements_list;
|
|
%type <node> for_options;
|
|
%type <node> object;
|
|
%type <node> options;
|
|
%type <node> optional_object_elements;
|
|
%type <node> object_elements_list;
|
|
%type <node> object_element;
|
|
%type <strval> object_element_name;
|
|
%type <intval> array_filter_operator;
|
|
%type <node> optional_array_filter;
|
|
%type <node> optional_array_limit;
|
|
%type <node> optional_array_return;
|
|
%type <node> graph_subject;
|
|
%type <intval> graph_direction;
|
|
%type <node> graph_direction_steps;
|
|
%type <node> graph_collection;
|
|
%type <node> reference;
|
|
%type <node> simple_value;
|
|
%type <node> value_literal;
|
|
%type <node> in_or_into_collection;
|
|
%type <node> in_or_into_collection_name;
|
|
%type <node> bind_parameter;
|
|
%type <strval> variable_name;
|
|
%type <node> numeric_value;
|
|
%type <intval> update_or_replace;
|
|
%type <node> 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<AstNode*>(parser->peekStack());
|
|
node->addMember($1);
|
|
}
|
|
| with_collection_list T_COMMA with_collection {
|
|
auto node = static_cast<AstNode*>(parser->peekStack());
|
|
node->addMember($3);
|
|
}
|
|
| with_collection_list with_collection {
|
|
auto node = static_cast<AstNode*>(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<AstNode*>(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<AstNode*>(parser->peekStack());
|
|
// Prune
|
|
node->addMember(parser->ast()->createNodeNop());
|
|
// Options
|
|
node->addMember(parser->ast()->createNodeNop());
|
|
}
|
|
| T_STRING expression {
|
|
auto node = static_cast<AstNode*>(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<AstNode*>(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<AstNode*>($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<AstNode*>(parser->popStack());
|
|
|
|
Variable* variable = static_cast<Variable*>(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<AstNode*>($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<AstNode*>($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<AstNode*>(parser->popStack());
|
|
auto variablesNode = static_cast<AstNode*>(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<AstNode*>($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<AstNode*>($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<AstNode*>($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<AstNode*>($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<AstNode*>(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::containers::HashSet<Variable const*> 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::containers::HashSet<Variable const*> 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::containers::HashSet<Variable const*> 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::containers::HashSet<Variable const*> 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<Variable const*>(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::containers::HashSet<Variable const*> 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::containers::HashSet<Variable const*> 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::containers::HashSet<Variable const*> 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<AstNode*>(parser->popStack());
|
|
$$ = list;
|
|
}
|
|
;
|
|
|
|
aggregate:
|
|
T_AGGREGATE {
|
|
auto node = parser->ast()->createNodeArray();
|
|
parser->pushStack(node);
|
|
} collect_list {
|
|
auto list = static_cast<AstNode*>(parser->popStack());
|
|
$$ = list;
|
|
}
|
|
;
|
|
|
|
sort_statement:
|
|
T_SORT {
|
|
auto node = parser->ast()->createNodeArray();
|
|
parser->pushStack(node);
|
|
} sort_list {
|
|
auto list = static_cast<AstNode const*>(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<int64_t>(NODE_TYPE_UPDATE);
|
|
}
|
|
| T_REPLACE {
|
|
$$ = static_cast<int64_t>(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<AstNode*>(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<AstNode*>(parser->popStack());
|
|
forNode->changeMember(1, $9);
|
|
|
|
if (!parser->configureWriteQuery($9, $10)) {
|
|
YYABORT;
|
|
}
|
|
|
|
auto node = parser->ast()->createNodeUpsert(static_cast<AstNodeType>($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<AstNode const*>(parser->popStack());
|
|
$$ = parser->ast()->createNodeFunctionCall(static_cast<char const*>(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<AstNode const*>(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<AstNode*>(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<AstNode*>(parser->peekStack());
|
|
node->addMember($1);
|
|
}
|
|
| graph_collection_list T_COMMA graph_collection {
|
|
auto node = static_cast<AstNode*>(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<AstNode*>(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<AstNode*>(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<AstNode*>(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<AstNode*>(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<AstNode const*>(parser->popStack());
|
|
auto variableNode = iterator->getMember(0);
|
|
TRI_ASSERT(variableNode->type == NODE_TYPE_VARIABLE);
|
|
auto variable = static_cast<Variable const*>(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;
|
|
}
|
|
;
|