diff --git a/CHANGELOG b/CHANGELOG index c9596a8c20..52768bcd1b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,17 +1,6 @@ v1.5.0 (XXXX-XX-XX) ------------------- -* added command-line option `--server.disable-authentication-unix-sockets` - - with this option, authentication can be disabled for all requests coming - in via UNIX domain sockets, enabling clients located on the same host as - the ArangoDB server to connect without authentication. - Other connections (e.g. TCP/IP) are not affected by this option. - - The default value for this option is `false`. - Note: this option is only supported on platforms that support Unix domain - sockets. - * fail if invalid `strategy`, `order` or `itemOrder` attribute values are passed to the AQL TRAVERSAL function. Omitting these attributes is not considered an error, but specifying an invalid value for any @@ -162,6 +151,48 @@ 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 + in via UNIX domain sockets, enabling clients located on the same host as + the ArangoDB server to connect without authentication. + Other connections (e.g. TCP/IP) are not affected by this option. + + The default value for this option is `false`. + Note: this option is only supported on platforms that support Unix domain + sockets. + +* call global arangod instance destructor on shutdown + * issue #755: TRAVERSAL does not use strategy, order and itemOrder options these options were not honored when configuring a traversal via the AQL diff --git a/Documentation/UserManual/Aql.md b/Documentation/UserManual/Aql.md index 284827f6ba..a0eb4994ec 100644 --- a/Documentation/UserManual/Aql.md +++ b/Documentation/UserManual/Aql.md @@ -1727,6 +1727,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 b8d1c3d767..0a15878e5e 100644 --- a/arangod/Ahuacatl/ahuacatl-functions.c +++ b/arangod/Ahuacatl/ahuacatl-functions.c @@ -716,6 +716,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 ffb9535338..0dbb1ecfdc 100644 --- a/js/server/modules/org/arangodb/ahuacatl.js +++ b/js/server/modules/org/arangodb/ahuacatl.js @@ -3284,6 +3284,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 //////////////////////////////////////////////////////////////////////////////// @@ -4210,6 +4255,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 500443b056..8220a4fcef 100644 --- a/js/server/tests/ahuacatl-functions.js +++ b/js/server/tests/ahuacatl-functions.js @@ -2147,6 +2147,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 ////////////////////////////////////////////////////////////////////////////////