From d6cb0bc17d834675a69620940036490b909a4b0d Mon Sep 17 00:00:00 2001 From: Jan Lehnardt Date: Wed, 12 Aug 2009 18:48:25 +0000 Subject: add native /db/_conflicts view, patch by Adam Kocolosk, closes COUCHDB-462 git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@803663 13f79535-47bb-0310-9956-ffa450edef68 --- src/couchdb/couch_db.hrl | 3 +- src/couchdb/couch_httpd_db.erl | 211 ++++++++++++++++++++++++++++----------- src/couchdb/couch_httpd_view.erl | 6 +- 3 files changed, 157 insertions(+), 63 deletions(-) (limited to 'src') diff --git a/src/couchdb/couch_db.hrl b/src/couchdb/couch_db.hrl index a487300f..46725ed1 100644 --- a/src/couchdb/couch_db.hrl +++ b/src/couchdb/couch_db.hrl @@ -175,7 +175,8 @@ stale = false, multi_get = false, callback = nil, - list = nil + list = nil, + deleted = false }). -record(view_fold_helper_funs, { diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl index 159bcbe8..c93c736d 100644 --- a/src/couchdb/couch_httpd_db.erl +++ b/src/couchdb/couch_httpd_db.erl @@ -383,76 +383,23 @@ db_req(#httpd{method='GET',path_parts=[_,<<"_all_docs">>]}=Req, Db) -> all_docs_view(Req, Db, nil); db_req(#httpd{method='POST',path_parts=[_,<<"_all_docs">>]}=Req, Db) -> - {Fields} = couch_httpd:json_body_obj(Req), - case proplists:get_value(<<"keys">>, Fields, nil) of - nil -> - ?LOG_DEBUG("POST to _all_docs with no keys member.", []), - all_docs_view(Req, Db, nil); - Keys when is_list(Keys) -> - all_docs_view(Req, Db, Keys); - _ -> - throw({bad_request, "`keys` member must be a array."}) - end; + post_keys_to_view(Req, Db, fun all_docs_view/3); db_req(#httpd{path_parts=[_,<<"_all_docs">>]}=Req, _Db) -> send_method_not_allowed(Req, "GET,HEAD,POST"); db_req(#httpd{method='GET',path_parts=[_,<<"_all_docs_by_seq">>]}=Req, Db) -> - #view_query_args{ - start_key = StartKey, - limit = Limit, - skip = SkipCount, - direction = Dir - } = QueryArgs = couch_httpd_view:parse_view_params(Req, nil, map), - - {ok, Info} = couch_db:get_db_info(Db), - CurrentEtag = couch_httpd:make_etag(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, - high_seq=Seq, - revs=[#rev_info{rev=Rev,deleted=Deleted} | RestInfo] - } = DocInfo, - ConflictRevs = couch_doc:rev_to_strs( - [Rev1 || #rev_info{deleted=false, rev=Rev1} <- RestInfo]), - DelConflictRevs = couch_doc:rev_to_strs( - [Rev1 || #rev_info{deleted=true, rev=Rev1} <- RestInfo]), - Json = { - [{<<"rev">>, couch_doc:rev_to_str(Rev)}] ++ - case ConflictRevs of - [] -> []; - _ -> [{<<"conflicts">>, ConflictRevs}] - end ++ - case DelConflictRevs of - [] -> []; - _ -> [{<<"deleted_conflicts">>, DelConflictRevs}] - end ++ - case Deleted of - true -> [{<<"deleted">>, true}]; - false -> [] - end - }, - FoldlFun({{Seq, Id}, Json}, Offset, Acc) - end, {Limit, SkipCount, undefined, [], nil}), - couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult}) - end); + all_docs_by_seq_view(Req, Db); db_req(#httpd{path_parts=[_,<<"_all_docs_by_seq">>]}=Req, _Db) -> send_method_not_allowed(Req, "GET,HEAD"); +db_req(#httpd{method='GET',path_parts=[_,<<"_conflicts">>]}=Req, Db) -> + conflicts_view(Req, Db, nil); + +db_req(#httpd{path_parts=[_,<<"_conflicts">>]}=Req, _Db) -> + send_method_not_allowed(Req, "GET,HEAD"); + db_req(#httpd{method='POST',path_parts=[_,<<"_missing_revs">>]}=Req, Db) -> {JsonDocIdRevs} = couch_httpd:json_body_obj(Req), JsonDocIdRevs2 = [{Id, [couch_doc:parse_rev(RevStr) || RevStr <- RevStrs]} || {Id, RevStrs} <- JsonDocIdRevs], @@ -511,6 +458,18 @@ db_req(#httpd{path_parts=[_, DocId]}=Req, Db) -> db_req(#httpd{path_parts=[_, DocId | FileNameParts]}=Req, Db) -> db_attachment_req(Req, Db, DocId, FileNameParts). +post_keys_to_view(Req, Db, ViewFun) -> + {Fields} = couch_httpd:json_body_obj(Req), + case proplists:get_value(<<"keys">>, Fields, nil) of + nil -> + ?LOG_DEBUG("POST to view with no keys member.", []), + ViewFun(Req, Db, nil); + Keys when is_list(Keys) -> + ViewFun(Req, Db, Keys); + _ -> + throw({bad_request, "`keys` member must be a array."}) + end. + all_docs_view(Req, Db, Keys) -> #view_query_args{ start_key = StartKey, @@ -597,6 +556,136 @@ all_docs_view(Req, Db, Keys) -> end end). +all_docs_by_seq_view(Req, Db) -> + #view_query_args{ + start_key = StartKey, + limit = Limit, + skip = SkipCount, + direction = Dir + } = QueryArgs = couch_httpd_view:parse_view_params(Req, nil, map), + + {ok, Info} = couch_db:get_db_info(Db), + CurrentEtag = couch_httpd:make_etag(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, + high_seq=Seq, + revs=[#rev_info{rev=Rev,deleted=Deleted} | RestInfo] + } = DocInfo, + ConflictRevs = couch_doc:rev_to_strs( + [Rev1 || #rev_info{deleted=false, rev=Rev1} <- RestInfo]), + DelConflictRevs = couch_doc:rev_to_strs( + [Rev1 || #rev_info{deleted=true, rev=Rev1} <- RestInfo]), + Json = { + [{<<"rev">>, couch_doc:rev_to_str(Rev)}] ++ + case ConflictRevs of + [] -> []; + _ -> [{<<"conflicts">>, ConflictRevs}] + end ++ + case DelConflictRevs of + [] -> []; + _ -> [{<<"deleted_conflicts">>, DelConflictRevs}] + end ++ + case Deleted of + true -> [{<<"deleted">>, true}]; + false -> [] + end + }, + FoldlFun({{Seq, Id}, Json}, Offset, Acc) + end, {Limit, SkipCount, undefined, [], nil}), + couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult}) + end). + +conflicts_view(Req, Db, nil) -> + #view_query_args{ + start_key = StartKey, + limit = Limit, + skip = SkipCount, + direction = Dir, + deleted = ShowDeletedConflicts + } = QueryArgs = couch_httpd_view:parse_view_params(Req, nil, map), + + StartResp = fun(Req2, Etag, _TotalViewCount, _Offset, _Acc) -> + {ok, Resp} = couch_httpd:start_json_response(Req2, 200, [{"Etag",Etag}]), + {ok, Resp, "{\"rows\":[\r\n"} + end, + + SendRow = fun(Resp, _Db, {{Id,Rev}, Value}, _IncludeDocs, RowFront) -> + {IsDeleted, Conflicts, DelConflicts} = Value, + JsonProps = lists:flatten([{key, Id},{id, Id}, {rev, Rev}, + case IsDeleted of true -> {deleted, true}; _ -> [] end, + case Conflicts of [] -> []; _ -> {conflicts, Conflicts} end, + case DelConflicts of + [] -> []; + _ -> {deleted_conflicts, DelConflicts} + end + ]), + send_chunk(Resp, RowFront ++ ?JSON_ENCODE({JsonProps})), + {ok, ",\r\n"} + end, + + CurrentEtag = couch_httpd:make_etag(couch_db:get_update_seq(Db)), + couch_httpd:etag_respond(Req, CurrentEtag, fun() -> + HelperFuns = #view_fold_helper_funs{ + start_response = StartResp, + send_row = SendRow, + reduce_count = fun couch_db:enum_docs_reduce_to_count/1 + }, + FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, + CurrentEtag, Db, 0, HelperFuns), + + EnumFun = fun(FullDocInfo, Offset, Acc) -> + IsDeleted = FullDocInfo#full_doc_info.deleted, + #doc_info{ + id = Id, + revs = [RevInfo | ConflictInfo] + } = couch_doc:to_doc_info(FullDocInfo), + RevStr = couch_doc:rev_to_str(RevInfo#rev_info.rev), + Conflicts = couch_doc:rev_to_strs( + [Rev || #rev_info{deleted=false, rev=Rev} <- ConflictInfo]), + DelConflicts = couch_doc:rev_to_strs( + [Rev || #rev_info{deleted=true, rev=Rev} <- ConflictInfo]), + case {ShowDeletedConflicts, Conflicts, DelConflicts} of + {_, [], []} -> + {ok, Acc}; + {false, [], _} -> + {ok, Acc}; + {true, _, _} -> + Value = {IsDeleted, Conflicts, DelConflicts}, + FoldlFun({{Id,RevStr}, Value}, Offset, Acc); + {false, _, _} -> + Value = {IsDeleted, Conflicts, []}, + FoldlFun({{Id,RevStr}, Value}, Offset, Acc) + end + end, + + Acc0 = {Limit, SkipCount, undefined, [], nil}, + case couch_db:enum_docs(Db, StartKey, Dir, EnumFun, Acc0) of + {ok, {_, _, undefined, _, _}} -> + % nothing found in the view, send empty view + send_json(Req, 200, {[{rows, []}]}); + {ok, {_, _, Resp, _, _}} -> + % end the view + send_chunk(Resp, "\r\n]}"), + end_json_response(Resp); + Error -> + throw(Error) + end + end). + db_doc_req(#httpd{method='DELETE'}=Req, Db, DocId) -> % check for the existence of the doc to handle the 404 case. couch_doc_open(Db, DocId, nil, []), diff --git a/src/couchdb/couch_httpd_view.erl b/src/couchdb/couch_httpd_view.erl index 8264186b..da9d478d 100644 --- a/src/couchdb/couch_httpd_view.erl +++ b/src/couchdb/couch_httpd_view.erl @@ -314,6 +314,8 @@ parse_view_param("include_docs", Value) -> [{include_docs, parse_bool_param(Value)}]; parse_view_param("list", Value) -> [{list, ?l2b(Value)}]; +parse_view_param("deleted", Value) -> + [{deleted, parse_bool_param(Value)}]; parse_view_param("callback", _) -> []; % Verified in the JSON response functions parse_view_param(Key, Value) -> @@ -398,7 +400,9 @@ validate_view_query(include_docs, true, Args) -> validate_view_query(include_docs, _Value, Args) -> Args; validate_view_query(extra, _Value, Args) -> - Args. + Args; +validate_view_query(deleted, Value, Args) -> + Args#view_query_args{deleted = Value}. make_view_fold_fun(Req, QueryArgs, Etag, Db, TotalViewCount, HelperFuns) -> #view_query_args{ -- cgit v1.2.3