mirror of https://gitee.com/bigwinds/arangodb
Feature/aql native call apply (#5100)
This commit is contained in:
parent
79eef177ce
commit
ac2a8721e6
|
@ -34,3 +34,10 @@ REST API
|
||||||
|
|
||||||
Miscellaneous
|
Miscellaneous
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
AQL Functions
|
||||||
|
-------------
|
||||||
|
- CALL / APPLY
|
||||||
|
- may emmit `ERROR_QUERY_FUNCTION_NAME_UNKNOWN` or `ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH`
|
||||||
|
instead of `ERROR_QUERY_FUNCTION_NOT_FOUND` in some situations.
|
||||||
|
- are now able to be invoked recursive
|
||||||
|
|
|
@ -495,6 +495,8 @@ fi
|
||||||
|
|
||||||
if [ "${USE_JEMALLOC}" = 1 ]; then
|
if [ "${USE_JEMALLOC}" = 1 ]; then
|
||||||
CONFIGURE_OPTIONS+=(-DUSE_JEMALLOC=On)
|
CONFIGURE_OPTIONS+=(-DUSE_JEMALLOC=On)
|
||||||
|
else
|
||||||
|
CONFIGURE_OPTIONS+=(-DUSE_JEMALLOC=Off)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$SANITIZE" == 1 ]; then
|
if [ "$SANITIZE" == 1 ]; then
|
||||||
|
|
|
@ -365,8 +365,10 @@ void AqlFunctionFeature::addListFunctions() {
|
||||||
&Functions::Nth});
|
&Functions::Nth});
|
||||||
add({"POSITION", ".,.|.", true, false, true, true,
|
add({"POSITION", ".,.|.", true, false, true, true,
|
||||||
&Functions::Position});
|
&Functions::Position});
|
||||||
add({"CALL", ".|.+", false, true, false, true});
|
add({"CALL", ".|.+", false, true, false, true,
|
||||||
add({"APPLY", ".|.", false, true, false, false});
|
&Functions::Call});
|
||||||
|
add({"APPLY", ".|.", false, true, false, false,
|
||||||
|
&Functions::Apply});
|
||||||
add({"PUSH", ".,.|.", true, false, true, false,
|
add({"PUSH", ".,.|.", true, false, true, false,
|
||||||
&Functions::Push});
|
&Functions::Push});
|
||||||
add({"APPEND", ".,.|.", true, false, true, true,
|
add({"APPEND", ".,.|.", true, false, true, true,
|
||||||
|
|
|
@ -95,7 +95,6 @@ Expression::Expression(ExecutionPlan* plan, Ast* ast, AstNode* node)
|
||||||
_canRunOnDBServer(false),
|
_canRunOnDBServer(false),
|
||||||
_isDeterministic(false),
|
_isDeterministic(false),
|
||||||
_willUseV8(false),
|
_willUseV8(false),
|
||||||
_preparedV8Context(false),
|
|
||||||
_attributes(),
|
_attributes(),
|
||||||
_expressionContext(nullptr) {
|
_expressionContext(nullptr) {
|
||||||
TRI_ASSERT(_ast != nullptr);
|
TRI_ASSERT(_ast != nullptr);
|
||||||
|
@ -234,8 +233,6 @@ void Expression::invalidateAfterReplacements() {
|
||||||
/// used and destroyed in the same context. when a V8 function is used across
|
/// used and destroyed in the same context. when a V8 function is used across
|
||||||
/// multiple V8 contexts, it must be invalidated in between
|
/// multiple V8 contexts, it must be invalidated in between
|
||||||
void Expression::invalidate() {
|
void Expression::invalidate() {
|
||||||
// context may change next time, so "prepare for re-preparation"
|
|
||||||
_preparedV8Context = false;
|
|
||||||
|
|
||||||
// V8 expressions need a special handling
|
// V8 expressions need a special handling
|
||||||
freeInternals();
|
freeInternals();
|
||||||
|
@ -909,28 +906,88 @@ AqlValue Expression::executeSimpleExpressionFCallCxx(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AqlValue Expression::invokeV8Function(arangodb::aql::Query* query,
|
||||||
|
transaction::Methods* trx,
|
||||||
|
std::string const& jsName,
|
||||||
|
std::string const& ucInvokeFN,
|
||||||
|
char const* AFN,
|
||||||
|
bool rethrowV8Exception,
|
||||||
|
int callArgs,
|
||||||
|
v8::Handle<v8::Value>* args,
|
||||||
|
bool &mustDestroy
|
||||||
|
){
|
||||||
|
ISOLATE;
|
||||||
|
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);
|
||||||
|
|
||||||
|
try {
|
||||||
|
V8Executor::HandleV8Error(tryCatch, result, nullptr, false);
|
||||||
|
}
|
||||||
|
catch (arangodb::basics::Exception const& ex) {
|
||||||
|
if (rethrowV8Exception) {
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
if (ex.code() == TRI_ERROR_QUERY_FUNCTION_NOT_FOUND) {
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
std::string message("While invoking '");
|
||||||
|
message += ucInvokeFN + "' via '" + AFN + "': " + ex.message();
|
||||||
|
query->registerWarning(ex.code(), message.c_str());
|
||||||
|
return AqlValue(AqlValueHintNull());
|
||||||
|
}
|
||||||
|
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, JavaScript variant
|
/// @brief execute an expression of type SIMPLE, JavaScript variant
|
||||||
AqlValue Expression::executeSimpleExpressionFCallJS(
|
AqlValue Expression::executeSimpleExpressionFCallJS(
|
||||||
AstNode const* node, transaction::Methods* trx, bool& mustDestroy) {
|
AstNode const* node, transaction::Methods* trx, bool& mustDestroy) {
|
||||||
|
|
||||||
prepareV8Context();
|
|
||||||
|
|
||||||
auto member = node->getMemberUnchecked(0);
|
auto member = node->getMemberUnchecked(0);
|
||||||
TRI_ASSERT(member->type == NODE_TYPE_ARRAY);
|
TRI_ASSERT(member->type == NODE_TYPE_ARRAY);
|
||||||
|
|
||||||
mustDestroy = false;
|
mustDestroy = false;
|
||||||
|
|
||||||
v8::Isolate* isolate = v8::Isolate::GetCurrent();
|
|
||||||
TRI_ASSERT(isolate != nullptr);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
||||||
v8::HandleScope scope(isolate);
|
ISOLATE;
|
||||||
|
TRI_ASSERT(isolate != nullptr);
|
||||||
|
TRI_V8_CURRENT_GLOBALS_AND_SCOPE;
|
||||||
|
_ast->query()->prepareV8Context();
|
||||||
|
|
||||||
|
auto old = v8g->_query;
|
||||||
|
v8g->_query = static_cast<void*>(_ast->query());
|
||||||
|
TRI_DEFER(v8g->_query = old);
|
||||||
|
|
||||||
std::string jsName;
|
std::string jsName;
|
||||||
size_t const n = member->numMembers();
|
size_t const n = member->numMembers();
|
||||||
size_t const callArgs = (node->type == NODE_TYPE_FCALL_USER ? 2 : n);
|
int callArgs = (node->type == NODE_TYPE_FCALL_USER ? 2 : n);
|
||||||
auto args = std::make_unique<v8::Handle<v8::Value>[]>(callArgs);
|
v8::Handle<v8::Value> args[callArgs];
|
||||||
|
|
||||||
if (node->type == NODE_TYPE_FCALL_USER) {
|
if (node->type == NODE_TYPE_FCALL_USER) {
|
||||||
// a call to a user-defined function
|
// a call to a user-defined function
|
||||||
|
@ -951,6 +1008,7 @@ AqlValue Expression::executeSimpleExpressionFCallJS(
|
||||||
args[0] = TRI_V8_STD_STRING(isolate, node->getString());
|
args[0] = TRI_V8_STD_STRING(isolate, node->getString());
|
||||||
// call parameters
|
// call parameters
|
||||||
args[1] = params;
|
args[1] = params;
|
||||||
|
// args[2] will be null
|
||||||
} else {
|
} else {
|
||||||
// a call to a built-in V8 function
|
// a call to a built-in V8 function
|
||||||
auto func = static_cast<Function*>(node->getData());
|
auto func = static_cast<Function*>(node->getData());
|
||||||
|
@ -972,43 +1030,7 @@ AqlValue Expression::executeSimpleExpressionFCallJS(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TRI_v8_global_t* v8g = static_cast<TRI_v8_global_t*>(isolate->GetData(arangodb::V8PlatformFeature::V8_DATA_SLOT));
|
return invokeV8Function(_ast->query(), trx, jsName, "", "", true, callArgs, args, mustDestroy);
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1654,31 +1676,3 @@ AqlValue Expression::executeSimpleExpressionArithmetic(
|
||||||
// this will convert NaN, +inf & -inf to null
|
// this will convert NaN, +inf & -inf to null
|
||||||
return AqlValue(AqlValueHintDouble(result));
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
|
|
||||||
#include <velocypack/Builder.h>
|
#include <velocypack/Builder.h>
|
||||||
#include <velocypack/Slice.h>
|
#include <velocypack/Slice.h>
|
||||||
|
#include <v8.h>
|
||||||
|
|
||||||
namespace arangodb {
|
namespace arangodb {
|
||||||
namespace transaction {
|
namespace transaction {
|
||||||
|
@ -50,6 +51,7 @@ class Ast;
|
||||||
class AttributeAccessor;
|
class AttributeAccessor;
|
||||||
class ExecutionPlan;
|
class ExecutionPlan;
|
||||||
class ExpressionContext;
|
class ExpressionContext;
|
||||||
|
class Query;
|
||||||
|
|
||||||
/// @brief AqlExpression, used in execution plans and execution blocks
|
/// @brief AqlExpression, used in execution plans and execution blocks
|
||||||
class Expression {
|
class Expression {
|
||||||
|
@ -160,6 +162,17 @@ class Expression {
|
||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @brief invoke javascript aql functions with args as param.
|
||||||
|
static AqlValue invokeV8Function(arangodb::aql::Query* query,
|
||||||
|
transaction::Methods* trx,
|
||||||
|
std::string const& jsName,
|
||||||
|
std::string const& ucInvokeFN,
|
||||||
|
char const* AFN,
|
||||||
|
bool rethrowV8Exception,
|
||||||
|
int callArgs,
|
||||||
|
v8::Handle<v8::Value>* args,
|
||||||
|
bool &mustDestroy);
|
||||||
|
|
||||||
/// @brief check whether this is an attribute access of any degree (e.g. a.b,
|
/// @brief check whether this is an attribute access of any degree (e.g. a.b,
|
||||||
/// a.b.c, ...)
|
/// a.b.c, ...)
|
||||||
bool isAttributeAccess() const;
|
bool isAttributeAccess() const;
|
||||||
|
@ -269,7 +282,6 @@ class Expression {
|
||||||
AqlValue executeSimpleExpressionFCallCxx(AstNode const*,
|
AqlValue executeSimpleExpressionFCallCxx(AstNode const*,
|
||||||
transaction::Methods*,
|
transaction::Methods*,
|
||||||
bool& mustDestroy);
|
bool& mustDestroy);
|
||||||
|
|
||||||
/// @brief execute an expression of type SIMPLE with FCALL, JavaScript variant
|
/// @brief execute an expression of type SIMPLE with FCALL, JavaScript variant
|
||||||
AqlValue executeSimpleExpressionFCallJS(AstNode const*,
|
AqlValue executeSimpleExpressionFCallJS(AstNode const*,
|
||||||
transaction::Methods*,
|
transaction::Methods*,
|
||||||
|
@ -337,11 +349,6 @@ class Expression {
|
||||||
AstNode const*, transaction::Methods*,
|
AstNode const*, transaction::Methods*,
|
||||||
bool& mustDestroy);
|
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:
|
private:
|
||||||
/// @brief the query execution plan. note: this may be a nullptr for expressions
|
/// @brief the query execution plan. note: this may be a nullptr for expressions
|
||||||
/// created in the early optimization stage!
|
/// created in the early optimization stage!
|
||||||
|
@ -374,11 +381,6 @@ class Expression {
|
||||||
/// @brief whether or not the expression will make use of V8
|
/// @brief whether or not the expression will make use of V8
|
||||||
bool _willUseV8;
|
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
|
/// @brief the top-level attributes used in the expression, grouped
|
||||||
/// by variable name
|
/// by variable name
|
||||||
std::unordered_map<Variable const*, std::unordered_set<std::string>>
|
std::unordered_map<Variable const*, std::unordered_set<std::string>>
|
||||||
|
|
|
@ -25,9 +25,12 @@
|
||||||
|
|
||||||
#include "ApplicationFeatures/ApplicationServer.h"
|
#include "ApplicationFeatures/ApplicationServer.h"
|
||||||
#include "ApplicationFeatures/LanguageFeature.h"
|
#include "ApplicationFeatures/LanguageFeature.h"
|
||||||
|
#include "Aql/AqlFunctionFeature.h"
|
||||||
|
#include "Aql/Expression.h"
|
||||||
#include "Aql/Function.h"
|
#include "Aql/Function.h"
|
||||||
#include "Aql/Query.h"
|
#include "Aql/Query.h"
|
||||||
#include "Aql/RegexCache.h"
|
#include "Aql/RegexCache.h"
|
||||||
|
#include "Aql/V8Executor.h"
|
||||||
#include "Basics/Exceptions.h"
|
#include "Basics/Exceptions.h"
|
||||||
#include "Basics/StringBuffer.h"
|
#include "Basics/StringBuffer.h"
|
||||||
#include "Basics/StringRef.h"
|
#include "Basics/StringRef.h"
|
||||||
|
@ -37,6 +40,7 @@
|
||||||
#include "Basics/VelocyPackHelper.h"
|
#include "Basics/VelocyPackHelper.h"
|
||||||
#include "Basics/fpconv.h"
|
#include "Basics/fpconv.h"
|
||||||
#include "Basics/tri-strings.h"
|
#include "Basics/tri-strings.h"
|
||||||
|
#include "V8/v8-vpack.h"
|
||||||
#include "GeneralServer/AuthenticationFeature.h"
|
#include "GeneralServer/AuthenticationFeature.h"
|
||||||
#include "Indexes/Index.h"
|
#include "Indexes/Index.h"
|
||||||
#include "Logger/Logger.h"
|
#include "Logger/Logger.h"
|
||||||
|
@ -112,7 +116,7 @@ static_assert(DateSelectionModifier::MONTH < DateSelectionModifier::YEAR,
|
||||||
- ICU related errors: if (U_FAILURE(status)) { RegisterICUWarning(query, "MYFUNC", status); }
|
- ICU related errors: if (U_FAILURE(status)) { RegisterICUWarning(query, "MYFUNC", status); }
|
||||||
- close with: return AqlValue(AqlValueHintNull());
|
- close with: return AqlValue(AqlValueHintNull());
|
||||||
- specify the number of parameters you expect at least and at max using:
|
- specify the number of parameters you expect at least and at max using:
|
||||||
ValidateParameters(parameters, "MYFUNC", 1, 3); (min: 1, max: 3)
|
ValidateParameters(parameters, "MYFUNC", 1, 3); (min: 1, max: 3); Max is optional.
|
||||||
- if you support optional parameters, first check whether the count is sufficient
|
- if you support optional parameters, first check whether the count is sufficient
|
||||||
using parameters.size()
|
using parameters.size()
|
||||||
- fetch the values using:
|
- fetch the values using:
|
||||||
|
@ -483,6 +487,24 @@ void Functions::RegisterWarning(arangodb::aql::Query* query, char const* fName,
|
||||||
query->registerWarning(code, msg.c_str());
|
query->registerWarning(code, msg.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief register warning
|
||||||
|
void Functions::RegisterError(arangodb::aql::Query* query,
|
||||||
|
char const* fName,
|
||||||
|
int code) {
|
||||||
|
std::string msg;
|
||||||
|
|
||||||
|
if (code == TRI_ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH) {
|
||||||
|
msg = arangodb::basics::Exception::FillExceptionString(code, fName);
|
||||||
|
} else {
|
||||||
|
msg.append("in function '");
|
||||||
|
msg.append(fName);
|
||||||
|
msg.append("()': ");
|
||||||
|
msg.append(TRI_errno_string(code));
|
||||||
|
}
|
||||||
|
|
||||||
|
query->registerError(code, msg.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
/// @brief register usage of an invalid function argument
|
/// @brief register usage of an invalid function argument
|
||||||
void Functions::RegisterInvalidArgumentWarning(arangodb::aql::Query* query,
|
void Functions::RegisterInvalidArgumentWarning(arangodb::aql::Query* query,
|
||||||
char const* functionName) {
|
char const* functionName) {
|
||||||
|
@ -1681,13 +1703,13 @@ AqlValue Functions::Substitute(arangodb::aql::Query* query,
|
||||||
for (auto const& it : VPackObjectIterator(slice)) {
|
for (auto const& it : VPackObjectIterator(slice)) {
|
||||||
arangodb::velocypack::ValueLength length;
|
arangodb::velocypack::ValueLength length;
|
||||||
const char *str = it.key.getString(length);
|
const char *str = it.key.getString(length);
|
||||||
matchPatterns.push_back(UnicodeString(str, length));
|
matchPatterns.push_back(UnicodeString(str, static_cast<int32_t>(length)));
|
||||||
if (!it.value.isString()) {
|
if (!it.value.isString()) {
|
||||||
RegisterInvalidArgumentWarning(query, AFN);
|
RegisterInvalidArgumentWarning(query, AFN);
|
||||||
return AqlValue(AqlValueHintNull());
|
return AqlValue(AqlValueHintNull());
|
||||||
}
|
}
|
||||||
str = it.value.getString(length);
|
str = it.value.getString(length);
|
||||||
replacePatterns.push_back(UnicodeString(str, length));
|
replacePatterns.push_back(UnicodeString(str, static_cast<int32_t>(length)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -1708,7 +1730,7 @@ AqlValue Functions::Substitute(arangodb::aql::Query* query,
|
||||||
}
|
}
|
||||||
arangodb::velocypack::ValueLength length;
|
arangodb::velocypack::ValueLength length;
|
||||||
const char *str = it.getString(length);
|
const char *str = it.getString(length);
|
||||||
matchPatterns.push_back(UnicodeString(str, length));
|
matchPatterns.push_back(UnicodeString(str, static_cast<int32_t>(length)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -1718,7 +1740,7 @@ AqlValue Functions::Substitute(arangodb::aql::Query* query,
|
||||||
}
|
}
|
||||||
arangodb::velocypack::ValueLength length;
|
arangodb::velocypack::ValueLength length;
|
||||||
const char *str = slice.getString(length);
|
const char *str = slice.getString(length);
|
||||||
matchPatterns.push_back(UnicodeString(str, length));
|
matchPatterns.push_back(UnicodeString(str, static_cast<int32_t>(length)));
|
||||||
}
|
}
|
||||||
if (parameters.size() > 2) {
|
if (parameters.size() > 2) {
|
||||||
AqlValue replace = ExtractFunctionParameterValue(parameters, 2);
|
AqlValue replace = ExtractFunctionParameterValue(parameters, 2);
|
||||||
|
@ -1731,7 +1753,7 @@ AqlValue Functions::Substitute(arangodb::aql::Query* query,
|
||||||
}
|
}
|
||||||
arangodb::velocypack::ValueLength length;
|
arangodb::velocypack::ValueLength length;
|
||||||
const char *str = it.getString(length);
|
const char *str = it.getString(length);
|
||||||
replacePatterns.push_back(UnicodeString(str, length));
|
replacePatterns.push_back(UnicodeString(str, static_cast<int32_t>(length)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (replace.isString()) {
|
else if (replace.isString()) {
|
||||||
|
@ -1740,7 +1762,7 @@ AqlValue Functions::Substitute(arangodb::aql::Query* query,
|
||||||
replaceWasPlainString = true;
|
replaceWasPlainString = true;
|
||||||
arangodb::velocypack::ValueLength length;
|
arangodb::velocypack::ValueLength length;
|
||||||
const char *str = rslice.getString(length);
|
const char *str = rslice.getString(length);
|
||||||
replacePatterns.push_back(UnicodeString(str, length));
|
replacePatterns.push_back(UnicodeString(str, static_cast<int32_t>(length)));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
RegisterInvalidArgumentWarning(query, AFN);
|
RegisterInvalidArgumentWarning(query, AFN);
|
||||||
|
@ -5628,6 +5650,159 @@ AqlValue Functions::Position(arangodb::aql::Query* query,
|
||||||
return AqlValue(builder.get());
|
return AqlValue(builder.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
AqlValue Functions::CallApplyBackend(arangodb::aql::Query* query,
|
||||||
|
transaction::Methods* trx,
|
||||||
|
VPackFunctionParameters const& parameters,
|
||||||
|
char const* AFN,
|
||||||
|
AqlValue const& invokeFN,
|
||||||
|
VPackFunctionParameters const& invokeParams) {
|
||||||
|
std::string ucInvokeFN;
|
||||||
|
transaction::StringBufferLeaser buffer(trx);
|
||||||
|
arangodb::basics::VPackStringBufferAdapter adapter(buffer->stringBuffer());
|
||||||
|
|
||||||
|
AppendAsString(trx, adapter, invokeFN);
|
||||||
|
|
||||||
|
UnicodeString unicodeStr(buffer->c_str(),
|
||||||
|
static_cast<int32_t>(buffer->length()));
|
||||||
|
unicodeStr.toUpper(NULL);
|
||||||
|
unicodeStr.toUTF8String(ucInvokeFN);
|
||||||
|
|
||||||
|
arangodb::aql::Function const* func = nullptr;
|
||||||
|
if (ucInvokeFN.find("::") == std::string::npos) {
|
||||||
|
func = AqlFunctionFeature::getFunctionByName(ucInvokeFN);
|
||||||
|
if (func->implementation != nullptr) {
|
||||||
|
return func->implementation(query, trx, invokeParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ISOLATE;
|
||||||
|
TRI_V8_CURRENT_GLOBALS_AND_SCOPE;
|
||||||
|
query->prepareV8Context();
|
||||||
|
|
||||||
|
auto old = v8g->_query;
|
||||||
|
v8g->_query = query;
|
||||||
|
TRI_DEFER(v8g->_query = old);
|
||||||
|
|
||||||
|
std::string jsName;
|
||||||
|
size_t const n = invokeParams.size();
|
||||||
|
int const callArgs = (func == nullptr ? 3 : n);
|
||||||
|
v8::Handle<v8::Value> args[callArgs];
|
||||||
|
|
||||||
|
if (func == nullptr) {
|
||||||
|
// a call to a user-defined function
|
||||||
|
jsName = "FCALL_USER";
|
||||||
|
|
||||||
|
// function name
|
||||||
|
args[0] = TRI_V8_STD_STRING(isolate, ucInvokeFN);
|
||||||
|
// call parameters
|
||||||
|
v8::Handle<v8::Array> params = v8::Array::New(isolate, static_cast<int>(n));
|
||||||
|
|
||||||
|
for (size_t i = 0; i < n; ++i) {
|
||||||
|
params->Set(static_cast<uint32_t>(i), invokeParams[i].toV8(isolate, trx));
|
||||||
|
}
|
||||||
|
args[1] = params;
|
||||||
|
args[2] = TRI_V8_ASCII_STRING(isolate, AFN);
|
||||||
|
} else {
|
||||||
|
// a call to a built-in V8 function
|
||||||
|
jsName = "AQL_" + func->nonAliasedName;
|
||||||
|
for (size_t i = 0; i < n; ++i) {
|
||||||
|
args[i] = invokeParams[i].toV8(isolate, trx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool dummy;
|
||||||
|
return Expression::invokeV8Function(query, trx, jsName, ucInvokeFN, AFN, false, callArgs, args, dummy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// @brief function CALL
|
||||||
|
AqlValue Functions::Call(arangodb::aql::Query* query,
|
||||||
|
transaction::Methods* trx,
|
||||||
|
VPackFunctionParameters const& parameters) {
|
||||||
|
static char const* AFN = "CALL";
|
||||||
|
ValidateParameters(parameters, AFN, 1);
|
||||||
|
|
||||||
|
AqlValue invokeFN = ExtractFunctionParameterValue(parameters, 0);
|
||||||
|
if (!invokeFN.isString()) {
|
||||||
|
RegisterError(query, AFN, TRI_ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
|
||||||
|
return AqlValue(AqlValueHintNull());
|
||||||
|
}
|
||||||
|
|
||||||
|
SmallVector<AqlValue>::allocator_type::arena_type arena;
|
||||||
|
VPackFunctionParameters invokeParams{arena};
|
||||||
|
if (parameters.size() >= 2) {
|
||||||
|
// we have a list of parameters, need to copy them over except the functionname:
|
||||||
|
invokeParams.reserve(parameters.size() -1);
|
||||||
|
|
||||||
|
for (uint64_t i = 1; i < parameters.size(); i++) {
|
||||||
|
invokeParams.push_back(ExtractFunctionParameterValue(parameters, i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CallApplyBackend(query, trx, parameters, AFN, invokeFN, invokeParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief function APPLY
|
||||||
|
AqlValue Functions::Apply(
|
||||||
|
arangodb::aql::Query* query, transaction::Methods* trx,
|
||||||
|
VPackFunctionParameters const& parameters) {
|
||||||
|
static char const* AFN = "APPLY";
|
||||||
|
ValidateParameters(parameters, AFN, 1, 2);
|
||||||
|
|
||||||
|
AqlValue invokeFN = ExtractFunctionParameterValue(parameters, 0);
|
||||||
|
if (!invokeFN.isString()) {
|
||||||
|
RegisterError(query, AFN, TRI_ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
|
||||||
|
return AqlValue(AqlValueHintNull());
|
||||||
|
}
|
||||||
|
|
||||||
|
SmallVector<AqlValue>::allocator_type::arena_type arena;
|
||||||
|
VPackFunctionParameters invokeParams{arena};
|
||||||
|
VPackSlice paramArray;
|
||||||
|
AqlValue rawParamArray;
|
||||||
|
std::vector<bool> mustFree;
|
||||||
|
if (parameters.size() == 2) {
|
||||||
|
// We have a parameter that should be an array, whichs content we need to make
|
||||||
|
// the sub functions parameters.
|
||||||
|
rawParamArray = ExtractFunctionParameterValue(parameters, 1);
|
||||||
|
|
||||||
|
if (!rawParamArray.isArray()) {
|
||||||
|
RegisterWarning(query, AFN, TRI_ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
|
||||||
|
return AqlValue(AqlValueHintNull());
|
||||||
|
}
|
||||||
|
uint64_t len = rawParamArray.length();
|
||||||
|
invokeParams.reserve(len);
|
||||||
|
mustFree.reserve(len);
|
||||||
|
for (uint64_t i = 0; i < len; i++) {
|
||||||
|
bool f;
|
||||||
|
invokeParams.push_back(rawParamArray.at(trx, i, f, false));
|
||||||
|
mustFree[i] = f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto rc = CallApplyBackend(query, trx, parameters, AFN, invokeFN, invokeParams);
|
||||||
|
for (size_t i = 0; i < mustFree.size(); ++i) {
|
||||||
|
if (mustFree[i]) {
|
||||||
|
invokeParams[i].destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
for (size_t i = 0; i < mustFree.size(); ++i) {
|
||||||
|
if (mustFree[i]) {
|
||||||
|
invokeParams[i].destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// @brief function IS_SAME_COLLECTION
|
/// @brief function IS_SAME_COLLECTION
|
||||||
AqlValue Functions::IsSameCollection(
|
AqlValue Functions::IsSameCollection(
|
||||||
arangodb::aql::Query* query, transaction::Methods* trx,
|
arangodb::aql::Query* query, transaction::Methods* trx,
|
||||||
|
|
|
@ -75,6 +75,9 @@ struct Functions {
|
||||||
/// @brief register warning
|
/// @brief register warning
|
||||||
static void RegisterWarning(arangodb::aql::Query* query,
|
static void RegisterWarning(arangodb::aql::Query* query,
|
||||||
char const* functionName, int code);
|
char const* functionName, int code);
|
||||||
|
/// @brief register error
|
||||||
|
static void RegisterError(arangodb::aql::Query* query,
|
||||||
|
char const* functionName, int code);
|
||||||
/// @brief register usage of an invalid function argument
|
/// @brief register usage of an invalid function argument
|
||||||
static void RegisterInvalidArgumentWarning(arangodb::aql::Query* query,
|
static void RegisterInvalidArgumentWarning(arangodb::aql::Query* query,
|
||||||
char const* functionName);
|
char const* functionName);
|
||||||
|
@ -456,6 +459,16 @@ struct Functions {
|
||||||
VPackFunctionParameters const&);
|
VPackFunctionParameters const&);
|
||||||
static AqlValue Position(arangodb::aql::Query*, transaction::Methods*,
|
static AqlValue Position(arangodb::aql::Query*, transaction::Methods*,
|
||||||
VPackFunctionParameters const&);
|
VPackFunctionParameters const&);
|
||||||
|
static AqlValue CallApplyBackend(arangodb::aql::Query* query,
|
||||||
|
transaction::Methods* trx,
|
||||||
|
VPackFunctionParameters const& parameters,
|
||||||
|
char const* AFN,
|
||||||
|
AqlValue const& invokeFN,
|
||||||
|
VPackFunctionParameters const& invokeParams);
|
||||||
|
static AqlValue Call(arangodb::aql::Query*, transaction::Methods*,
|
||||||
|
VPackFunctionParameters const&);
|
||||||
|
static AqlValue Apply(arangodb::aql::Query*, transaction::Methods*,
|
||||||
|
VPackFunctionParameters const&);
|
||||||
static AqlValue IsSameCollection(arangodb::aql::Query*,
|
static AqlValue IsSameCollection(arangodb::aql::Query*,
|
||||||
transaction::Methods*,
|
transaction::Methods*,
|
||||||
VPackFunctionParameters const&);
|
VPackFunctionParameters const&);
|
||||||
|
|
|
@ -90,7 +90,8 @@ Query::Query(bool contextOwnedByExterior, TRI_vocbase_t* vocbase,
|
||||||
_part(part),
|
_part(part),
|
||||||
_contextOwnedByExterior(contextOwnedByExterior),
|
_contextOwnedByExterior(contextOwnedByExterior),
|
||||||
_killed(false),
|
_killed(false),
|
||||||
_isModificationQuery(false) {
|
_isModificationQuery(false),
|
||||||
|
_preparedV8Context(false) {
|
||||||
|
|
||||||
AqlFeature* aql = AqlFeature::lease();
|
AqlFeature* aql = AqlFeature::lease();
|
||||||
if (aql == nullptr) {
|
if (aql == nullptr) {
|
||||||
|
@ -168,7 +169,8 @@ Query::Query(bool contextOwnedByExterior, TRI_vocbase_t* vocbase,
|
||||||
_part(part),
|
_part(part),
|
||||||
_contextOwnedByExterior(contextOwnedByExterior),
|
_contextOwnedByExterior(contextOwnedByExterior),
|
||||||
_killed(false),
|
_killed(false),
|
||||||
_isModificationQuery(false) {
|
_isModificationQuery(false),
|
||||||
|
_preparedV8Context(false) {
|
||||||
|
|
||||||
AqlFeature* aql = AqlFeature::lease();
|
AqlFeature* aql = AqlFeature::lease();
|
||||||
if (aql == nullptr) {
|
if (aql == nullptr) {
|
||||||
|
@ -1000,6 +1002,34 @@ void Query::releaseEngine() {
|
||||||
_engine.release();
|
_engine.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @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 Query::prepareV8Context() {
|
||||||
|
if (_preparedV8Context) {
|
||||||
|
// already done
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TRI_ASSERT(_trx != nullptr);
|
||||||
|
|
||||||
|
ISOLATE;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// @brief enter a V8 context
|
/// @brief enter a V8 context
|
||||||
void Query::enterContext() {
|
void Query::enterContext() {
|
||||||
if (!_contextOwnedByExterior) {
|
if (!_contextOwnedByExterior) {
|
||||||
|
@ -1016,12 +1046,14 @@ void Query::enterContext() {
|
||||||
|
|
||||||
ISOLATE;
|
ISOLATE;
|
||||||
TRI_GET_GLOBALS();
|
TRI_GET_GLOBALS();
|
||||||
|
_preparedV8Context = false;
|
||||||
auto ctx = static_cast<arangodb::transaction::V8Context*>(
|
auto ctx = static_cast<arangodb::transaction::V8Context*>(
|
||||||
v8g->_transactionContext);
|
v8g->_transactionContext);
|
||||||
if (ctx != nullptr) {
|
if (ctx != nullptr) {
|
||||||
ctx->registerTransaction(_trx->state());
|
ctx->registerTransaction(_trx->state());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_preparedV8Context = false;
|
||||||
|
|
||||||
TRI_ASSERT(_context != nullptr);
|
TRI_ASSERT(_context != nullptr);
|
||||||
}
|
}
|
||||||
|
@ -1043,6 +1075,7 @@ void Query::exitContext() {
|
||||||
V8DealerFeature::DEALER->exitContext(_context);
|
V8DealerFeature::DEALER->exitContext(_context);
|
||||||
_context = nullptr;
|
_context = nullptr;
|
||||||
}
|
}
|
||||||
|
_preparedV8Context = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -219,6 +219,11 @@ class Query {
|
||||||
/// @brief mark a query as modification query
|
/// @brief mark a query as modification query
|
||||||
void setIsModificationQuery() { _isModificationQuery = true; }
|
void setIsModificationQuery() { _isModificationQuery = true; }
|
||||||
|
|
||||||
|
/// @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();
|
||||||
|
|
||||||
/// @brief enter a V8 context
|
/// @brief enter a V8 context
|
||||||
void enterContext();
|
void enterContext();
|
||||||
|
|
||||||
|
@ -375,6 +380,12 @@ class Query {
|
||||||
|
|
||||||
/// @brief whether or not the query is a data modification query
|
/// @brief whether or not the query is a data modification query
|
||||||
bool _isModificationQuery;
|
bool _isModificationQuery;
|
||||||
|
|
||||||
|
/// @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;
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -819,7 +819,7 @@ function COMPILE_LIKE (regex, modifiers) {
|
||||||
// / @brief call a user function
|
// / @brief call a user function
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
function FCALL_USER (name, parameters) {
|
function FCALL_USER (name, parameters, func) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var prefix = DB_PREFIX(), reloaded = false;
|
var prefix = DB_PREFIX(), reloaded = false;
|
||||||
|
@ -834,7 +834,7 @@ function FCALL_USER (name, parameters) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!UserFunctions[prefix].hasOwnProperty(name)) {
|
if (!UserFunctions[prefix].hasOwnProperty(name)) {
|
||||||
THROW(null, INTERNAL.errors.ERROR_QUERY_FUNCTION_NOT_FOUND, name);
|
THROW(func, INTERNAL.errors.ERROR_QUERY_FUNCTION_NOT_FOUND, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -5835,7 +5835,6 @@ exports.AQL_WARN = AQL_WARN;
|
||||||
|
|
||||||
exports.reload = reloadUserFunctions;
|
exports.reload = reloadUserFunctions;
|
||||||
exports.clearCaches = clearCaches;
|
exports.clearCaches = clearCaches;
|
||||||
exports.lookupFunction = GET_USERFUNCTION;
|
|
||||||
exports.throwFromFunction = THROW;
|
exports.throwFromFunction = THROW;
|
||||||
exports.fixValue = FIX_VALUE;
|
exports.fixValue = FIX_VALUE;
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,11 @@ function ahuacatlCallApplyTestSuite () {
|
||||||
|
|
||||||
data.forEach(function (d) {
|
data.forEach(function (d) {
|
||||||
var actual = getQueryResults("RETURN CALL(" + d[1].map(function (v) { return JSON.stringify(v); }).join(", ") + ")");
|
var actual = getQueryResults("RETURN CALL(" + d[1].map(function (v) { return JSON.stringify(v); }).join(", ") + ")");
|
||||||
|
if (Array.isArray(d[0])) {
|
||||||
|
assertEqual(d[0].sort(), actual[0].sort(), d);
|
||||||
|
} else {
|
||||||
assertEqual(d[0], actual[0], d);
|
assertEqual(d[0], actual[0], d);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -97,23 +101,35 @@ function ahuacatlCallApplyTestSuite () {
|
||||||
testCallNonExisting : function () {
|
testCallNonExisting : function () {
|
||||||
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN CALL()");
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN CALL()");
|
||||||
|
|
||||||
assertQueryError(errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code, "RETURN CALL('nono-existing', [ 'baz' ])");
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_NAME_UNKNOWN.code, "RETURN CALL('nono-existing', [ 'baz' ])");
|
||||||
assertQueryError(errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code, "RETURN CALL('foobar', 'baz')");
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_NAME_UNKNOWN.code, "RETURN CALL('foobar', 'baz')");
|
||||||
assertQueryError(errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code, "RETURN CALL(' trim', 'baz')");
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_NAME_UNKNOWN.code, "RETURN CALL(' trim', 'baz')");
|
||||||
assertQueryError(errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code, "RETURN CALL('foo::bar::baz', 'baz')");
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code, "RETURN CALL('foo::bar::baz', 'baz')");
|
||||||
assertQueryError(errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code, "RETURN CALL(123, 'baz')");
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN CALL(123, 'baz')");
|
||||||
assertQueryError(errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code, "RETURN CALL([ ], 'baz')");
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN CALL([ ], 'baz')");
|
||||||
},
|
},
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
/// @brief test call function
|
/// @brief test call function
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
testCallRecursive : function () {
|
||||||
|
var actual = getQueryResults("RETURN CALL('CALL', 'TRIM', ' foo bar ')");
|
||||||
|
assertEqual(actual, [ 'foo bar' ]);
|
||||||
|
actual = getQueryResults("RETURN CALL('APPLY', 'TRIM', [' foo bar '])");
|
||||||
|
assertEqual(actual, [ 'foo bar' ]);
|
||||||
|
|
||||||
|
let recursion = [];
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
recursion.push('CALL');
|
||||||
|
}
|
||||||
|
recursion.push('TRIM');
|
||||||
|
recursion.push(' foo bar ');
|
||||||
|
let query = "RETURN CALL('" + recursion.join('\',\'') + "')";
|
||||||
|
actual = getQueryResults(query);
|
||||||
|
|
||||||
|
assertEqual(actual, [ 'foo bar' ]);
|
||||||
|
|
||||||
testCallDisallowed : function () {
|
|
||||||
assertQueryError(errors.ERROR_QUERY_DISALLOWED_DYNAMIC_CALL.code, "RETURN CALL('CALL')");
|
|
||||||
assertQueryError(errors.ERROR_QUERY_DISALLOWED_DYNAMIC_CALL.code, "RETURN CALL('APPLY')");
|
|
||||||
},
|
},
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
/// @brief test apply function
|
/// @brief test apply function
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -144,7 +160,11 @@ function ahuacatlCallApplyTestSuite () {
|
||||||
args.push(d[1][i]);
|
args.push(d[1][i]);
|
||||||
}
|
}
|
||||||
var actual = getQueryResults("RETURN APPLY(" + JSON.stringify(d[1][0]) + ", " + JSON.stringify(args) + ")");
|
var actual = getQueryResults("RETURN APPLY(" + JSON.stringify(d[1][0]) + ", " + JSON.stringify(args) + ")");
|
||||||
|
if (Array.isArray(d[0])) {
|
||||||
|
assertEqual(d[0].sort(), actual[0].sort(), d);
|
||||||
|
} else {
|
||||||
assertEqual(d[0], actual[0], d);
|
assertEqual(d[0], actual[0], d);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -174,21 +194,42 @@ function ahuacatlCallApplyTestSuite () {
|
||||||
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN APPLY()");
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN APPLY()");
|
||||||
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN APPLY('TRIM', 1, 2)");
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN APPLY('TRIM', 1, 2)");
|
||||||
|
|
||||||
assertQueryError(errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code, "RETURN APPLY('nono-existing', [ 'baz' ])");
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_NAME_UNKNOWN.code, "RETURN APPLY('nono-existing', [ 'baz' ])");
|
||||||
assertQueryError(errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code, "RETURN APPLY('foobar', [ 'baz' ])");
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_NAME_UNKNOWN.code, "RETURN APPLY('foobar', [ 'baz' ])");
|
||||||
assertQueryError(errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code, "RETURN APPLY(' trim', [ 'baz' ])");
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_NAME_UNKNOWN.code, "RETURN APPLY(' trim', [ 'baz' ])");
|
||||||
assertQueryError(errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code, "RETURN APPLY('foo::bar::baz', [ 'baz' ])");
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code, "RETURN APPLY('foo::bar::baz', [ 'baz' ])");
|
||||||
assertQueryError(errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code, "RETURN APPLY(123, [ 'baz' ])");
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN APPLY(123, [ 'baz' ])");
|
||||||
assertQueryError(errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code, "RETURN APPLY([ ], [ 'baz' ])");
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN APPLY([ ], [ 'baz' ])");
|
||||||
},
|
},
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
/// @brief test apply function
|
/// @brief test apply function
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
testApplyDisallowed : function () {
|
testApplyRecursive : function () {
|
||||||
assertQueryError(errors.ERROR_QUERY_DISALLOWED_DYNAMIC_CALL.code, "RETURN APPLY('CALL')");
|
var actual = getQueryResults("RETURN APPLY('CALL', ['TRIM', ' foo bar '])");
|
||||||
assertQueryError(errors.ERROR_QUERY_DISALLOWED_DYNAMIC_CALL.code, "RETURN APPLY('APPLY')");
|
assertEqual(actual, [ 'foo bar' ]);
|
||||||
|
actual = getQueryResults("RETURN APPLY('APPLY', ['TRIM', [' foo bar ']])");
|
||||||
|
assertEqual(actual, [ 'foo bar' ]);
|
||||||
|
|
||||||
|
let recursion = '';
|
||||||
|
let close = '';
|
||||||
|
let rDepth = 20;
|
||||||
|
for (let i = 0; i < rDepth; i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
recursion += '[';
|
||||||
|
}
|
||||||
|
recursion += '\'APPLY\'';
|
||||||
|
if (i < rDepth) {
|
||||||
|
recursion += ',';
|
||||||
|
}
|
||||||
|
close += ']';
|
||||||
|
}
|
||||||
|
recursion += '[\'TRIM\', [ \' foo bar \'] ' + close;
|
||||||
|
let query = "RETURN APPLY(" + recursion + ")";
|
||||||
|
actual = getQueryResults(query);
|
||||||
|
|
||||||
|
assertEqual(actual, [ 'foo bar' ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -203,6 +203,7 @@ function ahuacatlFunctionsBruteTestSuite () {
|
||||||
errors.ERROR_ARANGO_CROSS_COLLECTION_REQUEST.code,
|
errors.ERROR_ARANGO_CROSS_COLLECTION_REQUEST.code,
|
||||||
errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code,
|
errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code,
|
||||||
errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code,
|
errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code,
|
||||||
|
errors.ERROR_QUERY_FUNCTION_NAME_UNKNOWN.code,
|
||||||
errors.ERROR_GRAPH_NOT_FOUND.code,
|
errors.ERROR_GRAPH_NOT_FOUND.code,
|
||||||
errors.ERROR_GRAPH_INVALID_GRAPH.code,
|
errors.ERROR_GRAPH_INVALID_GRAPH.code,
|
||||||
].indexOf(err.errorNum) !== -1;
|
].indexOf(err.errorNum) !== -1;
|
||||||
|
|
|
@ -30,7 +30,6 @@ fi
|
||||||
--package DEB \
|
--package DEB \
|
||||||
--buildDir build-${EP}deb-dbg \
|
--buildDir build-${EP}deb-dbg \
|
||||||
--targetDir /var/tmp/ \
|
--targetDir /var/tmp/ \
|
||||||
--jemalloc \
|
|
||||||
--noopt \
|
--noopt \
|
||||||
${MOREOPTS} \
|
${MOREOPTS} \
|
||||||
$@
|
$@
|
||||||
|
|
Loading…
Reference in New Issue