1% Licensed under the Apache License, Version 2.0 (the "License"); you may not
2% use this file except in compliance with the License. You may obtain a copy of
3% the License at
4%
5%   http://www.apache.org/licenses/LICENSE-2.0
6%
7% Unless required by applicable law or agreed to in writing, software
8% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10% License for the specific language governing permissions and limitations under
11% the License.
12
13-module(couch_spatial).
14
15-export([query_view/6, query_view_count/4]).
16-export([get_info/2]).
17-export([compact/2, compact/3, cancel_compaction/2]).
18-export([cleanup/1]).
19
20-include("couch_db.hrl").
21-include("couch_spatial.hrl").
22-include_lib("vtree/include/vtree.hrl").
23
24-record(acc, {
25    meta_sent = false,
26    offset,
27    limit,
28    skip,
29    callback,
30    user_acc,
31    last_go = ok,
32    update_seq = 0
33}).
34
35
36query_view(Db, DDoc, ViewName, Args, Callback, Acc0) ->
37    {ok, View, Sig, Args2} = couch_spatial_util:get_view(
38        Db, DDoc, ViewName, Args),
39    {ok, Acc} = case Args#spatial_args.preflight_fun of
40        PFFun when is_function(PFFun, 2) -> PFFun(Sig, Acc0);
41        _ -> {ok, Acc0}
42    end,
43    spatial_fold(View, Args2, Callback, Acc).
44
45
46query_view_count(Db, DDoc, ViewName, Args) ->
47    {ok, View, _, Args2} = couch_spatial_util:get_view(
48        Db, DDoc, ViewName, Args),
49
50    case Args2#spatial_args.range of
51    nil ->
52        vtree_search:count_all(View#spatial.vtree);
53    Mbb ->
54        vtree_search:count_search(View#spatial.vtree, [Mbb])
55    end.
56
57
58get_info(Db, DDoc) ->
59    {ok, Pid} = couch_index_server:get_index(couch_spatial_index, Db, DDoc),
60    couch_index:get_info(Pid).
61
62
63compact(Db, DDoc) ->
64    compact(Db, DDoc, []).
65
66
67compact(Db, DDoc, Opts) ->
68    {ok, Pid} = couch_index_server:get_index(couch_spatial_index, Db, DDoc),
69    couch_index:compact(Pid, Opts).
70
71
72cancel_compaction(Db, DDoc) ->
73    {ok, IPid} = couch_index_server:get_index(couch_spatial_index, Db, DDoc),
74    {ok, CPid} = couch_index:get_compactor_pid(IPid),
75    ok = couch_index_compactor:cancel(CPid),
76
77    % Cleanup the compaction file if it exists
78    {ok, State} = couch_index:get_state(IPid, 0),
79    #spatial_state{
80        sig = Sig,
81        db_name = DbName
82    } = State,
83    couch_spatial_util:delete_compaction_file(DbName, Sig),
84    ok.
85
86
87cleanup(Db) ->
88    couch_spatial_cleanup:run(Db).
89
90
91spatial_fold(View, Args, Callback, UserAcc) ->
92    % TODO vmx 2016-06-10: Remove `bounds` completely
93    #spatial_args{
94        limit = Limit,
95        skip = Skip,
96        bounds = Bounds,
97        range = Range
98    } = Args,
99    Acc = #acc{
100        limit = Limit,
101        skip = Skip,
102        callback = Callback,
103        user_acc = UserAcc,
104        update_seq = View#spatial.update_seq
105    },
106    Acc2 = fold(View, fun do_fold/2, Acc, Range, Bounds),
107    finish_fold(Acc2, []).
108
109
110fold(Index, FoldFun, InitAcc, Range, _Bounds) ->
111    #spatial{
112       vtree = #vtree{
113           root = Root
114       } = Vt
115    } = Index,
116
117    WrapperFun = fun(Node, Acc) ->
118        % NOTE vmx 2012-11-28: in Apache CouchDB the body is stored as
119        %     Erlang terms
120        Value = binary_to_term(Node#kv_node.body),
121        Expanded = couch_spatial_util:expand_dups(
122            [Node#kv_node{body=Value}], []),
123        fold_fun(FoldFun, Expanded, Acc)
124    end,
125
126    case Root of
127    nil ->
128        InitAcc;
129    #kp_node{key = Key} ->
130        case Range of
131        [] ->
132            vtree_search:all(Index#spatial.vtree, WrapperFun, InitAcc);
133        _ when length(Range) =/= length(Key) ->
134            throw(<<"The query range must have the same "
135                "dimensionality as the index.">>);
136        Range ->
137            vtree_search:search(Vt, [Range], WrapperFun, InitAcc)
138        end
139    end.
140
141
142% This is like a normal fold that can be interrupted in the middle
143fold_fun(_Fun, [], Acc) ->
144    {ok, Acc};
145fold_fun(Fun, [KV|Rest], Acc) ->
146    case Fun(KV, Acc) of
147    {ok, Acc2} ->
148        fold_fun(Fun, Rest, Acc2);
149    {stop, Acc2} ->
150        {stop, Acc2}
151    end.
152
153do_fold(_Kv, #acc{skip=N}=Acc) when N > 0 ->
154    {ok, Acc#acc{skip=N-1, last_go=ok}};
155do_fold(Kv, #acc{meta_sent=false}=Acc) ->
156    #acc{
157        callback = Callback,
158        user_acc = UserAcc,
159        update_seq = UpdateSeq
160    } = Acc,
161    Meta = make_meta(UpdateSeq, []),
162    {Go, UserAcc2} = Callback(Meta, UserAcc),
163    Acc2 = Acc#acc{meta_sent=true, user_acc=UserAcc2, last_go=Go},
164    case Go of
165        ok -> do_fold(Kv, Acc2);
166        stop -> {stop, Acc2}
167    end;
168do_fold(_Kv, #acc{limit=0}=Acc) ->
169    {stop, Acc};
170do_fold(#kv_node{}=Node, Acc) ->
171    #kv_node{
172        key = Mbb,
173        docid = DocId,
174        geometry = Geom,
175        body = Value
176    } = Node,
177    #acc{
178        limit = Limit,
179        callback = Callback,
180        user_acc = UserAcc
181    } = Acc,
182    Row = {Mbb, DocId, Geom, Value},
183    {Go, UserAcc2} = Callback({row, Row}, UserAcc),
184    {Go, Acc#acc{
185        limit = Limit-1,
186        user_acc = UserAcc2,
187        last_go = Go
188    }}.
189
190
191finish_fold(#acc{last_go=ok}=Acc, ExtraMeta) ->
192    #acc{
193        callback = Callback,
194        user_acc = UserAcc,
195        update_seq = UpdateSeq,
196        meta_sent = MetaSent
197    }=Acc,
198    % Possible send meta info
199    Meta = make_meta(UpdateSeq, ExtraMeta),
200    {Go, UserAcc1} = case MetaSent of
201        false -> Callback(Meta, UserAcc);
202        _ -> {ok, UserAcc}
203    end,
204    % Notify callback that the fold is complete.
205    {_, UserAcc2} = case Go of
206        ok -> Callback(complete, UserAcc1);
207        _ -> {ok, UserAcc1}
208    end,
209    {ok, UserAcc2};
210finish_fold(Acc, _ExtraMeta) ->
211    {ok, Acc#acc.user_acc}.
212
213
214make_meta(UpdateSeq, Base) ->
215    {meta, Base ++ [{update_seq, UpdateSeq}]}.
216