From fe9464afeca71850d6b3665d4b0ca8189969dbe3 Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Mon, 3 Feb 2014 16:37:07 +0100 Subject: [PATCH] added SKIPLIST AQL function --- CHANGELOG | 29 ++++++ Documentation/UserManual/Aql.md | 17 ++++ arangod/Ahuacatl/ahuacatl-functions.c | 1 + js/server/modules/org/arangodb/ahuacatl.js | 46 ++++++++++ js/server/tests/ahuacatl-functions.js | 100 +++++++++++++++++++++ 5 files changed, 193 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 3fbcd9e32c..2dee517dab 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -146,6 +146,35 @@ v1.5.0 (XXXX-XX-XX) v1.4.9 (XXXX-XX-XX) ------------------- +* added AQL function `SKIPLIST` to directly access skiplist indexes from AQL + + This is a shortcut method to use a skiplist index for retrieving specific documents in + indexed order. The function capability is rather limited, but it may be used + for several cases to speed up queries. The documents are returned in index order if + only one condition is used. + + /* return all documents with mycollection.created > 12345678 */ + FOR doc IN SKIPLIST(mycollection, { created: [[ '>', 12345678 ]] }) + RETURN doc + + /* return first document with mycollection.created > 12345678 */ + FOR doc IN SKIPLIST(mycollection, { created: [[ '>', 12345678 ]] }, 0, 1) + RETURN doc + + /* return all documents with mycollection.created between 12345678 and 123456790 */ + FOR doc IN SKIPLIST(mycollection, { created: [[ '>', 12345678 ], [ '<=', 123456790 ]] }) + RETURN doc + + /* return all documents with mycollection.a equal 1 and .b equal 2 */ + FOR doc IN SKIPLIST(mycollection, { a: [[ '==', 1 ]], b: [[ '==', 2 ]] }) + RETURN doc + + The function requires a skiplist index with the exact same attributes to + be present on the specified colelction. All attributes present in the skiplist + index must be specified in the conditions specified for the `SKIPLIST` function. + Attribute declaration order is important, too: attributes must be specified in the + same order in the condition as they have been declared in the skiplist index. + * added command-line option `--server.disable-authentication-unix-sockets` with this option, authentication can be disabled for all requests coming diff --git a/Documentation/UserManual/Aql.md b/Documentation/UserManual/Aql.md index f82a84b695..07d4b356b4 100644 --- a/Documentation/UserManual/Aql.md +++ b/Documentation/UserManual/Aql.md @@ -1617,6 +1617,23 @@ function categories: DOCUMENT("users/john") DOCUMENT([ "users/john", "users/amy" ]) +- @FN{SKIPLIST(@FA{collection}, @FA{condition}, @FA{skip}, @FA{limit})}: return all documents + from a skiplist index on collection @FA{collection} that match the specified @FA{condition}. + This is a shortcut method to use a skiplist index for retrieving specific documents in + indexed order. The skiplist index supports equality and less than/greater than queries. The + @FA{skip} and @FA{limit} parameters are optional but can be specified to further limit the + results: + + SKIPLIST(test, { created: [[ '>', 0 ]] }, 0, 100) + SKIPLIST(test, { age: [[ '>', 25 ], [ '<=', 65 ]] }) + SKIPLIST(test, { a: [[ '==', 10 ]], b: [[ '==', 25 ]] } + + The @FA{condition} document must contain an entry for each attribute that is contained in the + index. It is not allowed to specify just a subset of attributes that are present in an index. + Additionally the attributes in the @FA{condition} document must be specified in the same order + as in the index. + If no suitable skiplist index is found, an error will be raised and the query will be aborted. + High-level operations {#AqlOperations} ====================================== diff --git a/arangod/Ahuacatl/ahuacatl-functions.c b/arangod/Ahuacatl/ahuacatl-functions.c index be469118af..f1e82d728f 100644 --- a/arangod/Ahuacatl/ahuacatl-functions.c +++ b/arangod/Ahuacatl/ahuacatl-functions.c @@ -713,6 +713,7 @@ TRI_associative_pointer_t* TRI_CreateFunctionsAql (void) { REGISTER_FUNCTION("FIRST_LIST", "FIRST_LIST", true, false, ".|+", NULL); REGISTER_FUNCTION("FIRST_DOCUMENT", "FIRST_DOCUMENT", true, false, ".|+", NULL); REGISTER_FUNCTION("PARSE_IDENTIFIER", "PARSE_IDENTIFIER", true, false, ".", NULL); + REGISTER_FUNCTION("SKIPLIST", "SKIPLIST_QUERY", false, false, "h,a|n,n", NULL); if (! result) { TRI_FreeFunctionsAql(functions); diff --git a/js/server/modules/org/arangodb/ahuacatl.js b/js/server/modules/org/arangodb/ahuacatl.js index bdbe86532d..23cb1b6797 100644 --- a/js/server/modules/org/arangodb/ahuacatl.js +++ b/js/server/modules/org/arangodb/ahuacatl.js @@ -3246,6 +3246,51 @@ function PARSE_IDENTIFIER (value) { THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, "PARSE_IDENTIFIER"); } +//////////////////////////////////////////////////////////////////////////////// +/// @brief query a skiplist index +/// +/// returns a documents from a skiplist index on the specified collection. Only +/// documents that match the specified condition will be returned. +//////////////////////////////////////////////////////////////////////////////// + +function SKIPLIST_QUERY (collection, condition, skip, limit) { + "use strict"; + + var keys = [ ], key, idx; + + for (key in condition) { + if (condition.hasOwnProperty(key)) { + keys.push(key); + } + } + + var c = COLLECTION(collection); + if (c === null) { + THROW(INTERNAL.errors.ERROR_ARANGO_COLLECTION_NOT_FOUND, collection); + } + + idx = c.lookupSkiplist.apply(c, keys); + + if (idx === null) { + THROW(INTERNAL.errors.ERROR_ARANGO_NO_INDEX); + } + + if (skip === undefined || skip === null) { + skip = 0; + } + + if (limit === undefined || limit === null) { + limit = null; + } + + try { + return c.BY_CONDITION_SKIPLIST(idx.id, condition, skip, limit).documents; + } + catch (err) { + THROW(INTERNAL.errors.ERROR_ARANGO_NO_INDEX); + } +} + //////////////////////////////////////////////////////////////////////////////// /// @brief check whether a document has a specific attribute //////////////////////////////////////////////////////////////////////////////// @@ -4109,6 +4154,7 @@ exports.NOT_NULL = NOT_NULL; exports.FIRST_LIST = FIRST_LIST; exports.FIRST_DOCUMENT = FIRST_DOCUMENT; exports.PARSE_IDENTIFIER = PARSE_IDENTIFIER; +exports.SKIPLIST_QUERY = SKIPLIST_QUERY; exports.HAS = HAS; exports.ATTRIBUTES = ATTRIBUTES; exports.UNSET = UNSET; diff --git a/js/server/tests/ahuacatl-functions.js b/js/server/tests/ahuacatl-functions.js index 8abf40d3a8..97ffcf68d3 100644 --- a/js/server/tests/ahuacatl-functions.js +++ b/js/server/tests/ahuacatl-functions.js @@ -2004,6 +2004,106 @@ function ahuacatlFunctionsTestSuite () { internal.db._drop(cn); }, +//////////////////////////////////////////////////////////////////////////////// +/// @brief test skiplist function +//////////////////////////////////////////////////////////////////////////////// + + testSkiplist1 : function () { + var cn = "UnitTestsAhuacatlFunctions"; + + internal.db._drop(cn); + var cx = internal.db._create(cn); + cx.ensureSkiplist("created"); + + var i; + for (i = 0; i < 1000; ++i) { + cx.save({ created: i }); + } + + expected = [ { created: 0 } ]; + actual = getQueryResults("FOR x IN SKIPLIST(" + cn + ", { created: [[ '>=', 0 ]] }, 0, 1) RETURN x"); + assertEqual(expected, actual); + actual = getQueryResults("FOR x IN SKIPLIST(" + cn + ", { created: [[ '>=', 0 ], [ '<', 1 ]] }) RETURN x"); + assertEqual(expected, actual); + + expected = [ { created: 1 } ]; + actual = getQueryResults("FOR x IN SKIPLIST(" + cn + ", { created: [[ '>', 0 ]] }, 0, 1) RETURN x"); + assertEqual(expected, actual); + actual = getQueryResults("FOR x IN SKIPLIST(" + cn + ", { created: [[ '>', 0 ], [ '<=', 1 ]] }) RETURN x"); + assertEqual(expected, actual); + + expected = [ { created: 0 }, { created: 1 }, { created: 2 } ]; + actual = getQueryResults("FOR x IN SKIPLIST(" + cn + ", { created: [[ '>=', 0 ]] }, 0, 3) RETURN x"); + assertEqual(expected, actual); + + expected = [ { created: 5 }, { created: 6 } ]; + actual = getQueryResults("FOR x IN SKIPLIST(" + cn + ", { created: [[ '>=', 0 ]] }, 5, 2) RETURN x"); + assertEqual(expected, actual); + + expected = [ { created: 5 }, { created: 6 } ]; + actual = getQueryResults("FOR x IN SKIPLIST(" + cn + ", { created: [[ '>=', 5 ], [ '<=', 6 ]] }, 0, 5) RETURN x"); + assertEqual(expected, actual); + + expected = [ { created: 5 } ]; + actual = getQueryResults("FOR x IN SKIPLIST(" + cn + ", { created: [[ '>=', 5 ], [ '<=', 6 ]] }, 0, 1) RETURN x"); + assertEqual(expected, actual); + + expected = [ { created: 2 }, { created: 3 } ]; + actual = getQueryResults("FOR x IN SKIPLIST(" + cn + ", { created: [[ '<', 4 ]] }, 2, 10) RETURN x"); + assertEqual(expected, actual); + + expected = [ ]; + actual = getQueryResults("FOR x IN SKIPLIST(" + cn + ", { created: [[ '>', 0 ]] }, 10000, 10) RETURN x"); + assertEqual(expected, actual); + + assertQueryError(errors.ERROR_ARANGO_NO_INDEX.code, "RETURN SKIPLIST(" + cn + ", { a: [[ '==', 1 ]] })"); + + internal.db._drop(cn); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test skiplist function +//////////////////////////////////////////////////////////////////////////////// + + testSkiplist2 : function () { + var cn = "UnitTestsAhuacatlFunctions"; + + internal.db._drop(cn); + var cx = internal.db._create(cn); + + assertQueryError(errors.ERROR_ARANGO_NO_INDEX.code, "RETURN SKIPLIST(" + cn + ", { a: [[ '==', 1 ]], b: [[ '==', 0 ]] })"); + + cx.ensureSkiplist("a", "b"); + + var i; + for (i = 0; i < 1000; ++i) { + cx.save({ a: i, b: i + 1 }); + } + + expected = [ { a: 1, b: 2 } ]; + actual = getQueryResults("FOR x IN SKIPLIST(" + cn + ", { a: [[ '==', 1 ]], b: [[ '>=', 2 ], [ '<=', 3 ]] }) RETURN x"); + assertEqual(expected, actual); + + expected = [ { a: 1, b: 2 } ]; + actual = getQueryResults("FOR x IN SKIPLIST(" + cn + ", { a: [[ '==', 1 ]], b: [[ '==', 2 ]] }) RETURN x"); + assertEqual(expected, actual); + + assertQueryError(errors.ERROR_ARANGO_NO_INDEX.code, "RETURN SKIPLIST(" + cn + ", { b: [[ '==', 2 ]], a: [[ '==', 1 ]] })"); + + expected = [ ]; + actual = getQueryResults("FOR x IN SKIPLIST(" + cn + ", { a: [[ '==', 2 ]], b: [[ '==', 1 ]] }, 1, 1) RETURN x"); + assertEqual(expected, actual); + actual = getQueryResults("FOR x IN SKIPLIST(" + cn + ", { a: [[ '==', 99 ]], b: [[ '==', 17 ]] }, 1, 1) RETURN x"); + assertEqual(expected, actual); + + assertQueryError(errors.ERROR_ARANGO_NO_INDEX.code, "RETURN SKIPLIST(" + cn + ", { a: [[ '==', 1 ]] })"); + assertQueryError(errors.ERROR_ARANGO_NO_INDEX.code, "RETURN SKIPLIST(" + cn + ", { b: [[ '==', 1 ]] })"); + assertQueryError(errors.ERROR_ARANGO_NO_INDEX.code, "RETURN SKIPLIST(" + cn + ", { c: [[ '==', 1 ]] })"); + + internal.db._drop(cn); + }, + + //////////////////////////////////////////////////////////////////////////////// /// @brief test min function ////////////////////////////////////////////////////////////////////////////////