1#!/usr/bin/env escript
2%% -*- erlang -*-
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("../include/couch_spatial.hrl").
18-include_lib("couch_set_view/include/couch_set_view.hrl").
19
20
21% from couch_db.hrl
22-define(MIN_STR, <<>>).
23-define(MAX_STR, <<255>>).
24
25%-record(view_query_args, {
26%    start_key,
27%    end_key,
28%    start_docid = ?MIN_STR,
29%    end_docid = ?MAX_STR,
30%    direction = fwd,
31%    inclusive_end = true,
32%    limit = 10000000000,
33%    skip = 0,
34%    group_level = 0,
35%    view_type = nil,
36%    include_docs = false,
37%    conflicts = false,
38%    stale = false,
39%    multi_get = false,
40%    callback = nil,
41%    list = nil,
42%    run_reduce = true,
43%    keys = nil,
44%    view_name = nil,
45%    debug = false,
46%    filter = true,
47%    type = main
48%}).
49
50test_set_name() -> <<"couch_test_spatial_view_query_params">>.
51num_set_partitions() -> 4.
52ddoc_id() -> <<"_design/test">>.
53num_docs() -> 1024.  % keep it a multiple of num_set_partitions()
54
55
56main(_) ->
57    test_util:init_code_path(),
58
59    etap:plan(12),
60    case (catch test()) of
61        ok ->
62            etap:end_tests();
63        Other ->
64            etap:diag(io_lib:format("Test died abnormally: ~p", [Other])),
65            etap:bail(Other)
66    end,
67    %init:stop(),
68    %receive after infinity -> ok end,
69    ok.
70
71
72test() ->
73    spatial_test_util:start_server(test_set_name()),
74
75    etap:diag("Testing query parameters of spatial views"),
76
77    test_spatial_query_all(),
78    test_spatial_query_range(),
79    test_spatial_query_range_error(),
80
81    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
82    spatial_test_util:stop_server(),
83    ok.
84
85
86% Return just all the data
87test_spatial_query_all() ->
88    setup_test(),
89    ok = configure_spatial_group(ddoc_id()),
90
91    ViewArgs = #spatial_query_args{
92        %view_name = ViewName
93    },
94
95    {ok, Rows} = (catch query_spatial_view(<<"test">>, ViewArgs)),
96    etap:is(length(Rows), num_docs(),
97        "Got all view rows (" ++ integer_to_list(num_docs()) ++ ")"),
98    verify_rows(Rows),
99
100    shutdown_group().
101
102
103test_spatial_query_range() ->
104    setup_test(),
105    ok = configure_spatial_group(ddoc_id()),
106
107    Tests = [{[{740, 1500}, {300, 1765}, {80, 628}, {51, 457}],
108              283, "All dimensions limit the result"},
109             {[{631.482, 963.315}, {-10.8, 8576.5}, {83.1, 584.27}, {80, 300}],
110              84, "2nd dimension doesn't limit the result"},
111             {[{740, 1500}, {300, 1765}, {80, 80}, {51, 457}],
112              34, "3rd dimension is a collapsed to point"},
113             {[{740, 1500}, {300, 1765}, {80, 628}, {nil, nil}],
114              322, "4th dimension is wildcard"},
115             {[{740, 1500}, {300, 1765}, {80, 628}, {nil, 457}],
116              302, "4th dimension is open at start"},
117             {[{740, 1500}, {300, 1765}, {80, 628}, {51, nil}],
118              303, "4th dimension is open at end"},
119             {[{740, 1500}, {nil, nil}, {80, 628}, {51, nil}],
120              349, "4th dimension is open at end and 2nd is a wildcard"},
121             {[{nil, nil}, {nil, nil}, {nil, nil}, {nil, nil}],
122              num_docs(), "All dimensions are wildcards"}],
123    lists:foreach(fun({Range, Expected, Message}) ->
124                          query_for_expected_result(
125                            Range, Expected, Message)
126                  end, Tests),
127
128    shutdown_group().
129
130
131test_spatial_query_range_error() ->
132    setup_test(),
133    ok = configure_spatial_group(ddoc_id()),
134
135    Tests = [{[{1, 1}, {2, 2}, {3, 3}],
136              <<"The query range must have the same "
137                "dimensionality as the index. "
138                "Your range was `[{1,1},{2,2},{3,3}]`, "
139                "but the index has a dimensionality of `4`.">>,
140              "Only 3 dimensions given for a 4 dimensional index"},
141             {[{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}],
142              <<"The query range must have the same "
143                "dimensionality as the index. "
144                "Your range was `[{1,1},{2,2},{3,3},{4,4},{5,5}]`, "
145                "but the index has a dimensionality of `4`.">>,
146              "5 dimensions given for a 4 dimensional index"}],
147    lists:foreach(fun({Range, Expected, Message}) ->
148                          query_for_expected_error(
149                            Range, Expected, Message)
150                  end, Tests),
151
152    shutdown_group().
153
154
155query_for_expected_result(Range, Expected, Message) ->
156    ViewArgs = #spatial_query_args{ range = Range},
157    {ok, Rows} = (catch query_spatial_view(<<"test">>, ViewArgs)),
158    etap:is(length(Rows), Expected, Message).
159
160
161verify_rows(Rows) ->
162    DocList = lists:map(fun(Doc) ->
163        {[{<<"meta">>, {[{<<"id">>, DocId}]}},
164          {<<"json">>, {[{<<"value">>, Value} | _Rest]}}]} = Doc,
165        {DocId,
166         <<"\"val", (list_to_binary(integer_to_list(Value)))/binary, "\"">>}
167    end, create_docs(1, num_docs())),
168
169    RowsWithoutKey = [{DocId, Value} ||
170        {Key, DocId, {_PartId, Value, nil}} <- Rows],
171    etap:is(lists:sort(RowsWithoutKey), lists:sort(DocList),
172            "Returned correct rows").
173
174
175query_for_expected_error(Range, Error, Message) ->
176    ViewArgs = #spatial_query_args{range = Range},
177    etap:throws_ok(
178      fun() -> query_spatial_view(<<"test">>, ViewArgs) end,
179      Error,
180      Message).
181
182
183query_spatial_view(ViewName, ViewArgs) ->
184    etap:diag("Querying spatial view " ++ binary_to_list(ddoc_id()) ++ "/" ++
185        binary_to_list(ViewName)),
186    Req = #set_view_group_req{
187        stale = false
188    },
189    {ok, View, Group, _} = spatial_view:get_spatial_view(
190        test_set_name(), ddoc_id(), ViewName, Req),
191
192    FoldFun = fun({{Key, DocId}, Value}, Acc) ->
193        {ok, [{Key, DocId, Value} | Acc]}
194    end,
195    {ok, _, Rows} = couch_set_view:fold(Group, View, FoldFun, [], ViewArgs),
196    couch_set_view:release_group(Group),
197    {ok, lists:reverse(Rows)}.
198
199
200setup_test() ->
201    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
202    couch_set_view_test_util:create_set_dbs(test_set_name(), num_set_partitions()),
203
204    DDoc = {[
205        {<<"meta">>, {[{<<"id">>, ddoc_id()}]}},
206        {<<"json">>, {[
207            {<<"spatial">>, {[
208                {<<"test">>, <<"function(doc, meta) { "
209                               "emit([[doc.min, doc.max], "
210                               "[doc.min2, doc.max2], "
211                               "[doc.min3, doc.max3], "
212                               "[doc.min4, doc.max4]], "
213                               "'val'+doc.value); }">>}
214            ]}}
215        ]}}
216    ]},
217    populate_set(DDoc).
218
219
220create_docs(From, To) ->
221    rand:seed(exrop, {91, 1, 11}),
222    lists:map(
223        fun(I) ->
224            RandomMin = rand:uniform(2000),
225            RandomMax = RandomMin + rand:uniform(167),
226            RandomMin2 = rand:uniform(1769),
227            RandomMax2 = RandomMin2 + rand:uniform(132),
228            RandomMin3 = rand:uniform(651),
229            RandomMax3 = RandomMin3 + rand:uniform(201),
230            RandomMin4 = rand:uniform(482),
231            RandomMax4 = RandomMin4 + rand:uniform(26),
232            {[
233              {<<"meta">>, {[{<<"id">>, iolist_to_binary(["doc", integer_to_list(I)])}]}},
234              {<<"json">>, {[
235                             {<<"value">>, I},
236                             {<<"min">>, RandomMin},
237                             {<<"max">>, RandomMax},
238                             {<<"min2">>, RandomMin2},
239                             {<<"max2">>, RandomMax2},
240                             {<<"min3">>, RandomMin3},
241                             {<<"max3">>, RandomMax3},
242                             {<<"min4">>, RandomMin4},
243                             {<<"max4">>, RandomMax4}
244                            ]}}
245            ]}
246        end,
247        lists:seq(From, To)).
248
249
250populate_set(DDoc) ->
251    etap:diag("Populating the " ++ integer_to_list(num_set_partitions()) ++
252        " databases with " ++ integer_to_list(num_docs()) ++ " documents"),
253    ok = couch_set_view_test_util:update_ddoc(test_set_name(), DDoc),
254    DocList = create_docs(1, num_docs()),
255    ok = couch_set_view_test_util:populate_set_sequentially(
256        test_set_name(),
257        lists:seq(0, num_set_partitions() - 1),
258        DocList).
259
260
261configure_spatial_group(DDocId) ->
262    etap:diag("Configuring spatial view group"),
263    Params = #set_view_params{
264        max_partitions = num_set_partitions(),
265        active_partitions = lists:seq(0, num_set_partitions()-1),
266        passive_partitions = [],
267        use_replica_index = false
268    },
269    try
270        ok = couch_set_view:define_group(
271            spatial_view, test_set_name(), DDocId, Params)
272    catch _:Error ->
273        Error
274    end.
275
276
277% A clean shutdown is not implemented yet, it will come in future commits
278shutdown_group() ->
279    GroupPid = couch_set_view:get_group_pid(
280        spatial_view, test_set_name(), ddoc_id(), prod),
281    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
282    MonRef = erlang:monitor(process, GroupPid),
283    receive
284    {'DOWN', MonRef, _, _, _} ->
285        ok
286    after 10000 ->
287        etap:bail("Timeout waiting for group shutdown")
288    end.
289