mirror of https://gitee.com/bigwinds/arangodb
1044 lines
34 KiB
C++
1044 lines
34 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
|
/// DISCLAIMER
|
|
///
|
|
/// Copyright 2014-2016 ArangoDB GmbH, Cologne, Germany
|
|
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
|
|
///
|
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|
/// you may not use this file except in compliance with the License.
|
|
/// You may obtain a copy of the License at
|
|
///
|
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
|
///
|
|
/// Unless required by applicable law or agreed to in writing, software
|
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
/// See the License for the specific language governing permissions and
|
|
/// limitations under the License.
|
|
///
|
|
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
|
///
|
|
/// @author Jan Steemann
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "V8Executor.h"
|
|
#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"
|
|
#include "V8/v8-conv.h"
|
|
#include "V8/v8-globals.h"
|
|
#include "V8/v8-utils.h"
|
|
#include "V8/v8-vpack.h"
|
|
|
|
#include <velocypack/Builder.h>
|
|
#include <velocypack/Slice.h>
|
|
#include <velocypack/velocypack-aliases.h>
|
|
|
|
using namespace arangodb::aql;
|
|
|
|
/// @brief 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) {
|
|
ISOLATE;
|
|
|
|
bool failed = false;
|
|
|
|
if (tryCatch.HasCaught()) {
|
|
// caught a V8 exception
|
|
if (!tryCatch.CanContinue()) {
|
|
// request was canceled
|
|
TRI_GET_GLOBALS();
|
|
v8g->_canceled = true;
|
|
|
|
THROW_ARANGO_EXCEPTION(TRI_ERROR_REQUEST_CANCELED);
|
|
}
|
|
|
|
// request was not canceled, but some other error occurred
|
|
// peek into the exception
|
|
if (tryCatch.Exception()->IsObject()) {
|
|
// cast the exception to an object
|
|
|
|
v8::Handle<v8::Array> objValue =
|
|
v8::Handle<v8::Array>::Cast(tryCatch.Exception());
|
|
v8::Handle<v8::String> errorNum = TRI_V8_ASCII_STRING(isolate, "errorNum");
|
|
v8::Handle<v8::String> errorMessage = TRI_V8_ASCII_STRING(isolate, "errorMessage");
|
|
|
|
TRI_Utf8ValueNFC stacktrace(tryCatch.StackTrace());
|
|
|
|
if (objValue->HasOwnProperty(errorNum) &&
|
|
objValue->HasOwnProperty(errorMessage)) {
|
|
v8::Handle<v8::Value> errorNumValue = objValue->Get(errorNum);
|
|
v8::Handle<v8::Value> errorMessageValue = objValue->Get(errorMessage);
|
|
|
|
// found something that looks like an ArangoError
|
|
if ((errorNumValue->IsNumber() || errorNumValue->IsNumberObject()) &&
|
|
(errorMessageValue->IsString() ||
|
|
errorMessageValue->IsStringObject())) {
|
|
int errorCode = static_cast<int>(TRI_ObjectToInt64(errorNumValue));
|
|
std::string errorMessage(TRI_ObjectToString(errorMessageValue));
|
|
|
|
if (*stacktrace && stacktrace.length() > 0) {
|
|
errorMessage += "\nstacktrace of offending AQL function: ";
|
|
errorMessage += *stacktrace;
|
|
}
|
|
|
|
THROW_ARANGO_EXCEPTION_MESSAGE(errorCode, errorMessage);
|
|
}
|
|
}
|
|
|
|
// exception is no ArangoError
|
|
std::string details(TRI_ObjectToString(tryCatch.Exception()));
|
|
|
|
if (buffer) {
|
|
//std::string script(buffer->c_str(), buffer->length());
|
|
LOG_TOPIC(ERR, arangodb::Logger::FIXME) << details << " " << Logger::CHARS(buffer->c_str(), buffer->length());
|
|
details += "\nSee log for more details";
|
|
}
|
|
if (*stacktrace && stacktrace.length() > 0) {
|
|
details += "\nstacktrace of offending AQL function: ";
|
|
details += *stacktrace;
|
|
}
|
|
|
|
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_SCRIPT, details);
|
|
}
|
|
|
|
failed = true;
|
|
}
|
|
|
|
if (result.IsEmpty()) {
|
|
failed = true;
|
|
}
|
|
|
|
if (failed) {
|
|
std::string msg("unknown error in scripting");
|
|
if (duringCompile) {
|
|
msg += " (during compilation)";
|
|
}
|
|
if (buffer) {
|
|
//std::string script(buffer->c_str(), buffer->length());
|
|
LOG_TOPIC(ERR, arangodb::Logger::FIXME) << msg << " " << Logger::CHARS(buffer->c_str(), buffer->length());
|
|
msg += " See log for details";
|
|
}
|
|
// we can't figure out what kind of error occurred and throw a generic error
|
|
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_SCRIPT, msg);
|
|
}
|
|
|
|
// 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);
|
|
}
|