diff --git a/arangod/Aql/Expression.cpp b/arangod/Aql/Expression.cpp index b98a54b395..63864b9458 100644 --- a/arangod/Aql/Expression.cpp +++ b/arangod/Aql/Expression.cpp @@ -278,9 +278,91 @@ void Expression::invalidate () { } // ----------------------------------------------------------------------------- -// --SECTION-- private functions +// --SECTION-- private methods // ----------------------------------------------------------------------------- +//////////////////////////////////////////////////////////////////////////////// +/// @brief extracts a range from an array +//////////////////////////////////////////////////////////////////////////////// + +AqlValue Expression::extractRange (AqlValue const& result, + AqlValue const& indexResult, + TRI_document_collection_t const* collection, + triagens::arango::AqlTransaction* trx) const { + TRI_ASSERT(result.isArray()); + + size_t const length = result.arraySize(); + + if (length == 0) { + // cannot extract anything from an empty array + return AqlValue(new Json(Json::Array)); + } + + int64_t low = indexResult.getRange()->_low; + int64_t high = indexResult.getRange()->_high; + + // negative positions are translated into their positive counterparts + if (low < 0) { + low = static_cast(length) + low; + } + if (high < 0) { + high = static_cast(length) + high; + } + + std::unique_ptr array(new Json(Json::Array)); + + if (low <= high) { + // forward iteration + // adjust bounds + if (low < 0) { + low = 0; + } + if (high >= static_cast(length)) { + high = static_cast(length); + } + else { + // increase by one so we include the last specified element + ++high; + } + if (low <= high) { + array->reserve(static_cast(high - low)); + // forward iteration + for (int64_t position = low; position < high; ++position) { + auto j = result.extractArrayMember(trx, collection, position, true); + array->add(j); + } + } + } + else { + // backward iteration + // swap low and high + int64_t tmp = low; + low = high; + high = tmp; + + // backward iteration + if (low < 0) { + low = 0; + } + if (high < 0) { + high = 0; + } + else if (high >= static_cast(length)) { + high = static_cast(length) - 1; + } + + if (high >= low) { + array->reserve(static_cast(high - low + 1)); + for (int64_t position = high; position >= low; --position) { + auto j = result.extractArrayMember(trx, collection, position, true); + array->add(j); + } + } + } + + return AqlValue(array.release()); +} + //////////////////////////////////////////////////////////////////////////////// /// @brief find a value in an AQL list node /// this performs either a binary search (if the node is sorted) or a @@ -520,79 +602,15 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node, // no number found. } } - else if (indexResult.isRange()) { - size_t const length = result.arraySize(); - - if (length == 0) { - // cannot extract anything from an empty array + else if (indexResult.isRange()) { + try { + auto range = extractRange(result, indexResult, myCollection, trx); + indexResult.destroy(); result.destroy(); - return AqlValue(new Json(Json::Array)); - } - - int64_t low = indexResult.getRange()->_low; - int64_t high = indexResult.getRange()->_high; - indexResult.destroy(); - - // negative positions are translated into their positive counterparts - if (low < 0) { - low = static_cast(length) + low; - } - if (high < 0) { - high = static_cast(length) + high; - } - - try { - std::unique_ptr array(new Json(Json::Array)); - - if (low <= high) { - // adjust bounds - if (low < 0) { - low = 0; - } - if (high >= static_cast(length)) { - high = static_cast(length); - } - else { - // increase by one so we include the last specified element - ++high; - } - if (low <= high) { - array->reserve(static_cast(high - low)); - // forward iteration - for (int64_t position = low; position < high; ++position) { - auto j = result.extractArrayMember(trx, myCollection, position, true); - array->add(j); - } - } - } - else { - // swap - int64_t tmp = low; - low = high; - high = tmp; - - // backward iteration - if (low < 0) { - low = 0; - } - if (high < 0) { - high = 0; - } - else if (high >= static_cast(length)) { - high = static_cast(length) - 1; - } - - array->reserve(static_cast(high - low + 1)); - for (int64_t position = high; position >= low; --position) { - auto j = result.extractArrayMember(trx, myCollection, position, true); - array->add(j); - } - } - result.destroy(); - - return AqlValue(array.release()); + return range; } catch (...) { + indexResult.destroy(); result.destroy(); throw; } diff --git a/arangod/Aql/Expression.h b/arangod/Aql/Expression.h index 003fcfb743..83a5a8ac89 100644 --- a/arangod/Aql/Expression.h +++ b/arangod/Aql/Expression.h @@ -291,11 +291,20 @@ namespace triagens { void invalidate (); // ----------------------------------------------------------------------------- -// --SECTION-- private functions +// --SECTION-- private methods // ----------------------------------------------------------------------------- private: +//////////////////////////////////////////////////////////////////////////////// +/// @brief extracts a range from an array +//////////////////////////////////////////////////////////////////////////////// + + AqlValue extractRange (AqlValue const&, + AqlValue const&, + TRI_document_collection_t const*, + triagens::arango::AqlTransaction*) const; + //////////////////////////////////////////////////////////////////////////////// /// @brief find a value in an array //////////////////////////////////////////////////////////////////////////////// diff --git a/js/server/modules/org/arangodb/aql.js b/js/server/modules/org/arangodb/aql.js index f58980b550..aeb044be5b 100644 --- a/js/server/modules/org/arangodb/aql.js +++ b/js/server/modules/org/arangodb/aql.js @@ -994,7 +994,7 @@ function GET_RANGE (value, low, high) { 'use strict'; if (TYPEWEIGHT(value) !== TYPEWEIGHT_ARRAY) { - return [ ]; + return null; } low = parseInt(low, 10); @@ -1039,8 +1039,10 @@ function GET_RANGE (value, low, high) { else if (high >= value.length) { high = value.length - 1; } - for (position = high; position >= low; --position) { - result.push(value[position]); + if (high >= low) { + for (position = high; position >= low; --position) { + result.push(value[position]); + } } } diff --git a/js/server/tests/aql-array-access.js b/js/server/tests/aql-array-access.js index ee22ad624e..faef4f5fd3 100644 --- a/js/server/tests/aql-array-access.js +++ b/js/server/tests/aql-array-access.js @@ -131,6 +131,24 @@ function arrayAccessTestSuite () { assertEqual([ null ], result); }, +//////////////////////////////////////////////////////////////////////////////// +/// @brief test non-array access +//////////////////////////////////////////////////////////////////////////////// + + testV8NonArrayRange1 : function () { + var result = AQL_EXECUTE("RETURN NOOPT(V8({ foo: 'bar' }))[0..1]").json; + assertEqual([ null ], result); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test non-array access +//////////////////////////////////////////////////////////////////////////////// + + testV8NonArrayRange2 : function () { + var result = AQL_EXECUTE("RETURN NOOPT(V8({ foo: 'bar' }))[-2..-1]").json; + assertEqual([ null ], result); + }, + //////////////////////////////////////////////////////////////////////////////// /// @brief test subquery result //////////////////////////////////////////////////////////////////////////////// @@ -143,7 +161,7 @@ function arrayAccessTestSuite () { }, //////////////////////////////////////////////////////////////////////////////// -/// @brief test subquery result +/// @brief test range result //////////////////////////////////////////////////////////////////////////////// testSubqueryResultRangeForward1 : function () { @@ -158,7 +176,7 @@ function arrayAccessTestSuite () { }, //////////////////////////////////////////////////////////////////////////////// -/// @brief test subquery result +/// @brief test range result //////////////////////////////////////////////////////////////////////////////// testSubqueryResultRangeForward2 : function () { @@ -170,6 +188,96 @@ function arrayAccessTestSuite () { } assertEqual([ expected ], result); } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test range result +//////////////////////////////////////////////////////////////////////////////// + + testSubqueryResultRangeBackward1 : function () { + for (var to = 0; to < 10; ++to) { + var result = AQL_EXECUTE("RETURN (FOR value IN @values RETURN value)[10.." + to + "]", { values: values }).json; + var expected = [ ]; + for (var i = Math.min(10, values.length - 1); i >= to; --i) { + expected.push(values[i]); + } + assertEqual([ expected ], result); + } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test range result +//////////////////////////////////////////////////////////////////////////////// + + testSubqueryResultRangeBackward2 : function () { + for (var from = 0; from < 10; ++from) { + var result = AQL_EXECUTE("RETURN (FOR value IN @values RETURN value)[" + from + "..0]", { values: values }).json; + var expected = [ ]; + for (var i = Math.min(from, values.length - 1); i >= 0; --i) { + expected.push(values[i]); + } + assertEqual([ expected ], result); + } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test range result +//////////////////////////////////////////////////////////////////////////////// + + testV8SubqueryResultRangeForward1 : function () { + for (var to = 0; to < 10; ++to) { + var result = AQL_EXECUTE("RETURN NOOPT(V8(FOR value IN @values RETURN value))[0.." + to + "]", { values: values }).json; + var expected = [ ]; + for (var i = 0; i < Math.min(to + 1, values.length); ++i) { + expected.push(values[i]); + } + assertEqual([ expected ], result); + } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test range result +//////////////////////////////////////////////////////////////////////////////// + + testV8SubqueryResultRangeForward2 : function () { + for (var from = 0; from < 10; ++from) { + var result = AQL_EXECUTE("RETURN NOOPT(V8(FOR value IN @values RETURN value))[" + from + "..99]", { values: values }).json; + var expected = [ ]; + for (var i = Math.min(from, values.length); i < values.length; ++i) { + expected.push(values[i]); + } + assertEqual([ expected ], result); + } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test range result +//////////////////////////////////////////////////////////////////////////////// + + testV8SubqueryResultRangeBackward1 : function () { + for (var to = 0; to < 10; ++to) { + var result = AQL_EXECUTE("RETURN NOOPT(V8(FOR value IN @values RETURN value))[10.." + to + "]", { values: values }).json; + var expected = [ ]; + for (var i = Math.min(10, values.length - 1); i >= to; --i) { + expected.push(values[i]); + } + assertEqual([ expected ], result); + } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test range result +//////////////////////////////////////////////////////////////////////////////// + + testV8SubqueryResultRangeBackward2 : function () { + for (var from = 0; from < 10; ++from) { + var result = AQL_EXECUTE("RETURN NOOPT(V8(FOR value IN @values RETURN value))[" + from + "..0]", { values: values }).json; + var expected = [ ]; + for (var i = Math.min(from, values.length - 1); i >= 0; --i) { + expected.push(values[i]); + } + assertEqual([ expected ], result); + } } };