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).