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    [] ->
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    #spatial_args{
93        limit = Limit,
94        skip = Skip,
95        range = Range
96    } = Args,
97    Acc = #acc{
98        limit = Limit,
99        skip = Skip,
100        callback = Callback,
101        user_acc = UserAcc,
102        update_seq = View#spatial.update_seq
103    },
104    Acc2 = fold(View, fun do_fold/2, Acc, Range),
105    finish_fold(Acc2, []).
106
107
108fold(Index, FoldFun, InitAcc, Range) ->
109    #spatial{
110       vtree = #vtree{
111           root = Root
112       } = Vt
113    } = Index,
114
115    WrapperFun = fun(Node, Acc) ->
116        % NOTE vmx 2012-11-28: in Apache CouchDB the body is stored as
117        %     Erlang terms
118        Value = binary_to_term(Node#kv_node.body),
119        Expanded = couch_spatial_util:expand_dups(
120            [Node#kv_node{body=Value}], []),
121        fold_fun(FoldFun, Expanded, Acc)
122    end,
123
124    case Root of
125    nil ->
126        InitAcc;
127    % Use the original MBB for comparison as the key is not necessarily
128    % set. The original MBB is good enough for this check as it will have
129    % the same dimesionality.
130    #kp_node{mbb_orig = MbbOrig} ->
131        case Range of
132        [] ->
133            vtree_search:all(Index#spatial.vtree, WrapperFun, InitAcc);
134        _ when length(Range) =/= length(MbbOrig) ->
135            throw(list_to_binary(io_lib:format(
136                "The query range must have the same dimensionality as "
137                "the index. Your range was `~10000p`, but the index has a "
138                "dimensionality of `~p`.", [Range, length(MbbOrig)])));
139        Range ->
140            vtree_search:search(Vt, [Range], WrapperFun, InitAcc)
141        end
142    end.
143
144
145% This is like a normal fold that can be interrupted in the middle
146fold_fun(_Fun, [], Acc) ->
147    {ok, Acc};
148fold_fun(Fun, [KV|Rest], Acc) ->
149    case Fun(KV, Acc) of
150    {ok, Acc2} ->
151        fold_fun(Fun, Rest, Acc2);
152    {stop, Acc2} ->
153        {stop, Acc2}
154    end.
155
156do_fold(_Kv, #acc{skip=N}=Acc) when N > 0 ->
157    {ok, Acc#acc{skip=N-1, last_go=ok}};
158do_fold(Kv, #acc{meta_sent=false}=Acc) ->
159    #acc{
160        callback = Callback,
161        user_acc = UserAcc,
162        update_seq = UpdateSeq
163    } = Acc,
164    Meta = make_meta(UpdateSeq, []),
165    {Go, UserAcc2} = Callback(Meta, UserAcc),
166    Acc2 = Acc#acc{meta_sent=true, user_acc=UserAcc2, last_go=Go},
167    case Go of
168        ok -> do_fold(Kv, Acc2);
169        stop -> {stop, Acc2}
170    end;
171do_fold(_Kv, #acc{limit=0}=Acc) ->
172    {stop, Acc};
173do_fold(#kv_node{}=Node, Acc) ->
174    #kv_node{
175        key = Mbb,
176        docid = DocId,
177        geometry = Geom,
178        body = Value
179    } = Node,
180    #acc{
181        limit = Limit,
182        callback = Callback,
183        user_acc = UserAcc
184    } = Acc,
185    Row = {Mbb, DocId, Geom, Value},
186    {Go, UserAcc2} = Callback({row, Row}, UserAcc),
187    {Go, Acc#acc{
188        limit = Limit-1,
189        user_acc = UserAcc2,
190        last_go = Go
191    }}.
192
193
194finish_fold(#acc{last_go=ok}=Acc, ExtraMeta) ->
195    #acc{
196        callback = Callback,
197        user_acc = UserAcc,
198        update_seq = UpdateSeq,
199        meta_sent = MetaSent
200    }=Acc,
201    % Possible send meta info
202    Meta = make_meta(UpdateSeq, ExtraMeta),
203    {Go, UserAcc1} = case MetaSent of
204        false -> Callback(Meta, UserAcc);
205        _ -> {ok, UserAcc}
206    end,
207    % Notify callback that the fold is complete.
208    {_, UserAcc2} = case Go of
209        ok -> Callback(complete, UserAcc1);
210        _ -> {ok, UserAcc1}
211    end,
212    {ok, UserAcc2};
213finish_fold(Acc, _ExtraMeta) ->
214    {ok, Acc#acc.user_acc}.
215
216
217make_meta(UpdateSeq, Base) ->
218    {meta, Base ++ [{update_seq, UpdateSeq}]}.
219