blob: c4a714d1ed96cbd11a46fa1f3c8f8480edbe5869 [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(couch_mrview_collation_tests).
-include_lib("couch/include/couch_eunit.hrl").
-include_lib("couch/include/couch_db.hrl").
-define(TIMEOUT, 1000).
-define(VALUES, [
null,
false,
true,
1,
2,
3.0,
4,
<<"a">>,
<<"A">>,
<<"aa">>,
<<"b">>,
<<"B">>,
<<"ba">>,
<<"bb">>,
% U+200B is a zero-width space, which will be ignored by ICU but will cause
% the raw collator to treat these as three distinct keys
<<"c">>,
unicode:characters_to_binary([$c, 16#200B]),
unicode:characters_to_binary([$c, 16#200B, 16#200B]),
[<<"a">>],
[<<"b">>],
[<<"b">>, <<"c">>],
[<<"b">>, <<"c">>, <<"a">>],
[<<"b">>, <<"d">>],
[<<"b">>, <<"d">>, <<"e">>],
{[{<<"a">>, 1}]},
{[{<<"a">>, 2}]},
{[{<<"b">>, 1}]},
{[{<<"b">>, 2}]},
{[{<<"b">>, 2}, {<<"a">>, 1}]},
{[{<<"b">>, 2}, {<<"c">>, 2}]}
]).
setup() ->
{ok, Db1} = couch_mrview_test_util:new_db(?tempdb(), map),
Docs = [couch_mrview_test_util:ddoc(red) | make_docs()],
{ok, Db2} = couch_mrview_test_util:save_docs(Db1, Docs),
Db2.
teardown(Db) ->
couch_db:close(Db),
couch_server:delete(Db#db.name, [?ADMIN_CTX]),
ok.
collation_test_() ->
{
"Collation tests",
{
setup,
fun test_util:start_couch/0, fun test_util:stop_couch/1,
{
foreach,
fun setup/0, fun teardown/1,
[
fun should_collate_fwd/1,
fun should_collate_rev/1,
fun should_collate_range_/1,
fun should_collate_with_inclusive_end_fwd/1,
fun should_collate_with_inclusive_end_rev/1,
fun should_collate_without_inclusive_end_fwd/1,
fun should_collate_without_inclusive_end_rev/1,
fun should_collate_with_endkey_docid/1,
fun should_use_collator_for_reduce_grouping/1
]
}
}
}.
should_collate_fwd(Db) ->
{ok, Results} = run_query(Db, []),
Expect = [{meta, [{total, length(?VALUES)}, {offset, 0}]}] ++ rows(),
?_assertEquiv(Expect, Results).
should_collate_rev(Db) ->
{ok, Results} = run_query(Db, [{direction, rev}]),
Expect = [{meta, [{total, length(?VALUES)}, {offset, 0}]}] ++ lists:reverse(rows()),
?_assertEquiv(Expect, Results).
should_collate_range_(Db) ->
Index = lists:zip(lists:seq(0, length(?VALUES)-1), ?VALUES),
lists:map(fun(V) ->
{ok, Results} = run_query(Db, [{start_key, V}, {end_key, V}]),
Expect = [
{meta, [{total, length(?VALUES)}, find_offset(Index, V)]} |
find_matching_rows(Index, V)
],
?_assertEquiv(Expect, Results)
end, ?VALUES).
find_offset(Index, Value) ->
[{Offset, _} | _] = lists:dropwhile(fun({_, V}) ->
couch_ejson_compare:less(Value, V) =/= 0
end, Index),
{offset, Offset}.
find_matching_rows(Index, Value) ->
Matches = lists:filter(fun({_, V}) ->
couch_ejson_compare:less(Value, V) =:= 0
end, Index),
lists:map(fun({Id, V}) ->
{row, [{id, list_to_binary(integer_to_list(Id))}, {key, V}, {value, 0}]}
end, Matches).
should_collate_with_inclusive_end_fwd(Db) ->
Opts = [{end_key, <<"b">>}, {inclusive_end, true}],
{ok, Rows0} = run_query(Db, Opts),
LastRow = lists:last(Rows0),
Expect = {row, [{id,<<"10">>}, {key,<<"b">>}, {value,0}]},
?_assertEqual(Expect, LastRow).
should_collate_with_inclusive_end_rev(Db) ->
Opts = [{end_key, <<"b">>}, {inclusive_end, true}, {direction, rev}],
{ok, Rows} = run_query(Db, Opts),
LastRow = lists:last(Rows),
Expect = {row, [{id,<<"10">>}, {key,<<"b">>}, {value,0}]},
?_assertEqual(Expect, LastRow).
should_collate_without_inclusive_end_fwd(Db) ->
Opts = [{end_key, <<"b">>}, {inclusive_end, false}],
{ok, Rows0} = run_query(Db, Opts),
LastRow = lists:last(Rows0),
Expect = {row, [{id,<<"9">>}, {key,<<"aa">>}, {value,0}]},
?_assertEqual(Expect, LastRow).
should_collate_without_inclusive_end_rev(Db) ->
Opts = [{end_key, <<"b">>}, {inclusive_end, false}, {direction, rev}],
{ok, Rows} = run_query(Db, Opts),
LastRow = lists:last(Rows),
Expect = {row, [{id,<<"11">>}, {key,<<"B">>}, {value,0}]},
?_assertEqual(Expect, LastRow).
should_collate_with_endkey_docid(Db) ->
?_test(begin
{ok, Rows0} = run_query(Db, [
{end_key, <<"b">>}, {end_key_docid, <<"10">>},
{inclusive_end, false}
]),
Result0 = lists:last(Rows0),
Expect0 = {row, [{id,<<"9">>}, {key,<<"aa">>}, {value,0}]},
?assertEqual(Expect0, Result0),
{ok, Rows1} = run_query(Db, [
{end_key, <<"b">>}, {end_key_docid, <<"11">>},
{inclusive_end, false}
]),
Result1 = lists:last(Rows1),
Expect1 = {row, [{id,<<"10">>}, {key,<<"b">>}, {value,0}]},
?assertEqual(Expect1, Result1)
end).
should_use_collator_for_reduce_grouping(Db) ->
UniqueKeys = lists:usort(fun(A, B) ->
not couch_ejson_compare:less_json(B, A)
end, ?VALUES),
{ok, [{meta,_} | Rows]} = reduce_query(Db, [{group_level, exact}]),
?_assertEqual(length(UniqueKeys), length(Rows)).
make_docs() ->
{Docs, _} = lists:foldl(fun(V, {Docs0, Count}) ->
Doc = couch_doc:from_json_obj({[
{<<"_id">>, list_to_binary(integer_to_list(Count))},
{<<"foo">>, V}
]}),
{[Doc | Docs0], Count+1}
end, {[], 0}, ?VALUES),
Docs.
rows() ->
{Rows, _} = lists:foldl(fun(V, {Rows0, Count}) ->
Id = list_to_binary(integer_to_list(Count)),
Row = {row, [{id, Id}, {key, V}, {value, 0}]},
{[Row | Rows0], Count+1}
end, {[], 0}, ?VALUES),
lists:reverse(Rows).
run_query(Db, Opts) ->
couch_mrview:query_view(Db, <<"_design/bar">>, <<"zing">>, Opts).
reduce_query(Db, Opts) ->
couch_mrview:query_view(Db, <<"_design/red">>, <<"zing">>, Opts).