From 9ccb235a2d58d6b7caf406952f18ca13d9889f3e Mon Sep 17 00:00:00 2001 From: John Christopher Anderson Date: Fri, 10 Jul 2009 00:59:36 +0000 Subject: Apply patch from Benoit Chesneau's COUCHDB-404 Restores 0.8-style /db/_view view urls and adds an option to render views and documents as other formats like: /db/docid?show=blog/post /db/_view/blog/posts?list=index We're retaining the longer _design/appname paths as well because that resource is valuable for reverse proxies and rewriters. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@792771 13f79535-47bb-0310-9956-ffa450edef68 --- src/couchdb/couch_db.hrl | 3 +- src/couchdb/couch_httpd_db.erl | 89 ++++++++++++++++++++++++---------------- src/couchdb/couch_httpd_show.erl | 56 +++++++++++++------------ src/couchdb/couch_httpd_view.erl | 49 +++++++++++++++++++++- 4 files changed, 132 insertions(+), 65 deletions(-) (limited to 'src/couchdb') diff --git a/src/couchdb/couch_db.hrl b/src/couchdb/couch_db.hrl index abb301eb..f74d0ae0 100644 --- a/src/couchdb/couch_db.hrl +++ b/src/couchdb/couch_db.hrl @@ -167,7 +167,8 @@ include_docs = false, stale = false, multi_get = false, - callback = nil + callback = nil, + list = nil }). -record(view_fold_helper_funs, { diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl index f5bebc20..fcbdc4c6 100644 --- a/src/couchdb/couch_httpd_db.erl +++ b/src/couchdb/couch_httpd_db.erl @@ -26,7 +26,8 @@ -record(doc_query_args, { options = [], rev = nil, - open_revs = [] + open_revs = [], + show = nil }). % Database request handlers @@ -192,7 +193,6 @@ handle_design_info_req(#httpd{ handle_design_info_req(Req, _Db) -> send_method_not_allowed(Req, "GET"). - create_db_req(#httpd{user_ctx=UserCtx}=Req, DbName) -> ok = couch_httpd:verify_is_server_admin(Req), case couch_server:create(DbName, [{user_ctx, UserCtx}]) of @@ -558,8 +558,6 @@ all_docs_view(Req, Db, Keys) -> end end). - - db_doc_req(#httpd{method='DELETE'}=Req, Db, DocId) -> % check for the existence of the doc to handle the 404 case. couch_doc_open(Db, DocId, nil, []), @@ -572,43 +570,50 @@ db_doc_req(#httpd{method='DELETE'}=Req, Db, DocId) -> db_doc_req(#httpd{method='GET'}=Req, Db, DocId) -> #doc_query_args{ + show = Format, rev = Rev, open_revs = Revs, options = Options } = parse_doc_query(Req), - case Revs of - [] -> - Doc = couch_doc_open(Db, DocId, Rev, Options), - DiskEtag = couch_httpd:doc_etag(Doc), - couch_httpd:etag_respond(Req, DiskEtag, fun() -> - Headers = case Doc#doc.meta of - [] -> [{"Etag", DiskEtag}]; % output etag only when we have no meta - _ -> [] - end, - send_json(Req, 200, Headers, couch_doc:to_json_obj(Doc, Options)) - end); - _ -> - {ok, Results} = couch_db:open_doc_revs(Db, DocId, Revs, Options), - {ok, Resp} = start_json_response(Req, 200), - send_chunk(Resp, "["), - % We loop through the docs. The first time through the separator - % is whitespace, then a comma on subsequent iterations. - lists:foldl( - fun(Result, AccSeparator) -> - case Result of - {ok, Doc} -> - JsonDoc = couch_doc:to_json_obj(Doc, Options), - Json = ?JSON_ENCODE({[{ok, JsonDoc}]}), - send_chunk(Resp, AccSeparator ++ Json); - {{not_found, missing}, RevId} -> - Json = ?JSON_ENCODE({[{"missing", RevId}]}), - send_chunk(Resp, AccSeparator ++ Json) + case Format of + nil -> + case Revs of + [] -> + Doc = couch_doc_open(Db, DocId, Rev, Options), + DiskEtag = couch_httpd:doc_etag(Doc), + couch_httpd:etag_respond(Req, DiskEtag, fun() -> + Headers = case Doc#doc.meta of + [] -> [{"Etag", DiskEtag}]; % output etag only when we have no meta + _ -> [] end, - "," % AccSeparator now has a comma - end, - "", Results), - send_chunk(Resp, "]"), - end_json_response(Resp) + send_json(Req, 200, Headers, couch_doc:to_json_obj(Doc, Options)) + end); + _ -> + {ok, Results} = couch_db:open_doc_revs(Db, DocId, Revs, Options), + {ok, Resp} = start_json_response(Req, 200), + send_chunk(Resp, "["), + % We loop through the docs. The first time through the separator + % is whitespace, then a comma on subsequent iterations. + lists:foldl( + fun(Result, AccSeparator) -> + case Result of + {ok, Doc} -> + JsonDoc = couch_doc:to_json_obj(Doc, Options), + Json = ?JSON_ENCODE({[{ok, JsonDoc}]}), + send_chunk(Resp, AccSeparator ++ Json); + {{not_found, missing}, RevId} -> + Json = ?JSON_ENCODE({[{"missing", RevId}]}), + send_chunk(Resp, AccSeparator ++ Json) + end, + "," % AccSeparator now has a comma + end, + "", Results), + send_chunk(Resp, "]"), + end_json_response(Resp) + end; + _ -> + {DesignName, ShowName} = Format, + couch_httpd_show:handle_doc_show(Req, DesignName, ShowName, DocId, Db) end; db_doc_req(#httpd{method='POST'}=Req, Db, DocId) -> @@ -843,6 +848,16 @@ db_attachment_req(#httpd{method=Method}=Req, Db, DocId, FileNameParts) db_attachment_req(Req, _Db, _DocId, _FileNameParts) -> send_method_not_allowed(Req, "DELETE,GET,HEAD,PUT"). +parse_doc_format(FormatStr) when is_binary(FormatStr) -> + parse_doc_format(?b2l(FormatStr)); +parse_doc_format(FormatStr) when is_list(FormatStr) -> + SplitFormat = lists:splitwith(fun($/) -> false; (_) -> true end, FormatStr), + case SplitFormat of + {DesignName, [$/ | ShowName]} -> {?l2b(DesignName), ?l2b(ShowName)}; + _Else -> throw({bad_request, <<"Invalid doc format">>}) + end; +parse_doc_format(_BadFormatStr) -> + throw({bad_request, <<"Invalid doc format">>}). parse_doc_query(Req) -> lists:foldl(fun({Key,Value}, Args) -> @@ -875,6 +890,8 @@ parse_doc_query(Req) -> {"open_revs", RevsJsonStr} -> JsonArray = ?JSON_DECODE(RevsJsonStr), Args#doc_query_args{open_revs=[couch_doc:parse_rev(Rev) || Rev <- JsonArray]}; + {"show", FormatStr} -> + Args#doc_query_args{show=parse_doc_format(FormatStr)}; _Else -> % unknown key value pair, ignore. Args end diff --git a/src/couchdb/couch_httpd_show.erl b/src/couchdb/couch_httpd_show.erl index c29d89c5..9c873e8f 100644 --- a/src/couchdb/couch_httpd_show.erl +++ b/src/couchdb/couch_httpd_show.erl @@ -12,7 +12,8 @@ -module(couch_httpd_show). --export([handle_doc_show_req/2, handle_view_list_req/2]). +-export([handle_doc_show_req/2, handle_view_list_req/2, + handle_doc_show/5, handle_view_list/6]). -include("couch_db.hrl"). @@ -26,58 +27,59 @@ handle_doc_show_req(#httpd{ method='GET', path_parts=[_DbName, _Design, DesignName, _Show, ShowName, 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">>), - ShowSrc = get_nested_json_value({Props}, [<<"shows">>, ShowName]), - Doc = try couch_httpd_db:couch_doc_open(Db, DocId, nil, [conflicts]) of - FoundDoc -> FoundDoc - catch - _ -> nil - end, - send_doc_show_response(Lang, ShowSrc, DocId, Doc, Req, Db); - + handle_doc_show(Req, DesignName, ShowName, DocId, Db); + handle_doc_show_req(#httpd{ method='GET', path_parts=[_DbName, _Design, DesignName, _Show, ShowName] }=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">>), - ShowSrc = get_nested_json_value({Props}, [<<"shows">>, ShowName]), - send_doc_show_response(Lang, ShowSrc, nil, nil, Req, Db); + handle_doc_show(Req, DesignName, ShowName, nil, Db); handle_doc_show_req(#httpd{method='GET'}=Req, _Db) -> send_error(Req, 404, <<"show_error">>, <<"Invalid path.">>); handle_doc_show_req(Req, _Db) -> send_method_not_allowed(Req, "GET,HEAD"). - -handle_view_list_req(#httpd{method='GET', - path_parts=[_DbName, _Design, DesignName, _List, ListName, ViewName]}=Req, Db) -> + +handle_doc_show(Req, DesignName, ShowName, DocId, Db) -> DesignId = <<"_design/", DesignName/binary>>, #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, nil, []), Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>), - ListSrc = get_nested_json_value({Props}, [<<"lists">>, ListName]), - send_view_list_response(Lang, ListSrc, ViewName, DesignId, Req, Db, nil); + ShowSrc = get_nested_json_value({Props}, [<<"shows">>, ShowName]), + Doc = case DocId of + nil -> nil; + _ -> + try couch_httpd_db:couch_doc_open(Db, DocId, nil, [conflicts]) of + FoundDoc -> FoundDoc + catch + _ -> nil + end + end, + send_doc_show_response(Lang, ShowSrc, DocId, Doc, Req, Db). + +handle_view_list_req(#httpd{method='GET', + path_parts=[_DbName, _Design, DesignName, _List, ListName, ViewName]}=Req, Db) -> + handle_view_list(Req, DesignName, ListName, ViewName, Db, nil); handle_view_list_req(#httpd{method='GET'}=Req, _Db) -> send_error(Req, 404, <<"list_error">>, <<"Invalid path.">>); handle_view_list_req(#httpd{method='POST', path_parts=[_DbName, _Design, DesignName, _List, ListName, ViewName]}=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">>), - ListSrc = get_nested_json_value({Props}, [<<"lists">>, ListName]), ReqBody = couch_httpd:body(Req), {Props2} = ?JSON_DECODE(ReqBody), Keys = proplists:get_value(<<"keys">>, Props2, nil), - send_view_list_response(Lang, ListSrc, ViewName, DesignId, Req#httpd{req_body=ReqBody}, Db, Keys); + handle_view_list(Req#httpd{req_body=ReqBody}, DesignName, ListName, ViewName, Db, Keys); handle_view_list_req(Req, _Db) -> send_method_not_allowed(Req, "GET,POST,HEAD"). +handle_view_list(Req, DesignName, ListName, ViewName, Db, Keys) -> + DesignId = <<"_design/", DesignName/binary>>, + #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, nil, []), + Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>), + ListSrc = get_nested_json_value({Props}, [<<"lists">>, ListName]), + send_view_list_response(Lang, ListSrc, ViewName, DesignId, Req, Db, Keys). get_nested_json_value({Props}, [Key|Keys]) -> case proplists:get_value(Key, Props, nil) of diff --git a/src/couchdb/couch_httpd_view.erl b/src/couchdb/couch_httpd_view.erl index 2028840c..0150582a 100644 --- a/src/couchdb/couch_httpd_view.erl +++ b/src/couchdb/couch_httpd_view.erl @@ -13,7 +13,7 @@ -module(couch_httpd_view). -include("couch_db.hrl"). --export([handle_view_req/2,handle_temp_view_req/2]). +-export([handle_view_req/2,handle_temp_view_req/2,handle_db_view_req/2]). -export([get_stale_type/1, get_reduce_type/1, parse_view_params/3]). -export([make_view_fold_fun/6, finish_view_fold/3, view_row_obj/3]). @@ -73,6 +73,49 @@ handle_view_req(#httpd{method='POST', handle_view_req(Req, _Db) -> send_method_not_allowed(Req, "GET,POST,HEAD"). +handle_db_view_req(#httpd{method='GET', + path_parts=[_Db, _View, DName, ViewName]}=Req, Db) -> + QueryArgs = couch_httpd_view:parse_view_params(Req, nil, nil), + #view_query_args{ + list = ListName + } = QueryArgs, + ?LOG_DEBUG("ici ~p", [ListName]), + case ListName of + nil -> couch_httpd_view:design_doc_view(Req, Db, DName, ViewName, nil); + _ -> + couch_httpd_show:handle_view_list(Req, DName, ListName, ViewName, Db, nil) + end; + +handle_db_view_req(#httpd{method='POST', + path_parts=[_Db, _View, DName, ViewName]}=Req, Db) -> + QueryArgs = couch_httpd_view:parse_view_params(Req, nil, nil), + #view_query_args{ + list = ListName + } = QueryArgs, + case ListName of + nil -> + {Fields} = couch_httpd:json_body_obj(Req), + case proplists:get_value(<<"keys">>, Fields, nil) of + nil -> + Fmt = "POST to view ~p/~p in database ~p with no keys member.", + ?LOG_DEBUG(Fmt, [DName, ViewName, Db]), + couch_httpd_view:design_doc_view(Req, Db, DName, ViewName, nil); + Keys when is_list(Keys) -> + couch_httpd_view:design_doc_view(Req, Db, DName, ViewName, Keys); + _ -> + throw({bad_request, "`keys` member must be a array."}) + end; + _ -> + ReqBody = couch_httpd:body(Req), + {Props2} = ?JSON_DECODE(ReqBody), + Keys = proplists:get_value(<<"keys">>, Props2, nil), + couch_httpd_show:handle_view_list(Req#httpd{req_body=ReqBody}, + DName, ListName, ViewName, Db, Keys) + end; + +handle_db_view_req(Req, _Db) -> + send_method_not_allowed(Req, "GET,POST,HEAD"). + handle_temp_view_req(#httpd{method='POST'}=Req, Db) -> couch_stats_collector:increment({httpd, temporary_view_reads}), {Props} = couch_httpd:json_body_obj(Req), @@ -269,6 +312,8 @@ parse_view_param("reduce", Value) -> [{reduce, parse_bool_param(Value)}]; parse_view_param("include_docs", Value) -> [{include_docs, parse_bool_param(Value)}]; +parse_view_param("list", Value) -> + [{list, ?l2b(Value)}]; parse_view_param("callback", _) -> []; % Verified in the JSON response functions parse_view_param(Key, Value) -> @@ -298,6 +343,8 @@ validate_view_query(end_docid, Value, Args) -> Args#view_query_args{end_docid=Value}; validate_view_query(limit, Value, Args) -> Args#view_query_args{limit=Value}; +validate_view_query(list, Value, Args) -> + Args#view_query_args{list=Value}; validate_view_query(stale, _, Args) -> Args; validate_view_query(descending, true, Args) -> -- cgit v1.2.3