From 3e04acf3eeb705c2d1782e596940d94a597ded3a Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Thu, 14 Feb 2013 16:14:22 +0100 Subject: [PATCH] added AQL functions KEEP() and UNSET() --- CHANGELOG | 2 + Documentation/Manual/NewFeatures12.md | 4 ++ Documentation/UserManual/Aql.md | 14 ++++ arangod/Ahuacatl/ahuacatl-functions.c | 2 + js/server/modules/org/arangodb/ahuacatl.js | 78 ++++++++++++++++++++++ js/server/tests/ahuacatl-functions.js | 63 +++++++++++++++++ 6 files changed, 163 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index d12725ff4c..94196f5524 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ v1.2.alpha (XXXX-XX-XX) ----------------------- +* added AQL functions KEEP() and UNSET() + * fixed issue #348: "HTTP Interface for Administration and Monitoring" documentation errors. diff --git a/Documentation/Manual/NewFeatures12.md b/Documentation/Manual/NewFeatures12.md index 132d7bee95..ce64c676b1 100644 --- a/Documentation/Manual/NewFeatures12.md +++ b/Documentation/Manual/NewFeatures12.md @@ -154,6 +154,10 @@ in ArangoDB 1.2: * `ATTRIBUTES()`: returns the names of all attributes of a document as a list +* `KEEP()`: keeps only the specified attributes of a document, and removes all others + +* `UNSET()`: removes only the specified attributes from a document, and preserves all others + * `MATCHES()`: to check if a document matches one of multiple example documents * `LIKE()`: pattern-based text comparison diff --git a/Documentation/UserManual/Aql.md b/Documentation/UserManual/Aql.md index faf8b89a46..0485195b42 100644 --- a/Documentation/UserManual/Aql.md +++ b/Documentation/UserManual/Aql.md @@ -1032,6 +1032,20 @@ AQL supports the following functions to operate on document values: `_key` etc.) are removed from the result. If @FA{sort} is set to `true`, then the attribute names in the result will be sorted. Otherwise they will be returned in any order. +- @FN{UNSET(@FA{document}\, @FA{attributename}\, ...)}: removes the attributes @FA{attributename} + (can be one or many) from @FA{document}. All other attributes will be preserved. + Multiple attribute names can be specified by either passing multiple individual string argument + names, or by passing a list of attribute names: + + RETURN UNSET(doc, '_id', '_key', [ 'foo', 'bar' ]) + +- @FN{KEEP(@FA{document}\, @FA{attributename}\, ...)}: keeps only the attributes @FA{attributename} + (can be one or many) from @FA{document}. All other attributes will be removed from the result. + Multiple attribute names can be specified by either passing multiple individual string argument + names, or by passing a list of attribute names: + + RETURN KEEP(doc, 'firstname', 'name', 'likes') + @subsubsection AqlFunctionsGeo Geo functions AQL offers the following functions to filter data based on geo indexes: diff --git a/arangod/Ahuacatl/ahuacatl-functions.c b/arangod/Ahuacatl/ahuacatl-functions.c index 88bf598d01..2885f69408 100644 --- a/arangod/Ahuacatl/ahuacatl-functions.c +++ b/arangod/Ahuacatl/ahuacatl-functions.c @@ -625,6 +625,8 @@ TRI_associative_pointer_t* TRI_InitialiseFunctionsAql (void) { REGISTER_FUNCTION("MERGE_RECURSIVE", "MERGE_RECURSIVE", true, false, "a,a|+", NULL); REGISTER_FUNCTION("DOCUMENT", "DOCUMENT", false, false, "h,sl", NULL); REGISTER_FUNCTION("MATCHES", "MATCHES", true, false, ".,l|b", NULL); + REGISTER_FUNCTION("UNSET", "UNSET", true, false, "a,sl|+", NULL); + REGISTER_FUNCTION("KEEP", "KEEP", true, false, "a,sl|+", NULL); // geo functions REGISTER_FUNCTION("NEAR", "GEO_NEAR", false, false, "h,n,n,n|s", NULL); diff --git a/js/server/modules/org/arangodb/ahuacatl.js b/js/server/modules/org/arangodb/ahuacatl.js index c89a8126e9..ffe1ac884c 100644 --- a/js/server/modules/org/arangodb/ahuacatl.js +++ b/js/server/modules/org/arangodb/ahuacatl.js @@ -354,6 +354,34 @@ function VALUES (value) { return values; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief extract key names from an argument list +//////////////////////////////////////////////////////////////////////////////// + +function EXTRACT_KEYS (args, startArgument, functionName) { + var keys = { }, i, j, key, key2; + + for (i = startArgument; i < args.length; ++i) { + key = args[i]; + if (typeof key === 'string') { + keys[key] = true; + } + else if (Array.isArray(key)) { + for (j = 0; j < key.length; ++j) { + key2 = key[j]; + if (typeof key2 === 'string') { + keys[key2] = true; + } + else { + THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, functionName); + } + } + } + } + + return keys; +} + //////////////////////////////////////////////////////////////////////////////// /// @brief get the keys of an array or object in a comparable way //////////////////////////////////////////////////////////////////////////////// @@ -2330,6 +2358,54 @@ function ATTRIBUTES (element, removeInternal, sort) { return result; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief unset specific attributes from a document +//////////////////////////////////////////////////////////////////////////////// + +function UNSET (value) { + if (TYPEWEIGHT(value) !== TYPEWEIGHT_DOCUMENT) { + THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, "UNSET"); + } + + var keys = EXTRACT_KEYS(arguments, 1, "UNSET"), i; + + var result = { }; + // copy over all that is left + for (i in value) { + if (value.hasOwnProperty(i)) { + if (keys[i] !== true) { + result[i] = CLONE(value[i]); + } + } + } + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief keep specific attributes from a document +//////////////////////////////////////////////////////////////////////////////// + +function KEEP (value) { + if (TYPEWEIGHT(value) !== TYPEWEIGHT_DOCUMENT) { + THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, "KEEP"); + } + + var keys = EXTRACT_KEYS(arguments, 1, "KEEP"), i; + + // copy over all that is left + var result = { }; + for (i in keys) { + if (keys.hasOwnProperty(i)) { + if (value.hasOwnProperty(i)) { + result[i] = CLONE(value[i]); + } + } + } + + return result; +} + //////////////////////////////////////////////////////////////////////////////// /// @brief merge all arguments //////////////////////////////////////////////////////////////////////////////// @@ -2980,6 +3056,8 @@ exports.FIRST_LIST = FIRST_LIST; exports.FIRST_DOCUMENT = FIRST_DOCUMENT; exports.HAS = HAS; exports.ATTRIBUTES = ATTRIBUTES; +exports.UNSET = UNSET; +exports.KEEP = KEEP; exports.MERGE = MERGE; exports.MERGE_RECURSIVE = MERGE_RECURSIVE; exports.MATCHES = MATCHES; diff --git a/js/server/tests/ahuacatl-functions.js b/js/server/tests/ahuacatl-functions.js index 0f9a8f5fdd..7cff572fea 100644 --- a/js/server/tests/ahuacatl-functions.js +++ b/js/server/tests/ahuacatl-functions.js @@ -913,6 +913,69 @@ function ahuacatlFunctionsTestSuite () { assertEqual(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, getErrorCode(function() { QUERY("RETURN RAND(2)"); } )); }, +//////////////////////////////////////////////////////////////////////////////// +/// @brief test keep function +//////////////////////////////////////////////////////////////////////////////// + + testKeepInvalid : function () { + assertEqual(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, getErrorCode(function() { QUERY("RETURN KEEP({ }, 1)"); } )); + assertEqual(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, getErrorCode(function() { QUERY("RETURN KEEP({ }, { })"); } )); + assertEqual(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, getErrorCode(function() { QUERY("RETURN KEEP({ }, 'foo', { })"); } )); + assertEqual(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, getErrorCode(function() { QUERY("RETURN KEEP('foo', 'foo')"); } )); + + assertEqual(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, getErrorCode(function() { QUERY("RETURN KEEP()"); } )); + assertEqual(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, getErrorCode(function() { QUERY("RETURN KEEP({ })"); } )); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test keep function +//////////////////////////////////////////////////////////////////////////////// + + testKeep : function () { + var actual, expected; + + actual = getQueryResults("FOR i IN [ { }, { foo: 1, bar: 2, moo: 3, goof: 4, bang: 5, meow: 6 }, { foo: 0, goof: 1, meow: 2 }, { foo: null }, { foo: true }, { goof: null } ] RETURN KEEP(i, 'foo', 'bar', 'baz', [ 'meow' ], [ ])", false); + assertEqual([ { }, { bar: 2, foo: 1, meow: 6 }, { foo: 0, meow: 2 }, { foo: null }, { foo: true }, { } ], actual); + + actual = getQueryResults("FOR i IN [ { }, { foo: 1, bar: 2, moo: 3, goof: 4, bang: 5, meow: 6 }, { foo: 0, goof: 1, meow: 2 }, { foo: null }, { foo: true }, { goof: null } ] RETURN KEEP(i, [ 'foo', 'bar', 'baz', 'meow' ])", false); + assertEqual([ { }, { bar: 2, foo: 1, meow: 6 }, { foo: 0, meow: 2 }, { foo: null }, { foo: true }, { } ], actual); + + actual = getQueryResults("FOR i IN [ { }, { foo: 1, bar: 2, moo: 3, goof: 4, bang: 5, meow: 6 }, { foo: 0, goof: 1, meow: 2 }, { foo: null }, { foo: true }, { goof: null } ] RETURN KEEP(i, 'foo', 'bar', 'baz', 'meow')", false); + assertEqual([ { }, { bar: 2, foo: 1, meow: 6 }, { foo: 0, meow: 2 }, { foo: null }, { foo: true }, { } ], actual); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test unset function +//////////////////////////////////////////////////////////////////////////////// + + testUnsetInvalid : function () { + assertEqual(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, getErrorCode(function() { QUERY("RETURN UNSET({ }, 1)"); } )); + assertEqual(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, getErrorCode(function() { QUERY("RETURN UNSET({ }, { })"); } )); + assertEqual(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, getErrorCode(function() { QUERY("RETURN UNSET({ }, 'foo', { })"); } )); + assertEqual(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, getErrorCode(function() { QUERY("RETURN UNSET('foo', 'foo')"); } )); + + assertEqual(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, getErrorCode(function() { QUERY("RETURN UNSET()"); } )); + assertEqual(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, getErrorCode(function() { QUERY("RETURN UNSET({ })"); } )); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test unset function +//////////////////////////////////////////////////////////////////////////////// + + testUnset : function () { + var expected = [ { bang: 5, goof: 4, moo: 3 }, { goof: 1 }, { }, { }, { goof: null } ]; + var actual; + + actual = getQueryResults("FOR i IN [ { foo: 1, bar: 2, moo: 3, goof: 4, bang: 5, meow: 6 }, { foo: 0, goof: 1, meow: 2 }, { foo: null }, { foo: true }, { goof: null } ] RETURN UNSET(i, 'foo', 'bar', 'baz', [ 'meow' ], [ ])", false); + assertEqual(expected, actual); + + actual = getQueryResults("FOR i IN [ { foo: 1, bar: 2, moo: 3, goof: 4, bang: 5, meow: 6 }, { foo: 0, goof: 1, meow: 2 }, { foo: null }, { foo: true }, { goof: null } ] RETURN UNSET(i, [ 'foo', 'bar', 'baz', 'meow' ])", false); + assertEqual(expected, actual); + + actual = getQueryResults("FOR i IN [ { foo: 1, bar: 2, moo: 3, goof: 4, bang: 5, meow: 6 }, { foo: 0, goof: 1, meow: 2 }, { foo: null }, { foo: true }, { goof: null } ] RETURN UNSET(i, 'foo', 'bar', 'baz', 'meow')", false); + assertEqual(expected, actual); + }, + //////////////////////////////////////////////////////////////////////////////// /// @brief test merge function ////////////////////////////////////////////////////////////////////////////////