From 9e78ab5af57767cc311d1589561c0c8d77eb8012 Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Mon, 4 Nov 2013 18:24:03 +0100 Subject: [PATCH] added NTH and POSITION --- CHANGELOG | 2 + Documentation/UserManual/Aql.md | 10 ++ arangod/Ahuacatl/ahuacatl-functions.c | 2 + js/server/modules/org/arangodb/ahuacatl.js | 40 ++++++ js/server/tests/ahuacatl-functions.js | 143 +++++++++++++++++++++ 5 files changed, 197 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 9ecf26d0e3..a4bfa39d7b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ v1.5.x (XXXX-XX-XX) ------------------- +* added AQL functions `NTH` and `POSITION` + * added signal handler for arangosh to save last command in more cases * added extra prompt placeholders for arangosh: diff --git a/Documentation/UserManual/Aql.md b/Documentation/UserManual/Aql.md index 5d80b71d38..7495f60647 100644 --- a/Documentation/UserManual/Aql.md +++ b/Documentation/UserManual/Aql.md @@ -1103,6 +1103,16 @@ AQL supports the following functions to operate on list values: - @FN{LAST(@FA{list})}: returns the last element in @FA{list} or `null` if the list is empty. +- @FN{NTH(@FA{list}, @FA{position})}: returns the list element at position @FA{position}. + Positions start at 0. If @FA{position} is negative or beyond the upper bound of the list + specified by @FA{list}, then `null` will be returned. + +- @FN{POSITION(@FA{list}, @FA{search}, @FA{return-index})}: returns the position of the + element @FA{search} in list @FA{list}. Positions start at 0. If the element is not + found, then `-1` is returned. If @FA{return-index} is `false`, then instead of the + position only `true` or `false` are returned, depending on whether the sought element + is contained in the list. + - @FN{UNIQUE(@FA{list})}: returns all unique elements in @FA{list}. To determine uniqueness, the function will use the comparison order defined in @ref AqlTypeOrder. Calling this function might return the unique elements in any order. diff --git a/arangod/Ahuacatl/ahuacatl-functions.c b/arangod/Ahuacatl/ahuacatl-functions.c index 34c203bb04..3f639a4ce4 100644 --- a/arangod/Ahuacatl/ahuacatl-functions.c +++ b/arangod/Ahuacatl/ahuacatl-functions.c @@ -673,6 +673,8 @@ TRI_associative_pointer_t* TRI_CreateFunctionsAql (void) { REGISTER_FUNCTION("REVERSE", "REVERSE", true, false, "ls", NULL); REGISTER_FUNCTION("FIRST", "FIRST", true, false, "l", NULL); REGISTER_FUNCTION("LAST", "LAST", true, false, "l", NULL); + REGISTER_FUNCTION("NTH", "NTH", true, false, "l,n", NULL); + REGISTER_FUNCTION("POSITION", "POSITION", true, false, "l,.|b", NULL); // document functions REGISTER_FUNCTION("HAS", "HAS", true, false, "az,s", NULL); diff --git a/js/server/modules/org/arangodb/ahuacatl.js b/js/server/modules/org/arangodb/ahuacatl.js index bf6687aa59..c30e907a18 100644 --- a/js/server/modules/org/arangodb/ahuacatl.js +++ b/js/server/modules/org/arangodb/ahuacatl.js @@ -2482,6 +2482,44 @@ function LAST (value) { return value[value.length - 1]; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief get the position of an element in a list +//////////////////////////////////////////////////////////////////////////////// + +function POSITION (value, search, returnIndex) { + "use strict"; + + LIST(value); + returnIndex = returnIndex || false; + + var i, n = value.length; + + if (n > 0) { + for (i = 0; i < n; ++i) { + if (RELATIONAL_EQUAL(value[i], search)) { + return returnIndex ? i : true; + } + } + } + + return returnIndex ? -1 : false; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief get the nth element in a list, or null if the item does not exist +//////////////////////////////////////////////////////////////////////////////// + +function NTH (value, position) { + "use strict"; + + LIST(value); + if (position < 0 || position >= value.length) { + return null; + } + + return value[position]; +} + //////////////////////////////////////////////////////////////////////////////// /// @brief reverse the elements in a list or in a string //////////////////////////////////////////////////////////////////////////////// @@ -4035,6 +4073,8 @@ exports.LIMIT = LIMIT; exports.LENGTH = LENGTH; exports.FIRST = FIRST; exports.LAST = LAST; +exports.POSITION = POSITION; +exports.NTH = NTH; exports.REVERSE = REVERSE; exports.RANGE = RANGE; exports.UNIQUE = UNIQUE; diff --git a/js/server/tests/ahuacatl-functions.js b/js/server/tests/ahuacatl-functions.js index 3d71255954..e1e2e0ccd8 100644 --- a/js/server/tests/ahuacatl-functions.js +++ b/js/server/tests/ahuacatl-functions.js @@ -306,6 +306,149 @@ function ahuacatlFunctionsTestSuite () { assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN LAST({ })"); }, +//////////////////////////////////////////////////////////////////////////////// +/// @brief test position function +//////////////////////////////////////////////////////////////////////////////// + + testPosition : function () { + var list = [ "foo", "bar", null, "baz", true, 42, [ "BORK" ], { code: "foo", name: "test" }, false, 0, "" ]; + + var data = [ + [ "foo", 0 ], + [ "bar", 1 ], + [ null, 2 ], + [ "baz", 3 ], + [ true, 4 ], + [ 42, 5 ], + [ [ "BORK" ], 6 ], + [ { code: "foo", name: "test" }, 7 ], + [ false, 8 ], + [ 0, 9 ], + [ "", 10 ] + ]; + + data.forEach(function (d) { + var search = d[0]; + var expected = d[1]; + + // find if element is contained in list (should be true) + var actual = getQueryResults("RETURN POSITION(@list, @search, false)", { list: list, search: search }); + assertTrue(actual[0]); + + // find position of element in list + actual = getQueryResults("RETURN POSITION(@list, @search, true)", { list: list, search: search }); + assertEqual(expected, actual[0]); + + // look up the element using the position + actual = getQueryResults("RETURN NTH(@list, @position)", { list: list, position: actual[0] }); + assertEqual(search, actual[0]); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test position function +//////////////////////////////////////////////////////////////////////////////// + + testPositionNotThere : function () { + var list = [ "foo", "bar", null, "baz", true, 42, [ "BORK" ], { code: "foo", name: "test" }, false, 0, "" ]; + + var data = [ "foot", "barz", 43, 1, -42, { code: "foo", name: "test", bar: "baz" }, " ", " foo", "FOO", "bazt", 0.1, [ ], [ "bork" ] ]; + + data.forEach(function (d) { + var actual = getQueryResults("RETURN POSITION(@list, @search, false)", { list: list, search: d }); + assertFalse(actual[0]); + + actual = getQueryResults("RETURN POSITION(@list, @search, true)", { list: list, search: d }); + assertEqual(-1, actual[0]); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test position function +//////////////////////////////////////////////////////////////////////////////// + + testPositionInvalid : function () { + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN POSITION()"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN POSITION([ ])"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN POSITION(null, 'foo')"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN POSITION(true, 'foo')"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN POSITION(4, 'foo')"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN POSITION(\"yes\", 'foo')"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN POSITION({ }, 'foo')"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test nth function +//////////////////////////////////////////////////////////////////////////////// + + testNthEmpty : function () { + var i; + + for (i = -3; i <= 3; ++i) { + var actual = getQueryResults("RETURN NTH([ ], @pos)", { pos: i }); + assertNull(actual[0]); + } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test nth function +//////////////////////////////////////////////////////////////////////////////// + + testNthNegative : function () { + var i; + + for (i = -3; i <= 3; ++i) { + var actual = getQueryResults("RETURN NTH([ 1, 2, 3, 4 ], @pos)", { pos: i }); + if (i < 0) { + assertNull(actual[0]); + } + else { + assertEqual(i + 1, actual[0]); + } + } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test nth function +//////////////////////////////////////////////////////////////////////////////// + + testNthBounds : function () { + var i; + + for (i = 0; i <= 10; ++i) { + var actual = getQueryResults("RETURN NTH([ 'a1', 'a2', 'a3', 'a4', 'a5' ], @pos)", { pos: i }); + if (i < 5) { + assertEqual('a' + (i + 1), actual[0]); + } + else { + assertNull(actual[0]); + } + } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test nth function +//////////////////////////////////////////////////////////////////////////////// + + testNthInvalid : function () { + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN NTH()"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN NTH([ ])"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN NTH(null, 1)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN NTH(true, 1)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN NTH(4, 1)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN NTH(\"yes\", 1)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN NTH({ }, 1)"); + + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN NTH([ ], null)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN NTH([ ], false)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN NTH([ ], true)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN NTH([ ], '')"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN NTH([ ], '1234')"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN NTH([ ], [ ])"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN NTH([ ], { \"foo\": true})"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN NTH([ ], { })"); + }, + //////////////////////////////////////////////////////////////////////////////// /// @brief test reverse function ////////////////////////////////////////////////////////////////////////////////