2 %% Copyright (C) 2006 Damien Katz
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.
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.
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_fabric
).
19 -behaviour(gen_server
).
21 -include("couch_db.hrl").
23 -export([start_link
/1, test
/0]).
25 -export([init
/1, terminate
/2, handle_call
/3, handle_cast
/2, handle_info
/2,code_change
/3,stop
/0]).
26 -export([check
/2, check_all
/1, table_compute
/2, table_columns
/1]).
28 -define(ERR_HANDLE
, {Port
, {exit_status
, Status
}} -> {stop
, {unknown_error
, Status
}, {unknown_error
, Status
}, Port
}).
30 start_link(FabricExec
) ->
31 gen_server:start_link({local
, couch_fabric
}, couch_fabric
, FabricExec
, []).
34 exit(whereis(couch_fabric
), close
).
36 check(Formula
, Type
) ->
37 {ok
, [Result
]} = check_all([{Formula
,Type
}]),
40 check_all(Formulas
) ->
41 gen_server:call(couch_fabric
, {check
, Formulas
}).
44 % in Msecs. maybe need to make this a configurable value
47 table_compute(Docs
, Formulas
) ->
48 gen_server:call(couch_fabric
, {table_compute
, Docs
, Formulas
}, timeout()).
50 table_columns(Formulas
) ->
51 gen_server:call(couch_fabric
, {table_columns
, Formulas
}).
55 case erlang:system_info(os_type
) of
57 lists:flatten(io_lib:format("~s launched_from_erlang", [FabricExec
]));
59 lists:flatten(io_lib:format("cmd /c\"~s\" launched_from_erlang", [FabricExec
]))
61 Port
= open_port({spawn, Cmd
}, [{packet
, 4}, exit_status
, hide
]),
64 terminate(_Reason
, _Server
) ->
68 handle_call({check
, Formulas
}, _From
, Port
) ->
70 lists:foreach(fun({Formula
, Type
}) ->
71 send_dword(Port
, 1), % indicate compilation
78 send_string(Port
, Formula
)
82 Results
= lists:map(fun(_Formula
) ->
83 case get_dword(Port
) of
91 {reply
, {ok
, Results
}, Port
};
92 handle_call({table_columns
, Formulas
}, _From
, Port
) ->
93 % indicate table columns
95 % send the number of formulas
96 send_dword(Port
, length(Formulas
)),
98 Results
= lists:map(fun(Formula
) ->
99 send_string(Port
, Formula
),
100 case get_dword(Port
) of
104 Names
= get_list(Port
),
105 {ok
, [list_to_atom(Name
) || Name
<- Names
]}
109 {reply
, {ok
, Results
}, Port
};
110 handle_call({table_compute
, Docs
, Formulas
}, _From
, Port
) ->
111 % indicate table computation
113 % send the number of formulas
114 send_dword(Port
, length(Formulas
)),
116 lists:foreach(fun(Formula
) ->
117 send_string(Port
, Formula
)
121 % send the number of documents
122 send_dword(Port
, length(Docs
)),
125 lists:foreach(fun(Doc
) ->
132 lists:map(fun(_Doc
) ->
133 % loop for each document and receive the corresponding response
134 lists:map(fun(_Formula
) ->
135 % loop for each formula
136 % did the formula match?
137 case get_dword(Port
) of
141 {ok
, get_values(Port
)}
147 {reply
, {ok
, DocResults
}, Port
}.
149 handle_cast(_Whatever
, Port
) ->
152 handle_info({Port
, {exit_status
, Status
}}, Port
) ->
153 {stop
, {fabric_server_exited
, Status
}, Port
};
154 handle_info(_Whatever
, Port
) ->
157 code_change(_OldVsn
, State
, _Extra
) ->
160 send_dword(Port
, Num
) when Num
=< 16#FFFFFFFF
, Num
>= 0 ->
161 port_command(Port
, <<Num:32/native
>>).
165 {Port
, {data
, Bytes
}} ->
166 <<Result:32/native
>> = list_to_binary(Bytes
),
173 {Port
, {data
, Bytes
}} ->
174 <<Result:64/float>> = list_to_binary(Bytes
),
180 ErrorAtom
= get_atom(Port
),
181 ErrorString
= get_string(Port
),
182 {ErrorAtom
, ErrorString
}.
185 send_float(Port
, Num
) ->
186 port_command(Port
, <<Num:64/float>>).
188 send_string(Port
, []) ->
189 % can't send null, send a single zeroed byte
190 port_command(Port
, [0]);
191 send_string(Port
, String
) ->
192 port_command(Port
, String
).
196 {Port
, {data
, [0]}} ->
197 % we can't send or receive null strings, so to represent a
198 % null string we send a zeroed 1 byte string and then
199 % convert to a null string
201 {Port
, {data
, String
}} ->
207 list_to_atom(get_string(Port
)).
209 send_doc(Port
, Doc
) ->
211 {"$docid", [Doc#doc
.uuid
]},
212 {"$revisions", Doc#doc
.revisions
}
213 | dict:to_list(Doc#doc
.summary_fields
)],
214 send_fields(Port
, Fields
).
218 send_fields(Port
, Fields
) ->
219 % send the number of fields
220 send_dword(Port
, length(Fields
)),
222 lists:foreach(fun({FieldName
, FieldValue
}) ->
223 send_string(Port
, FieldName
),
224 send_list(Port
, FieldValue
)
229 get_values2(Port
, get_dword(Port
), []).
231 get_values2(_Port
, 0, Acc
) ->
233 get_values2(Port
, Count
, Acc
) ->
234 Value
= get_list(Port
),
235 get_values2(Port
, Count
-1, [Value
| Acc
]).
238 send_list(Port
, List
) ->
239 send_dword(Port
, length(List
)),
240 send_list2(Port
, List
).
242 send_list2(_Port
, []) ->
244 send_list2(Port
, [Element
| Rest
]) when is_list(Element
) ->
245 send_dword(Port
, 0), % indicate string
246 send_string(Port
, Element
),
247 send_list2(Port
, Rest
);
248 send_list2(Port
, [Element
| Rest
]) when is_number(Element
) ->
249 send_dword(Port
, 1), % indicate float
250 send_float(Port
, Element
),
251 send_list2(Port
, Rest
).
254 get_list2(Port
, get_dword(Port
), []).
256 get_list2(_Port
, 0, Acc
) ->
258 get_list2(Port
, Count
, Acc
) ->
260 case get_dword(Port
) of
266 get_list2(Port
, Count
- 1, [Element
| Acc
]).
269 ok
= check("damien", simple
),
270 io:format("Formula compiled~n"),
271 {ok
, Columns
} = table_columns([" select Foo == \"bar\"; COLUMN Food := Foo + Bar2", " select Foo != \"bar\";"]),
272 io:format("Table Columns: ~p~n", [Columns
]),
273 Doc
= couch_doc:new([{"Foo", ["bar"]}, {"Bar2", ["bar2"]}, {"Num", [1.0]}, {"gak", ["glopf"]}]),
274 Doc2
= couch_doc:new([{"Foo", ["bar"]}, {"Bar2", ["bbbbbbbabs"]}, {"Num", [2.0]}, {"gak", ["glopf"]}]),
275 {ok
, [Result
, ResultB
]} = table_compute([Doc
, Doc2
], [" select Foo == \"bar\"; COLUMN Food := Num + 2", " select Foo != \"bar\";"]),
276 io:format("~p~n", [Result
]),
277 io:format("ResultB: ~p~n", [ResultB
]),
278 {ok
, [Result2
]} = table_compute([Doc
], [" select Foo != \"bar\";"]),
279 io:format("~p~n", [Result2
]),
280 {ok
, [Result3
]} = table_compute([Doc
], [" select Num == 1"]),
281 io:format("~p~n", [Result3
]),