blob: 0b6e6c9f8c37fc08762c725845101240692a839b [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_views_red_test).
-include_lib("couch/include/couch_eunit.hrl").
-include_lib("couch/include/couch_db.hrl").
-include_lib("fabric/test/fabric2_test.hrl").
-include("couch_views.hrl").
-define(NUM_DOCS, 2000).
reduce_views_shraed_db_test_() ->
{
"Reduce views",
{
setup,
fun setup_db_with_docs/0,
fun teardown_db/1,
with([
?TDEF(should_reduce),
?TDEF(should_reduce_rev),
?TDEF(should_reduce_start_key),
?TDEF(should_reduce_start_key_rev),
?TDEF(should_reduce_end_key),
?TDEF(should_reduce_end_key_rev),
?TDEF(should_reduce_inclusive_end_false),
?TDEF(should_reduce_inclusive_end_false_rev),
?TDEF(should_reduce_start_and_end_key),
?TDEF(should_reduce_start_and_end_key_rev),
?TDEF(should_reduce_empty_range),
?TDEF(should_reduce_empty_range_rev),
?TDEF(should_reduce_grouped),
?TDEF(should_reduce_grouped_rev),
?TDEF(should_reduce_grouped_start_key),
?TDEF(should_reduce_grouped_start_key_rev),
?TDEF(should_reduce_grouped_end_key),
?TDEF(should_reduce_grouped_end_key_rev),
?TDEF(should_reduce_grouped_inclusive_end_false),
?TDEF(should_reduce_grouped_inclusive_end_false_rev),
?TDEF(should_reduce_grouped_start_and_end_key),
?TDEF(should_reduce_grouped_start_and_end_key_rev),
?TDEF(should_reduce_grouped_empty_range),
?TDEF(should_reduce_grouped_empty_range_rev),
?TDEF(should_reduce_array_keys),
?TDEF(should_reduce_grouped_array_keys),
?TDEF(should_reduce_group_1_array_keys),
?TDEF(should_reduce_group_1_array_keys_start_key),
?TDEF(should_reduce_group_1_array_keys_start_key_rev),
?TDEF(should_reduce_group_1_array_keys_end_key),
?TDEF(should_reduce_group_1_array_keys_end_key_rev),
?TDEF(should_reduce_group_1_array_keys_inclusive_end_false),
?TDEF(should_reduce_group_1_array_keys_inclusive_end_false_rev),
?TDEF(should_reduce_group_1_array_keys_start_and_end_key),
?TDEF(should_reduce_group_1_array_keys_start_and_end_key_rev),
?TDEF(should_reduce_group_1_array_keys_sub_array_select),
?TDEF(should_reduce_group_1_array_keys_sub_array_select_rev),
?TDEF(should_reduce_group_1_array_keys_sub_array_inclusive_end),
?TDEF(should_reduce_group_1_array_keys_empty_range),
?TDEF(should_reduce_group_1_array_keys_empty_range_rev)
])
}
}.
reduce_views_collation_test_() ->
{
"Reduce collation test",
{
setup,
fun setup_db/0,
fun teardown_db/1,
with([
?TDEF(should_collate_group_keys)
])
}
}.
setup_db() ->
Ctx = test_util:start_couch([
fabric,
couch_jobs,
couch_js,
couch_views
]),
{ok, Db} = fabric2_db:create(?tempdb(), [{user_ctx, ?ADMIN_USER}]),
{Db, Ctx}.
setup_db_with_docs() ->
{Db, Ctx} = setup_db(),
fabric2_db:update_docs(Db, [create_ddoc()]),
make_docs(Db, ?NUM_DOCS),
run_query(Db, <<"baz">>, #{limit => 0}),
{Db, Ctx}.
teardown_db({Db, Ctx}) ->
fabric2_db:delete(fabric2_db:name(Db), [{user_ctx, ?ADMIN_USER}]),
test_util:stop_couch(Ctx).
should_reduce({Db, _}) ->
Result = run_query(Db, <<"baz_count">>, #{}),
Expect = {ok, [row(null, ?NUM_DOCS)]},
?assertEqual(Expect, Result).
should_reduce_rev({Db, _}) ->
Args = #{
direction => rev
},
Result = run_query(Db, <<"baz_count">>, Args),
Expect = {ok, [row(null, ?NUM_DOCS)]},
?assertEqual(Expect, Result).
should_reduce_start_key({Db, _}) ->
Args = #{
start_key => 4
},
Result = run_query(Db, <<"baz_count">>, Args),
Expect = {ok, [row(null, ?NUM_DOCS - 3)]},
?assertEqual(Expect, Result).
should_reduce_start_key_rev({Db, _}) ->
Args = #{
direction => rev,
start_key => 4
},
Result = run_query(Db, <<"baz_count">>, Args),
Expect = {ok, [row(null, 4)]},
?assertEqual(Expect, Result).
should_reduce_end_key({Db, _}) ->
Args = #{
end_key => 6
},
Result = run_query(Db, <<"baz_count">>, Args),
Expect = {ok, [row(null, 6)]},
?assertEqual(Expect, Result).
should_reduce_end_key_rev({Db, _}) ->
Args = #{
direction => rev,
end_key => 6
},
Result = run_query(Db, <<"baz_count">>, Args),
Expect = {ok, [row(null, ?NUM_DOCS - 5)]},
?assertEqual(Expect, Result).
should_reduce_inclusive_end_false({Db, _}) ->
Args = #{
end_key => 6,
inclusive_end => false
},
Result = run_query(Db, <<"baz_count">>, Args),
Expect = {ok, [row(null, 5)]},
?assertEqual(Expect, Result).
should_reduce_inclusive_end_false_rev({Db, _}) ->
Args = #{
direction => rev,
end_key => 6,
inclusive_end => false
},
Result = run_query(Db, <<"baz_count">>, Args),
Expect = {ok, [row(null, ?NUM_DOCS - 6)]},
?assertEqual(Expect, Result).
should_reduce_start_and_end_key({Db, _}) ->
Args = #{
start_key => 3,
end_key => 5
},
Result = run_query(Db, <<"baz_count">>, Args),
Expect = {ok, [row(null, 3)]},
?assertEqual(Expect, Result).
should_reduce_start_and_end_key_rev({Db, _}) ->
Args = #{
direction => rev,
start_key => 5,
end_key => 3
},
Result = run_query(Db, <<"baz_count">>, Args),
Expect = {ok, [row(null, 3)]},
?assertEqual(Expect, Result).
should_reduce_empty_range({Db, _}) ->
Args = #{
start_key => 100000,
end_key => 100001
},
Result = run_query(Db, <<"baz_count">>, Args),
Expect = {ok, []},
?assertEqual(Expect, Result).
should_reduce_empty_range_rev({Db, _}) ->
Args = #{
direction => rev,
start_key => 100001,
end_key => 100000
},
Result = run_query(Db, <<"baz_count">>, Args),
Expect = {ok, []},
?assertEqual(Expect, Result).
should_reduce_grouped({Db, _}) ->
Args = #{
group_level => exact
},
Result = run_query(Db, <<"baz_count">>, Args),
Expect = {ok, [row(I, 1) || I <- lists:seq(1, ?NUM_DOCS)]},
?assertEqual(Expect, Result).
should_reduce_grouped_rev({Db, _}) ->
Args = #{
direction => rev,
group_level => exact
},
Result = run_query(Db, <<"baz_count">>, Args),
Expect = {ok, [row(I, 1) || I <- lists:seq(?NUM_DOCS, 1, -1)]},
?assertEqual(Expect, Result).
should_reduce_grouped_start_key({Db, _}) ->
Args = #{
group_level => exact,
start_key => 3
},
Result = run_query(Db, <<"baz_count">>, Args),
Expect = {ok, [row(I, 1) || I <- lists:seq(3, ?NUM_DOCS)]},
?assertEqual(Expect, Result).
should_reduce_grouped_start_key_rev({Db, _}) ->
Args = #{
direction => rev,
group_level => exact,
start_key => 3
},
Result = run_query(Db, <<"baz_count">>, Args),
Expect =
{ok, [
row(3, 1),
row(2, 1),
row(1, 1)
]},
?assertEqual(Expect, Result).
should_reduce_grouped_end_key({Db, _}) ->
Args = #{
group_level => exact,
end_key => 6
},
Result = run_query(Db, <<"baz_count">>, Args),
Expect = {ok, [row(I, 1) || I <- lists:seq(1, 6)]},
?assertEqual(Expect, Result).
should_reduce_grouped_end_key_rev({Db, _}) ->
Args = #{
direction => rev,
group_level => exact,
end_key => 6
},
Result = run_query(Db, <<"baz_count">>, Args),
Expect = {ok, [row(I, 1) || I <- lists:seq(?NUM_DOCS, 6, -1)]},
?assertEqual(Expect, Result).
should_reduce_grouped_inclusive_end_false({Db, _}) ->
Args = #{
group_level => exact,
end_key => 4,
inclusive_end => false
},
Result = run_query(Db, <<"baz_count">>, Args),
Expect = {ok, [row(I, 1) || I <- lists:seq(1, 3)]},
?assertEqual(Expect, Result).
should_reduce_grouped_inclusive_end_false_rev({Db, _}) ->
Args = #{
direction => rev,
group_level => exact,
end_key => 4,
inclusive_end => false
},
Result = run_query(Db, <<"baz_count">>, Args),
Expect = {ok, [row(I, 1) || I <- lists:seq(?NUM_DOCS, 5, -1)]},
?assertEqual(Expect, Result).
should_reduce_grouped_start_and_end_key({Db, _}) ->
Args = #{
group_level => exact,
start_key => 2,
end_key => 4
},
Result = run_query(Db, <<"baz_count">>, Args),
Expect = {ok, [row(I, 1) || I <- lists:seq(2, 4)]},
?assertEqual(Expect, Result).
should_reduce_grouped_start_and_end_key_rev({Db, _}) ->
Args = #{
direction => rev,
group_level => exact,
start_key => 4,
end_key => 2
},
Result = run_query(Db, <<"baz_count">>, Args),
Expect = {ok, [row(I, 1) || I <- lists:seq(4, 2, -1)]},
?assertEqual(Expect, Result).
should_reduce_grouped_empty_range({Db, _}) ->
Args = #{
group_level => exact,
start_key => 100000,
end_key => 100001
},
Result = run_query(Db, <<"baz_count">>, Args),
Expect = {ok, []},
?assertEqual(Expect, Result).
should_reduce_grouped_empty_range_rev({Db, _}) ->
Args = #{
direction => rev,
group_level => exact,
start_key => 100001,
end_key => 100000
},
Result = run_query(Db, <<"baz_count">>, Args),
Expect = {ok, []},
?assertEqual(Expect, Result).
should_reduce_array_keys({Db, _}) ->
Result = run_query(Db, <<"boom">>, #{}),
Expect = {ok, [row(null, 1.5 * ?NUM_DOCS)]},
?assertEqual(Expect, Result).
should_reduce_grouped_array_keys({Db, _}) ->
Args = #{
group_level => exact
},
Result = run_query(Db, <<"boom">>, Args),
Expect = {ok, lists:sort([row([I rem 3, I], 1.5) || I <- lists:seq(1, ?NUM_DOCS)])},
?assertEqual(Expect, Result).
should_reduce_group_1_array_keys({Db, _}) ->
Args = #{
group_level => 1
},
Result = run_query(Db, <<"boom">>, Args),
Expect =
{ok, [
row([0], rem_count(0, ?NUM_DOCS) * 1.5),
row([1], rem_count(1, ?NUM_DOCS) * 1.5),
row([2], rem_count(2, ?NUM_DOCS) * 1.5)
]},
?assertEqual(Expect, Result).
should_reduce_group_1_array_keys_start_key({Db, _}) ->
Args = #{
group_level => 1,
start_key => [1]
},
Result = run_query(Db, <<"boom">>, Args),
Expect =
{ok, [
row([1], rem_count(1, ?NUM_DOCS) * 1.5),
row([2], rem_count(2, ?NUM_DOCS) * 1.5)
]},
?assertEqual(Expect, Result).
should_reduce_group_1_array_keys_start_key_rev({Db, _}) ->
Args = #{
direction => rev,
group_level => 1,
start_key => [1, ?NUM_DOCS + 1]
},
Result = run_query(Db, <<"boom">>, Args),
Expect =
{ok, [
row([1], rem_count(1, ?NUM_DOCS) * 1.5),
row([0], rem_count(0, ?NUM_DOCS) * 1.5)
]},
?assertEqual(Expect, Result).
should_reduce_group_1_array_keys_end_key({Db, _}) ->
Args = #{
group_level => 1,
end_key => [1, ?NUM_DOCS + 1]
},
Result = run_query(Db, <<"boom">>, Args),
Expect =
{ok, [
row([0], rem_count(0, ?NUM_DOCS) * 1.5),
row([1], rem_count(1, ?NUM_DOCS) * 1.5)
]},
?assertEqual(Expect, Result).
should_reduce_group_1_array_keys_end_key_rev({Db, _}) ->
Args = #{
direction => rev,
group_level => 1,
end_key => [1]
},
Result = run_query(Db, <<"boom">>, Args),
Expect =
{ok, [
row([2], rem_count(2, ?NUM_DOCS) * 1.5),
row([1], rem_count(1, ?NUM_DOCS) * 1.5)
]},
?assertEqual(Expect, Result).
should_reduce_group_1_array_keys_inclusive_end_false({Db, _}) ->
Args = #{
group_level => 1,
end_key => [1],
inclusive_end => false
},
Result = run_query(Db, <<"boom">>, Args),
Expect =
{ok, [
row([0], rem_count(0, ?NUM_DOCS) * 1.5)
]},
?assertEqual(Expect, Result).
should_reduce_group_1_array_keys_inclusive_end_false_rev({Db, _}) ->
Args = #{
direction => rev,
group_level => 1,
end_key => [1, ?NUM_DOCS + 1],
inclusive_end => false
},
Result = run_query(Db, <<"boom">>, Args),
Expect =
{ok, [
row([2], rem_count(2, ?NUM_DOCS) * 1.5)
]},
?assertEqual(Expect, Result).
should_reduce_group_1_array_keys_start_and_end_key({Db, _}) ->
Args = #{
group_level => 1,
start_key => [1],
end_key => [1, ?NUM_DOCS + 1]
},
Result = run_query(Db, <<"boom">>, Args),
Expect =
{ok, [
row([1], rem_count(1, ?NUM_DOCS) * 1.5)
]},
?assertEqual(Expect, Result).
should_reduce_group_1_array_keys_start_and_end_key_rev({Db, _}) ->
Args = #{
direction => rev,
group_level => 1,
start_key => [1, ?NUM_DOCS + 1],
end_key => [1]
},
Result = run_query(Db, <<"boom">>, Args),
Expect =
{ok, [
row([1], rem_count(1, ?NUM_DOCS) * 1.5)
]},
?assertEqual(Expect, Result).
should_reduce_group_1_array_keys_sub_array_select({Db, _}) ->
% Test that keys are applied below the key grouping
Args = #{
group_level => 1,
start_key => [0, ?NUM_DOCS - 6],
end_key => [1, 4]
},
Result = run_query(Db, <<"boom">>, Args),
Expect =
{ok, [
row([0], 3.0),
row([1], 3.0)
]},
?assertEqual(Expect, Result).
should_reduce_group_1_array_keys_sub_array_select_rev({Db, _}) ->
% Test that keys are applied below the key grouping
Args = #{
direction => rev,
group_level => 1,
start_key => [1, 4],
end_key => [0, ?NUM_DOCS - 6]
},
Result = run_query(Db, <<"boom">>, Args),
Expect =
{ok, [
row([1], 3.0),
row([0], 3.0)
]},
?assertEqual(Expect, Result).
should_reduce_group_1_array_keys_sub_array_inclusive_end({Db, _}) ->
% Test that keys are applied below the key grouping
Args = #{
group_level => 1,
start_key => [0, ?NUM_DOCS - 6],
end_key => [1, 4],
inclusive_end => false
},
Result = run_query(Db, <<"boom">>, Args),
Expect =
{ok, [
row([0], 3.0),
row([1], 1.5)
]},
?assertEqual(Expect, Result).
should_reduce_group_1_array_keys_empty_range({Db, _}) ->
Args = #{
group_level => 1,
start_key => [100],
end_key => [101]
},
Result = run_query(Db, <<"boom">>, Args),
Expect = {ok, []},
?assertEqual(Expect, Result).
should_reduce_group_1_array_keys_empty_range_rev({Db, _}) ->
Args = #{
direction => rev,
group_level => 1,
start_key => [101],
end_key => [100]
},
Result = run_query(Db, <<"boom">>, Args),
Expect = {ok, []},
?assertEqual(Expect, Result).
should_collate_group_keys({Db, _}) ->
DDoc = couch_doc:from_json_obj(
{[
{<<"_id">>, <<"_design/bar">>},
{<<"views">>,
{[
{<<"group">>,
{[
{<<"map">>, <<"function(doc) {emit([doc.val], 1);}">>},
{<<"reduce">>, <<"_count">>}
]}}
]}}
]}
),
% val is "föö" without combining characters
Doc1 = couch_doc:from_json_obj(
{[
{<<"_id">>, <<"a">>},
{<<"val">>, <<16#66, 16#C3, 16#B6, 16#C3, 16#B6>>}
]}
),
% val is "föö" without combining characters
Doc2 = couch_doc:from_json_obj(
{[
{<<"_id">>, <<"b">>},
{<<"val">>, <<16#66, 16#6F, 16#CC, 16#88, 16#6F, 16#CC, 16#88>>}
]}
),
{ok, _} = fabric2_db:update_docs(Db, [DDoc, Doc1, Doc2]),
% An implementation detail we have is that depending on
% the direction of the view read we'll get the first
% or last key to represent a group. In this particular
% implementation the document ID breaks the sort tie
% in the map view data.
ArgsFwd = #{
group_level => exact
},
ResultFwd = run_query(Db, DDoc, <<"group">>, ArgsFwd),
ExpectFwd =
{ok, [
row([<<16#66, 16#C3, 16#B6, 16#C3, 16#B6>>], 2)
]},
?assertEqual(ExpectFwd, ResultFwd),
ArgsRev = #{
direction => rev,
group_level => exact
},
ResultRev = run_query(Db, DDoc, <<"group">>, ArgsRev),
ExpectRev =
{ok, [
row([<<16#66, 16#6F, 16#CC, 16#88, 16#6F, 16#CC, 16#88>>], 2)
]},
?assertEqual(ExpectRev, ResultRev).
rem_count(Rem, Count) ->
Members = [I || I <- lists:seq(1, Count), I rem 3 == Rem],
length(Members).
run_query(Db, Idx, Args) ->
DDoc = create_ddoc(),
run_query(Db, DDoc, Idx, Args).
run_query(Db, DDoc, Idx, Args) ->
couch_views:query(Db, DDoc, Idx, fun default_cb/2, [], Args).
default_cb(complete, Acc) ->
{ok, lists:reverse(Acc)};
default_cb({final, Info}, []) ->
{ok, [Info]};
default_cb({final, _}, Acc) ->
{ok, Acc};
default_cb({meta, _}, Acc) ->
{ok, Acc};
default_cb(ok, ddoc_updated) ->
{ok, ddoc_updated};
default_cb(Row, Acc) ->
{ok, [Row | Acc]}.
row(Key, Value) ->
{row, [{key, Key}, {value, Value}]}.
create_ddoc() ->
couch_doc:from_json_obj(
{[
{<<"_id">>, <<"_design/bar">>},
{<<"views">>,
{[
{<<"baz">>,
{[
{<<"map">>, <<"function(doc) {emit(doc.val, doc.val);}">>}
]}},
{<<"baz_count">>,
{[
{<<"map">>, <<"function(doc) {emit(doc.val, doc.val);}">>},
{<<"reduce">>, <<"_count">>}
]}},
{<<"baz_size">>,
{[
{<<"map">>, <<"function(doc) {emit(doc.val, doc.val);}">>},
{<<"reduce">>, <<"_sum">>}
]}},
{<<"boom">>,
{[
{<<"map">>, <<
"function(doc) {\n"
" emit([doc.val % 3, doc.val], 1.5);\n"
"}"
>>},
{<<"reduce">>, <<"_sum">>}
]}},
{<<"bing">>,
{[
{<<"map">>, <<"function(doc) {}">>},
{<<"reduce">>, <<"_count">>}
]}},
{<<"bing_hyper">>,
{[
{<<"map">>, <<"function(doc) {}">>},
{<<"reduce">>, <<"_approx_count_distinct">>}
]}},
{<<"doc_emit">>,
{[
{<<"map">>, <<"function(doc) {emit(doc.val, doc)}">>}
]}},
{<<"duplicate_keys">>,
{[
{<<"map">>, <<
"function(doc) {\n"
" emit(doc._id, doc.val);\n"
" emit(doc._id, doc.val + 1);\n"
"}"
>>},
{<<"reduce">>, <<"_count">>}
]}},
{<<"zing">>,
{[
{<<"map">>, <<
"function(doc) {\n"
" if(doc.foo !== undefined)\n"
" emit(doc.foo, 0);\n"
"}"
>>}
]}}
]}}
]}
).
make_docs(Db, TotalDocs) when TotalDocs > 0 ->
make_docs(Db, TotalDocs, 0).
make_docs(Db, TotalDocs, DocsMade) when TotalDocs > DocsMade ->
DocCount = min(TotalDocs - DocsMade, 500),
Docs = [doc(I + DocsMade) || I <- lists:seq(1, DocCount)],
fabric2_db:update_docs(Db, Docs),
make_docs(Db, TotalDocs, DocsMade + DocCount);
make_docs(_Db, TotalDocs, DocsMade) when TotalDocs =< DocsMade ->
ok.
doc(Id) ->
couch_doc:from_json_obj(
{[
{<<"_id">>, list_to_binary(integer_to_list(Id))},
{<<"val">>, Id}
]}
).