fixed a problem with deleted docs and the all_docss view
[couchdbimport.git] / src / CouchDb / couch_js.erl
blob20f02bba5347dbdadbca0c364b1e8dc397a65909
1 %% CouchDb
2 %% Copyright (C) 2006 Damien Katz
3 %%
4 %% This program is free software; you can redistribute it and/or
5 %% modify it under the terms of the GNU General Public License
6 %% as published by the Free Software Foundation; either version 2
7 %% of the License, or (at your option) any later version.
8 %%
9 %% This program is distributed in the hope that it will be useful,
10 %% but WITHOUT ANY WARRANTY; without even the implied warranty of
11 %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 %% GNU General Public License for more details.
13 %%
14 %% You should have received a copy of the GNU General Public License
15 %% along with this program; if not, write to the Free Software
16 %% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 -module(couch_js).
19 -behaviour(gen_server).
21 -export([start_link/1]).
23 -export([init/1, terminate/2, handle_call/3, handle_cast/2, handle_info/2,code_change/3,stop/0]).
24 -export([start_doc_map/1, map_docs/2, stop_doc_map/1]).
26 -export([test/0, test/1]).
28 -include("couch_db.hrl").
30 start_link(JsExec) ->
31 gen_server:start_link({local, couch_js}, couch_js, JsExec, []).
33 stop() ->
34 exit(whereis(couch_js), close).
36 readline(Port) ->
37 readline(Port, []).
39 readline(Port, Acc) ->
40 receive
41 {Port, {data, {noeol, Data}}} ->
42 readline(Port, [Data|Acc]);
43 {Port, {data, {eol, Data}}} ->
44 lists:flatten(lists:reverse(Acc, Data));
45 {Port, Err} ->
46 throw({map_process_error, Err})
47 end.
49 writeline(Port, String) ->
50 true = port_command(Port, String ++ "\n").
52 % send command and get a response.
53 prompt(Port, String) ->
54 writeline(Port, String),
55 readline(Port).
57 start_doc_map(Functions) ->
58 case gen_server:call(couch_js, get_port) of
59 {ok, Port} ->
60 link(Port);
61 {empty, Cmd} ->
62 couch_log:info("Spawing new js instance."),
63 Port = open_port({spawn, Cmd}, [stream, {line, 1000}, exit_status, hide, {cd, filename:dirname(Cmd)}])
64 end,
65 "true" = prompt(Port, "[\"reset\"]"),
66 % send the functions as json strings
67 lists:foreach(fun(FunctionSource) ->
68 case prompt(Port, "[\"add_map_fun\"," ++ cjson:encode(FunctionSource) ++ "]") of
69 "true" -> ok;
70 ErrorJsonString ->
71 ErrorString = cjson:decode(ErrorJsonString),
72 throw({error_compiling_map_function, ErrorString})
73 end
74 end,
75 Functions),
76 {ok, Port}.
78 map_docs(Port, Docs) ->
79 % send the documents
80 Results =
81 lists:map(
82 fun(Doc) ->
83 Json = cjson:encode(couch_doc:to_json_obj(Doc, false)),
84 ResultsStr = prompt(Port, "[\"map_doc\"," ++ Json ++ "]"),
85 Results = cjson:decode(ResultsStr),
86 lists:map(
87 fun(Result) ->
88 case obj_to_key_value(Result) of
89 {ok, KV} ->
90 [KV];
91 0 ->
92 []; % not a match
93 {obj, [{"multi", List}]} ->
94 % list of key values
95 lists:reverse(lists:map(
96 fun(Result2) ->
97 case obj_to_key_value(Result2) of
98 {ok, KV} ->
99 KV;
100 _ ->
101 {nil, Result2}
103 end,
104 tuple_to_list(List)));
105 ElseValue ->
106 [{nil, ElseValue}]
108 end,
109 tuple_to_list(Results))
110 end,
111 Docs),
112 {ok, Results}.
114 % returns {ok, {Key,Value}} if the input is an object with just a key and/or value
115 % member, otherwise returns the input unchanged
116 obj_to_key_value({obj, PropList}) ->
117 case PropList of
118 [{"key", Key}, {"value", Value}] ->
119 {ok, {Key, Value}};
120 [{"value", Value}, {"key", Key}] ->
121 {ok, {Key, Value}};
122 [{"key", Key}] ->
123 {ok, {Key, nil}};
124 [{"value", Value}] ->
125 {ok, {nil, Value}};
126 _ ->
127 {obj, PropList}
128 end;
129 obj_to_key_value(Other) ->
130 Other.
133 stop_doc_map(nil) ->
135 stop_doc_map(Port) ->
136 ok = gen_server:call(couch_js, {return_port, Port}),
137 true = unlink(Port),
140 init(Cmd) ->
141 Port = open_port({spawn, Cmd}, [stream, {line, 1000}, exit_status, hide, {cd, filename:dirname(Cmd)}]),
142 {ok, {Cmd, [Port]}}.
144 terminate(_Reason, _Server) ->
148 handle_call(get_port, {FromPid, _}, {Cmd, [Port|Rest]}) ->
149 true = port_connect(Port, FromPid),
150 true = unlink(Port),
151 {reply, {ok, Port}, {Cmd, Rest}};
152 handle_call(get_port, _From, {Cmd, []})->
153 {reply, {empty, Cmd}, {Cmd, []}};
154 handle_call({return_port, Port}, _From, {Cmd, Ports}) ->
155 true = port_connect(Port, self()),
156 {reply, ok, {Cmd, Ports ++ [Port]}}.
158 handle_cast(_Whatever, {Cmd, Ports}) ->
159 {noreply, {Cmd, Ports}}.
161 handle_info({Port, {exit_status, 0}}, {Cmd, Ports}) ->
162 {noreply, {Cmd, Ports -- [Port]}};
163 handle_info({Port, {exit_status, Status}}, {Cmd, Ports}) ->
164 couch_log:error("Abnormal shutdown of JS process (exit_status: ~w).", [Status]),
165 {noreply, {Cmd, Ports -- [Port]}};
166 handle_info(_Whatever, {Cmd, Ports}) ->
167 {noreply, {Cmd, Ports}}.
169 code_change(_OldVsn, State, _Extra) ->
170 {ok, State}.
172 test() ->
173 test("../js/js -f main.js").
175 test(Cmd) ->
176 start_link(Cmd),
177 {ok, DocMap} = start_doc_map(["function(doc) {if (doc[0] == 'a') return doc[1];}"]),
178 {ok, Results} = map_docs(DocMap, [#doc{body={"a", "b"}}, #doc{body={"c", "d"}},#doc{body={"a", "c"}}]),
179 io:format("Results: ~w~n", [Results]),
180 stop_doc_map(DocMap),