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