From 36bbc72b6b0992639a0ba7a2077d75ec9f7cf03d Mon Sep 17 00:00:00 2001 From: John Christopher Anderson Date: Tue, 11 Aug 2009 18:50:08 +0000 Subject: Initial commit of _update handler. Thanks to Paul Davis, Jason Davies for code and others for discussion. The _update handler accepts POSTs to paths like: /db/_design/foo/_update/bar and PUTs which include docids, like: /db/_design/foo/_update/bar/docid The function signature: function(doc, req) { doc.a_new_field = req.query.something; return [doc, "

added something to your doc

"]; } The tests in update_documents.js are fairly complete and include examples of bumping a counter, changing only a single field, parsing from (and returning) XML, and creating new documents. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@803245 13f79535-47bb-0310-9956-ffa450edef68 --- src/couchdb/couch_httpd.erl | 2 +- src/couchdb/couch_httpd_external.erl | 6 +-- src/couchdb/couch_httpd_show.erl | 93 +++++++++++++++++++++++++++++------- src/couchdb/couch_query_servers.erl | 20 +++++++- 4 files changed, 99 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/couchdb/couch_httpd.erl b/src/couchdb/couch_httpd.erl index fee5004e..fde02317 100644 --- a/src/couchdb/couch_httpd.erl +++ b/src/couchdb/couch_httpd.erl @@ -353,7 +353,7 @@ send_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers, Body) -> {ok, MochiReq:respond({Code, Headers ++ server_header() ++ couch_httpd_auth:cookie_auth_header(Req, Headers), Body})}. send_method_not_allowed(Req, Methods) -> - send_response(Req, 405, [{"Allow", Methods}], <<>>). + send_error(Req, 405, [{"Allow", Methods}], <<"method_not_allowed">>, ?l2b("Only " ++ Methods ++ " allowed")). send_json(Req, Value) -> send_json(Req, 200, Value). diff --git a/src/couchdb/couch_httpd_external.erl b/src/couchdb/couch_httpd_external.erl index d6fa945f..0a222311 100644 --- a/src/couchdb/couch_httpd_external.erl +++ b/src/couchdb/couch_httpd_external.erl @@ -57,8 +57,7 @@ process_external_req(HttpReq, Db, Name) -> json_req_obj(#httpd{mochi_req=Req, method=Verb, path_parts=Path, - req_body=ReqBody, - user_ctx=#user_ctx{name=UserName, roles=UserRoles} + req_body=ReqBody }, Db) -> Body = case ReqBody of undefined -> Req:recv_body(); @@ -70,7 +69,6 @@ json_req_obj(#httpd{mochi_req=Req, _ -> [] end, - UserCtx = {[{<<"name">>, UserName}, {<<"roles">>, UserRoles}]}, Headers = Req:get(headers), Hlist = mochiweb_headers:to_list(Headers), {ok, Info} = couch_db:get_db_info(Db), @@ -83,7 +81,7 @@ json_req_obj(#httpd{mochi_req=Req, {<<"body">>, Body}, {<<"form">>, to_json_terms(ParsedForm)}, {<<"cookie">>, to_json_terms(Req:parse_cookie())}, - {<<"userCtx">>, UserCtx}]}. + {<<"userCtx">>, couch_util:json_user_ctx(Db)}]}. to_json_terms(Data) -> to_json_terms(Data, []). diff --git a/src/couchdb/couch_httpd_show.erl b/src/couchdb/couch_httpd_show.erl index 1428e612..01bf0055 100644 --- a/src/couchdb/couch_httpd_show.erl +++ b/src/couchdb/couch_httpd_show.erl @@ -12,10 +12,9 @@ -module(couch_httpd_show). --export([handle_doc_show_req/2, handle_view_list_req/2, +-export([handle_doc_show_req/2, handle_doc_update_req/2, handle_view_list_req/2, handle_doc_show/5, handle_view_list/6]). - -include("couch_db.hrl"). -import(couch_httpd, @@ -40,6 +39,47 @@ handle_doc_show_req(#httpd{method='GET'}=Req, _Db) -> handle_doc_show_req(Req, _Db) -> send_method_not_allowed(Req, "GET,POST,HEAD"). +handle_doc_update_req(#httpd{ + method = 'PUT', + path_parts=[_DbName, _Design, DesignName, _Update, UpdateName, DocId] + }=Req, Db) -> + DesignId = <<"_design/", DesignName/binary>>, + #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, nil, []), + Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>), + UpdateSrc = couch_util:get_nested_json_value({Props}, [<<"updates">>, UpdateName]), + Doc = try couch_httpd_db:couch_doc_open(Db, DocId, nil, [conflicts]) of + FoundDoc -> FoundDoc + catch + _ -> nil + end, + send_doc_update_response(Lang, UpdateSrc, DocId, Doc, Req, Db); + +handle_doc_update_req(#httpd{ + method = 'POST', + path_parts=[_DbName, _Design, DesignName, _Update, UpdateName] + }=Req, Db) -> + DesignId = <<"_design/", DesignName/binary>>, + #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, nil, []), + Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>), + UpdateSrc = couch_util:get_nested_json_value({Props}, [<<"updates">>, UpdateName]), + send_doc_update_response(Lang, UpdateSrc, nil, nil, Req, Db); + +handle_doc_update_req(#httpd{ + path_parts=[_DbName, _Design, DesignName, _Update, UpdateName, DocId] + }=Req, Db) -> + send_method_not_allowed(Req, "PUT"); + +handle_doc_update_req(#httpd{ + path_parts=[_DbName, _Design, DesignName, _Update, UpdateName] + }=Req, Db) -> + send_method_not_allowed(Req, "POST"); + +handle_doc_update_req(Req, _Db) -> + send_error(Req, 404, <<"update_error">>, <<"Invalid path.">>). + + + + handle_doc_show(Req, DesignName, ShowName, DocId, Db) -> DesignId = <<"_design/", DesignName/binary>>, #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, nil, []), @@ -364,18 +404,39 @@ send_doc_show_response(Lang, ShowSrc, DocId, #doc{revs=Revs}=Doc, #httpd{mochi_r couch_httpd_external:send_external_response(Req, JsonResp) end). -set_or_replace_header(H, L) -> - set_or_replace_header(H, L, []). - -set_or_replace_header({Key, NewValue}, [{Key, _OldVal} | Headers], Acc) -> +send_doc_update_response(Lang, UpdateSrc, DocId, Doc, #httpd{mochi_req=MReq}=Req, Db) -> + case couch_query_servers:render_doc_update(Lang, UpdateSrc, + DocId, Doc, Req, Db) of + [<<"up">>, {NewJsonDoc}, JsonResp] -> + Options = case couch_httpd:header_value(Req, "X-Couch-Full-Commit", "false") of + "true" -> + [full_commit]; + _ -> + [] + end, + NewDoc = couch_doc:from_json_obj({NewJsonDoc}), + Code = 201, + {ok, NewRev} = couch_db:update_doc(Db, NewDoc, Options); + [<<"up">>, _Other, JsonResp] -> + Code = 200, + ok + end, + JsonResp2 = json_apply_field({<<"code">>, Code}, JsonResp), + couch_httpd_external:send_external_response(Req, JsonResp2). + +% Maybe this is in the proplists API +% todo move to couch_util +json_apply_field(H, {L}) -> + json_apply_field(H, L, []). +json_apply_field({Key, NewValue}, [{Key, _OldVal} | Headers], Acc) -> % drop matching keys - set_or_replace_header({Key, NewValue}, Headers, Acc); -set_or_replace_header({Key, NewValue}, [{OtherKey, OtherVal} | Headers], Acc) -> + json_apply_field({Key, NewValue}, Headers, Acc); +json_apply_field({Key, NewValue}, [{OtherKey, OtherVal} | Headers], Acc) -> % something else is next, leave it alone. - set_or_replace_header({Key, NewValue}, Headers, [{OtherKey, OtherVal} | Acc]); -set_or_replace_header({Key, NewValue}, [], Acc) -> + json_apply_field({Key, NewValue}, Headers, [{OtherKey, OtherVal} | Acc]); +json_apply_field({Key, NewValue}, [], Acc) -> % end of list, add ours - [{Key, NewValue}|Acc]. + {[{Key, NewValue}|Acc]}. apply_etag({ExternalResponse}, CurrentEtag) -> % Here we embark on the delicate task of replacing or creating the @@ -387,12 +448,12 @@ apply_etag({ExternalResponse}, CurrentEtag) -> % no JSON headers % add our Etag and Vary headers to the response {[{<<"headers">>, {[{<<"Etag">>, CurrentEtag}, {<<"Vary">>, <<"Accept">>}]}} | ExternalResponse]}; - {JsonHeaders} -> + JsonHeaders -> {[case Field of - {<<"headers">>, {JsonHeaders}} -> % add our headers - JsonHeadersEtagged = set_or_replace_header({<<"Etag">>, CurrentEtag}, JsonHeaders), - JsonHeadersVaried = set_or_replace_header({<<"Vary">>, <<"Accept">>}, JsonHeadersEtagged), - {<<"headers">>, {JsonHeadersVaried}}; + {<<"headers">>, JsonHeaders} -> % add our headers + JsonHeadersEtagged = json_apply_field({<<"Etag">>, CurrentEtag}, JsonHeaders), + JsonHeadersVaried = json_apply_field({<<"Vary">>, <<"Accept">>}, JsonHeadersEtagged), + {<<"headers">>, JsonHeadersVaried}; _ -> % skip non-header fields Field end || Field <- ExternalResponse]} diff --git a/src/couchdb/couch_query_servers.erl b/src/couchdb/couch_query_servers.erl index bb0cc853..fca7c85a 100644 --- a/src/couchdb/couch_query_servers.erl +++ b/src/couchdb/couch_query_servers.erl @@ -18,7 +18,7 @@ -export([init/1, terminate/2, handle_call/3, handle_cast/2, handle_info/2,code_change/3,stop/0]). -export([start_doc_map/2, map_docs/2, stop_doc_map/1]). -export([reduce/3, rereduce/3,validate_doc_update/5]). --export([render_doc_show/6, start_view_list/2, +-export([render_doc_show/6, render_doc_update/6, start_view_list/2, render_list_head/4, render_list_row/4, render_list_tail/1]). -export([start_filter/2, filter_doc/4, end_filter/1]). % -export([test/0]). @@ -170,6 +170,7 @@ validate_doc_update(Lang, FunSrc, EditDoc, DiskDoc, Ctx) -> after ok = ret_os_process(Lang, Pid) end. +% todo use json_apply_field append_docid(DocId, JsonReqIn) -> [{<<"docId">>, DocId} | JsonReqIn]. @@ -190,6 +191,23 @@ render_doc_show(Lang, ShowSrc, DocId, Doc, Req, Db) -> ok = ret_os_process(Lang, Pid) end. +render_doc_update(Lang, UpdateSrc, DocId, Doc, Req, Db) -> + Pid = get_os_process(Lang), + {JsonReqIn} = couch_httpd_external:json_req_obj(Req, Db), + + {JsonReq, JsonDoc} = case {DocId, Doc} of + {nil, nil} -> {{JsonReqIn}, null}; + {DocId, nil} -> {{append_docid(DocId, JsonReqIn)}, null}; + _ -> {{append_docid(DocId, JsonReqIn)}, couch_doc:to_json_obj(Doc, [revs])} + end, + try couch_os_process:prompt(Pid, + [<<"update">>, UpdateSrc, JsonDoc, JsonReq]) of + FormResp -> + FormResp + after + ok = ret_os_process(Lang, Pid) + end. + start_view_list(Lang, ListSrc) -> Pid = get_os_process(Lang), true = couch_os_process:prompt(Pid, [<<"add_fun">>, ListSrc]), -- cgit v1.2.3