mirror of https://gitee.com/bigwinds/arangodb
Bug fix/remove v8 executor (#4756)
This commit is contained in:
parent
4b666de5c0
commit
67e7e4181e
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -47,13 +47,6 @@
|
|||
#include <velocypack/velocypack-aliases.h>
|
||||
#include <array>
|
||||
|
||||
namespace {
|
||||
|
||||
arangodb::StringRef const VIEW_NODE_SUFFIX("\0v", 2);
|
||||
arangodb::StringRef const COLLECTION_NODE_SUFFIX("\0c", 2);
|
||||
|
||||
}
|
||||
|
||||
using namespace arangodb::aql;
|
||||
|
||||
std::unordered_map<int, std::string const> const AstNode::Operators{
|
||||
|
@ -1542,14 +1535,6 @@ bool AstNode::isSimple() const {
|
|||
auto func = static_cast<Function*>(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<Function*>(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)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 <velocypack/Builder.h>
|
||||
#include <velocypack/Iterator.h>
|
||||
#include <velocypack/Slice.h>
|
||||
#include <velocypack/velocypack-aliases.h>
|
||||
|
||||
#include <v8.h>
|
||||
|
||||
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<AstNode*>(_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<size_t>(builder->size())];
|
||||
memcpy(_data, builder->data(), static_cast<size_t>(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<Function*>(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<Function*>(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<v8::Handle<v8::Value>[]>(callArgs);
|
||||
|
||||
if (node->type == NODE_TYPE_FCALL_USER) {
|
||||
// a call to a user-defined function
|
||||
jsName = "FCALL_USER";
|
||||
v8::Handle<v8::Array> params = v8::Array::New(isolate, static_cast<int>(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<uint32_t>(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<Function*>(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<TRI_v8_global_t*>(isolate->GetData(arangodb::V8PlatformFeature::V8_DATA_SLOT));
|
||||
auto old = v8g->_query;
|
||||
v8g->_query = static_cast<void*>(_ast->query());
|
||||
TRI_DEFER(v8g->_query = old);
|
||||
|
||||
auto current = isolate->GetCurrentContext()->Global();
|
||||
|
||||
v8::Handle<v8::Value> 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<v8::Value> function = v8::Handle<v8::Object>::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<v8::Value> result = v8::Handle<v8::Function>::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<v8::Script> compiled = v8::Script::Compile(
|
||||
TRI_V8_STD_STRING(isolate, body), TRI_V8_ASCII_STRING(isolate, "--script--"));
|
||||
|
||||
if (!compiled.IsEmpty()) {
|
||||
v8::Handle<v8::Value> func(compiled->Run());
|
||||
_preparedV8Context = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<std::string> 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -179,7 +179,7 @@ int IndexBlock::initialize() {
|
|||
THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
|
||||
}
|
||||
|
||||
_hasV8Expression |= e->isV8();
|
||||
_hasV8Expression |= e->willUseV8();
|
||||
|
||||
std::unordered_set<Variable const*> inVars;
|
||||
e->variables(inVars);
|
||||
|
|
|
@ -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<TRI_voc_rid_t> _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<ManagedDocumentResult> _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;
|
||||
|
||||
|
|
|
@ -126,7 +126,7 @@ void arangodb::aql::sortInValuesRule(Optimizer* opt,
|
|||
// expression
|
||||
auto s = static_cast<CalculationNode*>(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<Variable const*>(rhs->getData());
|
||||
setter = plan->getVarSetBy(variable->id);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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> _v8Executor;
|
||||
|
||||
/// @brief the currently used V8 context
|
||||
V8Context* _context;
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 <velocypack/Builder.h>
|
||||
#include <velocypack/Slice.h>
|
||||
#include <velocypack/velocypack-aliases.h>
|
||||
|
||||
using namespace arangodb::aql;
|
||||
|
||||
/// @brief creates an executor
|
||||
V8Executor::V8Executor(int64_t literalSizeThreshold)
|
||||
: _constantRegisters(),
|
||||
_literalSizeThreshold(literalSizeThreshold >= 0
|
||||
? static_cast<size_t>(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<v8::Object> constantValues = v8::Object::New(isolate);
|
||||
for (auto const& it : _constantRegisters) {
|
||||
std::string name = "r";
|
||||
name.append(std::to_string(it.second));
|
||||
|
||||
constantValues->ForceSet(TRI_V8_STD_STRING(isolate, name), toV8(isolate, it.first));
|
||||
}
|
||||
|
||||
TRI_ASSERT(_buffer != nullptr);
|
||||
|
||||
v8::Handle<v8::Script> compiled = v8::Script::Compile(
|
||||
TRI_V8_STD_STRING(isolate, (*_buffer)), TRI_V8_ASCII_STRING(isolate, "--script--"));
|
||||
|
||||
if (!compiled.IsEmpty()) {
|
||||
v8::Handle<v8::Value> 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<v8::Function>::Cast(func),
|
||||
constantValues, isSimple);
|
||||
}
|
||||
else {
|
||||
v8::Handle<v8::Value> 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<v8::Script> compiled = v8::Script::Compile(
|
||||
TRI_V8_STD_STRING(isolate, (*_buffer)), TRI_V8_ASCII_STRING(isolate, "--script--"));
|
||||
|
||||
if (!compiled.IsEmpty()) {
|
||||
|
||||
v8::Handle<v8::Value> func(compiled->Run());
|
||||
|
||||
// exit early if an error occurred
|
||||
HandleV8Error(tryCatch, func, _buffer.get(), false);
|
||||
|
||||
TRI_ASSERT(query != nullptr);
|
||||
|
||||
TRI_GET_GLOBALS();
|
||||
v8::Handle<v8::Value> result;
|
||||
auto old = v8g->_query;
|
||||
|
||||
try {
|
||||
v8g->_query = static_cast<void*>(query);
|
||||
TRI_ASSERT(v8g->_query != nullptr);
|
||||
|
||||
// execute the function
|
||||
v8::Handle<v8::Value> args[] = { v8::Object::New(isolate), v8::Object::New(isolate) };
|
||||
result = v8::Handle<v8::Function>::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<v8::Value> 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<Function*>(node->getData());
|
||||
|
||||
if (!func->canPassArgumentsByReference) {
|
||||
// function should not retrieve its arguments by reference,
|
||||
// so we pretend here that it is a user-defined function
|
||||
// (user-defined functions will not get their arguments by
|
||||
// reference)
|
||||
nextType = NODE_TYPE_FCALL_USER;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
detectConstantValues(node->getMemberUnchecked(i), nextType);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief convert an AST value node to a V8 object
|
||||
v8::Handle<v8::Value> V8Executor::toV8(v8::Isolate* isolate,
|
||||
AstNode const* node) const {
|
||||
if (node->type == NODE_TYPE_ARRAY) {
|
||||
size_t const n = node->numMembers();
|
||||
|
||||
v8::Handle<v8::Array> result = v8::Array::New(isolate, static_cast<int>(n));
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
result->Set(static_cast<uint32_t>(i), toV8(isolate, node->getMember(i)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (node->type == NODE_TYPE_OBJECT) {
|
||||
size_t const n = node->numMembers();
|
||||
|
||||
v8::Handle<v8::Object> result = v8::Object::New(isolate);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
auto sub = node->getMember(i);
|
||||
result->ForceSet(
|
||||
TRI_V8_PAIR_STRING(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<double>(node->value.value._int));
|
||||
case VALUE_TYPE_DOUBLE:
|
||||
return v8::Number::New(isolate,
|
||||
static_cast<double>(node->value.value._double));
|
||||
case VALUE_TYPE_STRING:
|
||||
return TRI_V8_PAIR_STRING(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<v8::Value>& result,
|
||||
arangodb::basics::StringBuffer* const buffer,
|
||||
bool duringCompile) {
|
||||
v8::Handle<v8::Value>& 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<Variable*>(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<Variable*>(node->getData());
|
||||
|
||||
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("vars["));
|
||||
generateCodeString(variable->name);
|
||||
_buffer->appendChar(']');
|
||||
}
|
||||
|
||||
/// @brief generate JavaScript code for a full collection access
|
||||
void 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<Function*>(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<Variable*>(iterator->getMember(0)->getData());
|
||||
|
||||
// start LIMIT
|
||||
auto limitNode = node->getMember(3);
|
||||
|
||||
if (limitNode->type != NODE_TYPE_NOP) {
|
||||
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("_AQL.AQL_SLICE("));
|
||||
}
|
||||
|
||||
generateCodeForcedArray(node->getMember(0), levels);
|
||||
|
||||
// FILTER
|
||||
auto filterNode = node->getMember(2);
|
||||
|
||||
if (filterNode->type != NODE_TYPE_NOP) {
|
||||
_buffer->appendText(TRI_CHAR_LENGTH_PAIR(".filter(function (v) { "));
|
||||
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("vars[\""));
|
||||
_buffer->appendText(variable->name);
|
||||
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("\"]=v; "));
|
||||
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("return _AQL.AQL_TO_BOOL("));
|
||||
generateCodeNode(filterNode);
|
||||
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("); })"));
|
||||
}
|
||||
|
||||
// finish LIMIT
|
||||
if (limitNode->type != NODE_TYPE_NOP) {
|
||||
_buffer->appendChar(',');
|
||||
generateCodeNode(limitNode->getMember(0));
|
||||
_buffer->appendChar(',');
|
||||
generateCodeNode(limitNode->getMember(1));
|
||||
_buffer->appendText(TRI_CHAR_LENGTH_PAIR(",true)"));
|
||||
}
|
||||
|
||||
// RETURN
|
||||
_buffer->appendText(TRI_CHAR_LENGTH_PAIR(".map(function (v) { "));
|
||||
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("vars[\""));
|
||||
_buffer->appendText(variable->name);
|
||||
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("\"]=v; "));
|
||||
|
||||
size_t projectionNode = 1;
|
||||
if (node->getMember(4)->type != NODE_TYPE_NOP) {
|
||||
projectionNode = 4;
|
||||
}
|
||||
|
||||
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("return "));
|
||||
generateCodeNode(node->getMember(projectionNode));
|
||||
_buffer->appendText(TRI_CHAR_LENGTH_PAIR("; })"));
|
||||
}
|
||||
|
||||
/// @brief generate JavaScript code for an expansion iterator
|
||||
void 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);
|
||||
}
|
||||
|
|
|
@ -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 <v8.h>
|
||||
|
||||
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<v8::Value>&, 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<v8::Value> 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<arangodb::basics::StringBuffer> _buffer;
|
||||
|
||||
/// @brief mapping from literal array/objects to register ids
|
||||
std::unordered_map<AstNode const*, size_t> _constantRegisters;
|
||||
|
||||
/// @brief mapping from user-defined function names to register ids
|
||||
std::unordered_map<std::string, size_t> _userFunctions;
|
||||
|
||||
/// @brief local value for literal object size threshold
|
||||
size_t const _literalSizeThreshold;
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <velocypack/Builder.h>
|
||||
#include <velocypack/velocypack-aliases.h>
|
||||
|
||||
using namespace arangodb;
|
||||
using namespace arangodb::aql;
|
||||
|
||||
/// @brief create the v8 expression
|
||||
V8Expression::V8Expression(v8::Isolate* isolate, v8::Handle<v8::Function> func,
|
||||
v8::Handle<v8::Object> 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<v8::Object> 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<v8::Value> result;
|
||||
|
||||
auto old = v8g->_query;
|
||||
|
||||
try {
|
||||
v8g->_query = static_cast<void*>(query);
|
||||
TRI_ASSERT(v8g->_query != nullptr);
|
||||
|
||||
auto state = v8::Local<v8::Object>::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<v8::Object>::New(isolate, _constantValues);
|
||||
|
||||
v8::Handle<v8::Value> args[] = { values, state, constantValues };
|
||||
|
||||
// execute the function
|
||||
v8::TryCatch tryCatch;
|
||||
|
||||
auto func = v8::Local<v8::Function>::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());
|
||||
}
|
||||
|
|
@ -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 <v8.h>
|
||||
|
||||
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::Function>, v8::Handle<v8::Object>,
|
||||
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<std::string>> 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<v8::Function> _func;
|
||||
|
||||
/// @brief setup state
|
||||
v8::Persistent<v8::Object> _state;
|
||||
|
||||
/// @brief constants
|
||||
v8::Persistent<v8::Object> _constantValues;
|
||||
|
||||
/// @brief a Builder object, shared across calls
|
||||
std::unique_ptr<arangodb::velocypack::Builder> _builder;
|
||||
|
||||
/// @brief restrictions for creating the input values
|
||||
std::unordered_map<Variable const*, std::unordered_set<std::string>>
|
||||
_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
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<VPackBuilder>();
|
||||
|
@ -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<int>() != 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<int>(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<int>();
|
||||
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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')"
|
||||
];
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue