blob: e3ff0eb21d4884bec0df72e9d46ae065dceaf88c [file] [log] [blame]
% Licensed under the Apache License, Version 2.0 (the "License"); you may not
% use this file except in compliance with the License. You may obtain a copy of
% the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
% License for the specific language governing permissions and limitations under
% the License.
-module(fabric2_local_doc_fold_tests).
-include_lib("couch/include/couch_db.hrl").
-include_lib("couch/include/couch_eunit.hrl").
-include_lib("eunit/include/eunit.hrl").
-include("fabric2_test.hrl").
-define(DOC_COUNT, 50).
doc_fold_test_() ->
{
"Test local document fold operations",
{
setup,
fun setup/0,
fun cleanup/1,
with([
?TDEF(fold_local_docs_basic),
?TDEF(fold_local_docs_rev),
?TDEF(fold_local_docs_with_start_key),
?TDEF(fold_local_docs_with_end_key),
?TDEF(fold_local_docs_with_both_keys_the_same),
?TDEF(fold_local_docs_with_different_keys, 15000),
?TDEF(fold_local_docs_with_limit),
?TDEF(fold_local_docs_with_skip),
?TDEF(fold_local_docs_with_skip_and_limit)
])
}
}.
setup() ->
Ctx = test_util:start_couch([fabric]),
{ok, Db} = fabric2_db:create(?tempdb(), [{user_ctx, ?ADMIN_USER}]),
DocIdRevs = lists:map(fun(Val) ->
UUID = fabric2_util:uuid(),
DocId = <<?LOCAL_DOC_PREFIX, UUID/binary>>,
% Every 10th doc is large to force the doc to be chunkified
BigChunk = << <<"x">> || _ <- lists:seq(1, 200000) >>,
Body = case Val rem 10 == 0 of
true -> {[{<<"value">>, BigChunk}]};
false -> {[{<<"value">>, Val}]}
end,
Doc = #doc{
id = DocId,
body = Body
},
{ok, Rev} = fabric2_db:update_doc(Db, Doc, []),
{DocId, {[{rev, couch_doc:rev_to_str(Rev)}]}}
end, lists:seq(1, ?DOC_COUNT)),
{Db, lists:sort(DocIdRevs), Ctx}.
cleanup({Db, _DocIdRevs, Ctx}) ->
ok = fabric2_db:delete(fabric2_db:name(Db), []),
test_util:stop_couch(Ctx).
fold_local_docs_basic({Db, DocIdRevs, _}) ->
{ok, {?DOC_COUNT, Rows}} = fabric2_db:fold_local_docs(Db, fun fold_fun/2, [], []),
?assertEqual(DocIdRevs, lists:reverse(Rows)).
fold_local_docs_rev({Db, DocIdRevs, _}) ->
Opts = [{dir, rev}],
{ok, {?DOC_COUNT, Rows}} =
fabric2_db:fold_local_docs(Db, fun fold_fun/2, [], Opts),
?assertEqual(DocIdRevs, Rows).
fold_local_docs_with_start_key({Db, DocIdRevs, _}) ->
{StartKey, _} = hd(DocIdRevs),
Opts = [{start_key, StartKey}],
{ok, {?DOC_COUNT, Rows}}
= fabric2_db:fold_local_docs(Db, fun fold_fun/2, [], Opts),
?assertEqual(DocIdRevs, lists:reverse(Rows)),
if length(DocIdRevs) == 1 -> ok; true ->
fold_local_docs_with_start_key({Db, tl(DocIdRevs), nil})
end.
fold_local_docs_with_end_key({Db, DocIdRevs, _}) ->
RevDocIdRevs = lists:reverse(DocIdRevs),
{EndKey, _} = hd(RevDocIdRevs),
Opts = [{end_key, EndKey}],
{ok, {?DOC_COUNT, Rows}} =
fabric2_db:fold_local_docs(Db, fun fold_fun/2, [], Opts),
?assertEqual(RevDocIdRevs, Rows),
if length(DocIdRevs) == 1 -> ok; true ->
fold_local_docs_with_end_key({Db, lists:reverse(tl(RevDocIdRevs)), nil})
end.
fold_local_docs_with_both_keys_the_same({Db, DocIdRevs, _}) ->
lists:foreach(fun({DocId, _} = Row) ->
check_all_combos(Db, DocId, DocId, [Row])
end, DocIdRevs).
fold_local_docs_with_different_keys({Db, DocIdRevs, _}) ->
lists:foreach(fun(_) ->
{StartKey, EndKey, Rows} = pick_range(DocIdRevs),
check_all_combos(Db, StartKey, EndKey, Rows)
end, lists:seq(1, 100)).
fold_local_docs_with_limit({Db, DocIdRevs, _}) ->
lists:foreach(fun(Limit) ->
Opts1 = [{limit, Limit}],
{ok, {?DOC_COUNT, Rows1}} =
fabric2_db:fold_local_docs(Db, fun fold_fun/2, [], Opts1),
?assertEqual(lists:sublist(DocIdRevs, Limit), lists:reverse(Rows1)),
Opts2 = [{dir, rev} | Opts1],
{ok, {?DOC_COUNT, Rows2}} =
fabric2_db:fold_local_docs(Db, fun fold_fun/2, [], Opts2),
?assertEqual(
lists:sublist(lists:reverse(DocIdRevs), Limit),
lists:reverse(Rows2)
)
end, lists:seq(0, 51)).
fold_local_docs_with_skip({Db, DocIdRevs, _}) ->
lists:foreach(fun(Skip) ->
Opts1 = [{skip, Skip}],
{ok, {?DOC_COUNT, Rows1}} =
fabric2_db:fold_local_docs(Db, fun fold_fun/2, [], Opts1),
Expect1 = case Skip > length(DocIdRevs) of
true -> [];
false -> lists:nthtail(Skip, DocIdRevs)
end,
?assertEqual(Expect1, lists:reverse(Rows1)),
Opts2 = [{dir, rev} | Opts1],
{ok, {?DOC_COUNT, Rows2}} =
fabric2_db:fold_local_docs(Db, fun fold_fun/2, [], Opts2),
Expect2 = case Skip > length(DocIdRevs) of
true -> [];
false -> lists:nthtail(Skip, lists:reverse(DocIdRevs))
end,
?assertEqual(Expect2, lists:reverse(Rows2))
end, lists:seq(0, 51)).
fold_local_docs_with_skip_and_limit({Db, DocIdRevs, _}) ->
lists:foreach(fun(_) ->
check_skip_and_limit(Db, [], DocIdRevs),
check_skip_and_limit(Db, [{dir, rev}], lists:reverse(DocIdRevs))
end, lists:seq(1, 100)).
check_all_combos(Db, StartKey, EndKey, Rows) ->
Opts1 = make_opts(fwd, StartKey, EndKey, true),
{ok, {?DOC_COUNT, Rows1}} =
fabric2_db:fold_local_docs(Db, fun fold_fun/2, [], Opts1),
?assertEqual(lists:reverse(Rows), Rows1),
check_skip_and_limit(Db, Opts1, Rows),
Opts2 = make_opts(fwd, StartKey, EndKey, false),
{ok, {?DOC_COUNT, Rows2}} =
fabric2_db:fold_local_docs(Db, fun fold_fun/2, [], Opts2),
Expect2 = if EndKey == undefined -> lists:reverse(Rows); true ->
lists:reverse(all_but_last(Rows))
end,
?assertEqual(Expect2, Rows2),
check_skip_and_limit(Db, Opts2, lists:reverse(Expect2)),
Opts3 = make_opts(rev, StartKey, EndKey, true),
{ok, {?DOC_COUNT, Rows3}} =
fabric2_db:fold_local_docs(Db, fun fold_fun/2, [], Opts3),
?assertEqual(Rows, Rows3),
check_skip_and_limit(Db, Opts3, lists:reverse(Rows)),
Opts4 = make_opts(rev, StartKey, EndKey, false),
{ok, {?DOC_COUNT, Rows4}} =
fabric2_db:fold_local_docs(Db, fun fold_fun/2, [], Opts4),
Expect4 = if StartKey == undefined -> Rows; true ->
tl(Rows)
end,
?assertEqual(Expect4, Rows4),
check_skip_and_limit(Db, Opts4, lists:reverse(Expect4)).
check_skip_and_limit(Db, Opts, []) ->
Skip = rand:uniform(?DOC_COUNT + 1) - 1,
Limit = rand:uniform(?DOC_COUNT + 1) - 1,
NewOpts = [{skip, Skip}, {limit, Limit} | Opts],
{ok, {?DOC_COUNT, OutRows}} =
fabric2_db:fold_local_docs(Db, fun fold_fun/2, [], NewOpts),
?assertEqual([], OutRows);
check_skip_and_limit(Db, Opts, Rows) ->
Skip = rand:uniform(length(Rows) + 1) - 1,
Limit = rand:uniform(?DOC_COUNT + 1 - Skip) - 1,
ExpectRows = case Skip >= length(Rows) of
true ->
[];
false ->
lists:sublist(lists:nthtail(Skip, Rows), Limit)
end,
SkipLimitOpts = [{skip, Skip}, {limit, Limit} | Opts],
{ok, {?DOC_COUNT, RevRows}} =
fabric2_db:fold_local_docs(Db, fun fold_fun/2, [], SkipLimitOpts),
OutRows = lists:reverse(RevRows),
?assertEqual(ExpectRows, OutRows).
make_opts(fwd, StartKey, EndKey, InclusiveEnd) ->
DirOpts = case rand:uniform() =< 0.50 of
true -> [{dir, fwd}];
false -> []
end,
StartOpts = case StartKey of
undefined -> [];
<<_/binary>> -> [{start_key, StartKey}]
end,
EndOpts = case EndKey of
undefined -> [];
<<_/binary>> when InclusiveEnd -> [{end_key, EndKey}];
<<_/binary>> -> [{end_key_gt, EndKey}]
end,
DirOpts ++ StartOpts ++ EndOpts;
make_opts(rev, StartKey, EndKey, InclusiveEnd) ->
BaseOpts = make_opts(fwd, EndKey, StartKey, InclusiveEnd),
[{dir, rev}] ++ BaseOpts -- [{dir, fwd}].
all_but_last([]) ->
[];
all_but_last([_]) ->
[];
all_but_last(Rows) ->
lists:sublist(Rows, length(Rows) - 1).
pick_range(DocIdRevs) ->
{StartKey, StartRow, RestRows} = pick_start_key(DocIdRevs),
{EndKey, EndRow, RowsBetween} = pick_end_key(RestRows),
{StartKey, EndKey, StartRow ++ RowsBetween ++ EndRow}.
pick_start_key(Rows) ->
case rand:uniform() =< 0.1 of
true ->
{undefined, [], Rows};
false ->
Idx = rand:uniform(length(Rows)),
{DocId, _} = Row = lists:nth(Idx, Rows),
{DocId, [Row], lists:nthtail(Idx, Rows)}
end.
pick_end_key([]) ->
{undefined, [], []};
pick_end_key(Rows) ->
case rand:uniform() =< 0.1 of
true ->
{undefined, [], Rows};
false ->
Idx = rand:uniform(length(Rows)),
{DocId, _} = Row = lists:nth(Idx, Rows),
Tail = lists:nthtail(Idx, Rows),
{DocId, [Row], Rows -- [Row | Tail]}
end.
fold_fun({meta, Meta}, _Acc) ->
Total = fabric2_util:get_value(total, Meta),
{ok, {Total, []}};
fold_fun({row, Row}, {Total, Rows}) ->
RowId = fabric2_util:get_value(id, Row),
RowId = fabric2_util:get_value(key, Row),
RowRev = fabric2_util:get_value(value, Row),
{ok, {Total, [{RowId, RowRev} | Rows]}};
fold_fun(complete, Acc) ->
{ok, Acc}.