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
48test_set_name() -> <<"couch_test_set_index_meta_params">>.
49num_set_partitions() -> 4.
50ddoc_id() -> <<"_design/dev_test">>.
51num_docs() -> 1024.  % keep it a multiple of num_set_partitions()
52docs_per_partition() -> num_docs() div num_set_partitions().
53
54
55main(_) ->
56    test_util:init_code_path(),
57
58    etap:plan(52),
59    case (catch test()) of
60        ok ->
61            etap:end_tests();
62        Other ->
63            etap:diag(io_lib:format("Test died abnormally: ~p", [Other])),
64            etap:bail(Other)
65    end,
66    ok.
67
68
69test() ->
70    couch_set_view_test_util:start_server(test_set_name()),
71
72    etap:diag("Testing development views with meta params"),
73
74    % Test for PartId (a.k.a vbucket) to which document hash into
75    test_map_query_vb(0),
76    test_map_query_vb(1),
77    test_map_query_vb(2),
78    test_map_query_vb(3),
79
80    % Test for current seq number of documents
81    test_map_query_seq(0),
82    test_map_query_seq(1),
83    test_map_query_seq(2),
84    test_map_query_seq(3),
85
86    % Test if seq number changes after documents are updated
87    test_map_query_updated(0),
88    test_map_query_updated(1),
89    test_map_query_updated(2),
90    test_map_query_updated(3),
91
92    % Test xattrs when document does not contain extra attribute
93    test_map_query_xattrs(0, false, false),
94    test_map_query_xattrs(1, false, false),
95    test_map_query_xattrs(2, false, false),
96    test_map_query_xattrs(3, false, false),
97
98    % Test xattrs when document contains extra attribute
99    test_map_query_xattrs(0, true, false),
100    test_map_query_xattrs(1, true, false),
101    test_map_query_xattrs(2, true, false),
102    test_map_query_xattrs(3, true, false),
103
104    % Test xattrs when document is deleted
105    % and does not contain extra attribute
106    test_map_query_xattrs(0, false, true),
107    test_map_query_xattrs(1, false, true),
108    test_map_query_xattrs(2, false, true),
109    test_map_query_xattrs(3, false, true),
110
111    % Test xattrs when document is deleted
112    % and contains extra attribute
113    test_map_query_xattrs(0, true, true),
114    test_map_query_xattrs(1, true, true),
115    test_map_query_xattrs(2, true, true),
116    test_map_query_xattrs(3, true, true),
117
118
119    couch_set_view_test_util:delete_set_dbs(test_set_name(),
120        num_set_partitions()),
121    couch_set_view_test_util:stop_server(),
122    ok.
123
124
125test_map_query_vb(PartitionId) ->
126    setup_test_vb(),
127    ok = configure_view_group(ddoc_id(), PartitionId),
128
129    {ok, Rows} = (catch query_map_view(<<"test">>)),
130    etap:is(length(Rows), docs_per_partition(),
131        "Got " ++ integer_to_list(docs_per_partition()) ++ " view rows"),
132    verify_rows_vb(Rows, PartitionId),
133
134    shutdown_group().
135
136test_map_query_seq(PartitionId) ->
137    setup_test_seq(),
138    ok = configure_view_group(ddoc_id(), PartitionId),
139
140    {ok, Rows} = (catch query_map_view(<<"test">>)),
141    etap:is(length(Rows), docs_per_partition(),
142        "Got " ++ integer_to_list(docs_per_partition()) ++ " view rows"),
143    verify_rows_seq(Rows, PartitionId, 1, docs_per_partition()),
144
145    shutdown_group().
146
147test_map_query_updated(PartitionId) ->
148    setup_test_seq(),
149    update_docs(),
150    ok = configure_view_group(ddoc_id(), PartitionId),
151
152    {ok, Rows} = (catch query_map_view(<<"test">>)),
153    etap:is(length(Rows), docs_per_partition(),
154        "Got " ++ integer_to_list(docs_per_partition()) ++ " view rows"),
155    verify_rows_seq(Rows, PartitionId, (1 + docs_per_partition()),
156        (2 * docs_per_partition())),
157
158    shutdown_group().
159
160test_map_query_xattrs(PartitionId, HasXattrs, Deleted) ->
161    setup_test_xattrs(HasXattrs, Deleted),
162    ok = configure_view_group(ddoc_id(), PartitionId),
163
164    {ok, Rows} = (catch query_map_view(<<"test">>)),
165    case HasXattrs of
166    true ->
167        etap:is(length(Rows), docs_per_partition(),
168            "Got " ++ integer_to_list(docs_per_partition()) ++ " view rows"),
169        verify_rows_xattrs(Rows, PartitionId, HasXattrs);
170    false ->
171        case Deleted of
172        true ->
173            etap:is(length(Rows), 0,
174                "Got " ++ integer_to_list(0) ++ " view rows");
175        false ->
176            etap:is(length(Rows), docs_per_partition(),
177                "Got " ++ integer_to_list(docs_per_partition()) ++ " view rows"),
178            verify_rows_xattrs(Rows, PartitionId, HasXattrs)
179        end
180    end,
181    shutdown_group().
182
183% As the partitions are populated sequentially we can easily verify them
184verify_rows_vb(Rows, PartitionId) ->
185    Offset = (PartitionId * docs_per_partition()),
186    PartId = list_to_binary(integer_to_list(PartitionId)),
187    DocList = lists:map(fun(Doc) ->
188        {[{<<"meta">>, {[{<<"deleted">>, false}, {<<"id">>, DocId}]}},
189          {<<"json">>, {[{<<"value">>, _Value}]}}]} = Doc,
190        {<<"\"", DocId/binary, "\"">>, DocId,
191            <<"\"", PartId/binary, "\"">>}
192    end, create_docs(1 + Offset, Offset + docs_per_partition(), false)),
193    etap:is(Rows, lists:sort(DocList), "Returned correct rows").
194
195verify_rows_seq(Rows, PartitionId, From, To) ->
196    Offset = (PartitionId * docs_per_partition()),
197    DocList = lists:zipwith(fun(Doc, I) ->
198        {[{<<"meta">>, {[{<<"deleted">>, false}, {<<"id">>, DocId}]}},
199          {<<"json">>, {[{<<"value">>, _Value}]}}]} = Doc,
200          Seq = list_to_binary(integer_to_list(I)),
201        {<<"\"", DocId/binary, "\"">>, DocId,
202            <<"\"", Seq/binary, "\"">>}
203    end, lists:sort(create_docs(1 + Offset, Offset + docs_per_partition(), false)),
204         lists:seq(From, To)),
205    etap:is(Rows, lists:sort(DocList), "Returned correct rows").
206
207verify_rows_xattrs(Rows, PartitionId, HasXattrs) ->
208    Offset = (PartitionId * docs_per_partition()),
209    DocList = lists:zipwith(fun(Doc, I) ->
210        {[{<<"meta">>, {[{<<"deleted">>, false}, {<<"id">>, DocId}]}},
211          {<<"json">>, {[{<<"value">>, _Value}]}}]} = Doc,
212        Id = list_to_binary(integer_to_list(I)),
213        case HasXattrs of
214        true ->
215            {<<"\"", DocId/binary, "\"">>, DocId, <<"{\"xattr_key\":",Id/binary, "}">>};
216        false ->
217            {<<"\"", DocId/binary, "\"">>, DocId, <<"{}">>}
218        end
219    end, create_docs(1 + Offset, Offset + docs_per_partition(), false),
220         lists:seq(1+Offset, Offset + docs_per_partition())),
221    etap:is(Rows, lists:sort(DocList), "Returned correct rows").
222
223
224query_map_view(ViewName) ->
225    etap:diag("Querying map view " ++ binary_to_list(ddoc_id()) ++ "/" ++
226        binary_to_list(ViewName)),
227    Req = #set_view_group_req{
228        stale = false,
229        category = dev
230    },
231    {ok, View, Group, _} = couch_set_view:get_map_view(
232        test_set_name(), ddoc_id(), ViewName, Req),
233
234    FoldFun = fun({{{json, Key}, DocId}, {_PartId, {json, Value}}}, _, Acc) ->
235        {ok, [{Key, DocId, Value} | Acc]}
236    end,
237    ViewArgs = #view_query_args{
238        run_reduce = false,
239        view_name = ViewName
240    },
241    {ok, _, Rows} = couch_set_view:fold(Group, View, FoldFun, [], ViewArgs),
242    couch_set_view:release_group(Group),
243    {ok, lists:reverse(Rows)}.
244
245setup_test_vb() ->
246    couch_set_view_test_util:delete_set_dbs(test_set_name(),
247        num_set_partitions()),
248    couch_set_view_test_util:create_set_dbs(test_set_name(),
249        num_set_partitions()),
250
251    DDoc = {[
252        {<<"meta">>, {[{<<"id">>, ddoc_id()}]}},
253        {<<"json">>, {[
254            {<<"views">>, {[
255                {<<"test">>, {[
256                    {<<"map">>, <<"function(doc, meta)
257                        { emit(meta.id, meta.vb); }">>}
258                ]}}
259            ]}}
260        ]}}
261    ]},
262    populate_set(DDoc, false).
263
264setup_test_seq() ->
265    couch_set_view_test_util:delete_set_dbs(test_set_name(),
266        num_set_partitions()),
267    couch_set_view_test_util:create_set_dbs(test_set_name(),
268        num_set_partitions()),
269
270    DDoc = {[
271        {<<"meta">>, {[{<<"id">>, ddoc_id()}]}},
272        {<<"json">>, {[
273            {<<"views">>, {[
274                {<<"test">>, {[
275                    {<<"map">>, <<"function(doc, meta)
276                        { emit(meta.id, meta.seq); }">>}
277                ]}}
278            ]}}
279        ]}}
280    ]},
281    populate_set(DDoc, false).
282
283setup_test_xattrs(HasXattrs, Deleted) ->
284    couch_set_view_test_util:delete_set_dbs(test_set_name(),
285        num_set_partitions()),
286    couch_set_view_test_util:create_set_dbs(test_set_name(),
287        num_set_partitions()),
288
289    DDoc = {[
290        {<<"meta">>, {[{<<"id">>, ddoc_id()}]}},
291        {<<"json">>, {[
292            {<<"views">>, {[
293                {<<"test">>, {[
294                    {<<"map">>, <<"function(doc, meta)
295                        { emit(meta.id, meta.xattrs); }">>}
296                ]}}
297            ]}}
298        ]}}
299    ]},
300    populate_set_xattrs(DDoc, HasXattrs, Deleted).
301
302create_docs(From, To, Deleted) ->
303    lists:map(
304        fun(I) ->
305            {[
306                {<<"meta">>, {[{<<"deleted">>, Deleted}, {<<"id">>, iolist_to_binary(["doc",
307                    integer_to_list(I)])}]}},
308                {<<"json">>, {[{<<"value">>, I}]}}
309            ]}
310        end,
311        lists:seq(From, To)).
312
313create_docs_xattrs(From, To, HasXattrs, Deleted) ->
314    lists:map(
315        fun(I) ->
316            {
317                case HasXattrs of
318                true ->
319                [{<<"meta">>, {[{<<"id">>, iolist_to_binary(["doc",
320                    integer_to_list(I)])}]}},
321                    {<<"json">>, {[{<<"xattrs">>, I}, {<<"deleted">>, Deleted}, {<<"value">>, I}]}}];
322                false ->
323                [{<<"meta">>, {[{<<"deleted">>, Deleted}, {<<"id">>, iolist_to_binary(["doc",
324                    integer_to_list(I)])}]}},
325                    {<<"json">>, {[{<<"value">>, I}]}}]
326                end
327            }
328        end,
329        lists:seq(From, To)).
330
331
332update_docs(From, To) ->
333    lists:map(
334        fun(I) ->
335            {[
336                {<<"meta">>, {[{<<"id">>, iolist_to_binary(["doc",
337                    integer_to_list(I)])}]}},
338                {<<"json">>, {[{<<"value">>, 2*I}]}}
339            ]}
340        end,
341        lists:seq(From, To)).
342
343update_docs() ->
344    DocList = update_docs(1, num_docs()),
345    ok = couch_set_view_test_util:populate_set_sequentially(
346        test_set_name(),
347        lists:seq(0, num_set_partitions() - 1),
348        DocList).
349
350populate_set(DDoc, Deleted) ->
351    etap:diag("Populating the " ++ integer_to_list(num_set_partitions()) ++
352        " databases with " ++ integer_to_list(num_docs()) ++ " documents"),
353    ok = couch_set_view_test_util:update_ddoc(test_set_name(), DDoc),
354    DocList = create_docs(1, num_docs(), Deleted),
355    ok = couch_set_view_test_util:populate_set_sequentially(
356        test_set_name(),
357        lists:seq(0, num_set_partitions() - 1),
358        DocList).
359
360populate_set_xattrs(DDoc, HasXattrs, Deleted) ->
361    etap:diag("Populating the " ++ integer_to_list(num_set_partitions()) ++
362        " databases with " ++ integer_to_list(num_docs()) ++ " documents"),
363    ok = couch_set_view_test_util:update_ddoc(test_set_name(), DDoc),
364    DocList = create_docs_xattrs(1, num_docs(), HasXattrs, Deleted),
365    ok = couch_set_view_test_util:populate_set_sequentially(
366        test_set_name(),
367        lists:seq(0, num_set_partitions() - 1),
368        DocList).
369
370
371configure_view_group(DDocId, PartitionId) ->
372    etap:diag("Configuring view group"),
373    try
374        ok = couch_set_view_dev:define_group(
375            mapreduce_view, test_set_name(), DDocId, PartitionId)
376    catch _:Error ->
377        Error
378    end.
379
380shutdown_group() ->
381    GroupPid = couch_set_view:get_group_pid(
382        mapreduce_view, test_set_name(), ddoc_id(), dev),
383    couch_set_view_test_util:delete_set_dbs(test_set_name(),
384        num_set_partitions()),
385    MonRef = erlang:monitor(process, GroupPid),
386    receive
387    {'DOWN', MonRef, _, _, _} ->
388        ok
389    after 10000 ->
390        etap:bail("Timeout waiting for group shutdown")
391    end.
392