_temp_view support for alternate query languages, using content-type
[couchdbimport.git] / src / CouchDb / mod_couch.erl
blob99221dc09f42f5d5f64b321ef6667dad740db858
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(mod_couch).
20 -include("couch_db.hrl").
22 -export([do/1, load/2]).
24 -include_lib("inets/src/httpd.hrl").
26 -record(uri_parts,
27 {db = "",
28 doc = "",
29 resource = "",
30 querystr = ""}).
32 -record(doc_query_args,
33 {full=false,
34 options = [],
35 revs = [],
36 all_revs = false,
37 bin = ""
38 }).
40 %% do. This is the main entry point into CouchDb from the HTTP server
42 do(ModData) ->
43 #mod{request_uri=Uri,request_line=Request, parsed_header=Header,entity_body=Body} = ModData,
44 case Uri of
45 "/_utils/" ++ RestURI ->
46 % if the URI is the utils directory, then this
47 % tells mod_get (a std HTTP module) where to serve the file from
48 DocumentRoot = httpd_util:lookup(ModData#mod.config_db, document_root, ""),
49 {Path, AfterPath} = httpd_util:split_path(DocumentRoot ++ "/" ++ RestURI),
50 case filelib:is_file(Path) of
51 true ->
52 {proceed, [{real_name, {Path, AfterPath}} | ModData#mod.data]};
53 false ->
54 case filelib:is_dir(Path) of
55 true ->
56 % this ends up causing a "Internal Server Error", need to fix.
57 {proceed, [{response,{403,"Forbidden"}}]};
58 false ->
59 {proceed, [{response,{404,"Not found"}}]}
60 end
61 end;
62 "/favicon.ico" ->
63 DocumentRoot = httpd_util:lookup(ModData#mod.config_db, document_root, ""),
64 RealName = DocumentRoot ++ "/" ++ Uri,
65 {Path, AfterPath} = httpd_util:split_path(RealName),
66 {proceed, [{real_name, {Path, AfterPath}} | ModData#mod.data]};
67 _ ->
68 couch_log:info("HTTP Request: ~s~nHeader:~p", [Request, Header]),
69 couch_log:debug("Body:~P", [Body, 100]),
70 Parts = parse_uri(Uri),
71 {ok, ResponseCode} =
72 case Parts of
73 #uri_parts{db="_all_dbs"} ->
74 send_all_dbs(ModData, Parts);
75 #uri_parts{db=""} ->
76 % empty request
77 show_couch_welcome(ModData);
78 _Else ->
79 case (catch handle_request(ModData, Parts)) of
80 {ok, Response} ->
81 {ok, Response};
82 {error_response, Error} ->
83 send_error(ModData, Error);
84 Error ->
85 send_error(ModData, Error)
86 end
87 end,
88 couch_log:info("HTTP Response Code:~p~n", [ResponseCode]),
89 {proceed, [{response, {already_sent, ResponseCode, 0}} | ModData#mod.data]}
90 end.
92 parse_uri(RequestUri) ->
93 % seperate out the path and query portions and
94 % strip out leading slash and question mark.
95 case httpd_util:split_path(RequestUri) of
96 {[$/|UriPath], [$?|QueryStr]} -> ok;
97 {[$/|UriPath], QueryStr} -> ok % there is no query
98 end,
100 case UriPath of
101 "" ->
102 #uri_parts{querystr=QueryStr};
103 UriPath ->
104 case lists:last(UriPath) of
105 $/ -> % ends with a slash means database only
106 DbId = lists:sublist(UriPath, length(UriPath) - 1), % strip off trailing slash
107 #uri_parts{db=DbId,querystr=QueryStr};
108 _Else ->
109 % lets try to parse out the UriPath
110 {ok, SplitUrl} = regexp:split(UriPath, "/"),
111 case SplitUrl of
112 [DbId] -> % single element list.
113 #uri_parts{db = DbId, querystr = QueryStr};
114 _Else2 ->
115 % multi element list. treat last element as the docid
116 [DocIdDocResource | RevDbPath] = lists:reverse(SplitUrl),
117 DbId = couch_util:implode(lists:reverse(RevDbPath), "/"),
118 % Everything before first ":" is the docId,
119 % and everything after is the resource id.
120 {ok, [DocId | DocResourceList]} = regexp:split(DocIdDocResource, ":"),
121 ResourceId = couch_util:implode(DocResourceList, ":"),
122 #uri_parts{db=DbId, doc=DocId, resource=ResourceId, querystr=QueryStr}
125 end.
127 % return json doc header values list
128 resp_json_header(ModData) ->
129 resp_header(ModData, "text/plain; charset=utf-8").
131 % return doc header values list
132 resp_header(#mod{http_version=Version}, ContentType) ->
133 [{"cache-control", "no-cache"},
134 {"pragma", "no-cache"},
135 {"expires", httpd_util:rfc1123_date()},
136 {"damien", "awesome"},
137 {"content-type", ContentType},
138 case Version == "HTTP/1.1" of
139 true ->
140 {"transfer-encoding", "chunked"};
141 false ->
142 {"connection", "close"}
143 end].
146 url_decode([$%, Hi, Lo | Tail]) ->
147 Hex = erlang:list_to_integer([Hi, Lo], 16),
148 xmerl_ucs:to_utf8([Hex]) ++ url_decode(Tail);
149 url_decode([H|T]) ->
150 [H |url_decode(T)];
151 url_decode([]) ->
155 send_header(ModData, RespCode, Headers) ->
156 couch_log:debug("HTTP response headers (code ~w):~p", [RespCode, Headers]),
157 httpd_response:send_header(ModData, RespCode, Headers).
159 send_chunk(ModData, Data) ->
160 httpd_response:send_chunk(ModData, Data, false).
162 send_final_chunk(ModData) ->
163 httpd_response:send_final_chunk(ModData, false).
165 show_couch_welcome(ModData) ->
166 send_header(ModData, 200, resp_json_header(ModData)),
167 send_chunk(ModData, "{\"couchdb\": \"Welcome\", "),
168 send_chunk(ModData, "\"version\": \"" ++ couch_server:get_version()),
169 send_chunk(ModData, "\"}"),
170 send_final_chunk(ModData),
171 {ok, 200}.
173 handle_request(#mod{method="POST"}=ModData, #uri_parts{db=DbName, doc="_missing_revs"}=UriParts) ->
174 handle_missing_revs_request(ModData, UriParts, open_db(DbName));
175 handle_request(#mod{method="POST"}=ModData, #uri_parts{db=DbName, doc=""}=UriParts) ->
176 % POST with no doc specified, therefore document addition
177 handle_db_request(ModData#mod{method="PUT"}, UriParts, open_db(DbName));
178 handle_request(#mod{method="PUT"}=ModData, #uri_parts{db=DbName, doc=""}=_UriParts) ->
179 % put with no doc specified, therefore database creation
180 case couch_server:create(DbName, []) of
181 {ok, _Db} ->
182 send_ok(ModData, 201);
183 {error, database_already_exists} ->
184 Msg = io_lib:format("Database ~p already exists.", [DbName]),
185 throw({error_response, {database_already_exists, Msg}});
186 Error ->
187 Msg = io_lib:format("Error creating database ~p: ~p", [DbName, Error]),
188 throw({error_response, {unknown_error, Msg}})
189 end;
190 handle_request(#mod{method="DELETE"}=ModData, #uri_parts{db=DbName, doc=""}=_UriParts) ->
191 % delete with no doc specified, therefore database delete
192 case couch_server:delete(DbName) of
193 ok ->
194 send_ok(ModData, 202);
195 Error ->
196 throw({error_response, Error})
197 end;
198 handle_request(ModData, #uri_parts{db=DbName}=UriParts) ->
199 handle_db_request(ModData, UriParts, open_db(DbName)).
201 % returns db, otherwise throws exception. Note, no {ok,_}.
202 open_db(DbName) ->
203 case couch_server:open(DbName) of
204 {ok, Db} ->
206 Error ->
207 throw({error_response, Error})
208 end.
210 handle_missing_revs_request(#mod{entity_body=RawJson}=ModData, _UriParts, Db) ->
211 {obj, DocIdRevs} = cjson:decode(RawJson),
212 {ok, Results} = couch_db:get_missing_revs(Db, tuple_to_list(DocIdRevs)),
213 send_json(ModData, 200, {obj, Results}).
217 handle_db_request(#mod{method="POST", entity_body=RawBody, parsed_header=Headers}=ModData, #uri_parts{doc="_temp_view"}=Uri, Db) ->
218 Type = proplists:get_value(Headers, "content-type", "text/javascript"),
219 handle_db_request(ModData#mod{method="GET"}, Uri#uri_parts{resource=Type ++ "|" ++ RawBody}, Db);
220 handle_db_request(#mod{method="DELETE"}=ModData, #uri_parts{doc=DocId,querystr=QueryStr}, Db) ->
221 % handle doc deletion
222 #doc_query_args{revs=Revs} = doc_parse_query(QueryStr),
224 Revs2 = case Revs of [] -> all; _ -> Revs end,
226 case couch_db:delete_doc(Db, DocId, Revs2) of
227 {ok, [{ok, NewRev}]} ->
228 send_ok(ModData, 202, [{"_rev",NewRev}]); % single doc success
229 {conflict, _Rev} ->
230 throw({error_response, conflict}); % single doc conflict failure
231 {ok, Results} ->
232 % multiple results, send results as list
233 Results2 =
234 lists:map(fun(Result) ->
235 case Result of
236 {ok, NewRev} ->
237 NewRev;
238 {conflict, _Rev} ->
239 "conflict"
241 end, Results),
242 send_ok(ModData, 202, [{multi, list_to_tuple(Results2)}]);
243 Error ->
244 throw({error_response, Error})
245 end;
246 handle_db_request(#mod{method="PUT",entity_body=RawBody}=ModData, #uri_parts{doc=UrlDocId,querystr=QueryStr}, Db) ->
247 % handle doc creation
248 % just assume the content
249 % is application/json utf-8
251 #doc_query_args{options=SaveOptions} = doc_parse_query(QueryStr),
252 couch_log:debug("Body:~100s", [RawBody]),
254 Json = cjson:decode(RawBody),
255 case Json of
256 {obj, [{"_bulk_docs", SubJsonDocs}]} ->
257 % convert all the doc elements to native docs
258 DocsAndOptions =
259 lists:foldl(
260 fun(SubDoc, DocAcc) ->
261 NewDoc = couch_doc:from_json_obj(SubDoc),
262 NewDoc2 = NewDoc#doc{uuid=
263 case NewDoc#doc.uuid of
264 [] -> couch_util:new_uuid();
265 Id -> Id
266 end},
267 [{[NewDoc2], [new_edits| SaveOptions]} | DocAcc]
268 end,
269 [], tuple_to_list(SubJsonDocs)),
270 % save them
271 {ok, Results} = couch_db:save_docs(Db, DocsAndOptions),
272 DocResults =
273 lists:zipwith(fun([Result], {[Doc],_}) ->
274 case Result of
275 {ok, RevId} ->
276 {obj, [{"ok",true}, {"_id", Doc#doc.uuid}, {"_rev", RevId}]};
277 Error ->
278 {JsonError, _HttpCode} = error_to_json(Error),
279 JsonError
281 end, Results, DocsAndOptions),
283 send_ok(ModData, 201, [{results, list_to_tuple(DocResults)}], []);
284 _ ->
285 Doc = couch_doc:from_json_obj(Json),
286 %if the docid is specified in the URL, use that (we override the
287 % json document's _id member).
288 Doc2 =
289 case {UrlDocId, Doc#doc.uuid} of
290 {"", ""} -> % both blank
291 Doc#doc{uuid = couch_util:new_uuid()};
292 {"", _} -> % url is blank, doc's id is not, use that
293 Doc;
294 {_, _} -> % both are available, prefer the Url one
295 Doc#doc{uuid = UrlDocId}
296 end,
297 {obj, ObjProps} = Json,
298 Doc3 =
299 case proplists:get_value("_rev", ObjProps) of
300 undefined ->
301 Doc2;
302 PrevRev ->
303 % there is a "based on" revisionid
304 % load the document from the database and see if its the same
305 % revision. If so then snag the revision history and use it.
306 case couch_db:open_doc(Db, Doc2#doc.uuid, [allow_stub]) of
307 {ok, DiskDoc} ->
308 case PrevRev == lists:nth(1, DiskDoc#doc.revisions) of
309 true ->
310 Doc2#doc{revisions = DiskDoc#doc.revisions};
311 false ->
312 % oops, the revision in db isn't what this doc is based
313 % upon, a conflict!
314 throw({error_response, {update_error, conflict}})
315 end;
316 {not_found, missing} ->
317 throw({error_response, {update_error, missing_rev}})
319 end,
320 case couch_db:save_doc(Db, Doc3, SaveOptions) of
321 {ok, NewRevId} ->
322 Props = [{"_id", Doc3#doc.uuid}, {"_rev", NewRevId}],
323 HeaderInfo = [{"x-couch-id", Doc3#doc.uuid}, {"x-couch-rev", integer_to_list(NewRevId)}],
324 send_ok(ModData, 201, Props, HeaderInfo);
325 Error ->
326 throw({error_response, {update_error, Error}})
328 end;
329 handle_db_request(#mod{method="GET"}=ModData,
330 #uri_parts{doc=DocId, resource=Resource}=UriParts, Db) ->
331 % handle doc retrieval
332 case {DocId, Resource} of
333 {"", ""} ->
334 send_database_info(ModData, UriParts, Db);
335 {"_all_docs", ""} ->
336 send_all_docs(ModData, UriParts, Db);
337 {"_all_docs_by_update_seq", ""} ->
338 send_all_docs_by_seq(ModData, UriParts, Db);
339 {_, ""} ->
340 send_doc(ModData, UriParts, Db);
341 {?DESIGN_DOC_PREFIX ++ _, _ViewId} ->
342 send_view(ModData, UriParts, Db);
343 {"_temp_view", _ViewId} ->
344 send_view(ModData, UriParts, Db);
345 {_, _ViewId} ->
346 throw({error_response, {error, not_a_design_doc}})
347 end.
349 doc_parse_query(QueryStr) ->
350 QueryList = httpd:parse_query(QueryStr),
351 lists:foldl(fun({Key,Value}, Args) ->
352 case {Key, Value} of
353 {"full", "true"} ->
354 Args#doc_query_args{full=true};
355 {"latest", "true"} ->
356 Options = [latest | Args#doc_query_args.options],
357 Args#doc_query_args{options=Options};
358 {"rev", RevIdStr} ->
359 Revs = [list_to_integer(RevIdStr) | Args#doc_query_args.revs],
360 Args#doc_query_args{revs=Revs};
361 {"all_revs", "true"} ->
362 Args#doc_query_args{revs=all};
363 {"new_edits", "true"} ->
364 Options = [new_edits | Args#doc_query_args.options],
365 Args#doc_query_args{options=Options};
366 {"attachment", BinName} ->
367 Args#doc_query_args{bin=BinName};
368 {"delay_commit", "true"} ->
369 Options = [delay_commit | Args#doc_query_args.options],
370 Args#doc_query_args{options=Options};
371 _Else -> % unknown key value pair, ignore.
372 Args
374 end,
375 #doc_query_args{}, QueryList).
377 send_database_info(ModData, #uri_parts{db=DbName}, Db) ->
378 {ok, InfoList} = couch_db:get_info(Db),
379 ok = send_header(ModData, 200, resp_json_header(ModData)),
380 DocCount = proplists:get_value(doc_count, InfoList),
381 LastUpdateSequence = proplists:get_value(last_update_seq, InfoList),
382 ok = send_chunk(ModData, "{\"db_name\": \"" ++ DbName ++ "\", \"doc_count\":" ++ integer_to_list(DocCount) ++ ", \"update_seq\":" ++ integer_to_list(LastUpdateSequence)++"}"),
383 ok = send_final_chunk(ModData),
384 {ok, 200}.
386 send_doc(ModData, #uri_parts{doc=DocId,querystr=QueryStr}, Db) ->
387 #doc_query_args{full=Full, options=OpenOptions, revs=Revs, bin=BinName} = doc_parse_query(QueryStr),
388 case BinName of
389 "" ->
390 case Revs of
391 [] ->
392 case couch_db:open_doc(Db, DocId, OpenOptions) of
393 {ok, Doc} ->
394 send_json(ModData, 200, couch_doc:to_json_obj(Doc, Full));
395 Error ->
396 throw({error_response, Error})
397 end;
398 Revs ->
399 {ok, Results} = couch_db:open_doc_revs(Db, DocId, Revs, OpenOptions),
400 ok = send_header(ModData, 200, resp_json_header(ModData)),
401 ok = send_chunk(ModData, "{\"docs\":["),
402 % We loop through the docs. The first time through the seperator
403 % is whitespace, then a comma on subsequent iterations.
404 lists:foldl(
405 fun(Result, AccSeperator) ->
406 case Result of
407 {ok, Doc} ->
408 JsonDoc= couch_doc:to_json_obj(Doc, Full),
409 ok = send_chunk(ModData, AccSeperator ++ lists:flatten(cjson:encode(JsonDoc)));
410 {{not_found, missing}, RevId} ->
411 Json = {obj, [{id, DocId}, {rev, RevId}, {missing, true}]},
412 ok = send_chunk(ModData, AccSeperator ++ lists:flatten(cjson:encode(Json)))
413 end,
414 ",\n" % AccSeperator now has a comma
415 end,
416 "\n", Results),
417 ok = send_chunk(ModData, "\n]}"),
418 ok = send_final_chunk(ModData),
419 {ok, 200}
420 end;
421 BinName ->
422 case couch_db:open_doc(Db, DocId, OpenOptions) of
423 {ok, #doc{attachments=Bins}} ->
424 case proplists:lookup(BinName,Bins) of
425 none ->
426 throw({error_response, {not_found, missing}});
427 {_, {"base64", Bin}} ->
428 Suffix = httpd_util:suffix(BinName),
429 MimeType = httpd_util:lookup_mime_default(ModData#mod.config_db,
430 Suffix,"text/plain"),
431 ok = send_header(ModData, 200, resp_header(ModData, MimeType) ++ [{"content-length", integer_to_list(couch_doc:bin_size(Bin))}]),
432 couch_doc:bin_foldl(Bin,
433 fun(BinSegment, []) ->
434 ok = send_chunk(ModData, BinSegment),
435 {ok, []}
436 end,
437 []),
438 ok = send_final_chunk(ModData),
439 {ok, 200};
440 {_, {_, Bin}} ->
441 % for now, anything else, send as json
442 Json = binary_to_term(couch_doc:bin_to_binary(Bin)),
443 send_json(ModData, 200, Json)
444 end;
445 Error ->
446 throw({error_response, Error})
448 end.
450 send_json(ModData, Code, JsonData) ->
451 send_json(ModData, Code, JsonData, []).
453 send_json(ModData, Code, JsonData, AdditionalHeaders) ->
454 ok = send_header(ModData, Code, resp_json_header(ModData) ++ AdditionalHeaders),
455 ok = send_chunk(ModData, lists:flatten(cjson:encode(JsonData))),
456 ok = send_final_chunk(ModData),
457 {ok, Code}.
460 send_ok(ModData, Code) ->
461 send_ok(ModData, Code, []).
463 send_ok(ModData, Code, AdditionalProps) ->
464 send_ok(ModData, Code, AdditionalProps, []).
466 send_ok(ModData, Code, AdditionalProps, AdditionalHeaders) ->
467 send_json(ModData, Code, {obj, [{ok, true}|AdditionalProps]}, AdditionalHeaders).
470 -record(query_args,
471 {start_key = nil,
472 end_key = <<>>,
473 count = 10000000000, % a huge huge default number. Picked so we don't have
474 % to do different logic for when there is no count limit
475 update = true,
476 direction = fwd,
477 start_docid = nil,
478 end_docid = <<>>,
479 skip = 0
482 reverse_key_default(nil) -> <<>>;
483 reverse_key_default(<<>>) -> nil;
484 reverse_key_default(Key) -> Key.
486 view_parse_query(QueryStr) ->
487 QueryList = httpd:parse_query(QueryStr),
488 lists:foldl(fun({Key,Value}, Args) ->
489 case {Key, Value} of
490 {"key", Value} ->
491 JsonKey = cjson:decode(url_decode(Value)),
492 Args#query_args{start_key=JsonKey,end_key=JsonKey};
493 {"startkey_docid", DocId} ->
494 Args#query_args{start_docid=DocId};
495 {"startkey", Value} ->
496 Args#query_args{start_key=cjson:decode(url_decode(Value))};
497 {"endkey", Value} ->
498 Args#query_args{end_key=cjson:decode(url_decode(Value))};
499 {"count", Value} ->
500 case (catch list_to_integer(Value)) of
501 Count when is_integer(Count) ->
502 Args#query_args{count=Count};
503 _Error ->
504 Msg = io_lib:format("Bad URL query value, number expected: count=~s", [Value]),
505 throw({error_response, {query_parse_error, Msg}})
506 end;
507 {"update", "false"} ->
508 Args#query_args{update=false};
509 {"reverse", "true"} ->
510 case Args#query_args.direction of
511 fwd ->
512 Args#query_args {
513 direction = rev,
514 start_key = reverse_key_default(Args#query_args.start_key),
515 start_docid = reverse_key_default(Args#query_args.start_docid),
516 end_key = reverse_key_default(Args#query_args.end_key),
517 end_docid = reverse_key_default(Args#query_args.end_docid)};
518 _ ->
519 Args %already reversed
520 end;
521 {"skip", Value} ->
522 case (catch list_to_integer(Value)) of
523 Count when is_integer(Count) ->
524 Args#query_args{skip=Count};
525 _Error ->
526 Msg = io_lib:format("Bad URL query value, number expected: skip=~s", [Value]),
527 throw({error_response, {query_parse_error, Msg}})
528 end;
529 _Else -> % unknown key value pair, ignore.
530 Args
532 end,
533 #query_args{}, QueryList).
536 make_view_fold_fun(ModData, #uri_parts{doc=DocId, resource=ViewId}, QueryArgs) ->
537 #query_args{
538 end_key=EndKey,
539 end_docid=EndDocId,
540 direction=Dir
541 } = QueryArgs,
543 fun({Uuid, Rev}, Key, Value, Offset, TotalViewCount, {AccCount, AccSkip, HeaderSent}) ->
544 PassedEnd =
545 case Dir of
546 fwd ->
547 couch_view_group:less_json({EndKey,EndDocId}, {Key, Uuid});
548 rev ->
549 couch_view_group:less_json({Key, Uuid}, {EndKey,EndDocId})
550 end,
551 case PassedEnd of
552 true ->
553 % The stop key has been passed, stop looping.
554 {stop, {AccCount, AccSkip, HeaderSent}};
555 false ->
556 case AccCount > 0 of
557 true ->
558 case AccSkip > 0 of
559 true ->
560 {ok, {AccCount, AccSkip - 1, HeaderSent}};
561 false ->
562 case HeaderSent of
563 header_sent ->
564 JsonObj = {obj, [{"_id", Uuid}, {"_rev", Rev} | key_value_to_props(Key,Value)]},
565 ok = send_chunk(ModData, ",\n" ++ lists:flatten(cjson:encode(JsonObj)));
566 _Else ->
567 % We do this the first time through, NOT before folding,
568 % because we want to make sure the view exists efficiently.
569 ok = send_header(ModData, 200, resp_json_header(ModData)),
570 FullViewId = cjson:encode(lists:flatten(io_lib:format("~s:~s", [DocId, ViewId]))),
571 JsonBegin = io_lib:format("{\"view\":~s,\"total_rows\":~w, \"offset\":~w, \"rows\":[\n",
572 [FullViewId, TotalViewCount, Offset]),
573 JsonObj = {obj, [{"_id", Uuid}, {"_rev", Rev} | key_value_to_props(Key,Value)]},
574 ok = send_chunk(ModData, lists:flatten(JsonBegin ++ cjson:encode(JsonObj)))
575 end,
576 {ok, {AccCount - 1, 0, header_sent}}
577 end;
578 false ->
579 {stop, {0, 0, HeaderSent}} % we've done "count" rows, stop foldling
582 end.
584 key_value_to_props(Key,Value) ->
585 case Key of nil -> []; _-> [{"key", Key}] end ++
586 case Value of nil -> []; _-> [{"value", Value}] end.
589 send_view(ModData, #uri_parts{doc=DocId, resource=ViewId, querystr=QueryStr}=UriParts, Db) ->
590 QueryArgs = view_parse_query(QueryStr),
591 #query_args{
592 start_key=StartKey,
593 count=Count,
594 skip=SkipCount,
595 update=Update,
596 direction=Dir,
597 start_docid=StartDocId} = QueryArgs,
598 case Update of
599 true ->
600 UpdateResult =
601 case DocId of
602 "_temp_view" ->
603 couch_db:update_temp_view_group_sync(Db, ViewId);
604 _ ->
605 couch_db:update_view_group_sync(Db, DocId)
606 end,
607 case UpdateResult of
608 ok -> ok;
609 Error -> throw({error_response, Error})
610 end;
611 false ->
613 end,
614 FoldlFun = make_view_fold_fun(ModData, UriParts, QueryArgs),
615 FoldResult =
616 case DocId of
617 "_temp_view" ->
618 couch_db:fold_temp_view(Db, ViewId, {StartKey, StartDocId}, Dir, FoldlFun, {Count, SkipCount, header_not_sent});
619 _ ->
620 couch_db:fold_view(Db, DocId, ViewId, {StartKey, StartDocId}, Dir, FoldlFun, {Count, SkipCount, header_not_sent})
621 end,
622 case FoldResult of
623 {ok, TotalRows, {_, _, header_not_sent}} ->
624 % nothing found in the view, nothing has been returned
625 % send empty view
626 ok = send_header(ModData, 200, resp_json_header(ModData)),
627 FullViewId = lists:flatten(cjson:encode(lists:flatten(io_lib:format("~s:~s", [DocId, ViewId])))),
628 JsonEmptyView = lists:flatten(
629 io_lib:format("{\"view\":~s,\"total_rows\":~w,\"rows\":[]}",
630 [FullViewId, TotalRows])),
631 ok = send_chunk(ModData, JsonEmptyView),
632 ok = send_final_chunk(ModData),
633 {ok, 200};
634 {ok, _TotalRows, {_, _, header_sent}} ->
635 % end the view
636 ok = send_chunk(ModData, "\n]}"),
637 ok = send_final_chunk(ModData),
638 {ok, 200};
639 Error2 ->
640 throw({error_response, Error2})
641 end.
643 send_all_dbs(ModData, _Parts)->
644 {ok, DbNames} = couch_server:all_databases(),
645 ok = send_header(ModData, 200, resp_json_header(ModData)),
646 ok = send_chunk(ModData, lists:flatten(cjson:encode(list_to_tuple(DbNames)))),
647 ok = send_final_chunk(ModData),
648 {ok, 200}.
651 send_all_docs(ModData, #uri_parts{querystr=QueryStr}, Db) ->
652 QueryArgs = view_parse_query(QueryStr),
653 #query_args{
654 start_key=StartKey,
655 count=Count,
656 direction=Dir} = QueryArgs,
657 ok = send_header(ModData, 200, resp_json_header(ModData)),
658 ok = send_chunk(ModData,
659 "{\"view\":\"_all_docs\", \"rows\":[\n"),
660 couch_db:enum_docs(Db, StartKey, Dir,
661 fun(#doc_info{uuid=Id, revision=Rev, deleted=Deleted}, {AccSeperator, AccCount}) ->
662 case {Deleted, AccCount > 0} of
663 {false, true} ->
664 Json = {obj, [{"_id", Id}, {"_rev", Rev}]} ,
665 ok = send_chunk(ModData, AccSeperator ++ lists:flatten(cjson:encode(Json))),
666 {ok, {",\n", AccCount - 1}};
667 {true, true} ->
668 {ok, {AccSeperator, AccCount}}; % skip
669 {_, false} ->
670 {stop, 0} % we've sent "count" rows, stop folding
672 end, {"", Count}),
673 ok = send_chunk(ModData, "\n]}"),
674 ok = send_final_chunk(ModData),
675 {ok, 200}.
677 send_all_docs_by_seq(ModData, #uri_parts{querystr=QueryStr}, Db) ->
678 QueryArgs = view_parse_query(QueryStr),
679 #query_args{
680 start_key=StartKey,
681 count=Count,
682 direction=Dir} = QueryArgs,
683 ok = send_header(ModData, 200, resp_json_header(ModData)),
684 ok = send_chunk(ModData,
685 "{\"view\":\"_docs_by_update_seq\", \"docs\":[\n"),
687 couch_db:enum_docs_since(Db, list_to_integer(StartKey), Dir,
688 fun(#doc_info{uuid=Id, revision=Rev, update_seq=UpdateSeq, deleted=Deleted, conflict_revs=ConflictRevs}, {AccSeperator, AccCount}) ->
689 case AccCount of
690 0 ->
691 {stop, 0}; % we've sent "count" rows, stop folding
692 _ ->
693 ConflictRevsProp =
694 case ConflictRevs of
695 [] -> [];
696 _ -> [{conflicts, list_to_tuple(ConflictRevs)}]
697 end,
698 DeletedProp =
699 case Deleted of
700 true -> [{deleted, true}];
701 false -> []
702 end,
703 Json = {obj,
704 [{id, Id},
705 {rev, Rev},
706 {update_seq, UpdateSeq}] ++
707 ConflictRevsProp ++ DeletedProp},
708 ok = send_chunk(ModData, AccSeperator ++ lists:flatten(cjson:encode(Json))),
709 {ok, {",\n", AccCount - 1}}
711 end, Count),
712 ok = send_chunk(ModData, "\n]}"),
713 ok = send_final_chunk(ModData),
714 {ok, 200}.
716 send_error(ModData, Error) ->
717 {Json, Code} = error_to_json(Error),
718 couch_log:info("HTTP Error (code ~w): ~p", [Code, Json]),
719 send_json(ModData, Code, Json).
723 % convert an error response into a json object and http error code.
724 error_to_json(Error) ->
725 {HttpCode, Atom, Reason} = error_to_json0(Error),
726 Reason1 =
728 is_list(Reason) -> lists:flatten(Reason);
729 is_atom(Reason) -> atom_to_list(Reason);
730 true -> lists:flatten(io_lib:write(Reason)) % else term to text
731 end,
733 Json =
734 {obj,
735 [{error, {obj,
736 [{id, atom_to_list(Atom)},
737 {reason, Reason1}]}}]},
738 {Json, HttpCode}.
740 error_to_json0(not_found) ->
741 {404, not_found, "missing"};
742 error_to_json0({not_found, Reason}) ->
743 {404, not_found, Reason};
744 error_to_json0({database_already_exists, Reason}) ->
745 {409, database_already_exists, Reason}; % 409, conflict error
746 error_to_json0({update_error, conflict}) ->
747 {409, conflict, conflict}; % 409, conflict error
748 error_to_json0({Id, Reason}) when is_atom(Id) ->
749 {500, Id, Reason};
750 error_to_json0(Error) ->
751 {500, error, Error}.
754 %% Configuration
757 %% load
759 load("Foo Bar", []) ->
760 {ok, [], {script_alias, {"foo", "bar"}}}.