Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 419 lines (387 sloc) 13.585 kB
7fe84eb @janl trailing whitespace pedantry
janl authored
1 % Licensed under the Apache License, Version 2.0 (the "License");
2 % you may not use this file except in compliance with the License.
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
3 %
4 % You may obtain a copy of the License at
5 % http://www.apache.org/licenses/LICENSE-2.0
6 %
7fe84eb @janl trailing whitespace pedantry
janl authored
7 % Unless required by applicable law or agreed to in writing,
8 % software distributed under the License is distributed on an
9 % "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
10 % either express or implied.
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
11 %
12 % See the License for the specific language governing permissions
7fe84eb @janl trailing whitespace pedantry
janl authored
13 % and limitations under the License.
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
14 %
15 % This file drew much inspiration from erlview, which was written by and
16 % copyright Michael McDaniel [http://autosys.us], and is also under APL 2.0
17 %
18 %
19 % This module provides the smallest possible native view-server.
20 % With this module in-place, you can add the following to your couch INI files:
21 % [native_query_servers]
22 % erlang={couch_native_process, start_link, []}
23 %
24 % Which will then allow following example map function to be used:
25 %
26 % fun({Doc}) ->
27 % % Below, we emit a single record - the _id as key, null as value
e7c7dc1 @rnewson fix misleading code sample in comment (thanks vmx).
rnewson authored
28 % DocId = couch_util:get_value(<<"_id">>, Doc, null),
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
29 % Emit(DocId, null)
30 % end.
31 %
32 % which should be roughly the same as the javascript:
33 % emit(doc._id, null);
34 %
7fe84eb @janl trailing whitespace pedantry
janl authored
35 % This module exposes enough functions such that a native erlang server can
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
36 % act as a fully-fleged view server, but no 'helper' functions specifically
37 % for simplifying your erlang view code. It is expected other third-party
38 % extensions will evolve which offer useful layers on top of this view server
39 % to help simplify your view code.
40 -module(couch_native_process).
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
41 -behaviour(gen_server).
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
42
8ef616c @janl fix unused variable warnings, remove unused code.
janl authored
43 -export([start_link/0,init/1,terminate/2,handle_call/3,handle_cast/2,code_change/3,
44 handle_info/2]).
a851c6e @fdmanana More efficient communication with the view server
fdmanana authored
45 -export([set_timeout/2, prompt/2, prompt_many/2]).
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
46
47 -define(STATE, native_proc_state).
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
48 -record(evstate, {ddocs, funs=[], query_config=[], list_pid=nil, timeout=5000}).
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
49
50 -include("couch_db.hrl").
51
52 start_link() ->
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
53 gen_server:start_link(?MODULE, [], []).
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
54
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
55 % this is a bit messy, see also couch_query_servers handle_info
56 % stop(_Pid) ->
57 % ok.
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
58
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
59 set_timeout(Pid, TimeOut) ->
60 gen_server:call(Pid, {set_timeout, TimeOut}).
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
61
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
62 prompt(Pid, Data) when is_list(Data) ->
63 gen_server:call(Pid, {prompt, Data}).
64
a851c6e @fdmanana More efficient communication with the view server
fdmanana authored
65 prompt_many(Pid, DataList) ->
66 prompt_many(Pid, DataList, []).
67
68 prompt_many(_Pid, [], Acc) ->
69 {ok, lists:reverse(Acc)};
70 prompt_many(Pid, [Data | Rest], Acc) ->
71 Result = prompt(Pid, Data),
72 prompt_many(Pid, Rest, [Result | Acc]).
73
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
74 % gen_server callbacks
75 init([]) ->
76 {ok, #evstate{ddocs=dict:new()}}.
77
78 handle_call({set_timeout, TimeOut}, _From, State) ->
79 {reply, ok, State#evstate{timeout=TimeOut}};
80
81 handle_call({prompt, Data}, _From, State) ->
82 ?LOG_DEBUG("Prompt native qs: ~s",[?JSON_ENCODE(Data)]),
83 {NewState, Resp} = try run(State, to_binary(Data)) of
84 {S, R} -> {S, R}
85 catch
86 throw:{error, Why} ->
87 {State, [<<"error">>, Why, Why]}
88 end,
8ef616c @janl fix unused variable warnings, remove unused code.
janl authored
89
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
90 case Resp of
91 {error, Reason} ->
92 Msg = io_lib:format("couch native server error: ~p", [Reason]),
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
93 {reply, [<<"error">>, <<"native_query_server">>, list_to_binary(Msg)], NewState};
94 [<<"error">> | Rest] ->
8ef616c @janl fix unused variable warnings, remove unused code.
janl authored
95 % Msg = io_lib:format("couch native server error: ~p", [Rest]),
96 % TODO: markh? (jan)
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
97 {reply, [<<"error">> | Rest], NewState};
98 [<<"fatal">> | Rest] ->
8ef616c @janl fix unused variable warnings, remove unused code.
janl authored
99 % Msg = io_lib:format("couch native server error: ~p", [Rest]),
100 % TODO: markh? (jan)
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
101 {stop, fatal, [<<"error">> | Rest], NewState};
102 Resp ->
103 {reply, Resp, NewState}
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
104 end.
105
c1ba70c Deterministic/synchronous shutdown code.
Damien F. Katz authored
106 handle_cast(foo, State) -> {noreply, State}.
107 handle_info({'EXIT',_,normal}, State) -> {noreply, State};
108 handle_info({'EXIT',_,Reason}, State) ->
109 {stop, Reason, State}.
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
110 terminate(_Reason, _State) -> ok.
111 code_change(_OldVersion, State, _Extra) -> {ok, State}.
112
113 run(#evstate{list_pid=Pid}=State, [<<"list_row">>, Row]) when is_pid(Pid) ->
114 Pid ! {self(), list_row, Row},
115 receive
116 {Pid, chunks, Data} ->
117 {State, [<<"chunks">>, Data]};
118 {Pid, list_end, Data} ->
119 receive
120 {'EXIT', Pid, normal} -> ok
121 after State#evstate.timeout ->
122 throw({timeout, list_cleanup})
123 end,
124 process_flag(trap_exit, erlang:get(do_trap)),
125 {State#evstate{list_pid=nil}, [<<"end">>, Data]}
126 after State#evstate.timeout ->
127 throw({timeout, list_row})
128 end;
129 run(#evstate{list_pid=Pid}=State, [<<"list_end">>]) when is_pid(Pid) ->
130 Pid ! {self(), list_end},
131 Resp =
132 receive
133 {Pid, list_end, Data} ->
134 receive
135 {'EXIT', Pid, normal} -> ok
136 after State#evstate.timeout ->
137 throw({timeout, list_cleanup})
138 end,
139 [<<"end">>, Data]
140 after State#evstate.timeout ->
141 throw({timeout, list_end})
142 end,
143 process_flag(trap_exit, erlang:get(do_trap)),
144 {State#evstate{list_pid=nil}, Resp};
145 run(#evstate{list_pid=Pid}=State, _Command) when is_pid(Pid) ->
146 {State, [<<"error">>, list_error, list_error]};
147 run(#evstate{ddocs=DDocs}, [<<"reset">>]) ->
148 {#evstate{ddocs=DDocs}, true};
149 run(#evstate{ddocs=DDocs}, [<<"reset">>, QueryConfig]) ->
150 {#evstate{ddocs=DDocs, query_config=QueryConfig}, true};
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
151 run(#evstate{funs=Funs}=State, [<<"add_fun">> , BinFunc]) ->
152 FunInfo = makefun(State, BinFunc),
153 {State#evstate{funs=Funs ++ [FunInfo]}, true};
154 run(State, [<<"map_doc">> , Doc]) ->
155 Resp = lists:map(fun({Sig, Fun}) ->
156 erlang:put(Sig, []),
157 Fun(Doc),
158 lists:reverse(erlang:get(Sig))
159 end, State#evstate.funs),
160 {State, Resp};
161 run(State, [<<"reduce">>, Funs, KVs]) ->
162 {Keys, Vals} =
163 lists:foldl(fun([K, V], {KAcc, VAcc}) ->
164 {[K | KAcc], [V | VAcc]}
165 end, {[], []}, KVs),
166 Keys2 = lists:reverse(Keys),
167 Vals2 = lists:reverse(Vals),
168 {State, catch reduce(State, Funs, Keys2, Vals2, false)};
169 run(State, [<<"rereduce">>, Funs, Vals]) ->
170 {State, catch reduce(State, Funs, null, Vals, true)};
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
171 run(#evstate{ddocs=DDocs}=State, [<<"ddoc">>, <<"new">>, DDocId, DDoc]) ->
172 DDocs2 = store_ddoc(DDocs, DDocId, DDoc),
173 {State#evstate{ddocs=DDocs2}, true};
174 run(#evstate{ddocs=DDocs}=State, [<<"ddoc">>, DDocId | Rest]) ->
175 DDoc = load_ddoc(DDocs, DDocId),
176 ddoc(State, DDoc, Rest);
177 run(_, Unknown) ->
178 ?LOG_ERROR("Native Process: Unknown command: ~p~n", [Unknown]),
179 throw({error, unknown_command}).
180
181 ddoc(State, {DDoc}, [FunPath, Args]) ->
182 % load fun from the FunPath
183 BFun = lists:foldl(fun
184 (Key, {Props}) when is_list(Props) ->
6ae13b5 @fdmanana Removing ?getv macros.
fdmanana authored
185 couch_util:get_value(Key, Props, nil);
8ef616c @janl fix unused variable warnings, remove unused code.
janl authored
186 (_Key, Fun) when is_binary(Fun) ->
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
187 Fun;
8ef616c @janl fix unused variable warnings, remove unused code.
janl authored
188 (_Key, nil) ->
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
189 throw({error, not_found});
8ef616c @janl fix unused variable warnings, remove unused code.
janl authored
190 (_Key, _Fun) ->
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
191 throw({error, malformed_ddoc})
192 end, {DDoc}, FunPath),
193 ddoc(State, makefun(State, BFun, {DDoc}), FunPath, Args).
194
195 ddoc(State, {_, Fun}, [<<"validate_doc_update">>], Args) ->
196 {State, (catch apply(Fun, Args))};
197 ddoc(State, {_, Fun}, [<<"filters">>|_], [Docs, Req]) ->
45837ab @janl show runtime errors in native list functions
janl authored
198 FilterFunWrapper = fun(Doc) ->
199 case catch Fun(Doc, Req) of
200 true -> true;
201 false -> false;
202 {'EXIT', Error} -> ?LOG_ERROR("~p", [Error])
203 end
204 end,
205 Resp = lists:map(FilterFunWrapper, Docs),
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
206 {State, [true, Resp]};
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
207 ddoc(State, {_, Fun}, [<<"shows">>|_], Args) ->
208 Resp = case (catch apply(Fun, Args)) of
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
209 FunResp when is_list(FunResp) ->
210 FunResp;
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
211 {FunResp} ->
212 [<<"resp">>, {FunResp}];
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
213 FunResp ->
214 FunResp
215 end,
216 {State, Resp};
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
217 ddoc(State, {_, Fun}, [<<"updates">>|_], Args) ->
218 Resp = case (catch apply(Fun, Args)) of
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
219 [JsonDoc, JsonResp] ->
220 [<<"up">>, JsonDoc, JsonResp]
221 end,
222 {State, Resp};
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
223 ddoc(State, {Sig, Fun}, [<<"lists">>|_], Args) ->
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
224 Self = self(),
225 SpawnFun = fun() ->
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
226 LastChunk = (catch apply(Fun, Args)),
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
227 case start_list_resp(Self, Sig) of
228 started ->
229 receive
230 {Self, list_row, _Row} -> ignore;
231 {Self, list_end} -> ignore
232 after State#evstate.timeout ->
233 throw({timeout, list_cleanup_pid})
234 end;
235 _ ->
236 ok
237 end,
238 LastChunks =
239 case erlang:get(Sig) of
240 undefined -> [LastChunk];
241 OtherChunks -> [LastChunk | OtherChunks]
242 end,
243 Self ! {self(), list_end, lists:reverse(LastChunks)}
244 end,
245 erlang:put(do_trap, process_flag(trap_exit, true)),
246 Pid = spawn_link(SpawnFun),
247 Resp =
248 receive
249 {Pid, start, Chunks, JsonResp} ->
250 [<<"start">>, Chunks, JsonResp]
251 after State#evstate.timeout ->
252 throw({timeout, list_start})
253 end,
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
254 {State#evstate{list_pid=Pid}, Resp}.
255
256 store_ddoc(DDocs, DDocId, DDoc) ->
257 dict:store(DDocId, DDoc, DDocs).
258 load_ddoc(DDocs, DDocId) ->
259 try dict:fetch(DDocId, DDocs) of
260 {DDoc} -> {DDoc}
261 catch
8ef616c @janl fix unused variable warnings, remove unused code.
janl authored
262 _:_Else -> throw({error, ?l2b(io_lib:format("Native Query Server missing DDoc with Id: ~s",[DDocId]))})
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
263 end.
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
264
265 bindings(State, Sig) ->
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
266 bindings(State, Sig, nil).
267 bindings(State, Sig, DDoc) ->
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
268 Self = self(),
269
270 Log = fun(Msg) ->
271 ?LOG_INFO(Msg, [])
272 end,
273
274 Emit = fun(Id, Value) ->
275 Curr = erlang:get(Sig),
276 erlang:put(Sig, [[Id, Value] | Curr])
277 end,
278
279 Start = fun(Headers) ->
280 erlang:put(list_headers, Headers)
281 end,
282
283 Send = fun(Chunk) ->
284 Curr =
285 case erlang:get(Sig) of
286 undefined -> [];
287 Else -> Else
288 end,
289 erlang:put(Sig, [Chunk | Curr])
290 end,
291
292 GetRow = fun() ->
293 case start_list_resp(Self, Sig) of
294 started ->
295 ok;
296 _ ->
297 Chunks =
298 case erlang:get(Sig) of
299 undefined -> [];
300 CurrChunks -> CurrChunks
301 end,
302 Self ! {self(), chunks, lists:reverse(Chunks)}
303 end,
304 erlang:put(Sig, []),
305 receive
306 {Self, list_row, Row} -> Row;
307 {Self, list_end} -> nil
308 after State#evstate.timeout ->
309 throw({timeout, list_pid_getrow})
310 end
311 end,
312
313 FoldRows = fun(Fun, Acc) -> foldrows(GetRow, Fun, Acc) end,
314
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
315 Bindings = [
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
316 {'Log', Log},
317 {'Emit', Emit},
318 {'Start', Start},
319 {'Send', Send},
320 {'GetRow', GetRow},
321 {'FoldRows', FoldRows}
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
322 ],
323 case DDoc of
8ef616c @janl fix unused variable warnings, remove unused code.
janl authored
324 {_Props} ->
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
325 Bindings ++ [{'DDoc', DDoc}];
326 _Else -> Bindings
327 end.
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
328
329 % thanks to erlview, via:
330 % http://erlang.org/pipermail/erlang-questions/2003-November/010544.html
331 makefun(State, Source) ->
d7d047f @kocolosk use crypto:md5 when available. thx fdmanana. Closes COUCHDB-757
kocolosk authored
332 Sig = couch_util:md5(Source),
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
333 BindFuns = bindings(State, Sig),
334 {Sig, makefun(State, Source, BindFuns)}.
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
335 makefun(State, Source, {DDoc}) ->
d7d047f @kocolosk use crypto:md5 when available. thx fdmanana. Closes COUCHDB-757
kocolosk authored
336 Sig = couch_util:md5(lists:flatten([Source, term_to_binary(DDoc)])),
ea3b115 @jchris move query server to a design-doc based protocol, closes COUCHDB-589
jchris authored
337 BindFuns = bindings(State, Sig, {DDoc}),
338 {Sig, makefun(State, Source, BindFuns)};
339 makefun(_State, Source, BindFuns) when is_list(BindFuns) ->
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
340 FunStr = binary_to_list(Source),
341 {ok, Tokens, _} = erl_scan:string(FunStr),
342 Form = case (catch erl_parse:parse_exprs(Tokens)) of
343 {ok, [ParsedForm]} ->
344 ParsedForm;
345 {error, {LineNum, _Mod, [Mesg, Params]}}=Error ->
346 io:format(standard_error, "Syntax error on line: ~p~n", [LineNum]),
347 io:format(standard_error, "~s~p~n", [Mesg, Params]),
348 throw(Error)
349 end,
350 Bindings = lists:foldl(fun({Name, Fun}, Acc) ->
351 erl_eval:add_binding(Name, Fun, Acc)
352 end, erl_eval:new_bindings(), BindFuns),
353 {value, Fun, _} = erl_eval:expr(Form, Bindings),
354 Fun.
355
356 reduce(State, BinFuns, Keys, Vals, ReReduce) ->
357 Funs = case is_list(BinFuns) of
358 true ->
359 lists:map(fun(BF) -> makefun(State, BF) end, BinFuns);
360 _ ->
361 [makefun(State, BinFuns)]
362 end,
363 Reds = lists:map(fun({_Sig, Fun}) ->
364 Fun(Keys, Vals, ReReduce)
365 end, Funs),
366 [true, Reds].
367
368 foldrows(GetRow, ProcRow, Acc) ->
369 case GetRow() of
370 nil ->
371 {ok, Acc};
372 Row ->
373 case (catch ProcRow(Row, Acc)) of
374 {ok, Acc2} ->
375 foldrows(GetRow, ProcRow, Acc2);
376 {stop, Acc2} ->
377 {ok, Acc2}
378 end
379 end.
380
381 start_list_resp(Self, Sig) ->
382 case erlang:get(list_started) of
383 undefined ->
384 Headers =
385 case erlang:get(list_headers) of
386 undefined -> {[{<<"headers">>, {[]}}]};
387 CurrHdrs -> CurrHdrs
388 end,
7fe84eb @janl trailing whitespace pedantry
janl authored
389 Chunks =
e10858c @jchris Introduces native Erlang query servers. Closes COUCHDB-377
jchris authored
390 case erlang:get(Sig) of
391 undefined -> [];
392 CurrChunks -> CurrChunks
393 end,
394 Self ! {self(), start, lists:reverse(Chunks), Headers},
395 erlang:put(list_started, true),
396 erlang:put(Sig, []),
397 started;
398 _ ->
399 ok
400 end.
4143b68 @davisp Munge all ErlJSON to use binaries.
davisp authored
401
402 to_binary({Data}) ->
403 Pred = fun({Key, Value}) ->
404 {to_binary(Key), to_binary(Value)}
405 end,
406 {lists:map(Pred, Data)};
407 to_binary(Data) when is_list(Data) ->
aef9dbd @kocolosk code improvements from tidier. Patch by Kostis Sagonas. COUCHDB-570
kocolosk authored
408 [to_binary(D) || D <- Data];
4143b68 @davisp Munge all ErlJSON to use binaries.
davisp authored
409 to_binary(null) ->
410 null;
411 to_binary(true) ->
412 true;
413 to_binary(false) ->
414 false;
415 to_binary(Data) when is_atom(Data) ->
416 list_to_binary(atom_to_list(Data));
417 to_binary(Data) ->
418 Data.
Something went wrong with that request. Please try again.