You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by kx...@apache.org on 2015/09/18 00:10:27 UTC
chttpd commit: updated refs/heads/master to 933ba2e
Repository: couchdb-chttpd
Updated Branches:
refs/heads/master e1c0f12d9 -> 933ba2e3a
Implement /db/_bulk_get endpoint
COUCHDB-2310
Project: http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/commit/933ba2e3
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/tree/933ba2e3
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/diff/933ba2e3
Branch: refs/heads/master
Commit: 933ba2e3a2a77637b4ad275cf505b7d8a3ef0777
Parents: e1c0f12
Author: Alexander Shorin <kx...@apache.org>
Authored: Wed Apr 22 21:30:47 2015 +0300
Committer: Alexander Shorin <kx...@apache.org>
Committed: Fri Sep 18 01:08:16 2015 +0300
----------------------------------------------------------------------
src/chttpd_db.erl | 196 +++++++++++++++++++-
test/chttpd_db_bulk_get_test.erl | 334 ++++++++++++++++++++++++++++++++++
2 files changed, 526 insertions(+), 4 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/blob/933ba2e3/src/chttpd_db.erl
----------------------------------------------------------------------
diff --git a/src/chttpd_db.erl b/src/chttpd_db.erl
index 7798deb..327e6d0 100644
--- a/src/chttpd_db.erl
+++ b/src/chttpd_db.erl
@@ -438,6 +438,36 @@ db_req(#httpd{method='POST',path_parts=[_,<<"_bulk_docs">>], user_ctx=Ctx}=Req,
db_req(#httpd{path_parts=[_,<<"_bulk_docs">>]}=Req, _Db) ->
send_method_not_allowed(Req, "POST");
+
+db_req(#httpd{method='POST', path_parts=[_, <<"_bulk_get">>]}=Req, Db) ->
+ couch_stats:increment_counter([couchdb, httpd, bulk_requests]),
+ couch_httpd:validate_ctype(Req, "application/json"),
+ {JsonProps} = chttpd:json_body_obj(Req),
+ case couch_util:get_value(<<"docs">>, JsonProps) of
+ undefined ->
+ throw({bad_request, <<"Missing JSON list of 'docs'.">>});
+ Docs ->
+ #doc_query_args{
+ options = Options
+ } = bulk_get_parse_doc_query(Req),
+
+ {ok, Resp} = start_json_response(Req, 200),
+ send_chunk(Resp, <<"{\"results\": [">>),
+
+ lists:foldl(fun(Doc, Sep) ->
+ {DocId, Results, Options1} = bulk_get_open_doc_revs(Db, Doc,
+ Options),
+ bulk_get_send_docs_json(Resp, DocId, Results, Options1, Sep),
+ <<",">>
+ end, <<"">>, Docs),
+
+ send_chunk(Resp, <<"]}">>),
+ end_json_response(Resp)
+ end;
+db_req(#httpd{path_parts=[_, <<"_bulk_get">>]}=Req, _Db) ->
+ send_method_not_allowed(Req, "POST");
+
+
db_req(#httpd{method='POST',path_parts=[_,<<"_purge">>]}=Req, Db) ->
chttpd:validate_ctype(Req, "application/json"),
{IdsRevs} = chttpd:json_body_obj(Req),
@@ -1291,8 +1321,10 @@ get_md5_header(Req) ->
end.
parse_doc_query(Req) ->
- lists:foldl(fun({Key,Value}, Args) ->
- case {Key, Value} of
+ lists:foldl(fun parse_doc_query/2, #doc_query_args{}, chttpd:qs(Req)).
+
+parse_doc_query({Key, Value}, Args) ->
+ case {Key, Value} of
{"attachments", "true"} ->
Options = [attachments | Args#doc_query_args.options],
Args#doc_query_args{options=Options};
@@ -1345,8 +1377,7 @@ parse_doc_query(Req) ->
Args#doc_query_args{options=Options};
_Else -> % unknown key value pair, ignore.
Args
- end
- end, #doc_query_args{}, chttpd:qs(Req)).
+ end.
parse_changes_query(Req) ->
erlang:erase(changes_seq_interval),
@@ -1525,6 +1556,163 @@ set_namespace(<<"_design_docs">>, Args) ->
set_namespace(NS, #mrargs{extra = Extra} = Args) ->
Args#mrargs{extra = [{namespace, NS} | Extra]}.
+
+%% /db/_bulk_get stuff
+
+bulk_get_parse_doc_query(Req) ->
+ lists:foldl(fun({Key, Value}, Args) ->
+ ok = validate_query_param(Key),
+ parse_doc_query({Key, Value}, Args)
+ end, #doc_query_args{}, chttpd:qs(Req)).
+
+
+validate_query_param("open_revs"=Key) ->
+ throw_bad_query_param(Key);
+validate_query_param("new_edits"=Key) ->
+ throw_bad_query_param(Key);
+validate_query_param("w"=Key) ->
+ throw_bad_query_param(Key);
+validate_query_param("rev"=Key) ->
+ throw_bad_query_param(Key);
+validate_query_param("atts_since"=Key) ->
+ throw_bad_query_param(Key);
+validate_query_param(_) ->
+ ok.
+
+throw_bad_query_param(Key) when is_list(Key) ->
+ throw_bad_query_param(?l2b(Key));
+throw_bad_query_param(Key) when is_binary(Key) ->
+ Msg = <<"\"", Key/binary, "\" query parameter is not acceptable">>,
+ throw({bad_request, Msg}).
+
+
+bulk_get_open_doc_revs(Db, {Props}, Options) ->
+ bulk_get_open_doc_revs1(Db, Props, Options, {}).
+
+
+bulk_get_open_doc_revs1(Db, Props, Options, {}) ->
+ case parse_field(<<"id">>, couch_util:get_value(<<"id">>, Props)) of
+ {error, {DocId, Error, Reason}} ->
+ {DocId, {error, {null, Error, Reason}}, Options};
+
+ {ok, undefined} ->
+ Error = {null, bad_request, <<"document id missed">>},
+ {null, {error, Error}, Options};
+
+ {ok, DocId} ->
+ bulk_get_open_doc_revs1(Db, Props, Options, {DocId})
+ end;
+bulk_get_open_doc_revs1(Db, Props, Options, {DocId}) ->
+ RevStr = couch_util:get_value(<<"rev">>, Props),
+
+ case parse_field(<<"rev">>, RevStr) of
+ {error, {RevStr, Error, Reason}} ->
+ {DocId, {error, {RevStr, Error, Reason}}, Options};
+
+ {ok, undefined} ->
+ bulk_get_open_doc_revs1(Db, Props, Options, {DocId, all});
+
+ {ok, Rev} ->
+ bulk_get_open_doc_revs1(Db, Props, Options, {DocId, [Rev]})
+ end;
+bulk_get_open_doc_revs1(Db, Props, Options, {DocId, Revs}) ->
+ AttsSinceStr = couch_util:get_value(<<"atts_since">>, Props),
+
+ case parse_field(<<"atts_since">>, AttsSinceStr) of
+ {error, {BadAttsSinceRev, Error, Reason}} ->
+ {DocId, {error, {BadAttsSinceRev, Error, Reason}}, Options};
+
+ {ok, []} ->
+ bulk_get_open_doc_revs1(Db, Props, Options, {DocId, Revs, Options});
+
+ {ok, RevList} ->
+ Options1 = [{atts_since, RevList}, attachments | Options],
+ bulk_get_open_doc_revs1(Db, Props, Options, {DocId, Revs, Options1})
+ end;
+bulk_get_open_doc_revs1(Db, Props, _, {DocId, Revs, Options}) ->
+ case fabric:open_revs(Db, DocId, Revs, Options) of
+ {ok, []} ->
+ RevStr = couch_util:get_value(<<"rev">>, Props),
+ Error = {RevStr, <<"not_found">>, <<"missing">>},
+ {DocId, {error, Error}, Options};
+ Results ->
+ {DocId, Results, Options}
+ end.
+
+
+parse_field(<<"id">>, undefined) ->
+ {ok, undefined};
+parse_field(<<"id">>, Value) ->
+ try
+ ok = couch_doc:validate_docid(Value),
+ {ok, Value}
+ catch
+ throw:{Error, Reason} ->
+ {error, {Value, Error, Reason}}
+ end;
+parse_field(<<"rev">>, undefined) ->
+ {ok, undefined};
+parse_field(<<"rev">>, Value) ->
+ try
+ Rev = couch_doc:parse_rev(Value),
+ {ok, Rev}
+ catch
+ throw:{bad_request=Error, Reason} ->
+ {error, {Value, Error, Reason}}
+ end;
+parse_field(<<"atts_since">>, undefined) ->
+ {ok, []};
+parse_field(<<"atts_since">>, []) ->
+ {ok, []};
+parse_field(<<"atts_since">>, Value) when is_list(Value) ->
+ parse_atts_since(Value, []);
+parse_field(<<"atts_since">>, Value) ->
+ {error, {Value, bad_request, <<"att_since value must be array of revs.">>}}.
+
+
+parse_atts_since([], Acc) ->
+ {ok, lists:reverse(Acc)};
+parse_atts_since([RevStr | Rest], Acc) ->
+ case parse_field(<<"rev">>, RevStr) of
+ {ok, Rev} ->
+ parse_atts_since(Rest, [Rev | Acc]);
+ {error, _}=Error ->
+ Error
+ end.
+
+
+bulk_get_send_docs_json(Resp, DocId, Results, Options, Sep) ->
+ Id = ?JSON_ENCODE(DocId),
+ send_chunk(Resp, [Sep, <<"{\"id\": ">>, Id, <<", \"docs\": [">>]),
+ bulk_get_send_docs_json1(Resp, DocId, Results, Options),
+ send_chunk(Resp, <<"]}">>).
+
+bulk_get_send_docs_json1(Resp, DocId, {error, {Rev, Error, Reason}}, _) ->
+ send_chunk(Resp, [bulk_get_json_error(DocId, Rev, Error, Reason)]);
+bulk_get_send_docs_json1(_Resp, _DocId, {ok, []}, _) ->
+ ok;
+bulk_get_send_docs_json1(Resp, DocId, {ok, Docs}, Options) ->
+ lists:foldl(fun(Result, AccSeparator) ->
+ case Result of
+ {ok, Doc} ->
+ JsonDoc = couch_doc:to_json_obj(Doc, Options),
+ Json = ?JSON_ENCODE({[{ok, JsonDoc}]}),
+ send_chunk(Resp, [AccSeparator, Json]);
+ {{Error, Reason}, RevId} ->
+ RevStr = couch_doc:rev_to_str(RevId),
+ Json = bulk_get_json_error(DocId, RevStr, Error, Reason),
+ send_chunk(Resp, [AccSeparator, Json])
+ end,
+ <<",">>
+ end, <<"">>, Docs).
+
+bulk_get_json_error(DocId, Rev, Error, Reason) ->
+ ?JSON_ENCODE({[{error, {[{<<"id">>, DocId},
+ {<<"rev">>, Rev},
+ {<<"error">>, Error},
+ {<<"reason">>, Reason}]}}]}).
+
+
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/blob/933ba2e3/test/chttpd_db_bulk_get_test.erl
----------------------------------------------------------------------
diff --git a/test/chttpd_db_bulk_get_test.erl b/test/chttpd_db_bulk_get_test.erl
new file mode 100644
index 0000000..20d774d
--- /dev/null
+++ b/test/chttpd_db_bulk_get_test.erl
@@ -0,0 +1,334 @@
+%% 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.
+
+-module(chttpd_db_bulk_get_test).
+
+-include_lib("couch/include/couch_eunit.hrl").
+-include_lib("couch/include/couch_db.hrl").
+
+-define(TIMEOUT, 3000).
+
+
+setup() ->
+ mock(chttpd),
+ mock(couch_epi),
+ mock(couch_httpd),
+ mock(couch_stats),
+ mock(fabric),
+ mock(mochireq),
+ Pid = spawn_accumulator(),
+ Pid.
+
+
+teardown(Pid) ->
+ ok = stop_accumulator(Pid),
+ meck:unload(chttpd),
+ meck:unload(couch_epi),
+ meck:unload(couch_httpd),
+ meck:unload(couch_stats),
+ meck:unload(fabric),
+ meck:unload(mochireq).
+
+
+bulk_get_test_() ->
+ {
+ "/db/_bulk_get tests",
+ {
+ foreach, fun setup/0, fun teardown/1,
+ [
+ fun should_require_docs_field/1,
+ fun should_not_accept_specific_query_params/1,
+ fun should_return_empty_results_on_no_docs/1,
+ fun should_get_doc_with_all_revs/1,
+ fun should_validate_doc_with_bad_id/1,
+ fun should_validate_doc_with_bad_rev/1,
+ fun should_validate_missing_doc/1,
+ fun should_validate_bad_atts_since/1,
+ fun should_include_attachments_when_atts_since_specified/1
+ ]
+ }
+ }.
+
+
+should_require_docs_field(_) ->
+ Req = fake_request({[{}]}),
+ ?_assertThrow({bad_request, _}, chttpd_db:db_req(Req, nil)).
+
+
+should_not_accept_specific_query_params(_) ->
+ Req = fake_request({[{<<"docs">>, []}]}),
+ lists:map(fun (Param) ->
+ {Param, ?_assertThrow({bad_request, _},
+ begin
+ ok = meck:expect(chttpd, qs,
+ fun(_) -> [{Param, ""}] end),
+ chttpd_db:db_req(Req, nil)
+ end)}
+ end, ["rev", "open_revs", "atts_since", "w", "new_edits"]).
+
+
+should_return_empty_results_on_no_docs(Pid) ->
+ Req = fake_request({[{<<"docs">>, []}]}),
+ chttpd_db:db_req(Req, nil),
+ Results = get_results_from_response(Pid),
+ ?_assertEqual([], Results).
+
+
+should_get_doc_with_all_revs(Pid) ->
+ DocId = <<"docudoc">>,
+ Req = fake_request(DocId),
+
+ RevA = {[{<<"_id">>, DocId}, {<<"_rev">>, <<"1-ABC">>}]},
+ RevB = {[{<<"_id">>, DocId}, {<<"_rev">>, <<"1-CDE">>}]},
+ DocRevA = #doc{id = DocId, body = {[{<<"_rev">>, <<"1-ABC">>}]}},
+ DocRevB = #doc{id = DocId, body = {[{<<"_rev">>, <<"1-CDE">>}]}},
+
+ mock_open_revs(all, {ok, [{ok, DocRevA}, {ok, DocRevB}]}),
+ chttpd_db:db_req(Req, nil),
+
+ [{Result}] = get_results_from_response(Pid),
+ ?assertEqual(DocId, couch_util:get_value(<<"id">>, Result)),
+
+ Docs = couch_util:get_value(<<"docs">>, Result),
+ ?assertEqual(2, length(Docs)),
+
+ [{DocA0}, {DocB0}] = Docs,
+
+ DocA = couch_util:get_value(<<"ok">>, DocA0),
+ DocB = couch_util:get_value(<<"ok">>, DocB0),
+
+ ?_assertEqual([RevA, RevB], [DocA, DocB]).
+
+
+should_validate_doc_with_bad_id(Pid) ->
+ DocId = <<"_docudoc">>,
+
+ Req = fake_request(DocId),
+ chttpd_db:db_req(Req, nil),
+
+ [{Result}] = get_results_from_response(Pid),
+ ?assertEqual(DocId, couch_util:get_value(<<"id">>, Result)),
+
+ Docs = couch_util:get_value(<<"docs">>, Result),
+ ?assertEqual(1, length(Docs)),
+ [{DocResult}] = Docs,
+
+ Doc = couch_util:get_value(<<"error">>, DocResult),
+
+ ?_assertMatch({[{<<"id">>, DocId},
+ {<<"rev">>, null},
+ {<<"error">>, <<"bad_request">>},
+ {<<"reason">>, _}]},
+ Doc).
+
+
+should_validate_doc_with_bad_rev(Pid) ->
+ DocId = <<"docudoc">>,
+ Rev = <<"revorev">>,
+
+ Req = fake_request(DocId, Rev),
+ chttpd_db:db_req(Req, nil),
+
+ [{Result}] = get_results_from_response(Pid),
+ ?assertEqual(DocId, couch_util:get_value(<<"id">>, Result)),
+
+ Docs = couch_util:get_value(<<"docs">>, Result),
+ ?assertEqual(1, length(Docs)),
+ [{DocResult}] = Docs,
+
+ Doc = couch_util:get_value(<<"error">>, DocResult),
+
+ ?_assertMatch({[{<<"id">>, DocId},
+ {<<"rev">>, Rev},
+ {<<"error">>, <<"bad_request">>},
+ {<<"reason">>, _}]},
+ Doc).
+
+
+should_validate_missing_doc(Pid) ->
+ DocId = <<"docudoc">>,
+ Rev = <<"1-revorev">>,
+
+ Req = fake_request(DocId, Rev),
+ mock_open_revs([{1,<<"revorev">>}], {ok, []}),
+ chttpd_db:db_req(Req, nil),
+
+ [{Result}] = get_results_from_response(Pid),
+ ?assertEqual(DocId, couch_util:get_value(<<"id">>, Result)),
+
+ Docs = couch_util:get_value(<<"docs">>, Result),
+ ?assertEqual(1, length(Docs)),
+ [{DocResult}] = Docs,
+
+ Doc = couch_util:get_value(<<"error">>, DocResult),
+
+ ?_assertMatch({[{<<"id">>, DocId},
+ {<<"rev">>, Rev},
+ {<<"error">>, <<"not_found">>},
+ {<<"reason">>, _}]},
+ Doc).
+
+
+should_validate_bad_atts_since(Pid) ->
+ DocId = <<"docudoc">>,
+ Rev = <<"1-revorev">>,
+
+ Req = fake_request(DocId, Rev, <<"badattsince">>),
+ mock_open_revs([{1,<<"revorev">>}], {ok, []}),
+ chttpd_db:db_req(Req, nil),
+
+ [{Result}] = get_results_from_response(Pid),
+ ?assertEqual(DocId, couch_util:get_value(<<"id">>, Result)),
+
+ Docs = couch_util:get_value(<<"docs">>, Result),
+ ?assertEqual(1, length(Docs)),
+ [{DocResult}] = Docs,
+
+ Doc = couch_util:get_value(<<"error">>, DocResult),
+
+ ?_assertMatch({[{<<"id">>, DocId},
+ {<<"rev">>, <<"badattsince">>},
+ {<<"error">>, <<"bad_request">>},
+ {<<"reason">>, _}]},
+ Doc).
+
+
+should_include_attachments_when_atts_since_specified(_) ->
+ DocId = <<"docudoc">>,
+ Rev = <<"1-revorev">>,
+
+ Req = fake_request(DocId, Rev, [<<"1-abc">>]),
+ mock_open_revs([{1,<<"revorev">>}], {ok, []}),
+ chttpd_db:db_req(Req, nil),
+
+ ?_assert(meck:called(fabric, open_revs,
+ [nil, DocId, [{1, <<"revorev">>}],
+ [{atts_since, [{1, <<"abc">>}]}, attachments]])).
+
+%% helpers
+
+fake_request(Payload) when is_tuple(Payload) ->
+ #httpd{method='POST', path_parts=[<<"db">>, <<"_bulk_get">>],
+ mochi_req=mochireq, req_body=Payload};
+fake_request(DocId) when is_binary(DocId) ->
+ fake_request({[{<<"docs">>, [{[{<<"id">>, DocId}]}]}]}).
+
+fake_request(DocId, Rev) ->
+ fake_request({[{<<"docs">>, [{[{<<"id">>, DocId}, {<<"rev">>, Rev}]}]}]}).
+
+fake_request(DocId, Rev, AttsSince) ->
+ fake_request({[{<<"docs">>, [{[{<<"id">>, DocId},
+ {<<"rev">>, Rev},
+ {<<"atts_since">>, AttsSince}]}]}]}).
+
+
+mock_open_revs(RevsReq0, RevsResp) ->
+ ok = meck:expect(fabric, open_revs,
+ fun(_, _, RevsReq1, _) ->
+ ?assertEqual(RevsReq0, RevsReq1),
+ RevsResp
+ end).
+
+
+mock(mochireq) ->
+ ok = meck:new(mochireq, [non_strict]),
+ ok = meck:expect(mochireq, parse_qs, fun() -> [] end),
+ ok = meck:expect(mochireq, accepts_content_type, fun(_) -> false end),
+ ok;
+mock(couch_httpd) ->
+ ok = meck:new(couch_httpd, [passthrough]),
+ ok = meck:expect(couch_httpd, validate_ctype, fun(_, _) -> ok end),
+ ok;
+mock(chttpd) ->
+ ok = meck:new(chttpd, [passthrough]),
+ ok = meck:expect(chttpd, start_json_response, fun(_, _) -> {ok, nil} end),
+ ok = meck:expect(chttpd, end_json_response, fun(_) -> ok end),
+ ok = meck:expect(chttpd, send_chunk, fun send_chunk/2),
+ ok = meck:expect(chttpd, json_body_obj, fun (#httpd{req_body=Body}) -> Body end),
+ ok;
+mock(couch_epi) ->
+ ok = meck:new(couch_epi, [passthrough]),
+ ok = meck:expect(couch_epi, any, fun(_, _, _, _, _) -> false end),
+ ok;
+mock(couch_stats) ->
+ ok = meck:new(couch_stats, [passthrough]),
+ ok = meck:expect(couch_stats, increment_counter, fun(_) -> ok end),
+ ok = meck:expect(couch_stats, increment_counter, fun(_, _) -> ok end),
+ ok = meck:expect(couch_stats, decrement_counter, fun(_) -> ok end),
+ ok = meck:expect(couch_stats, decrement_counter, fun(_, _) -> ok end),
+ ok = meck:expect(couch_stats, update_histogram, fun(_, _) -> ok end),
+ ok = meck:expect(couch_stats, update_gauge, fun(_, _) -> ok end),
+ ok;
+mock(fabric) ->
+ ok = meck:new(fabric, [passthrough]),
+ ok.
+
+
+spawn_accumulator() ->
+ Parent = self(),
+ Pid = spawn(fun() -> accumulator_loop(Parent, []) end),
+ erlang:put(chunks_gather, Pid),
+ Pid.
+
+accumulator_loop(Parent, Acc) ->
+ receive
+ {stop, Ref} ->
+ Parent ! {ok, Ref};
+ {get, Ref} ->
+ Parent ! {ok, Ref, Acc},
+ accumulator_loop(Parent, Acc);
+ {put, Ref, Chunk} ->
+ Parent ! {ok, Ref},
+ accumulator_loop(Parent, [Chunk|Acc])
+ end.
+
+stop_accumulator(Pid) ->
+ Ref = make_ref(),
+ Pid ! {stop, Ref},
+ receive
+ {ok, Ref} ->
+ ok
+ after ?TIMEOUT ->
+ throw({timeout, <<"process stop timeout">>})
+ end.
+
+
+send_chunk(_, []) ->
+ {ok, nil};
+send_chunk(_Req, [H|T]=Chunk) when is_list(Chunk) ->
+ send_chunk(_Req, H),
+ send_chunk(_Req, T);
+send_chunk(_, Chunk) ->
+ Worker = erlang:get(chunks_gather),
+ Ref = make_ref(),
+ Worker ! {put, Ref, Chunk},
+ receive
+ {ok, Ref} -> {ok, nil}
+ after ?TIMEOUT ->
+ throw({timeout, <<"send chunk timeout">>})
+ end.
+
+
+get_response(Pid) ->
+ Ref = make_ref(),
+ Pid ! {get, Ref},
+ receive
+ {ok, Ref, Acc} ->
+ ?JSON_DECODE(iolist_to_binary(lists:reverse(Acc)))
+ after ?TIMEOUT ->
+ throw({timeout, <<"get response timeout">>})
+ end.
+
+
+get_results_from_response(Pid) ->
+ {Resp} = get_response(Pid),
+ couch_util:get_value(<<"results">>, Resp).