summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--share/www/script/test/all_docs.js8
-rw-r--r--share/www/script/test/view_include_docs.js54
-rw-r--r--src/couchdb/couch_changes.erl32
-rw-r--r--src/couchdb/couch_db.hrl2
-rw-r--r--src/couchdb/couch_httpd_db.erl19
-rw-r--r--src/couchdb/couch_httpd_show.erl19
-rw-r--r--src/couchdb/couch_httpd_view.erl54
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}];