diff --git a/src/fabric.erl b/src/fabric.erl index d84769a..1ffeba9 100644 --- a/src/fabric.erl +++ b/src/fabric.erl @@ -26,8 +26,9 @@ get_security/2, get_all_security/1, get_all_security/2]). % Documents --export([open_doc/3, open_revs/4, get_missing_revs/2, get_missing_revs/3, - update_doc/3, update_docs/3, purge_docs/2, att_receiver/2]). +-export([open_doc/3, open_revs/4, get_doc_info/3, get_full_doc_info/3, + get_missing_revs/2, get_missing_revs/3, update_doc/3, update_docs/3, + purge_docs/2, att_receiver/2]). % Views -export([all_docs/4, changes/4, query_view/3, query_view/4, query_view/6, @@ -164,7 +165,12 @@ get_all_security(DbName, Options) -> {error, any()} | {error, any() | any()}. open_doc(DbName, Id, Options) -> - fabric_doc_open:go(dbname(DbName), docid(Id), opts(Options)). + case proplists:get_value(doc_info, Options) of + undefined -> + fabric_doc_open:go(dbname(DbName), docid(Id), opts(Options)); + Else -> + {error, {invalid_option, {doc_info, Else}}} + end. %% @doc retrieve a collection of revisions, possible all -spec open_revs(dbname(), docid(), [revision()] | all, [option()]) -> @@ -175,6 +181,28 @@ open_doc(DbName, Id, Options) -> open_revs(DbName, Id, Revs, Options) -> fabric_doc_open_revs:go(dbname(DbName), docid(Id), Revs, opts(Options)). +%% @doc Retrieves an information on a document with a given id +-spec get_doc_info(dbname(), docid(), [options()]) -> + {ok, #doc_info{}} | + {not_found, missing} | + {timeout, any()} | + {error, any()} | + {error, any() | any()}. +get_doc_info(DbName, Id, Options) -> + Options1 = [doc_info|Options], + fabric_doc_open:go(dbname(DbName), docid(Id), opts(Options1)). + +%% @doc Retrieves a full information on a document with a given id +-spec get_full_doc_info(dbname(), docid(), [options()]) -> + {ok, #full_doc_info{}} | + {not_found, missing | deleted} | + {timeout, any()} | + {error, any()} | + {error, any() | any()}. +get_full_doc_info(DbName, Id, Options) -> + Options1 = [{doc_info, full}|Options], + fabric_doc_open:go(dbname(DbName), docid(Id), opts(Options1)). + %% @equiv get_missing_revs(DbName, IdsRevs, []) get_missing_revs(DbName, IdsRevs) -> get_missing_revs(DbName, IdsRevs, []). diff --git a/src/fabric_doc_open.erl b/src/fabric_doc_open.erl index 8f1820f..9d15b65 100644 --- a/src/fabric_doc_open.erl +++ b/src/fabric_doc_open.erl @@ -32,7 +32,12 @@ go(DbName, Id, Options) -> - Workers = fabric_util:submit_jobs(mem3:shards(DbName,Id), open_doc, + Handler = case proplists:get_value(doc_info, Options) of + true -> get_doc_info; + full -> get_full_doc_info; + undefined -> open_doc + end, + Workers = fabric_util:submit_jobs(mem3:shards(DbName,Id), Handler, [Id, [deleted|Options]]), SuppressDeletedDoc = not lists:member(deleted, Options), N = mem3:n(DbName), @@ -46,11 +51,15 @@ go(DbName, Id, Options) -> }, RexiMon = fabric_util:create_monitors(Workers), try fabric_util:recv(Workers, #shard.ref, fun handle_message/3, Acc0) of - {ok, #acc{}=Acc} -> + {ok, #acc{}=Acc} when Handler =:= open_doc -> Reply = handle_response(Acc), format_reply(Reply, SuppressDeletedDoc); + {ok, #acc{state = r_not_met}} -> + {error, quorum_not_met}; + {ok, #acc{q_reply = QuorumReply}} -> + format_reply(QuorumReply, SuppressDeletedDoc); {timeout, #acc{workers=DefunctWorkers}} -> - fabric_util:log_timeout(DefunctWorkers, "open_doc"), + fabric_util:log_timeout(DefunctWorkers, atom_to_list(Handler)), {error, timeout}; Error -> Error @@ -159,12 +168,15 @@ choose_reply(Docs) -> end, Docs), {ok, Winner}. +format_reply({ok, #full_doc_info{deleted=true}}, true) -> + {not_found, deleted}; format_reply({ok, #doc{deleted=true}}, true) -> {not_found, deleted}; +format_reply(not_found, _) -> + {not_found, missing}; format_reply(Else, _) -> Else. - is_r_met_test() -> Workers0 = [], Workers1 = [nil], @@ -475,6 +487,54 @@ handle_response_quorum_met_test() -> stop_meck_(), ok. +get_doc_info_test() -> + start_meck_(), + meck:new([mem3, rexi_monitor, fabric_util]), + meck:expect(twig, log, fun(_, _, _) -> ok end), + meck:expect(fabric, update_docs, fun(_, _, _) -> {ok, []} end), + meck:expect(couch_stats, increment_counter, fun(_) -> ok end), + meck:expect(fabric_util, submit_jobs, fun(_, _, _) -> ok end), + meck:expect(fabric_util, create_monitors, fun(_) -> ok end), + meck:expect(rexi_monitor, stop, fun(_) -> ok end), + meck:expect(mem3, shards, fun(_, _) -> ok end), + meck:expect(mem3, n, fun(_) -> 3 end), + meck:expect(mem3, quorum, fun(_) -> 2 end), + + meck:expect(fabric_util, recv, fun(_, _, _, _) -> + {ok, #acc{state = r_not_met}} + end), + Rsp1 = fabric_doc_open:go("test", "one", [doc_info]), + ?assertEqual({error, quorum_not_met}, Rsp1), + + Rsp2 = fabric_doc_open:go("test", "one", [{doc_info, full}]), + ?assertEqual({error, quorum_not_met}, Rsp2), + + meck:expect(fabric_util, recv, fun(_, _, _, _) -> + {ok, #acc{state = r_met, q_reply = not_found}} + end), + MissingRsp1 = fabric_doc_open:go("test", "one", [doc_info]), + ?assertEqual({not_found, missing}, MissingRsp1), + MissingRsp2 = fabric_doc_open:go("test", "one", [{doc_info, full}]), + ?assertEqual({not_found, missing}, MissingRsp2), + + meck:expect(fabric_util, recv, fun(_, _, _, _) -> + A = #doc_info{}, + {ok, #acc{state = r_met, q_reply = {ok, A}}} + end), + {ok, Rec1} = fabric_doc_open:go("test", "one", [doc_info]), + ?assert(is_record(Rec1, doc_info)), + + meck:expect(fabric_util, recv, fun(_, _, _, _) -> + A = #full_doc_info{deleted = true}, + {ok, #acc{state = r_met, q_reply = {ok, A}}} + end), + Rsp3 = fabric_doc_open:go("test", "one", [{doc_info, full}]), + ?assertEqual({not_found, deleted}, Rsp3), + {ok, Rec2} = fabric_doc_open:go("test", "one", [{doc_info, full},deleted]), + ?assert(is_record(Rec2, full_doc_info)), + + meck:unload([mem3, rexi_monitor, fabric_util]), + stop_meck_(). start_meck_() -> meck:new([twig, rexi, fabric, couch_stats]). diff --git a/src/fabric_rpc.erl b/src/fabric_rpc.erl index c570c28..ad3903b 100644 --- a/src/fabric_rpc.erl +++ b/src/fabric_rpc.erl @@ -15,8 +15,8 @@ -module(fabric_rpc). -export([get_db_info/1, get_doc_count/1, get_update_seq/1]). --export([open_doc/3, open_revs/4, get_missing_revs/2, get_missing_revs/3, - update_docs/3]). +-export([open_doc/3, open_revs/4, get_doc_info/3, get_full_doc_info/3, + get_missing_revs/2, get_missing_revs/3, update_docs/3]). -export([all_docs/2, changes/3, map_view/4, reduce_view/4, group_info/2]). -export([create_db/1, delete_db/1, reset_validation_funs/1, set_security/3, set_revs_limit/3, create_shard_db_doc/2, delete_shard_db_doc/2]). @@ -237,6 +237,12 @@ open_doc(DbName, DocId, Options) -> open_revs(DbName, Id, Revs, Options) -> with_db(DbName, Options, {couch_db, open_doc_revs, [Id, Revs, Options]}). +get_full_doc_info(DbName, DocId, Options) -> + with_db(DbName, Options, {couch_db, get_full_doc_info, [DocId]}). + +get_doc_info(DbName, DocId, Options) -> + with_db(DbName, Options, {couch_db, get_doc_info, [DocId]}). + get_missing_revs(DbName, IdRevsList) -> get_missing_revs(DbName, IdRevsList, []).