You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ch...@apache.org on 2014/03/28 19:45:54 UTC
[4/7] chttpd commit: updated refs/heads/1993-bigcouch-couch-mrview to
93c771d
Use couch_mrview for view logic and callbacks
This updates the various view functions to use couch_mrview utility
functions, and removes a large amount of the view utility functions
that are better served by couch_mrview. It also uses the view
callbacks from couch_mrview, removing even more logic from this
module.
Project: http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/commit/e2d1e926
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/tree/e2d1e926
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/diff/e2d1e926
Branch: refs/heads/1993-bigcouch-couch-mrview
Commit: e2d1e926d3a224d0adb48b9ea1bbd764b4128152
Parents: faaf44e
Author: Russell Branca <ch...@gmail.com>
Authored: Thu Mar 27 12:10:11 2014 -0700
Committer: Russell Branca <ch...@gmail.com>
Committed: Fri Mar 28 11:30:39 2014 -0700
----------------------------------------------------------------------
src/chttpd_view.erl | 428 +++++++----------------------------------------
1 file changed, 61 insertions(+), 367 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/blob/e2d1e926/src/chttpd_view.erl
----------------------------------------------------------------------
diff --git a/src/chttpd_view.erl b/src/chttpd_view.erl
index 85b88a3..471c380 100644
--- a/src/chttpd_view.erl
+++ b/src/chttpd_view.erl
@@ -14,117 +14,81 @@
-include_lib("couch/include/couch_db.hrl").
-include_lib("couch_mrview/include/couch_mrview.hrl").
--export([handle_view_req/3, handle_temp_view_req/2, get_reduce_type/1,
- parse_view_params/3, view_group_etag/2, view_group_etag/3,
- parse_bool_param/1, extract_view_type/3]).
-
+-export([handle_view_req/3, handle_temp_view_req/2]).
multi_query_view(Req, Db, DDoc, ViewName, Queries) ->
- Group = couch_view_group:design_doc_to_view_group(DDoc),
- IsReduce = get_reduce_type(Req),
- ViewType = extract_view_type(ViewName, couch_view_group:get_views(Group),
- IsReduce),
- % TODO proper calculation of etag
- % Etag = view_group_etag(ViewGroup, Db, Queries),
- Etag = couch_uuids:new(),
- DefaultParams = lists:flatmap(fun({K,V}) -> parse_view_param(K,V) end,
- chttpd:qs(Req)),
- [couch_stats_collector:increment({httpd, view_reads}) || _I <- Queries],
- chttpd:etag_respond(Req, Etag, fun() ->
+ Args0 = couch_mrview_http:parse_params(Req, undefined),
+ DbName = mem3:dbname(Db#db.name),
+ Args1 = couch_util:with_db(DbName, fun(WDb) ->
+ {ok, _, _, Args} = couch_mrview_util:get_view(WDb, DDoc, ViewName, Args0),
+ Args
+ end),
+ ArgQueries = lists:map(fun({Query}) ->
+ QueryArg = couch_mrview_http:parse_params(Query, undefined, Args1),
+ couch_mrview_util:validate_args(QueryArg)
+ end, Queries),
+ {ok, Resp2} = couch_httpd:etag_maybe(Req, fun() ->
+ VAcc0 = #vacc{db=Db, req=Req, prepend="\r\n"},
+ %% TODO: proper calculation of etag
+ Etag = couch_uuids:new(),
+ Headers = [{"ETag", Etag}],
FirstChunk = "{\"results\":[",
- {ok, Resp} = chttpd:start_delayed_json_response(Req, 200, [{"Etag",Etag}], FirstChunk),
- {_, Resp1} = lists:foldl(fun({QueryProps}, {Chunk, RespAcc}) ->
- if Chunk =/= nil -> chttpd:send_delayed_chunk(Resp, Chunk); true -> ok end,
- ThisQuery = lists:flatmap(fun parse_json_view_param/1, QueryProps),
- FullParams = lists:ukeymerge(1, ThisQuery, DefaultParams),
- {ok, RespAcc1} = fabric:query_view(
- Db,
- DDoc,
- ViewName,
- fun view_callback/2,
- {nil, RespAcc},
- parse_view_params(FullParams, nil, ViewType)
- ),
- {",\n", RespAcc1}
- end, {nil,Resp}, Queries),
- chttpd:send_delayed_chunk(Resp1, "]}"),
- chttpd:end_delayed_json_response(Resp1)
- end).
-
-design_doc_view(Req, Db, DDoc, ViewName, Keys) ->
- Group = couch_view_group:design_doc_to_view_group(DDoc),
- IsReduce = get_reduce_type(Req),
- ViewType = extract_view_type(ViewName, couch_view_group:get_views(Group),
- IsReduce),
- QueryArgs = parse_view_params(Req, Keys, ViewType),
- % TODO proper calculation of etag
- % Etag = view_group_etag(ViewGroup, Db, Keys),
- Etag = couch_uuids:new(),
- couch_stats_collector:increment({httpd, view_reads}),
- chttpd:etag_respond(Req, Etag, fun() ->
- {ok, Resp} = chttpd:start_delayed_json_response(Req, 200, [{"Etag",Etag}]),
- CB = fun view_callback/2,
- {ok, Resp1} = fabric:query_view(Db, DDoc, ViewName, CB, {nil, Resp}, QueryArgs),
- chttpd:end_delayed_json_response(Resp1)
- end).
+ {ok, Resp0} = chttpd:start_delayed_json_response(VAcc0#vacc.req, 200, Headers, FirstChunk),
+ VAcc1 = VAcc0#vacc{resp=Resp0},
+ VAcc2 = lists:foldl(fun(Args, Acc0) ->
+ {ok, Acc1} = fabric:query_view(Db, DDoc, ViewName, fun couch_mrview_http:view_cb/2, Acc0, Args),
+ Acc1
+ end, VAcc1, ArgQueries),
+ {ok, Resp1} = chttpd:send_delayed_chunk(VAcc2#vacc.resp, "\r\n]}"),
+ {ok, Resp2} = chttpd:end_delayed_json_response(Resp1),
+ {ok, VAcc2#vacc{resp=Resp2}}
+ end),
+ case is_record(Resp2, vacc) of
+ true -> {ok, Resp2#vacc.resp};
+ _ -> {ok, Resp2}
+ end.
-view_callback({total_and_offset, Total, Offset}, {nil, Resp}) ->
- Chunk = "{\"total_rows\":~p,\"offset\":~p,\"rows\":[\r\n",
- {ok, Resp1} = chttpd:send_delayed_chunk(Resp, io_lib:format(Chunk, [Total, Offset])),
- {ok, {"", Resp1}};
-view_callback({total_and_offset, _, _}, Acc) ->
- % a sorted=false view where the message came in late. Ignore.
- {ok, Acc};
-view_callback({row, Row}, {nil, Resp}) ->
- % first row of a reduce view, or a sorted=false view
- {ok, Resp1} = chttpd:send_delayed_chunk(Resp, ["{\"rows\":[\r\n", ?JSON_ENCODE(Row)]),
- {ok, {",\r\n", Resp1}};
-view_callback({row, Row}, {Prepend, Resp}) ->
- {ok, Resp1} = chttpd:send_delayed_chunk(Resp, [Prepend, ?JSON_ENCODE(Row)]),
- {ok, {",\r\n", Resp1}};
-view_callback(complete, {nil, Resp}) ->
- chttpd:send_delayed_chunk(Resp, "{\"rows\":[]}");
-view_callback(complete, {_, Resp}) ->
- chttpd:send_delayed_chunk(Resp, "\r\n]}");
-view_callback({error, Reason}, {_, Resp}) ->
- chttpd:send_delayed_error(Resp, Reason).
-extract_view_type(_ViewName, [], _IsReduce) ->
- throw({not_found, missing_named_view});
-extract_view_type(ViewName, [View|Rest], IsReduce) ->
- case lists:member(ViewName, [Name || {Name, _} <- View#mrview.reduce_funs]) of
- true ->
- if IsReduce -> reduce; true -> red_map end;
- false ->
- case lists:member(ViewName, View#mrview.map_names) of
- true -> map;
- false -> extract_view_type(ViewName, Rest, IsReduce)
- end
+design_doc_view(Req, Db, DDoc, ViewName, Keys) ->
+ Args0 = couch_mrview_http:parse_params(Req, Keys),
+ ETagFun = fun(Sig, Acc0) ->
+ couch_mrview_http:check_view_etag(Sig, Acc0, Req)
+ end,
+ Args = Args0#mrargs{preflight_fun=ETagFun},
+ {ok, Resp} = couch_httpd:etag_maybe(Req, fun() ->
+ VAcc0 = #vacc{db=Db, req=Req},
+ fabric:query_view(Db, DDoc, ViewName, fun couch_mrview_http:view_cb/2, VAcc0, Args)
+ end),
+ case is_record(Resp, vacc) of
+ true -> {ok, Resp#vacc.resp};
+ _ -> {ok, Resp}
end.
handle_view_req(#httpd{method='GET',
path_parts=[_, _, _, _, ViewName]}=Req, Db, DDoc) ->
- Keys = chttpd:qs_json_value(Req, "keys", nil),
+ couch_stats_collector:increment({httpd, view_reads}),
+ Keys = chttpd:qs_json_value(Req, "keys", undefined),
design_doc_view(Req, Db, DDoc, ViewName, Keys);
handle_view_req(#httpd{method='POST',
path_parts=[_, _, _, _, ViewName]}=Req, Db, DDoc) ->
- {Fields} = chttpd:json_body_obj(Req),
- Queries = couch_util:get_value(<<"queries">>, Fields),
- Keys = couch_util:get_value(<<"keys">>, Fields),
+ Props = couch_httpd:json_body_obj(Req),
+ Keys = couch_mrview_util:get_view_keys(Props),
+ Queries = couch_mrview_util:get_view_queries(Props),
case {Queries, Keys} of
- {Queries, undefined} when is_list(Queries) ->
- multi_query_view(Req, Db, DDoc, ViewName, Queries);
- {undefined, Keys} when is_list(Keys) ->
- design_doc_view(Req, Db, DDoc, ViewName, Keys);
- {undefined, undefined} ->
- throw({bad_request, "POST body must contain `keys` or `queries` field"});
- {undefined, _} ->
- throw({bad_request, "`keys` body member must be an array"});
- {_, undefined} ->
- throw({bad_request, "`queries` body member must be an array"});
- {_, _} ->
- throw({bad_request, "`keys` and `queries` are mutually exclusive"})
+ {Queries, undefined} when is_list(Queries) ->
+ [couch_stats_collector:increment({httpd, view_reads}) || _I <- Queries],
+ multi_query_view(Req, Db, DDoc, ViewName, Queries);
+ {undefined, Keys} when is_list(Keys) ->
+ couch_stats_collector:increment({httpd, view_reads}),
+ design_doc_view(Req, Db, DDoc, ViewName, Keys);
+ {undefined, undefined} ->
+ throw({
+ bad_request,
+ "POST body must contain `keys` or `queries` field"
+ });
+ {_, _} ->
+ throw({bad_request, "`keys` and `queries` are mutually exclusive"})
end;
handle_view_req(Req, _Db, _DDoc) ->
@@ -133,273 +97,3 @@ handle_view_req(Req, _Db, _DDoc) ->
handle_temp_view_req(Req, _Db) ->
Msg = <<"Temporary views are not supported in CouchDB">>,
chttpd:send_error(Req, 403, forbidden, Msg).
-
-reverse_key_default(?MIN_STR) -> ?MAX_STR;
-reverse_key_default(?MAX_STR) -> ?MIN_STR;
-reverse_key_default(Key) -> Key.
-
-get_reduce_type(Req) ->
- case chttpd:qs_value(Req, "reduce", "true") of
- "true" ->
- true;
- "false" ->
- false;
- _Error ->
- throw({bad_request, "`reduce` qs param must be `true` or `false`"})
- end.
-
-parse_view_params(Req, Keys, ViewType) when not is_list(Req) ->
- QueryParams = lists:flatmap(fun({K,V}) -> parse_view_param(K,V) end,
- chttpd:qs(Req)),
- parse_view_params(QueryParams, Keys, ViewType);
-parse_view_params(QueryParams, Keys, ViewType) ->
- IsMultiGet = (Keys =/= nil),
- Args = #mrargs{
- view_type=ViewType,
- multi_get=IsMultiGet,
- keys=Keys
- },
- QueryArgs = lists:foldl(fun({K, V}, Args2) ->
- validate_view_query(K, V, Args2)
- end, Args, QueryParams),
-
- GroupLevel = QueryArgs#mrargs.group_level,
- case {ViewType, GroupLevel, IsMultiGet} of
- {reduce, exact, true} ->
- QueryArgs;
- {reduce, _, false} ->
- QueryArgs;
- {reduce, _, _} ->
- Msg = <<"Multi-key fetchs for reduce "
- "view must include `group=true`">>,
- throw({query_parse_error, Msg});
- _ ->
- QueryArgs
- end,
- QueryArgs.
-
-parse_json_view_param({<<"key">>, V}) ->
- [{start_key, V}, {end_key, V}];
-parse_json_view_param({<<"startkey_docid">>, V}) ->
- [{start_key_docid, V}];
-parse_json_view_param({<<"endkey_docid">>, V}) ->
- [{end_key_docid, V}];
-parse_json_view_param({<<"startkey">>, V}) ->
- [{start_key, V}];
-parse_json_view_param({<<"endkey">>, V}) ->
- [{end_key, V}];
-parse_json_view_param({<<"limit">>, V}) when is_integer(V), V > 0 ->
- [{limit, V}];
-parse_json_view_param({<<"stale">>, <<"ok">>}) ->
- [{stale, ok}];
-parse_json_view_param({<<"stale">>, <<"update_after">>}) ->
- [{stale, update_after}];
-parse_json_view_param({<<"descending">>, V}) when is_boolean(V) ->
- [{descending, V}];
-parse_json_view_param({<<"skip">>, V}) when is_integer(V) ->
- [{skip, V}];
-parse_json_view_param({<<"group">>, true}) ->
- [{group_level, exact}];
-parse_json_view_param({<<"group">>, false}) ->
- [{group_level, 0}];
-parse_json_view_param({<<"group_level">>, V}) when is_integer(V), V > 0 ->
- [{group_level, V}];
-parse_json_view_param({<<"inclusive_end">>, V}) when is_boolean(V) ->
- [{inclusive_end, V}];
-parse_json_view_param({<<"reduce">>, V}) when is_boolean(V) ->
- [{reduce, V}];
-parse_json_view_param({<<"include_docs">>, V}) when is_boolean(V) ->
- [{include_docs, V}];
-parse_json_view_param({<<"conflicts">>, V}) when is_boolean(V) ->
- [{conflicts, V}];
-parse_json_view_param({<<"list">>, V}) ->
- [{list, couch_util:to_binary(V)}];
-parse_json_view_param({<<"sorted">>, V}) when is_boolean(V) ->
- [{sorted, V}];
-parse_json_view_param({K, V}) ->
- [{extra, {K, V}}].
-
-parse_view_param("", _) ->
- [];
-parse_view_param("key", Value) ->
- JsonKey = ?JSON_DECODE(Value),
- [{start_key, JsonKey}, {end_key, JsonKey}];
-parse_view_param("startkey_docid", Value) ->
- [{start_key_docid, ?l2b(Value)}];
-parse_view_param("endkey_docid", Value) ->
- [{end_key_docid, ?l2b(Value)}];
-parse_view_param("startkey", Value) ->
- [{start_key, ?JSON_DECODE(Value)}];
-parse_view_param("endkey", Value) ->
- [{end_key, ?JSON_DECODE(Value)}];
-parse_view_param("limit", Value) ->
- [{limit, parse_positive_int_param(Value)}];
-parse_view_param("count", _Value) ->
- throw({query_parse_error, <<"Query parameter 'count' is now 'limit'.">>});
-parse_view_param("stale", "ok") ->
- [{stale, ok}];
-parse_view_param("stale", "update_after") ->
- [{stale, update_after}];
-parse_view_param("stale", _Value) ->
- throw({query_parse_error,
- <<"stale only available as stale=ok or as stale=update_after">>});
-parse_view_param("update", _Value) ->
- throw({query_parse_error, <<"update=false is now stale=ok">>});
-parse_view_param("descending", Value) ->
- [{descending, parse_bool_param(Value)}];
-parse_view_param("skip", Value) ->
- [{skip, parse_int_param(Value)}];
-parse_view_param("group", Value) ->
- case parse_bool_param(Value) of
- true -> [{group_level, exact}];
- false -> [{group_level, 0}]
- end;
-parse_view_param("group_level", Value) ->
- [{group_level, parse_positive_int_param(Value)}];
-parse_view_param("inclusive_end", Value) ->
- [{inclusive_end, parse_bool_param(Value)}];
-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("conflicts", Value) ->
- [{conflicts, 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("sorted", Value) ->
- [{sorted, parse_bool_param(Value)}];
-parse_view_param(Key, Value) ->
- [{extra, {Key, Value}}].
-
-validate_view_query(start_key, Value, Args) ->
- case Args#mrargs.multi_get of
- true ->
- Msg = <<"Query parameter `start_key` is "
- "not compatiible with multi-get">>,
- throw({query_parse_error, Msg});
- _ ->
- Args#mrargs{start_key=Value}
- end;
-validate_view_query(start_key_docid, Value, Args) ->
- Args#mrargs{start_key_docid=Value};
-validate_view_query(end_key, Value, Args) ->
- case Args#mrargs.multi_get of
- true->
- Msg = <<"Query paramter `end_key` is "
- "not compatibile with multi-get">>,
- throw({query_parse_error, Msg});
- _ ->
- Args#mrargs{end_key=Value}
- end;
-validate_view_query(end_key_docid, Value, Args) ->
- Args#mrargs{end_key_docid=Value};
-validate_view_query(limit, Value, Args) ->
- Args#mrargs{limit=Value};
-validate_view_query(list, Value, Args) ->
- Args#mrargs{list=Value};
-validate_view_query(stale, Value, Args) ->
- Args#mrargs{stale=Value};
-validate_view_query(descending, true, Args) ->
- case Args#mrargs.direction of
- rev -> Args; % Already reversed
- fwd ->
- Args#mrargs{
- direction = rev,
- start_key_docid =
- reverse_key_default(Args#mrargs.start_key_docid),
- end_key_docid =
- reverse_key_default(Args#mrargs.end_key_docid)
- }
- end;
-validate_view_query(descending, false, Args) ->
- Args; % Ignore default condition
-validate_view_query(skip, Value, Args) ->
- Args#mrargs{skip=Value};
-validate_view_query(group_level, Value, Args) ->
- case Args#mrargs.view_type of
- reduce ->
- Args#mrargs{group_level=Value};
- _ ->
- Msg = <<"Invalid URL parameter 'group' or "
- " 'group_level' for non-reduce view.">>,
- throw({query_parse_error, Msg})
- end;
-validate_view_query(inclusive_end, Value, Args) ->
- Args#mrargs{inclusive_end=Value};
-validate_view_query(reduce, false, Args) ->
- Args;
-validate_view_query(reduce, _, Args) ->
- case Args#mrargs.view_type of
- map ->
- Msg = <<"Invalid URL parameter `reduce` for map view.">>,
- throw({query_parse_error, Msg});
- _ ->
- Args
- end;
-validate_view_query(include_docs, true, Args) ->
- case Args#mrargs.view_type of
- reduce ->
- Msg = <<"Query paramter `include_docs` "
- "is invalid for reduce views.">>,
- throw({query_parse_error, Msg});
- _ ->
- Args#mrargs{include_docs=true}
- end;
-validate_view_query(include_docs, _Value, Args) ->
- Args;
-validate_view_query(conflicts, true, Args) ->
- case Args#mrargs.view_type of
- reduce ->
- Msg = <<"Query parameter `conflicts` "
- "is invalid for reduce views.">>,
- throw({query_parse_error, Msg});
- _ ->
- Args#mrargs{extra = [conflicts|Args#mrargs.extra]}
- end;
-validate_view_query(conflicts, _Value, Args) ->
- Args;
-validate_view_query(sorted, false, Args) ->
- Args#mrargs{sorted=false};
-validate_view_query(sorted, _Value, Args) ->
- Args;
-validate_view_query(extra, _Value, Args) ->
- Args.
-
-view_group_etag(Group, Db) ->
- view_group_etag(Group, Db, nil).
-
-view_group_etag(Group, _Db, Extra) ->
- Sig = couch_view_group:get_signature(Group),
- CurrentSeq = couch_view_group:get_current_seq(Group),
- % This is not as granular as it could be.
- % If there are updates to the db that do not effect the view index,
- % they will change the Etag. For more granular Etags we'd need to keep
- % track of the last Db seq that caused an index change.
- chttpd:make_etag({Sig, CurrentSeq, Extra}).
-
-parse_bool_param("true") -> true;
-parse_bool_param("false") -> false;
-parse_bool_param(Val) ->
- Msg = io_lib:format("Invalid value for boolean paramter: ~p", [Val]),
- throw({query_parse_error, ?l2b(Msg)}).
-
-parse_int_param(Val) ->
- case (catch list_to_integer(Val)) of
- IntVal when is_integer(IntVal) ->
- IntVal;
- _ ->
- Msg = io_lib:format("Invalid value for integer parameter: ~p", [Val]),
- throw({query_parse_error, ?l2b(Msg)})
- end.
-
-parse_positive_int_param(Val) ->
- case parse_int_param(Val) of
- IntVal when IntVal >= 0 ->
- IntVal;
- _ ->
- Fmt = "Invalid value for positive integer parameter: ~p",
- Msg = io_lib:format(Fmt, [Val]),
- throw({query_parse_error, ?l2b(Msg)})
- end.