diff --git a/arangod/Aql/Ast.cpp b/arangod/Aql/Ast.cpp index e1b8ca2f8d..4d1f520d03 100644 --- a/arangod/Aql/Ast.cpp +++ b/arangod/Aql/Ast.cpp @@ -29,7 +29,6 @@ #include "Aql/Function.h" #include "Aql/Graphs.h" #include "Aql/Query.h" -#include "Aql/V8Executor.h" #include "Basics/Exceptions.h" #include "Basics/StringRef.h" #include "Basics/StringUtils.h" @@ -2604,28 +2603,6 @@ AstNode* Ast::createArithmeticResultNode(double value) { return createNodeValueDouble(value); } -/// @brief executes an expression with constant parameters in V8 -AstNode* Ast::executeConstExpressionV8(AstNode const* node) { - // must enter v8 before we can execute any expression - _query->enterContext(); - ISOLATE; - v8::HandleScope scope(isolate); // do not delete this! - - TRI_ASSERT(_query->trx() != nullptr); - transaction::BuilderLeaser builder(_query->trx()); - - int res = _query->v8Executor()->executeExpression(_query, node, *builder.get()); - - if (res != TRI_ERROR_NO_ERROR) { - THROW_ARANGO_EXCEPTION(res); - } - - // context is not left here, but later - // this allows re-using the same context for multiple expressions - - return nodeFromVPack(builder->slice(), true); -} - /// @brief optimizes the unary operators + and - /// the unary plus will be converted into a simple value node if the operand of /// the operation is a constant number @@ -2865,17 +2842,12 @@ AstNode* Ast::optimizeBinaryOperatorRelational(AstNode* node) { Expression exp(nullptr, this, node); FixedVarExpressionContext context; bool mustDestroy; - // execute the expression using the C++ variant + AqlValue a = exp.execute(_query->trx(), &context, mustDestroy); AqlValueGuard guard(a, mustDestroy); - // we cannot create slices from types Range and Docvec easily - if (!a.isRange() && !a.isDocvec()) { - return nodeFromVPack(a.slice(), true); - } - - // simply fall through to V8 now - return executeConstExpressionV8(node); + AqlValueMaterializer materializer(_query->trx()); + return nodeFromVPack(materializer.slice(a, false), true); } /// @brief optimizes the binary arithmetic operators +, -, *, / and % @@ -3146,24 +3118,22 @@ AstNode* Ast::optimizeFunctionCall(AstNode* node) { // place. note that the transaction has not necessarily been // started yet... TRI_ASSERT(_query->trx() != nullptr); - - if (func->hasImplementation() && node->isSimple()) { - Expression exp(nullptr, this, node); - FixedVarExpressionContext context; - bool mustDestroy; - // execute the expression using the C++ variant - AqlValue a = exp.execute(_query->trx(), &context, mustDestroy); - AqlValueGuard guard(a, mustDestroy); - - // we cannot create slices from types Range and Docvec easily - if (!a.isRange() && !a.isDocvec()) { - return nodeFromVPack(a.slice(), true); - } - // simply fall through to V8 now + + if (node->willUseV8()) { + // if the expression is going to use V8 internally, we do not + // bother to optimize it here + return node; } - // execute the expression using V8 - return executeConstExpressionV8(node); + Expression exp(nullptr, this, node); + FixedVarExpressionContext context; + bool mustDestroy; + + AqlValue a = exp.execute(_query->trx(), &context, mustDestroy); + AqlValueGuard guard(a, mustDestroy); + + AqlValueMaterializer materializer(_query->trx()); + return nodeFromVPack(materializer.slice(a, false), true); } /// @brief optimizes a reference to a variable diff --git a/arangod/Aql/Ast.h b/arangod/Aql/Ast.h index c9b1a5251d..0e426164dc 100644 --- a/arangod/Aql/Ast.h +++ b/arangod/Aql/Ast.h @@ -459,13 +459,9 @@ class Ast { /// @brief create a number node for an arithmetic result, double AstNode* createArithmeticResultNode(double); - /// @brief executes an expression with constant parameters in V8 - AstNode* executeConstExpressionV8(AstNode const*); - /// @brief optimizes the unary operators + and - /// the unary plus will be converted into a simple value node if the operand - /// of - /// the operation is a constant number + /// of the operation is a constant number AstNode* optimizeUnaryOperatorArithmetic(AstNode*); /// @brief optimizes the unary operator NOT with a non-constant expression diff --git a/arangod/Aql/AstNode.cpp b/arangod/Aql/AstNode.cpp index 0a0c265505..8f803912c5 100644 --- a/arangod/Aql/AstNode.cpp +++ b/arangod/Aql/AstNode.cpp @@ -47,13 +47,6 @@ #include #include -namespace { - -arangodb::StringRef const VIEW_NODE_SUFFIX("\0v", 2); -arangodb::StringRef const COLLECTION_NODE_SUFFIX("\0c", 2); - -} - using namespace arangodb::aql; std::unordered_map const AstNode::Operators{ @@ -1542,14 +1535,6 @@ bool AstNode::isSimple() const { auto func = static_cast(getData()); TRI_ASSERT(func != nullptr); - if (func->implementation == nullptr) { - // no C++ handler available for function - setFlag(DETERMINED_SIMPLE); - return false; - } - - TRI_ASSERT(func->implementation != nullptr); - TRI_ASSERT(numMembers() == 1); // check if there is a C++ function handler condition @@ -1584,11 +1569,63 @@ bool AstNode::isSimple() const { setFlag(DETERMINED_SIMPLE, VALUE_SIMPLE); return true; } + + if (type == NODE_TYPE_FCALL_USER) { + setFlag(DETERMINED_SIMPLE, VALUE_SIMPLE); + return true; + } setFlag(DETERMINED_SIMPLE); return false; } +/// @brief whether or not a node will use V8 internally +bool AstNode::willUseV8() const { + if (hasFlag(DETERMINED_V8)) { + // fast track exit + return hasFlag(VALUE_V8); + } + + if (type == NODE_TYPE_FCALL_USER) { + // user-defined function will always use v8 + setFlag(DETERMINED_V8, VALUE_V8); + return true; + } + + if (type == NODE_TYPE_FCALL) { + // some functions have C++ handlers + // check if the called function is one of them + auto func = static_cast(getData()); + TRI_ASSERT(func != nullptr); + + if (func->implementation == nullptr) { + // a function without a V8 implementation + setFlag(DETERMINED_V8, VALUE_V8); + return true; + } + + if (func->condition && !func->condition()) { + // a function with an execution condition + setFlag(DETERMINED_V8, VALUE_V8); + return true; + } + } + + size_t const n = numMembers(); + + for (size_t i = 0; i < n; ++i) { + auto member = getMemberUnchecked(i); + + if (member->willUseV8()) { + setFlag(DETERMINED_V8, VALUE_V8); + return true; + } + } + + setFlag(DETERMINED_V8); + return false; +} + /// @brief whether or not a node has a constant value bool AstNode::isConstant() const { if (hasFlag(DETERMINED_CONSTANT)) { diff --git a/arangod/Aql/AstNode.h b/arangod/Aql/AstNode.h index 1d4bd37c54..bb9a1ef74c 100644 --- a/arangod/Aql/AstNode.h +++ b/arangod/Aql/AstNode.h @@ -52,35 +52,26 @@ typedef uint32_t AstNodeFlagsType; /// the flags are used to prevent repeated calculations of node properties /// (e.g. is the node value constant, sorted etc.) enum AstNodeFlagType : AstNodeFlagsType { - DETERMINED_SORTED = 1, // node is a list and its members are sorted asc. - DETERMINED_CONSTANT = 2, // node value is constant (i.e. not dynamic) - DETERMINED_SIMPLE = - 4, // node value is simple (i.e. for use in a simple expression) - DETERMINED_THROWS = 8, // node can throw an exception - DETERMINED_NONDETERMINISTIC = - 16, // node produces non-deterministic result (e.g. function call nodes) - DETERMINED_RUNONDBSERVER = - 32, // node can run on the DB server in a cluster setup - DETERMINED_CHECKUNIQUENESS = 64, // object's keys must be checked for uniqueness + DETERMINED_SORTED = 0x0000001, // node is a list and its members are sorted asc. + DETERMINED_CONSTANT = 0x0000002, // node value is constant (i.e. not dynamic) + DETERMINED_SIMPLE = 0x0000004, // node value is simple (i.e. for use in a simple expression) + DETERMINED_THROWS = 0x0000008, // node can throw an exception + DETERMINED_NONDETERMINISTIC = 0x0000010, // node produces non-deterministic result (e.g. function call nodes) + DETERMINED_RUNONDBSERVER = 0x0000020, // node can run on the DB server in a cluster setup + DETERMINED_CHECKUNIQUENESS = 0x0000040, // object's keys must be checked for uniqueness + DETERMINED_V8 = 0x0000080, // node will use V8 internally - VALUE_SORTED = 128, // node is a list and its members are sorted asc. - VALUE_CONSTANT = 256, // node value is constant (i.e. not dynamic) - VALUE_SIMPLE = - 512, // node value is simple (i.e. for use in a simple expression) - VALUE_THROWS = 1024, // node can throw an exception - VALUE_NONDETERMINISTIC = 2048, // node produces non-deterministic result - // (e.g. function call nodes) - VALUE_RUNONDBSERVER = - 4096, // node can run on the DB server in a cluster setup - VALUE_CHECKUNIQUENESS = 8192, // object's keys must be checked for uniqueness - - FLAG_KEEP_VARIABLENAME = 16384, // node is a reference to a variable name, - // not the variable value (used in KEEP - // nodes) - FLAG_BIND_PARAMETER = 32768, // node was created from a bind parameter - FLAG_FINALIZED = 65536, // node has been finalized and should not be - // modified; only set and checked in - // maintainer mode + VALUE_SORTED = 0x0000100, // node is a list and its members are sorted asc. + VALUE_CONSTANT = 0x0000200, // node value is constant (i.e. not dynamic) + VALUE_SIMPLE = 0x0000400, // node value is simple (i.e. for use in a simple expression) + VALUE_THROWS = 0x0000800, // node can throw an exception + VALUE_NONDETERMINISTIC = 0x0001000, // node produces non-deterministic result (e.g. function call nodes) + VALUE_RUNONDBSERVER = 0x0002000, // node can run on the DB server in a cluster setup + VALUE_CHECKUNIQUENESS = 0x0004000, // object's keys must be checked for uniqueness + VALUE_V8 = 0x0008000, // node will use V8 internally + FLAG_KEEP_VARIABLENAME = 0x0010000, // node is a reference to a variable name, not the variable value (used in KEEP nodes) + FLAG_BIND_PARAMETER = 0x0020000, // node was created from a bind parameter + FLAG_FINALIZED = 0x0040000, // node has been finalized and should not be modified; only set and checked in maintainer mode }; /// @brief enumeration of AST node value types @@ -483,6 +474,10 @@ struct AstNode { /// @brief whether or not a node has a constant value /// this may also set the FLAG_CONSTANT or the FLAG_DYNAMIC flags for the node bool isConstant() const; + + /// @brief whether or not a node will use V8 internally + /// this may also set the FLAG_V8 flag for the node + bool willUseV8() const; /// @brief whether or not a node is a simple comparison operator bool isSimpleComparisonOperator() const; diff --git a/arangod/Aql/CalculationBlock.cpp b/arangod/Aql/CalculationBlock.cpp index 071d092a9a..dfb5fbc99e 100644 --- a/arangod/Aql/CalculationBlock.cpp +++ b/arangod/Aql/CalculationBlock.cpp @@ -155,7 +155,7 @@ void CalculationBlock::doEvaluation(AqlItemBlock* result) { TRI_ASSERT(_expression != nullptr); - if (!_expression->isV8()) { + if (!_expression->willUseV8()) { // an expression that does not require V8 executeExpression(result); } else { diff --git a/arangod/Aql/Expression.cpp b/arangod/Aql/Expression.cpp index d160be16a6..b91aa5c429 100644 --- a/arangod/Aql/Expression.cpp +++ b/arangod/Aql/Expression.cpp @@ -35,7 +35,6 @@ #include "Aql/Quantifier.h" #include "Aql/Query.h" #include "Aql/V8Executor.h" -#include "Aql/V8Expression.h" #include "Aql/Variable.h" #include "Basics/Exceptions.h" #include "Basics/NumberUtils.h" @@ -44,12 +43,16 @@ #include "Basics/VPackStringBufferAdapter.h" #include "Transaction/Helpers.h" #include "Transaction/Methods.h" +#include "V8/v8-globals.h" +#include "V8/v8-vpack.h" #include #include #include #include +#include + using namespace arangodb; using namespace arangodb::aql; using VelocyPackHelper = arangodb::basics::VelocyPackHelper; @@ -87,12 +90,12 @@ Expression::Expression(ExecutionPlan* plan, Ast* ast, AstNode* node) : _plan(plan), _ast(ast), _node(node), - _func(nullptr), // this will reset all pointers in the union _type(UNPROCESSED), _canThrow(true), _canRunOnDBServer(false), _isDeterministic(false), - _hasDeterminedAttributes(false), + _willUseV8(false), + _preparedV8Context(false), _attributes(), _expressionContext(nullptr) { TRI_ASSERT(_ast != nullptr); @@ -143,12 +146,6 @@ AqlValue Expression::execute(transaction::Methods* trx, ExpressionContext* ctx, return _accessor->getDynamic(trx, ctx, mustDestroy); } - case V8: { - TRI_ASSERT(_func != nullptr); - ISOLATE; - return _func->execute(isolate, _ast->query(), trx, ctx, mustDestroy); - } - case UNPROCESSED: { // fall-through to exception } @@ -168,7 +165,7 @@ void Expression::replaceVariables( if ((_type == ATTRIBUTE_SYSTEM || _type == ATTRIBUTE_DYNAMIC) && _accessor != nullptr) { _accessor->replaceVariable(replacements); - } else if (_type == V8) { + } else { freeInternals(); } } @@ -210,11 +207,6 @@ void Expression::freeInternals() noexcept { break; } - case V8: - delete _func; - _func = nullptr; - break; - case SIMPLE: case UNPROCESSED: { // nothing to do @@ -225,7 +217,7 @@ void Expression::freeInternals() noexcept { /// @brief reset internal attributes after variables in the expression were changed void Expression::invalidateAfterReplacements() { - if (_type == ATTRIBUTE_SYSTEM || _type == ATTRIBUTE_DYNAMIC || _type == SIMPLE || _type == V8) { + if (_type == ATTRIBUTE_SYSTEM || _type == ATTRIBUTE_DYNAMIC || _type == SIMPLE) { freeInternals(); // must even set back the expression type so the expression will be analyzed // again @@ -235,7 +227,6 @@ void Expression::invalidateAfterReplacements() { const_cast(_node)->clearFlags(); _attributes.clear(); - _hasDeterminedAttributes = false; } /// @brief invalidates an expression @@ -243,10 +234,11 @@ void Expression::invalidateAfterReplacements() { /// used and destroyed in the same context. when a V8 function is used across /// multiple V8 contexts, it must be invalidated in between void Expression::invalidate() { - if (_type == V8) { - // V8 expressions need a special handling - freeInternals(); - } + // context may change next time, so "prepare for re-preparation" + _preparedV8Context = false; + + // V8 expressions need a special handling + freeInternals(); // we do not need to invalidate the other expression type // expression data will be freed in the destructor } @@ -340,6 +332,7 @@ void Expression::initConstantExpression() { _canThrow = false; _canRunOnDBServer = true; _isDeterministic = true; + _willUseV8 = false; _data = nullptr; _type = JSON; @@ -349,6 +342,7 @@ void Expression::initSimpleExpression() { _canThrow = _node->canThrow(); _canRunOnDBServer = _node->canRunOnDBServer(); _isDeterministic = _node->isDeterministic(); + _willUseV8 = _node->willUseV8(); _type = SIMPLE; @@ -392,36 +386,6 @@ void Expression::initSimpleExpression() { } } -void Expression::initV8Expression() { - _canThrow = _node->canThrow(); - _canRunOnDBServer = _node->canRunOnDBServer(); - _isDeterministic = _node->isDeterministic(); - _func = nullptr; - - _type = V8; - - if (_hasDeterminedAttributes) { - return; - } - - // determine all top-level attributes used in expression only once - // as this might be expensive - bool isSafeForOptimization; - _attributes = - Ast::getReferencedAttributes(_node, isSafeForOptimization); - - if (!isSafeForOptimization) { - _attributes.clear(); - // unfortunately there are not only top-level attribute accesses but - // also other accesses, e.g. the index values or accesses of the whole - // value. - // for example, we cannot optimize LET x = a +1 or LET x = a[0], but LET - // x = a._key - } - - _hasDeterminedAttributes = true; -} - /// @brief analyze the expression (determine its type etc.) void Expression::initExpression() { TRI_ASSERT(_type == UNPROCESSED); @@ -429,13 +393,10 @@ void Expression::initExpression() { if (_node->isConstant()) { // expression is a constant value initConstantExpression(); - } else if (_node->isSimple()) { + } else { // expression is a simple expression initSimpleExpression(); - } else { - // expression is a V8 expression - initV8Expression(); - } + } TRI_ASSERT(_type != UNPROCESSED); } @@ -455,16 +416,7 @@ void Expression::buildExpression(transaction::Methods* trx) { _data = new uint8_t[static_cast(builder->size())]; memcpy(_data, builder->data(), static_cast(builder->size())); - } else if (_type == V8 && _func == nullptr) { - // generate a V8 expression - _func = _ast->query()->v8Executor()->generateExpression(_node); - - // optimizations for the generated function - if (_func != nullptr && !_attributes.empty()) { - // pass which variables do not need to be fully constructed - _func->setAttributeRestrictions(_attributes); - } - } + } } /// @brief execute an expression of type SIMPLE, the convention is that @@ -488,6 +440,8 @@ AqlValue Expression::executeSimpleExpression( return executeSimpleExpressionReference(node, trx, mustDestroy, doCopy); case NODE_TYPE_FCALL: return executeSimpleExpressionFCall(node, trx, mustDestroy); + case NODE_TYPE_FCALL_USER: + return executeSimpleExpressionFCallJS(node, trx, mustDestroy); case NODE_TYPE_RANGE: return executeSimpleExpressionRange(node, trx, mustDestroy); case NODE_TYPE_OPERATOR_UNARY_NOT: @@ -880,14 +834,25 @@ AqlValue Expression::executeSimpleExpressionRange( return AqlValue(resultLow.toInt64(trx), resultHigh.toInt64(trx)); } -/// @brief execute an expression of type SIMPLE with FCALL +/// @brief execute an expression of type SIMPLE with FCALL, dispatcher AqlValue Expression::executeSimpleExpressionFCall( AstNode const* node, transaction::Methods* trx, bool& mustDestroy) { - mustDestroy = false; // only some functions have C++ handlers // check that the called function actually has one auto func = static_cast(node->getData()); + if (func->implementation != nullptr && (!func->condition || func->condition())) { + return executeSimpleExpressionFCallCxx(node, trx, mustDestroy); + } + return executeSimpleExpressionFCallJS(node, trx, mustDestroy); +} + +/// @brief execute an expression of type SIMPLE with FCALL, CXX version +AqlValue Expression::executeSimpleExpressionFCallCxx( + AstNode const* node, transaction::Methods* trx, bool& mustDestroy) { + + mustDestroy = false; + auto func = static_cast(node->getData()); TRI_ASSERT(func->implementation != nullptr); auto member = node->getMemberUnchecked(0); @@ -916,8 +881,7 @@ AqlValue Expression::executeSimpleExpressionFCall( destroyParameters.push_back(1); } else { bool localMustDestroy; - AqlValue a = executeSimpleExpression(arg, trx, localMustDestroy, false); - parameters.emplace_back(a); + parameters.emplace_back(executeSimpleExpression(arg, trx, localMustDestroy, false)); destroyParameters.push_back(localMustDestroy ? 1 : 0); } } @@ -945,6 +909,109 @@ AqlValue Expression::executeSimpleExpressionFCall( } } +/// @brief execute an expression of type SIMPLE, JavaScript variant +AqlValue Expression::executeSimpleExpressionFCallJS( + AstNode const* node, transaction::Methods* trx, bool& mustDestroy) { + + prepareV8Context(); + + auto member = node->getMemberUnchecked(0); + TRI_ASSERT(member->type == NODE_TYPE_ARRAY); + + mustDestroy = false; + + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + TRI_ASSERT(isolate != nullptr); + + { + + v8::HandleScope scope(isolate); + + std::string jsName; + size_t const n = member->numMembers(); + size_t const callArgs = (node->type == NODE_TYPE_FCALL_USER ? 2 : n); + auto args = std::make_unique[]>(callArgs); + + if (node->type == NODE_TYPE_FCALL_USER) { + // a call to a user-defined function + jsName = "FCALL_USER"; + v8::Handle params = v8::Array::New(isolate, static_cast(n)); + + for (size_t i = 0; i < n; ++i) { + auto arg = member->getMemberUnchecked(i); + + bool localMustDestroy; + AqlValue a = executeSimpleExpression(arg, trx, localMustDestroy, false); + AqlValueGuard guard(a, localMustDestroy); + + params->Set(static_cast(i), a.toV8(isolate, trx)); + } + + // function name + args[0] = TRI_V8_STD_STRING(isolate, node->getString()); + // call parameters + args[1] = params; + } else { + // a call to a built-in V8 function + auto func = static_cast(node->getData()); + jsName = "AQL_" + func->nonAliasedName; + + for (size_t i = 0; i < n; ++i) { + auto arg = member->getMemberUnchecked(i); + + if (arg->type == NODE_TYPE_COLLECTION) { + // parameter conversion for NODE_TYPE_COLLECTION here + args[i] = TRI_V8_ASCII_PAIR_STRING(isolate, arg->getStringValue(), arg->getStringLength()); + } else { + bool localMustDestroy; + AqlValue a = executeSimpleExpression(arg, trx, localMustDestroy, false); + AqlValueGuard guard(a, localMustDestroy); + + args[i] = a.toV8(isolate, trx); + } + } + } + + TRI_v8_global_t* v8g = static_cast(isolate->GetData(arangodb::V8PlatformFeature::V8_DATA_SLOT)); + auto old = v8g->_query; + v8g->_query = static_cast(_ast->query()); + TRI_DEFER(v8g->_query = old); + + auto current = isolate->GetCurrentContext()->Global(); + + v8::Handle module = current->Get(TRI_V8_ASCII_STRING(isolate, "_AQL")); + if (module.IsEmpty() || !module->IsObject()) { + THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "unable to find global _AQL module"); + } + + v8::Handle function = v8::Handle::Cast(module)->Get(TRI_V8_STD_STRING(isolate, jsName)); + if (function.IsEmpty() || !function->IsFunction()) { + THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, std::string("unable to find AQL function '") + jsName + "'"); + } + + // actually call the V8 function + v8::TryCatch tryCatch; + v8::Handle result = v8::Handle::Cast(function)->Call(current, callArgs, args.get()); + + V8Executor::HandleV8Error(tryCatch, result, nullptr, false); + + if (result.IsEmpty() || result->IsUndefined()) { + return AqlValue(AqlValueHintNull()); + } + + transaction::BuilderLeaser builder(trx); + + int res = TRI_V8ToVPack(isolate, *builder.get(), result, false); + + if (res != TRI_ERROR_NO_ERROR) { + THROW_ARANGO_EXCEPTION(res); + } + + mustDestroy = true; // builder = dynamic data + return AqlValue(builder.get()); + } +} + /// @brief execute an expression of type SIMPLE with NOT AqlValue Expression::executeSimpleExpressionNot( AstNode const* node, transaction::Methods* trx, bool& mustDestroy) { @@ -1587,3 +1654,31 @@ AqlValue Expression::executeSimpleExpressionArithmetic( // this will convert NaN, +inf & -inf to null return AqlValue(AqlValueHintDouble(result)); } + +/// @brief prepare a V8 context for execution for this expression +/// this needs to be called once before executing any V8 function in this +/// expression +void Expression::prepareV8Context() { + if (_preparedV8Context) { + // already done + return; + } + + TRI_ASSERT(_ast->query()->trx() != nullptr); + + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + TRI_ASSERT(isolate != nullptr); + + std::string body("if (_AQL === undefined) { _AQL = require(\"@arangodb/aql\"); _AQL.clearCaches(); }"); + + { + v8::HandleScope scope(isolate); + v8::Handle compiled = v8::Script::Compile( + TRI_V8_STD_STRING(isolate, body), TRI_V8_ASCII_STRING(isolate, "--script--")); + + if (!compiled.IsEmpty()) { + v8::Handle func(compiled->Run()); + _preparedV8Context = true; + } + } +} diff --git a/arangod/Aql/Expression.h b/arangod/Aql/Expression.h index 083686c411..d61b927f9c 100644 --- a/arangod/Aql/Expression.h +++ b/arangod/Aql/Expression.h @@ -50,12 +50,11 @@ class Ast; class AttributeAccessor; class ExecutionPlan; class ExpressionContext; -struct V8Expression; /// @brief AqlExpression, used in execution plans and execution blocks class Expression { public: - enum ExpressionType : uint32_t { UNPROCESSED, JSON, V8, SIMPLE, ATTRIBUTE_SYSTEM, ATTRIBUTE_DYNAMIC }; + enum ExpressionType : uint32_t { UNPROCESSED, JSON, SIMPLE, ATTRIBUTE_SYSTEM, ATTRIBUTE_DYNAMIC }; Expression(Expression const&) = delete; Expression& operator=(Expression const&) = delete; @@ -104,6 +103,14 @@ class Expression { } return _isDeterministic; } + + /// @brief whether or not the expression will use V8 + inline bool willUseV8() { + if (_type == UNPROCESSED) { + initExpression(); + } + return _willUseV8; + } /// @brief clone the expression, needed to clone execution plans Expression* clone(ExecutionPlan* plan, Ast* ast) { @@ -132,14 +139,6 @@ class Expression { return _type == JSON; } - /// @brief check whether this is a V8 expression - inline bool isV8() { - if (_type == UNPROCESSED) { - initExpression(); - } - return _type == V8; - } - /// @brief get expression type as string std::string typeString() { if (_type == UNPROCESSED) { @@ -154,8 +153,6 @@ class Expression { case ATTRIBUTE_SYSTEM: case ATTRIBUTE_DYNAMIC: return "attribute"; - case V8: - return "v8"; case UNPROCESSED: { } } @@ -195,7 +192,7 @@ class Expression { void replaceAttributeAccess(Variable const*, std::vector const& attribute); /// @brief invalidates an expression - /// this only has an effect for V8-based functions, which need to be created, + /// this only has an effect for V8-using functions, which need to be created, /// used and destroyed in the same context. when a V8 function is used across /// multiple V8 contexts, it must be invalidated in between void invalidate(); @@ -220,7 +217,6 @@ class Expression { void initConstantExpression(); void initSimpleExpression(); - void initV8Expression(); /// @brief analyze the expression (determine its type etc.) void initExpression(); @@ -264,11 +260,21 @@ class Expression { bool& mustDestroy, bool); - /// @brief execute an expression of type SIMPLE with FCALL + /// @brief execute an expression of type SIMPLE with FCALL, dispatcher AqlValue executeSimpleExpressionFCall(AstNode const*, transaction::Methods*, bool& mustDestroy); - + + /// @brief execute an expression of type SIMPLE with FCALL, CXX variant + AqlValue executeSimpleExpressionFCallCxx(AstNode const*, + transaction::Methods*, + bool& mustDestroy); + + /// @brief execute an expression of type SIMPLE with FCALL, JavaScript variant + AqlValue executeSimpleExpressionFCallJS(AstNode const*, + transaction::Methods*, + bool& mustDestroy); + /// @brief execute an expression of type SIMPLE with RANGE AqlValue executeSimpleExpressionRange(AstNode const*, transaction::Methods*, @@ -331,6 +337,11 @@ class Expression { AstNode const*, transaction::Methods*, bool& mustDestroy); + /// @brief prepare a V8 context for execution for this expression + /// this needs to be called once before executing any V8 function in this + /// expression + void prepareV8Context(); + private: /// @brief the query execution plan. note: this may be a nullptr for expressions /// created in the early optimization stage! @@ -342,10 +353,8 @@ class Expression { /// @brief the AST node that contains the expression to execute AstNode* _node; - /// @brief a v8 function that will be executed for the expression /// if the expression is a constant, it will be stored as plain JSON instead union { - V8Expression* _func; uint8_t* _data; AttributeAccessor* _accessor; }; @@ -362,9 +371,13 @@ class Expression { /// @brief whether or not the expression is deterministic bool _isDeterministic; - /// @brief whether or not the top-level attributes of the expression were - /// determined - bool _hasDeterminedAttributes; + /// @brief whether or not the expression will make use of V8 + bool _willUseV8; + + /// @brief whether or not the preparation routine for V8 contexts was run + /// once for this expression + /// it needs to be run once before any V8-based function is called + bool _preparedV8Context; /// @brief the top-level attributes used in the expression, grouped /// by variable name diff --git a/arangod/Aql/Function.h b/arangod/Aql/Function.h index 3f593aeb29..e4dd4bc2d8 100644 --- a/arangod/Aql/Function.h +++ b/arangod/Aql/Function.h @@ -112,8 +112,7 @@ struct Function { /// @brief condition under which the C++ implementation of the function is /// executed (if returns false, the function will be executed as its - /// JavaScript - /// variant) + /// JavaScript variant) ExecutionCondition condition; /// @brief function argument conversion information diff --git a/arangod/Aql/IndexBlock.cpp b/arangod/Aql/IndexBlock.cpp index b2cb6493d8..9899a9f328 100644 --- a/arangod/Aql/IndexBlock.cpp +++ b/arangod/Aql/IndexBlock.cpp @@ -179,7 +179,7 @@ int IndexBlock::initialize() { THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG); } - _hasV8Expression |= e->isV8(); + _hasV8Expression |= e->willUseV8(); std::unordered_set inVars; e->variables(inVars); diff --git a/arangod/Aql/IndexBlock.h b/arangod/Aql/IndexBlock.h index 9a9e401c90..d45382d8e3 100644 --- a/arangod/Aql/IndexBlock.h +++ b/arangod/Aql/IndexBlock.h @@ -91,9 +91,6 @@ class IndexBlock final : public ExecutionBlock, public DocumentProducingBlock { /// @brief Initializes the indexes bool initIndexes(); - /// @brief whether or not one of the bounds expressions requires V8 - bool hasV8Expression() const; - /// @brief execute the bounds expressions void executeExpressions(); @@ -146,12 +143,13 @@ class IndexBlock final : public ExecutionBlock, public DocumentProducingBlock { /// @brief set of already returned documents. Used to make the result distinct std::unordered_set _alreadyReturned; - /// @brief whether or not at least one expression uses v8 - bool _hasV8Expression; - /// @brief A managed document result to temporary hold one document std::unique_ptr _mmdr; + /// @brief whether or not we will use an expression that requires V8, and we need to take + /// special care to enter a context before and exit it properly + bool _hasV8Expression; + /// @brief Flag if all indexes are exhausted to be maintained accross several getSome() calls bool _indexesExhausted; diff --git a/arangod/Aql/OptimizerRules.cpp b/arangod/Aql/OptimizerRules.cpp index a93a1df437..6540522a48 100644 --- a/arangod/Aql/OptimizerRules.cpp +++ b/arangod/Aql/OptimizerRules.cpp @@ -126,7 +126,7 @@ void arangodb::aql::sortInValuesRule(Optimizer* opt, // expression auto s = static_cast(setter); auto filterExpression = s->expression(); - auto const* inNode = filterExpression->node(); + auto* inNode = filterExpression->nodeForModification(); TRI_ASSERT(inNode != nullptr); @@ -140,7 +140,7 @@ void arangodb::aql::sortInValuesRule(Optimizer* opt, auto rhs = inNode->getMember(1); - if (rhs->type != NODE_TYPE_REFERENCE) { + if (rhs->type != NODE_TYPE_REFERENCE && rhs->type != NODE_TYPE_ARRAY) { continue; } @@ -151,6 +151,21 @@ void arangodb::aql::sortInValuesRule(Optimizer* opt, // not need to sort the IN values then continue; } + + if (rhs->type == NODE_TYPE_ARRAY) { + if (rhs->numMembers() < AstNode::SortNumberThreshold || rhs->isSorted()) { + // number of values is below threshold or array is already sorted + continue; + } + + auto ast = plan->getAst(); + auto args = ast->createNodeArray(); + args->addMember(rhs); + auto sorted = ast->createNodeFunctionCall(TRI_CHAR_LENGTH_PAIR("SORTED_UNIQUE"), args); + inNode->changeMember(1, sorted); + modified = true; + continue; + } variable = static_cast(rhs->getData()); setter = plan->getVarSetBy(variable->id); diff --git a/arangod/Aql/Query.cpp b/arangod/Aql/Query.cpp index 88365825b9..7e4d0b36ea 100644 --- a/arangod/Aql/Query.cpp +++ b/arangod/Aql/Query.cpp @@ -28,7 +28,6 @@ #include "Aql/ExecutionBlock.h" #include "Aql/ExecutionEngine.h" #include "Aql/ExecutionPlan.h" -#include "Aql/V8Executor.h" #include "Aql/Optimizer.h" #include "Aql/Parser.h" #include "Aql/PlanCache.h" @@ -204,8 +203,6 @@ Query::~Query() { } cleanupPlanAndEngine(TRI_ERROR_INTERNAL); // abort the transaction - _v8Executor.reset(); - exitContext(); _ast.reset(); @@ -1005,17 +1002,6 @@ void Query::releaseEngine() { _engine.release(); } -/// @brief get v8 executor -V8Executor* Query::v8Executor() { - if (_v8Executor == nullptr) { - // the executor is a singleton per query - _v8Executor.reset(new V8Executor(_queryOptions.literalSizeThreshold)); - } - - TRI_ASSERT(_v8Executor != nullptr); - return _v8Executor.get(); -} - /// @brief enter a V8 context void Query::enterContext() { if (!_contextOwnedByExterior) { diff --git a/arangod/Aql/Query.h b/arangod/Aql/Query.h index 945a494f53..df57820a47 100644 --- a/arangod/Aql/Query.h +++ b/arangod/Aql/Query.h @@ -65,7 +65,6 @@ class ExecutionPlan; class Query; struct QueryProfile; class QueryRegistry; -class V8Executor; /// @brief equery part enum QueryPart { PART_MAIN, PART_DEPENDENT }; @@ -200,9 +199,6 @@ class Query { /// @brief explain an AQL query QueryResult explain(); - /// @brief get v8 executor - V8Executor* v8Executor(); - /// @brief cache for regular expressions constructed by the query RegexCache* regexCache() { return &_regexCache; } @@ -229,6 +225,11 @@ class Query { /// @brief exits a V8 context void exitContext(); + /// @brief check if the query has a V8 context ready for use + bool hasEnteredContext() const { + return (_contextOwnedByExterior || _context != nullptr); + } + /// @brief returns statistics for current query. void getStats(arangodb::velocypack::Builder&); @@ -308,9 +309,6 @@ class Query { /// @brief pointer to vocbase the query runs in TRI_vocbase_t* _vocbase; - /// @brief V8 code executor - std::unique_ptr _v8Executor; - /// @brief the currently used V8 context V8Context* _context; diff --git a/arangod/Aql/TraversalNode.cpp b/arangod/Aql/TraversalNode.cpp index 8f0c2403ea..327bf2a346 100644 --- a/arangod/Aql/TraversalNode.cpp +++ b/arangod/Aql/TraversalNode.cpp @@ -560,7 +560,6 @@ void TraversalNode::prepareOptions() { it.second->addMember(jt); } opts->_vertexExpressions.emplace(it.first, new Expression(_plan, ast, it.second)); - TRI_ASSERT(!opts->_vertexExpressions[it.first]->isV8()); } if (!_globalVertexConditions.empty()) { auto cond = @@ -569,7 +568,6 @@ void TraversalNode::prepareOptions() { cond->addMember(it); } opts->_baseVertexExpression = new Expression(_plan, ast, cond); - TRI_ASSERT(!opts->_baseVertexExpression->isV8()); } // If we use the path output the cache should activate document // caching otherwise it is not worth it. diff --git a/arangod/Aql/V8Executor.cpp b/arangod/Aql/V8Executor.cpp index 5eb087d2bb..d4e6167bb6 100644 --- a/arangod/Aql/V8Executor.cpp +++ b/arangod/Aql/V8Executor.cpp @@ -25,7 +25,6 @@ #include "Aql/AstNode.h" #include "Aql/AqlFunctionFeature.h" #include "Aql/Functions.h" -#include "Aql/V8Expression.h" #include "Aql/Variable.h" #include "Basics/StringBuffer.h" #include "Basics/Exceptions.h" @@ -34,259 +33,14 @@ #include "V8/v8-utils.h" #include "V8/v8-vpack.h" -#include -#include -#include - using namespace arangodb::aql; -/// @brief creates an executor -V8Executor::V8Executor(int64_t literalSizeThreshold) - : _constantRegisters(), - _literalSizeThreshold(literalSizeThreshold >= 0 - ? static_cast(literalSizeThreshold) - : defaultLiteralSizeThreshold) {} - -V8Executor::~V8Executor() {} - -/// @brief generates an expression execution object -V8Expression* V8Executor::generateExpression(AstNode const* node) { - ISOLATE; - v8::HandleScope scope(isolate); - - v8::TryCatch tryCatch; - _constantRegisters.clear(); - detectConstantValues(node, node->type); - - _userFunctions.clear(); - detectUserFunctions(node); - - generateCodeExpression(node); - - // std::cout << "V8Executor::generateExpression: " << - // std::string(_buffer->c_str(), _buffer->length()) << "\n"; - v8::Handle 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(isolate, name), toV8(isolate, it.first)); - } - - TRI_ASSERT(_buffer != nullptr); - - v8::Handle compiled = v8::Script::Compile( - TRI_V8_STD_STRING(isolate, (*_buffer)), TRI_V8_ASCII_STRING(isolate, "--script--")); - - if (!compiled.IsEmpty()) { - v8::Handle func(compiled->Run()); - - // exit early if an error occurred - HandleV8Error(tryCatch, func, _buffer.get(), false); - - // 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::Cast(func), - constantValues, isSimple); - } - else { - v8::Handle empty; - TRI_ASSERT(_buffer != nullptr); - HandleV8Error(tryCatch, empty, _buffer.get(), true); - - // well we're almost sure we never reach this since the above call should throw: - THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "unable to compile AQL script code"); - } -} - -/// @brief executes an expression directly -/// this method is called during AST optimization and will be used to calculate -/// values for constant expressions -int V8Executor::executeExpression(Query* query, AstNode const* node, - VPackBuilder& builder) { - ISOLATE; - - _constantRegisters.clear(); - generateCodeExpression(node); - - // std::cout << "V8Executor::ExecuteExpression: " << - // std::string(_buffer->c_str(), _buffer->length()) << "\n"; - v8::HandleScope scope(isolate); - v8::TryCatch tryCatch; - - TRI_ASSERT(_buffer != nullptr); - - v8::Handle compiled = v8::Script::Compile( - TRI_V8_STD_STRING(isolate, (*_buffer)), TRI_V8_ASCII_STRING(isolate, "--script--")); - - if (!compiled.IsEmpty()) { - - v8::Handle func(compiled->Run()); - - // exit early if an error occurred - HandleV8Error(tryCatch, func, _buffer.get(), false); - - TRI_ASSERT(query != nullptr); - - TRI_GET_GLOBALS(); - v8::Handle result; - auto old = v8g->_query; - - try { - v8g->_query = static_cast(query); - TRI_ASSERT(v8g->_query != nullptr); - - // execute the function - v8::Handle args[] = { v8::Object::New(isolate), v8::Object::New(isolate) }; - result = v8::Handle::Cast(func) - ->Call(v8::Object::New(isolate), 2, args); - - v8g->_query = old; - - // exit if execution raised an error - HandleV8Error(tryCatch, result, _buffer.get(), false); - } 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); - } - else { - v8::Handle empty; - HandleV8Error(tryCatch, empty, _buffer.get(), true); - - // well we're almost sure we never reach this since the above call should throw: - THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "unable to compile AQL script code"); - } -} - -/// @brief traverse the expression and note all user-defined functions -void V8Executor::detectUserFunctions(AstNode const* node) { - if (node == nullptr) { - return; - } - - if (node->type == NODE_TYPE_FCALL_USER) { - _userFunctions.emplace(node->getString(), _userFunctions.size()); - } - - size_t const n = node->numMembers(); - for (size_t i = 0; i < n; ++i) { - detectUserFunctions(node->getMemberUnchecked(i)); - } -} - -/// @brief traverse the expression and note all (big) array/object literals -void V8Executor::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 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(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 value node to a V8 object -v8::Handle V8Executor::toV8(v8::Isolate* isolate, - AstNode const* node) const { - if (node->type == NODE_TYPE_ARRAY) { - size_t const n = node->numMembers(); - - v8::Handle result = v8::Array::New(isolate, static_cast(n)); - for (size_t i = 0; i < n; ++i) { - result->Set(static_cast(i), toV8(isolate, node->getMember(i))); - } - return result; - } - - if (node->type == NODE_TYPE_OBJECT) { - size_t const n = node->numMembers(); - - v8::Handle result = v8::Object::New(isolate); - for (size_t i = 0; i < n; ++i) { - auto sub = node->getMember(i); - result->ForceSet( - TRI_V8_PAIR_STRING(isolate, 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(node->value.value._int)); - case VALUE_TYPE_DOUBLE: - return v8::Number::New(isolate, - static_cast(node->value.value._double)); - case VALUE_TYPE_STRING: - return TRI_V8_PAIR_STRING(isolate, 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 V8Executor::HandleV8Error(v8::TryCatch& tryCatch, - v8::Handle& result, - arangodb::basics::StringBuffer* const buffer, - bool duringCompile) { + v8::Handle& result, + arangodb::basics::StringBuffer* const buffer, + bool duringCompile) { ISOLATE; bool failed = false; @@ -373,671 +127,3 @@ void V8Executor::HandleV8Error(v8::TryCatch& tryCatch, // if we get here, no exception has been raised } - -/// @brief generate JavaScript code for an arbitrary expression -void V8Executor::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 - // the check is only performed if "state.i" (=init) is not yet set - _buffer->appendText(TRI_CHAR_LENGTH_PAIR( - "(function (vars, state, consts) { " - "if (!state.i) { " - "if (_AQL === undefined) { " - "_AQL = require(\"@arangodb/aql\"); } " - "_AQL.clearCaches(); ")); - - // lookup all user-defined functions used and store them in variables - // "state.f\d+" - for (auto const& it : _userFunctions) { - _buffer->appendText(TRI_CHAR_LENGTH_PAIR("state.f")); - _buffer->appendInteger(it.second); - _buffer->appendText(TRI_CHAR_LENGTH_PAIR(" = _AQL.lookupFunction(\"")); - _buffer->appendText(it.first); - _buffer->appendText(TRI_CHAR_LENGTH_PAIR("\", {}); ")); - } - - // generate specialized functions for UDFs - for (auto const& it : _userFunctions) { - _buffer->appendText(TRI_CHAR_LENGTH_PAIR("state.e")); - _buffer->appendInteger(it.second); - // "state.e\d+" executes the user function in a wrapper, converting the - // function result back into the allowed range, and catching any errors - // thrown by the function - _buffer->appendText(TRI_CHAR_LENGTH_PAIR(" = function(params) { try { return _AQL.fixValue(state.f")); - _buffer->appendInteger(it.second); - _buffer->appendText(TRI_CHAR_LENGTH_PAIR(".apply({ name: \"")); - _buffer->appendText(it.first); - _buffer->appendText(TRI_CHAR_LENGTH_PAIR("\" }, params)); } catch (err) { _AQL.throwFromFunction(\"")); - _buffer->appendText(it.first); - _buffer->appendText(TRI_CHAR_LENGTH_PAIR("\", require(\"internal\").errors.ERROR_QUERY_FUNCTION_RUNTIME_ERROR, _AQL.AQL_TO_STRING(err.stack || String(err))); } }; ")); - } - - // set "state.i" to true (=initialized) - _buffer->appendText(TRI_CHAR_LENGTH_PAIR("state.i = true; } return ")); - - generateCodeNode(node); - - // write epilogue - _buffer->appendText(TRI_CHAR_LENGTH_PAIR("; })")); -} - -/// @brief generates code for a string value -void V8Executor::generateCodeString(char const* value, size_t length) { - TRI_ASSERT(value != nullptr); - - _buffer->appendJsonEncoded(value, length); -} - -/// @brief generates code for a string value -void V8Executor::generateCodeString(std::string const& value) { - _buffer->appendJsonEncoded(value.c_str(), value.size()); -} - -/// @brief generate JavaScript code for an array -void V8Executor::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 V8Executor::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 V8Executor::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 V8Executor::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 V8Executor::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 V8Executor::generateCodeUnaryOperator(AstNode const* node) { - TRI_ASSERT(node != nullptr); - TRI_ASSERT(node->numMembers() == 1); - auto functions = AqlFunctionFeature::AQLFUNCTIONS; - TRI_ASSERT(functions != nullptr); - - _buffer->appendText(TRI_CHAR_LENGTH_PAIR("_AQL.")); - _buffer->appendText(functions->getOperatorName(node->type, "unary operator function not found")); - _buffer->appendChar('('); - - generateCodeNode(node->getMember(0)); - _buffer->appendChar(')'); -} - -/// @brief generate JavaScript code for a binary operator -void V8Executor::generateCodeBinaryOperator(AstNode const* node) { - TRI_ASSERT(node != nullptr); - TRI_ASSERT(node->numMembers() == 2); - auto functions = AqlFunctionFeature::AQLFUNCTIONS; - TRI_ASSERT(functions != nullptr); - - 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(functions->getOperatorName(node->type, "binary operator function not found")); - _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 V8Executor::generateCodeBinaryArrayOperator(AstNode const* node) { - TRI_ASSERT(node != nullptr); - TRI_ASSERT(node->numMembers() == 3); - auto functions = AqlFunctionFeature::AQLFUNCTIONS; - TRI_ASSERT(functions != nullptr); - - _buffer->appendText(TRI_CHAR_LENGTH_PAIR("_AQL.")); - _buffer->appendText(functions->getOperatorName(node->type, "binary array function not found")); - _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 V8Executor::generateCodeTernaryOperator(AstNode const* node) { - TRI_ASSERT(node != nullptr); - TRI_ASSERT(node->numMembers() == 3); - auto functions = AqlFunctionFeature::AQLFUNCTIONS; - TRI_ASSERT(functions != nullptr); - - _buffer->appendText(TRI_CHAR_LENGTH_PAIR("_AQL.")); - _buffer->appendText(functions->getOperatorName(node->type, "function not found")); - _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 V8Executor::generateCodeReference(AstNode const* node) { - TRI_ASSERT(node != nullptr); - TRI_ASSERT(node->numMembers() == 0); - - auto variable = static_cast(node->getData()); - - _buffer->appendText(TRI_CHAR_LENGTH_PAIR("vars[")); - generateCodeString(variable->name); - _buffer->appendChar(']'); -} - -/// @brief generate JavaScript code for a variable -void V8Executor::generateCodeVariable(AstNode const* node) { - TRI_ASSERT(node != nullptr); - TRI_ASSERT(node->numMembers() == 0); - - auto variable = static_cast(node->getData()); - - _buffer->appendText(TRI_CHAR_LENGTH_PAIR("vars[")); - generateCodeString(variable->name); - _buffer->appendChar(']'); -} - -/// @brief generate JavaScript code for a full collection access -void V8Executor::generateCodeCollection(AstNode const* node) { - TRI_ASSERT(node != nullptr); - TRI_ASSERT(node->numMembers() == 0); - - // we should not get here anymore, as all collection accesses should - // have either been transformed to collection names (i.e. strings) - // or FOR ... RETURN ... subqueries beforehand - THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "unexpected node type 'collection' found in script generatin"); -} - -/// @brief generate JavaScript code for a full view access -void V8Executor::generateCodeView(AstNode const* node) { - TRI_ASSERT(node != nullptr); - TRI_ASSERT(node->numMembers() == 0); - - _buffer->appendText(TRI_CHAR_LENGTH_PAIR("_AQL.GET_DOCUMENTS_FROM_VIEW(")); - generateCodeString(node->getStringValue(), node->getStringLength()); - _buffer->appendChar(')'); -} - -/// @brief generate JavaScript code for a call to a built-in function -void V8Executor::generateCodeFunctionCall(AstNode const* node) { - TRI_ASSERT(node != nullptr); - TRI_ASSERT(node->numMembers() == 1); - - auto func = static_cast(node->getData()); - - auto args = node->getMember(0); - TRI_ASSERT(args != nullptr); - TRI_ASSERT(args->type == NODE_TYPE_ARRAY); - - if (func->name != "V8") { - // special case for the V8 function... this is actually not a function - // call at all, but a wrapper to ensure that the following expression - // is executed using V8 - _buffer->appendText(TRI_CHAR_LENGTH_PAIR("_AQL.")); - _buffer->appendText(func->v8FunctionName()); - } - _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->name.c_str()); - } else { - generateCodeNode(args->getMember(i)); - } - } - - _buffer->appendChar(')'); -} - -/// @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); - - auto args = node->getMember(0); - TRI_ASSERT(args != nullptr); - TRI_ASSERT(args->type == NODE_TYPE_ARRAY); - - auto it = _userFunctions.find(node->getString()); - - if (it == _userFunctions.end()) { - THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "user function not found"); - } - - _buffer->appendText(TRI_CHAR_LENGTH_PAIR("state.e")); - _buffer->appendInteger((*it).second); - _buffer->appendText(TRI_CHAR_LENGTH_PAIR("(")); - - generateCodeNode(args); - _buffer->appendChar(')'); -} - -/// @brief generate JavaScript code for an expansion (i.e. [*] operator) -void V8Executor::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(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 V8Executor::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 V8Executor::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 V8Executor::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 V8Executor::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 V8Executor::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 V8Executor::generateCodeNode(AstNode const* node) { - TRI_ASSERT(node != nullptr); - - switch (node->type) { - case NODE_TYPE_VALUE: - node->appendValue(_buffer.get()); - 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_VIEW: - generateCodeView(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 -void V8Executor::initializeBuffer() { - if (_buffer == nullptr) { - _buffer.reset(new arangodb::basics::StringBuffer(1024, false)); - - if (_buffer->stringBuffer()->_buffer == nullptr) { - _buffer.reset(); - THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); - } - } else { - _buffer->clear(); - } - - TRI_ASSERT(_buffer != nullptr); -} diff --git a/arangod/Aql/V8Executor.h b/arangod/Aql/V8Executor.h index e4e7b741d1..9914c3a715 100644 --- a/arangod/Aql/V8Executor.h +++ b/arangod/Aql/V8Executor.h @@ -25,151 +25,20 @@ #define ARANGOD_AQL_V8EXECUTOR_H 1 #include "Basics/Common.h" -#include "Aql/AstNode.h" -#include "Aql/Variable.h" -#include "V8/v8-globals.h" + +#include namespace arangodb { namespace basics { class StringBuffer; } -namespace velocypack { -class Builder; -} - namespace aql { -struct AstNode; -struct Function; -class Query; -struct V8Expression; - class V8Executor { public: - /// @brief create the executor - explicit V8Executor(int64_t literalSizeThreshold); - - /// @brief destroy the executor - ~V8Executor(); - - public: - /// @brief generates an expression execution object - V8Expression* generateExpression(AstNode const*); - - /// @brief executes an expression directly - int executeExpression(Query*, AstNode const*, arangodb::velocypack::Builder&); - /// @brief checks if a V8 exception has occurred and throws an appropriate C++ /// exception from it if so static void HandleV8Error(v8::TryCatch&, v8::Handle&, arangodb::basics::StringBuffer*, bool duringCompile); - - private: - /// @brief traverse the expression and note all user-defined functions - void detectUserFunctions(AstNode const*); - - /// @brief traverse the expression and note all (big) array/object literals - void detectConstantValues(AstNode const*, AstNodeType); - - /// @brief convert an AST value node to a V8 object - v8::Handle toV8(v8::Isolate*, AstNode const*) const; - - /// @brief generate JavaScript code for an arbitrary expression - void generateCodeExpression(AstNode const*); - - /// @brief generates code for a string value - void generateCodeString(char const*, size_t); - - /// @brief generates code for a string value - void generateCodeString(std::string const&); - - /// @brief generate JavaScript code for an array - void generateCodeArray(AstNode const*); - - /// @brief generate JavaScript code for a forced array - void generateCodeForcedArray(AstNode const*, int64_t); - - /// @brief generate JavaScript code for an object - void generateCodeObject(AstNode const*); - - /// @brief generate JavaScript code for an object with dynamically named - /// attributes - void generateCodeDynamicObject(AstNode const*); - - /// @brief generate JavaScript code for an object without dynamically named - /// attributes - void generateCodeRegularObject(AstNode const*); - - /// @brief generate JavaScript code for a unary operator - void generateCodeUnaryOperator(AstNode const*); - - /// @brief generate JavaScript code for a binary operator - void generateCodeBinaryOperator(AstNode const*); - - /// @brief generate JavaScript code for a binary array operator - void generateCodeBinaryArrayOperator(AstNode const*); - - /// @brief generate JavaScript code for the ternary operator - void generateCodeTernaryOperator(AstNode const*); - - /// @brief generate JavaScript code for a variable (read) access - void generateCodeReference(AstNode const*); - - /// @brief generate JavaScript code for a variable - void generateCodeVariable(AstNode const*); - - /// @brief generate JavaScript code for a full collection access - void generateCodeCollection(AstNode const*); - - /// @brief generate JavaScript code for a full view access - void generateCodeView(AstNode const*); - - /// @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) - void generateCodeExpansion(AstNode const*); - - /// @brief generate JavaScript code for an expansion iterator - void generateCodeExpansionIterator(AstNode const*); - - /// @brief generate JavaScript code for a range (i.e. 1..10) - void generateCodeRange(AstNode const*); - - /// @brief generate JavaScript code for a named attribute access - void generateCodeNamedAccess(AstNode const*); - - /// @brief generate JavaScript code for a named attribute access - void generateCodeBoundAccess(AstNode const*); - - /// @brief generate JavaScript code for an indexed attribute access - void generateCodeIndexedAccess(AstNode const*); - - /// @brief generate JavaScript code for a node - void generateCodeNode(AstNode const*); - - /// @brief create the string buffer - void initializeBuffer(); - - private: - /// @brief minimum number of array members / object attributes for considering - /// an array / object literal "big" and pulling it out of the expression - static constexpr size_t defaultLiteralSizeThreshold = 32; - - /// @brief a string buffer used for operations - std::unique_ptr _buffer; - - /// @brief mapping from literal array/objects to register ids - std::unordered_map _constantRegisters; - - /// @brief mapping from user-defined function names to register ids - std::unordered_map _userFunctions; - - /// @brief local value for literal object size threshold - size_t const _literalSizeThreshold; - }; } } diff --git a/arangod/Aql/V8Expression.cpp b/arangod/Aql/V8Expression.cpp deleted file mode 100644 index e5680c5d8e..0000000000 --- a/arangod/Aql/V8Expression.cpp +++ /dev/null @@ -1,172 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -/// 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 "V8Expression.h" -#include "Aql/AqlItemBlock.h" -#include "Aql/ExpressionContext.h" -#include "Aql/Query.h" -#include "Aql/V8Executor.h" -#include "Aql/Variable.h" -#include "Basics/VelocyPackHelper.h" -#include "V8/v8-conv.h" -#include "V8/v8-utils.h" -#include "V8/v8-vpack.h" - -#include -#include - -using namespace arangodb; -using namespace arangodb::aql; - -/// @brief create the v8 expression -V8Expression::V8Expression(v8::Isolate* isolate, v8::Handle func, - v8::Handle constantValues, bool isSimple) - : isolate(isolate), - _func(), - _state(), - _constantValues(), - _isSimple(isSimple) { - _func.Reset(isolate, func); - _state.Reset(isolate, v8::Object::New(isolate)); - _constantValues.Reset(isolate, constantValues); -} - -/// @brief destroy the v8 expression -V8Expression::~V8Expression() { - _constantValues.Reset(); - _state.Reset(); - _func.Reset(); -} - -/// @brief execute the expression -AqlValue V8Expression::execute(v8::Isolate* isolate, Query* query, - transaction::Methods* trx, - ExpressionContext* context, - bool& mustDestroy) { - bool const hasRestrictions = !_attributeRestrictions.empty(); - - v8::Handle values = v8::Object::New(isolate); - - size_t const n = context->numRegisters(); - - for (size_t i = 0; i < n; ++i) { - AqlValue const& value = context->getRegisterValue(i); - - if (value.isEmpty()) { - continue; - } - - Variable const* var = context->getVariable(i); - std::string const& varname = var->name; - - if (hasRestrictions && value.isObject()) { - // check if we can get away with constructing a partial JSON object - auto it = _attributeRestrictions.find(var); - - if (it != _attributeRestrictions.end()) { - // build a partial object - values->ForceSet( - TRI_V8_STD_STRING(isolate, varname), - value.toV8Partial(isolate, trx, (*it).second)); - continue; - } - } - - // fallthrough to building the complete object - - // build the regular object - values->ForceSet(TRI_V8_STD_STRING(isolate, varname), - value.toV8(isolate, trx)); - } - - TRI_ASSERT(query != nullptr); - - TRI_GET_GLOBALS(); - - v8::Handle result; - - auto old = v8g->_query; - - try { - v8g->_query = static_cast(query); - TRI_ASSERT(v8g->_query != nullptr); - - auto state = v8::Local::New(isolate, _state); - - // set constant function arguments - // note: constants are passed by reference so we can save re-creating them - // on every invocation. this however means that these constants must not be - // modified by the called function. there is a hash check in place below to - // verify that constants don't get modified by the called function. - // note: user-defined AQL functions are always called without constants - // because they are opaque to the optimizer and the assumption that they - // won't modify their arguments is unsafe - auto constantValues = v8::Local::New(isolate, _constantValues); - - v8::Handle args[] = { values, state, constantValues }; - - // execute the function - v8::TryCatch tryCatch; - - auto func = v8::Local::New(isolate, _func); - result = func->Call(func, 3, args); - - v8g->_query = old; - - V8Executor::HandleV8Error(tryCatch, result, nullptr, false); - } catch (...) { - v8g->_query = old; - // bubble up exception - throw; - } - - // no exception was thrown if we get here - - if (result->IsUndefined()) { - // expression does not have any (defined) value. replace with null - mustDestroy = false; - return AqlValue(AqlValueHintNull()); - } - - // expression had a result. convert it to JSON - if (_builder == nullptr) { - _builder.reset(new VPackBuilder); - } else { - _builder->clear(); - } - - int res; - if (_isSimple) { - res = TRI_V8ToVPackSimple(isolate, *_builder.get(), result); - } else { - res = TRI_V8ToVPack(isolate, *_builder.get(), result, false); - } - - if (res != TRI_ERROR_NO_ERROR) { - THROW_ARANGO_EXCEPTION(res); - } - - mustDestroy = true; // builder = dynamic data - return AqlValue(_builder.get()); -} - diff --git a/arangod/Aql/V8Expression.h b/arangod/Aql/V8Expression.h deleted file mode 100644 index 1cbe734caf..0000000000 --- a/arangod/Aql/V8Expression.h +++ /dev/null @@ -1,96 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -/// 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 -//////////////////////////////////////////////////////////////////////////////// - -#ifndef ARANGOD_AQL_V8_EXPRESSION_H -#define ARANGOD_AQL_V8_EXPRESSION_H 1 - -#include "Basics/Common.h" -#include "Aql/AqlValue.h" -#include "Aql/types.h" - -#include - -namespace arangodb { -namespace velocypack { -class Builder; -} - -namespace aql { - -class AqlItemBlock; -class ExpressionContext; -class Query; -struct Variable; - -struct V8Expression { - - /// @brief create the v8 expression - V8Expression(v8::Isolate*, v8::Handle, v8::Handle, - bool); - - /// @brief destroy the v8 expression - ~V8Expression(); - - /// @brief sets attribute restrictions. these prevent input variables to be - /// fully constructed as V8 objects (which can be very expensive), but limits - /// the objects to the actually used attributes only. - /// For example, the expression LET x = a.value + 1 will not build the full - /// object for "a", but only its "value" attribute - void setAttributeRestrictions(std::unordered_map< - Variable const*, std::unordered_set> const& - attributeRestrictions) { - _attributeRestrictions = attributeRestrictions; - } - - /// @brief execute the expression - AqlValue execute(v8::Isolate* isolate, Query* query, - transaction::Methods*, ExpressionContext* context, bool& mustDestroy); - - /// @brief the isolate used when executing and destroying the expression - v8::Isolate* isolate; - - /// @brief the compiled expression as a V8 function - v8::Persistent _func; - - /// @brief setup state - v8::Persistent _state; - - /// @brief constants - v8::Persistent _constantValues; - - /// @brief a Builder object, shared across calls - std::unique_ptr _builder; - - /// @brief restrictions for creating the input values - std::unordered_map> - _attributeRestrictions; - - /// @brief whether or not the expression is simple. simple in this case means - /// that the expression result will always contain non-cyclic data and no - /// special JavaScript types such as Date, RegExp, Function etc. - bool const _isSimple; -}; -} -} - -#endif diff --git a/arangod/CMakeLists.txt b/arangod/CMakeLists.txt index 52d7cbd210..d0262ec584 100644 --- a/arangod/CMakeLists.txt +++ b/arangod/CMakeLists.txt @@ -241,7 +241,6 @@ SET(ARANGOD_SOURCES Aql/TraversalConditionFinder.cpp Aql/TraversalNode.cpp Aql/V8Executor.cpp - Aql/V8Expression.cpp Aql/Variable.cpp Aql/VariableGenerator.cpp Aql/grammar.cpp diff --git a/arangod/Graph/BaseOptions.cpp b/arangod/Graph/BaseOptions.cpp index c25b6387df..da70a5f6b7 100644 --- a/arangod/Graph/BaseOptions.cpp +++ b/arangod/Graph/BaseOptions.cpp @@ -368,7 +368,6 @@ bool BaseOptions::evaluateExpression(arangodb::aql::Expression* expression, return true; } - TRI_ASSERT(!expression->isV8()); TRI_ASSERT(value.isObject() || value.isNull()); expression->setVariable(_tmpVar, value); bool mustDestroy = false; diff --git a/arangod/VocBase/Methods/AqlUserFunctions.cpp b/arangod/VocBase/Methods/AqlUserFunctions.cpp index 3696768b35..05037f616f 100644 --- a/arangod/VocBase/Methods/AqlUserFunctions.cpp +++ b/arangod/VocBase/Methods/AqlUserFunctions.cpp @@ -32,6 +32,7 @@ #include "Transaction/Methods.h" #include "Transaction/Helpers.h" #include "Transaction/StandaloneContext.h" +#include "Transaction/V8Context.h" #include "Utils/OperationOptions.h" #include "Utils/SingleCollectionTransaction.h" #include "V8/v8-globals.h" @@ -76,12 +77,7 @@ Result arangodb::unregisterUserFunction(TRI_vocbase_t* vocbase, "' contains invalid characters"); } - std::string aql("RETURN LENGTH( " - " FOR fn IN @@col" - " FILTER fn._key == @fnName" - " REMOVE { _key: fn._key } in @@col RETURN 1)"); - - + std::string aql("FOR fn IN @@col FILTER fn._key == @fnName REMOVE { _key: fn._key } in @@col RETURN 1"); std::string UCFN = basics::StringUtils::toupper(functionName); auto binds = std::make_shared(); @@ -90,28 +86,31 @@ Result arangodb::unregisterUserFunction(TRI_vocbase_t* vocbase, binds->add("@col", VPackValue(collectionName)); binds->close(); // obj - arangodb::aql::Query query(false, vocbase, arangodb::aql::QueryString(aql), - binds, nullptr, arangodb::aql::PART_MAIN); + { + bool const contextOwnedByExterior = (v8::Isolate::GetCurrent() != nullptr); + arangodb::aql::Query query(contextOwnedByExterior, vocbase, arangodb::aql::QueryString(aql), + binds, nullptr, arangodb::aql::PART_MAIN); - auto queryRegistry = QueryRegistryFeature::QUERY_REGISTRY; - auto queryResult = query.execute(queryRegistry); + auto queryRegistry = QueryRegistryFeature::QUERY_REGISTRY; + auto queryResult = query.execute(queryRegistry); - if (queryResult.code != TRI_ERROR_NO_ERROR) { - if (queryResult.code == TRI_ERROR_REQUEST_CANCELED || - (queryResult.code == TRI_ERROR_QUERY_KILLED)) { - return Result(TRI_ERROR_REQUEST_CANCELED); + if (queryResult.code != TRI_ERROR_NO_ERROR) { + if (queryResult.code == TRI_ERROR_REQUEST_CANCELED || + (queryResult.code == TRI_ERROR_QUERY_KILLED)) { + return Result(TRI_ERROR_REQUEST_CANCELED); + } + return Result(queryResult.code, "error group-deleting user defined AQL"); } - return Result(queryResult.code, "error group-deleting user defined AQL"); - } - VPackSlice countSlice = queryResult.result->slice(); - if (!countSlice.isArray() || (countSlice.length() != 1)) { - return Result(TRI_ERROR_INTERNAL, "bad query result for deleting AQL user functions"); - } + VPackSlice countSlice = queryResult.result->slice(); + if (!countSlice.isArray()) { + return Result(TRI_ERROR_INTERNAL, "bad query result for deleting AQL user functions"); + } - if (countSlice[0].getNumericValue() != 1) { - return Result(TRI_ERROR_QUERY_FUNCTION_NOT_FOUND, - std::string("no AQL user function with name '") + functionName + "' found"); + if (countSlice.length() != 1) { + return Result(TRI_ERROR_QUERY_FUNCTION_NOT_FOUND, + std::string("no AQL user function with name '") + functionName + "' found"); + } } reloadAqlUserFunctions(); @@ -147,40 +146,42 @@ Result arangodb::unregisterUserFunctionsGroup(TRI_vocbase_t* vocbase, binds->add("@col", VPackValue(collectionName)); binds->close(); - std::string aql("RETURN LENGTH(" - " FOR fn IN @@col" - " FILTER UPPER(LEFT(fn.name, @fnLength)) == @ucName" - " REMOVE { _key: fn._key} in @@col RETURN 1)"); + std::string aql("FOR fn IN @@col FILTER UPPER(LEFT(fn.name, @fnLength)) == @ucName REMOVE { _key: fn._key} in @@col RETURN 1"); - arangodb::aql::Query query(false, vocbase, arangodb::aql::QueryString(aql), - binds, nullptr, arangodb::aql::PART_MAIN); + { + bool const contextOwnedByExterior = (v8::Isolate::GetCurrent() != nullptr); + arangodb::aql::Query query(contextOwnedByExterior, vocbase, arangodb::aql::QueryString(aql), + binds, nullptr, arangodb::aql::PART_MAIN); - auto queryRegistry = QueryRegistryFeature::QUERY_REGISTRY; - auto queryResult = query.execute(queryRegistry); + auto queryRegistry = QueryRegistryFeature::QUERY_REGISTRY; + auto queryResult = query.execute(queryRegistry); - if (queryResult.code != TRI_ERROR_NO_ERROR) { - if (queryResult.code == TRI_ERROR_REQUEST_CANCELED || - (queryResult.code == TRI_ERROR_QUERY_KILLED)) { - return Result(TRI_ERROR_REQUEST_CANCELED); + if (queryResult.code != TRI_ERROR_NO_ERROR) { + if (queryResult.code == TRI_ERROR_REQUEST_CANCELED || + (queryResult.code == TRI_ERROR_QUERY_KILLED)) { + return Result(TRI_ERROR_REQUEST_CANCELED); + } + return Result(queryResult.code, + std::string("Error group-deleting AQL user functions")); } - return Result(queryResult.code, - std::string("Error group-deleting AQL user functions")); + + VPackSlice countSlice = queryResult.result->slice(); + if (!countSlice.isArray()) { + return Result(TRI_ERROR_INTERNAL, "bad query result for deleting AQL user functions"); + } + + deleteCount = static_cast(countSlice.length()); } - VPackSlice countSlice = queryResult.result->slice(); - if (!countSlice.isArray() || (countSlice.length() != 1)) { - return Result(TRI_ERROR_INTERNAL, "bad query result for deleting AQL user functions"); - } - - deleteCount = countSlice[0].getNumericValue(); reloadAqlUserFunctions(); return Result(); } Result arangodb::registerUserFunction(TRI_vocbase_t* vocbase, velocypack::Slice userFunction, - bool& replacedExisting - ) { + bool& replacedExisting) { + replacedExisting = false; + Result res; std::string name; try{ @@ -223,7 +224,7 @@ Result arangodb::registerUserFunction(TRI_vocbase_t* vocbase, ISOLATE; bool throwV8Exception = (isolate != nullptr); V8ContextDealerGuard dealerGuard(res, isolate, vocbase, true /*allowModification*/); - if(res.fail()){ + if (res.fail()) { return res; } @@ -287,32 +288,32 @@ Result arangodb::registerUserFunction(TRI_vocbase_t* vocbase, oneFunctionDocument.add("isDeterministic", VPackValue(isDeterministic)); oneFunctionDocument.close(); - arangodb::OperationOptions opOptions; - opOptions.isRestore = false; - opOptions.waitForSync = true; - opOptions.silent = false; + { + arangodb::OperationOptions opOptions; + opOptions.waitForSync = true; - // find and load collection given by name or identifier - auto ctx = transaction::StandaloneContext::Create(vocbase); - SingleCollectionTransaction trx(ctx, collectionName, AccessMode::Type::WRITE); + // find and load collection given by name or identifier + auto ctx = transaction::V8Context::CreateWhenRequired(vocbase, true); + SingleCollectionTransaction trx(ctx, collectionName, AccessMode::Type::WRITE); - res = trx.begin(); - if (!res.ok()) { - return res; + res = trx.begin(); + if (!res.ok()) { + return res; + } + + arangodb::OperationResult result; + result = trx.insert(collectionName, oneFunctionDocument.slice(), opOptions); + + if (result.result.is(TRI_ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED)) { + replacedExisting = true; + result = trx.replace(collectionName, oneFunctionDocument.slice(), opOptions); + } + // Will commit if no error occured. + // or abort if an error occured. + // result stays valid! + res = trx.finish(result.result); } - arangodb::OperationResult result; - result = trx.insert(collectionName, oneFunctionDocument.slice(), opOptions); - - if (result.result.is(TRI_ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED)) { - replacedExisting = true; - result = trx.replace(collectionName, oneFunctionDocument.slice(), opOptions); - } - // Will commit if no error occured. - // or abort if an error occured. - // result stays valid! - res = trx.finish(result.result); - if (res.ok()) { reloadAqlUserFunctions(); } @@ -344,7 +345,8 @@ Result arangodb::toArrayUserFunctions(TRI_vocbase_t* vocbase, binds->add("@col", VPackValue(collectionName)); binds->close(); - arangodb::aql::Query query(false, vocbase, arangodb::aql::QueryString(aql), + bool const contextOwnedByExterior = (v8::Isolate::GetCurrent() != nullptr); + arangodb::aql::Query query(contextOwnedByExterior, vocbase, arangodb::aql::QueryString(aql), binds, nullptr, arangodb::aql::PART_MAIN); auto queryRegistry = QueryRegistryFeature::QUERY_REGISTRY; diff --git a/js/server/modules/@arangodb/aql.js b/js/server/modules/@arangodb/aql.js index 3738245219..74ac14e589 100644 --- a/js/server/modules/@arangodb/aql.js +++ b/js/server/modules/@arangodb/aql.js @@ -840,7 +840,7 @@ function FCALL_USER (name, parameters) { try { return FIX_VALUE(UserFunctions[prefix][name].func.apply({ name: name }, parameters)); } catch (err) { - WARN(name, INTERNAL.errors.ERROR_QUERY_FUNCTION_RUNTIME_ERROR, AQL_TO_STRING(err.stack || String(err))); + THROW(name, INTERNAL.errors.ERROR_QUERY_FUNCTION_RUNTIME_ERROR, AQL_TO_STRING(err.stack || String(err))); return null; } } @@ -5639,7 +5639,6 @@ function AQL_WARN (expression, message) { return true; } - exports.FCALL_USER = FCALL_USER; exports.KEYS = KEYS; exports.GET_INDEX = GET_INDEX; diff --git a/js/server/tests/aql/aql-dynamic-attributes.js b/js/server/tests/aql/aql-dynamic-attributes.js index 5237a96e3f..393592c618 100644 --- a/js/server/tests/aql/aql-dynamic-attributes.js +++ b/js/server/tests/aql/aql-dynamic-attributes.js @@ -41,7 +41,7 @@ function ahuacatlDynamicAttributesTestSuite () { q = "RETURN NOOPT(V8(" + query + "))"; assertEqual(expected, AQL_EXECUTE(q).json[0]); - assertEqual("v8", AQL_EXPLAIN(q).plan.nodes[1].expressionType); + assertEqual("simple", AQL_EXPLAIN(q).plan.nodes[1].expressionType); q = "RETURN NOOPT(" + query + ")"; assertEqual(expected, AQL_EXECUTE(q).json[0]); diff --git a/js/server/tests/aql/aql-failures-noncluster.js b/js/server/tests/aql/aql-failures-noncluster.js index 4474cc5fd3..ab0b054fce 100644 --- a/js/server/tests/aql/aql-failures-noncluster.js +++ b/js/server/tests/aql/aql-failures-noncluster.js @@ -55,7 +55,7 @@ function ahuacatlFailureSuite () { fail(); } catch (err) { - assertEqual(internal.errors.ERROR_DEBUG.code, err.errorNum); + assertEqual(internal.errors.ERROR_DEBUG.code, err.errorNum, query); } }; @@ -478,13 +478,6 @@ function ahuacatlFailureSuite () { }, testIndexBlock6 : function () { - c.ensureHashIndex("value"); - internal.debugSetFailAt("IndexBlock::executeV8"); - // DATE_NOW is an arbitrary v8 function and can be replaced - assertFailingQuery("FOR i IN " + c.name() + " FILTER i.value == NOOPT(PASSTHRU(DATE_NOW())) RETURN i"); - }, - - testIndexBlock7 : function () { c.ensureHashIndex("value"); internal.debugSetFailAt("IndexBlock::executeExpression"); // CONCAT is an arbitrary non v8 function and can be replaced diff --git a/js/server/tests/aql/aql-optimizer-rule-remove-unnecessary-calculations.js b/js/server/tests/aql/aql-optimizer-rule-remove-unnecessary-calculations.js index 228100369c..e445ff6948 100644 --- a/js/server/tests/aql/aql-optimizer-rule-remove-unnecessary-calculations.js +++ b/js/server/tests/aql/aql-optimizer-rule-remove-unnecessary-calculations.js @@ -168,10 +168,6 @@ function optimizerRuleTestSuite () { "FOR i IN [1] LET a = CONCAT('a', i) RETURN CONCAT(a, a)", "FOR i IN [1] LET a = i * 2 COLLECT y = a INTO g RETURN [ y * 2, g ]", - // v8 vs. non-v8 expression types - "FOR doc IN [ { a: 1 }, { a: 2 } ] LET a = V8(ATTRIBUTES(doc)) RETURN KEEP(doc, a)", - "FOR doc IN [ { a: 1 }, { a: 2 } ] LET a = ATTRIBUTES(doc) RETURN V8(KEEP(doc, a))", - // different loop "LET a = NOOPT(CONCAT('a', 'b')) FOR i IN [ 1, 2, 3 ] RETURN CONCAT(a, 'b')" ]; diff --git a/js/server/tests/aql/aql-optimizer-rule-sort-in-values.js b/js/server/tests/aql/aql-optimizer-rule-sort-in-values.js index a1d0009cbf..9ef75b9280 100644 --- a/js/server/tests/aql/aql-optimizer-rule-sort-in-values.js +++ b/js/server/tests/aql/aql-optimizer-rule-sort-in-values.js @@ -88,9 +88,6 @@ function optimizerRuleTestSuite () { var queryList = [ "LET values = NOOPT(SPLIT('foo,bar,foobar,qux', ',')) FOR i IN [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] FILTER i.a == 'foobar' && i.a IN values RETURN i", "LET values = NOOPT(SPLIT('foo,bar,foobar,qux', ',')) FOR i IN [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] FILTER i.a == 'foobar' || i.a IN values RETURN i", - "LET values = SPLIT('foo,bar,foobar,qux', ',') FOR i IN [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] FILTER i.a IN values RETURN i", - "LET values = SPLIT('foo,bar,foobar,qux', ',') FOR i IN [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] FILTER i.a NOT IN values RETURN i", - "FOR i IN [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] FILTER i.a IN SPLIT('foo,bar,foobar,qux', ',') RETURN i", "FOR i IN [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] FILTER i.a NOT IN SPLIT('foo,bar,foobar,qux', ',') RETURN i", "LET values = RANGE(1, 100) FOR i IN 1..100 FILTER i IN values RETURN i", "FOR i IN 1..100 FILTER i IN RANGE(1, 100) RETURN i", @@ -99,7 +96,8 @@ function optimizerRuleTestSuite () { "LET values = NOOPT([ 1, 2 ]) FOR i IN 1..100 FILTER i IN values RETURN i", "LET values = NOOPT([ 1, 2, 3 ]) FOR i IN 1..100 FILTER i IN values RETURN i", "LET values = NOOPT({ }) FOR i IN 1..100 FILTER i IN values RETURN i", - "LET values = NOOPT('foobar') FOR i IN 1..100 FILTER i IN values RETURN i" + "LET values = NOOPT('foobar') FOR i IN 1..100 FILTER i IN values RETURN i", + "FOR i IN [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] FILTER i.a IN SPLIT('foo,bar,foobar,qux', ',') RETURN i" ]; queryList.forEach(function(query) { @@ -128,7 +126,9 @@ function optimizerRuleTestSuite () { "LET values = NOOPT(SPLIT('foo,bar,foobar,qux', ',')) FOR i IN [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] FILTER i.a NOT IN values RETURN i", "LET values = NOOPT(SPLIT('foo,bar,foobar,qux', ',')) FOR i IN [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] FILTER LENGTH(i.a) >= 3 FILTER i.a IN values RETURN i", "LET values = NOOPT(RANGE(1, 100)) FOR i IN 1..100 FILTER i IN values RETURN i", - "LET values = NOOPT([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]) FOR i IN 1..100 FILTER i IN values RETURN i" + "LET values = NOOPT([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]) FOR i IN 1..100 FILTER i IN values RETURN i", + "LET values = NOOPT(SPLIT('foo,bar,foobar,qux', ',')) FOR i IN [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] FILTER i.a IN values RETURN i", + "LET values = NOOPT(SPLIT('foo,bar,foobar,qux', ',')) FOR i IN [ { a: 'foo' }, { a: 'bar' }, { a: 'baz' } ] FILTER i.a NOT IN values RETURN i", ]; queries.forEach(function(query) {