summaryrefslogtreecommitdiff
path: root/src/couchdb/couch_httpd_view.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/couchdb/couch_httpd_view.erl')
-rw-r--r--src/couchdb/couch_httpd_view.erl147
1 files changed, 84 insertions, 63 deletions
diff --git a/src/couchdb/couch_httpd_view.erl b/src/couchdb/couch_httpd_view.erl
index 2674754e..9c8545cb 100644
--- a/src/couchdb/couch_httpd_view.erl
+++ b/src/couchdb/couch_httpd_view.erl
@@ -15,12 +15,12 @@
-export([handle_view_req/2,handle_temp_view_req/2]).
--export([parse_view_query/1,parse_view_query/2,parse_view_query/4,make_view_fold_fun/5,
- finish_view_fold/3, view_row_obj/3]).
+-export([parse_view_query/1,parse_view_query/2,parse_view_query/4,make_view_fold_fun/6,
+ finish_view_fold/3, view_row_obj/3, view_group_etag/1, view_group_etag/2]).
-import(couch_httpd,
- [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,
- start_json_response/2,send_chunk/2,end_json_response/1]).
+ [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,send_chunk/2,
+ start_json_response/2, start_json_response/3, end_json_response/1]).
design_doc_view(Req, Db, Id, ViewName, Keys) ->
#view_query_args{
@@ -29,18 +29,18 @@ design_doc_view(Req, Db, Id, ViewName, Keys) ->
} = QueryArgs = parse_view_query(Req, Keys),
DesignId = <<"_design/", Id/binary>>,
case couch_view:get_map_view(Db, DesignId, ViewName, Stale) of
- {ok, View} ->
- output_map_view(Req, View, Db, QueryArgs, Keys);
+ {ok, View, Group} ->
+ output_map_view(Req, View, Group, Db, QueryArgs, Keys);
{not_found, Reason} ->
case couch_view:get_reduce_view(Db, DesignId, ViewName, Stale) of
- {ok, ReduceView} ->
+ {ok, ReduceView, Group} ->
parse_view_query(Req, Keys, true), % just for validation
case Reduce of
false ->
MapView = couch_view:extract_map_view(ReduceView),
- output_map_view(Req, MapView, Db, QueryArgs, Keys);
+ output_map_view(Req, MapView, Group, Db, QueryArgs, Keys);
_ ->
- output_reduce_view(Req, ReduceView, QueryArgs, Keys)
+ output_reduce_view(Req, ReduceView, Group, QueryArgs, Keys)
end;
_ ->
throw({not_found, Reason})
@@ -73,19 +73,19 @@ handle_temp_view_req(#httpd{method='POST'}=Req, Db) ->
Keys = proplists:get_value(<<"keys">>, Props, nil),
case proplists:get_value(<<"reduce">>, Props, null) of
null ->
- {ok, View} = couch_view:get_temp_map_view(Db, Language,
+ {ok, View, Group} = couch_view:get_temp_map_view(Db, Language,
DesignOptions, MapSrc),
- output_map_view(Req, View, Db, QueryArgs, Keys);
+ output_map_view(Req, View, Group, Db, QueryArgs, Keys);
RedSrc ->
- {ok, View} = couch_view:get_temp_reduce_view(Db, Language,
+ {ok, View, Group} = couch_view:get_temp_reduce_view(Db, Language,
DesignOptions, MapSrc, RedSrc),
- output_reduce_view(Req, View, QueryArgs, Keys)
+ output_reduce_view(Req, View, Group, QueryArgs, Keys)
end;
handle_temp_view_req(Req, _Db) ->
send_method_not_allowed(Req, "POST").
-output_map_view(Req, View, Db, QueryArgs, nil) ->
+output_map_view(Req, View, Group, Db, QueryArgs, nil) ->
#view_query_args{
limit = Limit,
direction = Dir,
@@ -93,38 +93,44 @@ output_map_view(Req, View, Db, QueryArgs, nil) ->
start_key = StartKey,
start_docid = StartDocId
} = QueryArgs,
- {ok, RowCount} = couch_view:get_row_count(View),
- Start = {StartKey, StartDocId},
- FoldlFun = make_view_fold_fun(Req, QueryArgs, Db, RowCount, #view_fold_helper_funs{reduce_count=fun couch_view:reduce_to_count/1}),
- FoldAccInit = {Limit, SkipCount, undefined, []},
- FoldResult = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
- finish_view_fold(Req, RowCount, FoldResult);
+ CurrentEtag = view_group_etag(Group),
+ couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
+ {ok, RowCount} = couch_view:get_row_count(View),
+ Start = {StartKey, StartDocId},
+ FoldlFun = make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, RowCount, #view_fold_helper_funs{reduce_count=fun couch_view:reduce_to_count/1}),
+ FoldAccInit = {Limit, SkipCount, undefined, []},
+ FoldResult = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
+ finish_view_fold(Req, RowCount, FoldResult)
+ end);
-output_map_view(Req, View, Db, QueryArgs, Keys) ->
+output_map_view(Req, View, Group, Db, QueryArgs, Keys) ->
#view_query_args{
limit = Limit,
direction = Dir,
skip = SkipCount,
start_docid = StartDocId
} = QueryArgs,
- {ok, RowCount} = couch_view:get_row_count(View),
- FoldAccInit = {Limit, SkipCount, undefined, []},
- FoldResult = lists:foldl(
- fun(Key, {ok, FoldAcc}) ->
- Start = {Key, StartDocId},
- FoldlFun = make_view_fold_fun(Req,
- QueryArgs#view_query_args{
- start_key = Key,
- end_key = Key
- }, Db, RowCount,
- #view_fold_helper_funs{
- reduce_count = fun couch_view:reduce_to_count/1
- }),
- couch_view:fold(View, Start, Dir, FoldlFun, FoldAcc)
- end, {ok, FoldAccInit}, Keys),
- finish_view_fold(Req, RowCount, FoldResult).
+ CurrentEtag = view_group_etag(Group, Keys),
+ couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
+ {ok, RowCount} = couch_view:get_row_count(View),
+ FoldAccInit = {Limit, SkipCount, undefined, []},
+ FoldResult = lists:foldl(
+ fun(Key, {ok, FoldAcc}) ->
+ Start = {Key, StartDocId},
+ FoldlFun = make_view_fold_fun(Req,
+ QueryArgs#view_query_args{
+ start_key = Key,
+ end_key = Key
+ }, CurrentEtag, Db, RowCount,
+ #view_fold_helper_funs{
+ reduce_count = fun couch_view:reduce_to_count/1
+ }),
+ couch_view:fold(View, Start, Dir, FoldlFun, FoldAcc)
+ end, {ok, FoldAccInit}, Keys),
+ finish_view_fold(Req, RowCount, FoldResult)
+ end).
-output_reduce_view(Req, View, QueryArgs, nil) ->
+output_reduce_view(Req, View, Group, QueryArgs, nil) ->
#view_query_args{
start_key = StartKey,
end_key = EndKey,
@@ -135,15 +141,18 @@ output_reduce_view(Req, View, QueryArgs, nil) ->
end_docid = EndDocId,
group_level = GroupLevel
} = QueryArgs,
- {ok, Resp} = start_json_response(Req, 200),
- {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Resp, GroupLevel),
- send_chunk(Resp, "{\"rows\":["),
- {ok, _} = couch_view:fold_reduce(View, Dir, {StartKey, StartDocId},
- {EndKey, EndDocId}, GroupRowsFun, RespFun, {"", Skip, Limit}),
- send_chunk(Resp, "]}"),
- end_json_response(Resp);
+ CurrentEtag = view_group_etag(Group),
+ couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
+ {ok, Resp} = start_json_response(Req, 200, [{"Etag",CurrentEtag}]),
+ {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Resp, GroupLevel),
+ send_chunk(Resp, "{\"rows\":["),
+ {ok, _} = couch_view:fold_reduce(View, Dir, {StartKey, StartDocId},
+ {EndKey, EndDocId}, GroupRowsFun, RespFun, {"", Skip, Limit}),
+ send_chunk(Resp, "]}"),
+ end_json_response(Resp)
+ end);
-output_reduce_view(Req, View, QueryArgs, Keys) ->
+output_reduce_view(Req, View, Group, QueryArgs, Keys) ->
#view_query_args{
limit = Limit,
skip = Skip,
@@ -152,19 +161,22 @@ output_reduce_view(Req, View, QueryArgs, Keys) ->
end_docid = EndDocId,
group_level = GroupLevel
} = QueryArgs,
- {ok, Resp} = start_json_response(Req, 200),
- {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Resp, GroupLevel),
- send_chunk(Resp, "{\"rows\":["),
- lists:foldl(
- fun(Key, AccSeparator) ->
- {ok, {NewAcc, _, _}} = couch_view:fold_reduce(View, Dir, {Key, StartDocId},
- {Key, EndDocId}, GroupRowsFun, RespFun,
- {AccSeparator, Skip, Limit}),
- NewAcc % Switch to comma
- end,
- "", Keys), % Start with no comma
- send_chunk(Resp, "]}"),
- end_json_response(Resp).
+ CurrentEtag = view_group_etag(Group),
+ couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
+ {ok, Resp} = start_json_response(Req, 200, [{"Etag",CurrentEtag}]),
+ {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Resp, GroupLevel),
+ send_chunk(Resp, "{\"rows\":["),
+ lists:foldl(
+ fun(Key, AccSeparator) ->
+ {ok, {NewAcc, _, _}} = couch_view:fold_reduce(View, Dir, {Key, StartDocId},
+ {Key, EndDocId}, GroupRowsFun, RespFun,
+ {AccSeparator, Skip, Limit}),
+ NewAcc % Switch to comma
+ end,
+ "", Keys), % Start with no comma
+ send_chunk(Resp, "]}"),
+ end_json_response(Resp)
+ end).
make_reduce_fold_funs(Resp, GroupLevel) ->
GroupRowsFun =
@@ -381,7 +393,7 @@ parse_view_query(Req, Keys, IsReduce, IgnoreExtra) ->
end
end.
-make_view_fold_fun(Req, QueryArgs, Db,
+make_view_fold_fun(Req, QueryArgs, Etag, Db,
TotalViewCount, HelperFuns) ->
#view_query_args{
end_key = EndKey,
@@ -414,7 +426,7 @@ make_view_fold_fun(Req, QueryArgs, Db,
{ok, {AccLimit, AccSkip - 1, Resp, AccRevRows}};
{_, _, _, undefined} ->
Offset = ReduceCountFun(OffsetReds),
- {ok, Resp2, BeginBody} = StartRespFun(Req, 200,
+ {ok, Resp2, BeginBody} = StartRespFun(Req, Etag,
TotalViewCount, Offset),
case SendRowFun(Resp2, Db,
{{Key, DocId}, Value}, BeginBody, IncludeDocs) of
@@ -468,8 +480,8 @@ make_passed_end_fun(Dir, EndKey, EndDocId) ->
end
end.
-json_view_start_resp(Req, Code, TotalViewCount, Offset) ->
- {ok, Resp} = couch_httpd:start_json_response(Req, Code),
+json_view_start_resp(Req, Etag, TotalViewCount, Offset) ->
+ {ok, Resp} = start_json_response(Req, 200, [{"Etag", Etag}]),
BeginBody = io_lib:format("{\"total_rows\":~w,\"offset\":~w,\"rows\":[\r\n",
[TotalViewCount, Offset]),
{ok, Resp, BeginBody}.
@@ -481,7 +493,16 @@ send_json_view_row(Resp, Db, {{Key, DocId}, Value}, RowFront, IncludeDocs) ->
_ -> RowFront
end,
send_chunk(Resp, RowFront2 ++ ?JSON_ENCODE(JsonObj)).
+
+view_group_etag(Group) ->
+ view_group_etag(Group, nil).
+view_group_etag(#group{sig=Sig,current_seq=CurrentSeq}, Extra) ->
+ % 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_row_obj(Db, {{Key, DocId}, Value}, IncludeDocs) ->
case DocId of