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 <[email protected]> Authored: Sat Feb 18 06:07:27 2012 +0000 Committer: Filipe David Borba Manana <[email protected]> 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).
