diff options
author | John Christopher Anderson <jchris@apache.org> | 2009-02-15 20:36:53 +0000 |
---|---|---|
committer | John Christopher Anderson <jchris@apache.org> | 2009-02-15 20:36:53 +0000 |
commit | 146bc594aef47b675670e7a7fd7f89b7c6a10843 (patch) | |
tree | 4f1ea083353560951b640f8c1ceaaf728e2def65 | |
parent | c91f851dc421466402721eb7baa644860f874ce1 (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.ini | 2 | ||||
-rw-r--r-- | share/www/script/couch_tests.js | 97 | ||||
-rw-r--r-- | src/couchdb/couch_db.hrl | 2 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_db.erl | 218 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_show.erl | 102 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_view.erl | 147 | ||||
-rw-r--r-- | src/couchdb/couch_view.erl | 26 |
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. |