summaryrefslogtreecommitdiff
path: root/apps/couch/src/couch_httpd_view.erl
diff options
context:
space:
mode:
authorRobert Newson <robert.newson@cloudant.com>2011-06-13 14:59:55 +0100
committerRobert Newson <robert.newson@cloudant.com>2011-06-13 14:59:55 +0100
commite9a5a6f90a021db1db8a7e55ec797a4c86edcad6 (patch)
tree06d0c28969cfcf54b2f137e7407b097f73aa0f21 /apps/couch/src/couch_httpd_view.erl
parent266ba88ac6ded40087e0211ad9e75e4ce64e66cb (diff)
parent3c1a0d7e2c9adef4f8b20c9df205a86e5c0feefb (diff)
Merge CouchDB 1.1
Diffstat (limited to 'apps/couch/src/couch_httpd_view.erl')
-rw-r--r--apps/couch/src/couch_httpd_view.erl133
1 files changed, 97 insertions, 36 deletions
diff --git a/apps/couch/src/couch_httpd_view.erl b/apps/couch/src/couch_httpd_view.erl
index cb387d1b..b71fc2c6 100644
--- a/apps/couch/src/couch_httpd_view.erl
+++ b/apps/couch/src/couch_httpd_view.erl
@@ -15,10 +15,10 @@
-export([handle_view_req/3,handle_temp_view_req/2]).
--export([get_stale_type/1, get_reduce_type/1, parse_view_params/3]).
--export([make_view_fold_fun/7, finish_view_fold/4, finish_view_fold/5, view_row_obj/3]).
--export([view_group_etag/2, view_group_etag/3, make_reduce_fold_funs/6]).
--export([design_doc_view/5, parse_bool_param/1, doc_member/2]).
+-export([parse_view_params/3]).
+-export([make_view_fold_fun/7, finish_view_fold/4, finish_view_fold/5, view_row_obj/4]).
+-export([view_etag/3, view_etag/4, make_reduce_fold_funs/6]).
+-export([design_doc_view/5, parse_bool_param/1, doc_member/3]).
-export([make_key_options/1, load_view/4]).
-import(couch_httpd,
@@ -57,7 +57,8 @@ design_doc_view(Req, Db, DName, ViewName, Keys) ->
handle_view_req(#httpd{method='GET',
path_parts=[_, _, DName, _, ViewName]}=Req, Db, _DDoc) ->
- design_doc_view(Req, Db, DName, ViewName, nil);
+ Keys = couch_httpd:qs_json_value(Req, "keys", nil),
+ design_doc_view(Req, Db, DName, ViewName, Keys);
handle_view_req(#httpd{method='POST',
path_parts=[_, _, DName, _, ViewName]}=Req, Db, _DDoc) ->
@@ -113,7 +114,7 @@ output_map_view(Req, View, Group, Db, QueryArgs, nil) ->
limit = Limit,
skip = SkipCount
} = QueryArgs,
- CurrentEtag = view_group_etag(Group, Db),
+ CurrentEtag = view_etag(Db, Group, View),
couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
{ok, RowCount} = couch_view:get_row_count(View),
FoldlFun = make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, Group#group.current_seq, RowCount, #view_fold_helper_funs{reduce_count=fun couch_view:reduce_to_count/1}),
@@ -129,7 +130,7 @@ output_map_view(Req, View, Group, Db, QueryArgs, Keys) ->
limit = Limit,
skip = SkipCount
} = QueryArgs,
- CurrentEtag = view_group_etag(Group, Db, Keys),
+ CurrentEtag = view_etag(Db, Group, View, Keys),
couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
{ok, RowCount} = couch_view:get_row_count(View),
FoldAccInit = {Limit, SkipCount, undefined, []},
@@ -154,7 +155,7 @@ output_reduce_view(Req, Db, View, Group, QueryArgs, nil) ->
skip = Skip,
group_level = GroupLevel
} = QueryArgs,
- CurrentEtag = view_group_etag(Group, Db),
+ CurrentEtag = view_etag(Db, Group, View),
couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
{ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Req, GroupLevel,
QueryArgs, CurrentEtag, Group#group.current_seq,
@@ -172,7 +173,7 @@ output_reduce_view(Req, Db, View, Group, QueryArgs, Keys) ->
skip = Skip,
group_level = GroupLevel
} = QueryArgs,
- CurrentEtag = view_group_etag(Group, Db, Keys),
+ CurrentEtag = view_etag(Db, Group, View, Keys),
couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
{ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Req, GroupLevel,
QueryArgs, CurrentEtag, Group#group.current_seq,
@@ -246,7 +247,7 @@ parse_view_params(Req, Keys, ViewType) ->
QueryArgs = lists:foldl(fun({K, V}, Args2) ->
validate_view_query(K, V, Args2)
end, Args, lists:reverse(QueryParams)), % Reverse to match QS order.
-
+ warn_on_empty_key_range(QueryArgs),
GroupLevel = QueryArgs#view_query_args.group_level,
case {ViewType, GroupLevel, IsMultiGet} of
{reduce, exact, true} ->
@@ -268,22 +269,37 @@ parse_view_param("", _) ->
parse_view_param("key", Value) ->
JsonKey = ?JSON_DECODE(Value),
[{start_key, JsonKey}, {end_key, JsonKey}];
+% TODO: maybe deprecate startkey_docid
parse_view_param("startkey_docid", Value) ->
[{start_docid, ?l2b(Value)}];
+parse_view_param("start_key_doc_id", Value) ->
+ [{start_docid, ?l2b(Value)}];
+% TODO: maybe deprecate endkey_docid
parse_view_param("endkey_docid", Value) ->
[{end_docid, ?l2b(Value)}];
+parse_view_param("end_key_doc_id", Value) ->
+ [{end_docid, ?l2b(Value)}];
+% TODO: maybe deprecate startkey
parse_view_param("startkey", Value) ->
[{start_key, ?JSON_DECODE(Value)}];
+parse_view_param("start_key", Value) ->
+ [{start_key, ?JSON_DECODE(Value)}];
+% TODO: maybe deprecate endkey
parse_view_param("endkey", Value) ->
[{end_key, ?JSON_DECODE(Value)}];
+parse_view_param("end_key", 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">>});
+ 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) ->
@@ -303,6 +319,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("conflicts", Value) ->
+ [{conflicts, parse_bool_param(Value)}];
parse_view_param("list", Value) ->
[{list, ?l2b(Value)}];
parse_view_param("callback", _) ->
@@ -310,6 +328,26 @@ parse_view_param("callback", _) ->
parse_view_param(Key, Value) ->
[{extra, {Key, Value}}].
+warn_on_empty_key_range(#view_query_args{start_key=undefined}) ->
+ ok;
+warn_on_empty_key_range(#view_query_args{end_key=undefined}) ->
+ ok;
+warn_on_empty_key_range(#view_query_args{start_key=A, end_key=A}) ->
+ ok;
+warn_on_empty_key_range(#view_query_args{
+ start_key=StartKey, end_key=EndKey, direction=Dir}) ->
+ case {Dir, couch_view:less_json(StartKey, EndKey)} of
+ {fwd, false} ->
+ throw({query_parse_error,
+ <<"No rows can match your key range, reverse your ",
+ "start_key and end_key or set descending=true">>});
+ {rev, true} ->
+ throw({query_parse_error,
+ <<"No rows can match your key range, reverse your ",
+ "start_key and end_key or set descending=false">>});
+ _ -> ok
+ end.
+
validate_view_query(start_key, Value, Args) ->
case Args#view_query_args.multi_get of
true ->
@@ -336,6 +374,10 @@ 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, ok, Args) ->
+ Args#view_query_args{stale=ok};
+validate_view_query(stale, update_after, Args) ->
+ Args#view_query_args{stale=update_after};
validate_view_query(stale, _, Args) ->
Args;
validate_view_query(descending, true, Args) ->
@@ -387,6 +429,15 @@ validate_view_query(include_docs, true, Args) ->
% Use the view_query_args record's default value
validate_view_query(include_docs, _Value, Args) ->
Args;
+validate_view_query(conflicts, true, Args) ->
+ case Args#view_query_args.view_type of
+ reduce ->
+ Msg = <<"Query parameter `conflicts` "
+ "is invalid for reduce views.">>,
+ throw({query_parse_error, Msg});
+ _ ->
+ Args#view_query_args{conflicts = true}
+ end;
validate_view_query(extra, _Value, Args) ->
Args.
@@ -398,7 +449,8 @@ make_view_fold_fun(Req, QueryArgs, Etag, Db, UpdateSeq, TotalViewCount, HelperFu
} = apply_default_helper_funs(HelperFuns),
#view_query_args{
- include_docs = IncludeDocs
+ include_docs = IncludeDocs,
+ conflicts = Conflicts
} = QueryArgs,
fun({{Key, DocId}, Value}, OffsetReds,
@@ -416,12 +468,12 @@ make_view_fold_fun(Req, QueryArgs, Etag, Db, UpdateSeq, TotalViewCount, HelperFu
{ok, Resp2, RowFunAcc0} = StartRespFun(Req, Etag,
TotalViewCount, Offset, RowFunAcc, UpdateSeq),
{Go, RowFunAcc2} = SendRowFun(Resp2, Db, {{Key, DocId}, Value},
- IncludeDocs, RowFunAcc0),
+ IncludeDocs, Conflicts, RowFunAcc0),
{Go, {AccLimit - 1, 0, Resp2, RowFunAcc2}};
{AccLimit, _, Resp} when (AccLimit > 0) ->
% rendering all other rows
{Go, RowFunAcc2} = SendRowFun(Resp, Db, {{Key, DocId}, Value},
- IncludeDocs, RowFunAcc),
+ IncludeDocs, Conflicts, RowFunAcc),
{Go, {AccLimit - 1, 0, Resp, RowFunAcc2}}
end
end.
@@ -497,7 +549,7 @@ apply_default_helper_funs(
end,
SendRow2 = case SendRow of
- undefined -> fun send_json_view_row/5;
+ undefined -> fun send_json_view_row/6;
_ -> SendRow
end,
@@ -570,8 +622,8 @@ json_view_start_resp(Req, Etag, TotalViewCount, Offset, _Acc, UpdateSeq) ->
end,
{ok, Resp, BeginBody}.
-send_json_view_row(Resp, Db, {{Key, DocId}, Value}, IncludeDocs, RowFront) ->
- JsonObj = view_row_obj(Db, {{Key, DocId}, Value}, IncludeDocs),
+send_json_view_row(Resp, Db, Kv, IncludeDocs, Conflicts, RowFront) ->
+ JsonObj = view_row_obj(Db, Kv, IncludeDocs, Conflicts),
send_chunk(Resp, RowFront ++ ?JSON_ENCODE(JsonObj)),
{ok, ",\r\n"}.
@@ -588,22 +640,21 @@ send_json_reduce_row(Resp, {Key, Value}, RowFront) ->
send_chunk(Resp, RowFront ++ ?JSON_ENCODE({[{key, Key}, {value, Value}]})),
{ok, ",\r\n"}.
-view_group_etag(Group, Db) ->
- view_group_etag(Group, Db, nil).
+view_etag(Db, Group, View) ->
+ view_etag(Db, Group, View, nil).
-view_group_etag(#group{sig=Sig,current_seq=CurrentSeq}, _Db, Extra) ->
- % ?LOG_ERROR("Group ~p",[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.
- couch_httpd:make_etag({Sig, CurrentSeq, Extra}).
+view_etag(Db, Group, {reduce, _, _, View}, Extra) ->
+ view_etag(Db, Group, View, Extra);
+view_etag(Db, Group, {temp_reduce, View}, Extra) ->
+ view_etag(Db, Group, View, Extra);
+view_etag(_Db, #group{sig=Sig}, #view{update_seq=UpdateSeq, purge_seq=PurgeSeq}, Extra) ->
+ couch_httpd:make_etag({Sig, UpdateSeq, PurgeSeq, Extra}).
% the view row has an error
-view_row_obj(_Db, {{Key, error}, Value}, _IncludeDocs) ->
+view_row_obj(_Db, {{Key, error}, Value}, _IncludeDocs, _Conflicts) ->
{[{key, Key}, {error, Value}]};
% include docs in the view output
-view_row_obj(Db, {{Key, DocId}, {Props}}, true) ->
+view_row_obj(Db, {{Key, DocId}, {Props}}, true, Conflicts) ->
Rev = case couch_util:get_value(<<"_rev">>, Props) of
undefined ->
nil;
@@ -611,19 +662,29 @@ view_row_obj(Db, {{Key, DocId}, {Props}}, true) ->
couch_doc:parse_rev(Rev0)
end,
IncludeId = couch_util:get_value(<<"_id">>, Props, DocId),
- view_row_with_doc(Db, {{Key, DocId}, {Props}}, {IncludeId, Rev});
-view_row_obj(Db, {{Key, DocId}, Value}, true) ->
- view_row_with_doc(Db, {{Key, DocId}, Value}, {DocId, nil});
+ view_row_with_doc(Db, {{Key, DocId}, {Props}}, {IncludeId, Rev}, Conflicts);
+view_row_obj(Db, {{Key, DocId}, Value}, true, Conflicts) ->
+ view_row_with_doc(Db, {{Key, DocId}, Value}, {DocId, nil}, Conflicts);
% the normal case for rendering a view row
-view_row_obj(_Db, {{Key, DocId}, Value}, _IncludeDocs) ->
+view_row_obj(_Db, {{Key, DocId}, Value}, _IncludeDocs, _Conflicts) ->
{[{id, DocId}, {key, Key}, {value, Value}]}.
-view_row_with_doc(Db, {{Key, DocId}, Value}, IdRev) ->
- {[{id, DocId}, {key, Key}, {value, Value}] ++ doc_member(Db, IdRev)}.
+view_row_with_doc(Db, {{Key, DocId}, Value}, IdRev, Conflicts) ->
+ {[{id, DocId}, {key, Key}, {value, Value}] ++
+ doc_member(Db, IdRev, if Conflicts -> [conflicts]; true -> [] end)}.
-doc_member(Db, {DocId, Rev}) ->
+doc_member(Db, #doc_info{id = Id, revs = [#rev_info{rev = Rev} | _]} = Info,
+ Options) ->
+ ?LOG_DEBUG("Include Doc: ~p ~p", [Id, Rev]),
+ case couch_db:open_doc(Db, Info, [deleted | Options]) of
+ {ok, Doc} ->
+ [{doc, couch_doc:to_json_obj(Doc, [])}];
+ _ ->
+ [{doc, null}]
+ end;
+doc_member(Db, {DocId, Rev}, Options) ->
?LOG_DEBUG("Include Doc: ~p ~p", [DocId, Rev]),
- case (catch couch_httpd_db:couch_doc_open(Db, DocId, Rev, [])) of
+ case (catch couch_httpd_db:couch_doc_open(Db, DocId, Rev, Options)) of
#doc{} = Doc ->
JsonDoc = couch_doc:to_json_obj(Doc, []),
[{doc, JsonDoc}];