summaryrefslogtreecommitdiff
path: root/src/couch_mrview/test/eunit/couch_mrview_collation_tests.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/couch_mrview/test/eunit/couch_mrview_collation_tests.erl')
-rw-r--r--src/couch_mrview/test/eunit/couch_mrview_collation_tests.erl207
1 files changed, 207 insertions, 0 deletions
diff --git a/src/couch_mrview/test/eunit/couch_mrview_collation_tests.erl b/src/couch_mrview/test/eunit/couch_mrview_collation_tests.erl
new file mode 100644
index 000000000..5c8cb54b1
--- /dev/null
+++ b/src/couch_mrview/test/eunit/couch_mrview_collation_tests.erl
@@ -0,0 +1,207 @@
+% 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(couch_db:name(Db), [?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).