From f1f13a428427f38a7917905c99d96fd9fa787675 Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Tue, 18 Nov 2014 11:26:42 +0100 Subject: [PATCH 1/2] added several AQL functions --- CHANGELOG | 5 + .../Books/Users/Aql/ListFunctions.mdpp | 69 ++ .../Users/Aql/MiscellaneousFunctions.mdpp | 14 + UnitTests/Makefile.unittests | 2 + arangod/Aql/Executor.cpp | 13 + arangod/Aql/Function.h | 2 +- .../aardvark/frontend/js/bootstrap/errors.js | 1 + js/common/bootstrap/errors.js | 1 + js/server/modules/org/arangodb/aql.js | 400 +++++++++++- js/server/tests/aql-call-apply.js | 340 ++++++++++ js/server/tests/aql-functions-list.js | 617 ++++++++++++++++++ js/server/tests/aql-functions.js | 1 - lib/Basics/errors.dat | 1 + lib/Basics/voc-errors.cpp | 1 + lib/Basics/voc-errors.h | 14 + 15 files changed, 1477 insertions(+), 4 deletions(-) create mode 100644 js/server/tests/aql-call-apply.js create mode 100644 js/server/tests/aql-functions-list.js diff --git a/CHANGELOG b/CHANGELOG index 30552d8bb7..58b0e43563 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,11 @@ v2.4.0 (XXXX-XX-XX) * fixed deflate in SimpleHttpClient +* added AQL list functions `PUSH`, `POP`, `UNSHIFT`, `SHIFT`, `REMOVE_ALL`, + `REMOVE_VALUE`, `REMOVE_NTH` and `APPEND` + +* added AQL functions `CALL` and `APPLY` to dynamically call other functions + v2.3.0 (XXXX-XX-XX) ------------------- diff --git a/Documentation/Books/Users/Aql/ListFunctions.mdpp b/Documentation/Books/Users/Aql/ListFunctions.mdpp index e01da19f0b..366c0291c6 100644 --- a/Documentation/Books/Users/Aql/ListFunctions.mdpp +++ b/Documentation/Books/Users/Aql/ListFunctions.mdpp @@ -173,6 +173,75 @@ AQL supports the following functions to operate on list values: Note: Duplicates will be removed. +- *APPEND(list, values, unique)*: Adds all elements from the list *values* to the list + specified by *list*. If *unique* is set to true, then only those *values* will be added + that are not already contained in *list*. + The modified list is returned. All values are added at the end of the list (right side). + + /* [ 1, 2, 3, 5, 6, 9 ] */ + APPEND([ 1, 2, 3 ], [ 5, 6, 9 ]) + + /* [ 1, 2, 3, 4, 5, 9 ] */ + APPEND([ 1, 2, 3 ], [ 3, 4, 5, 2, 9 ], true) + +- *PUSH(list, value, unique)*: Adds *value* to the list specified by *list*. If + *unique* is set to true, then *value* is not added if already present in the list. + The modified list is returned. The value is added at the end of the list (right side). + + /* [ 1, 2, 3, 4 ] */ + PUSH([ 1, 2, 3 ], 4) + + /* [ 1, 2, 3 ] */ + PUSH([ 1, 2, 3 ], 2, true) + +- *UNSHIFT(list, value, unique)*: Adds *value* to the list specified by *list*. If + *unique* is set to true, then *value* is not added if already present in the list. + The modified list is returned. The value is added at the start of the list (left side). + + /* [ 4, 1, 2, 3 ] */ + UNSHIFT([ 1, 2, 3 ], 4) + + /* [ 1, 2, 3 ] */ + UNSHIFT([ 1, 2, 3 ], 2, true) + +- *POP(list)*: Removes the element at the end (right side) of *list*. The modified list + is returned. If the list is already empty or *null*, an empty list is returned. + + /* [ 1, 2, 3 ] */ + POP([ 1, 2, 3, 4 ]) + +- *SHIFT(list)*: Removes the element at the start (left side) of *list*. The modified list + is returned. If the list is already empty or *null*, an empty list is returned. + + /* [ 2, 3, 4 ] */ + SHIFT([ 1, 2, 3, 4 ]) + +- *REMOVE_VALUE(list, value, limit)*: Removes all occurrences of *value* in the list + specified by *list*. If the optional *limit* is specified, only *limit* occurrences + will be removed. + + /* [ "b", "b", "c" ] */ + REMOVE_VALUE([ "a", "b", "b", "a", "c" ], "a") + + /* [ "b", "b", "a", "c" ] */ + REMOVE_VALUE([ "a", "b", "b", "a", "c" ], "a", 1) + +- *REMOVE_ALL(list, values)*: Removes all occurrences of any of the values specified + in list *values* from the list specified by *list*. + + /* [ "b", "c", "e", "g" ] */ + REMOVE_ALL([ "a", "b", "c", "d", "e", "f", "g" ], [ "a", "f", "d" ]) + +- *REMOVE_NTH(list, position)*: Removes the element at position *position* from the + list specified by *list*. Positions start at 0. Negative positions are supported, + with -1 being the last list element. If *position* is out of bounds, the list is + returned unmodified. Otherwise, the modified list is returned. + + /* [ "a", "c", "d", "e" ] */ + REMOVE_NTH([ "a", "b", "c", "d", "e" ], 1) + + /* [ "a", "b", "c", "e" ] */ + REMOVE_NTH([ "a", "b", "c", "d", "e" ], -2) Apart from these functions, AQL also offers several language constructs (e.g. *FOR*, *SORT*, *LIMIT*, *COLLECT*) to operate on lists. diff --git a/Documentation/Books/Users/Aql/MiscellaneousFunctions.mdpp b/Documentation/Books/Users/Aql/MiscellaneousFunctions.mdpp index ed7a88dc0e..172cd57232 100644 --- a/Documentation/Books/Users/Aql/MiscellaneousFunctions.mdpp +++ b/Documentation/Books/Users/Aql/MiscellaneousFunctions.mdpp @@ -66,3 +66,17 @@ function categories: as in the index. If no suitable skiplist index is found, an error will be raised and the query will be aborted. +- *CALL(function, arg1, ..., argn)*: Dynamically calls the function with name *function* + with the arguments specified. Both built-in and user-defined functions can be called. + Arguments are passed as seperate parameters to the called function. + + /* "this" */ + CALL('SUBSTRING', 'this is a test', 0, 4) + +- *APPLY(function, arguments)*: Dynamically calls the function with name *function* + with the arguments specified. Both built-in and user-defined functions can be called. + Arguments are passed as seperate parameters to the called function. + + /* "this is" */ + APPLY('SUBSTRING', [ 'this is a test', 0, 7 ]) + diff --git a/UnitTests/Makefile.unittests b/UnitTests/Makefile.unittests index 78d34e2712..5a7510744a 100755 --- a/UnitTests/Makefile.unittests +++ b/UnitTests/Makefile.unittests @@ -533,6 +533,7 @@ unittests-shell-server-ahuacatl: SHELL_SERVER_AQL = @top_srcdir@/js/server/tests/aql-arithmetic.js \ @top_srcdir@/js/server/tests/aql-bind.js \ + @top_srcdir@/js/server/tests/aql-call-apply.js \ @top_srcdir@/js/server/tests/aql-complex.js \ @top_srcdir@/js/server/tests/aql-cross.js \ @top_srcdir@/js/server/tests/aql-edges-noncluster.js \ @@ -540,6 +541,7 @@ SHELL_SERVER_AQL = @top_srcdir@/js/server/tests/aql-arithmetic.js \ @top_srcdir@/js/server/tests/aql-explain-noncluster.js \ @top_srcdir@/js/server/tests/aql-functions.js \ @top_srcdir@/js/server/tests/aql-functions-date.js \ + @top_srcdir@/js/server/tests/aql-functions-list.js \ @top_srcdir@/js/server/tests/aql-functions-misc.js \ @top_srcdir@/js/server/tests/aql-functions-numeric.js \ @top_srcdir@/js/server/tests/aql-functions-string.js \ diff --git a/arangod/Aql/Executor.cpp b/arangod/Aql/Executor.cpp index b6aa324d73..081df26ad3 100644 --- a/arangod/Aql/Executor.cpp +++ b/arangod/Aql/Executor.cpp @@ -155,6 +155,19 @@ std::unordered_map const Executor::FunctionNames{ { "LAST", Function("LAST", "AQL_LAST", "l", true, false, true) }, { "NTH", Function("NTH", "AQL_NTH", "l,n", true, false, true) }, { "POSITION", Function("POSITION", "AQL_POSITION", "l,.|b", true, false, true) }, +// { "CALL", Function("CALL", "AQL_CALL", "l,s|.+", false, true, false) }, +// { "APPLY", Function("APPLY", "AQL_APPLY", "l,s|.", false, true, false) }, + { "CALL", Function("CALL", "AQL_CALL", "s|.+", false, true, false) }, + { "APPLY", Function("APPLY", "AQL_APPLY", "s|l", false, true, false) }, + { "PUSH", Function("PUSH", "AQL_PUSH", "l,.|b", true, false, false) }, + { "APPEND", Function("APPEND", "AQL_APPEND", "l,lz|b", true, false, false) }, + { "POP", Function("POP", "AQL_POP", "l", true, false, false) }, + { "POP", Function("POP", "AQL_POP", "l", true, false, false) }, + { "SHIFT", Function("SHIFT", "AQL_SHIFT", "l", true, false, false) }, + { "UNSHIFT", Function("UNSHIFT", "AQL_UNSHIFT", "l,.|b", true, false, false) }, + { "REMOVE_VALUE", Function("REMOVE_VALUE", "AQL_REMOVE_VALUE", "l,.|n", true, false, false) }, + { "REMOVE_ALL", Function("REMOVE_ALL", "AQL_REMOVE_ALL", "l,lz", true, false, false) }, + { "REMOVE_NTH", Function("REMOVE_NTH", "AQL_REMOVE_NTH", "l,n", true, false, false) }, // document functions { "HAS", Function("HAS", "AQL_HAS", "az,s", true, false, true) }, diff --git a/arangod/Aql/Function.h b/arangod/Aql/Function.h index bf86ce3245..8fa7414e47 100644 --- a/arangod/Aql/Function.h +++ b/arangod/Aql/Function.h @@ -177,7 +177,7 @@ namespace triagens { /// @brief maximum number of function arguments that can be used //////////////////////////////////////////////////////////////////////////////// - static size_t const MaxArguments = 1024; + static size_t const MaxArguments = 65536; }; diff --git a/js/apps/system/aardvark/frontend/js/bootstrap/errors.js b/js/apps/system/aardvark/frontend/js/bootstrap/errors.js index 67cab48c85..1a95092f2a 100644 --- a/js/apps/system/aardvark/frontend/js/bootstrap/errors.js +++ b/js/apps/system/aardvark/frontend/js/bootstrap/errors.js @@ -177,6 +177,7 @@ "ERROR_QUERY_COMPILE_TIME_OPTIONS" : { "code" : 1575, "message" : "query options must be readable at query compile time" }, "ERROR_QUERY_EXCEPTION_OPTIONS" : { "code" : 1576, "message" : "query options expected" }, "ERROR_QUERY_COLLECTION_USED_IN_EXPRESSION" : { "code" : 1577, "message" : "collection '%s' used as expression operand" }, + "ERROR_QUERY_DISALLOWED_DYNAMIC_CALL" : { "code" : 1578, "message" : "disallowed dynamic call to '%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 '%s()' not found" }, diff --git a/js/common/bootstrap/errors.js b/js/common/bootstrap/errors.js index 67cab48c85..1a95092f2a 100644 --- a/js/common/bootstrap/errors.js +++ b/js/common/bootstrap/errors.js @@ -177,6 +177,7 @@ "ERROR_QUERY_COMPILE_TIME_OPTIONS" : { "code" : 1575, "message" : "query options must be readable at query compile time" }, "ERROR_QUERY_EXCEPTION_OPTIONS" : { "code" : 1576, "message" : "query options expected" }, "ERROR_QUERY_COLLECTION_USED_IN_EXPRESSION" : { "code" : 1577, "message" : "collection '%s' used as expression operand" }, + "ERROR_QUERY_DISALLOWED_DYNAMIC_CALL" : { "code" : 1578, "message" : "disallowed dynamic call to '%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 '%s()' not found" }, diff --git a/js/server/modules/org/arangodb/aql.js b/js/server/modules/org/arangodb/aql.js index 9a00bd9f28..434b2fc173 100644 --- a/js/server/modules/org/arangodb/aql.js +++ b/js/server/modules/org/arangodb/aql.js @@ -544,8 +544,7 @@ function FCALL_USER (name, parameters) { if (UserFunctions[prefix].hasOwnProperty(name)) { try { - var result = UserFunctions[prefix][name].func.apply(null, parameters); - return FIX_VALUE(result); + return FIX_VALUE(UserFunctions[prefix][name].func.apply(null, parameters)); } catch (err) { WARN(name, INTERNAL.errors.ERROR_QUERY_FUNCTION_RUNTIME_ERROR, AQL_TO_STRING(err)); @@ -556,6 +555,76 @@ function FCALL_USER (name, parameters) { THROW(null, INTERNAL.errors.ERROR_QUERY_FUNCTION_NOT_FOUND, NORMALIZE_FNAME(name)); } +//////////////////////////////////////////////////////////////////////////////// +/// @brief dynamically call a function +//////////////////////////////////////////////////////////////////////////////// + +function FCALL_DYNAMIC (func, applyDirect, values, name, args) { + var toCall; + + name = AQL_TO_STRING(name).toUpperCase(); + if (name.indexOf('::') > 0) { + // user-defined function + var prefix = DB_PREFIX(); + if (! UserFunctions.hasOwnProperty(prefix)) { + reloadUserFunctions(); + } + + if (! UserFunctions.hasOwnProperty(prefix) || + ! UserFunctions[prefix].hasOwnProperty(name)) { + THROW(func, INTERNAL.errors.ERROR_QUERY_FUNCTION_NOT_FOUND, NORMALIZE_FNAME(name)); + } + + toCall = UserFunctions[prefix][name].func; + } + else { + // built-in function + if (name === "CALL" || name === "APPLY") { + THROW(func, INTERNAL.errors.ERROR_QUERY_DISALLOWED_DYNAMIC_CALL, NORMALIZE_FNAME(name)); + } + + if (! exports.hasOwnProperty("AQL_" + name)) { + THROW(func, INTERNAL.errors.ERROR_QUERY_FUNCTION_NOT_FOUND, NORMALIZE_FNAME(name)); + } + + toCall = exports["AQL_" + name]; + } + + if (applyDirect) { + try { + return FIX_VALUE(toCall.apply(null, args)); + } + catch (err) { + WARN(name, INTERNAL.errors.ERROR_QUERY_FUNCTION_RUNTIME_ERROR, AQL_TO_STRING(err)); + return null; + } + } + + var type = TYPEWEIGHT(values), result, i; + + if (type === TYPEWEIGHT_DOCUMENT) { + result = { }; + for (i in values) { + if (values.hasOwnProperty(i)) { + args[0] = values[i]; + result[i] = FIX_VALUE(toCall.apply(null, args)); + } + } + return result; + } + else if (type === TYPEWEIGHT_LIST) { + result = [ ]; + for (i = 0; i < values.length; ++i) { + args[0] = values[i]; + result[i] = FIX_VALUE(toCall.apply(null, args)); + } + return result; + } + + WARN(func, INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); + return null; +} + //////////////////////////////////////////////////////////////////////////////// /// @brief return the numeric value or undefined if it is out of range //////////////////////////////////////////////////////////////////////////////// @@ -2525,6 +2594,323 @@ function AQL_UNION_DISTINCT () { return result; } +/* +//////////////////////////////////////////////////////////////////////////////// +/// @brief call a function for each element in the input list +//////////////////////////////////////////////////////////////////////////////// + +function AQL_CALL (values, name) { + "use strict"; + + var args = [ null ], i; + for (i = 2; i < arguments.length; ++i) { + args.push(arguments[i]); + } + + return FCALL_DYNAMIC("CALL", false, values, name, args); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief call a function for each element in the input list +//////////////////////////////////////////////////////////////////////////////// + +function AQL_APPLY (values, name, parameters) { + "use strict"; + + var args = [ null ], i; + if (Array.isArray(parameters)) { + args = args.concat(parameters); + } + + return FCALL_DYNAMIC("APPLY", false, values, name, args); +} +*/ +//////////////////////////////////////////////////////////////////////////////// +/// @brief call a function for each element in the input list +//////////////////////////////////////////////////////////////////////////////// + +function AQL_CALL (name) { + "use strict"; + + var args = [ ], i; + for (i = 1; i < arguments.length; ++i) { + args.push(arguments[i]); + } + + return FCALL_DYNAMIC("CALL", true, null, name, args); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief call a function for each element in the input list +//////////////////////////////////////////////////////////////////////////////// + +function AQL_APPLY (name, parameters) { + "use strict"; + + var args = [ ], i; + if (Array.isArray(parameters)) { + args = args.concat(parameters); + } + + return FCALL_DYNAMIC("APPLY", true, null, name, args); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief removes elements from a list +//////////////////////////////////////////////////////////////////////////////// + +function AQL_REMOVE_ALL (list, values) { + "use strict"; + + var type = TYPEWEIGHT(values); + if (type === TYPEWEIGHT_NULL) { + return list; + } + else if (type !== TYPEWEIGHT_LIST) { + WARN("REMOVE_ALL", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); + return null; + } + + type = TYPEWEIGHT(list); + if (type === TYPEWEIGHT_NULL) { + return [ ]; + } + else if (type === TYPEWEIGHT_LIST) { + var copy = [ ], i; + for (i = 0; i < list.length; ++i) { + if (RELATIONAL_IN(list[i], values)) { + continue; + } + copy.push(CLONE(list[i])); + } + return copy; + } + + WARN("REMOVE_ALL", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); + return null; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief removes an element from a list +//////////////////////////////////////////////////////////////////////////////// + +function AQL_REMOVE_VALUE (list, value, limit) { + "use strict"; + + var type = TYPEWEIGHT(list); + if (type === TYPEWEIGHT_NULL) { + return [ ]; + } + else if (type === TYPEWEIGHT_LIST) { + if (TYPEWEIGHT(limit) === TYPEWEIGHT_NULL) { + limit = -1; + } + + var copy = [ ], i; + for (i = 0; i < list.length; ++i) { + if (limit === -1 && RELATIONAL_CMP(list[i], value) === 0) { + continue; + } + else if (limit > 0 && RELATIONAL_CMP(list[i], value) === 0) { + --limit; + continue; + } + copy.push(CLONE(list[i])); + } + return copy; + } + + WARN("REMOVE_VALUE", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); + return null; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief removes an element from a list +//////////////////////////////////////////////////////////////////////////////// + +function AQL_REMOVE_NTH (list, position) { + "use strict"; + + var type = TYPEWEIGHT(list); + if (type === TYPEWEIGHT_NULL) { + return [ ]; + } + else if (type === TYPEWEIGHT_LIST) { + position = AQL_TO_NUMBER(position); + if (position >= list.length || position < - list.length) { + return list; + } + if (position === 0) { + return list.slice(1); + } + else if (position === - list.length) { + return list.slice(position + 1); + } + else if (position === list.length - 1) { + return list.slice(0, position); + } + else if (position < 0) { + return list.slice(0, list.length + position).concat(list.slice(list.length + position + 1)); + } + + return list.slice(0, position).concat(list.slice(position + 1)); + } + + WARN("REMOVE_NTH", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); + return null; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief adds an element to a list +//////////////////////////////////////////////////////////////////////////////// + +function AQL_PUSH (list, value, unique) { + "use strict"; + + var type = TYPEWEIGHT(list); + if (type === TYPEWEIGHT_NULL) { + return [ value ]; + } + else if (type === TYPEWEIGHT_LIST) { + if (AQL_TO_BOOL(unique)) { + if (RELATIONAL_IN(value, list)) { + return list; + } + } + + var copy = CLONE(list); + copy.push(value); + return copy; + } + + WARN("PUSH", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); + return null; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief adds elements to a list +//////////////////////////////////////////////////////////////////////////////// + +function AQL_APPEND (list, values, unique) { + "use strict"; + + var type = TYPEWEIGHT(values); + if (type === TYPEWEIGHT_NULL) { + return list; + } + else if (type !== TYPEWEIGHT_LIST) { + WARN("APPEND", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); + return null; + } + + if (values.length === 0) { + return list; + } + + unique = AQL_TO_BOOL(unique); + if (values.length > 1 && unique) { + // make values unique themselves + values = AQL_UNIQUE(values); + } + + type = TYPEWEIGHT(list); + if (type === TYPEWEIGHT_NULL) { + return values; + } + else if (type === TYPEWEIGHT_LIST) { + var copy = CLONE(list); + if (unique) { + var i; + for (i = 0; i < values.length; ++i) { + if (RELATIONAL_IN(values[i], list)) { + continue; + } + copy.push(values[i]); + } + return copy; + } + return copy.concat(values); + } + + WARN("APPEND", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); + return null; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief pops an element from a list +//////////////////////////////////////////////////////////////////////////////// + +function AQL_POP (list) { + "use strict"; + + var type = TYPEWEIGHT(list); + if (type === TYPEWEIGHT_NULL) { + return null; + } + else if (type === TYPEWEIGHT_LIST) { + if (list.length === 0) { + return [ ]; + } + var copy = CLONE(list); + copy.pop(); + + return copy; + } + + WARN("POP", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); + return null; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief insert an element into a list +//////////////////////////////////////////////////////////////////////////////// + +function AQL_UNSHIFT (list, value, unique) { + "use strict"; + + var type = TYPEWEIGHT(list); + if (type === TYPEWEIGHT_NULL) { + return [ value ]; + } + else if (type === TYPEWEIGHT_LIST) { + if (unique) { + if (RELATIONAL_IN(value, list)) { + return list; + } + } + + var copy = CLONE(list); + copy.unshift(value); + return copy; + } + + WARN("UNSHIFT", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief pops an element from a list +//////////////////////////////////////////////////////////////////////////////// + +function AQL_SHIFT (list) { + "use strict"; + + var type = TYPEWEIGHT(list); + if (type === TYPEWEIGHT_NULL) { + return null; + } + else if (type === TYPEWEIGHT_LIST) { + if (list.length === 0) { + return [ ]; + } + var copy = CLONE(list); + copy.shift(); + + return copy; + } + + WARN("SHIFT", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); + return null; +} //////////////////////////////////////////////////////////////////////////////// /// @brief extract a slice from a list @@ -7139,6 +7525,16 @@ exports.AQL_RANGE = AQL_RANGE; exports.AQL_UNIQUE = AQL_UNIQUE; exports.AQL_UNION = AQL_UNION; exports.AQL_UNION_DISTINCT = AQL_UNION_DISTINCT; +exports.AQL_CALL = AQL_CALL; +exports.AQL_APPLY = AQL_APPLY; +exports.AQL_REMOVE_VALUE = AQL_REMOVE_VALUE; +exports.AQL_REMOVE_ALL = AQL_REMOVE_ALL; +exports.AQL_REMOVE_NTH = AQL_REMOVE_NTH; +exports.AQL_PUSH = AQL_PUSH; +exports.AQL_APPEND = AQL_APPEND; +exports.AQL_POP = AQL_POP; +exports.AQL_SHIFT = AQL_SHIFT; +exports.AQL_UNSHIFT = AQL_UNSHIFT; exports.AQL_SLICE = AQL_SLICE; exports.AQL_MINUS = AQL_MINUS; exports.AQL_INTERSECTION = AQL_INTERSECTION; diff --git a/js/server/tests/aql-call-apply.js b/js/server/tests/aql-call-apply.js new file mode 100644 index 0000000000..f076698eba --- /dev/null +++ b/js/server/tests/aql-call-apply.js @@ -0,0 +1,340 @@ +/*jshint strict: false, maxlen: 500 */ +/*global require, assertEqual */ +//////////////////////////////////////////////////////////////////////////////// +/// @brief tests for query language, functions +/// +/// @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 internal = require("internal"); +var errors = internal.errors; +var jsunity = require("jsunity"); +var helper = require("org/arangodb/aql-helper"); +var getQueryResults = helper.getQueryResults; +var assertQueryError = helper.assertQueryError; +var assertQueryWarningAndNull = helper.assertQueryWarningAndNull; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test suite +//////////////////////////////////////////////////////////////////////////////// + +function ahuacatlCallApplyTestSuite () { + return { + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test call function +//////////////////////////////////////////////////////////////////////////////// + + testCall : function () { + var data = [ + [ "foo bar", [ "TRIM", " foo bar " ] ], + [ "foo bar", [ "TRIM", " foo bar ", "\r\n \t" ] ], + [ "..foo bar..", [ "TRIM", " ..foo bar.. " ] ], + [ "foo bar", [ "TRIM", " ..foo bar.. ", ". " ] ], + [ "foo", [ "LEFT", "foobarbaz", 3 ] ], + [ "fooba", [ "LEFT", "foobarbaz", 5 ] ], + [ "foob", [ "SUBSTRING", "foobarbaz", 0, 4 ] ], + [ "oob", [ "SUBSTRING", "foobarbaz", 1, 3 ] ], + [ "barbaz", [ "SUBSTRING", "foobarbaz", 3 ] ], + [ "FOOBARBAZ", [ "UPPER", "foobarbaz" ] ], + [ "foobarbaz", [ "lower", "FOOBARBAZ" ] ], + [ "abcfood", [ "concat", "a", "b", "c", "foo", "d" ] ], + [ 1, [ "flOoR", 1.6 ] ], + [ 17, [ "MIN", [ 23, 42, 17 ] ] ], + [ [ 1, 2, 3, 4, 7, 9, 10, 12 ], [ "UNION_DISTINCT", [ 1, 2, 3, 4 ], [ 9, 12 ], [ 2, 9, 7, 10 ] ] ], + [ { a: true, b: false, c: null }, [ "ZIP", [ "a", "b", "c" ], [ true, false, null ] ] ] + ]; + + data.forEach(function (d) { + var actual = getQueryResults("RETURN CALL(" + d[1].map(function (v) { return JSON.stringify(v); }).join(", ") + ")"); + assertEqual(d[0], actual[0], d); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test call function +//////////////////////////////////////////////////////////////////////////////// + + testCallDynamic1 : function () { + var actual = getQueryResults("FOR func IN [ 'TRIM', 'LOWER', 'UPPER' ] RETURN CALL(func, ' foObAr ')"); + assertEqual(actual, [ 'foObAr', ' foobar ', ' FOOBAR ' ]); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test call function +//////////////////////////////////////////////////////////////////////////////// + + testCallDynamic2 : function () { + var actual = getQueryResults("FOR doc IN [ { value: ' foobar', func: 'TRIM' }, { value: 'FOOBAR', func: 'LOWER' }, { value: 'foobar', func: 'UPPER' } ] RETURN CALL(doc.func, doc.value)"); + assertEqual(actual, [ 'foobar', 'foobar', 'FOOBAR' ]); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test call function +//////////////////////////////////////////////////////////////////////////////// + + testCallNonExisting : function () { + 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_NOT_FOUND.code, "RETURN CALL('foobar', 'baz')"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_NOT_FOUND.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(123, 'baz')"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code, "RETURN CALL([ ], 'baz')"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test call function +//////////////////////////////////////////////////////////////////////////////// + + 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 +//////////////////////////////////////////////////////////////////////////////// + + testApply : function () { + var data = [ + [ "foo bar", [ "TRIM", " foo bar " ] ], + [ "foo bar", [ "TRIM", " foo bar ", "\r\n \t" ] ], + [ "..foo bar..", [ "TRIM", " ..foo bar.. " ] ], + [ "foo bar", [ "TRIM", " ..foo bar.. ", ". " ] ], + [ "foo", [ "LEFT", "foobarbaz", 3 ] ], + [ "fooba", [ "LEFT", "foobarbaz", 5 ] ], + [ "foob", [ "SUBSTRING", "foobarbaz", 0, 4 ] ], + [ "oob", [ "SUBSTRING", "foobarbaz", 1, 3 ] ], + [ "barbaz", [ "SUBSTRING", "foobarbaz", 3 ] ], + [ "FOOBARBAZ", [ "UPPER", "foobarbaz" ] ], + [ "foobarbaz", [ "lower", "FOOBARBAZ" ] ], + [ "abcfood", [ "concat", "a", "b", "c", "foo", "d" ] ], + [ 1, [ "flOoR", 1.6 ] ], + [ 17, [ "MIN", [ 23, 42, 17 ] ] ], + [ [ 1, 2, 3, 4, 7, 9, 10, 12 ], [ "UNION_DISTINCT", [ 1, 2, 3, 4 ], [ 9, 12 ], [ 2, 9, 7, 10 ] ] ], + [ { a: true, b: false, c: null }, [ "ZIP", [ "a", "b", "c" ], [ true, false, null ] ] ] + ]; + + data.forEach(function (d) { + var args = [ ]; + for (var i = 1; i < d[1].length; ++i) { + args.push(d[1][i]); + } + var actual = getQueryResults("RETURN APPLY(" + JSON.stringify(d[1][0]) + ", " + JSON.stringify(args) + ")"); + assertEqual(d[0], actual[0], d); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test apply function +//////////////////////////////////////////////////////////////////////////////// + + testApplyDynamic1 : function () { + var actual = getQueryResults("FOR func IN [ 'TRIM', 'LOWER', 'UPPER' ] RETURN APPLY(func, [ ' foObAr ' ])"); + assertEqual(actual, [ 'foObAr', ' foobar ', ' FOOBAR ' ]); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test apply function +//////////////////////////////////////////////////////////////////////////////// + + testApplyDynamic2 : function () { + var actual = getQueryResults("FOR doc IN [ { value: ' foobar', func: 'TRIM' }, { value: 'FOOBAR', func: 'LOWER' }, { value: 'foobar', func: 'UPPER' } ] RETURN APPLY(doc.func, [ doc.value ])"); + assertEqual(actual, [ 'foobar', 'foobar', 'FOOBAR' ]); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test apply function +//////////////////////////////////////////////////////////////////////////////// + + testApplyNonExisting : function () { + 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_NOT_FOUND.code, "RETURN APPLY('nono-existing', [ 'baz' ])"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code, "RETURN APPLY('foobar', [ 'baz' ])"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_NOT_FOUND.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(123, [ 'baz' ])"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code, "RETURN APPLY([ ], [ 'baz' ])"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test apply function +//////////////////////////////////////////////////////////////////////////////// + + testApplyDisallowed : function () { + assertQueryError(errors.ERROR_QUERY_DISALLOWED_DYNAMIC_CALL.code, "RETURN APPLY('CALL')"); + assertQueryError(errors.ERROR_QUERY_DISALLOWED_DYNAMIC_CALL.code, "RETURN APPLY('APPLY')"); + } + + }; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test suite +//////////////////////////////////////////////////////////////////////////////// + +function ahuacatlCallUserDefinedTestSuite () { + var aqlfunctions = require("org/arangodb/aql/functions"); + + return { + +//////////////////////////////////////////////////////////////////////////////// +/// @brief set up +//////////////////////////////////////////////////////////////////////////////// + + setUp : function () { + [ "add3", "add2", "call", "throwing" ].forEach(function (f) { + try { + aqlfunctions.unregister("UnitTests::func::" + f); + } + catch (err) { + } + }); + + aqlfunctions.register("UnitTests::func::add3", function (a, b, c) { + return a + b + c; + }); + aqlfunctions.register("UnitTests::func::add2", function (a, b) { + return a + b; + }); + aqlfunctions.register("UnitTests::func::call", function () { + return undefined; + }); + aqlfunctions.register("UnitTests::func::throwing", function () { + throw "doh!"; + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief tear down +//////////////////////////////////////////////////////////////////////////////// + + tearDown : function () { + [ "add3", "add2", "call", "throwing" ].forEach(function (f) { + try { + aqlfunctions.unregister("UnitTests::func::" + f); + } + catch (err) { + } + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test call function +//////////////////////////////////////////////////////////////////////////////// + + testCall : function () { + var data = [ + [ null, [ "UnitTests::func::call", 1234 ] ], + [ null, [ "UnitTests::func::call", "foo", "bar" ] ], + [ null, [ "UnitTests::func::add2" ] ], + [ null, [ "UnitTests::func::add2", 23 ] ], + [ 23, [ "UnitTests::func::add2", 23, null ] ], + [ 65, [ "UnitTests::func::add2", 23, 42 ] ], + [ null, [ "UnitTests::func::add3", 23 ] ], + [ null, [ "UnitTests::func::add3", 23, 42 ] ], + [ 65, [ "UnitTests::func::add3", 23, 42, null ] ], + [ 120, [ "UnitTests::func::add3", 23, 42, 55 ] ], + [ "65foo", [ "UnitTests::func::add3", 23, 42, "foo" ] ], + [ "bar4213", [ "UnitTests::func::add3", "bar", 42, 13 ] ], + [ 96, [ "UNITTESTS::FUNC::aDD3", -1, 42, 55 ] ], + [ 96, [ "unittests::func::ADD3", -1, 42, 55 ] ] + ]; + + data.forEach(function (d) { + var actual = getQueryResults("RETURN CALL(" + d[1].map(function (v) { return JSON.stringify(v); }).join(", ") + ")"); + assertEqual(d[0], actual[0], d); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test apply function +//////////////////////////////////////////////////////////////////////////////// + + testApply : function () { + var data = [ + [ null, [ "UnitTests::func::call", 1234 ] ], + [ null, [ "UnitTests::func::call", "foo", "bar" ] ], + [ null, [ "UnitTests::func::add2" ] ], + [ null, [ "UnitTests::func::add2", 23 ] ], + [ 23, [ "UnitTests::func::add2", 23, null ] ], + [ 65, [ "UnitTests::func::add2", 23, 42 ] ], + [ null, [ "UnitTests::func::add3", 23 ] ], + [ null, [ "UnitTests::func::add3", 23, 42 ] ], + [ 65, [ "UnitTests::func::add3", 23, 42, null ] ], + [ 120, [ "UnitTests::func::add3", 23, 42, 55 ] ], + [ "65foo", [ "UnitTests::func::add3", 23, 42, "foo" ] ], + [ "bar4213", [ "UnitTests::func::add3", "bar", 42, 13 ] ], + [ 96, [ "UNITTESTS::FUNC::aDD3", -1, 42, 55 ] ], + [ 96, [ "unittests::func::ADD3", -1, 42, 55 ] ] + ]; + + data.forEach(function (d) { + var args = [ ]; + for (var i = 1; i < d[1].length; ++i) { + args.push(d[1][i]); + } + var actual = getQueryResults("RETURN APPLY(" + JSON.stringify(d[1][0]) + ", " + JSON.stringify(args) + ")"); + assertEqual(d[0], actual[0], d); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test non-existing functions +//////////////////////////////////////////////////////////////////////////////// + + testNonExisting : function () { + assertQueryError(errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code, "RETURN CALL('UNITTESTS::FUNC::MEOW', 'baz')"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code, "RETURN APPLY('UNITTESTS::FUNC::MEOW', [ 'baz' ])"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test throwing function +//////////////////////////////////////////////////////////////////////////////// + + testThrows : function () { + assertQueryWarningAndNull(errors.ERROR_QUERY_FUNCTION_RUNTIME_ERROR.code, "RETURN CALL('UNITTESTS::FUNC::THROWING')"); + assertQueryWarningAndNull(errors.ERROR_QUERY_FUNCTION_RUNTIME_ERROR.code, "RETURN APPLY('UNITTESTS::FUNC::THROWING', [ ])"); + } + + }; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief executes the test suite +//////////////////////////////////////////////////////////////////////////////// + +jsunity.run(ahuacatlCallApplyTestSuite); +jsunity.run(ahuacatlCallUserDefinedTestSuite); + +return jsunity.done(); + +// Local Variables: +// mode: outline-minor +// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)" +// End: diff --git a/js/server/tests/aql-functions-list.js b/js/server/tests/aql-functions-list.js new file mode 100644 index 0000000000..e5bca91e69 --- /dev/null +++ b/js/server/tests/aql-functions-list.js @@ -0,0 +1,617 @@ +/*jshint strict: false, maxlen: 500 */ +/*global require, assertEqual */ +//////////////////////////////////////////////////////////////////////////////// +/// @brief tests for query language, functions +/// +/// @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 internal = require("internal"); +var errors = internal.errors; +var jsunity = require("jsunity"); +var helper = require("org/arangodb/aql-helper"); +var getQueryResults = helper.getQueryResults; +var assertQueryError = helper.assertQueryError; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test suite +//////////////////////////////////////////////////////////////////////////////// + +function ahuacatlListTestSuite () { + return { + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test push function +//////////////////////////////////////////////////////////////////////////////// + + testPush : function () { + var data = [ + [ [ 1, 2, 3, 4 ], [ [ 1, 2, 3 ], 4 ] ], + [ [ 1, 2, 3, "foo" ], [ [ 1, 2, 3 ], "foo" ] ], + [ [ "foo", "bar", "baz", "foo" ], [ [ "foo", "bar", "baz", ], "foo" ] ], + [ [ "foo" ], [ null, "foo" ] ], + [ null, [ false, "foo" ] ], + [ null, [ 1, "foo" ] ], + [ [ "foo" ], [ [ ], "foo" ] ], + [ null, [ "foo", "foo" ] ], + [ null, [ { }, "foo" ] ], + [ [ "" ], [ [ ], "" ] ], + [ [ false, false ], [ [ false ], false ] ], + [ [ false, null ], [ [ false ], null ] ], + [ [ 0 ], [ [ ], 0, true ] ], + [ [ true, 1 ], [ [ true ], 1, true ] ], + [ [ true ], [ [ true ], true, true ] ], + [ [ true, true ], [ [ true ], true, false ] ], + [ [ "foo", "bar", "foo" ], [ [ "foo", "bar", "foo" ], "foo", true ] ], + [ [ "foo", [ ] ], [ [ "foo" ], [ ], true ] ], + [ [ "foo", [ ] ], [ [ "foo", [ ] ], [ ], true ] ], + [ [ "foo", [ ], [ ] ], [ [ "foo", [ ] ], [ ], false ] ], + [ [ { a: 1 }, { a: 2 }, { b: 1 } ], [ [ { a: 1 }, { a: 2 }, { b: 1 } ], { a: 1 }, true ] ], + [ [ { a: 1 }, { a: 2 }, { b: 1 }, { a: 1 } ], [ [ { a: 1 }, { a: 2 }, { b: 1 } ], { a: 1 }, false ] ], + [ [ { a: 1 }, { a: 2 }, { b: 1 }, { b: 2 } ], [ [ { a: 1 }, { a: 2 }, { b: 1 } ], { b: 2 }, true ] ] + ]; + + data.forEach(function (d) { + var actual = getQueryResults("RETURN PUSH(" + d[1].map(function (v) { return JSON.stringify(v); }).join(", ") + ")"); + assertEqual(d[0], actual[0], d); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test push function +//////////////////////////////////////////////////////////////////////////////// + + testPushBig : function () { + var l = []; + for (var i = 0; i < 2000; i += 2) { + l.push(i); + } + var actual = getQueryResults("RETURN PUSH(" + JSON.stringify(l) + ", 1000, true)"); + assertEqual(l, actual[0]); + assertEqual(1000, actual[0].length); + + actual = getQueryResults("RETURN PUSH(" + JSON.stringify(l) + ", 1000, true)"); + assertEqual(l, actual[0]); + assertEqual(1000, actual[0].length); + + actual = getQueryResults("RETURN PUSH(" + JSON.stringify(l) + ", 1000, false)"); + l.push(1000); + assertEqual(l, actual[0]); + assertEqual(1001, actual[0].length); + + actual = getQueryResults("RETURN PUSH(" + JSON.stringify(l) + ", 1001, true)"); + l.push(1001); + assertEqual(l, actual[0]); + assertEqual(1002, actual[0].length); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test push function +//////////////////////////////////////////////////////////////////////////////// + + testPushInvalid : function () { + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN PUSH()"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN PUSH([ ])"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN PUSH([ ], 1, 2, 3)"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test unshift function +//////////////////////////////////////////////////////////////////////////////// + + testUnshift : function () { + var data = [ + [ [ 4, 1, 2, 3 ], [ [ 1, 2, 3 ], 4 ] ], + [ [ "foo", 1, 2, 3 ], [ [ 1, 2, 3 ], "foo" ] ], + [ [ "foo", "foo", "bar", "baz" ], [ [ "foo", "bar", "baz", ], "foo" ] ], + [ [ "foo" ], [ null, "foo" ] ], + [ null, [ false, "foo" ] ], + [ null, [ 1, "foo" ] ], + [ [ "foo" ], [ [ ], "foo" ] ], + [ null, [ "foo", "foo" ] ], + [ null, [ { }, "foo" ] ], + [ [ "" ], [ [ ], "" ] ], + [ [ false, false ], [ [ false ], false ] ], + [ [ null, false ], [ [ false ], null ] ], + [ [ 0 ], [ [ ], 0, true ] ], + [ [ 1, true ], [ [ true ], 1, true ] ], + [ [ true ], [ [ true ], true, true ] ], + [ [ true, true ], [ [ true ], true, false ] ], + [ [ "foo", "bar", "foo" ], [ [ "foo", "bar", "foo" ], "foo", true ] ], + [ [ "baz", "foo", "bar", "foo" ], [ [ "foo", "bar", "foo" ], "baz", true ] ], + [ [ [ ], "foo" ], [ [ "foo" ], [ ], true ] ], + [ [ "foo", [ ] ], [ [ "foo", [ ] ], [ ], true ] ], + [ [ [ ], "foo" ], [ [ [ ], "foo" ], [ ], true ] ], + [ [ [ ], "foo", [ ] ], [ [ "foo", [ ] ], [ ], false ] ], + [ [ [ ], [ ], "foo" ], [ [ [ ], "foo" ], [ ], false ] ], + [ [ { a: 1 }, { a: 2 }, { b: 1 } ], [ [ { a: 1 }, { a: 2 }, { b: 1 } ], { a: 1 }, true ] ], + [ [ { a: 1 }, { a: 1 }, { a: 2 }, { b: 1 } ], [ [ { a: 1 }, { a: 2 }, { b: 1 } ], { a: 1 }, false ] ], + [ [ { b: 2 }, { a: 1 }, { a: 2 }, { b: 1 } ], [ [ { a: 1 }, { a: 2 }, { b: 1 } ], { b: 2 }, true ] ] + ]; + + data.forEach(function (d) { + var actual = getQueryResults("RETURN UNSHIFT(" + d[1].map(function (v) { return JSON.stringify(v); }).join(", ") + ")"); + assertEqual(d[0], actual[0], d); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test unshift function +//////////////////////////////////////////////////////////////////////////////// + + testUnshiftBig : function () { + var l = []; + for (var i = 0; i < 2000; i += 2) { + l.push(i); + } + var actual = getQueryResults("RETURN UNSHIFT(" + JSON.stringify(l) + ", 1000, true)"); + assertEqual(l, actual[0]); + assertEqual(1000, actual[0].length); + + actual = getQueryResults("RETURN UNSHIFT(" + JSON.stringify(l) + ", 1000, true)"); + assertEqual(l, actual[0]); + assertEqual(1000, actual[0].length); + + actual = getQueryResults("RETURN UNSHIFT(" + JSON.stringify(l) + ", 1000, false)"); + l.unshift(1000); + assertEqual(l, actual[0]); + assertEqual(1001, actual[0].length); + + actual = getQueryResults("RETURN UNSHIFT(" + JSON.stringify(l) + ", 1001, true)"); + l.unshift(1001); + assertEqual(l, actual[0]); + assertEqual(1002, actual[0].length); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test unshift function +//////////////////////////////////////////////////////////////////////////////// + + testUnshiftInvalid : function () { + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN UNSHIFT()"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN UNSHIFT([ ])"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN UNSHIFT([ ], 1, 2, 3)"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test pop function +//////////////////////////////////////////////////////////////////////////////// + + testPop : function () { + var data = [ + [ null, null ], + [ null, false ], + [ null, true ], + [ null, 23 ], + [ null, "foo" ], + [ null, { } ], + [ [ ], [ ] ], + [ [ 1, 2 ], [ 1, 2, 3 ] ], + [ [ 1, 2, 3, "foo" ], [ 1, 2, 3, "foo", 4 ] ], + [ [ 1, 2, 3 ], [ 1, 2, 3, "foo" ] ], + [ [ "foo", "bar", "baz" ], [ "foo", "bar", "baz", "foo" ] ], + [ [ null ], [ null, "foo" ] ], + [ [ false ], [ false, "foo" ] ], + [ [ 1 ], [ 1, "foo" ] ], + [ [ [ ] ], [ [ ], "foo" ] ], + [ [ "foo" ], [ "foo", "foo" ] ], + [ [ "foo" ], [ "foo", "bar" ] ], + [ [ { } ], [ { }, "foo" ] ], + [ [ [ ] ], [ [ ], "" ] ], + [ [ [ false ] ], [ [ false ], false ] ], + [ [ [ false ] ], [ [ false ], null ] ], + [ [ [ ], 0 ], [ [ ], 0, true ] ], + [ [ true, 1 ], [ true, 1, true ] ], + [ [ true, true ], [ true, true, true ] ], + [ [ true, false ], [ true, false, true ] ], + [ [ true, false ], [ true, false, false ] ], + [ [ [ true ], true ], [ [ true ], true, false ] ], + [ [ [ "foo", "bar", "foo" ], "foo" ], [ [ "foo", "bar", "foo" ], "foo", true ] ], + [ [ "foo", [ ] ], [ "foo", [ ], true ] ], + [ [ "foo", [ ], [ ] ], [ "foo", [ ], [ ], true ] ], + [ [ "foo", [ ], [ ] ], [ "foo", [ ], [ ], false ] ], + [ [ { a: 1 }, { a: 2 } ], [ { a: 1 }, { a: 2 }, { b: 1 } ] ], + [ [ { a: 1 }, { a: 2 }, { b: 1 } ], [ { a: 1 }, { a: 2 }, { b: 1 }, { a: 1 } ] ], + [ [ { a: 1 }, { a: 2 }, { b: 1 } ], [ { a: 1 }, { a: 2 }, { b: 1 }, { b: 2 } ] ] + ]; + + data.forEach(function (d) { + var actual = getQueryResults("RETURN POP(" + JSON.stringify(d[1]) + ")"); + assertEqual(d[0], actual[0], d); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test pop function +//////////////////////////////////////////////////////////////////////////////// + + testPopBig : function () { + var l = []; + for (var i = 0; i < 2000; i += 2) { + l.push(i); + } + var actual = getQueryResults("RETURN POP(" + JSON.stringify(l) + ")"); + l.pop(); + assertEqual(l, actual[0]); + assertEqual(999, actual[0].length); + + actual = getQueryResults("RETURN POP(" + JSON.stringify(l) + ")"); + l.pop(); + assertEqual(l, actual[0]); + assertEqual(998, actual[0].length); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test pop function +//////////////////////////////////////////////////////////////////////////////// + + testPopInvalid : function () { + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN POP()"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN POP([ ], 1)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN POP([ ], 1, 2)"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test shift function +//////////////////////////////////////////////////////////////////////////////// + + testShift : function () { + var data = [ + [ null, null ], + [ null, false ], + [ null, true ], + [ null, 23 ], + [ null, "foo" ], + [ null, { } ], + [ [ ], [ ] ], + [ [ 2, 3 ], [ 1, 2, 3 ] ], + [ [ 2, 3, "foo", 4 ], [ 1, 2, 3, "foo", 4 ] ], + [ [ 2, 3, "foo" ], [ 1, 2, 3, "foo" ] ], + [ [ "bar", "baz", "foo" ], [ "foo", "bar", "baz", "foo" ] ], + [ [ "foo" ], [ null, "foo" ] ], + [ [ "foo" ], [ false, "foo" ] ], + [ [ "foo" ], [ 1, "foo" ] ], + [ [ "foo" ], [ [ ], "foo" ] ], + [ [ "foo" ], [ "foo", "foo" ] ], + [ [ "bar" ], [ "foo", "bar" ] ], + [ [ "foo" ], [ { }, "foo" ] ], + [ [ { } ], [ "foo", { } ] ], + [ [ "" ], [ [ ], "" ] ], + [ [ [ ] ], [ "", [ ] ] ], + [ [ false ], [ [ false ], false ] ], + [ [ null ], [ [ false ], null ] ], + [ [ 0, true ], [ [ ], 0, true ] ], + [ [ 1, true ], [ true, 1, true ] ], + [ [ true, true ], [ true, true, true ] ], + [ [ false, true ], [ true, false, true ] ], + [ [ false, false ], [ true, false, false ] ], + [ [ true, false ], [ [ true ], true, false ] ], + [ [ "foo", true ], [ [ "foo", "bar", "foo" ], "foo", true ] ], + [ [ [ ], true ], [ "foo", [ ], true ] ], + [ [ [ ], [ ], true ], [ "foo", [ ], [ ], true ] ], + [ [ [ ], [ ], false ], [ "foo", [ ], [ ], false ] ], + [ [ { a: 2 }, { b: 1 } ], [ { a: 1 }, { a: 2 }, { b: 1 } ] ], + [ [ { a: 2 }, { b: 1 }, { a: 1 } ], [ { a: 1 }, { a: 2 }, { b: 1 }, { a: 1 } ] ], + [ [ { a: 2 }, { b: 1 }, { b: 2 } ], [ { a: 1 }, { a: 2 }, { b: 1 }, { b: 2 } ] ] + ]; + + data.forEach(function (d) { + var actual = getQueryResults("RETURN SHIFT(" + JSON.stringify(d[1]) + ")"); + assertEqual(d[0], actual[0], d); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test shift function +//////////////////////////////////////////////////////////////////////////////// + + testShiftBig : function () { + var l = []; + for (var i = 0; i < 2000; i += 2) { + l.push(i); + } + var actual = getQueryResults("RETURN SHIFT(" + JSON.stringify(l) + ")"); + l.shift(); + assertEqual(l, actual[0]); + assertEqual(999, actual[0].length); + + actual = getQueryResults("RETURN SHIFT(" + JSON.stringify(l) + ")"); + l.shift(); + assertEqual(l, actual[0]); + assertEqual(998, actual[0].length); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test shift function +//////////////////////////////////////////////////////////////////////////////// + + testShiftInvalid : function () { + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN SHIFT()"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN SHIFT([ ], 1)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN SHIFT([ ], 1, 2)"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test push/pop function +//////////////////////////////////////////////////////////////////////////////// + + testPushPop : function () { + var l = [], i, actual; + for (i = 0; i < 1000; ++i) { + actual = getQueryResults("RETURN PUSH(" + JSON.stringify(l) + ", " + JSON.stringify(i) + ")"); + l.push(i); + assertEqual(l, actual[0]); + } + + for (i = 0; i < 1000; ++i) { + actual = getQueryResults("RETURN POP(" + JSON.stringify(l) + ")"); + l.pop(); + assertEqual(l, actual[0]); + } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test unshift/shift function +//////////////////////////////////////////////////////////////////////////////// + + testUnshiftShift : function () { + var l = [], i, actual; + for (i = 0; i < 1000; ++i) { + actual = getQueryResults("RETURN UNSHIFT(" + JSON.stringify(l) + ", " + JSON.stringify(i) + ")"); + l.unshift(i); + assertEqual(l, actual[0]); + } + + for (i = 0; i < 1000; ++i) { + actual = getQueryResults("RETURN SHIFT(" + JSON.stringify(l) + ")"); + l.shift(); + assertEqual(l, actual[0]); + } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test append function +//////////////////////////////////////////////////////////////////////////////// + + testAppend : function () { + var data = [ + [ [ ], [ [ ], [ ] ] ], + [ [ "foo", "bar", "baz" ], [ [ "foo" ], [ "bar", "baz" ] ] ], + [ [ "foo", "foo", "bar", "baz" ], [ [ "foo" ], [ "foo", "bar", "baz" ] ] ], + [ [ "foo", "bar", "baz" ], [ [ "foo" ], [ "foo", "bar", "baz" ], true ] ], + [ [ "foo", "bar", "baz" ], [ [ "foo" ], [ "foo", "bar", "baz", "foo" ], true ] ], + [ [ "foo", "bar", "baz" ], [ [ "foo", "bar" ], [ "baz" ] ] ], + [ [ "foo", "bar", "baz" ], [ null, [ "foo", "bar", "baz" ] ] ], + [ [ "foo", "bar", "baz" ], [ [ "foo", "bar", "baz" ], null ] ], + [ [ "foo", "bar", "baz" ], [ [ ], [ "foo", "bar", "baz" ] ] ], + [ [ "foo", "bar", "baz" ], [ [ "foo", "bar", "baz" ], [ ] ] ], + [ [ "foo", "bar", "baz", "one", "two", "three" ], [ [ "foo", "bar", "baz" ], [ "one", "two", "three" ] ] ], + [ [ "foo", "bar", "baz", "one", "two", null, "three" ], [ [ "foo", "bar", "baz" ], [ "one", "two", null, "three" ] ] ], + [ [ "two", "one", null, "three", "one", "two", null, "three" ], [ [ "two", "one", null, "three" ], [ "one", "two", null, "three" ] ] ], + [ [ "two", "one", null, "three" ], [ [ "two", "one", null, "three" ], [ "one", "two", null, "three" ], true ] ], + [ [ "two", "one", null, "three", "four" ], [ [ "two", "one", null, "three" ], [ "one", "two", "four", null, "three" ], true ] ] + ]; + + data.forEach(function (d) { + var actual = getQueryResults("RETURN APPEND(" + d[1].map(function (v) { return JSON.stringify(v); }).join(", ") + ")"); + assertEqual(d[0], actual[0], d); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test append function +//////////////////////////////////////////////////////////////////////////////// + + testAppendBig : function () { + var l1 = [], l2 = [ ]; + for (var i = 0; i < 2000; i += 4) { + l1.push(i); + l2.push(i + 1); + } + var actual = getQueryResults("RETURN APPEND(" + JSON.stringify(l1) + ", " + JSON.stringify(l2) + ")"); + var l = l1.concat(l2); + assertEqual(l, actual[0]); + assertEqual(1000, actual[0].length); + + actual = getQueryResults("RETURN APPEND(" + JSON.stringify(l) + ", " + JSON.stringify(l1) + ")"); + l = l.concat(l1); + assertEqual(l, actual[0]); + assertEqual(1500, actual[0].length); + + actual = getQueryResults("RETURN APPEND(" + JSON.stringify(l) + ", " + JSON.stringify(l1) + ", true)"); + assertEqual(l, actual[0]); + assertEqual(1500, actual[0].length); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test append function +//////////////////////////////////////////////////////////////////////////////// + + testAppendInvalid : function () { + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN APPEND()"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN APPEND([ ])"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN APPEND([ ], [ ], false, [ ])"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test remove_all function +//////////////////////////////////////////////////////////////////////////////// + + testRemoveAll : function () { + var data = [ + [ [ ], [ [ ], [ ] ] ], + [ [ ], [ [ ], [ "1", "2" ] ] ], + [ [ "1", "2" ], [ [ "1", "2" ], [ ] ] ], + [ [ "1", "2" ], [ [ "1", "2" ], [ 1, 2 ] ] ], + [ [ 1, 2 ], [ [ 1, 2 ], [ "1", "2" ] ] ], + [ [ 2 ], [ [ 1, 2 ], [ "1", "2", 1 ] ] ], + [ [ "1", "2" ], [ [ "1", "2" ], [ "foo", 1] ] ], + [ [ "foo" ], [ [ "foo" ], [ "bar", "baz" ] ] ], + [ [ ], [ [ "foo" ], [ "foo", "bar", "baz" ] ] ], + [ [ ], [ [ "foo" ], [ "foo", "bar", "baz" ] ] ], + [ [ ], [ [ "foo" ], [ "foo", "bar", "baz", "foo" ] ] ], + [ [ "foo", "bar" ], [ [ "foo", "bar" ], [ "baz" ] ] ], + [ [ ], [ null, [ "foo", "bar", "baz" ] ] ], + [ [ "foo", "bar", "baz" ], [ [ "foo", "bar", "baz" ], null ] ], + [ [ ], [ [ ], [ "foo", "bar", "baz" ] ] ], + [ [ "foo", "bar", "baz" ], [ [ "foo", "bar", "baz" ], [ ] ] ], + [ [ "foo", "bar", "baz" ], [ [ "foo", "bar", "baz" ], [ "one", "two", "three" ] ] ], + [ [ "foo", "bar", "baz" ], [ [ "foo", null, "bar", "baz" ], [ "one", "two", null, "three" ] ] ], + [ [ ], [ [ "two", "one", null, "three" ], [ "one", "two", null, "three" ] ] ], + [ [ null ], [ [ "two", "one", null, "three" ], [ "one", "two", "three" ] ] ], + [ [ "four", "five" ], [ [ "two", "four", "one", null, "three", "five" ], [ "one", "two", null, "three" ] ] ], + [ [ ], [ [ "two", "one", null, "three" ], [ "one", "two", "four", null, "three" ] ] ] + ]; + + data.forEach(function (d) { + var actual = getQueryResults("RETURN REMOVE_ALL(" + d[1].map(function (v) { return JSON.stringify(v); }).join(", ") + ")"); + assertEqual(d[0], actual[0], d); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test remove_all function +//////////////////////////////////////////////////////////////////////////////// + + testRemoveAllInvalid : function () { + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN REMOVE_ALL()"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN REMOVE_ALL([ ])"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN REMOVE_ALL([ ], [ ], true)"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test remove_value function +//////////////////////////////////////////////////////////////////////////////// + + testRemoveValue : function () { + var data = [ + [ [ ], [ [ ], null ] ], + [ [ ], [ [ ], false ] ], + [ [ ], [ [ ], 1 ] ], + [ [ ], [ [ ], "1" ] ], + [ [ ], [ [ ], [ ] ] ], + [ [ ], [ [ ], [ "1", "2" ] ] ], + [ [ "1", "2" ], [ [ "1", "2" ], [ ] ] ], + [ [ "1", "2" ], [ [ "1", "2" ], 1 ] ], + [ [ "1", "2" ], [ [ "1", "2" ], 2 ] ], + [ [ "2" ], [ [ "1", "2" ], "1" ] ], + [ [ "1" ], [ [ "1", "2" ], "2" ] ], + [ [ "1", "1", "1", "3" ], [ [ "1", "1", "1", "2", "2", "3" ], "2" ] ], + [ [ "1", "1", "1", "2", "3" ], [ [ "1", "1", "1", "2", "2", "3" ], "2", 1 ] ], + [ [ ], [ [ "foo" ], "foo" ] ], + [ [ "bar" ], [ [ "bar" ], "foo" ] ], + [ [ "foo" ], [ [ "foo" ], "bar" ] ], + [ [ ], [ [ "foo", "foo", "foo" ], "foo" ] ], + [ [ "foo", "foo" ], [ [ "foo", "foo", "foo" ], "foo", 1 ] ], + [ [ "foo" ], [ [ "foo", "foo", "foo" ], "foo", 2 ] ], + [ [ ], [ [ "foo", "foo", "foo" ], "foo", 3 ] ], + [ [ ], [ [ "foo", "foo", "foo" ], "foo", 496 ] ], + [ [ "bar", "foo", "bam", "foo", "baz" ], [ [ "bar", "foo", "foo", "bam", "foo", "baz" ], "foo", 1 ] ], + [ [ "bar", "bam", "baz", "foo" ], [ [ "bar", "bam", "baz", "foo", "foo" ], "foo", 1 ] ], + [ [ "bar", "bam", "baz" ], [ [ "bar", "bam", "baz", "foo", "foo" ], "foo", 2 ] ], + [ [ [ 1, 2, 3 ] ], [ [ [ 1, 2, 3 ] ], [ 1, 2 ] ] ], + [ [ [ 1, 2, 3 ] ], [ [ [ 1, 2, 3 ] ], [ 1, 3, 2 ] ] ], + [ [ ], [ [ [ 1, 2, 3 ] ], [ 1, 2, 3] ] ] + ]; + + data.forEach(function (d) { + var actual = getQueryResults("RETURN REMOVE_VALUE(" + d[1].map(function (v) { return JSON.stringify(v); }).join(", ") + ")"); + assertEqual(d[0], actual[0], d); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test remove_value function +//////////////////////////////////////////////////////////////////////////////// + + testRemoveValueInvalid : function () { + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN REMOVE_VALUE()"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN REMOVE_VALUE([ ])"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN REMOVE_VALUE([ ], [ ], true, true)"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test remove_nth function +//////////////////////////////////////////////////////////////////////////////// + + testRemoveNth : function () { + var data = [ + [ [ ], [ [ ], 0 ] ], + [ [ ], [ [ ], -1 ] ], + [ [ ], [ [ ], -2 ] ], + [ [ ], [ [ ], 99 ] ], + [ [ ], [ [ ], null ] ], + [ [ "2" ], [ [ "1", "2" ], 0 ] ], + [ [ "1" ], [ [ "1", "2" ], 1 ] ], + [ [ "1", "2" ], [ [ "1", "2" ], 2 ] ], + [ [ "1", "2" ], [ [ "1", "2" ], -3 ] ], + [ [ "2" ], [ [ "1", "2" ], -2 ] ], + [ [ "1" ], [ [ "1", "2" ], -1 ] ], + [ [ "1b", "1c", "2a", "2b", "3" ], [ [ "1a", "1b", "1c", "2a", "2b", "3" ], 0 ] ], + [ [ "1a", "1c", "2a", "2b", "3" ], [ [ "1a", "1b", "1c", "2a", "2b", "3" ], 1 ] ], + [ [ "1a", "1b", "2a", "2b", "3" ], [ [ "1a", "1b", "1c", "2a", "2b", "3" ], 2 ] ], + [ [ "1a", "1b", "1c", "2b", "3" ], [ [ "1a", "1b", "1c", "2a", "2b", "3" ], 3 ] ], + [ [ "1a", "1b", "1c", "2a", "3" ], [ [ "1a", "1b", "1c", "2a", "2b", "3" ], 4 ] ], + [ [ "1a", "1b", "1c", "2a", "2b" ], [ [ "1a", "1b", "1c", "2a", "2b", "3" ], 5 ] ], + [ [ "1a", "1b", "1c", "2a", "2b", "3" ], [ [ "1a", "1b", "1c", "2a", "2b", "3" ], 6 ] ], + [ [ "1a", "1b", "1c", "2a", "2b" ], [ [ "1a", "1b", "1c", "2a", "2b", "3" ], -1 ] ], + [ [ "1a", "1b", "1c", "2a", "3" ], [ [ "1a", "1b", "1c", "2a", "2b", "3" ], -2 ] ], + [ [ "1a", "1b", "1c", "2b", "3" ], [ [ "1a", "1b", "1c", "2a", "2b", "3" ], -3 ] ], + [ [ "1a", "1b", "2a", "2b", "3" ], [ [ "1a", "1b", "1c", "2a", "2b", "3" ], -4 ] ], + [ [ "1a", "1c", "2a", "2b", "3" ], [ [ "1a", "1b", "1c", "2a", "2b", "3" ], -5 ] ], + [ [ "1b", "1c", "2a", "2b", "3" ], [ [ "1a", "1b", "1c", "2a", "2b", "3" ], -6 ] ], + [ [ "1a", "1b", "1c", "2a", "2b", "3" ], [ [ "1a", "1b", "1c", "2a", "2b", "3" ], -7 ] ] + ]; + + data.forEach(function (d) { + var actual = getQueryResults("RETURN REMOVE_NTH(" + d[1].map(function (v) { return JSON.stringify(v); }).join(", ") + ")"); + assertEqual(d[0], actual[0], d); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test remove_nth function +//////////////////////////////////////////////////////////////////////////////// + + testRemoveNthInvalid : function () { + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN REMOVE_NTH()"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN REMOVE_NTH([ ])"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN REMOVE_NTH([ ], 1, true)"); + } + + }; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief executes the test suite +//////////////////////////////////////////////////////////////////////////////// + +jsunity.run(ahuacatlListTestSuite); + +return jsunity.done(); + +// Local Variables: +// mode: outline-minor +// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)" +// End: diff --git a/js/server/tests/aql-functions.js b/js/server/tests/aql-functions.js index b7b733ef84..d7cc84a0a4 100644 --- a/js/server/tests/aql-functions.js +++ b/js/server/tests/aql-functions.js @@ -32,7 +32,6 @@ var errors = internal.errors; var jsunity = require("jsunity"); var helper = require("org/arangodb/aql-helper"); var getQueryResults = helper.getQueryResults; -var getQueryResults = helper.getQueryResults; var assertQueryError = helper.assertQueryError; var assertQueryWarningAndNull = helper.assertQueryWarningAndNull; diff --git a/lib/Basics/errors.dat b/lib/Basics/errors.dat index 60a560b2bd..f58ad6d15e 100755 --- a/lib/Basics/errors.dat +++ b/lib/Basics/errors.dat @@ -218,6 +218,7 @@ ERROR_QUERY_MODIFY_IN_SUBQUERY,1574,"modify operation in subquery", "Will be rai ERROR_QUERY_COMPILE_TIME_OPTIONS,1575,"query options must be readable at query compile time", "Will be raised when an AQL data-modification query contains options that cannot be figured out at query compile time." ERROR_QUERY_EXCEPTION_OPTIONS,1576,"query options expected", "Will be raised when an AQL data-modification query contains an invalid options specification." ERROR_QUERY_COLLECTION_USED_IN_EXPRESSION,1577,"collection '%s' used as expression operand", "Will be raised when a collection is used as an operand in an AQL expression." +ERROR_QUERY_DISALLOWED_DYNAMIC_CALL,1578,"disallowed dynamic call to '%s'", "Will be raised when a dynamic function call is made to a function that cannot be called dynamically." ################################################################################ ## AQL user functions diff --git a/lib/Basics/voc-errors.cpp b/lib/Basics/voc-errors.cpp index 4fa6539886..f79e5e80fa 100644 --- a/lib/Basics/voc-errors.cpp +++ b/lib/Basics/voc-errors.cpp @@ -173,6 +173,7 @@ void TRI_InitialiseErrorMessages () { REG_ERROR(ERROR_QUERY_COMPILE_TIME_OPTIONS, "query options must be readable at query compile time"); REG_ERROR(ERROR_QUERY_EXCEPTION_OPTIONS, "query options expected"); REG_ERROR(ERROR_QUERY_COLLECTION_USED_IN_EXPRESSION, "collection '%s' used as expression operand"); + REG_ERROR(ERROR_QUERY_DISALLOWED_DYNAMIC_CALL, "disallowed dynamic call to '%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 '%s()' not found"); diff --git a/lib/Basics/voc-errors.h b/lib/Basics/voc-errors.h index 10c915e77d..fb7f8a395a 100644 --- a/lib/Basics/voc-errors.h +++ b/lib/Basics/voc-errors.h @@ -427,6 +427,9 @@ /// - 1577: @LIT{collection '\%s' used as expression operand} /// "Will be raised when a collection is used as an operand in an AQL /// expression." +/// - 1578: @LIT{disallowed dynamic call to '\%s'} +/// "Will be raised when a dynamic function call is made to a function that +/// cannot be called dynamically." /// - 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} @@ -2352,6 +2355,17 @@ void TRI_InitialiseErrorMessages (); #define TRI_ERROR_QUERY_COLLECTION_USED_IN_EXPRESSION (1577) +//////////////////////////////////////////////////////////////////////////////// +/// @brief 1578: ERROR_QUERY_DISALLOWED_DYNAMIC_CALL +/// +/// disallowed dynamic call to '%s' +/// +/// "Will be raised when a dynamic function call is made to a function that +/// cannot be called dynamically." +//////////////////////////////////////////////////////////////////////////////// + +#define TRI_ERROR_QUERY_DISALLOWED_DYNAMIC_CALL (1578) + //////////////////////////////////////////////////////////////////////////////// /// @brief 1580: ERROR_QUERY_FUNCTION_INVALID_NAME /// From ed919090ea64b184672698864d74253b9343ab70 Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Thu, 20 Nov 2014 14:46:32 +0100 Subject: [PATCH 2/2] renamed function, updated documentation --- CHANGELOG | 2 +- .../Books/Users/Aql/ListFunctions.mdpp | 16 ++++++++++--- arangod/Aql/Executor.cpp | 4 +--- js/server/modules/org/arangodb/aql.js | 8 +++---- js/server/tests/aql-functions-list.js | 24 +++++++++---------- 5 files changed, 31 insertions(+), 23 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 17ca3dd179..72cf4ccdfd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,7 @@ v2.4.0 (XXXX-XX-XX) ------------------- -* added AQL list functions `PUSH`, `POP`, `UNSHIFT`, `SHIFT`, `REMOVE_ALL`, +* added AQL list functions `PUSH`, `POP`, `UNSHIFT`, `SHIFT`, `REMOVE_VALUES`, `REMOVE_VALUE`, `REMOVE_NTH` and `APPEND` * added AQL functions `CALL` and `APPLY` to dynamically call other functions diff --git a/Documentation/Books/Users/Aql/ListFunctions.mdpp b/Documentation/Books/Users/Aql/ListFunctions.mdpp index 366c0291c6..8070c50624 100644 --- a/Documentation/Books/Users/Aql/ListFunctions.mdpp +++ b/Documentation/Books/Users/Aql/ListFunctions.mdpp @@ -185,9 +185,14 @@ AQL supports the following functions to operate on list values: APPEND([ 1, 2, 3 ], [ 3, 4, 5, 2, 9 ], true) - *PUSH(list, value, unique)*: Adds *value* to the list specified by *list*. If - *unique* is set to true, then *value* is not added if already present in the list. + *unique* is set to true, then *value* is not added if already present in the list. The modified list is returned. The value is added at the end of the list (right side). + Note: non-unique elements will not be removed from the list if they were already present + before the call to `PUSH`. The *unique* flag will only control if the value will + be added again to the list if already present. To make a list unique, use the `UNIQUE` + function. + /* [ 1, 2, 3, 4 ] */ PUSH([ 1, 2, 3 ], 4) @@ -197,6 +202,11 @@ AQL supports the following functions to operate on list values: - *UNSHIFT(list, value, unique)*: Adds *value* to the list specified by *list*. If *unique* is set to true, then *value* is not added if already present in the list. The modified list is returned. The value is added at the start of the list (left side). + + Note: non-unique elements will not be removed from the list if they were already present + before the call to `UNSHIFT`. The *unique* flag will only control if the value will + be added again to the list if already present. To make a list unique, use the `UNIQUE` + function. /* [ 4, 1, 2, 3 ] */ UNSHIFT([ 1, 2, 3 ], 4) @@ -226,11 +236,11 @@ AQL supports the following functions to operate on list values: /* [ "b", "b", "a", "c" ] */ REMOVE_VALUE([ "a", "b", "b", "a", "c" ], "a", 1) -- *REMOVE_ALL(list, values)*: Removes all occurrences of any of the values specified +- *REMOVE_VALUES(list, values)*: Removes all occurrences of any of the values specified in list *values* from the list specified by *list*. /* [ "b", "c", "e", "g" ] */ - REMOVE_ALL([ "a", "b", "c", "d", "e", "f", "g" ], [ "a", "f", "d" ]) + REMOVE_VALUES([ "a", "b", "c", "d", "e", "f", "g" ], [ "a", "f", "d" ]) - *REMOVE_NTH(list, position)*: Removes the element at position *position* from the list specified by *list*. Positions start at 0. Negative positions are supported, diff --git a/arangod/Aql/Executor.cpp b/arangod/Aql/Executor.cpp index 081df26ad3..88cbd9c6b3 100644 --- a/arangod/Aql/Executor.cpp +++ b/arangod/Aql/Executor.cpp @@ -155,8 +155,6 @@ std::unordered_map const Executor::FunctionNames{ { "LAST", Function("LAST", "AQL_LAST", "l", true, false, true) }, { "NTH", Function("NTH", "AQL_NTH", "l,n", true, false, true) }, { "POSITION", Function("POSITION", "AQL_POSITION", "l,.|b", true, false, true) }, -// { "CALL", Function("CALL", "AQL_CALL", "l,s|.+", false, true, false) }, -// { "APPLY", Function("APPLY", "AQL_APPLY", "l,s|.", false, true, false) }, { "CALL", Function("CALL", "AQL_CALL", "s|.+", false, true, false) }, { "APPLY", Function("APPLY", "AQL_APPLY", "s|l", false, true, false) }, { "PUSH", Function("PUSH", "AQL_PUSH", "l,.|b", true, false, false) }, @@ -166,7 +164,7 @@ std::unordered_map const Executor::FunctionNames{ { "SHIFT", Function("SHIFT", "AQL_SHIFT", "l", true, false, false) }, { "UNSHIFT", Function("UNSHIFT", "AQL_UNSHIFT", "l,.|b", true, false, false) }, { "REMOVE_VALUE", Function("REMOVE_VALUE", "AQL_REMOVE_VALUE", "l,.|n", true, false, false) }, - { "REMOVE_ALL", Function("REMOVE_ALL", "AQL_REMOVE_ALL", "l,lz", true, false, false) }, + { "REMOVE_VALUES", Function("REMOVE_VALUES", "AQL_REMOVE_VALUES", "l,lz", true, false, false) }, { "REMOVE_NTH", Function("REMOVE_NTH", "AQL_REMOVE_NTH", "l,n", true, false, false) }, // document functions diff --git a/js/server/modules/org/arangodb/aql.js b/js/server/modules/org/arangodb/aql.js index 89aa3b85a5..cf89834797 100644 --- a/js/server/modules/org/arangodb/aql.js +++ b/js/server/modules/org/arangodb/aql.js @@ -2659,7 +2659,7 @@ function AQL_APPLY (name, parameters) { /// @brief removes elements from a list //////////////////////////////////////////////////////////////////////////////// -function AQL_REMOVE_ALL (list, values) { +function AQL_REMOVE_VALUES (list, values) { "use strict"; var type = TYPEWEIGHT(values); @@ -2667,7 +2667,7 @@ function AQL_REMOVE_ALL (list, values) { return list; } else if (type !== TYPEWEIGHT_LIST) { - WARN("REMOVE_ALL", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); + WARN("REMOVE_VALUES", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); return null; } @@ -2686,7 +2686,7 @@ function AQL_REMOVE_ALL (list, values) { return copy; } - WARN("REMOVE_ALL", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); + WARN("REMOVE_VALUES", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); return null; } @@ -7537,7 +7537,7 @@ exports.AQL_UNION_DISTINCT = AQL_UNION_DISTINCT; exports.AQL_CALL = AQL_CALL; exports.AQL_APPLY = AQL_APPLY; exports.AQL_REMOVE_VALUE = AQL_REMOVE_VALUE; -exports.AQL_REMOVE_ALL = AQL_REMOVE_ALL; +exports.AQL_REMOVE_VALUES = AQL_REMOVE_VALUES; exports.AQL_REMOVE_NTH = AQL_REMOVE_NTH; exports.AQL_PUSH = AQL_PUSH; exports.AQL_APPEND = AQL_APPEND; diff --git a/js/server/tests/aql-functions-list.js b/js/server/tests/aql-functions-list.js index e5bca91e69..82ce1cf86b 100644 --- a/js/server/tests/aql-functions-list.js +++ b/js/server/tests/aql-functions-list.js @@ -455,10 +455,10 @@ function ahuacatlListTestSuite () { }, //////////////////////////////////////////////////////////////////////////////// -/// @brief test remove_all function +/// @brief test removeValues function //////////////////////////////////////////////////////////////////////////////// - testRemoveAll : function () { + testRemoveValues : function () { var data = [ [ [ ], [ [ ], [ ] ] ], [ [ ], [ [ ], [ "1", "2" ] ] ], @@ -485,23 +485,23 @@ function ahuacatlListTestSuite () { ]; data.forEach(function (d) { - var actual = getQueryResults("RETURN REMOVE_ALL(" + d[1].map(function (v) { return JSON.stringify(v); }).join(", ") + ")"); + var actual = getQueryResults("RETURN REMOVE_VALUES(" + d[1].map(function (v) { return JSON.stringify(v); }).join(", ") + ")"); assertEqual(d[0], actual[0], d); }); }, //////////////////////////////////////////////////////////////////////////////// -/// @brief test remove_all function +/// @brief test removeValues function //////////////////////////////////////////////////////////////////////////////// - testRemoveAllInvalid : function () { - assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN REMOVE_ALL()"); - assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN REMOVE_ALL([ ])"); - assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN REMOVE_ALL([ ], [ ], true)"); + testRemoveValuesInvalid : function () { + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN REMOVE_VALUES()"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN REMOVE_VALUES([ ])"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN REMOVE_VALUES([ ], [ ], true)"); }, //////////////////////////////////////////////////////////////////////////////// -/// @brief test remove_value function +/// @brief test removeValue function //////////////////////////////////////////////////////////////////////////////// testRemoveValue : function () { @@ -542,7 +542,7 @@ function ahuacatlListTestSuite () { }, //////////////////////////////////////////////////////////////////////////////// -/// @brief test remove_value function +/// @brief test removeValue function //////////////////////////////////////////////////////////////////////////////// testRemoveValueInvalid : function () { @@ -552,7 +552,7 @@ function ahuacatlListTestSuite () { }, //////////////////////////////////////////////////////////////////////////////// -/// @brief test remove_nth function +/// @brief test removeNth function //////////////////////////////////////////////////////////////////////////////// testRemoveNth : function () { @@ -591,7 +591,7 @@ function ahuacatlListTestSuite () { }, //////////////////////////////////////////////////////////////////////////////// -/// @brief test remove_nth function +/// @brief test removeNth function //////////////////////////////////////////////////////////////////////////////// testRemoveNthInvalid : function () {