diff options
-rw-r--r-- | THANKS | 1 | ||||
-rw-r--r-- | share/Makefile.am | 1 | ||||
-rw-r--r-- | share/www/script/couch_tests.js | 1 | ||||
-rw-r--r-- | share/www/script/test/view_update_seq.js | 76 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_db.erl | 6 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_show.erl | 29 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_view.erl | 55 |
7 files changed, 129 insertions, 40 deletions
@@ -51,5 +51,6 @@ suggesting improvements or submitting changes. Some of these people are: * Matt Lyon <matt@flowerpowered.com> * mikeal <mikeal.rogers@gmail.com> * Randall Leeds <randall.leeds@gmail.com> + * Joscha Feth <joscha@feth.com> For a list of authors see the `AUTHORS` file. diff --git a/share/Makefile.am b/share/Makefile.am index a6911b12..ac4d6fb0 100644 --- a/share/Makefile.am +++ b/share/Makefile.am @@ -162,6 +162,7 @@ nobase_dist_localdata_DATA = \ www/script/test/view_multi_key_design.js \ www/script/test/view_multi_key_temp.js \ www/script/test/view_offsets.js \ + www/script/test/view_update_seq.js \ www/script/test/view_pagination.js \ www/script/test/view_sandboxing.js \ www/script/test/view_xml.js \ diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js index 943b851b..896f150b 100644 --- a/share/www/script/couch_tests.js +++ b/share/www/script/couch_tests.js @@ -91,6 +91,7 @@ loadTest("view_multi_key_temp.js"); loadTest("view_offsets.js"); loadTest("view_pagination.js"); loadTest("view_sandboxing.js"); +loadTest("view_update_seq.js"); loadTest("view_xml.js"); // keep sorted diff --git a/share/www/script/test/view_update_seq.js b/share/www/script/test/view_update_seq.js new file mode 100644 index 00000000..3935e153 --- /dev/null +++ b/share/www/script/test/view_update_seq.js @@ -0,0 +1,76 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +couchTests.view_update_seq = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + T(db.info().update_seq == 0); + + var designDoc = { + _id:"_design/test", + language: "javascript", + views: { + all_docs: { + map: "function(doc) { emit(doc.integer, doc.string) }" + }, + summate: { + map:"function (doc) {emit(doc.integer, doc.integer)};", + reduce:"function (keys, values) { return sum(values); };" + } + } + } + T(db.save(designDoc).ok); + + T(db.info().update_seq == 1); + + var resp = db.allDocs({}); + + T(resp.rows.length == 1); + T(resp.update_seq == 1); + + var docs = makeDocs(0, 100); + db.bulkSave(docs); + + resp = db.allDocs({limit: 1}); + T(resp.rows.length == 1); + T(resp.update_seq == 101); + + resp = db.view('test/all_docs', {limit: 1}); + T(resp.rows.length == 1); + T(resp.update_seq == 101); + + resp = db.view('test/summate', {}); + T(resp.rows.length == 1); + T(resp.update_seq == 101); + + db.save({"id":"0"}); + resp = db.view('test/all_docs', {limit: 1,stale: "ok"}); + T(resp.rows.length == 1); + T(resp.update_seq == 101); + + resp = db.view('test/all_docs', {limit: 1}); + T(resp.rows.length == 1); + T(resp.update_seq == 102); + + resp = db.view('test/all_docs',{},["0","1"]); + T(resp.update_seq == 102); + + resp = db.view('test/all_docs',{},["0","1"]); + T(resp.update_seq == 102); + + resp = db.view('test/summate',{group:true},["0","1"]); + T(resp.update_seq == 102); + +}; diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl index b473ff6c..463519a8 100644 --- a/src/couchdb/couch_httpd_db.erl +++ b/src/couchdb/couch_httpd_db.erl @@ -492,10 +492,10 @@ all_docs_view(Req, Db, Keys) -> true -> EndDocId end, FoldAccInit = {Limit, SkipCount, undefined, []}, - + UpdateSeq = couch_db:get_update_seq(Db), case Keys of nil -> - FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, + FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, UpdateSeq, TotalRowCount, #view_fold_helper_funs{ reduce_count = fun couch_db:enum_docs_reduce_to_count/1 }), @@ -512,7 +512,7 @@ all_docs_view(Req, Db, Keys) -> {if Inclusive -> end_key; true -> end_key_gt end, EndId}]), couch_httpd_view:finish_view_fold(Req, TotalRowCount, LastOffset, FoldResult); _ -> - FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, + FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, UpdateSeq, TotalRowCount, #view_fold_helper_funs{ reduce_count = fun(Offset) -> Offset end }), diff --git a/src/couchdb/couch_httpd_show.erl b/src/couchdb/couch_httpd_show.erl index a472c97a..f40a0421 100644 --- a/src/couchdb/couch_httpd_show.erl +++ b/src/couchdb/couch_httpd_show.erl @@ -190,21 +190,21 @@ handle_view_list(Req, Db, DDoc, LName, {ViewDesignName, ViewName}, Keys) -> {ViewType, View, Group, QueryArgs} = couch_httpd_view:load_view(Req, Db, {ViewDesignId, ViewName}, Keys), Etag = list_etag(Req, Db, Group, {couch_httpd:doc_etag(DDoc), Keys}), couch_httpd:etag_respond(Req, Etag, fun() -> - output_list(ViewType, Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) + output_list(ViewType, Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys, Group) end). list_etag(#httpd{user_ctx=UserCtx}=Req, Db, Group, More) -> Accept = couch_httpd:header_value(Req, "Accept"), couch_httpd_view:view_group_etag(Group, Db, {More, Accept, UserCtx#user_ctx.roles}). -output_list(map, Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) -> - output_map_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys); -output_list(reduce, Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) -> - output_reduce_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys). +output_list(map, Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys, Group) -> + output_map_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys, Group); +output_list(reduce, Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys, Group) -> + output_reduce_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys, Group). % next step: % use with_ddoc_proc/2 to make this simpler -output_map_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) -> +output_map_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys, Group) -> #view_query_args{ limit = Limit, skip = SkipCount @@ -220,11 +220,12 @@ output_map_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) -> reduce_count = fun couch_view:reduce_to_count/1, start_response = StartListRespFun = make_map_start_resp_fun(QServer, Db, LName), send_row = make_map_send_row_fun(QServer) - }, + }, + CurrentSeq = Group#group.current_seq, {ok, _, FoldResult} = case Keys of nil -> - FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Etag, Db, RowCount, ListFoldHelpers), + FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Etag, Db, CurrentSeq, RowCount, ListFoldHelpers), couch_view:fold(View, FoldlFun, FoldAccInit, couch_httpd_view:make_key_options(QueryArgs)); Keys -> @@ -234,7 +235,7 @@ output_map_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) -> start_key = Key, end_key = Key }, - FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs2, Etag, Db, RowCount, ListFoldHelpers), + FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs2, Etag, Db, CurrentSeq, RowCount, ListFoldHelpers), couch_view:fold(View, FoldlFun, FoldAcc, couch_httpd_view:make_key_options(QueryArgs2)) end, {ok, nil, FoldAccInit}, Keys) @@ -243,18 +244,20 @@ output_map_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) -> end). -output_reduce_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) -> +output_reduce_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys, Group) -> #view_query_args{ limit = Limit, skip = SkipCount, group_level = GroupLevel } = QueryArgs, + CurrentSeq = Group#group.current_seq, + couch_query_servers:with_ddoc_proc(DDoc, fun(QServer) -> StartListRespFun = make_reduce_start_resp_fun(QServer, Db, LName), SendListRowFun = make_reduce_send_row_fun(QServer, Db), {ok, GroupRowsFun, RespFun} = couch_httpd_view:make_reduce_fold_funs(Req, - GroupLevel, QueryArgs, Etag, + GroupLevel, QueryArgs, Etag, CurrentSeq, #reduce_fold_helper_funs{ start_response = StartListRespFun, send_row = SendListRowFun @@ -279,8 +282,8 @@ output_reduce_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) -> make_map_start_resp_fun(QueryServer, Db, LName) -> - fun(Req, Etag, TotalRows, Offset, _Acc) -> - Head = {[{<<"total_rows">>, TotalRows}, {<<"offset">>, Offset}]}, + fun(Req, Etag, TotalRows, Offset, _Acc, UpdateSeq) -> + Head = {[{<<"total_rows">>, TotalRows}, {<<"offset">>, Offset}, {<<"update_seq">>, UpdateSeq}]}, start_list_resp(QueryServer, LName, Req, Db, Head, Etag) end. diff --git a/src/couchdb/couch_httpd_view.erl b/src/couchdb/couch_httpd_view.erl index 6419ca55..5be702ca 100644 --- a/src/couchdb/couch_httpd_view.erl +++ b/src/couchdb/couch_httpd_view.erl @@ -16,8 +16,8 @@ -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/6, finish_view_fold/4, view_row_obj/3]). --export([view_group_etag/2, view_group_etag/3, make_reduce_fold_funs/5]). +-export([make_view_fold_fun/7, finish_view_fold/4, 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([make_key_options/1, load_view/4]). @@ -26,6 +26,8 @@ start_json_response/2, start_json_response/3, end_json_response/1, send_chunked_error/2]). +-import(couch_db,[get_update_seq/1]). + design_doc_view(Req, Db, DName, ViewName, Keys) -> DesignId = <<"_design/", DName/binary>>, Stale = get_stale_type(Req), @@ -111,7 +113,7 @@ output_map_view(Req, View, Group, Db, QueryArgs, nil) -> CurrentEtag = view_group_etag(Group, Db), couch_httpd:etag_respond(Req, CurrentEtag, fun() -> {ok, RowCount} = couch_view:get_row_count(View), - FoldlFun = make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, RowCount, #view_fold_helper_funs{reduce_count=fun couch_view:reduce_to_count/1}), + 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}), FoldAccInit = {Limit, SkipCount, undefined, []}, {ok, LastReduce, FoldResult} = couch_view:fold(View, FoldlFun, FoldAccInit, make_key_options(QueryArgs)), @@ -132,7 +134,7 @@ output_map_view(Req, View, Group, Db, QueryArgs, Keys) -> fun(Key, {_, FoldAcc}) -> FoldlFun = make_view_fold_fun(Req, QueryArgs#view_query_args{ - }, CurrentEtag, Db, RowCount, + }, CurrentEtag, Db, Group#group.current_seq, RowCount, #view_fold_helper_funs{ reduce_count = fun couch_view:reduce_to_count/1 }), @@ -140,7 +142,7 @@ output_map_view(Req, View, Group, Db, QueryArgs, Keys) -> make_key_options(QueryArgs#view_query_args{start_key=Key, end_key=Key})), {LastReduce, FoldResult} end, {{[],[]}, FoldAccInit}, Keys), - finish_view_fold(Req, RowCount, couch_view:reduce_to_count(LastReduce), FoldResult) + finish_view_fold(Req, RowCount, couch_view:reduce_to_count(LastReduce), FoldResult, [{update_seq,Group#group.current_seq}]) end). output_reduce_view(Req, Db, View, Group, QueryArgs, nil) -> @@ -151,7 +153,7 @@ output_reduce_view(Req, Db, View, Group, QueryArgs, nil) -> } = QueryArgs, CurrentEtag = view_group_etag(Group, Db), couch_httpd:etag_respond(Req, CurrentEtag, fun() -> - {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Req, GroupLevel, QueryArgs, CurrentEtag, #reduce_fold_helper_funs{}), + {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Req, GroupLevel, QueryArgs, CurrentEtag, Group#group.current_seq, #reduce_fold_helper_funs{}), FoldAccInit = {Limit, Skip, undefined, []}, {ok, {_, _, Resp, _}} = couch_view:fold_reduce(View, RespFun, FoldAccInit, [{key_group_fun, GroupRowsFun} | @@ -167,7 +169,7 @@ output_reduce_view(Req, Db, View, Group, QueryArgs, Keys) -> } = QueryArgs, CurrentEtag = view_group_etag(Group, Db, Keys), couch_httpd:etag_respond(Req, CurrentEtag, fun() -> - {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Req, GroupLevel, QueryArgs, CurrentEtag, #reduce_fold_helper_funs{}), + {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Req, GroupLevel, QueryArgs, CurrentEtag, Group#group.current_seq, #reduce_fold_helper_funs{}), {Resp, _RedAcc3} = lists:foldl( fun(Key, {Resp, RedAcc}) -> % run the reduce once for each key in keys, with limit etc reapplied for each key @@ -180,7 +182,7 @@ output_reduce_view(Req, Db, View, Group, QueryArgs, Keys) -> {Resp2, RedAcc2} end, {undefined, []}, Keys), % Start with no comma - finish_reduce_fold(Req, Resp) + finish_reduce_fold(Req, Resp, [{update_seq,Group#group.current_seq}]) end). reverse_key_default(?MIN_STR) -> ?MAX_STR; @@ -377,7 +379,7 @@ validate_view_query(include_docs, _Value, Args) -> validate_view_query(extra, _Value, Args) -> Args. -make_view_fold_fun(Req, QueryArgs, Etag, Db, TotalViewCount, HelperFuns) -> +make_view_fold_fun(Req, QueryArgs, Etag, Db, UpdateSeq, TotalViewCount, HelperFuns) -> #view_fold_helper_funs{ start_response = StartRespFun, send_row = SendRowFun, @@ -400,7 +402,7 @@ make_view_fold_fun(Req, QueryArgs, Etag, Db, TotalViewCount, HelperFuns) -> % rendering the first row, first we start the response Offset = ReduceCountFun(OffsetReds), {ok, Resp2, RowFunAcc0} = StartRespFun(Req, Etag, - TotalViewCount, Offset, RowFunAcc), + TotalViewCount, Offset, RowFunAcc, UpdateSeq), {Go, RowFunAcc2} = SendRowFun(Resp2, Db, {{Key, DocId}, Value}, IncludeDocs, RowFunAcc0), {Go, {AccLimit - 1, 0, Resp2, RowFunAcc2}}; @@ -412,7 +414,7 @@ make_view_fold_fun(Req, QueryArgs, Etag, Db, TotalViewCount, HelperFuns) -> end end. -make_reduce_fold_funs(Req, GroupLevel, _QueryArgs, Etag, HelperFuns) -> +make_reduce_fold_funs(Req, GroupLevel, _QueryArgs, Etag, UpdateSeq, HelperFuns) -> #reduce_fold_helper_funs{ start_response = StartRespFun, send_row = SendRowFun @@ -438,7 +440,7 @@ make_reduce_fold_funs(Req, GroupLevel, _QueryArgs, Etag, HelperFuns) -> (_Key, Red, {AccLimit, 0, undefined, RowAcc0}) when GroupLevel == 0 -> % we haven't started responding yet and group=false - {ok, Resp2, RowAcc} = StartRespFun(Req, Etag, RowAcc0), + {ok, Resp2, RowAcc} = StartRespFun(Req, Etag, RowAcc0,UpdateSeq), {Go, RowAcc2} = SendRowFun(Resp2, {null, Red}, RowAcc), {Go, {AccLimit - 1, 0, Resp2, RowAcc2}}; (_Key, Red, {AccLimit, 0, Resp, RowAcc}) when GroupLevel == 0 -> @@ -449,7 +451,7 @@ make_reduce_fold_funs(Req, GroupLevel, _QueryArgs, Etag, HelperFuns) -> (Key, Red, {AccLimit, 0, undefined, RowAcc0}) when is_integer(GroupLevel), is_list(Key) -> % group_level and we haven't responded yet - {ok, Resp2, RowAcc} = StartRespFun(Req, Etag, RowAcc0), + {ok, Resp2, RowAcc} = StartRespFun(Req, Etag, RowAcc0,UpdateSeq), {Go, RowAcc2} = SendRowFun(Resp2, {lists:sublist(Key, GroupLevel), Red}, RowAcc), {Go, {AccLimit - 1, 0, Resp2, RowAcc2}}; (Key, Red, {AccLimit, 0, Resp, RowAcc}) @@ -460,7 +462,7 @@ make_reduce_fold_funs(Req, GroupLevel, _QueryArgs, Etag, HelperFuns) -> (Key, Red, {AccLimit, 0, undefined, RowAcc0}) -> % group=true and we haven't responded yet - {ok, Resp2, RowAcc} = StartRespFun(Req, Etag, RowAcc0), + {ok, Resp2, RowAcc} = StartRespFun(Req, Etag, RowAcc0,UpdateSeq), {Go, RowAcc2} = SendRowFun(Resp2, {Key, Red}, RowAcc), {Go, {AccLimit - 1, 0, Resp2, RowAcc2}}; (Key, Red, {AccLimit, 0, Resp, RowAcc}) -> @@ -476,7 +478,7 @@ apply_default_helper_funs(#view_fold_helper_funs{ }=Helpers) -> StartResp2 = case StartResp of - undefined -> fun json_view_start_resp/5; + undefined -> fun json_view_start_resp/6; _ -> StartResp end, @@ -496,7 +498,7 @@ apply_default_helper_funs(#reduce_fold_helper_funs{ send_row = SendRow }=Helpers) -> StartResp2 = case StartResp of - undefined -> fun json_reduce_start_resp/3; + undefined -> fun json_reduce_start_resp/4; _ -> StartResp end, @@ -538,10 +540,10 @@ make_end_key_option( inclusive_end = false}) -> [{end_key_gt, {EndKey,reverse_key_default(EndDocId)}}]. -json_view_start_resp(Req, Etag, TotalViewCount, Offset, _Acc) -> +json_view_start_resp(Req, Etag, TotalViewCount, Offset, _Acc, UpdateSeq) -> {ok, Resp} = start_json_response(Req, 200, [{"Etag", Etag}]), - BeginBody = io_lib:format("{\"total_rows\":~w,\"offset\":~w,\"rows\":[\r\n", - [TotalViewCount, Offset]), + BeginBody = io_lib:format("{\"total_rows\":~w,\"update_seq\":~w,\"offset\":~w,\"rows\":[\r\n", + [TotalViewCount, UpdateSeq, Offset]), {ok, Resp, BeginBody}. send_json_view_row(Resp, Db, {{Key, DocId}, Value}, IncludeDocs, RowFront) -> @@ -549,9 +551,9 @@ send_json_view_row(Resp, Db, {{Key, DocId}, Value}, IncludeDocs, RowFront) -> send_chunk(Resp, RowFront ++ ?JSON_ENCODE(JsonObj)), {ok, ",\r\n"}. -json_reduce_start_resp(Req, Etag, _Acc0) -> +json_reduce_start_resp(Req, Etag, _Acc0, UpdateSeq) -> {ok, Resp} = start_json_response(Req, 200, [{"Etag", Etag}]), - {ok, Resp, "{\"rows\":[\r\n"}. + {ok, Resp, io_lib:format("{\"update_seq\":~w,\"rows\":[\r\n",[UpdateSeq])}. send_json_reduce_row(Resp, {Key, Value}, RowFront) -> send_chunk(Resp, RowFront ++ ?JSON_ENCODE({[{key, Key}, {value, Value}]})), @@ -599,9 +601,11 @@ doc_member(Db, {DocId, Rev}) -> _Else -> [{doc, null}] end. - finish_view_fold(Req, TotalRows, Offset, FoldResult) -> + finish_view_fold(Req, TotalRows, Offset, FoldResult, []). + +finish_view_fold(Req, TotalRows, Offset, FoldResult, Fields) -> case FoldResult of {_, _, undefined, _} -> % nothing found in the view or keys, nothing has been returned @@ -610,7 +614,7 @@ finish_view_fold(Req, TotalRows, Offset, FoldResult) -> {total_rows, TotalRows}, {offset, Offset}, {rows, []} - ]}); + ] ++ Fields}); {_, _, Resp, _} -> % end the view send_chunk(Resp, "\r\n]}"), @@ -618,11 +622,14 @@ finish_view_fold(Req, TotalRows, Offset, FoldResult) -> end. finish_reduce_fold(Req, Resp) -> + finish_reduce_fold(Req, Resp, []). + +finish_reduce_fold(Req, Resp, Fields) -> case Resp of undefined -> send_json(Req, 200, {[ {rows, []} - ]}); + ] ++ Fields}); Resp -> send_chunk(Resp, "\r\n]}"), end_json_response(Resp) |