mirror of https://gitee.com/bigwinds/arangodb
AQL user functions
This commit is contained in:
parent
31f4edde25
commit
ed10d9b6aa
|
@ -210,6 +210,7 @@ endif
|
|||
################################################################################
|
||||
|
||||
SHELL_COMMON = @top_srcdir@/js/common/tests/shell-require.js \
|
||||
@top_srcdir@/js/common/tests/shell-aqlfunctions.js \
|
||||
@top_srcdir@/js/common/tests/shell-attributes.js \
|
||||
@top_srcdir@/js/common/tests/shell-collection.js \
|
||||
@top_srcdir@/js/common/tests/shell-collection-volatile.js \
|
||||
|
|
|
@ -100,6 +100,64 @@ inline static void InitNode (TRI_aql_context_t* const context,
|
|||
TRI_RegisterNodeContextAql(context, node);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief create an AST function call node
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static TRI_aql_node_t* CreateNodeInternalFcall (TRI_aql_context_t* const context,
|
||||
const char* const name,
|
||||
const char* const internalName,
|
||||
const TRI_aql_node_t* const parameters) {
|
||||
CREATE_NODE(TRI_AQL_NODE_FCALL)
|
||||
|
||||
TRI_AQL_NODE_DATA(node) = 0;
|
||||
|
||||
{
|
||||
TRI_aql_function_t* function;
|
||||
TRI_associative_pointer_t* functions;
|
||||
|
||||
assert(context->_vocbase);
|
||||
functions = context->_vocbase->_functions;
|
||||
assert(functions);
|
||||
|
||||
function = TRI_GetByExternalNameFunctionAql(functions, internalName);
|
||||
|
||||
if (function == NULL) {
|
||||
// function name is unknown
|
||||
TRI_SetErrorContextAql(context, TRI_ERROR_QUERY_FUNCTION_NAME_UNKNOWN, name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// validate function call arguments
|
||||
if (! TRI_ValidateArgsFunctionAql(context, function, parameters)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// initialise
|
||||
ADD_MEMBER(parameters)
|
||||
TRI_AQL_NODE_DATA(node) = function;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief create an AST function call node
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static TRI_aql_node_t* CreateNodeUserFcall (TRI_aql_context_t* const context,
|
||||
const char* const name,
|
||||
char* const internalName,
|
||||
const TRI_aql_node_t* const parameters) {
|
||||
CREATE_NODE(TRI_AQL_NODE_FCALL_USER)
|
||||
|
||||
// we'll take ownership of the function name now
|
||||
TRI_AQL_NODE_STRING(node) = internalName;
|
||||
ADD_MEMBER(parameters)
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @}
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -934,38 +992,33 @@ TRI_aql_node_t* TRI_CreateNodeArrayElementAql (TRI_aql_context_t* const context,
|
|||
TRI_aql_node_t* TRI_CreateNodeFcallAql (TRI_aql_context_t* const context,
|
||||
const char* const name,
|
||||
const TRI_aql_node_t* const parameters) {
|
||||
CREATE_NODE(TRI_AQL_NODE_FCALL)
|
||||
|
||||
// initialise
|
||||
TRI_AQL_NODE_DATA(node) = NULL;
|
||||
TRI_aql_node_t* node;
|
||||
char* upperName;
|
||||
|
||||
if (name == NULL) {
|
||||
ABORT_OOM
|
||||
}
|
||||
|
||||
{
|
||||
TRI_aql_function_t* function;
|
||||
TRI_associative_pointer_t* functions;
|
||||
if (strchr(name, TRI_AQL_NAMESPACE_SEPARATOR_CHAR) != NULL) {
|
||||
upperName = TRI_UpperAsciiStringZ(TRI_CORE_MEM_ZONE, name);
|
||||
}
|
||||
else {
|
||||
upperName = TRI_Concatenate2String(TRI_AQL_DEFAULT_PREFIX, TRI_UpperAsciiStringZ(TRI_UNKNOWN_MEM_ZONE, name));
|
||||
}
|
||||
|
||||
assert(context->_vocbase);
|
||||
functions = context->_vocbase->_functions;
|
||||
assert(functions);
|
||||
|
||||
function = TRI_GetByExternalNameFunctionAql(functions, name);
|
||||
|
||||
if (! function) {
|
||||
// function name is unknown
|
||||
TRI_SetErrorContextAql(context, TRI_ERROR_QUERY_FUNCTION_NAME_UNKNOWN, name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// validate function call arguments
|
||||
if (! TRI_ValidateArgsFunctionAql(context, function, parameters)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ADD_MEMBER(parameters)
|
||||
TRI_AQL_NODE_DATA(node) = function;
|
||||
if (upperName == NULL) {
|
||||
ABORT_OOM
|
||||
}
|
||||
|
||||
if (*upperName == '_') {
|
||||
// default internal namespace
|
||||
node = CreateNodeInternalFcall(context, name, upperName, parameters);
|
||||
TRI_Free(TRI_CORE_MEM_ZONE, upperName);
|
||||
}
|
||||
else {
|
||||
// user namespace
|
||||
node = CreateNodeUserFcall(context, name, upperName, parameters);
|
||||
// upperName intentionally not freed!
|
||||
}
|
||||
|
||||
return node;
|
||||
|
|
|
@ -1427,7 +1427,8 @@ static void ProcessArgList (TRI_aql_codegen_js_t* const generator,
|
|||
ScopeOutput(generator, ", ");
|
||||
}
|
||||
|
||||
if (parameter->_type == TRI_AQL_NODE_COLLECTION &&
|
||||
if (function != NULL &&
|
||||
parameter->_type == TRI_AQL_NODE_COLLECTION &&
|
||||
TRI_ConvertParameterFunctionAql(function, i)) {
|
||||
// collection arguments will be created as string argument => e.g. "users"
|
||||
TRI_aql_node_t* nameNode = TRI_AQL_NODE_MEMBER(parameter, 0);
|
||||
|
@ -1880,8 +1881,8 @@ static void ProcessSubquery (TRI_aql_codegen_js_t* const generator,
|
|||
/// @brief generate code for function calls
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void ProcessFcall (TRI_aql_codegen_js_t* const generator,
|
||||
const TRI_aql_node_t* const node) {
|
||||
static void ProcessFcallInternal (TRI_aql_codegen_js_t* const generator,
|
||||
const TRI_aql_node_t* const node) {
|
||||
ScopeOutput(generator, "aql.");
|
||||
ScopeOutput(generator, TRI_GetInternalNameFunctionAql((TRI_aql_function_t*) TRI_AQL_NODE_DATA(node)));
|
||||
ScopeOutput(generator, "(");
|
||||
|
@ -1889,6 +1890,19 @@ static void ProcessFcall (TRI_aql_codegen_js_t* const generator,
|
|||
ScopeOutput(generator, ")");
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief generate code for function calls
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void ProcessFcallUser (TRI_aql_codegen_js_t* const generator,
|
||||
const TRI_aql_node_t* const node) {
|
||||
ScopeOutput(generator, "aql.FCALL_USER(");
|
||||
ScopeOutputQuoted(generator, TRI_AQL_NODE_STRING(node));
|
||||
ScopeOutput(generator, ", [");
|
||||
ProcessArgList(generator, NULL, TRI_AQL_NODE_MEMBER(node, 0));
|
||||
ScopeOutput(generator, "])");
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief generate code for a scope start
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -2402,7 +2416,10 @@ static void ProcessNode (TRI_aql_codegen_js_t* const generator, const TRI_aql_no
|
|||
ProcessTernary(generator, node);
|
||||
break;
|
||||
case TRI_AQL_NODE_FCALL:
|
||||
ProcessFcall(generator, node);
|
||||
ProcessFcallInternal(generator, node);
|
||||
break;
|
||||
case TRI_AQL_NODE_FCALL_USER:
|
||||
ProcessFcallUser(generator, node);
|
||||
break;
|
||||
case TRI_AQL_NODE_FOR:
|
||||
ProcessFor(generator, node);
|
||||
|
|
|
@ -423,7 +423,57 @@ char* TRI_RegisterStringAql (TRI_aql_context_t* const context,
|
|||
ABORT_OOM
|
||||
}
|
||||
|
||||
TRI_PushBackVectorPointer(&context->_memory._strings, copy);
|
||||
if (TRI_PushBackVectorPointer(&context->_memory._strings, copy) != TRI_ERROR_NO_ERROR) {
|
||||
TRI_FreeString(TRI_UNKNOWN_MEM_ZONE, copy);
|
||||
ABORT_OOM
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register a combined string
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
char* TRI_RegisterString2Aql (TRI_aql_context_t* const context,
|
||||
const char* const s1,
|
||||
const char* const s2) {
|
||||
char* copy;
|
||||
|
||||
copy = TRI_Concatenate2StringZ(TRI_UNKNOWN_MEM_ZONE, s1, s2);
|
||||
|
||||
if (copy == NULL) {
|
||||
ABORT_OOM
|
||||
}
|
||||
|
||||
if (TRI_PushBackVectorPointer(&context->_memory._strings, copy) != TRI_ERROR_NO_ERROR) {
|
||||
TRI_FreeString(TRI_UNKNOWN_MEM_ZONE, copy);
|
||||
ABORT_OOM
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register a combined string
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
char* TRI_RegisterString3Aql (TRI_aql_context_t* const context,
|
||||
const char* const s1,
|
||||
const char* const s2,
|
||||
const char* const s3) {
|
||||
char* copy;
|
||||
|
||||
copy = TRI_Concatenate3StringZ(TRI_UNKNOWN_MEM_ZONE, s1, s2, s3);
|
||||
|
||||
if (copy == NULL) {
|
||||
ABORT_OOM
|
||||
}
|
||||
|
||||
if (TRI_PushBackVectorPointer(&context->_memory._strings, copy) != TRI_ERROR_NO_ERROR) {
|
||||
TRI_FreeString(TRI_UNKNOWN_MEM_ZONE, copy);
|
||||
ABORT_OOM
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
|
|
@ -143,6 +143,23 @@ char* TRI_RegisterStringAql (TRI_aql_context_t* const,
|
|||
const size_t,
|
||||
const bool);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register a combined string
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
char* TRI_RegisterString2Aql (TRI_aql_context_t* const,
|
||||
const char* const,
|
||||
const char* const);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register a combined string
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
char* TRI_RegisterString3Aql (TRI_aql_context_t* const,
|
||||
const char* const,
|
||||
const char* const,
|
||||
const char* const);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register a node
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -603,6 +603,22 @@ bool TRI_NodeStringAql (TRI_string_buffer_t* const buffer,
|
|||
return TRI_AppendStringStringBuffer(buffer, ")") == TRI_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
case TRI_AQL_NODE_FCALL_USER: {
|
||||
if (TRI_AppendStringStringBuffer(buffer, TRI_AQL_NODE_STRING(node)) != TRI_ERROR_NO_ERROR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TRI_AppendStringStringBuffer(buffer, "(") != TRI_ERROR_NO_ERROR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! TRI_NodeStringAql(buffer, TRI_AQL_NODE_MEMBER(node, 0))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return TRI_AppendStringStringBuffer(buffer, ")") == TRI_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
case TRI_AQL_NODE_EXPAND: {
|
||||
return TRI_NodeStringAql(buffer, TRI_AQL_NODE_MEMBER(node, 3));
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define REGISTER_FUNCTION(internalName, externalName, deterministic, group, argPattern, optimiseCallback) \
|
||||
result &= TRI_RegisterFunctionAql(functions, internalName, externalName, deterministic, group, argPattern, optimiseCallback)
|
||||
result &= TRI_RegisterFunctionAql(functions, TRI_AQL_DEFAULT_PREFIX internalName, externalName, deterministic, group, argPattern, optimiseCallback)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief shorthand to check an argument and return an error if it is invalid
|
||||
|
@ -680,10 +680,11 @@ void TRI_FreeFunctionsAql (TRI_associative_pointer_t* functions) {
|
|||
|
||||
for (i = 0; i < functions->_nrAlloc; ++i) {
|
||||
TRI_aql_function_t* function = (TRI_aql_function_t*) functions->_table[i];
|
||||
if (!function) {
|
||||
if (function == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TRI_Free(TRI_UNKNOWN_MEM_ZONE, function->_argPattern);
|
||||
TRI_Free(TRI_UNKNOWN_MEM_ZONE, function->_externalName);
|
||||
TRI_Free(TRI_UNKNOWN_MEM_ZONE, function->_internalName);
|
||||
TRI_Free(TRI_UNKNOWN_MEM_ZONE, function);
|
||||
|
@ -707,13 +708,14 @@ TRI_aql_function_t* TRI_GetByExternalNameFunctionAql (TRI_associative_pointer_t*
|
|||
|
||||
// normalize the name by upper-casing it
|
||||
upperName = TRI_UpperAsciiStringZ(TRI_UNKNOWN_MEM_ZONE, externalName);
|
||||
|
||||
if (upperName == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
function = (TRI_aql_function_t*) TRI_LookupByKeyAssociativePointer(functions, (void*) upperName);
|
||||
TRI_Free(TRI_UNKNOWN_MEM_ZONE, upperName);
|
||||
|
||||
|
||||
return function;
|
||||
}
|
||||
|
||||
|
@ -744,21 +746,30 @@ bool TRI_RegisterFunctionAql (TRI_associative_pointer_t* functions,
|
|||
return false;
|
||||
}
|
||||
|
||||
function->_externalName = TRI_DuplicateStringZ(TRI_UNKNOWN_MEM_ZONE, externalName);
|
||||
function->_externalName = TRI_UpperAsciiStringZ(TRI_UNKNOWN_MEM_ZONE, externalName);
|
||||
if (function->_externalName == NULL) {
|
||||
TRI_Free(TRI_UNKNOWN_MEM_ZONE, function);
|
||||
return false;
|
||||
}
|
||||
|
||||
// normalize name by upper-casing it
|
||||
function->_internalName = TRI_UpperAsciiStringZ(TRI_UNKNOWN_MEM_ZONE, internalName);
|
||||
function->_internalName = TRI_DuplicateStringZ(TRI_UNKNOWN_MEM_ZONE, internalName);
|
||||
if (function->_internalName == NULL) {
|
||||
TRI_Free(TRI_UNKNOWN_MEM_ZONE, function->_externalName);
|
||||
TRI_Free(TRI_UNKNOWN_MEM_ZONE, function);
|
||||
return false;
|
||||
}
|
||||
|
||||
function->_argPattern = TRI_DuplicateStringZ(TRI_UNKNOWN_MEM_ZONE, argPattern);
|
||||
if (function->_argPattern == NULL) {
|
||||
TRI_Free(TRI_UNKNOWN_MEM_ZONE, function->_internalName);
|
||||
TRI_Free(TRI_UNKNOWN_MEM_ZONE, function->_externalName);
|
||||
TRI_Free(TRI_UNKNOWN_MEM_ZONE, function);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (TRI_InsertKeyAssociativePointer(functions, externalName, function, false)) {
|
||||
if (TRI_InsertKeyAssociativePointer(functions, function->_externalName, function, false)) {
|
||||
// function already registered
|
||||
TRI_Free(TRI_UNKNOWN_MEM_ZONE, function->_externalName);
|
||||
TRI_Free(TRI_UNKNOWN_MEM_ZONE, function->_internalName);
|
||||
|
@ -768,7 +779,6 @@ bool TRI_RegisterFunctionAql (TRI_associative_pointer_t* functions,
|
|||
|
||||
function->_isDeterministic = isDeterministic;
|
||||
function->_isGroup = isGroup;
|
||||
function->_argPattern = argPattern;
|
||||
function->optimise = optimise;
|
||||
|
||||
// set minArgs and maxArgs
|
||||
|
|
|
@ -36,10 +36,55 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- forward declarations
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
struct TRI_aql_context_s;
|
||||
struct TRI_aql_field_access_s;
|
||||
struct TRI_associative_pointer_s;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- public defines
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @addtogroup Ahuacatl
|
||||
/// @{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief default namespace for aql functions
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define TRI_AQL_DEFAULT_NAMESPACE "_AQL"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief separator between namespace and function name
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define TRI_AQL_NAMESPACE_SEPARATOR ":"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief separator between namespace and function name
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define TRI_AQL_NAMESPACE_SEPARATOR_CHAR ':'
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief default namespace for aql functions
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define TRI_AQL_DEFAULT_PREFIX TRI_AQL_DEFAULT_NAMESPACE TRI_AQL_NAMESPACE_SEPARATOR
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @}
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- public types
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @addtogroup Ahuacatl
|
||||
/// @{
|
||||
|
@ -54,13 +99,26 @@ typedef struct TRI_aql_function_s {
|
|||
char* _internalName;
|
||||
bool _isDeterministic;
|
||||
bool _isGroup;
|
||||
const char* _argPattern;
|
||||
char* _argPattern;
|
||||
size_t _minArgs;
|
||||
size_t _maxArgs;
|
||||
void (*optimise)(const TRI_aql_node_t* const, struct TRI_aql_context_s* const, struct TRI_aql_field_access_s*);
|
||||
}
|
||||
TRI_aql_function_t;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @}
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- public functions
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @addtogroup Ahuacatl
|
||||
/// @{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief initialise the array with the function declarations
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -149,6 +149,7 @@ void Ahuacatlerror (YYLTYPE* locp, TRI_aql_context_t* const context, const char*
|
|||
%type <node> operator_binary;
|
||||
%type <node> operator_ternary;
|
||||
%type <node> function_call;
|
||||
%type <strval> function_name;
|
||||
%type <node> optional_function_call_arguments;
|
||||
%type <node> function_arguments_list;
|
||||
%type <node> compound_type;
|
||||
|
@ -459,10 +460,30 @@ expression:
|
|||
}
|
||||
;
|
||||
|
||||
function_call:
|
||||
function_name:
|
||||
T_STRING {
|
||||
$$ = $1;
|
||||
|
||||
if ($$ == NULL) {
|
||||
ABORT_OOM
|
||||
}
|
||||
}
|
||||
| function_name T_COLON T_STRING {
|
||||
if ($1 == NULL || $3 == NULL) {
|
||||
ABORT_OOM
|
||||
}
|
||||
|
||||
$$ = TRI_RegisterString3Aql(context, $1, ":", $3);
|
||||
|
||||
if ($$ == NULL) {
|
||||
ABORT_OOM
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
function_call:
|
||||
function_name {
|
||||
TRI_aql_node_t* node;
|
||||
/* function call */
|
||||
|
||||
if (! TRI_PushStackParseAql(context, $1)) {
|
||||
ABORT_OOM
|
||||
|
|
|
@ -183,6 +183,8 @@ const char* TRI_NodeNameAql (const TRI_aql_node_type_e type) {
|
|||
return "parameter";
|
||||
case TRI_AQL_NODE_FCALL:
|
||||
return "function call";
|
||||
case TRI_AQL_NODE_FCALL_USER:
|
||||
return "function call (user)";
|
||||
}
|
||||
|
||||
assert(false);
|
||||
|
|
|
@ -182,7 +182,8 @@ typedef enum {
|
|||
TRI_AQL_NODE_REFERENCE,
|
||||
TRI_AQL_NODE_ATTRIBUTE,
|
||||
TRI_AQL_NODE_PARAMETER,
|
||||
TRI_AQL_NODE_FCALL
|
||||
TRI_AQL_NODE_FCALL,
|
||||
TRI_AQL_NODE_FCALL_USER
|
||||
}
|
||||
TRI_aql_node_type_e;
|
||||
|
||||
|
|
|
@ -148,9 +148,13 @@ static TRI_aql_node_t* DumpNode (TRI_aql_statement_walker_t* const walker,
|
|||
case TRI_AQL_NODE_ATTRIBUTE_ACCESS:
|
||||
DumpString(state, node);
|
||||
break;
|
||||
|
||||
case TRI_AQL_NODE_FCALL:
|
||||
printf("name: %s\n", TRI_GetInternalNameFunctionAql((TRI_aql_function_t*) TRI_AQL_NODE_DATA(node)));
|
||||
break;
|
||||
case TRI_AQL_NODE_FCALL_USER:
|
||||
printf("name: %s\n", TRI_AQL_NODE_STRING(node));
|
||||
break;
|
||||
|
||||
case TRI_AQL_NODE_SORT_ELEMENT:
|
||||
PrintIndent(state);
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
|
||||
#include "Basics/ConditionLocker.h"
|
||||
#include "Basics/ReadLocker.h"
|
||||
#include "Basics/MutexLocker.h"
|
||||
#include "Basics/Mutex.h"
|
||||
#include "Basics/StringUtils.h"
|
||||
#include "Basics/WriteLocker.h"
|
||||
#include "Logger/Logger.h"
|
||||
|
@ -121,6 +123,8 @@ namespace {
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void ApplicationV8::V8Context::addGlobalContextMethod (string const& method) {
|
||||
MUTEX_LOCKER(_globalMethodsLock);
|
||||
|
||||
_globalMethods.push_back(method);
|
||||
}
|
||||
|
||||
|
@ -130,6 +134,8 @@ void ApplicationV8::V8Context::addGlobalContextMethod (string const& method) {
|
|||
|
||||
void ApplicationV8::V8Context::handleGlobalContextMethods () {
|
||||
v8::HandleScope scope;
|
||||
|
||||
MUTEX_LOCKER(_globalMethodsLock);
|
||||
|
||||
for (vector<string>::iterator i = _globalMethods.begin(); i != _globalMethods.end(); ++i) {
|
||||
string const& func = *i;
|
||||
|
@ -278,6 +284,7 @@ ApplicationV8::V8Context* ApplicationV8::enterContext (bool initialise) {
|
|||
context->_isolate->Enter();
|
||||
context->_context->Enter();
|
||||
|
||||
LOGGER_TRACE("entering V8 context " << context->_id);
|
||||
context->handleGlobalContextMethods();
|
||||
|
||||
if (_developmentMode && ! initialise) {
|
||||
|
@ -300,6 +307,7 @@ void ApplicationV8::exitContext (V8Context* context) {
|
|||
V8GcThread* gc = dynamic_cast<V8GcThread*>(_gcThread);
|
||||
assert(gc != 0);
|
||||
|
||||
LOGGER_TRACE("leaving V8 context " << context->_id);
|
||||
double lastGc = gc->getLastGcStamp();
|
||||
|
||||
CONDITION_LOCKER(guard, _contextCondition);
|
||||
|
@ -337,24 +345,8 @@ void ApplicationV8::exitContext (V8Context* context) {
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void ApplicationV8::addGlobalContextMethod (string const& method) {
|
||||
CONDITION_LOCKER(guard, _contextCondition);
|
||||
|
||||
for (vector<V8Context*>::iterator i = _freeContexts.begin(); i != _freeContexts.end(); ++i) {
|
||||
V8Context* context = *i;
|
||||
|
||||
context->addGlobalContextMethod(method);
|
||||
}
|
||||
|
||||
for (vector<V8Context*>::iterator i = _dirtyContexts.begin(); i != _dirtyContexts.end(); ++i) {
|
||||
V8Context* context = *i;
|
||||
|
||||
context->addGlobalContextMethod(method);
|
||||
}
|
||||
|
||||
for (set<V8Context*>::iterator i = _busyContexts.begin(); i != _busyContexts.end(); ++i) {
|
||||
V8Context* context = *i;
|
||||
|
||||
context->addGlobalContextMethod(method);
|
||||
for (size_t i = 0; i < _nrInstances; ++i) {
|
||||
_contexts[i]->addGlobalContextMethod(method);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -129,6 +129,12 @@ namespace triagens {
|
|||
|
||||
void handleGlobalContextMethods ();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief mutex to protect _globalMethods
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
basics::Mutex _globalMethodsLock;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief open global methods
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -110,6 +110,9 @@
|
|||
"ERROR_QUERY_FAIL_CALLED" : { "code" : 1569, "message" : "FAIL(%s) called" },
|
||||
"ERROR_QUERY_GEO_INDEX_MISSING" : { "code" : 1570, "message" : "no suitable geo index found for geo restriction on '%s'" },
|
||||
"ERROR_QUERY_FULLTEXT_INDEX_MISSING" : { "code" : 1571, "message" : "no suitable fulltext index found for fulltext query on '%s'" },
|
||||
"ERROR_QUERY_FUNCTION_INVALID_NAME" : { "code" : 1580, "message" : "invalid user function name" },
|
||||
"ERROR_QUERY_FUNCTION_INVALID_CODE" : { "code" : 1581, "message" : "invalid user function code" },
|
||||
"ERROR_QUERY_FUNCTION_NOT_FOUND" : { "code" : 1582, "message" : "user function not found" },
|
||||
"ERROR_CURSOR_NOT_FOUND" : { "code" : 1600, "message" : "cursor not found" },
|
||||
"ERROR_TRANSACTION_INCOMPLETE" : { "code" : 1650, "message" : "transaction definition is incomplete" },
|
||||
"ERROR_TRANSACTION_INVALID_STATE" : { "code" : 1651, "message" : "invalid transaction state" },
|
||||
|
|
|
@ -80,6 +80,19 @@
|
|||
/// @{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief reloads the AQL user functions
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
internal.reloadAqlFunctions = function () {
|
||||
if (typeof internal.arango !== 'undefined') {
|
||||
internal.arango.POST("/_admin/aql/reload", "");
|
||||
return;
|
||||
}
|
||||
|
||||
throw "not connected";
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief flushes the module cache of the server
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -158,6 +158,21 @@ actions.defineHttp({
|
|||
}
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief reloads the AQL user functions
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
actions.defineHttp({
|
||||
url : "_admin/aql/reload",
|
||||
context : "admin",
|
||||
prefix : false,
|
||||
|
||||
callback : function (req, res) {
|
||||
internal.reloadAqlFunctions();
|
||||
actions.resultOk(req, res, actions.HTTP_OK);
|
||||
}
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @fn JSF_GET_admin_routing_reloads
|
||||
/// @brief reloads the routing information
|
||||
|
|
|
@ -80,6 +80,19 @@
|
|||
/// @{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief reloads the AQL user functions
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
internal.reloadAqlFunctions = function () {
|
||||
if (typeof internal.arango !== 'undefined') {
|
||||
internal.arango.POST("/_admin/aql/reload", "");
|
||||
return;
|
||||
}
|
||||
|
||||
throw "not connected";
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief flushes the module cache of the server
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -110,6 +110,9 @@
|
|||
"ERROR_QUERY_FAIL_CALLED" : { "code" : 1569, "message" : "FAIL(%s) called" },
|
||||
"ERROR_QUERY_GEO_INDEX_MISSING" : { "code" : 1570, "message" : "no suitable geo index found for geo restriction on '%s'" },
|
||||
"ERROR_QUERY_FULLTEXT_INDEX_MISSING" : { "code" : 1571, "message" : "no suitable fulltext index found for fulltext query on '%s'" },
|
||||
"ERROR_QUERY_FUNCTION_INVALID_NAME" : { "code" : 1580, "message" : "invalid user function name" },
|
||||
"ERROR_QUERY_FUNCTION_INVALID_CODE" : { "code" : 1581, "message" : "invalid user function code" },
|
||||
"ERROR_QUERY_FUNCTION_NOT_FOUND" : { "code" : 1582, "message" : "user function not found" },
|
||||
"ERROR_CURSOR_NOT_FOUND" : { "code" : 1600, "message" : "cursor not found" },
|
||||
"ERROR_TRANSACTION_INCOMPLETE" : { "code" : 1650, "message" : "transaction definition is incomplete" },
|
||||
"ERROR_TRANSACTION_INVALID_STATE" : { "code" : 1651, "message" : "invalid transaction state" },
|
||||
|
|
|
@ -0,0 +1,226 @@
|
|||
/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */
|
||||
/*global require, exports */
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief AQL user functions management
|
||||
///
|
||||
/// @file
|
||||
///
|
||||
/// DISCLAIMER
|
||||
///
|
||||
/// Copyright 2012 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 triAGENS GmbH, Cologne, Germany
|
||||
///
|
||||
/// @author Jan Steemann
|
||||
/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var internal = require("internal");
|
||||
var arangodb = require("org/arangodb");
|
||||
var db = arangodb.db;
|
||||
var ArangoError = require("org/arangodb/arango-error").ArangoError;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- module "org/arangodb/aql/functions"
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- private functions
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @addtogroup Ahuacatl
|
||||
/// @{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief validate a function name
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var validateName = function (name) {
|
||||
if (typeof name !== 'string' ||
|
||||
! name.match(/^[a-zA-Z0-9_]+(:[a-zA-Z0-9_]+)+$/) ||
|
||||
name.substr(0, 1) === "_") {
|
||||
var err = new ArangoError();
|
||||
err.errorNum = arangodb.errors.ERROR_QUERY_FUNCTION_INVALID_NAME.code;
|
||||
err.errorMessage = arangodb.errors.ERROR_QUERY_FUNCTION_INVALID_NAME.message;
|
||||
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief validate user function code
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var stringifyFunction = function (code, name) {
|
||||
if (typeof code === 'function') {
|
||||
code = String(code);
|
||||
}
|
||||
|
||||
if (typeof code === 'string') {
|
||||
code = "(" + code + ")";
|
||||
|
||||
if (! internal.parse) {
|
||||
// no parsing possible. assume always valid
|
||||
return code;
|
||||
}
|
||||
|
||||
try {
|
||||
if (internal.parse(code, name)) {
|
||||
// parsing successful
|
||||
return code;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
// fall-through intentional
|
||||
|
||||
var err = new ArangoError();
|
||||
err.errorNum = arangodb.errors.ERROR_QUERY_FUNCTION_INVALID_CODE.code;
|
||||
err.errorMessage = arangodb.errors.ERROR_QUERY_FUNCTION_INVALID_CODE.message;
|
||||
|
||||
throw err;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief return the _aqlfunctions collection
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var getStorage = function () {
|
||||
var functions = db._collection("_aqlfunctions");
|
||||
|
||||
if (functions === null) {
|
||||
var err = new ArangoError();
|
||||
err.errorNum = arangodb.errors.ERROR_ARANGO_COLLECTION_NOT_FOUND.code;
|
||||
err.errorMessage = "collection _aqlfunctions not found";
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
return functions;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @}
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- public functions
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @addtogroup Ahuacatl
|
||||
/// @{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief delete an existing AQL user function
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var unregisterFunction = function (name) {
|
||||
var func = null;
|
||||
|
||||
validateName(name);
|
||||
|
||||
try {
|
||||
func = getStorage().document(name.toUpperCase());
|
||||
}
|
||||
catch (err1) {
|
||||
}
|
||||
|
||||
if (func === null) {
|
||||
var err = new ArangoError();
|
||||
err.errorNum = arangodb.errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code;
|
||||
err.errorMessage = arangodb.errors.ERROR_QUERY_FUNCTION_NOT_FOUND.message;
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
getStorage().remove(func._id);
|
||||
internal.reloadAqlFunctions();
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register an AQL user function
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var registerFunction = function (name, code, isDeterministic) {
|
||||
// validate input
|
||||
validateName(name);
|
||||
code = stringifyFunction(code, name);
|
||||
|
||||
try {
|
||||
unregisterFunction(name);
|
||||
}
|
||||
catch (err) {
|
||||
}
|
||||
|
||||
var data = {
|
||||
_key: name.toUpperCase(),
|
||||
name: name,
|
||||
code: code,
|
||||
isDeterministic: isDeterministic || false
|
||||
};
|
||||
|
||||
getStorage().save(data);
|
||||
internal.reloadAqlFunctions();
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief reloads the AQL user functons
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var reloadFunctions = function () {
|
||||
throw "cannot use abstract reload function";
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @}
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- module exports
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @addtogroup Ahuacatl
|
||||
/// @{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
exports.register = registerFunction;
|
||||
exports.unregister = unregisterFunction;
|
||||
exports.reload = reloadFunctions;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @}
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- END-OF-FILE
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Local Variables:
|
||||
// mode: outline-minor
|
||||
// outline-regexp: "/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\|/\\*jslint"
|
||||
// End:
|
||||
|
|
@ -0,0 +1,346 @@
|
|||
/*jslint indent: 2, nomen: true, maxlen: 80 */
|
||||
/*global require, assertEqual, assertTrue */
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief test the AQL user functions management
|
||||
///
|
||||
/// @file
|
||||
///
|
||||
/// DISCLAIMER
|
||||
///
|
||||
/// Copyright 2010-2012 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 triAGENS GmbH, Cologne, Germany
|
||||
///
|
||||
/// @author Jan Steemann
|
||||
/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var jsunity = require("jsunity");
|
||||
var arangodb = require("org/arangodb");
|
||||
var ERRORS = arangodb.errors;
|
||||
var db = arangodb.db;
|
||||
|
||||
var aqlfunctions = require("org/arangodb/aql/functions");
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- AQL user functions tests
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief test suite
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function AqlFunctionsSuite () {
|
||||
|
||||
var unregister = function (name) {
|
||||
try {
|
||||
aqlfunctions.unregister(name);
|
||||
}
|
||||
catch (err) {
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief set up
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
setUp : function () {
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief tear down
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
tearDown : function () {
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register a function
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testRegisterFunc1 : function () {
|
||||
unregister("UnitTests:tryme:foo");
|
||||
aqlfunctions.register("UnitTests:tryme:foo", function (what) { return what * 2; }, true);
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register a function
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testRegisterFunc2 : function () {
|
||||
unregister("UnitTests:tryme");
|
||||
aqlfunctions.register("UnitTests:tryme", function (what) { return what * 2; }, true);
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register a function
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testRegisterString1 : function () {
|
||||
unregister("UnitTests:tryme");
|
||||
aqlfunctions.register("UnitTests:tryme", "function (what) { return what * 2; }", true);
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register a function
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testRegisterString2 : function () {
|
||||
unregister("UnitTests:tryme:foo");
|
||||
aqlfunctions.register("UnitTests:tryme:foo", "function (what) { return what * 2; }", true);
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief re-register a function
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testReRegister : function () {
|
||||
unregister("UnitTests:tryme");
|
||||
aqlfunctions.register("UnitTests:tryme", function (what) { return what * 2; }, true);
|
||||
aqlfunctions.register("UnitTests:tryme", function (what) { return what * 2; }, true);
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register a function with an invalid name
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testRegisterInvalidName1 : function () {
|
||||
try {
|
||||
aqlfunctions.register("foo", function (what) { return what * 2; }, true);
|
||||
fail();
|
||||
}
|
||||
catch (err) {
|
||||
assertEqual(ERRORS.ERROR_QUERY_FUNCTION_INVALID_NAME.code, err.errorNum);
|
||||
}
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register a function with an invalid name
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testRegisterInvalidName2 : function () {
|
||||
try {
|
||||
aqlfunctions.register("_test", function (what) { return what * 2; }, true);
|
||||
fail();
|
||||
}
|
||||
catch (err) {
|
||||
assertEqual(ERRORS.ERROR_QUERY_FUNCTION_INVALID_NAME.code, err.errorNum);
|
||||
}
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register a function with an invalid name
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testRegisterInvalidName3 : function () {
|
||||
try {
|
||||
aqlfunctions.register("_test:foo", function (what) { return what * 2; }, true);
|
||||
fail();
|
||||
}
|
||||
catch (err) {
|
||||
assertEqual(ERRORS.ERROR_QUERY_FUNCTION_INVALID_NAME.code, err.errorNum);
|
||||
}
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register a function with an invalid name
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testRegisterInvalidName4 : function () {
|
||||
try {
|
||||
aqlfunctions.register("test:", function (what) { return what * 2; }, true);
|
||||
fail();
|
||||
}
|
||||
catch (err) {
|
||||
assertEqual(ERRORS.ERROR_QUERY_FUNCTION_INVALID_NAME.code, err.errorNum);
|
||||
}
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register a function with an invalid body
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testRegisterInvalidCode1 : function () {
|
||||
unregister("UnitTests:tryme");
|
||||
try {
|
||||
aqlfunctions.register("UnitTests:tryme", "function (what) { ", true);
|
||||
fail();
|
||||
}
|
||||
catch (err) {
|
||||
assertEqual(ERRORS.ERROR_QUERY_FUNCTION_INVALID_CODE.code, err.errorNum);
|
||||
}
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register a function with an invalid body
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testRegisterInvalidCode2 : function () {
|
||||
unregister("UnitTests:tryme");
|
||||
try {
|
||||
aqlfunctions.register("UnitTests:tryme", 1234, true);
|
||||
fail();
|
||||
}
|
||||
catch (err) {
|
||||
assertEqual(ERRORS.ERROR_QUERY_FUNCTION_INVALID_CODE.code, err.errorNum);
|
||||
}
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register a function and unregister it
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testUnregister : function () {
|
||||
unregister("UnitTests:tryme");
|
||||
aqlfunctions.register("UnitTests:tryme", function (what) { return what * 4; }, true);
|
||||
aqlfunctions.unregister("UnitTests:tryme");
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief unregister an unknown function
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testUnregisterUnknown : function () {
|
||||
unregister("UnitTests:tryme");
|
||||
|
||||
try {
|
||||
aqlfunctions.unregister("UnitTests:tryme");
|
||||
fail();
|
||||
}
|
||||
catch (err) {
|
||||
assertEqual(ERRORS.ERROR_QUERY_FUNCTION_NOT_FOUND.code, err.errorNum);
|
||||
}
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register a function and run a query
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testQuery1 : function () {
|
||||
unregister("UnitTests:tryme");
|
||||
aqlfunctions.register("UnitTests:tryme", function (what) { return what * 2; }, true);
|
||||
|
||||
var actual = db._createStatement({ query: "RETURN UnitTests:tryme(4)" }).execute().toArray();
|
||||
assertEqual([ 8 ], actual);
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register a function and run a query
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testQuery2 : function () {
|
||||
unregister("UnitTests:tryme");
|
||||
unregister("UnitTests:foo");
|
||||
aqlfunctions.register("UnitTests:tryme", function (what) { return what * 2; }, true);
|
||||
aqlfunctions.register("UnitTests:foo", function (what) { return what * 4; }, true);
|
||||
|
||||
var actual = db._createStatement({ query: "RETURN UnitTests:tryme(4) + UnitTests:foo(9)" }).execute().toArray();
|
||||
assertEqual([ 4 * 2 + 9 * 4 ], actual);
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register a function and run a query
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testQueryReturnUndefined : function () {
|
||||
unregister("UnitTests:tryme");
|
||||
aqlfunctions.register("UnitTests:tryme", function (what) { }, true);
|
||||
|
||||
var actual = db._createStatement({ query: "RETURN UnitTests:tryme(4)" }).execute().toArray();
|
||||
assertEqual([ null ], actual);
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register a function and run a query
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testQueryReturnNan : function () {
|
||||
unregister("UnitTests:tryme");
|
||||
aqlfunctions.register("UnitTests:tryme", function (what) { return 1 / 0; }, true);
|
||||
|
||||
var actual = db._createStatement({ query: "RETURN UnitTests:tryme(4)" }).execute().toArray();
|
||||
assertEqual([ null ], actual);
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register a function and run a query
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testQueryReturnNull : function () {
|
||||
unregister("UnitTests:tryme");
|
||||
aqlfunctions.register("UnitTests:tryme", function (what) { return null; }, true);
|
||||
|
||||
var actual = db._createStatement({ query: "RETURN UnitTests:tryme(4)" }).execute().toArray();
|
||||
assertEqual([ null ], actual);
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register a function and run a query
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testQueryReturnTrue : function () {
|
||||
unregister("UnitTests:tryme");
|
||||
aqlfunctions.register("UnitTests:tryme", function (what) { return true; }, true);
|
||||
|
||||
var actual = db._createStatement({ query: "RETURN UnitTests:tryme(4)" }).execute().toArray();
|
||||
assertEqual([ true ], actual);
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register a function and run a query
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testQueryReturnFalse : function () {
|
||||
unregister("UnitTests:tryme");
|
||||
aqlfunctions.register("UnitTests:tryme", function (what) { return false; }, true);
|
||||
|
||||
var actual = db._createStatement({ query: "RETURN UnitTests:tryme(4)" }).execute().toArray();
|
||||
assertEqual([ false ], actual);
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief register a function and run a query
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testQueryReturnComplex : function () {
|
||||
unregister("UnitTests:tryme");
|
||||
aqlfunctions.register("UnitTests:tryme", function (what) { return [ true, false, null, 1, 2, -4, [ 5.5, { a: 1, "b": "def" } ] ] }, true);
|
||||
|
||||
var actual = db._createStatement({ query: "RETURN UnitTests:tryme()" }).execute().toArray();
|
||||
assertEqual([ [ true, false, null, 1, 2, -4, [ 5.5, { a: 1, "b": "def" } ] ] ], actual);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- main
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief executes the test suite
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
jsunity.run(AqlFunctionsSuite);
|
||||
|
||||
return jsunity.done();
|
||||
|
||||
// Local Variables:
|
||||
// mode: outline-minor
|
||||
// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)"
|
||||
// End:
|
|
@ -191,6 +191,15 @@
|
|||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief reloads the AQL user functions
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
internal.reloadAqlFunctions = function () {
|
||||
internal.executeGlobalContextFunction("require(\"org/arangodb/ahuacatl\").reload();");
|
||||
require("org/arangodb/ahuacatl").reload();
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief executes a string in all V8 contexts
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -34,8 +34,6 @@ var INTERNAL = require("internal");
|
|||
var TRAVERSAL = require("org/arangodb/graph/traversal");
|
||||
var ArangoError = require("org/arangodb/arango-error").ArangoError;
|
||||
|
||||
var RegexCache = { 'i' : { }, '' : { } };
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- private variables
|
||||
// -----------------------------------------------------------------------------
|
||||
|
@ -45,6 +43,18 @@ var RegexCache = { 'i' : { }, '' : { } };
|
|||
/// @{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief cache for compiled regexes
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var RegexCache = { };
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief user functions cache
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var UserFunctions = { };
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief type weight used for sorting and comparing
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -209,6 +219,44 @@ function CLONE (obj) {
|
|||
return copy;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief box a value into the AQL datatype system
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function FIX_VALUE (value) {
|
||||
var type = typeof(value), i;
|
||||
|
||||
if (value === undefined ||
|
||||
value === null ||
|
||||
(type === 'number' && (isNaN(value) || ! isFinite(value)))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (type === 'boolean' || type === 'string' || type === 'number') {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
for (i = 0; i < value.length; ++i) {
|
||||
value[i] = FIX_VALUE(value[i]);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
if (type === 'object') {
|
||||
for (i in value) {
|
||||
if (value.hasOwnProperty(i)) {
|
||||
value[i] = FIX_VALUE(value[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief get the sort type of an operand
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -317,6 +365,20 @@ function FCALL (name, parameters) {
|
|||
return name.apply(null, parameters);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief call a user function
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function FCALL_USER (name, parameters) {
|
||||
if (UserFunctions.hasOwnProperty(name)) {
|
||||
var result = UserFunctions[name].func.apply(null, parameters);
|
||||
|
||||
return FIX_VALUE(result);
|
||||
}
|
||||
|
||||
THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_NOT_FOUND, name);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief return the numeric value or undefined if it is out of range
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -3220,6 +3282,71 @@ function GRAPH_EDGES (edgeCollection,
|
|||
/// @}
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- setup / reset functions
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @addtogroup Ahuacatl
|
||||
/// @{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief reset the regex cache
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function resetRegexCache () {
|
||||
RegexCache = { 'i' : { }, '' : { } };
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief reset the user functions and reload them from the database
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function reloadUserFunctions () {
|
||||
var c;
|
||||
|
||||
UserFunctions = { };
|
||||
|
||||
c = INTERNAL.db._collection("_aqlfunctions");
|
||||
if (c === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
c.toArray().forEach(function (f) {
|
||||
var code;
|
||||
|
||||
code = "(function() { var callback = " + f.code + "; return callback; })();";
|
||||
|
||||
try {
|
||||
var res = INTERNAL.executeScript(code, undefined, "(user function " + f._key + ")");
|
||||
|
||||
UserFunctions[f._key.toUpperCase()] = {
|
||||
name: f._key,
|
||||
func: res,
|
||||
isDeterministic: f.isDeterministic || false
|
||||
};
|
||||
|
||||
}
|
||||
catch (err) {
|
||||
THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_INVALID_CODE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief reset the query engine
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function resetEngine () {
|
||||
resetRegexCache();
|
||||
reloadUserFunctions();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @}
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- MODULE EXPORTS
|
||||
// -----------------------------------------------------------------------------
|
||||
|
@ -3230,6 +3357,7 @@ function GRAPH_EDGES (edgeCollection,
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
exports.FCALL = FCALL;
|
||||
exports.FCALL_USER = FCALL_USER;
|
||||
exports.KEYS = KEYS;
|
||||
exports.GET_INDEX = GET_INDEX;
|
||||
exports.DOCUMENT_MEMBER = DOCUMENT_MEMBER;
|
||||
|
@ -3331,10 +3459,16 @@ exports.MATCHES = MATCHES;
|
|||
exports.PASSTHRU = PASSTHRU;
|
||||
exports.FAIL = FAIL;
|
||||
|
||||
exports.reload = reloadUserFunctions;
|
||||
|
||||
// initialise the query engine
|
||||
resetEngine();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @}
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- END-OF-FILE
|
||||
// -----------------------------------------------------------------------------
|
||||
|
|
|
@ -344,6 +344,11 @@
|
|||
|
||||
return true;
|
||||
});
|
||||
|
||||
// set up the collection _aqlfunctions
|
||||
addTask("setupAqlFunctions", "setup _aqlfunctions collection", function () {
|
||||
return createSystemCollection("_aqlfunctions", { waitForSync : false });
|
||||
});
|
||||
|
||||
// loop through all tasks and execute them
|
||||
console.log("Found " + allTasks.length + " defined task(s), "
|
||||
|
|
|
@ -141,6 +141,14 @@ ERROR_QUERY_FAIL_CALLED,1569,"FAIL(%s) called","Will be raised when the function
|
|||
ERROR_QUERY_GEO_INDEX_MISSING,1570,"no suitable geo index found for geo restriction on '%s'","Will be raised when a geo restriction was specified but no suitable geo index is found to resolve it."
|
||||
ERROR_QUERY_FULLTEXT_INDEX_MISSING,1571,"no suitable fulltext index found for fulltext query on '%s'","Will be raised when a fulltext query is performed on a collection without a suitable fulltext index."
|
||||
|
||||
################################################################################
|
||||
## AQL user functions
|
||||
################################################################################
|
||||
|
||||
ERROR_QUERY_FUNCTION_INVALID_NAME,1580,"invalid user function name","Will be raised when a user function with an invalid name is registered."
|
||||
ERROR_QUERY_FUNCTION_INVALID_CODE,1581,"invalid user function code","Will be raised when a user function is registered with invalid code."
|
||||
ERROR_QUERY_FUNCTION_NOT_FOUND,1582,"user function not found","Will be raised when a user function is accessed but not found."
|
||||
|
||||
################################################################################
|
||||
## ArangoDB cursor errors
|
||||
################################################################################
|
||||
|
|
|
@ -106,6 +106,9 @@ void TRI_InitialiseErrorMessages (void) {
|
|||
REG_ERROR(ERROR_QUERY_FAIL_CALLED, "FAIL(%s) called");
|
||||
REG_ERROR(ERROR_QUERY_GEO_INDEX_MISSING, "no suitable geo index found for geo restriction on '%s'");
|
||||
REG_ERROR(ERROR_QUERY_FULLTEXT_INDEX_MISSING, "no suitable fulltext index found for fulltext query on '%s'");
|
||||
REG_ERROR(ERROR_QUERY_FUNCTION_INVALID_NAME, "invalid user function name");
|
||||
REG_ERROR(ERROR_QUERY_FUNCTION_INVALID_CODE, "invalid user function code");
|
||||
REG_ERROR(ERROR_QUERY_FUNCTION_NOT_FOUND, "user function not found");
|
||||
REG_ERROR(ERROR_CURSOR_NOT_FOUND, "cursor not found");
|
||||
REG_ERROR(ERROR_TRANSACTION_INCOMPLETE, "transaction definition is incomplete");
|
||||
REG_ERROR(ERROR_TRANSACTION_INVALID_STATE, "invalid transaction state");
|
||||
|
|
|
@ -230,6 +230,12 @@ extern "C" {
|
|||
/// - 1571: @LIT{no suitable fulltext index found for fulltext query on '\%s'}
|
||||
/// Will be raised when a fulltext query is performed on a collection without
|
||||
/// a suitable fulltext index.
|
||||
/// - 1580: @LIT{invalid user function name}
|
||||
/// Will be raised when a user function with an invalid name is registered.
|
||||
/// - 1581: @LIT{invalid user function code}
|
||||
/// Will be raised when a user function is registered with invalid code.
|
||||
/// - 1582: @LIT{user function not found}
|
||||
/// Will be raised when a user function is accessed but not found.
|
||||
/// - 1600: @LIT{cursor not found}
|
||||
/// Will be raised when a cursor is requested via its id but a cursor with
|
||||
/// that id cannot be found.
|
||||
|
@ -1372,6 +1378,36 @@ void TRI_InitialiseErrorMessages (void);
|
|||
|
||||
#define TRI_ERROR_QUERY_FULLTEXT_INDEX_MISSING (1571)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief 1580: ERROR_QUERY_FUNCTION_INVALID_NAME
|
||||
///
|
||||
/// invalid user function name
|
||||
///
|
||||
/// Will be raised when a user function with an invalid name is registered.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define TRI_ERROR_QUERY_FUNCTION_INVALID_NAME (1580)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief 1581: ERROR_QUERY_FUNCTION_INVALID_CODE
|
||||
///
|
||||
/// invalid user function code
|
||||
///
|
||||
/// Will be raised when a user function is registered with invalid code.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define TRI_ERROR_QUERY_FUNCTION_INVALID_CODE (1581)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief 1582: ERROR_QUERY_FUNCTION_NOT_FOUND
|
||||
///
|
||||
/// user function not found
|
||||
///
|
||||
/// Will be raised when a user function is accessed but not found.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define TRI_ERROR_QUERY_FUNCTION_NOT_FOUND (1582)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief 1600: ERROR_CURSOR_NOT_FOUND
|
||||
///
|
||||
|
|
Loading…
Reference in New Issue