From 1015404edca645e7a9d75650a2498f12a5482639 Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Wed, 6 Aug 2014 16:19:43 +0200 Subject: [PATCH 1/3] working on AQL function calls and error handling --- arangod/Aql/Ast.cpp | 3 +- arangod/Aql/Function.h | 196 +++++++++++++++ arangod/Aql/Query.cpp | 2 +- arangod/Aql/Scopes.cpp | 2 +- arangod/Aql/V8Executor.cpp | 223 +++++++++++++++++- arangod/Aql/V8Executor.h | 10 +- arangod/Aql/V8Expression.h | 24 ++ arangod/Utils/Exception.cpp | 54 +++-- arangod/Utils/Exception.h | 16 +- .../aardvark/frontend/js/bootstrap/errors.js | 2 +- js/common/bootstrap/errors.js | 2 +- lib/BasicsC/errors.dat | 2 +- lib/BasicsC/voc-errors.c | 2 +- lib/BasicsC/voc-errors.h | 8 +- 14 files changed, 498 insertions(+), 48 deletions(-) create mode 100644 arangod/Aql/Function.h diff --git a/arangod/Aql/Ast.cpp b/arangod/Aql/Ast.cpp index dda9e808a7..9d5cd06d6f 100644 --- a/arangod/Aql/Ast.cpp +++ b/arangod/Aql/Ast.cpp @@ -1489,8 +1489,9 @@ std::string Ast::normalizeFunctionName (char const* name) { if (functionName.find(':') == std::string::npos) { // prepend default namespace for internal functions - functionName = "_AQL:" + functionName; + functionName = "_AQL::" + functionName; } + // note: user-defined functions do not need to be modified return functionName; } diff --git a/arangod/Aql/Function.h b/arangod/Aql/Function.h new file mode 100644 index 0000000000..45fcf49b95 --- /dev/null +++ b/arangod/Aql/Function.h @@ -0,0 +1,196 @@ +//////////////////////////////////////////////////////////////////////////////// +/// @brief Aql, built-in function +/// +/// @file +/// +/// DISCLAIMER +/// +/// Copyright 2014 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 +/// @author Copyright 2014, ArangoDB GmbH, Cologne, Germany +/// @author Copyright 2012-2013, triAGENS GmbH, Cologne, Germany +//////////////////////////////////////////////////////////////////////////////// + +#ifndef ARANGODB_AQL_FUNCTION_H +#define ARANGODB_AQL_FUNCTION_H 1 + +#include "Basics/Common.h" + +namespace triagens { + namespace aql { + + struct Function { + +// ----------------------------------------------------------------------------- +// --SECTION-- constructors / destructors +// ----------------------------------------------------------------------------- + + Function () = delete; + + Function (std::string const& name, + std::string const& arguments, + bool isDeterministic) + : name(name), + arguments(arguments), + isDeterministic(isDeterministic) { + + initArguments(); + } + + ~Function () { + } + +// ----------------------------------------------------------------------------- +// --SECTION-- public methods +// ----------------------------------------------------------------------------- + +//////////////////////////////////////////////////////////////////////////////// +/// @brief checks if the function has been deprecated +//////////////////////////////////////////////////////////////////////////////// + + inline bool isDeprecated () const { + // currently there are no deprecated functions + return false; + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief return the number of required arguments +//////////////////////////////////////////////////////////////////////////////// + + inline std::pair numArguments () const { + return std::make_pair(minRequiredArguments, maxRequiredArguments); + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief parse the argument list and set the minimum and maximum number of +/// arguments +//////////////////////////////////////////////////////////////////////////////// + + void initArguments () { + minRequiredArguments = maxRequiredArguments = 0; + + // setup some parsing state + bool inOptional = false; + bool foundArg = false; + + char const* p = arguments.c_str(); + while (true) { + char const c = *p++; + + switch (c) { + case '\0': + // end of argument list + if (foundArg) { + if (! inOptional) { + ++minRequiredArguments; + } + ++maxRequiredArguments; + } + return; + + case '|': + // beginning of optional arguments + TRI_ASSERT(! inOptional); + if (foundArg) { + ++minRequiredArguments; + ++maxRequiredArguments; + } + inOptional = true; + foundArg = false; + break; + + case ',': + // next argument + TRI_ASSERT(foundArg); + + if (! inOptional) { + ++minRequiredArguments; + } + ++maxRequiredArguments; + foundArg = false; + break; + + case '+': + // repeated optional argument + TRI_ASSERT(inOptional); + maxRequiredArguments = MaxArguments; + return; + + default: + foundArg = true; + } + } + } + +// ----------------------------------------------------------------------------- +// --SECTION-- public variables +// ----------------------------------------------------------------------------- + +//////////////////////////////////////////////////////////////////////////////// +/// @brief function name +//////////////////////////////////////////////////////////////////////////////// + + std::string const name; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief function arguments +//////////////////////////////////////////////////////////////////////////////// + + std::string const arguments; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief whether or not the function is deterministic (i.e. its results are +/// identical when called repeatedly with the same input values) +//////////////////////////////////////////////////////////////////////////////// + + bool const isDeterministic; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief minimum number of required arguments +//////////////////////////////////////////////////////////////////////////////// + + size_t minRequiredArguments; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief maximum number of required arguments +//////////////////////////////////////////////////////////////////////////////// + + size_t maxRequiredArguments; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief maximum number of function arguments that can be used +//////////////////////////////////////////////////////////////////////////////// + + static size_t const MaxArguments = 1024; + + }; + + } +} + +#endif + +// ----------------------------------------------------------------------------- +// --SECTION-- END-OF-FILE +// ----------------------------------------------------------------------------- + +// Local Variables: +// mode: outline-minor +// outline-regexp: "/// @brief\\|/// {@inheritDoc}\\|/// @page\\|// --SECTION--\\|/// @\\}" +// End: diff --git a/arangod/Aql/Query.cpp b/arangod/Aql/Query.cpp index 9a62ff39d2..0bd83ea289 100644 --- a/arangod/Aql/Query.cpp +++ b/arangod/Aql/Query.cpp @@ -163,7 +163,7 @@ void Query::registerError (int code, THROW_ARANGO_EXCEPTION(code); } else { - THROW_ARANGO_EXCEPTION_STRING(code, details); + THROW_ARANGO_EXCEPTION_PARAMS(code, details); } } diff --git a/arangod/Aql/Scopes.cpp b/arangod/Aql/Scopes.cpp index c75f71eed8..34277c760e 100644 --- a/arangod/Aql/Scopes.cpp +++ b/arangod/Aql/Scopes.cpp @@ -228,7 +228,7 @@ void Scopes::addVariable (Variable* variable) { if (scope->existsVariable(variable->name)) { // duplicate variable name - THROW_ARANGO_EXCEPTION_STRING(TRI_ERROR_QUERY_VARIABLE_REDECLARED, variable->name); + THROW_ARANGO_EXCEPTION_PARAMS(TRI_ERROR_QUERY_VARIABLE_REDECLARED, variable->name.c_str()); } } diff --git a/arangod/Aql/V8Executor.cpp b/arangod/Aql/V8Executor.cpp index 46bd57b4e5..98eeff3c79 100644 --- a/arangod/Aql/V8Executor.cpp +++ b/arangod/Aql/V8Executor.cpp @@ -39,10 +39,14 @@ using namespace triagens::aql; // ----------------------------------------------------------------------------- -// --SECTION-- function names used in execution +// --SECTION-- static initialization // ----------------------------------------------------------------------------- -std::unordered_map const V8Executor::FunctionNames{ +//////////////////////////////////////////////////////////////////////////////// +/// @brief internal functions used in execution +//////////////////////////////////////////////////////////////////////////////// + +std::unordered_map const V8Executor::InternalFunctionNames{ { static_cast(NODE_TYPE_OPERATOR_UNARY_PLUS), "UNARY_PLUS" }, { static_cast(NODE_TYPE_OPERATOR_UNARY_MINUS), "UNARY_MINUS" }, { static_cast(NODE_TYPE_OPERATOR_UNARY_NOT), "LOGICAL_NOT" }, @@ -63,6 +67,156 @@ std::unordered_map const V8Executor::FunctionNames{ { static_cast(NODE_TYPE_OPERATOR_TERNARY), "TERNARY_OPERATOR_FN" } }; +//////////////////////////////////////////////////////////////////////////////// +/// @brief user-accessible functions +//////////////////////////////////////////////////////////////////////////////// + +std::unordered_map const V8Executor::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 = list + // a = (hash) array/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", ".", true) }, + { "IS_BOOL", Function("IS_BOOL", ".", true) }, + { "IS_NUMBER", Function("IS_NUMBER", ".", true) }, + { "IS_STRING", Function("IS_STRING", ".", true) }, + { "IS_LIST", Function("IS_LIST", ".", true) }, + { "IS_DOCUMENT", Function("IS_DOCUMENT", ".", true) }, + + // type cast functions + { "TO_NUMBER", Function("CAST_NUMBER", ".", true) }, + { "TO_STRING", Function("CAST_STRING", ".", true) }, + { "TO_BOOL", Function("CAST_BOOL", ".", true) }, + { "TO_LIST", Function("CAST_LIST", ".", true) }, + + // string functions + { "CONCAT", Function("STRING_CONCAT", "sz,sz|+", true) }, + { "CONCAT_SEPARATOR", Function("STRING_CONCAT_SEPARATOR", "s,sz,sz|+", true) }, + { "CHAR_LENGTH", Function("CHAR_LENGTH", "s", true) }, + { "LOWER", Function("STRING_LOWER", "s", true) }, + { "UPPER", Function("STRING_UPPER", "s", true) }, + { "SUBSTRING", Function("STRING_SUBSTRING", "s,n|n", true) }, + { "CONTAINS", Function("STRING_CONTAINS", "s,s|b", true) }, + { "LIKE", Function("STRING_LIKE", "s,r|b", true) }, + { "LEFT", Function("STRING_LEFT", "s,n", true) }, + { "RIGHT", Function("STRING_RIGHT", "s,n", true) }, + { "TRIM", Function("STRING_TRIM", "s|n", true) }, + + // numeric functions + { "FLOOR", Function("NUMBER_FLOOR", "n", true) }, + { "CEIL", Function("NUMBER_CEIL", "n", true) }, + { "ROUND", Function("NUMBER_ROUND", "n", true) }, + { "ABS", Function("NUMBER_ABS", "n", true) }, + { "RAND", Function("NUMBER_RAND", "", false) }, + { "SQRT", Function("NUMBER_SQRT", "n", true) }, + + // list functions + { "RANGE", Function("RANGE", "n,n|n", true) }, + { "UNION", Function("UNION", "l,l|+",true) }, + { "UNION_DISTINCT", Function("UNION_DISTINCT", "l,l|+", true) }, + { "MINUS", Function("MINUS", "l,l|+", true) }, + { "INTERSECTION", Function("INTERSECTION", "l,l|+", true) }, + { "FLATTEN", Function("FLATTEN", "l|n", true) }, + { "LENGTH", Function("LENGTH", "las", true) }, + { "MIN", Function("MIN", "l", true) }, + { "MAX", Function("MAX", "l", true) }, + { "SUM", Function("SUM", "l", true) }, + { "MEDIAN", Function("MEDIAN", "l", true) }, + { "AVERAGE", Function("AVERAGE", "l", true) }, + { "VARIANCE_SAMPLE", Function("VARIANCE_SAMPLE", "l", true) }, + { "VARIANCE_POPULATION", Function("VARIANCE_POPULATION", "l", true) }, + { "STDDEV_SAMPLE", Function("STDDEV_SAMPLE", "l", true) }, + { "STDDEV_POPULATION", Function("STDDEV_POPULATION", "l", true) }, + { "UNIQUE", Function("UNIQUE", "l", true) }, + { "SLICE", Function("SLICE", "l,n|n", true) }, + { "REVERSE", Function("REVERSE", "ls", true) }, // note: REVERSE() can be applied on strings, too + { "FIRST", Function("FIRST", "l", true) }, + { "LAST", Function("LAST", "l", true) }, + { "NTH", Function("NTH", "l,n", true) }, + { "POSITION", Function("POSITION", "l,.|b", true) }, + + // document functions + { "HAS", Function("HAS", "az,s", true) }, + { "ATTRIBUTES", Function("ATTRIBUTES", "a|b,b", true) }, + { "MERGE", Function("MERGE", "a,a|+", true) }, + { "MERGE_RECURSIVE", Function("MERGE_RECURSIVE", "a,a|+", true) }, + { "DOCUMENT", Function("DOCUMENT", "h.|.", true) }, + { "MATCHES", Function("MATCHES", ".,l|b", true) }, + { "UNSET", Function("UNSET", "a,sl|+", true) }, + { "KEEP", Function("KEEP", "a,sl|+", true) }, + { "TRANSLATE", Function("TRANSLATE", ".,a|.", true) }, + + // geo functions + { "NEAR", Function("GEO_NEAR", "h,n,n|nz,s", false) }, + { "WITHIN", Function("GEO_WITHIN", "h,n,n,n|s", false) }, + + // fulltext functions + { "FULLTEXT", Function("FULLTEXT", "h,s,s", false) }, + + // graph functions + { "PATHS", Function("GRAPH_PATHS", "c,h|s,b", false )}, + { "GRAPH_PATHS", Function("GENERAL_GRAPH_PATHS", "s|a", false )}, + { "SHORTEST_PATH", Function("GRAPH_SHORTEST_PATH", "h,h,s,s,s|a", false )}, + { "GRAPH_SHORTEST_PATH", Function("GENERAL_GRAPH_SHORTEST_PATH", "s,als,als|a", false )}, + { "GRAPH_DISTANCE_TO", Function("GENERAL_GRAPH_DISTANCE_TO", "s,als,als|a", false )}, + { "TRAVERSAL", Function("GRAPH_TRAVERSAL", "h,h,s,s|a", false )}, + { "GRAPH_TRAVERSAL", Function("GENERAL_GRAPH_TRAVERSAL", "s,als,s|a", false )}, + { "TRAVERSAL_TREE", Function("GRAPH_TRAVERSAL_TREE", "s,als,s,s|a", false )}, + { "GRAPH_TRAVERSAL_TREE", Function("GENERAL_GRAPH_TRAVERSAL_TREE", "s,als,s,s|a", false )}, + { "EDGES", Function("GRAPH_EDGES", "h,s,s|l", false )}, + { "GRAPH_EDGES", Function("GENERAL_GRAPH_EDGES", "s,als|a", false )}, + { "GRAPH_VERTICES", Function("GENERAL_GRAPH_VERTICES", "s,als|a", false )}, + { "NEIGHBORS", Function("GRAPH_NEIGHBORS", "h,h,s,s|l", false )}, + { "GRAPH_NEIGHBORS", Function("GENERAL_GRAPH_NEIGHBORS", "s,als|a", false )}, + { "GRAPH_COMMON_NEIGHBORS", Function("GENERAL_GRAPH_COMMON_NEIGHBORS", "s,als,als|a,a", false )}, + { "GRAPH_COMMON_PROPERTIES", Function("GENERAL_GRAPH_COMMON_PROPERTIES", "s,als,als|a", false )}, + { "GRAPH_ECCENTRICITY", Function("GENERAL_GRAPH_ECCENTRICITY", "s|a", false )}, + { "GRAPH_BETWEENNESS", Function("GENERAL_GRAPH_BETWEENNESS", "s|a", false )}, + { "GRAPH_CLOSENESS", Function("GENERAL_GRAPH_CLOSENESS", "s|a", false )}, + { "GRAPH_ABSOLUTE_ECCENTRICITY", Function("GENERAL_GRAPH_ABSOLUTE_ECCENTRICITY", "s,als|a", false )}, + { "GRAPH_ABSOLUTE_CLOSENESS", Function("GENERAL_GRAPH_ABSOLUTE_CLOSENESS", "s,als|a", false )}, + { "GRAPH_DIAMETER", Function("GENERAL_GRAPH_DIAMETER", "s|a", false )}, + { "GRAPH_RADIUS", Function("GENERAL_GRAPH_RADIUS", "s|a", false )}, + + // date functions + { "DATE_NOW", Function("DATE_NOW", "", false) }, + { "DATE_TIMESTAMP", Function("DATE_TIMESTAMP", "ns|ns,ns,ns,ns,ns,ns", true) }, + { "DATE_ISO8601", Function("DATE_ISO8601", "ns|ns,ns,ns,ns,ns,ns", true) }, + { "DATE_DAYOFWEEK", Function("DATE_DAYOFWEEK", "ns", true) }, + { "DATE_YEAR", Function("DATE_YEAR", "ns", true) }, + { "DATE_MONTH", Function("DATE_MONTH", "ns", true) }, + { "DATE_DAY", Function("DATE_DAY", "ns", true) }, + { "DATE_HOUR", Function("DATE_HOUR", "ns", true) }, + { "DATE_MINUTE", Function("DATE_MINUTE", "ns", true) }, + { "DATE_SECOND", Function("DATE_SECOND", "ns", true) }, + { "DATE_MILLISECOND", Function("DATE_MILLISECOND", "ns", true) }, + + // misc functions + { "FAIL", Function("FAIL", "|s", false) }, + { "PASSTHRU", Function("PASSTHRU", ".", false) }, + { "SLEEP", Function("SLEEP", "n", false) }, + { "COLLECTIONS", Function("COLLECTIONS", "", false) }, + { "NOT_NULL", Function("NOT_NULL", ".|+", true) }, + { "FIRST_LIST", Function("FIRST_LIST", ".|+", true) }, + { "FIRST_DOCUMENT", Function("FIRST_DOCUMENT", ".|+", true) }, + { "PARSE_IDENTIFIER", Function("PARSE_IDENTIFIER", ".", true) }, + { "SKIPLIST", Function("SKIPLIST_QUERY", "h,a|n,n", false) }, + { "CURRENT_USER", Function("CURRENT_USER", "", false) }, + { "CURRENT_DATABASE", Function("CURRENT_DATABASE", "", false) } +}; + // ----------------------------------------------------------------------------- // --SECTION-- constructors / destructors // ----------------------------------------------------------------------------- @@ -97,8 +251,6 @@ V8Executor::~V8Executor () { V8Expression* V8Executor::generateExpression (AstNode const* node) { generateCodeExpression(node); - std::cout << "CREATED CODE: " << _buffer->c_str() << "\n"; - v8::Handle compiled = v8::Script::Compile(v8::String::New(_buffer->c_str(), (int) _buffer->length()), v8::String::New("--script--")); @@ -111,6 +263,23 @@ V8Expression* V8Executor::generateExpression (AstNode const* node) { if (tryCatch.HasCaught()) { if (tryCatch.CanContinue()) { + if (tryCatch.Exception()->IsObject()) { + // cast the exception into an object + v8::Handle objValue = v8::Handle::Cast(tryCatch.Exception()); + + v8::Handle errorName = v8::String::New("errorName"); + + if (objValue->HasOwnProperty(errorName)) { + v8::Handle errorNum = objValue->Get(errorName); + + if (errorNum->IsNumber()) { + int errorCode = static_cast(TRI_ObjectToInt64(errorNum)); + // TODO: handle errors + } + + } + } + THROW_ARANGO_EXCEPTION(TRI_ERROR_INTERNAL); } else { @@ -122,6 +291,7 @@ V8Expression* V8Executor::generateExpression (AstNode const* node) { } if (val.IsEmpty()) { + // out of memory THROW_ARANGO_EXCEPTION(TRI_ERROR_INTERNAL); } @@ -214,9 +384,9 @@ void V8Executor::generateCodeUnaryOperator (AstNode const* node) { TRI_ASSERT(node != nullptr); TRI_ASSERT(node->numMembers() == 1); - auto it = FunctionNames.find(static_cast(node->type)); + auto it = InternalFunctionNames.find(static_cast(node->type)); - if (it == FunctionNames.end()) { + if (it == InternalFunctionNames.end()) { // no function found for the type of node THROW_ARANGO_EXCEPTION(TRI_ERROR_INTERNAL); } @@ -237,9 +407,9 @@ void V8Executor::generateCodeBinaryOperator (AstNode const* node) { TRI_ASSERT(node != nullptr); TRI_ASSERT(node->numMembers() == 2); - auto it = FunctionNames.find(static_cast(node->type)); + auto it = InternalFunctionNames.find(static_cast(node->type)); - if (it == FunctionNames.end()) { + if (it == InternalFunctionNames.end()) { // no function found for the type of node THROW_ARANGO_EXCEPTION(TRI_ERROR_INTERNAL); } @@ -275,9 +445,9 @@ void V8Executor::generateCodeTernaryOperator (AstNode const* node) { TRI_ASSERT(node != nullptr); TRI_ASSERT(node->numMembers() == 3); - auto it = FunctionNames.find(static_cast(node->type)); + auto it = InternalFunctionNames.find(static_cast(node->type)); - if (it == FunctionNames.end()) { + if (it == InternalFunctionNames.end()) { // no function found for the type of node THROW_ARANGO_EXCEPTION(TRI_ERROR_INTERNAL); } @@ -349,15 +519,41 @@ void V8Executor::generateCodeFunctionCall (AstNode const* node) { char const* name = node->getStringValue(); - _buffer->appendText("aql."); - _buffer->appendText(name); - _buffer->appendText("("); + TRI_ASSERT(name != nullptr); + TRI_ASSERT(strncmp(name, "_AQL::", 6) == 0); + + // pointer to the function name as specified in the query + char const* userName = name + sizeof("_AQL::") - 1; + + auto it = FunctionNames.find(std::string(userName)); + + if (it == FunctionNames.end()) { + // unknown function name + THROW_ARANGO_EXCEPTION_PARAMS(TRI_ERROR_QUERY_FUNCTION_NAME_UNKNOWN, userName); + } + + Function const& func = (*it).second; auto args = node->getMember(0); TRI_ASSERT(args != nullptr); TRI_ASSERT(args->type == NODE_TYPE_LIST); size_t const n = args->numMembers(); + + // check number of arguments + auto numExpectedArguments = func.numArguments(); + + if (n < numExpectedArguments.first || n > numExpectedArguments.second) { + THROW_ARANGO_EXCEPTION_PARAMS(TRI_ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH, + userName, + static_cast(numExpectedArguments.first), + static_cast(numExpectedArguments.second)); + } + + _buffer->appendText("aql."); + _buffer->appendText(func.name); + _buffer->appendText("("); + for (size_t i = 0; i < n; ++i) { if (i > 0) { _buffer->appendText(", "); @@ -560,6 +756,7 @@ TRI_json_t* V8Executor::execute () { // it will fail badly TRI_ASSERT(_buffer != nullptr); + v8::Isolate* isolate = v8::Isolate::GetCurrent(); v8::Handle compiled = v8::Script::Compile(v8::String::New(_buffer->c_str(), (int) _buffer->length()), diff --git a/arangod/Aql/V8Executor.h b/arangod/Aql/V8Executor.h index 03e80ee1e0..080fbdc82d 100644 --- a/arangod/Aql/V8Executor.h +++ b/arangod/Aql/V8Executor.h @@ -31,6 +31,7 @@ #define ARANGODB_AQL_V8_EXECUTOR_H 1 #include "Basics/Common.h" +#include "Aql/Function.h" struct TRI_json_s; @@ -213,11 +214,16 @@ namespace triagens { triagens::basics::StringBuffer* _buffer; //////////////////////////////////////////////////////////////////////////////// -/// @brief AQL function names +/// @brief AQL internal function names //////////////////////////////////////////////////////////////////////////////// - static std::unordered_map const FunctionNames; + static std::unordered_map const InternalFunctionNames; +//////////////////////////////////////////////////////////////////////////////// +/// @brief AQL user-callable function names +//////////////////////////////////////////////////////////////////////////////// + + static std::unordered_map const FunctionNames; }; diff --git a/arangod/Aql/V8Expression.h b/arangod/Aql/V8Expression.h index 0e3d95f9ea..a42f281b55 100644 --- a/arangod/Aql/V8Expression.h +++ b/arangod/Aql/V8Expression.h @@ -90,9 +90,33 @@ namespace triagens { values->Set(v8::String::New(varname.c_str(), (int) varname.size()), argv[startPos + reg].toV8(trx, docColls[reg])); } + // set function arguments v8::Handle args[] = { values }; + + // execute the function + v8::TryCatch tryCatch; v8::Handle result = func->Call(func, 1, args); + if (tryCatch.HasCaught()) { + if (tryCatch.CanContinue()) { + if (tryCatch.Exception()->IsObject()) { + // cast the exception into an object + v8::Handle objValue = v8::Handle::Cast(tryCatch.Exception()); + v8::Handle errorNum = v8::String::New("errorNum"); + + if (objValue->HasOwnProperty(errorNum)) { + v8::Handle errorNumValue = objValue->Get(errorNum); + + if (errorNumValue->IsNumber()) { + int errorCode = static_cast(TRI_ObjectToInt64(errorNumValue)); + THROW_ARANGO_EXCEPTION(errorCode); + } + } + } + } + // TODO: handle case when we can NOT continue + } + if (result.IsEmpty()) { THROW_ARANGO_EXCEPTION(TRI_ERROR_INTERNAL); } diff --git a/arangod/Utils/Exception.cpp b/arangod/Utils/Exception.cpp index 583b536f7c..55af9c900e 100644 --- a/arangod/Utils/Exception.cpp +++ b/arangod/Utils/Exception.cpp @@ -34,14 +34,28 @@ using namespace std; using namespace triagens::arango; //////////////////////////////////////////////////////////////////////////////// -/// @brief constructor +/// @brief constructor, without format string //////////////////////////////////////////////////////////////////////////////// Exception::Exception (int code, - string const& details, char const* file, int line) - : _details(details), + : _errorMessage(TRI_errno_string(code)), + _file(file), + _line(line), + _code(code) { +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief constructor, for creating an exception with an already created +/// error message (normally based on error templates containing %s, %d etc.) +//////////////////////////////////////////////////////////////////////////////// + +Exception::Exception (int code, + string const& errorMessage, + char const* file, + int line) + : _errorMessage(errorMessage), _file(file), _line(line), _code(code) { @@ -74,20 +88,7 @@ char const* Exception::what () const throw () { //////////////////////////////////////////////////////////////////////////////// string Exception::message () const throw () { - string message(TRI_errno_string(_code)); - - if (strstr(message.c_str(), "%s") != nullptr) { - // error message contains "%s" wildcard - try { - // replace %s with actual details - return basics::StringUtils::replace(message, "%s", _details); - } - catch (...) { - return "internal error"; - } - } - - return message; + return _errorMessage; } //////////////////////////////////////////////////////////////////////////////// @@ -98,6 +99,25 @@ int Exception::code () const throw () { return _code; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief construct an error message from a template string +//////////////////////////////////////////////////////////////////////////////// + +std::string Exception::FillExceptionString (int code, + ...) { + char const* format = TRI_errno_string(code); + TRI_ASSERT(format != nullptr); + + char buffer[1024]; + va_list ap; + va_start(ap, code); + vsnprintf(buffer, sizeof(buffer) - 1, format, ap); + va_end(ap); + buffer[sizeof(buffer) - 1] = '\0'; // Windows + + return std::string(buffer); +} + // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE // ----------------------------------------------------------------------------- diff --git a/arangod/Utils/Exception.h b/arangod/Utils/Exception.h index 99c455ef5f..04f940c678 100644 --- a/arangod/Utils/Exception.h +++ b/arangod/Utils/Exception.h @@ -44,14 +44,14 @@ //////////////////////////////////////////////////////////////////////////////// #define THROW_ARANGO_EXCEPTION(code) \ - throw triagens::arango::Exception(code, "", __FILE__, __LINE__) + throw triagens::arango::Exception(code, __FILE__, __LINE__) //////////////////////////////////////////////////////////////////////////////// /// @brief throws an exception for internal errors //////////////////////////////////////////////////////////////////////////////// -#define THROW_ARANGO_EXCEPTION_STRING(code, details) \ - throw triagens::arango::Exception(code, details, __FILE__, __LINE__) +#define THROW_ARANGO_EXCEPTION_PARAMS(code, ...) \ + throw triagens::arango::Exception(code, triagens::arango::Exception::FillExceptionString(code, __VA_ARGS__), __FILE__, __LINE__) // ----------------------------------------------------------------------------- // --SECTION-- public types @@ -67,7 +67,11 @@ namespace triagens { class Exception : public virtual std::exception { public: Exception (int code, - std::string const& details, + char const* file, + int line); + + Exception (int code, + std::string const& errorMessage, char const* file, int line); @@ -80,8 +84,10 @@ namespace triagens { int code () const throw(); + static std::string FillExceptionString (int, ...); + protected: - std::string const _details; + std::string const _errorMessage; char const* _file; int const _line; int const _code; diff --git a/js/apps/system/aardvark/frontend/js/bootstrap/errors.js b/js/apps/system/aardvark/frontend/js/bootstrap/errors.js index 176eede185..cd9f437075 100644 --- a/js/apps/system/aardvark/frontend/js/bootstrap/errors.js +++ b/js/apps/system/aardvark/frontend/js/bootstrap/errors.js @@ -160,7 +160,7 @@ "ERROR_QUERY_TOO_MANY_COLLECTIONS" : { "code" : 1522, "message" : "too many collections" }, "ERROR_QUERY_DOCUMENT_ATTRIBUTE_REDECLARED" : { "code" : 1530, "message" : "document attribute '%s' is assigned multiple times" }, "ERROR_QUERY_FUNCTION_NAME_UNKNOWN" : { "code" : 1540, "message" : "usage of unknown function '%s()'" }, - "ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH" : { "code" : 1541, "message" : "invalid number of arguments for function '%s()'" }, + "ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH" : { "code" : 1541, "message" : "invalid number of arguments for function '%s()', expected number of arguments: minimum: %d, maximum: %d" }, "ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH" : { "code" : 1542, "message" : "invalid argument type used in call to function '%s()'" }, "ERROR_QUERY_INVALID_REGEX" : { "code" : 1543, "message" : "invalid regex argument value used in call to function '%s()'" }, "ERROR_QUERY_BIND_PARAMETERS_INVALID" : { "code" : 1550, "message" : "invalid structure of bind parameters" }, diff --git a/js/common/bootstrap/errors.js b/js/common/bootstrap/errors.js index 176eede185..cd9f437075 100644 --- a/js/common/bootstrap/errors.js +++ b/js/common/bootstrap/errors.js @@ -160,7 +160,7 @@ "ERROR_QUERY_TOO_MANY_COLLECTIONS" : { "code" : 1522, "message" : "too many collections" }, "ERROR_QUERY_DOCUMENT_ATTRIBUTE_REDECLARED" : { "code" : 1530, "message" : "document attribute '%s' is assigned multiple times" }, "ERROR_QUERY_FUNCTION_NAME_UNKNOWN" : { "code" : 1540, "message" : "usage of unknown function '%s()'" }, - "ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH" : { "code" : 1541, "message" : "invalid number of arguments for function '%s()'" }, + "ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH" : { "code" : 1541, "message" : "invalid number of arguments for function '%s()', expected number of arguments: minimum: %d, maximum: %d" }, "ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH" : { "code" : 1542, "message" : "invalid argument type used in call to function '%s()'" }, "ERROR_QUERY_INVALID_REGEX" : { "code" : 1543, "message" : "invalid regex argument value used in call to function '%s()'" }, "ERROR_QUERY_BIND_PARAMETERS_INVALID" : { "code" : 1550, "message" : "invalid structure of bind parameters" }, diff --git a/lib/BasicsC/errors.dat b/lib/BasicsC/errors.dat index 45156d3375..521c8d4477 100755 --- a/lib/BasicsC/errors.dat +++ b/lib/BasicsC/errors.dat @@ -196,7 +196,7 @@ ERROR_QUERY_COLLECTION_LOCK_FAILED,1521,"unable to read-lock collection %s","Wil ERROR_QUERY_TOO_MANY_COLLECTIONS,1522,"too many collections","Will be raised when the number of collections in a query is beyond the allowed value." ERROR_QUERY_DOCUMENT_ATTRIBUTE_REDECLARED,1530,"document attribute '%s' is assigned multiple times","Will be raised when a document attribute is re-assigned." ERROR_QUERY_FUNCTION_NAME_UNKNOWN,1540,"usage of unknown function '%s()'","Will be raised when an undefined function is called." -ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH,1541,"invalid number of arguments for function '%s()'","Will be raised when the number of arguments used in a function call does not match the expected number of arguments for the function." +ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH,1541,"invalid number of arguments for function '%s()', expected number of arguments: minimum: %d, maximum: %d","Will be raised when the number of arguments used in a function call does not match the expected number of arguments for the function." ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH,1542,"invalid argument type used in call to function '%s()'","Will be raised when the type of an argument used in a function call does not match the expected argument type." ERROR_QUERY_INVALID_REGEX,1543,"invalid regex argument value used in call to function '%s()'","Will be raised when an invalid regex argument value is used in a call to a function that expects a regex." ERROR_QUERY_BIND_PARAMETERS_INVALID,1550,"invalid structure of bind parameters","Will be raised when the structure of bind parameters passed has an unexpected format." diff --git a/lib/BasicsC/voc-errors.c b/lib/BasicsC/voc-errors.c index 49339d5fc5..395445f06a 100644 --- a/lib/BasicsC/voc-errors.c +++ b/lib/BasicsC/voc-errors.c @@ -156,7 +156,7 @@ void TRI_InitialiseErrorMessages (void) { REG_ERROR(ERROR_QUERY_TOO_MANY_COLLECTIONS, "too many collections"); REG_ERROR(ERROR_QUERY_DOCUMENT_ATTRIBUTE_REDECLARED, "document attribute '%s' is assigned multiple times"); REG_ERROR(ERROR_QUERY_FUNCTION_NAME_UNKNOWN, "usage of unknown function '%s()'"); - REG_ERROR(ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH, "invalid number of arguments for function '%s()'"); + REG_ERROR(ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH, "invalid number of arguments for function '%s()', expected number of arguments: minimum: %d, maximum: %d"); REG_ERROR(ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, "invalid argument type used in call to function '%s()'"); REG_ERROR(ERROR_QUERY_INVALID_REGEX, "invalid regex argument value used in call to function '%s()'"); REG_ERROR(ERROR_QUERY_BIND_PARAMETERS_INVALID, "invalid structure of bind parameters"); diff --git a/lib/BasicsC/voc-errors.h b/lib/BasicsC/voc-errors.h index 8dc360a0b8..5240600849 100644 --- a/lib/BasicsC/voc-errors.h +++ b/lib/BasicsC/voc-errors.h @@ -8,7 +8,7 @@ extern "C" { //////////////////////////////////////////////////////////////////////////////// /// @page ArangoErrors Error codes and meanings -/// @startDocuBlock errorCodes +/// /// The following errors might be raised when running ArangoDB: /// /// - 0: @LIT{no error} @@ -371,7 +371,7 @@ extern "C" { /// Will be raised when a document attribute is re-assigned. /// - 1540: @LIT{usage of unknown function '\%s()'} /// Will be raised when an undefined function is called. -/// - 1541: @LIT{invalid number of arguments for function '\%s()'} +/// - 1541: @LIT{invalid number of arguments for function '\%s()', expected number of arguments: minimum: \%d, maximum: \%d} /// Will be raised when the number of arguments used in a function call does /// not match the expected number of arguments for the function. /// - 1542: @LIT{invalid argument type used in call to function '\%s()'} @@ -586,7 +586,6 @@ extern "C" { /// Will be returned if the element was not found in the structure. /// - 20000: @LIT{newest version of app already installed} /// newest version of app already installed -/// @endDocuBlock //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// @@ -2122,7 +2121,8 @@ void TRI_InitialiseErrorMessages (void); //////////////////////////////////////////////////////////////////////////////// /// @brief 1541: ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH /// -/// invalid number of arguments for function '%s()' +/// invalid number of arguments for function '%s()', expected number of +/// arguments: minimum: %d, maximum: %d /// /// Will be raised when the number of arguments used in a function call does /// not match the expected number of arguments for the function. From e8f69490ff5ecfd0048d07ab741c85ff506e5845 Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Wed, 6 Aug 2014 17:15:36 +0200 Subject: [PATCH 2/3] optimize away function calls with const arguments --- arangod/Aql/Ast.cpp | 77 +++++++++++++++++++++++++++++++------- arangod/Aql/Ast.h | 12 ++++-- arangod/Aql/AstNode.cpp | 7 +++- arangod/Aql/AstNode.h | 8 ++++ arangod/Aql/V8Executor.cpp | 51 +++++++++++-------------- arangod/Aql/V8Executor.h | 6 +++ 6 files changed, 115 insertions(+), 46 deletions(-) diff --git a/arangod/Aql/Ast.cpp b/arangod/Aql/Ast.cpp index 9d5cd06d6f..bf23892942 100644 --- a/arangod/Aql/Ast.cpp +++ b/arangod/Aql/Ast.cpp @@ -644,27 +644,48 @@ AstNode* Ast::createNodeArrayElement (char const* attributeName, //////////////////////////////////////////////////////////////////////////////// AstNode* Ast::createNodeFunctionCall (char const* functionName, - AstNode const* parameters) { + AstNode const* arguments) { if (functionName == nullptr) { THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); } - std::string const normalizedName = normalizeFunctionName(functionName); - char* fname = _query->registerString(normalizedName.c_str(), normalizedName.size(), false); + auto normalized = normalizeFunctionName(functionName); AstNode* node; - if (normalizedName[0] == '_') { + if (normalized.second) { // built-in function node = createNode(NODE_TYPE_FCALL); + // register a pointer to the function + auto func = _query->executor()->getFunctionByName(normalized.first); + + TRI_ASSERT(func != nullptr); + node->setData(static_cast(func)); + + TRI_ASSERT(arguments != nullptr); + TRI_ASSERT(arguments->type == NODE_TYPE_LIST); + + // validate number of function call arguments + size_t const n = arguments->numMembers(); + + auto numExpectedArguments = func->numArguments(); + if (n < numExpectedArguments.first || n > numExpectedArguments.second) { + THROW_ARANGO_EXCEPTION_PARAMS(TRI_ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH, + functionName, + static_cast(numExpectedArguments.first), + static_cast(numExpectedArguments.second)); + } + } else { // user-defined function node = createNode(NODE_TYPE_FCALL_USER); + // register the function name + char* fname = _query->registerString(normalized.first.c_str(), normalized.first.size(), false); + node->setStringValue(fname); } - node->setStringValue(fname); - node->addMember(parameters); + node->addMember(arguments); return node; } @@ -809,6 +830,11 @@ void Ast::optimize () { if (node->type == NODE_TYPE_OPERATOR_TERNARY) { return optimizeTernaryOperator(node); } + + // call to built-in function + if (node->type == NODE_TYPE_FCALL) { + return optimizeFunctionCall(node); + } // reference to a variable if (node->type == NODE_TYPE_REFERENCE) { @@ -875,10 +901,10 @@ std::unordered_set Ast::getReferencedVariables (AstNode const* node) // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// -/// @brief executes a comparison function +/// @brief executes an expression with constant parameters //////////////////////////////////////////////////////////////////////////////// -AstNode* Ast::executeConstComparison (AstNode const* node) { +AstNode* Ast::executeConstExpression (AstNode const* node) { TRI_json_t* result = _query->executor()->executeExpression(node); if (result == nullptr) { @@ -1105,7 +1131,7 @@ AstNode* Ast::optimizeBinaryOperatorRelational (AstNode* node) { return node; } - return executeConstComparison(node); + return executeConstExpression(node); } //////////////////////////////////////////////////////////////////////////////// @@ -1229,6 +1255,31 @@ AstNode* Ast::optimizeTernaryOperator (AstNode* node) { return falsePart; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief optimizes a call to a built-in function +//////////////////////////////////////////////////////////////////////////////// + +AstNode* Ast::optimizeFunctionCall (AstNode* node) { + TRI_ASSERT(node != nullptr); + TRI_ASSERT(node->type == NODE_TYPE_FCALL); + TRI_ASSERT(node->numMembers() == 1); + + auto func = static_cast(node->getData()); + TRI_ASSERT(func != nullptr); + + if (! func->isDeterministic) { + // non-deterministic function + return node; + } + + if (! node->getMember(0)->isConstant()) { + // arguments to function call are not constant + return node; + } + + return executeConstExpression(node); +} + //////////////////////////////////////////////////////////////////////////////// /// @brief optimizes a reference to a variable /// references are replaced with constants if possible @@ -1474,7 +1525,7 @@ void Ast::traverse (AstNode const* node, /// @brief normalize a function name //////////////////////////////////////////////////////////////////////////////// -std::string Ast::normalizeFunctionName (char const* name) { +std::pair Ast::normalizeFunctionName (char const* name) { TRI_ASSERT(name != nullptr); char* upperName = TRI_UpperAsciiStringZ(TRI_UNKNOWN_MEM_ZONE, name); @@ -1489,11 +1540,11 @@ std::string Ast::normalizeFunctionName (char const* name) { if (functionName.find(':') == std::string::npos) { // prepend default namespace for internal functions - functionName = "_AQL::" + functionName; + return std::make_pair(functionName, true); } - // note: user-defined functions do not need to be modified - return functionName; + // user-defined function + return std::make_pair(functionName, false); } //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/Ast.h b/arangod/Aql/Ast.h index cce6ac0fc1..64f219b13b 100644 --- a/arangod/Aql/Ast.h +++ b/arangod/Aql/Ast.h @@ -499,10 +499,10 @@ namespace triagens { private: //////////////////////////////////////////////////////////////////////////////// -/// @brief executes a comparison function +/// @brief executes an expression with constant parameters //////////////////////////////////////////////////////////////////////////////// - AstNode* executeConstComparison (AstNode const*); + AstNode* executeConstExpression (AstNode const*); //////////////////////////////////////////////////////////////////////////////// /// @brief optimizes a FILTER node @@ -548,6 +548,12 @@ namespace triagens { AstNode* optimizeTernaryOperator (AstNode*); +//////////////////////////////////////////////////////////////////////////////// +/// @brief optimizes a call to a built-in function +//////////////////////////////////////////////////////////////////////////////// + + AstNode* optimizeFunctionCall (AstNode*); + //////////////////////////////////////////////////////////////////////////////// /// @brief optimizes a reference to a variable //////////////////////////////////////////////////////////////////////////////// @@ -599,7 +605,7 @@ namespace triagens { /// @brief normalize a function name //////////////////////////////////////////////////////////////////////////////// - std::string normalizeFunctionName (char const*); + std::pair normalizeFunctionName (char const*); //////////////////////////////////////////////////////////////////////////////// /// @brief create a node of the specified type diff --git a/arangod/Aql/AstNode.cpp b/arangod/Aql/AstNode.cpp index e4f8bf06c4..6c038e1db7 100644 --- a/arangod/Aql/AstNode.cpp +++ b/arangod/Aql/AstNode.cpp @@ -28,6 +28,7 @@ //////////////////////////////////////////////////////////////////////////////// #include "Aql/AstNode.h" +#include "Aql/Function.h" #include "Aql/Scopes.h" #include "Basics/StringBuffer.h" @@ -77,12 +78,16 @@ TRI_json_t* AstNode::toJson (TRI_memory_zone_t* zone) const { if (type == NODE_TYPE_COLLECTION || type == NODE_TYPE_PARAMETER || type == NODE_TYPE_ATTRIBUTE_ACCESS || - type == NODE_TYPE_FCALL || type == NODE_TYPE_FCALL_USER) { // dump "name" of node TRI_Insert3ArrayJson(zone, node, "name", TRI_CreateStringCopyJson(zone, getStringValue())); } + if (type == NODE_TYPE_FCALL) { + auto func = static_cast(getData()); + TRI_Insert3ArrayJson(zone, node, "name", TRI_CreateStringCopyJson(zone, func->name.c_str())); + } + if (type == NODE_TYPE_VALUE) { // dump value of "value" node switch (value.type) { diff --git a/arangod/Aql/AstNode.h b/arangod/Aql/AstNode.h index 71792279d9..0460296d47 100644 --- a/arangod/Aql/AstNode.h +++ b/arangod/Aql/AstNode.h @@ -366,6 +366,14 @@ namespace triagens { value.value._data = v; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief set the data value of a node +//////////////////////////////////////////////////////////////////////////////// + + inline void setData (void const* v) { + value.value._data = const_cast(v); + } + //////////////////////////////////////////////////////////////////////////////// /// @brief return the type name of a node //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/V8Executor.cpp b/arangod/Aql/V8Executor.cpp index 98eeff3c79..a12dfb88f2 100644 --- a/arangod/Aql/V8Executor.cpp +++ b/arangod/Aql/V8Executor.cpp @@ -250,6 +250,8 @@ V8Executor::~V8Executor () { V8Expression* V8Executor::generateExpression (AstNode const* node) { generateCodeExpression(node); + + std::cout << "Executor::generateExpression: " << _buffer->c_str() << "\n"; v8::Handle compiled = v8::Script::Compile(v8::String::New(_buffer->c_str(), (int) _buffer->length()), v8::String::New("--script--")); @@ -306,9 +308,26 @@ V8Expression* V8Executor::generateExpression (AstNode const* node) { TRI_json_t* V8Executor::executeExpression (AstNode const* node) { generateCodeExpression(node); + std::cout << "Executor::ExecuteExpression: " << _buffer->c_str() << "\n"; + return execute(); } +//////////////////////////////////////////////////////////////////////////////// +/// @brief returns a reference to a built-in function +//////////////////////////////////////////////////////////////////////////////// + +Function const* V8Executor::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); +} + // ----------------------------------------------------------------------------- // --SECTION-- private methods // ----------------------------------------------------------------------------- @@ -517,43 +536,17 @@ void V8Executor::generateCodeFunctionCall (AstNode const* node) { TRI_ASSERT(node != nullptr); TRI_ASSERT(node->numMembers() == 1); - char const* name = node->getStringValue(); - - TRI_ASSERT(name != nullptr); - TRI_ASSERT(strncmp(name, "_AQL::", 6) == 0); - - // pointer to the function name as specified in the query - char const* userName = name + sizeof("_AQL::") - 1; - - auto it = FunctionNames.find(std::string(userName)); - - if (it == FunctionNames.end()) { - // unknown function name - THROW_ARANGO_EXCEPTION_PARAMS(TRI_ERROR_QUERY_FUNCTION_NAME_UNKNOWN, userName); - } - - Function const& func = (*it).second; + auto func = static_cast(node->getData()); auto args = node->getMember(0); TRI_ASSERT(args != nullptr); TRI_ASSERT(args->type == NODE_TYPE_LIST); - size_t const n = args->numMembers(); - - // check number of arguments - auto numExpectedArguments = func.numArguments(); - - if (n < numExpectedArguments.first || n > numExpectedArguments.second) { - THROW_ARANGO_EXCEPTION_PARAMS(TRI_ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH, - userName, - static_cast(numExpectedArguments.first), - static_cast(numExpectedArguments.second)); - } - _buffer->appendText("aql."); - _buffer->appendText(func.name); + _buffer->appendText(func->name); _buffer->appendText("("); + size_t const n = args->numMembers(); for (size_t i = 0; i < n; ++i) { if (i > 0) { _buffer->appendText(", "); diff --git a/arangod/Aql/V8Executor.h b/arangod/Aql/V8Executor.h index 080fbdc82d..4d50e3e03b 100644 --- a/arangod/Aql/V8Executor.h +++ b/arangod/Aql/V8Executor.h @@ -87,6 +87,12 @@ namespace triagens { struct TRI_json_s* executeExpression (AstNode const*); +//////////////////////////////////////////////////////////////////////////////// +/// @brief returns a reference to a built-in function +//////////////////////////////////////////////////////////////////////////////// + + Function const* getFunctionByName (std::string const&); + // ----------------------------------------------------------------------------- // --SECTION-- private methods // ----------------------------------------------------------------------------- From 7c226df013cd3a1535363aa23a5d1af11742eb7a Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Wed, 6 Aug 2014 17:30:21 +0200 Subject: [PATCH 3/3] handle calls to user-defined functions --- arangod/Aql/V8Executor.cpp | 36 +++++++++++++++++++++++++++++++++++- arangod/Aql/V8Executor.h | 8 +++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/arangod/Aql/V8Executor.cpp b/arangod/Aql/V8Executor.cpp index a12dfb88f2..5741275fa7 100644 --- a/arangod/Aql/V8Executor.cpp +++ b/arangod/Aql/V8Executor.cpp @@ -529,7 +529,7 @@ void V8Executor::generateCodeCollection (AstNode const* node) { } //////////////////////////////////////////////////////////////////////////////// -/// @brief generate JavaScript code for a function call +/// @brief generate JavaScript code for a call to a built-in function //////////////////////////////////////////////////////////////////////////////// void V8Executor::generateCodeFunctionCall (AstNode const* node) { @@ -557,6 +557,36 @@ void V8Executor::generateCodeFunctionCall (AstNode const* node) { _buffer->appendText(")"); } +//////////////////////////////////////////////////////////////////////////////// +/// @brief generate JavaScript code for a call to a user-defined function +//////////////////////////////////////////////////////////////////////////////// + +void V8Executor::generateCodeUserFunctionCall (AstNode const* node) { + TRI_ASSERT(node != nullptr); + TRI_ASSERT(node->numMembers() == 1); + + char const* name = node->getStringValue(); + TRI_ASSERT(name != nullptr); + + auto args = node->getMember(0); + TRI_ASSERT(args != nullptr); + TRI_ASSERT(args->type == NODE_TYPE_LIST); + + _buffer->appendText("aql.FCALL_USER(\""); + _buffer->appendJsonEncoded(name); + _buffer->appendText("\", ["); + + size_t const n = args->numMembers(); + for (size_t i = 0; i < n; ++i) { + if (i > 0) { + _buffer->appendText(", "); + } + + generateCodeNode(args->getMember(i)); + } + _buffer->appendText("])"); +} + //////////////////////////////////////////////////////////////////////////////// /// @brief generate JavaScript code for an expansion (i.e. [*] operator) //////////////////////////////////////////////////////////////////////////////// @@ -686,6 +716,10 @@ void V8Executor::generateCodeNode (AstNode const* node) { generateCodeFunctionCall(node); break; + case NODE_TYPE_FCALL_USER: + generateCodeUserFunctionCall(node); + break; + case NODE_TYPE_EXPAND: generateCodeExpand(node); break; diff --git a/arangod/Aql/V8Executor.h b/arangod/Aql/V8Executor.h index 4d50e3e03b..c56f8cec07 100644 --- a/arangod/Aql/V8Executor.h +++ b/arangod/Aql/V8Executor.h @@ -154,11 +154,17 @@ namespace triagens { void generateCodeCollection (AstNode const*); //////////////////////////////////////////////////////////////////////////////// -/// @brief generate JavaScript code for a function call +/// @brief generate JavaScript code for a call to a built-in function //////////////////////////////////////////////////////////////////////////////// void generateCodeFunctionCall (AstNode const*); +//////////////////////////////////////////////////////////////////////////////// +/// @brief generate JavaScript code for a user-defined function +//////////////////////////////////////////////////////////////////////////////// + + void generateCodeUserFunctionCall (AstNode const*); + //////////////////////////////////////////////////////////////////////////////// /// @brief generate JavaScript code for an expansion (i.e. [*] operator) ////////////////////////////////////////////////////////////////////////////////