summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Christopher Anderson <jchris@apache.org>2009-02-15 20:36:53 +0000
committerJohn Christopher Anderson <jchris@apache.org>2009-02-15 20:36:53 +0000
commit146bc594aef47b675670e7a7fd7f89b7c6a10843 (patch)
tree4f1ea083353560951b640f8c1ceaaf728e2def65
parentc91f851dc421466402721eb7baa644860f874ce1 (diff)
View etags are now provided. See note in the source about how they could be more efficient. Changes arity on make_view_fold_fun etc. Closes COUCHDB-4
git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@744747 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r--etc/couchdb/local_dev.ini2
-rw-r--r--share/www/script/couch_tests.js97
-rw-r--r--src/couchdb/couch_db.hrl2
-rw-r--r--src/couchdb/couch_httpd_db.erl218
-rw-r--r--src/couchdb/couch_httpd_show.erl102
-rw-r--r--src/couchdb/couch_httpd_view.erl147
-rw-r--r--src/couchdb/couch_view.erl26
7 files changed, 358 insertions, 236 deletions
diff --git a/etc/couchdb/local_dev.ini b/etc/couchdb/local_dev.ini
index b0f1f26d..13cfb494 100644
--- a/etc/couchdb/local_dev.ini
+++ b/etc/couchdb/local_dev.ini
@@ -12,7 +12,7 @@
;bind_address = 127.0.0.1
[log]
-level = info
+; level = debug
[update_notification]
;unique notifier name=/full/path/to/exe -with "cmd line arg"
diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js
index 0ffee4cd..7cf2c6a4 100644
--- a/share/www/script/couch_tests.js
+++ b/share/www/script/couch_tests.js
@@ -2425,17 +2425,87 @@ db.createDb();
});
T(xhr.status == 200);
},
+ etags_views: function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ var designDoc = {
+ _id:"_design/etags",
+ language: "javascript",
+ views : {
+ basicView : {
+ map : stringFun(function(doc) {
+ emit(doc.integer, doc.string);
+ })
+ },
+ withReduce : {
+ map : stringFun(function(doc) {
+ emit(doc.integer, doc.string);
+ }),
+ reduce : stringFun(function(keys, values, rereduce) {
+ if (rereduce) {
+ return sum(values);
+ } else {
+ return values.length;
+ }
+ })
+ }
+ }
+ }
+ T(db.save(designDoc).ok);
+ var xhr;
+ var docs = makeDocs(0, 10);
+ var saveResult = db.bulkSave(docs);
+ T(saveResult.ok);
+
+ // verify get w/Etag on map view
+ xhr = CouchDB.request("GET", "/test_suite_db/_view/etags/basicView");
+ T(xhr.status == 200);
+ var etag = xhr.getResponseHeader("etag");
+ xhr = CouchDB.request("GET", "/test_suite_db/_view/etags/basicView", {
+ headers: {"if-none-match": etag}
+ });
+ T(xhr.status == 304);
+ // TODO GET with keys (when that is available)
+
+ // reduce view
+ xhr = CouchDB.request("GET", "/test_suite_db/_view/etags/withReduce");
+ T(xhr.status == 200);
+ var etag = xhr.getResponseHeader("etag");
+ xhr = CouchDB.request("GET", "/test_suite_db/_view/etags/withReduce", {
+ headers: {"if-none-match": etag}
+ });
+ T(xhr.status == 304);
+
+ // all docs
+ xhr = CouchDB.request("GET", "/test_suite_db/_all_docs");
+ T(xhr.status == 200);
+ var etag = xhr.getResponseHeader("etag");
+ xhr = CouchDB.request("GET", "/test_suite_db/_all_docs", {
+ headers: {"if-none-match": etag}
+ });
+ T(xhr.status == 304);
+
+ // by seq
+ xhr = CouchDB.request("GET", "/test_suite_db/_all_docs_by_seq");
+ T(xhr.status == 200);
+ var etag = xhr.getResponseHeader("etag");
+ xhr = CouchDB.request("GET", "/test_suite_db/_all_docs_by_seq", {
+ headers: {"if-none-match": etag}
+ });
+ T(xhr.status == 304);
+
+ // list etag
+ // in the list test for now
+ },
show_documents: function(debug) {
var db = new CouchDB("test_suite_db");
db.deleteDb();
db.createDb();
if (debug) debugger;
-
- function stringFun(fun) {
- var string = fun.toSource ? fun.toSource() : "(" + fun.toString() + ")";
- return string;
- }
var designDoc = {
_id:"_design/template",
@@ -2706,11 +2776,6 @@ db.createDb();
db.deleteDb();
db.createDb();
if (debug) debugger;
-
- function stringFun(fun) {
- var string = fun.toSource ? fun.toSource() : "(" + fun.toString() + ")";
- return string;
- }
var designDoc = {
_id:"_design/lists",
@@ -2850,6 +2915,13 @@ db.createDb();
var lines = xhr.responseText.split('\n');
T(/LineNo: 5/.test(lines[6]));
+ // test that etags are available
+ var etag = xhr.getResponseHeader("etag");
+ xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/basicView", {
+ headers: {"if-none-match": etag}
+ });
+ T(xhr.status == 304);
+
// get with query params
var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/basicView?startkey=3");
T(xhr.status == 200);
@@ -3313,6 +3385,11 @@ function run_on_modified_server(settings, fun) {
}
}
+function stringFun(fun) {
+ var string = fun.toSource ? fun.toSource() : "(" + fun.toString() + ")";
+ return string;
+}
+
function restartServer() {
CouchDB.request("POST", "/_restart");
}
diff --git a/src/couchdb/couch_db.hrl b/src/couchdb/couch_db.hrl
index 5b8b74ef..bfa16a3d 100644
--- a/src/couchdb/couch_db.hrl
+++ b/src/couchdb/couch_db.hrl
@@ -169,7 +169,7 @@
}).
-record(group,
- {type=view, % can also be slow_view
+ {type=view, % can also be temp_view
sig=nil,
db=nil,
fd=nil,
diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl
index 89f8ce04..95c96349 100644
--- a/src/couchdb/couch_httpd_db.erl
+++ b/src/couchdb/couch_httpd_db.erl
@@ -191,46 +191,48 @@ db_req(#httpd{method='GET',path_parts=[_,<<"_all_docs_by_seq">>]}=Req, Db) ->
} = QueryArgs = couch_httpd_view:parse_view_query(Req),
{ok, Info} = couch_db:get_db_info(Db),
- TotalRowCount = proplists:get_value(doc_count, Info),
-
- FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db,
- TotalRowCount, #view_fold_helper_funs{
- reduce_count = fun couch_db:enum_docs_since_reduce_to_count/1
- }),
- StartKey2 = case StartKey of
- nil -> 0;
- <<>> -> 100000000000;
- {} -> 100000000000;
- StartKey when is_integer(StartKey) -> StartKey
- end,
- {ok, FoldResult} = couch_db:enum_docs_since(Db, StartKey2, Dir,
- fun(DocInfo, Offset, Acc) ->
- #doc_info{
- id=Id,
- rev=Rev,
- update_seq=UpdateSeq,
- deleted=Deleted,
- conflict_revs=ConflictRevs,
- deleted_conflict_revs=DelConflictRevs
- } = DocInfo,
- Json = {
- [{<<"rev">>, Rev}] ++
- case ConflictRevs of
- [] -> [];
- _ -> [{<<"conflicts">>, ConflictRevs}]
- end ++
- case DelConflictRevs of
- [] -> [];
- _ -> [{<<"deleted_conflicts">>, DelConflictRevs}]
- end ++
- case Deleted of
- true -> [{<<"deleted">>, true}];
- false -> []
- end
- },
- FoldlFun({{UpdateSeq, Id}, Json}, Offset, Acc)
- end, {Limit, SkipCount, undefined, []}),
- couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult});
+ CurrentEtag = couch_httpd:make_etag(proplists:get_value(update_seq, Info)),
+ couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
+ TotalRowCount = proplists:get_value(doc_count, Info),
+ FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db,
+ TotalRowCount, #view_fold_helper_funs{
+ reduce_count = fun couch_db:enum_docs_since_reduce_to_count/1
+ }),
+ StartKey2 = case StartKey of
+ nil -> 0;
+ <<>> -> 100000000000;
+ {} -> 100000000000;
+ StartKey when is_integer(StartKey) -> StartKey
+ end,
+ {ok, FoldResult} = couch_db:enum_docs_since(Db, StartKey2, Dir,
+ fun(DocInfo, Offset, Acc) ->
+ #doc_info{
+ id=Id,
+ rev=Rev,
+ update_seq=UpdateSeq,
+ deleted=Deleted,
+ conflict_revs=ConflictRevs,
+ deleted_conflict_revs=DelConflictRevs
+ } = DocInfo,
+ Json = {
+ [{<<"rev">>, Rev}] ++
+ case ConflictRevs of
+ [] -> [];
+ _ -> [{<<"conflicts">>, ConflictRevs}]
+ end ++
+ case DelConflictRevs of
+ [] -> [];
+ _ -> [{<<"deleted_conflicts">>, DelConflictRevs}]
+ end ++
+ case Deleted of
+ true -> [{<<"deleted">>, true}];
+ false -> []
+ end
+ },
+ FoldlFun({{UpdateSeq, Id}, Json}, Offset, Acc)
+ end, {Limit, SkipCount, undefined, []}),
+ couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult})
+ end);
db_req(#httpd{path_parts=[_,<<"_all_docs_by_seq">>]}=Req, _Db) ->
send_method_not_allowed(Req, "GET,HEAD");
@@ -289,77 +291,81 @@ all_docs_view(Req, Db, Keys) ->
direction = Dir
} = QueryArgs = couch_httpd_view:parse_view_query(Req, Keys),
{ok, Info} = couch_db:get_db_info(Db),
- TotalRowCount = proplists:get_value(doc_count, Info),
- StartId = if is_binary(StartKey) -> StartKey;
- true -> StartDocId
- end,
- FoldAccInit = {Limit, SkipCount, undefined, []},
+ CurrentEtag = couch_httpd:make_etag(proplists:get_value(update_seq, Info)),
+ couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
- PassedEndFun =
- case Dir of
- fwd ->
- fun(ViewKey, _ViewId) ->
- couch_db_updater:less_docid(EndKey, ViewKey)
- end;
- rev->
- fun(ViewKey, _ViewId) ->
- couch_db_updater:less_docid(ViewKey, EndKey)
- end
- end,
-
- case Keys of
- nil ->
- FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db,
- TotalRowCount, #view_fold_helper_funs{
- reduce_count = fun couch_db:enum_docs_reduce_to_count/1,
- passed_end = PassedEndFun
- }),
- AdapterFun = fun(#full_doc_info{id=Id}=FullDocInfo, Offset, Acc) ->
- case couch_doc:to_doc_info(FullDocInfo) of
- #doc_info{deleted=false, rev=Rev} ->
- FoldlFun({{Id, Id}, {[{rev, Rev}]}}, Offset, Acc);
- #doc_info{deleted=true} ->
- {ok, Acc}
- end
+ TotalRowCount = proplists:get_value(doc_count, Info),
+ StartId = if is_binary(StartKey) -> StartKey;
+ true -> StartDocId
end,
- {ok, FoldResult} = couch_db:enum_docs(Db, StartId, Dir,
- AdapterFun, FoldAccInit),
- couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult});
- _ ->
- FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db,
- TotalRowCount, #view_fold_helper_funs{
- reduce_count = fun(Offset) -> Offset end
- }),
- KeyFoldFun = case Dir of
+ FoldAccInit = {Limit, SkipCount, undefined, []},
+
+ PassedEndFun =
+ case Dir of
fwd ->
- fun lists:foldl/3;
- rev ->
- fun lists:foldr/3
+ fun(ViewKey, _ViewId) ->
+ couch_db_updater:less_docid(EndKey, ViewKey)
+ end;
+ rev->
+ fun(ViewKey, _ViewId) ->
+ couch_db_updater:less_docid(ViewKey, EndKey)
+ end
end,
- {ok, FoldResult} = KeyFoldFun(
- fun(Key, {ok, FoldAcc}) ->
- DocInfo = (catch couch_db:get_doc_info(Db, Key)),
- Doc = case DocInfo of
- {ok, #doc_info{id=Id, rev=Rev, deleted=false}} = DocInfo ->
- {{Id, Id}, {[{rev, Rev}]}};
- {ok, #doc_info{id=Id, rev=Rev, deleted=true}} = DocInfo ->
- {{Id, Id}, {[{rev, Rev}, {deleted, true}]}};
- not_found ->
- {{Key, error}, not_found};
- _ ->
- ?LOG_ERROR("Invalid DocInfo: ~p", [DocInfo]),
- throw({error, invalid_doc_info})
- end,
- Acc = (catch FoldlFun(Doc, 0, FoldAcc)),
- case Acc of
- {stop, Acc2} ->
- {ok, Acc2};
- _ ->
- Acc
+
+ case Keys of
+ nil ->
+ FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db,
+ TotalRowCount, #view_fold_helper_funs{
+ reduce_count = fun couch_db:enum_docs_reduce_to_count/1,
+ passed_end = PassedEndFun
+ }),
+ AdapterFun = fun(#full_doc_info{id=Id}=FullDocInfo, Offset, Acc) ->
+ case couch_doc:to_doc_info(FullDocInfo) of
+ #doc_info{deleted=false, rev=Rev} ->
+ FoldlFun({{Id, Id}, {[{rev, Rev}]}}, Offset, Acc);
+ #doc_info{deleted=true} ->
+ {ok, Acc}
end
- end, {ok, FoldAccInit}, Keys),
- couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult})
- end.
+ end,
+ {ok, FoldResult} = couch_db:enum_docs(Db, StartId, Dir,
+ AdapterFun, FoldAccInit),
+ couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult});
+ _ ->
+ FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db,
+ TotalRowCount, #view_fold_helper_funs{
+ reduce_count = fun(Offset) -> Offset end
+ }),
+ KeyFoldFun = case Dir of
+ fwd ->
+ fun lists:foldl/3;
+ rev ->
+ fun lists:foldr/3
+ end,
+ {ok, FoldResult} = KeyFoldFun(
+ fun(Key, {ok, FoldAcc}) ->
+ DocInfo = (catch couch_db:get_doc_info(Db, Key)),
+ Doc = case DocInfo of
+ {ok, #doc_info{id=Id, rev=Rev, deleted=false}} = DocInfo ->
+ {{Id, Id}, {[{rev, Rev}]}};
+ {ok, #doc_info{id=Id, rev=Rev, deleted=true}} = DocInfo ->
+ {{Id, Id}, {[{rev, Rev}, {deleted, true}]}};
+ not_found ->
+ {{Key, error}, not_found};
+ _ ->
+ ?LOG_ERROR("Invalid DocInfo: ~p", [DocInfo]),
+ throw({error, invalid_doc_info})
+ end,
+ Acc = (catch FoldlFun(Doc, 0, FoldAcc)),
+ case Acc of
+ {stop, Acc2} ->
+ {ok, Acc2};
+ _ ->
+ Acc
+ end
+ end, {ok, FoldAccInit}, Keys),
+ couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult})
+ end
+ end).
diff --git a/src/couchdb/couch_httpd_show.erl b/src/couchdb/couch_httpd_show.erl
index 1da8fe98..39c6cc1e 100644
--- a/src/couchdb/couch_httpd_show.erl
+++ b/src/couchdb/couch_httpd_show.erl
@@ -77,15 +77,15 @@ send_view_list_response(Lang, ListSrc, ViewName, DesignId, Req, Db) ->
reduce = Reduce
} = QueryArgs = couch_httpd_view:parse_view_query(Req, nil, nil, true),
case couch_view:get_map_view(Db, DesignId, ViewName, Stale) of
- {ok, View} ->
- output_map_list(Req, Lang, ListSrc, View, Db, QueryArgs);
+ {ok, View, Group} ->
+ output_map_list(Req, Lang, ListSrc, View, Group, Db, QueryArgs);
{not_found, _Reason} ->
case couch_view:get_reduce_view(Db, DesignId, ViewName, Stale) of
- {ok, ReduceView} ->
+ {ok, ReduceView, Group} ->
case Reduce of
false ->
MapView = couch_view:extract_map_view(ReduceView),
- output_map_list(Req, Lang, ListSrc, MapView, Db, QueryArgs);
+ output_map_list(Req, Lang, ListSrc, MapView, Group, Db, QueryArgs);
_ ->
throw({not_implemented, reduce_view_lists})
end;
@@ -94,7 +94,7 @@ send_view_list_response(Lang, ListSrc, ViewName, DesignId, Req, Db) ->
end
end.
-output_map_list(Req, Lang, ListSrc, View, Db, QueryArgs) ->
+output_map_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, QueryArgs) ->
#view_query_args{
limit = Limit,
direction = Dir,
@@ -104,51 +104,59 @@ output_map_list(Req, Lang, ListSrc, View, Db, QueryArgs) ->
} = QueryArgs,
{ok, RowCount} = couch_view:get_row_count(View),
Start = {StartKey, StartDocId},
- % get the os process here
- % pass it into the view fold with closures
- {ok, QueryServer} = couch_query_servers:start_view_list(Lang, ListSrc),
-
- StartListRespFun = fun(Req2, Code, TotalViewCount, Offset) ->
- JsonResp = couch_query_servers:render_list_head(QueryServer,
- Req2, Db, TotalViewCount, Offset),
- #extern_resp_args{
- code = Code,
- data = BeginBody,
- ctype = CType,
- headers = Headers
- } = couch_httpd_external:parse_external_response(JsonResp),
- JsonHeaders = couch_httpd_external:default_or_content_type(CType, Headers),
- {ok, Resp} = start_chunked_response(Req, Code, JsonHeaders),
- {ok, Resp, binary_to_list(BeginBody)}
- end,
+ Headers = MReq:get(headers),
+ Hlist = mochiweb_headers:to_list(Headers),
+ Accept = proplists:get_value('Accept', Hlist),
+ CurrentEtag = couch_httpd_view:view_group_etag(Group, {Lang, ListSrc, Accept}),
+ couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
+ % get the os process here
+ % pass it into the view fold with closures
+ {ok, QueryServer} = couch_query_servers:start_view_list(Lang, ListSrc),
+
+ StartListRespFun = fun(Req2, Etag, TotalViewCount, Offset) ->
+ ExternalResp = couch_query_servers:render_list_head(QueryServer,
+ Req2, Db, TotalViewCount, Offset),
+ JsonResp = apply_etag(ExternalResp, CurrentEtag),
+ #extern_resp_args{
+ code = Code,
+ data = BeginBody,
+ ctype = CType,
+ headers = ExtHeaders
+ } = couch_httpd_external:parse_external_response(JsonResp),
+ JsonHeaders = couch_httpd_external:default_or_content_type(CType, ExtHeaders),
+ % TODO use the Etag
+ {ok, Resp} = start_chunked_response(Req, Code, JsonHeaders),
+ {ok, Resp, binary_to_list(BeginBody)}
+ end,
- SendListRowFun = fun(Resp, Db2, {{Key, DocId}, Value},
- RowFront, _IncludeDocs) ->
- JsonResp = couch_query_servers:render_list_row(QueryServer,
- Req, Db2, {{Key, DocId}, Value}),
- #extern_resp_args{
- stop = StopIter,
- data = RowBody
- } = couch_httpd_external:parse_external_response(JsonResp),
- RowFront2 = case RowFront of
- nil -> [];
- _ -> RowFront
+ SendListRowFun = fun(Resp, Db2, {{Key, DocId}, Value},
+ RowFront, _IncludeDocs) ->
+ JsonResp = couch_query_servers:render_list_row(QueryServer,
+ Req, Db2, {{Key, DocId}, Value}),
+ #extern_resp_args{
+ stop = StopIter,
+ data = RowBody
+ } = couch_httpd_external:parse_external_response(JsonResp),
+ RowFront2 = case RowFront of
+ nil -> [];
+ _ -> RowFront
+ end,
+ case StopIter of
+ true -> stop;
+ _ -> send_chunk(Resp, RowFront2 ++ binary_to_list(RowBody))
+ end
end,
- case StopIter of
- true -> stop;
- _ -> send_chunk(Resp, RowFront2 ++ binary_to_list(RowBody))
- end
- end,
- FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db, RowCount,
- #view_fold_helper_funs{
- reduce_count = fun couch_view:reduce_to_count/1,
- start_response = StartListRespFun,
- send_row = SendListRowFun
- }),
- FoldAccInit = {Limit, SkipCount, undefined, []},
- FoldResult = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
- finish_view_list(Req, Db, QueryServer, RowCount, FoldResult, StartListRespFun).
+ FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, RowCount,
+ #view_fold_helper_funs{
+ reduce_count = fun couch_view:reduce_to_count/1,
+ start_response = StartListRespFun,
+ send_row = SendListRowFun
+ }),
+ FoldAccInit = {Limit, SkipCount, undefined, []},
+ FoldResult = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
+ finish_view_list(Req, Db, QueryServer, RowCount, FoldResult, StartListRespFun)
+ end).
finish_view_list(Req, Db, QueryServer, TotalRows,
FoldResult, StartListRespFun) ->
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
diff --git a/src/couchdb/couch_view.erl b/src/couchdb/couch_view.erl
index 5a7926b2..5fb2ec5a 100644
--- a/src/couchdb/couch_view.erl
+++ b/src/couchdb/couch_view.erl
@@ -61,14 +61,19 @@ get_row_count(#view{btree=Bt}) ->
{ok, Count}.
get_temp_reduce_view(Db, Type, DesignOptions, MapSrc, RedSrc) ->
- {ok, #group{views=[View]}} = get_temp_group(Db, Type, DesignOptions, MapSrc, RedSrc),
- {ok, {temp_reduce, View}}.
+ {ok, #group{views=[View]}=Group} = get_temp_group(Db, Type, DesignOptions, MapSrc, RedSrc),
+ {ok, {temp_reduce, View}, Group}.
get_reduce_view(Db, GroupId, Name, Update) ->
case get_group(Db, GroupId, Update) of
- {ok, #group{views=Views,def_lang=Lang}} ->
- get_reduce_view0(Name, Lang, Views);
+ {ok, #group{views=Views,def_lang=Lang}=Group} ->
+ case get_reduce_view0(Name, Lang, Views) of
+ {ok, View} ->
+ {ok, View, Group};
+ Else ->
+ Else
+ end;
Error ->
Error
end.
@@ -137,13 +142,18 @@ get_key_pos(Key, [_|Rest], N) ->
get_temp_map_view(Db, Type, DesignOptions, Src) ->
- {ok, #group{views=[View]}} = get_temp_group(Db, Type, DesignOptions, Src, []),
- {ok, View}.
+ {ok, #group{views=[View]}=Group} = get_temp_group(Db, Type, DesignOptions, Src, []),
+ {ok, View, Group}.
get_map_view(Db, GroupId, Name, Stale) ->
case get_group(Db, GroupId, Stale) of
- {ok, #group{views=Views}} ->
- get_map_view0(Name, Views);
+ {ok, #group{views=Views}=Group} ->
+ case get_map_view0(Name, Views) of
+ {ok, View} ->
+ {ok, View, Group};
+ Else ->
+ Else
+ end;
Error ->
Error
end.