From 772fdde2eb970177438038a2479ec13385372cf5 Mon Sep 17 00:00:00 2001 From: Filipe David Borba Manana Date: Wed, 9 Mar 2011 19:09:11 +0000 Subject: Merged revision 1079939 from trunk Parameter "include_docs" now honors parameter "conflicts" When querying a map view, /db/_all_docs/ or /db/_changes/ with "include_docs=true", if "conflicts=true" is given as well, the documents will contain the conflicts list (if there are conflicting revisions). Closes COUCHDB-549. git-svn-id: https://svn.apache.org/repos/asf/couchdb/branches/1.1.x@1079941 13f79535-47bb-0310-9956-ffa450edef68 --- share/www/script/test/all_docs.js | 8 +++-- share/www/script/test/view_include_docs.js | 54 ++++++++++++++++++++++++++++++ src/couchdb/couch_changes.erl | 32 ++++++++++-------- src/couchdb/couch_db.hrl | 2 ++ src/couchdb/couch_httpd_db.erl | 19 ++++++----- src/couchdb/couch_httpd_show.erl | 19 ++++++----- src/couchdb/couch_httpd_view.erl | 54 +++++++++++++++++++----------- 7 files changed, 136 insertions(+), 52 deletions(-) diff --git a/share/www/script/test/all_docs.js b/share/www/script/test/all_docs.js index 4324e3bb..1d83aa95 100644 --- a/share/www/script/test/all_docs.js +++ b/share/www/script/test/all_docs.js @@ -105,20 +105,24 @@ couchTests.all_docs = function(debug) { var winRev = db.open("3"); - changes = db.changes({include_docs: true, style: "all_docs"}); + changes = db.changes({include_docs: true, conflicts: true, style: "all_docs"}); TEquals("3", changes.results[3].id); TEquals(3, changes.results[3].changes.length); TEquals(winRev._rev, changes.results[3].changes[0].rev); TEquals("3", changes.results[3].doc._id); TEquals(winRev._rev, changes.results[3].doc._rev); + TEquals(true, changes.results[3].doc._conflicts instanceof Array); + TEquals(2, changes.results[3].doc._conflicts.length); - rows = db.allDocs({include_docs: true}).rows; + rows = db.allDocs({include_docs: true, conflicts: true}).rows; TEquals(3, rows.length); TEquals("3", rows[2].key); TEquals("3", rows[2].id); TEquals(winRev._rev, rows[2].value.rev); TEquals(winRev._rev, rows[2].doc._rev); TEquals("3", rows[2].doc._id); + TEquals(true, rows[2].doc._conflicts instanceof Array); + TEquals(2, rows[2].doc._conflicts.length); // test the all docs collates sanely db.save({_id: "Z", foo: "Z"}); diff --git a/share/www/script/test/view_include_docs.js b/share/www/script/test/view_include_docs.js index 06aafc56..944c9103 100644 --- a/share/www/script/test/view_include_docs.js +++ b/share/www/script/test/view_include_docs.js @@ -135,4 +135,58 @@ couchTests.view_include_docs = function(debug) { T(!resp.rows[0].doc); T(resp.rows[0].doc == null); T(resp.rows[1].doc.integer == 23); + + // COUCHDB-549 - include_docs=true with conflicts=true + + var dbA = new CouchDB("test_suite_db_a", {"X-Couch-Full-Commit":"false"}); + var dbB = new CouchDB("test_suite_db_b", {"X-Couch-Full-Commit":"false"}); + + dbA.deleteDb(); + dbA.createDb(); + dbB.deleteDb(); + dbB.createDb(); + + var ddoc = { + _id: "_design/mydesign", + language : "javascript", + views : { + myview : { + map: (function(doc) { + emit(doc.value, 1); + }).toString() + } + } + }; + TEquals(true, dbA.save(ddoc).ok); + + var doc1a = {_id: "foo", value: 1, str: "1"}; + TEquals(true, dbA.save(doc1a).ok); + + var doc1b = {_id: "foo", value: 1, str: "666"}; + TEquals(true, dbB.save(doc1b).ok); + + var doc2 = {_id: "bar", value: 2, str: "2"}; + TEquals(true, dbA.save(doc2).ok); + + TEquals(true, CouchDB.replicate(dbA.name, dbB.name).ok); + + doc1b = dbB.open("foo", {conflicts: true}); + TEquals(true, doc1b._conflicts instanceof Array); + TEquals(1, doc1b._conflicts.length); + var conflictRev = doc1b._conflicts[0]; + + doc2 = dbB.open("bar", {conflicts: true}); + TEquals("undefined", typeof doc2._conflicts); + + resp = dbB.view("mydesign/myview", {include_docs: true, conflicts: true}); + + TEquals(2, resp.rows.length); + TEquals(true, resp.rows[0].doc._conflicts instanceof Array); + TEquals(1, resp.rows[0].doc._conflicts.length); + TEquals(conflictRev, resp.rows[0].doc._conflicts[0]); + TEquals("undefined", typeof resp.rows[1].doc._conflicts); + + // cleanup + dbA.deleteDb(); + dbB.deleteDb(); }; diff --git a/src/couchdb/couch_changes.erl b/src/couchdb/couch_changes.erl index 6baaf7ec..2241e554 100644 --- a/src/couchdb/couch_changes.erl +++ b/src/couchdb/couch_changes.erl @@ -59,7 +59,7 @@ handle_changes(#changes_args{style=Style}=Args1, Req, Db) -> fun(CallbackAcc) -> {Callback, UserAcc} = get_callback_acc(CallbackAcc), UserAcc2 = start_sending_changes(Callback, UserAcc, Feed), - {ok, {_, LastSeq, _Prepend, _, _, UserAcc3, _, _, _}} = + {ok, {_, LastSeq, _Prepend, _, _, UserAcc3, _, _, _, _}} = send_changes( Args#changes_args{feed="normal"}, Callback, @@ -199,6 +199,7 @@ send_changes(Args, Callback, UserAcc, Db, StartSeq, Prepend) -> #changes_args{ style = Style, include_docs = IncludeDocs, + conflicts = Conflicts, limit = Limit, feed = ResponseType, dir = Dir, @@ -211,7 +212,7 @@ send_changes(Args, Callback, UserAcc, Db, StartSeq, Prepend) -> fun changes_enumerator/2, [{dir, Dir}], {Db, StartSeq, Prepend, FilterFun, Callback, UserAcc, ResponseType, - Limit, IncludeDocs} + Limit, IncludeDocs, Conflicts} ). keep_sending_changes(Args, Callback, UserAcc, Db, StartSeq, Prepend, Timeout, @@ -222,7 +223,7 @@ keep_sending_changes(Args, Callback, UserAcc, Db, StartSeq, Prepend, Timeout, db_open_options = DbOptions } = Args, % ?LOG_INFO("send_changes start ~p",[StartSeq]), - {ok, {_, EndSeq, Prepend2, _, _, UserAcc2, _, NewLimit, _}} = send_changes( + {ok, {_, EndSeq, Prepend2, _, _, UserAcc2, _, NewLimit, _, _}} = send_changes( Args#changes_args{dir=fwd}, Callback, UserAcc, Db, StartSeq, Prepend ), % ?LOG_INFO("send_changes last ~p",[EndSeq]), @@ -259,7 +260,7 @@ end_sending_changes(Callback, UserAcc, EndSeq, ResponseType) -> Callback({stop, EndSeq}, ResponseType, UserAcc). changes_enumerator(DocInfo, {Db, _, _, FilterFun, Callback, UserAcc, - "continuous", Limit, IncludeDocs}) -> + "continuous", Limit, IncludeDocs, Conflicts}) -> #doc_info{high_seq = Seq} = DocInfo, Results0 = FilterFun(DocInfo), @@ -268,17 +269,17 @@ changes_enumerator(DocInfo, {Db, _, _, FilterFun, Callback, UserAcc, case Results of [] -> {Go, {Db, Seq, nil, FilterFun, Callback, UserAcc, "continuous", Limit, - IncludeDocs} + IncludeDocs, Conflicts} }; _ -> - ChangesRow = changes_row(Db, Results, DocInfo, IncludeDocs), + ChangesRow = changes_row(Db, Results, DocInfo, IncludeDocs, Conflicts), UserAcc2 = Callback({change, ChangesRow, <<>>}, "continuous", UserAcc), {Go, {Db, Seq, nil, FilterFun, Callback, UserAcc2, "continuous", - Limit - 1, IncludeDocs} + Limit - 1, IncludeDocs, Conflicts} } end; changes_enumerator(DocInfo, {Db, _, Prepend, FilterFun, Callback, UserAcc, - ResponseType, Limit, IncludeDocs}) -> + ResponseType, Limit, IncludeDocs, Conflicts}) -> #doc_info{high_seq = Seq} = DocInfo, Results0 = FilterFun(DocInfo), @@ -287,25 +288,28 @@ changes_enumerator(DocInfo, {Db, _, Prepend, FilterFun, Callback, UserAcc, case Results of [] -> {Go, {Db, Seq, Prepend, FilterFun, Callback, UserAcc, ResponseType, - Limit, IncludeDocs} + Limit, IncludeDocs, Conflicts} }; _ -> - ChangesRow = changes_row(Db, Results, DocInfo, IncludeDocs), + ChangesRow = changes_row(Db, Results, DocInfo, IncludeDocs, Conflicts), UserAcc2 = Callback({change, ChangesRow, Prepend}, ResponseType, UserAcc), {Go, {Db, Seq, <<",\n">>, FilterFun, Callback, UserAcc2, ResponseType, - Limit - 1, IncludeDocs} + Limit - 1, IncludeDocs, Conflicts} } end. -changes_row(Db, Results, DocInfo, IncludeDoc) -> +changes_row(Db, Results, DocInfo, IncludeDoc, Conflicts) -> #doc_info{ id = Id, high_seq = Seq, revs = [#rev_info{deleted = Del} | _] } = DocInfo, {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"changes">>, Results}] ++ deleted_item(Del) ++ case IncludeDoc of - true -> couch_httpd_view:doc_member(Db, DocInfo); - false -> [] + true -> + Options = if Conflicts -> [conflicts]; true -> [] end, + couch_httpd_view:doc_member(Db, DocInfo, Options); + false -> + [] end}. deleted_item(true) -> [{<<"deleted">>, true}]; diff --git a/src/couchdb/couch_db.hrl b/src/couchdb/couch_db.hrl index 6b6c6440..003cb688 100644 --- a/src/couchdb/couch_db.hrl +++ b/src/couchdb/couch_db.hrl @@ -177,6 +177,7 @@ view_type = nil, include_docs = false, + conflicts = false, stale = false, multi_get = false, callback = nil, @@ -271,6 +272,7 @@ timeout, filter = "", include_docs = false, + conflicts = false, db_open_options = [] }). diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl index 82e9b989..85c439ba 100644 --- a/src/couchdb/couch_httpd_db.erl +++ b/src/couchdb/couch_httpd_db.erl @@ -506,7 +506,7 @@ all_docs_view(Req, Db, Keys) -> 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, - send_row = fun all_docs_send_json_view_row/5 + send_row = fun all_docs_send_json_view_row/6 }), AdapterFun = fun(#full_doc_info{id=Id}=FullDocInfo, Offset, Acc) -> case couch_doc:to_doc_info(FullDocInfo) of @@ -524,7 +524,7 @@ all_docs_view(Req, Db, Keys) -> FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, UpdateSeq, TotalRowCount, #view_fold_helper_funs{ reduce_count = fun(Offset) -> Offset end, - send_row = fun all_docs_send_json_view_row/5 + send_row = fun all_docs_send_json_view_row/6 }), KeyFoldFun = case Dir of fwd -> @@ -551,21 +551,22 @@ all_docs_view(Req, Db, Keys) -> end end). -all_docs_send_json_view_row(Resp, Db, KV, IncludeDocs, RowFront) -> - JsonRow = all_docs_view_row_obj(Db, KV, IncludeDocs), +all_docs_send_json_view_row(Resp, Db, KV, IncludeDocs, Conflicts, RowFront) -> + JsonRow = all_docs_view_row_obj(Db, KV, IncludeDocs, Conflicts), send_chunk(Resp, RowFront ++ ?JSON_ENCODE(JsonRow)), {ok, ",\r\n"}. -all_docs_view_row_obj(_Db, {{DocId, error}, Value}, _IncludeDocs) -> +all_docs_view_row_obj(_Db, {{DocId, error}, Value}, _IncludeDocs, _Conflicts) -> {[{key, DocId}, {error, Value}]}; -all_docs_view_row_obj(Db, {_KeyDocId, DocInfo}, true) -> +all_docs_view_row_obj(Db, {_KeyDocId, DocInfo}, true, Conflicts) -> case DocInfo of #doc_info{revs = [#rev_info{deleted = true} | _]} -> {all_docs_row(DocInfo) ++ [{doc, null}]}; _ -> - {all_docs_row(DocInfo) ++ couch_httpd_view:doc_member(Db, DocInfo)} + {all_docs_row(DocInfo) ++ couch_httpd_view:doc_member( + Db, DocInfo, if Conflicts -> [conflicts]; true -> [] end)} end; -all_docs_view_row_obj(_Db, {_KeyDocId, DocInfo}, _IncludeDocs) -> +all_docs_view_row_obj(_Db, {_KeyDocId, DocInfo}, _IncludeDocs, _Conflicts) -> {all_docs_row(DocInfo)}. all_docs_row(#doc_info{id = Id, revs = [RevInfo | _]}) -> @@ -1221,6 +1222,8 @@ parse_changes_query(Req) -> Args#changes_args{timeout=list_to_integer(Value)}; {"include_docs", "true"} -> Args#changes_args{include_docs=true}; + {"conflicts", "true"} -> + Args#changes_args{conflicts=true}; {"filter", _} -> Args#changes_args{filter=Value}; _Else -> % unknown key value pair, ignore. diff --git a/src/couchdb/couch_httpd_show.erl b/src/couchdb/couch_httpd_show.erl index 70dd82e1..59f74e1c 100644 --- a/src/couchdb/couch_httpd_show.erl +++ b/src/couchdb/couch_httpd_show.erl @@ -309,18 +309,20 @@ start_list_resp(QServer, LName, Req, Db, Head, Etag) -> {ok, Resp, ?b2l(?l2b(Chunks))}. make_map_send_row_fun(QueryServer) -> - fun(Resp, Db, Row, IncludeDocs, RowFront) -> - send_list_row(Resp, QueryServer, Db, Row, RowFront, IncludeDocs) + fun(Resp, Db, Row, IncludeDocs, Conflicts, RowFront) -> + send_list_row( + Resp, QueryServer, Db, Row, RowFront, IncludeDocs, Conflicts) end. make_reduce_send_row_fun(QueryServer, Db) -> fun(Resp, Row, RowFront) -> - send_list_row(Resp, QueryServer, Db, Row, RowFront, false) + send_list_row(Resp, QueryServer, Db, Row, RowFront, false, false) end. -send_list_row(Resp, QueryServer, Db, Row, RowFront, IncludeDoc) -> +send_list_row(Resp, QueryServer, Db, Row, RowFront, IncludeDoc, Conflicts) -> try - [Go,Chunks] = prompt_list_row(QueryServer, Db, Row, IncludeDoc), + [Go,Chunks] = prompt_list_row( + QueryServer, Db, Row, IncludeDoc, Conflicts), Chunk = RowFront ++ ?b2l(?l2b(Chunks)), send_non_empty_chunk(Resp, Chunk), case Go of @@ -336,11 +338,12 @@ send_list_row(Resp, QueryServer, Db, Row, RowFront, IncludeDoc) -> end. -prompt_list_row({Proc, _DDocId}, Db, {{Key, DocId}, Value}, IncludeDoc) -> - JsonRow = couch_httpd_view:view_row_obj(Db, {{Key, DocId}, Value}, IncludeDoc), +prompt_list_row({Proc, _DDocId}, Db, {{_Key, _DocId}, _} = Kv, + IncludeDoc, Conflicts) -> + JsonRow = couch_httpd_view:view_row_obj(Db, Kv, IncludeDoc, Conflicts), couch_query_servers:proc_prompt(Proc, [<<"list_row">>, JsonRow]); -prompt_list_row({Proc, _DDocId}, _, {Key, Value}, _IncludeDoc) -> +prompt_list_row({Proc, _DDocId}, _, {Key, Value}, _IncludeDoc, _Conflicts) -> JsonRow = {[{key, Key}, {value, Value}]}, couch_query_servers:proc_prompt(Proc, [<<"list_row">>, JsonRow]). diff --git a/src/couchdb/couch_httpd_view.erl b/src/couchdb/couch_httpd_view.erl index 8906de70..b71fc2c6 100644 --- a/src/couchdb/couch_httpd_view.erl +++ b/src/couchdb/couch_httpd_view.erl @@ -16,9 +16,9 @@ -export([handle_view_req/3,handle_temp_view_req/2]). -export([parse_view_params/3]). --export([make_view_fold_fun/7, finish_view_fold/4, finish_view_fold/5, view_row_obj/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/2]). +-export([design_doc_view/5, parse_bool_param/1, doc_member/3]). -export([make_key_options/1, load_view/4]). -import(couch_httpd, @@ -319,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", _) -> @@ -427,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. @@ -438,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, @@ -456,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. @@ -537,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, @@ -610,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"}. @@ -639,10 +651,10 @@ view_etag(_Db, #group{sig=Sig}, #view{update_seq=UpdateSeq, purge_seq=PurgeSeq}, 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; @@ -650,27 +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, #doc_info{id = Id, revs = [#rev_info{rev = Rev} | _]} = Info) -> +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]) of + 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}) -> +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}]; -- cgit v1.2.3