You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by fd...@apache.org on 2012/02/22 07:39:17 UTC

git commit: Fix fold reduce with non-inclusive end key

Updated Branches:
  refs/heads/master fd0ca45d2 -> 1e6a1b526


Fix fold reduce with non-inclusive end key

Fold reducing a btree with with end_key_gt was not producing
the correct values. For example, for view queries with startkey
and starkey_docid and/or endkey and endkey_docid and inclusive_end
set to false, the doc ID component of the view keys was not
respected.
Example query:

http://server:5984/db/_design/test/_view/myview?startkey=4&endkey=6&endkey_docid=5&inclusive_end=false

Closes COUCHDB-1413


Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/1e6a1b52
Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/1e6a1b52
Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/1e6a1b52

Branch: refs/heads/master
Commit: 1e6a1b526dbf80e407ca48ecb5f62a05cac4a740
Parents: fd0ca45
Author: Filipe David Borba Manana <fd...@apache.org>
Authored: Sat Feb 18 06:07:27 2012 +0000
Committer: Filipe David Borba Manana <fd...@apache.org>
Committed: Tue Feb 21 22:35:13 2012 -0800

----------------------------------------------------------------------
 share/www/script/test/reduce.js  |  229 +++++++++++++++++++++++++++++++++
 src/couchdb/couch_btree.erl      |   93 +++++++-------
 test/etap/021-btree-reductions.t |   98 ++++++++++++++-
 3 files changed, 371 insertions(+), 49 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb/blob/1e6a1b52/share/www/script/test/reduce.js
----------------------------------------------------------------------
diff --git a/share/www/script/test/reduce.js b/share/www/script/test/reduce.js
index 16c7a7b..36e5cb7 100644
--- a/share/www/script/test/reduce.js
+++ b/share/www/script/test/reduce.js
@@ -182,4 +182,233 @@ couchTests.reduce = function(debug) {
   // account for floating point rounding error
   T(Math.abs(difference) < 0.0000000001);
 
+  function testReducePagination() {
+    var ddoc = {
+      "_id": "_design/test",
+      "language": "javascript",
+      "views": {
+        "view1": {
+          "map": "function(doc) {" +
+             "emit(doc.int, doc._id);" +
+             "emit(doc.int + 1, doc._id);" +
+             "emit(doc.int + 2, doc._id);" +
+          "}",
+          "reduce": "_count"
+        }
+      }
+    };
+    var result, docs = [];
+
+    function randVal() {
+        return Math.random() * 100000000;
+    }
+
+    db.deleteDb();
+    db.createDb();
+
+    for (var i = 0; i < 1123; i++) {
+      docs.push({"_id": String(i), "int": i});
+    }
+    db.bulkSave(docs.concat([ddoc]));
+
+    // ?group=false tests
+    result = db.view('test/view1', {startkey: 400, endkey: 402, foobar: randVal()});
+    TEquals(9, result.rows[0].value);
+    result = db.view('test/view1', {startkey: 402, endkey: 400, descending: true,
+      foobar: randVal()});
+    TEquals(9, result.rows[0].value);
+
+    result = db.view('test/view1', {startkey: 400, endkey: 402, inclusive_end: false,
+      foobar: randVal()});
+    TEquals(6, result.rows[0].value);
+    result = db.view('test/view1', {startkey: 402, endkey: 400, inclusive_end: false,
+      descending: true, foobar: randVal()});
+    TEquals(6, result.rows[0].value);
+
+    result = db.view('test/view1', {startkey: 400, endkey: 402, endkey_docid: "400",
+      foobar: randVal()});
+    TEquals(7, result.rows[0].value);
+    result = db.view('test/view1', {startkey: 400, endkey: 402, endkey_docid: "400",
+      inclusive_end: false, foobar: randVal()});
+    TEquals(6, result.rows[0].value);
+
+    result = db.view('test/view1', {startkey: 400, endkey: 402, endkey_docid: "401",
+      foobar: randVal()});
+    TEquals(8, result.rows[0].value);
+    result = db.view('test/view1', {startkey: 400, endkey: 402, endkey_docid: "401",
+      inclusive_end: false, foobar: randVal()});
+    TEquals(7, result.rows[0].value);
+
+    result = db.view('test/view1', {startkey: 400, endkey: 402, endkey_docid: "402",
+      foobar: randVal()});
+    TEquals(9, result.rows[0].value);
+    result = db.view('test/view1', {startkey: 400, endkey: 402, endkey_docid: "402",
+      inclusive_end: false, foobar: randVal()});
+    TEquals(8, result.rows[0].value);
+
+    result = db.view('test/view1', {startkey: 402, endkey: 400, endkey_docid: "398",
+      descending: true, foobar: randVal()});
+    TEquals(9, result.rows[0].value);
+    result = db.view('test/view1', {startkey: 402, endkey: 400, endkey_docid: "398",
+      descending: true, inclusive_end: false, foobar: randVal()}),
+    TEquals(8, result.rows[0].value);
+
+    result = db.view('test/view1', {startkey: 402, endkey: 400, endkey_docid: "399",
+      descending: true, foobar: randVal()});
+    TEquals(8, result.rows[0].value);
+    result = db.view('test/view1', {startkey: 402, endkey: 400, endkey_docid: "399",
+      descending: true, inclusive_end: false, foobar: randVal()}),
+    TEquals(7, result.rows[0].value);
+
+    result = db.view('test/view1', {startkey: 402, endkey: 400, endkey_docid: "400",
+      descending: true, foobar: randVal()}),
+    TEquals(7, result.rows[0].value);
+    result = db.view('test/view1', {startkey: 402, endkey: 400, endkey_docid: "400",
+      descending: true, inclusive_end: false, foobar: randVal()}),
+    TEquals(6, result.rows[0].value);
+
+    result = db.view('test/view1', {startkey: 402, startkey_docid: "400", endkey: 400,
+      descending: true, foobar: randVal()});
+    TEquals(7, result.rows[0].value);
+
+    result = db.view('test/view1', {startkey: 402, startkey_docid: "401", endkey: 400,
+      descending: true, inclusive_end: false, foobar: randVal()});
+    TEquals(5, result.rows[0].value);
+
+    // ?group=true tests
+    result = db.view('test/view1', {group: true, startkey: 400, endkey: 402,
+      foobar: randVal()});
+    TEquals(3, result.rows.length);
+    TEquals(400, result.rows[0].key);
+    TEquals(3, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+    TEquals(402, result.rows[2].key);
+    TEquals(3, result.rows[2].value);
+
+    result = db.view('test/view1', {group: true, startkey: 402, endkey: 400,
+      descending: true, foobar: randVal()});
+    TEquals(3, result.rows.length);
+    TEquals(402, result.rows[0].key);
+    TEquals(3, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+    TEquals(400, result.rows[2].key);
+    TEquals(3, result.rows[2].value);
+
+    result = db.view('test/view1', {group: true, startkey: 400, endkey: 402,
+      inclusive_end: false, foobar: randVal()});
+    TEquals(2, result.rows.length);
+    TEquals(400, result.rows[0].key);
+    TEquals(3, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+
+    result = db.view('test/view1', {group: true, startkey: 402, endkey: 400,
+      descending: true, inclusive_end: false, foobar: randVal()});
+    TEquals(2, result.rows.length);
+    TEquals(402, result.rows[0].key);
+    TEquals(3, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+
+    result = db.view('test/view1', {group: true, startkey: 400, endkey: 402,
+      endkey_docid: "401", foobar: randVal()});
+    TEquals(3, result.rows.length);
+    TEquals(400, result.rows[0].key);
+    TEquals(3, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+    TEquals(402, result.rows[2].key);
+    TEquals(2, result.rows[2].value);
+
+    result = db.view('test/view1', {group: true, startkey: 400, endkey: 402,
+      endkey_docid: "400", foobar: randVal()});
+    TEquals(3, result.rows.length);
+    TEquals(400, result.rows[0].key);
+    TEquals(3, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+    TEquals(402, result.rows[2].key);
+    TEquals(1, result.rows[2].value);
+
+    result = db.view('test/view1', {group: true, startkey: 402, startkey_docid: "401",
+      endkey: 400, descending: true, foobar: randVal()});
+    TEquals(3, result.rows.length);
+    TEquals(402, result.rows[0].key);
+    TEquals(2, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+    TEquals(400, result.rows[2].key);
+    TEquals(3, result.rows[2].value);
+
+    result = db.view('test/view1', {group: true, startkey: 402, startkey_docid: "400",
+      endkey: 400, descending: true, foobar: randVal()});
+    TEquals(3, result.rows.length);
+    TEquals(402, result.rows[0].key);
+    TEquals(1, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+    TEquals(400, result.rows[2].key);
+    TEquals(3, result.rows[2].value);
+
+    result = db.view('test/view1', {group: true, startkey: 402, startkey_docid: "401",
+      endkey: 400, descending: true, inclusive_end: false, foobar: randVal()});
+    TEquals(2, result.rows.length);
+    TEquals(402, result.rows[0].key);
+    TEquals(2, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+
+    result = db.view('test/view1', {group: true, startkey: 402, startkey_docid: "400",
+      endkey: 400, descending: true, inclusive_end: false, foobar: randVal()});
+    TEquals(2, result.rows.length);
+    TEquals(402, result.rows[0].key);
+    TEquals(1, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+
+    result = db.view('test/view1', {group: true, startkey: 402, endkey: 400,
+      endkey_docid: "398", descending: true, inclusive_end: true, foobar: randVal()});
+    TEquals(3, result.rows.length);
+    TEquals(402, result.rows[0].key);
+    TEquals(3, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+    TEquals(400, result.rows[2].key);
+    TEquals(3, result.rows[2].value);
+
+    result = db.view('test/view1', {group: true, startkey: 402, endkey: 400,
+      endkey_docid: "399", descending: true, inclusive_end: true, foobar: randVal()});
+    TEquals(3, result.rows.length);
+    TEquals(402, result.rows[0].key);
+    TEquals(3, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+    TEquals(400, result.rows[2].key);
+    TEquals(2, result.rows[2].value);
+
+    result = db.view('test/view1', {group: true, startkey: 402, endkey: 400,
+      endkey_docid: "399", descending: true, inclusive_end: false, foobar: randVal()});
+    TEquals(3, result.rows.length);
+    TEquals(402, result.rows[0].key);
+    TEquals(3, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+    TEquals(400, result.rows[2].key);
+    TEquals(1, result.rows[2].value);
+
+    result = db.view('test/view1', {group: true, startkey: 402, endkey: 400,
+      endkey_docid: "400", descending: true, inclusive_end: false, foobar: randVal()});
+    TEquals(2, result.rows.length);
+    TEquals(402, result.rows[0].key);
+    TEquals(3, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+
+    db.deleteDb();
+  }
+
+  testReducePagination();
+
 };

http://git-wip-us.apache.org/repos/asf/couchdb/blob/1e6a1b52/src/couchdb/couch_btree.erl
----------------------------------------------------------------------
diff --git a/src/couchdb/couch_btree.erl b/src/couchdb/couch_btree.erl
index ee42d78..789819e 100644
--- a/src/couchdb/couch_btree.erl
+++ b/src/couchdb/couch_btree.erl
@@ -67,19 +67,11 @@ final_reduce(Reduce, {KVs, Reductions}) ->
 fold_reduce(#btree{root=Root}=Bt, Fun, Acc, Options) ->
     Dir = couch_util:get_value(dir, Options, fwd),
     StartKey = couch_util:get_value(start_key, Options),
-    EndKey = case couch_util:get_value(end_key_gt, Options) of
-        undefined -> couch_util:get_value(end_key, Options);
-        LastKey -> LastKey
-    end,
+    InEndRangeFun = make_key_in_end_range_function(Bt, Dir, Options),
     KeyGroupFun = couch_util:get_value(key_group_fun, Options, fun(_,_) -> true end),
-    {StartKey2, EndKey2} =
-    case Dir of
-        rev -> {EndKey, StartKey};
-        fwd -> {StartKey, EndKey}
-    end,
     try
         {ok, Acc2, GroupedRedsAcc2, GroupedKVsAcc2, GroupedKey2} =
-            reduce_stream_node(Bt, Dir, Root, StartKey2, EndKey2, undefined, [], [],
+            reduce_stream_node(Bt, Dir, Root, StartKey, InEndRangeFun, undefined, [], [],
             KeyGroupFun, Fun, Acc),
         if GroupedKey2 == undefined ->
             {ok, Acc2};
@@ -480,22 +472,24 @@ modify_kvnode(Bt, NodeTuple, LowerBound, [{ActionType, ActionKey, ActionValue} |
     end.
 
 
-reduce_stream_node(_Bt, _Dir, nil, _KeyStart, _KeyEnd, GroupedKey, GroupedKVsAcc,
+reduce_stream_node(_Bt, _Dir, nil, _KeyStart, _InEndRangeFun, GroupedKey, GroupedKVsAcc,
         GroupedRedsAcc, _KeyGroupFun, _Fun, Acc) ->
     {ok, Acc, GroupedRedsAcc, GroupedKVsAcc, GroupedKey};
-reduce_stream_node(Bt, Dir, Node, KeyStart, KeyEnd, GroupedKey, GroupedKVsAcc,
+reduce_stream_node(Bt, Dir, Node, KeyStart, InEndRangeFun, GroupedKey, GroupedKVsAcc,
         GroupedRedsAcc, KeyGroupFun, Fun, Acc) ->
     P = element(1, Node),
     case get_node(Bt, P) of
     {kp_node, NodeList} ->
-        reduce_stream_kp_node(Bt, Dir, NodeList, KeyStart, KeyEnd, GroupedKey,
+        NodeList2 = adjust_dir(Dir, NodeList),
+        reduce_stream_kp_node(Bt, Dir, NodeList2, KeyStart, InEndRangeFun, GroupedKey,
                 GroupedKVsAcc, GroupedRedsAcc, KeyGroupFun, Fun, Acc);
     {kv_node, KVs} ->
-        reduce_stream_kv_node(Bt, Dir, KVs, KeyStart, KeyEnd, GroupedKey,
+        KVs2 = adjust_dir(Dir, KVs),
+        reduce_stream_kv_node(Bt, Dir, KVs2, KeyStart, InEndRangeFun, GroupedKey,
                 GroupedKVsAcc, GroupedRedsAcc, KeyGroupFun, Fun, Acc)
     end.
 
-reduce_stream_kv_node(Bt, Dir, KVs, KeyStart, KeyEnd,
+reduce_stream_kv_node(Bt, Dir, KVs, KeyStart, InEndRangeFun,
                         GroupedKey, GroupedKVsAcc, GroupedRedsAcc,
                         KeyGroupFun, Fun, Acc) ->
 
@@ -504,19 +498,17 @@ reduce_stream_kv_node(Bt, Dir, KVs, KeyStart, KeyEnd,
     undefined ->
         KVs;
     _ ->
-        lists:dropwhile(fun({Key,_}) -> less(Bt, Key, KeyStart) end, KVs)
-    end,
-    KVs2 =
-    case KeyEnd of
-    undefined ->
-        GTEKeyStartKVs;
-    _ ->
-        lists:takewhile(
-            fun({Key,_}) ->
-                not less(Bt, KeyEnd, Key)
-            end, GTEKeyStartKVs)
+        DropFun = case Dir of
+        fwd ->
+            fun({Key, _}) -> less(Bt, Key, KeyStart) end;
+        rev ->
+            fun({Key, _}) -> less(Bt, KeyStart, Key) end
+        end,
+        lists:dropwhile(DropFun, KVs)
     end,
-    reduce_stream_kv_node2(Bt, adjust_dir(Dir, KVs2), GroupedKey, GroupedKVsAcc, GroupedRedsAcc,
+    KVs2 = lists:takewhile(
+        fun({Key, _}) -> InEndRangeFun(Key) end, GTEKeyStartKVs),
+    reduce_stream_kv_node2(Bt, KVs2, GroupedKey, GroupedKVsAcc, GroupedRedsAcc,
                         KeyGroupFun, Fun, Acc).
 
 
@@ -547,7 +539,7 @@ reduce_stream_kv_node2(Bt, [{Key, Value}| RestKVs], GroupedKey, GroupedKVsAcc,
         end
     end.
 
-reduce_stream_kp_node(Bt, Dir, NodeList, KeyStart, KeyEnd,
+reduce_stream_kp_node(Bt, Dir, NodeList, KeyStart, InEndRangeFun,
                         GroupedKey, GroupedKVsAcc, GroupedRedsAcc,
                         KeyGroupFun, Fun, Acc) ->
     Nodes =
@@ -555,34 +547,39 @@ reduce_stream_kp_node(Bt, Dir, NodeList, KeyStart, KeyEnd,
     undefined ->
         NodeList;
     _ ->
-        lists:dropwhile(
-            fun({Key,_}) ->
-                less(Bt, Key, KeyStart)
-            end, NodeList)
+        case Dir of
+        fwd ->
+            lists:dropwhile(fun({Key, _}) -> less(Bt, Key, KeyStart) end, NodeList);
+        rev ->
+            RevKPs = lists:reverse(NodeList),
+            case lists:splitwith(fun({Key, _}) -> less(Bt, Key, KeyStart) end, RevKPs) of
+            {_Before, []} ->
+                NodeList;
+            {Before, [FirstAfter | _]} ->
+                [FirstAfter | lists:reverse(Before)]
+            end
+        end
     end,
-    NodesInRange =
-    case KeyEnd of
-    undefined ->
-        Nodes;
+    {InRange, MaybeInRange} = lists:splitwith(
+        fun({Key, _}) -> InEndRangeFun(Key) end, Nodes),
+    NodesInRange = case MaybeInRange of
+    [FirstMaybeInRange | _] when Dir =:= fwd ->
+        InRange ++ [FirstMaybeInRange];
     _ ->
-        {InRange, MaybeInRange} = lists:splitwith(
-            fun({Key,_}) ->
-                less(Bt, Key, KeyEnd)
-            end, Nodes),
-        InRange ++ case MaybeInRange of [] -> []; [FirstMaybe|_] -> [FirstMaybe] end
+        InRange
     end,
-    reduce_stream_kp_node2(Bt, Dir, adjust_dir(Dir, NodesInRange), KeyStart, KeyEnd,
+    reduce_stream_kp_node2(Bt, Dir, NodesInRange, KeyStart, InEndRangeFun,
         GroupedKey, GroupedKVsAcc, GroupedRedsAcc, KeyGroupFun, Fun, Acc).
 
 
-reduce_stream_kp_node2(Bt, Dir, [{_Key, NodeInfo} | RestNodeList], KeyStart, KeyEnd,
+reduce_stream_kp_node2(Bt, Dir, [{_Key, NodeInfo} | RestNodeList], KeyStart, InEndRangeFun,
                         undefined, [], [], KeyGroupFun, Fun, Acc) ->
     {ok, Acc2, GroupedRedsAcc2, GroupedKVsAcc2, GroupedKey2} =
-            reduce_stream_node(Bt, Dir, NodeInfo, KeyStart, KeyEnd, undefined,
+            reduce_stream_node(Bt, Dir, NodeInfo, KeyStart, InEndRangeFun, undefined,
                 [], [], KeyGroupFun, Fun, Acc),
-    reduce_stream_kp_node2(Bt, Dir, RestNodeList, KeyStart, KeyEnd, GroupedKey2,
+    reduce_stream_kp_node2(Bt, Dir, RestNodeList, KeyStart, InEndRangeFun, GroupedKey2,
             GroupedKVsAcc2, GroupedRedsAcc2, KeyGroupFun, Fun, Acc2);
-reduce_stream_kp_node2(Bt, Dir, NodeList, KeyStart, KeyEnd,
+reduce_stream_kp_node2(Bt, Dir, NodeList, KeyStart, InEndRangeFun,
         GroupedKey, GroupedKVsAcc, GroupedRedsAcc, KeyGroupFun, Fun, Acc) ->
     {Grouped0, Ungrouped0} = lists:splitwith(fun({Key,_}) ->
         KeyGroupFun(GroupedKey, Key) end, NodeList),
@@ -598,9 +595,9 @@ reduce_stream_kp_node2(Bt, Dir, NodeList, KeyStart, KeyEnd,
     case UngroupedNodes of
     [{_Key, NodeInfo}|RestNodes] ->
         {ok, Acc2, GroupedRedsAcc2, GroupedKVsAcc2, GroupedKey2} =
-            reduce_stream_node(Bt, Dir, NodeInfo, KeyStart, KeyEnd, GroupedKey,
+            reduce_stream_node(Bt, Dir, NodeInfo, KeyStart, InEndRangeFun, GroupedKey,
                 GroupedKVsAcc, GroupedReds ++ GroupedRedsAcc, KeyGroupFun, Fun, Acc),
-        reduce_stream_kp_node2(Bt, Dir, RestNodes, KeyStart, KeyEnd, GroupedKey2,
+        reduce_stream_kp_node2(Bt, Dir, RestNodes, KeyStart, InEndRangeFun, GroupedKey2,
                 GroupedKVsAcc2, GroupedRedsAcc2, KeyGroupFun, Fun, Acc2);
     [] ->
         {ok, Acc, GroupedReds ++ GroupedRedsAcc, GroupedKVsAcc, GroupedKey}

http://git-wip-us.apache.org/repos/asf/couchdb/blob/1e6a1b52/test/etap/021-btree-reductions.t
----------------------------------------------------------------------
diff --git a/test/etap/021-btree-reductions.t b/test/etap/021-btree-reductions.t
index 331e49a..e80ac2d 100755
--- a/test/etap/021-btree-reductions.t
+++ b/test/etap/021-btree-reductions.t
@@ -19,7 +19,7 @@ rows() -> 1000.
 
 main(_) ->
     test_util:init_code_path(),
-    etap:plan(8),
+    etap:plan(20),
     case (catch test()) of
         ok ->
             etap:end_tests();
@@ -138,4 +138,100 @@ test()->
         "Reducing in reverse results in reversed accumulator."
     ),
 
+    etap:is(
+        couch_btree:fold_reduce(Btree2, FoldFun, [], [
+            {dir, fwd}, {key_group_fun, GroupFun},
+            {start_key, {"even", 0}}, {end_key, {"odd", rows() + 1}}
+        ]),
+        {ok, [{{"odd", 1}, 500}, {{"even", 2}, 500}]},
+        "Right fold reduce value for whole range with inclusive end key"),
+
+    etap:is(
+        couch_btree:fold_reduce(Btree2, FoldFun, [], [
+            {dir, fwd}, {key_group_fun, GroupFun},
+            {start_key, {"even", 0}}, {end_key_gt, {"odd", 999}}
+        ]),
+        {ok, [{{"odd", 1}, 499}, {{"even", 2}, 500}]},
+        "Right fold reduce value for whole range without inclusive end key"),
+
+    etap:is(
+        couch_btree:fold_reduce(Btree2, FoldFun, [], [
+            {dir, rev}, {key_group_fun, GroupFun},
+            {start_key, {"odd", 999}}, {end_key, {"even", 2}}
+        ]),
+        {ok, [{{"even", 1000}, 500}, {{"odd", 999}, 500}]},
+        "Right fold reduce value for whole reversed range with inclusive end key"),
+
+    etap:is(
+        couch_btree:fold_reduce(Btree2, FoldFun, [], [
+            {dir, rev}, {key_group_fun, GroupFun},
+            {start_key, {"odd", 999}}, {end_key_gt, {"even", 2}}
+        ]),
+        {ok, [{{"even", 1000}, 499}, {{"odd", 999}, 500}]},
+        "Right fold reduce value for whole reversed range without inclusive end key"),
+
+    etap:is(
+        couch_btree:fold_reduce(Btree2, FoldFun, [], [
+            {dir, fwd}, {key_group_fun, GroupFun},
+            {start_key, {"even", 0}}, {end_key, {"odd", 499}}
+        ]),
+        {ok, [{{"odd", 1}, 250}, {{"even", 2}, 500}]},
+        "Right fold reduce value for first half with inclusive end key"),
+
+    etap:is(
+        couch_btree:fold_reduce(Btree2, FoldFun, [], [
+            {dir, fwd}, {key_group_fun, GroupFun},
+            {start_key, {"even", 0}}, {end_key_gt, {"odd", 499}}
+        ]),
+        {ok, [{{"odd", 1}, 249}, {{"even", 2}, 500}]},
+        "Right fold reduce value for first half without inclusive end key"),
+
+    etap:is(
+        couch_btree:fold_reduce(Btree2, FoldFun, [], [
+            {dir, rev}, {key_group_fun, GroupFun},
+            {start_key, {"odd", 999}}, {end_key, {"even", 500}}
+        ]),
+        {ok, [{{"even", 1000}, 251}, {{"odd", 999}, 500}]},
+        "Right fold reduce value for first half reversed with inclusive end key"),
+
+    etap:is(
+        couch_btree:fold_reduce(Btree2, FoldFun, [], [
+            {dir, rev}, {key_group_fun, GroupFun},
+            {start_key, {"odd", 999}}, {end_key_gt, {"even", 500}}
+        ]),
+        {ok, [{{"even", 1000}, 250}, {{"odd", 999}, 500}]},
+        "Right fold reduce value for first half reversed without inclusive end key"),
+
+    etap:is(
+        couch_btree:fold_reduce(Btree2, FoldFun, [], [
+            {dir, fwd}, {key_group_fun, GroupFun},
+            {start_key, {"even", 500}}, {end_key, {"odd", 999}}
+        ]),
+        {ok, [{{"odd", 1}, 500}, {{"even", 500}, 251}]},
+        "Right fold reduce value for second half with inclusive end key"),
+
+    etap:is(
+        couch_btree:fold_reduce(Btree2, FoldFun, [], [
+            {dir, fwd}, {key_group_fun, GroupFun},
+            {start_key, {"even", 500}}, {end_key_gt, {"odd", 999}}
+        ]),
+        {ok, [{{"odd", 1}, 499}, {{"even", 500}, 251}]},
+        "Right fold reduce value for second half without inclusive end key"),
+
+    etap:is(
+        couch_btree:fold_reduce(Btree2, FoldFun, [], [
+            {dir, rev}, {key_group_fun, GroupFun},
+            {start_key, {"odd", 501}}, {end_key, {"even", 2}}
+        ]),
+        {ok, [{{"even", 1000}, 500}, {{"odd", 501}, 251}]},
+        "Right fold reduce value for second half reversed with inclusive end key"),
+
+    etap:is(
+        couch_btree:fold_reduce(Btree2, FoldFun, [], [
+            {dir, rev}, {key_group_fun, GroupFun},
+            {start_key, {"odd", 501}}, {end_key_gt, {"even", 2}}
+        ]),
+        {ok, [{{"even", 1000}, 499}, {{"odd", 501}, 251}]},
+        "Right fold reduce value for second half reversed without inclusive end key"),
+
     couch_file:close(Fd).