1
0
Fork 0
arangodb/arangod/Aql/Executor.cpp

1425 lines
55 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2014-2016 ArangoDB GmbH, Cologne, Germany
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Jan Steemann
////////////////////////////////////////////////////////////////////////////////
#include "Aql/Executor.h"
#include "Aql/AstNode.h"
#include "Aql/Functions.h"
#include "Aql/V8Expression.h"
#include "Aql/Variable.h"
#include "Basics/StringBuffer.h"
#include "Basics/Exceptions.h"
#include "Cluster/ServerState.h"
#include "V8/v8-conv.h"
#include "V8/v8-globals.h"
#include "V8/v8-utils.h"
#include "V8/v8-vpack.h"
#include <velocypack/Builder.h>
#include <velocypack/Slice.h>
#include <velocypack/velocypack-aliases.h>
using namespace arangodb::aql;
/// @brief determines if code is executed in cluster or not
static ExecutionCondition const NotInCluster =
[] { return !arangodb::ServerState::instance()->isRunningInCluster(); };
/// @brief determines if code is executed on coordinator or not
static ExecutionCondition const NotInCoordinator = [] {
return !arangodb::ServerState::instance()->isRunningInCluster() ||
!arangodb::ServerState::instance()->isCoordinator();
};
/// @brief internal functions used in execution
std::unordered_map<int,
std::string const> const Executor::InternalFunctionNames{
{static_cast<int>(NODE_TYPE_OPERATOR_UNARY_PLUS), "UNARY_PLUS"},
{static_cast<int>(NODE_TYPE_OPERATOR_UNARY_MINUS), "UNARY_MINUS"},
{static_cast<int>(NODE_TYPE_OPERATOR_UNARY_NOT), "LOGICAL_NOT"},
{static_cast<int>(NODE_TYPE_OPERATOR_BINARY_EQ), "RELATIONAL_EQUAL"},
{static_cast<int>(NODE_TYPE_OPERATOR_BINARY_NE), "RELATIONAL_UNEQUAL"},
{static_cast<int>(NODE_TYPE_OPERATOR_BINARY_GT), "RELATIONAL_GREATER"},
{static_cast<int>(NODE_TYPE_OPERATOR_BINARY_GE), "RELATIONAL_GREATEREQUAL"},
{static_cast<int>(NODE_TYPE_OPERATOR_BINARY_LT), "RELATIONAL_LESS"},
{static_cast<int>(NODE_TYPE_OPERATOR_BINARY_LE), "RELATIONAL_LESSEQUAL"},
{static_cast<int>(NODE_TYPE_OPERATOR_BINARY_IN), "RELATIONAL_IN"},
{static_cast<int>(NODE_TYPE_OPERATOR_BINARY_NIN), "RELATIONAL_NOT_IN"},
{static_cast<int>(NODE_TYPE_OPERATOR_BINARY_PLUS), "ARITHMETIC_PLUS"},
{static_cast<int>(NODE_TYPE_OPERATOR_BINARY_MINUS), "ARITHMETIC_MINUS"},
{static_cast<int>(NODE_TYPE_OPERATOR_BINARY_TIMES), "ARITHMETIC_TIMES"},
{static_cast<int>(NODE_TYPE_OPERATOR_BINARY_DIV), "ARITHMETIC_DIVIDE"},
{static_cast<int>(NODE_TYPE_OPERATOR_BINARY_MOD), "ARITHMETIC_MODULUS"},
{static_cast<int>(NODE_TYPE_OPERATOR_BINARY_AND), "LOGICAL_AND"},
{static_cast<int>(NODE_TYPE_OPERATOR_BINARY_OR), "LOGICAL_OR"},
{static_cast<int>(NODE_TYPE_OPERATOR_TERNARY), "TERNARY_OPERATOR"},
{static_cast<int>(NODE_TYPE_OPERATOR_BINARY_ARRAY_EQ), "RELATIONAL_ARRAY_EQUAL"},
{static_cast<int>(NODE_TYPE_OPERATOR_BINARY_ARRAY_NE), "RELATIONAL_ARRAY_UNEQUAL"},
{static_cast<int>(NODE_TYPE_OPERATOR_BINARY_ARRAY_GT), "RELATIONAL_ARRAY_GREATER"},
{static_cast<int>(NODE_TYPE_OPERATOR_BINARY_ARRAY_GE), "RELATIONAL_ARRAY_GREATEREQUAL"},
{static_cast<int>(NODE_TYPE_OPERATOR_BINARY_ARRAY_LT), "RELATIONAL_ARRAY_LESS"},
{static_cast<int>(NODE_TYPE_OPERATOR_BINARY_ARRAY_LE), "RELATIONAL_ARRAY_LESSEQUAL"},
{static_cast<int>(NODE_TYPE_OPERATOR_BINARY_ARRAY_IN), "RELATIONAL_ARRAY_IN"},
{static_cast<int>(NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN), "RELATIONAL_ARRAY_NOT_IN"}};
/// @brief user-accessible functions
std::unordered_map<std::string, Function const> const Executor::FunctionNames{
// meanings of the symbols in the function arguments list
// ------------------------------------------------------
//
// . = argument of any type (except collection)
// c = collection name, will be converted into list with documents
// h = collection name, will be converted into string
// z = null
// b = bool
// n = number
// s = string
// p = primitive
// l = array
// a = object / document
// r = regex (a string with a special format). note: the regex type is
// mutually exclusive with all other types
// type check functions
{"IS_NULL", Function("IS_NULL", "AQL_IS_NULL", ".", true, true, false, true,
true, &Functions::IsNull)},
{"IS_BOOL", Function("IS_BOOL", "AQL_IS_BOOL", ".", true, true, false, true,
true, &Functions::IsBool)},
{"IS_NUMBER", Function("IS_NUMBER", "AQL_IS_NUMBER", ".", true, true, false,
true, true, &Functions::IsNumber)},
{"IS_STRING", Function("IS_STRING", "AQL_IS_STRING", ".", true, true, false,
true, true, &Functions::IsString)},
{"IS_ARRAY", Function("IS_ARRAY", "AQL_IS_ARRAY", ".", true, true, false,
true, true, &Functions::IsArray)},
// IS_LIST is an alias for IS_ARRAY
{"IS_LIST", Function("IS_LIST", "AQL_IS_LIST", ".", true, true, false, true,
true, &Functions::IsArray)},
{"IS_OBJECT", Function("IS_OBJECT", "AQL_IS_OBJECT", ".", true, true, false,
true, true, &Functions::IsObject)},
// IS_DOCUMENT is an alias for IS_OBJECT
{"IS_DOCUMENT", Function("IS_DOCUMENT", "AQL_IS_DOCUMENT", ".", true, true,
false, true, true, &Functions::IsObject)},
{"IS_DATESTRING", Function("IS_DATESTRING", "AQL_IS_DATESTRING", ".", true,
true, false, true, true)},
// type cast functions
{"TO_NUMBER", Function("TO_NUMBER", "AQL_TO_NUMBER", ".", true, true, false,
true, true, &Functions::ToNumber)},
{"TO_STRING", Function("TO_STRING", "AQL_TO_STRING", ".", true, true, false,
true, true, &Functions::ToString)},
{"TO_BOOL", Function("TO_BOOL", "AQL_TO_BOOL", ".", true, true, false, true,
true, &Functions::ToBool)},
{"TO_ARRAY", Function("TO_ARRAY", "AQL_TO_ARRAY", ".", true, true, false,
true, true, &Functions::ToArray)},
// TO_LIST is an alias for TO_ARRAY
{"TO_LIST", Function("TO_LIST", "AQL_TO_LIST", ".", true, true, false, true,
true, &Functions::ToArray)},
// string functions
{"CONCAT", Function("CONCAT", "AQL_CONCAT", "szl|+", true, true, false,
true, true, &Functions::Concat)},
{"CONCAT_SEPARATOR", Function("CONCAT_SEPARATOR", "AQL_CONCAT_SEPARATOR",
"s,szl|+", true, true, false, true, true)},
{"CHAR_LENGTH", Function("CHAR_LENGTH", "AQL_CHAR_LENGTH", "s", true, true,
false, true, true)},
{"LOWER",
Function("LOWER", "AQL_LOWER", "s", true, true, false, true, true)},
{"UPPER",
Function("UPPER", "AQL_UPPER", "s", true, true, false, true, true)},
{"SUBSTRING", Function("SUBSTRING", "AQL_SUBSTRING", "s,n|n", true, true,
false, true, true)},
{"CONTAINS", Function("CONTAINS", "AQL_CONTAINS", "s,s|b", true, true,
false, true, true)},
{"LIKE", Function("LIKE", "AQL_LIKE", "s,r|b", true, true, false, true,
true, &Functions::Like)},
{"LEFT",
Function("LEFT", "AQL_LEFT", "s,n", true, true, false, true, true)},
{"RIGHT",
Function("RIGHT", "AQL_RIGHT", "s,n", true, true, false, true, true)},
{"TRIM",
Function("TRIM", "AQL_TRIM", "s|ns", true, true, false, true, true)},
{"LTRIM",
Function("LTRIM", "AQL_LTRIM", "s|s", true, true, false, true, true)},
{"RTRIM",
Function("RTRIM", "AQL_RTRIM", "s|s", true, true, false, true, true)},
{"FIND_FIRST", Function("FIND_FIRST", "AQL_FIND_FIRST", "s,s|zn,zn", true,
true, false, true, true)},
{"FIND_LAST", Function("FIND_LAST", "AQL_FIND_LAST", "s,s|zn,zn", true,
true, false, true, true)},
{"SPLIT",
Function("SPLIT", "AQL_SPLIT", "s|sl,n", true, true, false, true, true)},
{"SUBSTITUTE", Function("SUBSTITUTE", "AQL_SUBSTITUTE", "s,las|lsn,n", true,
true, false, true, true)},
{"MD5", Function("MD5", "AQL_MD5", "s", true, true, false, true, true,
&Functions::Md5)},
{"SHA1", Function("SHA1", "AQL_SHA1", "s", true, true, false, true, true,
&Functions::Sha1)},
{"RANDOM_TOKEN", Function("RANDOM_TOKEN", "AQL_RANDOM_TOKEN", "n", false,
false, true, true, true)},
// numeric functions
{"FLOOR", Function("FLOOR", "AQL_FLOOR", "n", true, true, false, true, true,
&Functions::Floor)},
{"CEIL", Function("CEIL", "AQL_CEIL", "n", true, true, false, true, true,
&Functions::Ceil)},
{"ROUND", Function("ROUND", "AQL_ROUND", "n", true, true, false, true, true,
&Functions::Round)},
{"ABS", Function("ABS", "AQL_ABS", "n", true, true, false, true, true,
&Functions::Abs)},
{"RAND", Function("RAND", "AQL_RAND", "", false, false, false, true, true,
&Functions::Rand)},
{"SQRT", Function("SQRT", "AQL_SQRT", "n", true, true, false, true, true,
&Functions::Sqrt)},
{"POW", Function("POW", "AQL_POW", "n,n", true, true, false, true, true,
&Functions::Pow)},
// list functions
{"RANGE", Function("RANGE", "AQL_RANGE", "n,n|n", true, true, false, true,
true, &Functions::Range)},
{"UNION", Function("UNION", "AQL_UNION", "l,l|+", true, true, false, true,
true, &Functions::Union)},
{"UNION_DISTINCT",
Function("UNION_DISTINCT", "AQL_UNION_DISTINCT", "l,l|+", true, true,
false, true, true, &Functions::UnionDistinct)},
{"MINUS", Function("MINUS", "AQL_MINUS", "l,l|+", true, true, false, true,
true, &Functions::Minus)},
{"INTERSECTION",
Function("INTERSECTION", "AQL_INTERSECTION", "l,l|+", true, true, false,
true, true, &Functions::Intersection)},
{"FLATTEN", Function("FLATTEN", "AQL_FLATTEN", "l|n", true, true, false,
true, true, &Functions::Flatten)},
{"LENGTH", Function("LENGTH", "AQL_LENGTH", "las", true, true, false, true,
true, &Functions::Length)},
{"COUNT", Function("COUNT", "AQL_LENGTH", "las", true, true, false, true,
true, &Functions::Length)}, // alias for LENGTH()
{"MIN", Function("MIN", "AQL_MIN", "l", true, true, false, true, true,
&Functions::Min)},
{"MAX", Function("MAX", "AQL_MAX", "l", true, true, false, true, true,
&Functions::Max)},
{"SUM", Function("SUM", "AQL_SUM", "l", true, true, false, true, true,
&Functions::Sum)},
{"MEDIAN", Function("MEDIAN", "AQL_MEDIAN", "l", true, true, false, true,
true, &Functions::Median)},
{"PERCENTILE", Function("PERCENTILE", "AQL_PERCENTILE", "l,n|s", true, true,
false, true, true, &Functions::Percentile)},
{"AVERAGE", Function("AVERAGE", "AQL_AVERAGE", "l", true, true, false, true,
true, &Functions::Average)},
{"AVG", Function("AVG", "AQL_AVERAGE", "l", true, true, false, true, true,
&Functions::Average)}, // alias for AVERAGE()
{"VARIANCE_SAMPLE",
Function("VARIANCE_SAMPLE", "AQL_VARIANCE_SAMPLE", "l", true, true, false,
true, true, &Functions::VarianceSample)},
{"VARIANCE_POPULATION",
Function("VARIANCE_POPULATION", "AQL_VARIANCE_POPULATION", "l", true, true,
false, true, true, &Functions::VariancePopulation)},
{"VARIANCE",
Function(
"VARIANCE", "AQL_VARIANCE_POPULATION", "l", true, true, false, true,
true,
&Functions::VariancePopulation)}, // alias for VARIANCE_POPULATION()
{"STDDEV_SAMPLE",
Function("STDDEV_SAMPLE", "AQL_STDDEV_SAMPLE", "l", true, true, false,
true, true, &Functions::StdDevSample)},
{"STDDEV_POPULATION",
Function("STDDEV_POPULATION", "AQL_STDDEV_POPULATION", "l", true, true,
false, true, true, &Functions::StdDevPopulation)},
{"STDDEV",
Function("STDDEV", "AQL_STDDEV_POPULATION", "l", true, true, false, true,
true,
&Functions::StdDevPopulation)}, // alias for STDDEV_POPULATION()
{"UNIQUE", Function("UNIQUE", "AQL_UNIQUE", "l", true, true, false, true,
true, &Functions::Unique)},
{"SORTED_UNIQUE",
Function("SORTED_UNIQUE", "AQL_SORTED_UNIQUE", "l", true, true, false,
true, true, &Functions::SortedUnique)},
{"SLICE",
Function("SLICE", "AQL_SLICE", "l,n|n", true, true, false, true, true)},
{"REVERSE",
Function("REVERSE", "AQL_REVERSE", "ls", true, true, false, true,
true)}, // note: REVERSE() can be applied on strings, too
{"FIRST", Function("FIRST", "AQL_FIRST", "l", true, true, false, true, true,
&Functions::First)},
{"LAST", Function("LAST", "AQL_LAST", "l", true, true, false, true, true,
&Functions::Last)},
{"NTH", Function("NTH", "AQL_NTH", "l,n", true, true, false, true, true,
&Functions::Nth)},
{"POSITION", Function("POSITION", "AQL_POSITION", "l,.|b", true, true,
false, true, true, &Functions::Position)},
{"CALL",
Function("CALL", "AQL_CALL", "s|.+", false, false, true, false, true)},
{"APPLY",
Function("APPLY", "AQL_APPLY", "s|l", false, false, true, false, false)},
{"PUSH", Function("PUSH", "AQL_PUSH", "l,.|b", true, true, false, true,
false, &Functions::Push)},
{"APPEND", Function("APPEND", "AQL_APPEND", "l,lz|b", true, true, false,
true, true, &Functions::Append)},
{"POP", Function("POP", "AQL_POP", "l", true, true, false, true, true,
&Functions::Pop)},
{"SHIFT", Function("SHIFT", "AQL_SHIFT", "l", true, true, false, true, true,
&Functions::Shift)},
{"UNSHIFT", Function("UNSHIFT", "AQL_UNSHIFT", "l,.|b", true, true, false,
true, true, &Functions::Unshift)},
{"REMOVE_VALUE",
Function("REMOVE_VALUE", "AQL_REMOVE_VALUE", "l,.|n", true, true, false,
true, true, &Functions::RemoveValue)},
{"REMOVE_VALUES",
Function("REMOVE_VALUES", "AQL_REMOVE_VALUES", "l,lz", true, true, false,
true, true, &Functions::RemoveValues)},
{"REMOVE_NTH", Function("REMOVE_NTH", "AQL_REMOVE_NTH", "l,n", true, true,
false, true, true, &Functions::RemoveNth)},
// document functions
{"HAS", Function("HAS", "AQL_HAS", "az,s", true, true, false, true, true,
&Functions::Has)},
{"ATTRIBUTES", Function("ATTRIBUTES", "AQL_ATTRIBUTES", "a|b,b", true, true,
false, true, true, &Functions::Attributes)},
{"VALUES", Function("VALUES", "AQL_VALUES", "a|b", true, true, false, true,
true, &Functions::Values)},
{"MERGE", Function("MERGE", "AQL_MERGE", "la|+", true, true, false, true,
true, &Functions::Merge)},
{"MERGE_RECURSIVE",
Function("MERGE_RECURSIVE", "AQL_MERGE_RECURSIVE", "a,a|+", true, true,
false, true, true, &Functions::MergeRecursive)},
{"DOCUMENT",
Function("DOCUMENT", "AQL_DOCUMENT", "h.|.", false, false, true, false,
true, &Functions::Document, NotInCluster)},
{"MATCHES", Function("MATCHES", "AQL_MATCHES", ".,l|b", true, true, false,
true, true)},
{"UNSET", Function("UNSET", "AQL_UNSET", "a,sl|+", true, true, false, true,
true, &Functions::Unset)},
{"UNSET_RECURSIVE",
Function("UNSET_RECURSIVE", "AQL_UNSET_RECURSIVE", "a,sl|+", true, true,
false, true, true, &Functions::UnsetRecursive)},
{"KEEP", Function("KEEP", "AQL_KEEP", "a,sl|+", true, true, false, true,
true, &Functions::Keep)},
{"TRANSLATE", Function("TRANSLATE", "AQL_TRANSLATE", ".,a|.", true, true,
false, true, true)},
{"ZIP", Function("ZIP", "AQL_ZIP", "l,l", true, true, false, true, true,
&Functions::Zip)},
// geo functions
{"NEAR", Function("NEAR", "AQL_NEAR", "hs,n,n|nz,s", true, false, true,
false, true, &Functions::Near, NotInCoordinator)},
{"WITHIN", Function("WITHIN", "AQL_WITHIN", "hs,n,n,n|s", true, false, true,
false, true, &Functions::Within, NotInCoordinator)},
{"WITHIN_RECTANGLE",
Function("WITHIN_RECTANGLE", "AQL_WITHIN_RECTANGLE", "hs,d,d,d,d", true,
false, true, false, true)},
{"IS_IN_POLYGON", Function("IS_IN_POLYGON", "AQL_IS_IN_POLYGON", "l,ln|nb",
true, true, false, true, true)},
// fulltext functions
{"FULLTEXT",
Function("FULLTEXT", "AQL_FULLTEXT", "hs,s,s|n", true, false, true, false,
true, &Functions::Fulltext, NotInCoordinator)},
// graph functions
{"PATHS", Function("PATHS", "AQL_PATHS", "c,h|s,ba", true, false, true,
false, false)},
{"GRAPH_PATHS", Function("GRAPH_PATHS", "AQL_GRAPH_PATHS", "s|a", false,
false, true, false, false)},
{"SHORTEST_PATH", Function("SHORTEST_PATH", "AQL_SHORTEST_PATH",
"h,h,s,s,s|a", true, false, true, false, false)},
{"GRAPH_SHORTEST_PATH",
Function("GRAPH_SHORTEST_PATH", "AQL_GRAPH_SHORTEST_PATH", "s,als,als|a",
false, false, true, false, false)},
{"GRAPH_DISTANCE_TO",
Function("GRAPH_DISTANCE_TO", "AQL_GRAPH_DISTANCE_TO", "s,als,als|a",
false, false, true, false, false)},
{"TRAVERSAL", Function("TRAVERSAL", "AQL_TRAVERSAL", "hs,hs,s,s|a", false,
false, true, false, false)},
{"GRAPH_TRAVERSAL",
Function("GRAPH_TRAVERSAL", "AQL_GRAPH_TRAVERSAL", "s,als,s|a", false,
false, true, false, false)},
{"TRAVERSAL_TREE",
Function("TRAVERSAL_TREE", "AQL_TRAVERSAL_TREE", "hs,hs,s,s,s|a", false,
false, true, false, false)},
{"GRAPH_TRAVERSAL_TREE",
Function("GRAPH_TRAVERSAL_TREE", "AQL_GRAPH_TRAVERSAL_TREE", "s,als,s,s|a",
false, false, true, false, false)},
{"EDGES", Function("EDGES", "AQL_EDGES", "hs,s,s|l,o", true, false, true,
false, false, &Functions::Edges, NotInCluster)},
{"GRAPH_EDGES", Function("GRAPH_EDGES", "AQL_GRAPH_EDGES", "s,als|a", false,
false, true, false, false)},
{"GRAPH_VERTICES", Function("GRAPH_VERTICES", "AQL_GRAPH_VERTICES",
"s,als|a", false, false, true, false, false)},
{"NEIGHBORS",
Function("NEIGHBORS", "AQL_NEIGHBORS", "hs,hs,s,s|l,a", true, false, true,
false, false, &Functions::Neighbors, NotInCluster)},
{"GRAPH_NEIGHBORS", Function("GRAPH_NEIGHBORS", "AQL_GRAPH_NEIGHBORS",
"s,als|a", false, false, true, false, false)},
{"GRAPH_COMMON_NEIGHBORS",
Function("GRAPH_COMMON_NEIGHBORS", "AQL_GRAPH_COMMON_NEIGHBORS",
"s,als,als|a,a", false, false, true, false, false)},
{"GRAPH_COMMON_PROPERTIES",
Function("GRAPH_COMMON_PROPERTIES", "AQL_GRAPH_COMMON_PROPERTIES",
"s,als,als|a", false, false, true, false, false)},
{"GRAPH_ECCENTRICITY",
Function("GRAPH_ECCENTRICITY", "AQL_GRAPH_ECCENTRICITY", "s|a", false,
false, true, false, false)},
{"GRAPH_BETWEENNESS", Function("GRAPH_BETWEENNESS", "AQL_GRAPH_BETWEENNESS",
"s|a", false, false, true, false, false)},
{"GRAPH_CLOSENESS", Function("GRAPH_CLOSENESS", "AQL_GRAPH_CLOSENESS",
"s|a", false, false, true, false, false)},
{"GRAPH_ABSOLUTE_ECCENTRICITY",
Function("GRAPH_ABSOLUTE_ECCENTRICITY", "AQL_GRAPH_ABSOLUTE_ECCENTRICITY",
"s,als|a", false, false, true, false, false)},
{"GRAPH_ABSOLUTE_BETWEENNESS",
Function("GRAPH_ABSOLUTE_BETWEENNESS", "AQL_GRAPH_ABSOLUTE_BETWEENNESS",
"s,als|a", false, false, true, false, false)},
{"GRAPH_ABSOLUTE_CLOSENESS",
Function("GRAPH_ABSOLUTE_CLOSENESS", "AQL_GRAPH_ABSOLUTE_CLOSENESS",
"s,als|a", false, false, true, false, false)},
{"GRAPH_DIAMETER", Function("GRAPH_DIAMETER", "AQL_GRAPH_DIAMETER", "s|a",
false, false, true, false, false)},
{"GRAPH_RADIUS", Function("GRAPH_RADIUS", "AQL_GRAPH_RADIUS", "s|a", false,
false, true, false, false)},
// date functions
{"DATE_NOW",
Function("DATE_NOW", "AQL_DATE_NOW", "", false, false, false, true, true)},
{"DATE_TIMESTAMP",
Function("DATE_TIMESTAMP", "AQL_DATE_TIMESTAMP", "ns|ns,ns,ns,ns,ns,ns",
true, true, false, true, true)},
{"DATE_ISO8601",
Function("DATE_ISO8601", "AQL_DATE_ISO8601", "ns|ns,ns,ns,ns,ns,ns", true,
true, false, true, true)},
{"DATE_DAYOFWEEK", Function("DATE_DAYOFWEEK", "AQL_DATE_DAYOFWEEK", "ns",
true, true, false, true, true)},
{"DATE_YEAR", Function("DATE_YEAR", "AQL_DATE_YEAR", "ns", true, true,
false, true, true)},
{"DATE_MONTH", Function("DATE_MONTH", "AQL_DATE_MONTH", "ns", true, true,
false, true, true)},
{"DATE_DAY",
Function("DATE_DAY", "AQL_DATE_DAY", "ns", true, true, false, true, true)},
{"DATE_HOUR", Function("DATE_HOUR", "AQL_DATE_HOUR", "ns", true, true,
false, true, true)},
{"DATE_MINUTE", Function("DATE_MINUTE", "AQL_DATE_MINUTE", "ns", true, true,
false, true, true)},
{"DATE_SECOND", Function("DATE_SECOND", "AQL_DATE_SECOND", "ns", true, true,
false, true, true)},
{"DATE_MILLISECOND", Function("DATE_MILLISECOND", "AQL_DATE_MILLISECOND",
"ns", true, true, false, true, true)},
{"DATE_DAYOFYEAR", Function("DATE_DAYOFYEAR", "AQL_DATE_DAYOFYEAR", "ns",
true, true, false, true, true)},
{"DATE_ISOWEEK", Function("DATE_ISOWEEK", "AQL_DATE_ISOWEEK", "ns", true,
true, false, true, true)},
{"DATE_LEAPYEAR", Function("DATE_LEAPYEAR", "AQL_DATE_LEAPYEAR", "ns", true,
true, false, true, true)},
{"DATE_QUARTER", Function("DATE_QUARTER", "AQL_DATE_QUARTER", "ns", true,
true, false, true, true)},
{"DATE_DAYS_IN_MONTH",
Function("DATE_DAYS_IN_MONTH", "AQL_DATE_DAYS_IN_MONTH", "ns", true, true,
false, true, true)},
{"DATE_ADD", Function("DATE_ADD", "AQL_DATE_ADD", "ns,ns|n", true, true,
false, true, true)},
{"DATE_SUBTRACT", Function("DATE_SUBTRACT", "AQL_DATE_SUBTRACT", "ns,ns|n",
true, true, false, true, true)},
{"DATE_DIFF", Function("DATE_DIFF", "AQL_DATE_DIFF", "ns,ns,s|b", true,
true, false, true, true)},
{"DATE_COMPARE", Function("DATE_COMPARE", "AQL_DATE_COMPARE", "ns,ns,s|s",
true, true, false, true, true)},
{"DATE_FORMAT", Function("DATE_FORMAT", "AQL_DATE_FORMAT", "ns,s", true,
true, false, true, true)},
// misc functions
{"FAIL",
Function("FAIL", "AQL_FAIL", "|s", false, false, true, true, true)},
{"PASSTHRU", Function("PASSTHRU", "AQL_PASSTHRU", ".", false, false, false,
true, true, &Functions::Passthru)},
{"NOOPT", Function("NOOPT", "AQL_PASSTHRU", ".", false, false, false, true,
true, &Functions::Passthru)},
{"V8",
Function("V8", "AQL_PASSTHRU", ".", false, false, false, true, true)},
{"TEST_INTERNAL", Function("TEST_INTERNAL", "AQL_TEST_INTERNAL", "s,.",
false, false, false, true, false)},
{"SLEEP",
Function("SLEEP", "AQL_SLEEP", "n", false, false, true, true, true)},
{"COLLECTIONS", Function("COLLECTIONS", "AQL_COLLECTIONS", "", false, false,
true, false, true)},
{"NOT_NULL", Function("NOT_NULL", "AQL_NOT_NULL", ".|+", true, true, false,
true, true, &Functions::NotNull)},
{"FIRST_LIST", Function("FIRST_LIST", "AQL_FIRST_LIST", ".|+", true, true,
false, true, true, &Functions::FirstList)},
{"FIRST_DOCUMENT",
Function("FIRST_DOCUMENT", "AQL_FIRST_DOCUMENT", ".|+", true, true, false,
true, true, &Functions::FirstDocument)},
{"PARSE_IDENTIFIER",
Function("PARSE_IDENTIFIER", "AQL_PARSE_IDENTIFIER", ".", true, true,
false, true, true, &Functions::ParseIdentifier)},
{"IS_SAME_COLLECTION",
Function("IS_SAME_COLLECTION", "AQL_IS_SAME_COLLECTION", "ch,as", true, true,
false, true, true, &Functions::IsSameCollection)},
{"CURRENT_USER", Function("CURRENT_USER", "AQL_CURRENT_USER", "", false,
false, false, false, true)},
{"CURRENT_DATABASE",
Function("CURRENT_DATABASE", "AQL_CURRENT_DATABASE", "", false, false,
false, false, true, &Functions::CurrentDatabase)},
{"COLLECTION_COUNT",
Function("COLLECTION_COUNT", "AQL_COLLECTION_COUNT", "chs", false, false,
true, false, true, &Functions::CollectionCount, NotInCluster)}};
/// @brief minimum number of array members / object attributes for considering
/// an array / object literal "big" and pulling it out of the expression
size_t const Executor::DefaultLiteralSizeThreshold = 32;
/// @brief maxmium number of array members created from range accesses
int64_t const Executor::MaxRangeAccessArraySize = 1024 * 1024 * 32;
/// @brief creates an executor
Executor::Executor(int64_t literalSizeThreshold)
: _buffer(nullptr),
_constantRegisters(),
_literalSizeThreshold(literalSizeThreshold >= 0
? static_cast<size_t>(literalSizeThreshold)
: DefaultLiteralSizeThreshold) {}
/// @brief destroys an executor
Executor::~Executor() { delete _buffer; }
/// @brief generates an expression execution object
V8Expression* Executor::generateExpression(AstNode const* node) {
ISOLATE;
v8::HandleScope scope(isolate);
v8::TryCatch tryCatch;
_constantRegisters.clear();
detectConstantValues(node, node->type);
generateCodeExpression(node);
// std::cout << "Executor::generateExpression: " <<
// std::string(_buffer->c_str(), _buffer->length()) << "\n";
v8::Handle<v8::Object> constantValues = v8::Object::New(isolate);
for (auto const& it : _constantRegisters) {
std::string name = "r";
name.append(std::to_string(it.second));
constantValues->ForceSet(TRI_V8_STD_STRING(name), toV8(isolate, it.first));
}
// compile the expression
v8::Handle<v8::Value> func(compileExpression());
// exit early if an error occurred
HandleV8Error(tryCatch, func);
// a "simple" expression here is any expression that will only return
// non-cyclic
// data and will not return any special JavaScript types such as Date, RegExp
// or
// Function
// as we know that all built-in AQL functions are simple but do not know
// anything
// about user-defined functions, so we expect them to be non-simple
bool const isSimple = (!node->callsUserDefinedFunction());
return new V8Expression(isolate, v8::Handle<v8::Function>::Cast(func),
constantValues, isSimple);
}
/// @brief executes an expression directly
/// this method is called during AST optimization and will be used to calculate
/// values for constant expressions
int Executor::executeExpression(Query* query, AstNode const* node,
VPackBuilder& builder) {
ISOLATE;
_constantRegisters.clear();
generateCodeExpression(node);
// std::cout << "Executor::ExecuteExpression: " <<
// std::string(_buffer->c_str(), _buffer->length()) << "\n";
v8::HandleScope scope(isolate);
v8::TryCatch tryCatch;
// compile the expression
v8::Handle<v8::Value> func(compileExpression());
// exit early if an error occurred
HandleV8Error(tryCatch, func);
TRI_ASSERT(query != nullptr);
TRI_GET_GLOBALS();
v8::Handle<v8::Value> result;
auto old = v8g->_query;
try {
v8g->_query = static_cast<void*>(query);
TRI_ASSERT(v8g->_query != nullptr);
// execute the function
v8::Handle<v8::Value> args;
result = v8::Handle<v8::Function>::Cast(func)
->Call(v8::Object::New(isolate), 0, &args);
v8g->_query = old;
// exit if execution raised an error
HandleV8Error(tryCatch, result);
} catch (...) {
v8g->_query = old;
throw;
}
if (result->IsUndefined()) {
// undefined => null
builder.add(VPackValue(VPackValueType::Null));
return TRI_ERROR_NO_ERROR;
}
return TRI_V8ToVPack(isolate, builder, result, false);
}
/// @brief returns a reference to a built-in function
Function const* Executor::getFunctionByName(std::string const& name) {
auto it = FunctionNames.find(name);
if (it == FunctionNames.end()) {
THROW_ARANGO_EXCEPTION_PARAMS(TRI_ERROR_QUERY_FUNCTION_NAME_UNKNOWN,
name.c_str());
}
// return the address of the function
return &((*it).second);
}
/// @brief traverse the expression and note all (big) array/object literals
void Executor::detectConstantValues(AstNode const* node, AstNodeType previous) {
if (node == nullptr) {
return;
}
size_t const n = node->numMembers();
if (previous != NODE_TYPE_FCALL && previous != NODE_TYPE_FCALL_USER) {
// FCALL has an ARRAY node as its immediate child
// however, we do not want to constify this whole array, but its
// individual memb
// just its individual members
// otherwise, only the ARRAY node will be marked as constant but not
// its members. When the code is generated for the function call,
// the ARRAY node will be ignored because only its individual members
// (not being marked as const), will be emitted regularly, which would
// disable the const optimizations if all function call arguments are
// constants
if ((node->type == NODE_TYPE_ARRAY || node->type == NODE_TYPE_OBJECT) &&
n >= _literalSizeThreshold && node->isConstant()) {
_constantRegisters.emplace(node, _constantRegisters.size());
return;
}
}
auto nextType = node->type;
if (previous == NODE_TYPE_FCALL_USER) {
// FCALL_USER is sticky, so its arguments will not be constified
nextType = NODE_TYPE_FCALL_USER;
} else if (nextType == NODE_TYPE_FCALL) {
auto func = static_cast<Function*>(node->getData());
if (!func->canPassArgumentsByReference) {
// function should not retrieve its arguments by reference,
// so we pretend here that it is a user-defined function
// (user-defined functions will not get their arguments by
// reference)
nextType = NODE_TYPE_FCALL_USER;
}
}
for (size_t i = 0; i < n; ++i) {
detectConstantValues(node->getMemberUnchecked(i), nextType);
}
}
/// @brief convert an AST node to a V8 object
v8::Handle<v8::Value> Executor::toV8(v8::Isolate* isolate,
AstNode const* node) const {
if (node->type == NODE_TYPE_ARRAY) {
size_t const n = node->numMembers();
v8::Handle<v8::Array> result = v8::Array::New(isolate, static_cast<int>(n));
for (size_t i = 0; i < n; ++i) {
result->Set(static_cast<uint32_t>(i), toV8(isolate, node->getMember(i)));
}
return result;
}
if (node->type == NODE_TYPE_OBJECT) {
size_t const n = node->numMembers();
v8::Handle<v8::Object> result = v8::Object::New(isolate);
for (size_t i = 0; i < n; ++i) {
auto sub = node->getMember(i);
result->ForceSet(
TRI_V8_PAIR_STRING(sub->getStringValue(), sub->getStringLength()),
toV8(isolate, sub->getMember(0)));
}
return result;
}
if (node->type == NODE_TYPE_VALUE) {
switch (node->value.type) {
case VALUE_TYPE_NULL:
return v8::Null(isolate);
case VALUE_TYPE_BOOL:
return v8::Boolean::New(isolate, node->value.value._bool);
case VALUE_TYPE_INT:
return v8::Number::New(isolate,
static_cast<double>(node->value.value._int));
case VALUE_TYPE_DOUBLE:
return v8::Number::New(isolate,
static_cast<double>(node->value.value._double));
case VALUE_TYPE_STRING:
return TRI_V8_PAIR_STRING(node->value.value._string,
node->value.length);
}
}
return v8::Null(isolate);
}
/// @brief checks if a V8 exception has occurred and throws an appropriate C++
/// exception from it if so
void Executor::HandleV8Error(v8::TryCatch& tryCatch,
v8::Handle<v8::Value>& result) {
ISOLATE;
if (tryCatch.HasCaught()) {
// caught a V8 exception
if (!tryCatch.CanContinue()) {
// request was canceled
TRI_GET_GLOBALS();
v8g->_canceled = true;
THROW_ARANGO_EXCEPTION(TRI_ERROR_REQUEST_CANCELED);
}
// request was not canceled, but some other error occurred
// peek into the exception
if (tryCatch.Exception()->IsObject()) {
// cast the exception to an object
v8::Handle<v8::Array> objValue =
v8::Handle<v8::Array>::Cast(tryCatch.Exception());
v8::Handle<v8::String> errorNum = TRI_V8_ASCII_STRING("errorNum");
v8::Handle<v8::String> errorMessage = TRI_V8_ASCII_STRING("errorMessage");
TRI_Utf8ValueNFC stacktrace(TRI_UNKNOWN_MEM_ZONE, tryCatch.StackTrace());
if (objValue->HasOwnProperty(errorNum) &&
objValue->HasOwnProperty(errorMessage)) {
v8::Handle<v8::Value> errorNumValue = objValue->Get(errorNum);
v8::Handle<v8::Value> errorMessageValue = objValue->Get(errorMessage);
// found something that looks like an ArangoError
if ((errorNumValue->IsNumber() || errorNumValue->IsNumberObject()) &&
(errorMessageValue->IsString() ||
errorMessageValue->IsStringObject())) {
int errorCode = static_cast<int>(TRI_ObjectToInt64(errorNumValue));
std::string errorMessage(TRI_ObjectToString(errorMessageValue));
if (*stacktrace && stacktrace.length() > 0) {
errorMessage += "\nstacktrace of offending AQL function: ";
errorMessage += *stacktrace;
}
THROW_ARANGO_EXCEPTION_MESSAGE(errorCode, errorMessage);
}
}
// exception is no ArangoError
std::string details(TRI_ObjectToString(tryCatch.Exception()));
if (*stacktrace && stacktrace.length() > 0) {
details += "\nstacktrace of offending AQL function: ";
details += *stacktrace;
}
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_SCRIPT, details);
}
// we can't figure out what kind of error occurred and throw a generic error
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL,
"unknown error in scripting");
}
if (result.IsEmpty()) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL,
"unknown error in scripting");
}
// if we get here, no exception has been raised
}
/// @brief compile a V8 function from the code contained in the buffer
v8::Handle<v8::Value> Executor::compileExpression() {
TRI_ASSERT(_buffer != nullptr);
ISOLATE;
v8::Handle<v8::Script> compiled = v8::Script::Compile(
TRI_V8_STD_STRING((*_buffer)), TRI_V8_ASCII_STRING("--script--"));
if (compiled.IsEmpty()) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL,
"unable to compile v8 expression");
}
return compiled->Run();
}
/// @brief generate JavaScript code for an arbitrary expression
void Executor::generateCodeExpression(AstNode const* node) {
// initialize and/or clear the buffer
initializeBuffer();
TRI_ASSERT(_buffer != nullptr);
// write prologue
// this checks if global variable _AQL is set and populates if it not
_buffer->appendText(TRI_CHAR_LENGTH_PAIR(
"(function (vars, consts, reset) { if (_AQL === undefined) { _AQL = "
"require(\"@arangodb/aql\"); } if (reset) { _AQL.clearCaches(); } "
"return "));
generateCodeNode(node);
// write epilogue
_buffer->appendText(TRI_CHAR_LENGTH_PAIR(";})"));
}
/// @brief generates code for a string value
void Executor::generateCodeString(char const* value, size_t length) {
TRI_ASSERT(value != nullptr);
_buffer->appendChar('"');
_buffer->appendJsonEncoded(value, length);
_buffer->appendChar('"');
}
/// @brief generates code for a string value
void Executor::generateCodeString(std::string const& value) {
_buffer->appendChar('"');
_buffer->appendJsonEncoded(value.c_str(), value.size());
_buffer->appendChar('"');
}
/// @brief generate JavaScript code for an array
void Executor::generateCodeArray(AstNode const* node) {
TRI_ASSERT(node != nullptr);
size_t const n = node->numMembers();
if (n >= _literalSizeThreshold && node->isConstant()) {
auto it = _constantRegisters.find(node);
if (it != _constantRegisters.end()) {
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("consts.r"));
_buffer->appendInteger((*it).second);
return;
}
}
// very conservative minimum bound
_buffer->reserve(2 + n * 3);
_buffer->appendChar('[');
for (size_t i = 0; i < n; ++i) {
if (i > 0) {
_buffer->appendChar(',');
}
generateCodeNode(node->getMemberUnchecked(i));
}
_buffer->appendChar(']');
}
/// @brief generate JavaScript code for an array
void Executor::generateCodeForcedArray(AstNode const* node, int64_t levels) {
TRI_ASSERT(node != nullptr);
if (levels > 1) {
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("_AQL.AQL_FLATTEN("));
}
bool castToArray = true;
if (node->type == NODE_TYPE_ARRAY) {
// value is an array already
castToArray = false;
} else if (node->type == NODE_TYPE_EXPANSION &&
node->getMember(0)->type == NODE_TYPE_ARRAY) {
// value is an expansion over an array
castToArray = false;
} else if (node->type == NODE_TYPE_ITERATOR &&
node->getMember(1)->type == NODE_TYPE_ARRAY) {
castToArray = false;
}
if (castToArray) {
// force the value to be an array
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("_AQL.AQL_TO_ARRAY("));
generateCodeNode(node);
_buffer->appendText(", false");
_buffer->appendChar(')');
} else {
// value already is an array
generateCodeNode(node);
}
if (levels > 1) {
_buffer->appendChar(',');
_buffer->appendInteger(levels - 1);
_buffer->appendChar(')');
}
}
/// @brief generate JavaScript code for an object
void Executor::generateCodeObject(AstNode const* node) {
TRI_ASSERT(node != nullptr);
if (node->containsDynamicAttributeName()) {
generateCodeDynamicObject(node);
} else {
generateCodeRegularObject(node);
}
}
/// @brief generate JavaScript code for an object with dynamically named
/// attributes
void Executor::generateCodeDynamicObject(AstNode const* node) {
size_t const n = node->numMembers();
// very conservative minimum bound
_buffer->reserve(64 + n * 10);
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("(function() { var o={};"));
for (size_t i = 0; i < n; ++i) {
auto member = node->getMemberUnchecked(i);
if (member->type == NODE_TYPE_OBJECT_ELEMENT) {
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("o["));
generateCodeString(member->getStringValue(), member->getStringLength());
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("]="));
generateCodeNode(member->getMember(0));
} else {
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("o[_AQL.AQL_TO_STRING("));
generateCodeNode(member->getMember(0));
_buffer->appendText(TRI_CHAR_LENGTH_PAIR(")]="));
generateCodeNode(member->getMember(1));
}
_buffer->appendChar(';');
}
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("return o;})()"));
}
/// @brief generate JavaScript code for an object without dynamically named
/// attributes
void Executor::generateCodeRegularObject(AstNode const* node) {
TRI_ASSERT(node != nullptr);
size_t const n = node->numMembers();
if (n >= _literalSizeThreshold && node->isConstant()) {
auto it = _constantRegisters.find(node);
if (it != _constantRegisters.end()) {
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("consts.r"));
_buffer->appendInteger((*it).second);
return;
}
}
// very conservative minimum bound
_buffer->reserve(2 + n * 7);
_buffer->appendChar('{');
for (size_t i = 0; i < n; ++i) {
if (i > 0) {
_buffer->appendChar(',');
}
auto member = node->getMember(i);
if (member != nullptr) {
generateCodeString(member->getStringValue(), member->getStringLength());
_buffer->appendChar(':');
generateCodeNode(member->getMember(0));
}
}
_buffer->appendChar('}');
}
/// @brief generate JavaScript code for a unary operator
void Executor::generateCodeUnaryOperator(AstNode const* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->numMembers() == 1);
auto it = InternalFunctionNames.find(static_cast<int>(node->type));
if (it == InternalFunctionNames.end()) {
// no function found for the type of node
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "unary operator function not found");
}
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("_AQL."));
_buffer->appendText((*it).second);
_buffer->appendChar('(');
generateCodeNode(node->getMember(0));
_buffer->appendChar(')');
}
/// @brief generate JavaScript code for a binary operator
void Executor::generateCodeBinaryOperator(AstNode const* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->numMembers() == 2);
auto it = InternalFunctionNames.find(static_cast<int>(node->type));
if (it == InternalFunctionNames.end()) {
// no function found for the type of node
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "binary operator function not found");
}
bool wrap = (node->type == NODE_TYPE_OPERATOR_BINARY_AND ||
node->type == NODE_TYPE_OPERATOR_BINARY_OR);
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("_AQL."));
_buffer->appendText((*it).second);
_buffer->appendChar('(');
if (wrap) {
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("function () { return "));
generateCodeNode(node->getMember(0));
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("}, function () { return "));
generateCodeNode(node->getMember(1));
_buffer->appendChar('}');
} else {
generateCodeNode(node->getMember(0));
_buffer->appendChar(',');
generateCodeNode(node->getMember(1));
}
_buffer->appendChar(')');
}
/// @brief generate JavaScript code for a binary array operator
void Executor::generateCodeBinaryArrayOperator(AstNode const* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->numMembers() == 3);
auto it = InternalFunctionNames.find(static_cast<int>(node->type));
if (it == InternalFunctionNames.end()) {
// no function found for the type of node
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "binary array function not found");
}
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("_AQL."));
_buffer->appendText((*it).second);
_buffer->appendChar('(');
generateCodeNode(node->getMember(0));
_buffer->appendChar(',');
generateCodeNode(node->getMember(1));
AstNode const* quantifier = node->getMember(2);
if (quantifier->type == NODE_TYPE_QUANTIFIER) {
_buffer->appendChar(',');
_buffer->appendInteger(quantifier->getIntValue(true));
}
_buffer->appendChar(')');
}
/// @brief generate JavaScript code for the ternary operator
void Executor::generateCodeTernaryOperator(AstNode const* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->numMembers() == 3);
auto it = InternalFunctionNames.find(static_cast<int>(node->type));
if (it == InternalFunctionNames.end()) {
// no function found for the type of node
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "function not found");
}
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("_AQL."));
_buffer->appendText((*it).second);
_buffer->appendChar('(');
generateCodeNode(node->getMember(0));
_buffer->appendText(TRI_CHAR_LENGTH_PAIR(", function () { return "));
generateCodeNode(node->getMember(1));
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("}, function () { return "));
generateCodeNode(node->getMember(2));
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("})"));
}
/// @brief generate JavaScript code for a variable (read) access
void Executor::generateCodeReference(AstNode const* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->numMembers() == 0);
auto variable = static_cast<Variable*>(node->getData());
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("vars["));
generateCodeString(variable->name);
_buffer->appendChar(']');
}
/// @brief generate JavaScript code for a variable
void Executor::generateCodeVariable(AstNode const* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->numMembers() == 0);
auto variable = static_cast<Variable*>(node->getData());
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("vars["));
generateCodeString(variable->name);
_buffer->appendChar(']');
}
/// @brief generate JavaScript code for a full collection access
void Executor::generateCodeCollection(AstNode const* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->numMembers() == 0);
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("_AQL.GET_DOCUMENTS("));
generateCodeString(node->getStringValue(), node->getStringLength());
_buffer->appendChar(')');
}
/// @brief generate JavaScript code for a call to a built-in function
void Executor::generateCodeFunctionCall(AstNode const* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->numMembers() == 1);
auto func = static_cast<Function*>(node->getData());
auto args = node->getMember(0);
TRI_ASSERT(args != nullptr);
TRI_ASSERT(args->type == NODE_TYPE_ARRAY);
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("_AQL."));
_buffer->appendText(func->internalName);
_buffer->appendChar('(');
size_t const n = args->numMembers();
for (size_t i = 0; i < n; ++i) {
if (i > 0) {
_buffer->appendChar(',');
}
auto member = args->getMember(i);
if (member == nullptr) {
continue;
}
auto conversion = func->getArgumentConversion(i);
if (member->type == NODE_TYPE_COLLECTION &&
(conversion == Function::CONVERSION_REQUIRED ||
conversion == Function::CONVERSION_OPTIONAL)) {
// the parameter at this position is a collection name that is converted
// to a string
// do a parameter conversion from a collection parameter to a collection
// name parameter
generateCodeString(member->getStringValue(), member->getStringLength());
} else if (conversion == Function::CONVERSION_REQUIRED) {
// the parameter at the position is not a collection name... fail
THROW_ARANGO_EXCEPTION_PARAMS(
TRI_ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH,
func->externalName.c_str());
} else {
generateCodeNode(args->getMember(i));
}
}
_buffer->appendChar(')');
}
/// @brief generate JavaScript code for a call to a user-defined function
void Executor::generateCodeUserFunctionCall(AstNode const* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->numMembers() == 1);
auto args = node->getMember(0);
TRI_ASSERT(args != nullptr);
TRI_ASSERT(args->type == NODE_TYPE_ARRAY);
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("_AQL.FCALL_USER("));
generateCodeString(node->getStringValue(), node->getStringLength());
_buffer->appendChar(',');
generateCodeNode(args);
_buffer->appendChar(')');
}
/// @brief generate JavaScript code for an expansion (i.e. [*] operator)
void Executor::generateCodeExpansion(AstNode const* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->numMembers() == 5);
auto levels = node->getIntValue(true);
auto iterator = node->getMember(0);
auto variable = static_cast<Variable*>(iterator->getMember(0)->getData());
// start LIMIT
auto limitNode = node->getMember(3);
if (limitNode->type != NODE_TYPE_NOP) {
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("_AQL.AQL_SLICE("));
}
generateCodeForcedArray(node->getMember(0), levels);
// FILTER
auto filterNode = node->getMember(2);
if (filterNode->type != NODE_TYPE_NOP) {
_buffer->appendText(TRI_CHAR_LENGTH_PAIR(".filter(function (v) { "));
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("vars[\""));
_buffer->appendText(variable->name);
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("\"]=v; "));
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("return _AQL.AQL_TO_BOOL("));
generateCodeNode(filterNode);
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("); })"));
}
// finish LIMIT
if (limitNode->type != NODE_TYPE_NOP) {
_buffer->appendChar(',');
generateCodeNode(limitNode->getMember(0));
_buffer->appendChar(',');
generateCodeNode(limitNode->getMember(1));
_buffer->appendText(TRI_CHAR_LENGTH_PAIR(",true)"));
}
// RETURN
_buffer->appendText(TRI_CHAR_LENGTH_PAIR(".map(function (v) { "));
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("vars[\""));
_buffer->appendText(variable->name);
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("\"]=v; "));
size_t projectionNode = 1;
if (node->getMember(4)->type != NODE_TYPE_NOP) {
projectionNode = 4;
}
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("return "));
generateCodeNode(node->getMember(projectionNode));
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("; })"));
}
/// @brief generate JavaScript code for an expansion iterator
void Executor::generateCodeExpansionIterator(AstNode const* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->numMembers() == 2);
// intentionally do not stringify node 0
generateCodeNode(node->getMember(1));
}
/// @brief generate JavaScript code for a range (i.e. 1..10)
void Executor::generateCodeRange(AstNode const* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->numMembers() == 2);
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("_AQL.AQL_RANGE("));
generateCodeNode(node->getMember(0));
_buffer->appendChar(',');
generateCodeNode(node->getMember(1));
_buffer->appendChar(')');
}
/// @brief generate JavaScript code for a named attribute access
void Executor::generateCodeNamedAccess(AstNode const* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->numMembers() == 1);
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("_AQL.DOCUMENT_MEMBER("));
generateCodeNode(node->getMember(0));
_buffer->appendChar(',');
generateCodeString(node->getStringValue(), node->getStringLength());
_buffer->appendChar(')');
}
/// @brief generate JavaScript code for a bound attribute access
void Executor::generateCodeBoundAccess(AstNode const* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->numMembers() == 2);
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("_AQL.DOCUMENT_MEMBER("));
generateCodeNode(node->getMember(0));
_buffer->appendChar(',');
generateCodeNode(node->getMember(1));
_buffer->appendChar(')');
}
/// @brief generate JavaScript code for an indexed attribute access
void Executor::generateCodeIndexedAccess(AstNode const* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->numMembers() == 2);
// indexed access
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("_AQL.GET_INDEX("));
generateCodeNode(node->getMember(0));
_buffer->appendChar(',');
generateCodeNode(node->getMember(1));
_buffer->appendChar(')');
}
/// @brief generate JavaScript code for a node
void Executor::generateCodeNode(AstNode const* node) {
TRI_ASSERT(node != nullptr);
switch (node->type) {
case NODE_TYPE_VALUE:
node->appendValue(_buffer);
break;
case NODE_TYPE_ARRAY:
generateCodeArray(node);
break;
case NODE_TYPE_OBJECT:
generateCodeObject(node);
break;
case NODE_TYPE_OPERATOR_UNARY_PLUS:
case NODE_TYPE_OPERATOR_UNARY_MINUS:
case NODE_TYPE_OPERATOR_UNARY_NOT:
generateCodeUnaryOperator(node);
break;
case NODE_TYPE_OPERATOR_BINARY_EQ:
case NODE_TYPE_OPERATOR_BINARY_NE:
case NODE_TYPE_OPERATOR_BINARY_LT:
case NODE_TYPE_OPERATOR_BINARY_LE:
case NODE_TYPE_OPERATOR_BINARY_GT:
case NODE_TYPE_OPERATOR_BINARY_GE:
case NODE_TYPE_OPERATOR_BINARY_IN:
case NODE_TYPE_OPERATOR_BINARY_NIN:
case NODE_TYPE_OPERATOR_BINARY_PLUS:
case NODE_TYPE_OPERATOR_BINARY_MINUS:
case NODE_TYPE_OPERATOR_BINARY_TIMES:
case NODE_TYPE_OPERATOR_BINARY_DIV:
case NODE_TYPE_OPERATOR_BINARY_MOD:
case NODE_TYPE_OPERATOR_BINARY_AND:
case NODE_TYPE_OPERATOR_BINARY_OR:
generateCodeBinaryOperator(node);
break;
case NODE_TYPE_OPERATOR_BINARY_ARRAY_EQ:
case NODE_TYPE_OPERATOR_BINARY_ARRAY_NE:
case NODE_TYPE_OPERATOR_BINARY_ARRAY_LT:
case NODE_TYPE_OPERATOR_BINARY_ARRAY_LE:
case NODE_TYPE_OPERATOR_BINARY_ARRAY_GT:
case NODE_TYPE_OPERATOR_BINARY_ARRAY_GE:
case NODE_TYPE_OPERATOR_BINARY_ARRAY_IN:
case NODE_TYPE_OPERATOR_BINARY_ARRAY_NIN:
generateCodeBinaryArrayOperator(node);
break;
case NODE_TYPE_OPERATOR_TERNARY:
generateCodeTernaryOperator(node);
break;
case NODE_TYPE_REFERENCE:
generateCodeReference(node);
break;
case NODE_TYPE_COLLECTION:
generateCodeCollection(node);
break;
case NODE_TYPE_FCALL:
generateCodeFunctionCall(node);
break;
case NODE_TYPE_FCALL_USER:
generateCodeUserFunctionCall(node);
break;
case NODE_TYPE_EXPANSION:
generateCodeExpansion(node);
break;
case NODE_TYPE_ITERATOR:
generateCodeExpansionIterator(node);
break;
case NODE_TYPE_RANGE:
generateCodeRange(node);
break;
case NODE_TYPE_ATTRIBUTE_ACCESS:
generateCodeNamedAccess(node);
break;
case NODE_TYPE_BOUND_ATTRIBUTE_ACCESS:
generateCodeBoundAccess(node);
break;
case NODE_TYPE_INDEXED_ACCESS:
generateCodeIndexedAccess(node);
break;
case NODE_TYPE_VARIABLE:
case NODE_TYPE_PARAMETER:
case NODE_TYPE_PASSTHRU:
case NODE_TYPE_ARRAY_LIMIT: {
// we're not expecting these types here
std::string message("unexpected node type in generateCodeNode: ");
message.append(node->getTypeString());
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_NOT_IMPLEMENTED, message);
}
default: {
std::string message("node type not implemented in generateCodeNode: ");
message.append(node->getTypeString());
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_NOT_IMPLEMENTED, message);
}
}
}
/// @brief create the string buffer
arangodb::basics::StringBuffer* Executor::initializeBuffer() {
if (_buffer == nullptr) {
_buffer = new arangodb::basics::StringBuffer(TRI_UNKNOWN_MEM_ZONE);
if (_buffer == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
_buffer->reserve(512);
} else {
_buffer->clear();
}
TRI_ASSERT(_buffer != nullptr);
return _buffer;
}