1#!/usr/bin/env escript
2%% -*- Mode: Erlang; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
3%%! -smp enable
4
5% Licensed under the Apache License, Version 2.0 (the "License"); you may not
6% use this file except in compliance with the License. You may obtain a copy of
7% the License at
8%
9%   http://www.apache.org/licenses/LICENSE-2.0
10%
11% Unless required by applicable law or agreed to in writing, software
12% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14% License for the specific language governing permissions and limitations under
15% the License.
16
17-include_lib("couch_set_view/include/couch_set_view.hrl").
18
19% from couch_db.hrl
20-define(MIN_STR, <<>>).
21-define(MAX_STR, <<255>>).
22
23-record(view_query_args, {
24    start_key,
25    end_key,
26    start_docid = ?MIN_STR,
27    end_docid = ?MAX_STR,
28    direction = fwd,
29    inclusive_end = true,
30    limit = 10000000000,
31    skip = 0,
32    group_level = 0,
33    view_type = nil,
34    include_docs = false,
35    conflicts = false,
36    stale = false,
37    multi_get = false,
38    callback = nil,
39    list = nil,
40    run_reduce = true,
41    keys = nil,
42    view_name = nil,
43    debug = false,
44    filter = true,
45    type = main
46}).
47
48-define(etap_match(Got, Expected, Desc),
49        etap:fun_is(fun(XXXXXX) ->
50            case XXXXXX of Expected -> true; _ -> false end
51        end, Got, Desc)).
52-define(MAX_WAIT_TIME, 10 * 1000).
53
54test_set_name() -> <<"couch_test_set_index_debug_params">>.
55num_set_partitions() -> 64.
56ddoc_id() -> <<"_design/test">>.
57num_docs() -> 1024.  % keep it a multiple of num_set_partitions()
58
59
60main(_) ->
61    test_util:init_code_path(),
62
63    etap:plan(16),
64    case (catch test()) of
65        ok ->
66            etap:end_tests();
67        Other ->
68            etap:diag(io_lib:format("Test died abnormally: ~p", [Other])),
69            etap:bail(Other)
70    end,
71    ok.
72
73
74test() ->
75    couch_set_view_test_util:start_server(test_set_name()),
76
77    etap:diag("Testing debug parameters"),
78    test_filter_parameter_main(erlang),
79    test_filter_parameter_replica(erlang),
80    test_filter_parameter_main(http),
81    test_filter_parameter_replica(http),
82
83    couch_set_view_test_util:stop_server(),
84    ok.
85
86
87setup_test() ->
88    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
89    couch_set_view_test_util:create_set_dbs(test_set_name(), num_set_partitions()),
90
91    DDoc = {[
92        {<<"meta">>, {[{<<"id">>, ddoc_id()}]}},
93        {<<"json">>, {[
94            {<<"views">>, {[
95                {<<"test">>, {[
96                    {<<"map">>, <<"function(doc, meta) { emit(meta.id, doc.value); }">>}
97                ]}},
98                {<<"testred">>, {[
99                    {<<"map">>, <<"function(doc, meta) { emit(meta.id, doc.value); }">>},
100                    {<<"reduce">>, <<"_count">>}
101                ]}}
102            ]}}
103        ]}}
104    ]},
105    populate_set(DDoc).
106
107
108test_filter_parameter_main(AccessType) ->
109    setup_test(),
110
111    % There are 75% active, 12.5% passive and 12.5% cleanup partitions
112    ok = configure_view_group(ddoc_id(), lists:seq(0, 47), lists:seq(48, 55),
113        lists:seq(56, 63)),
114
115    {ok, Rows} = (catch query_map_view(AccessType, <<"test">>, main, true)),
116    etap:is(length(Rows), (num_docs()) * 0.75,
117            "Map view query returned all rows from active partitions"),
118    {ok, Rows2} = (catch query_map_view(AccessType, <<"test">>, main, false)),
119    etap:is(length(Rows2), num_docs(),
120            "Map view query queried with _filter=false returns all "
121            "partitions"),
122
123    % The same, but with a reduce function
124    {ok, RowsRed} = (catch query_reduce_view(
125        AccessType, <<"testred">>, main, true)),
126    etap:is(RowsRed, (num_docs()) * 0.75,
127            "Reduce view query returned all rows from active partitions"),
128    {ok, RowsRed2} = (catch query_reduce_view(
129         AccessType, <<"testred">>, main, false)),
130    etap:is(RowsRed2, num_docs(),
131            "Reduce view query queried with _filter=false returns all "
132            "partitions"),
133
134    GroupPid = get_main_pid(),
135    couch_set_view_test_util:wait_for_updater_to_finish(GroupPid, ?MAX_WAIT_TIME),
136    shutdown_group().
137
138
139test_filter_parameter_replica(AccessType) ->
140    setup_test(),
141
142    ok = configure_view_group(ddoc_id(), lists:seq(0, 47), [], []),
143    ok = couch_set_view:add_replica_partitions(
144        mapreduce_view, test_set_name(), ddoc_id(), lists:seq(48, 63)),
145
146    % There are 75% main and 25% replica partitions, from the replica
147    % partitions are 87.5% active and 12.5% passive
148    ok = configure_replica_group(ddoc_id(), lists:seq(48, 61),
149        lists:seq(62, 63)),
150
151    {ok, Rows} = (catch query_map_view(
152        AccessType, <<"test">>, replica, true)),
153    etap:is(length(Rows), (num_docs() * 0.25) * 0.125,
154            "Replica map view query returned all rows from "
155            "passive partitions"),
156
157    {ok, Rows2} = (catch query_map_view(
158        AccessType, <<"test">>, replica, false)),
159    % There are 75% main and 25% replica partitions
160    etap:is(length(Rows2), (num_docs()) * 0.25,
161            "Replica map view query queried with _filter=false returns all "
162            "partitions"),
163
164    % The same, but with a reduce function
165    {ok, RowsRed} = (catch query_reduce_view(
166        AccessType, <<"testred">>, replica, true)),
167    etap:is(RowsRed, (num_docs() * 0.25) * 0.125,
168        "Replica reduce view query returned all rows from passive partitions"),
169    {ok, RowsRed2} = (catch query_reduce_view(
170        AccessType, <<"testred">>, replica, false)),
171    % There are 75% active and 25% replica partitions
172    etap:is(RowsRed2, (num_docs()) * 0.25,
173        "Replica reduce view query queried with _filter=false returns all "
174        "partitions"),
175
176    GroupPid = get_replica_pid(),
177    couch_set_view_test_util:wait_for_updater_to_finish(GroupPid, ?MAX_WAIT_TIME),
178
179    shutdown_group().
180
181
182shutdown_group() ->
183    GroupPid = couch_set_view:get_group_pid(
184        mapreduce_view, test_set_name(), ddoc_id(), prod),
185    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
186    MonRef = erlang:monitor(process, GroupPid),
187    receive
188    {'DOWN', MonRef, _, _, _} ->
189        ok
190    after 10000 ->
191        etap:bail("Timeout waiting for group shutdown")
192    end.
193
194
195query_map_view(erlang, ViewName, Type, Filter) ->
196    query_map_view_erlang(ViewName, Type, Filter);
197query_map_view(http, ViewName, Type, Filter) ->
198    query_map_view_http(ViewName, Type, Filter).
199
200
201query_map_view_erlang(ViewName, Type, Filter) ->
202    etap:diag("Querying map view " ++ binary_to_list(ddoc_id()) ++ "/" ++
203        binary_to_list(ViewName)),
204    Req = #set_view_group_req{
205        stale = false,
206        type = Type
207    },
208    {ok, View, Group, _} = couch_set_view:get_map_view(
209        test_set_name(), ddoc_id(), ViewName, Req),
210
211    FoldFun = fun({{Key, DocId}, {_PartId, Value}}, _, Acc) ->
212        {ok, [{{Key, DocId}, Value} | Acc]}
213    end,
214    ViewArgs = #view_query_args{
215        run_reduce = true,
216        view_name = ViewName,
217        filter = Filter
218    },
219    {ok, _, Rows} = couch_set_view:fold(Group, View, FoldFun, [], ViewArgs),
220    couch_set_view:release_group(Group),
221    {ok, Rows}.
222
223
224query_map_view_http(ViewName, Type, Filter) ->
225    etap:diag("Querying map view " ++ binary_to_list(ddoc_id()) ++ "/" ++
226        binary_to_list(ViewName) ++ " through HTTP"),
227    QueryString = "_filter=" ++ atom_to_list(Filter) ++
228                  "&_type=" ++ atom_to_list(Type),
229    {ok, {ViewResults}} = couch_set_view_test_util:query_view(
230        test_set_name(), ddoc_id(), ViewName, QueryString),
231    Rows = couch_util:get_value(<<"rows">>, ViewResults),
232    {ok, Rows}.
233
234
235query_reduce_view(erlang, ViewName, Type, Filter) ->
236    query_reduce_view_erlang(ViewName, Type, Filter);
237query_reduce_view(http, ViewName, Type, Filter) ->
238    query_reduce_view_http(ViewName, Type, Filter).
239
240
241query_reduce_view_erlang(ViewName, Type, Filter) ->
242    etap:diag("Querying reduce view " ++ binary_to_list(ddoc_id()) ++ "/" ++
243        binary_to_list(ViewName) ++ "with ?group=true"),
244    Req = #set_view_group_req{
245        stale = false,
246        type = Type
247    },
248    {ok, View, Group, _} = couch_set_view:get_reduce_view(
249        test_set_name(), ddoc_id(), ViewName, Req),
250
251    FoldFun = fun(Key, {json, Red}, Acc) ->
252        {ok, [{Key, ejson:decode(Red)} | Acc]}
253    end,
254    ViewArgs = #view_query_args{
255        run_reduce = true,
256        view_name = ViewName,
257        filter = Filter
258    },
259    {ok, Rows} = couch_set_view:fold_reduce(Group, View, FoldFun, [], ViewArgs),
260    couch_set_view:release_group(Group),
261    case Rows of
262    [{_Key, RedValue}] ->
263        {ok, RedValue};
264    [] ->
265        empty
266    end.
267
268
269query_reduce_view_http(ViewName, Type, Filter) ->
270    etap:diag("Querying reduce view " ++ binary_to_list(ddoc_id()) ++ "/" ++
271        binary_to_list(ViewName) ++ "with through HTTP"),
272    QueryString = "_filter=" ++ atom_to_list(Filter) ++
273                  "&_type=" ++ atom_to_list(Type),
274    {ok, {ViewResults}} = couch_set_view_test_util:query_view(
275        test_set_name(), ddoc_id(), ViewName, QueryString),
276    Rows = couch_util:get_value(<<"rows">>, ViewResults),
277    [{[_Key, {<<"value">>, RedValue}]}] = Rows,
278    {ok, RedValue}.
279
280
281populate_set(DDoc) ->
282    etap:diag("Populating the " ++ integer_to_list(num_set_partitions()) ++
283        " databases with " ++ integer_to_list(num_docs()) ++ " documents"),
284    ok = couch_set_view_test_util:update_ddoc(test_set_name(), DDoc),
285    DocList = lists:map(
286        fun(I) ->
287            {[
288                {<<"meta">>, {[{<<"id">>, iolist_to_binary(["doc", integer_to_list(I)])}]}},
289                {<<"json">>, {[{<<"value">>, I}]}}
290            ]}
291        end,
292        lists:seq(1, num_docs())),
293    ok = couch_set_view_test_util:populate_set_sequentially(
294        test_set_name(),
295        lists:seq(0, num_set_partitions() - 1),
296        DocList).
297
298
299configure_view_group(DDocId, Active, Passive, Cleanup) ->
300    etap:diag("Configuring view group"),
301    Params = #set_view_params{
302        max_partitions = num_set_partitions(),
303        % Set all partitions to active an later on passive/cleanup
304        active_partitions = lists:seq(0, num_set_partitions()-1),
305        passive_partitions = [],
306        use_replica_index = true
307    },
308    try
309        couch_set_view:define_group(
310            mapreduce_view, test_set_name(), DDocId, Params),
311
312        GroupPid = couch_set_view:get_group_pid(
313            mapreduce_view, test_set_name(), ddoc_id(), prod),
314        ok = gen_server:call(GroupPid, {set_auto_cleanup, false}, infinity),
315        % Somehow the set_auto_cleanup doesn't get set when I don't request
316        % the group afterwards
317        Req = #set_view_group_req{stale = false},
318        {ok, _Group} = couch_set_view:get_group(
319            mapreduce_view, test_set_name(), DDocId, Req),
320
321        couch_set_view_group:set_state(GroupPid, Active, Passive, Cleanup)
322    catch _:Error ->
323        Error
324    end.
325
326
327configure_replica_group(DDocId, Active, Passive) ->
328    etap:diag("Configuring replica group"),
329    Req = #set_view_group_req{stale = false},
330    {ok, Group} = couch_set_view:get_group(
331        mapreduce_view, test_set_name(), DDocId, Req),
332
333    couch_set_view_group:set_state(
334        Group#set_view_group.replica_pid, Active, Passive, []),
335    couch_set_view:release_group(Group).
336
337
338get_main_pid() ->
339    couch_set_view:get_group_pid(
340        mapreduce_view, test_set_name(), ddoc_id(), prod).
341
342get_replica_pid() ->
343    MainPid = get_main_pid(),
344    {ok, Group} = gen_server:call(MainPid, request_group, infinity),
345    Group#set_view_group.replica_pid.
346