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