summaryrefslogtreecommitdiff
path: root/src/couchdb/couch_httpd_db.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/couchdb/couch_httpd_db.erl')
-rw-r--r--src/couchdb/couch_httpd_db.erl105
1 files changed, 86 insertions, 19 deletions
diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl
index 4295ed77..b129d37e 100644
--- a/src/couchdb/couch_httpd_db.erl
+++ b/src/couchdb/couch_httpd_db.erl
@@ -14,7 +14,7 @@
-include("couch_db.hrl").
-export([handle_request/1, handle_compact_req/2, handle_design_req/2,
- db_req/2, couch_doc_open/4]).
+ db_req/2, couch_doc_open/4,handle_changes_req/2]).
-import(couch_httpd,
[send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,
@@ -42,6 +42,72 @@ handle_request(#httpd{path_parts=[DbName|RestParts],method=Method,
do_db_req(Req, Handler)
end.
+handle_changes_req(#httpd{method='GET',path_parts=[DbName|_]}=Req, Db) ->
+ StartSeq = list_to_integer(couch_httpd:qs_value(Req, "since", "0")),
+
+ {ok, Resp} = start_json_response(Req, 200),
+ send_chunk(Resp, "{\"results\":[\n"),
+ case couch_httpd:qs_value(Req, "continuous", "false") of
+ "true" ->
+ Self = self(),
+ Notify = couch_db_update_notifier:start_link(
+ fun({_, DbName0}) when DbName0 == DbName ->
+ Self ! db_updated;
+ (_) ->
+ ok
+ end),
+ try
+ keep_sending_changes(Req, Resp, Db, StartSeq, <<"">>)
+ after
+ catch couch_db_update_notifier:stop(Notify),
+ wait_db_updated(0) % clean out any remaining update messages
+ end;
+ "false" ->
+ {ok, {LastSeq, _Prepend}} =
+ send_changes(Req, Resp, Db, StartSeq, <<"">>),
+ send_chunk(Resp, io_lib:format("\n],\n\"last_seq\":~w}\n", [LastSeq])),
+ send_chunk(Resp, "")
+ end;
+
+handle_changes_req(#httpd{path_parts=[_,<<"_changes">>]}=Req, _Db) ->
+ send_method_not_allowed(Req, "GET,HEAD").
+
+% waits for a db_updated msg, if there are multiple msgs, collects them.
+wait_db_updated(Timeout) ->
+ receive db_updated ->
+ wait_db_updated(0)
+ after Timeout -> ok
+ end.
+
+keep_sending_changes(#httpd{user_ctx=UserCtx,path_parts=[DbName|_]}=Req, Resp, Db, StartSeq, Prepend) ->
+ {ok, {EndSeq, Prepend2}} = send_changes(Req, Resp, Db, StartSeq, Prepend),
+ couch_db:close(Db),
+ wait_db_updated(infinity),
+ {ok, Db2} = couch_db:open(DbName, [{user_ctx, UserCtx}]),
+ keep_sending_changes(Req, Resp, Db2, EndSeq, Prepend2).
+
+send_changes(Req, Resp, Db, StartSeq, Prepend0) ->
+ Style = list_to_existing_atom(
+ couch_httpd:qs_value(Req, "style", "main_only")),
+ couch_db:changes_since(Db, Style, StartSeq,
+ fun([#doc_info{id=Id, high_seq=Seq}|_]=DocInfos, {_, Prepend}) ->
+ FilterFun =
+ fun(#doc_info{revs=[#rev_info{rev=Rev}|_]}) ->
+ {[{rev, couch_doc:rev_to_str(Rev)}]}
+ end,
+ Results0 = [FilterFun(DocInfo) || DocInfo <- DocInfos],
+ Results = [Result || Result <- Results0, Result /= null],
+ case Results of
+ [] ->
+ {ok, {Seq, Prepend}};
+ _ ->
+ send_chunk(Resp,
+ [Prepend, ?JSON_ENCODE({[{seq,Seq}, {id, Id},
+ {changes,Results}]})]),
+ {ok, {Seq, <<",\n">>}}
+ end
+ end, {StartSeq, Prepend0}).
+
handle_compact_req(#httpd{method='POST',path_parts=[DbName,_,Id|_]}=Req, _Db) ->
ok = couch_view_compactor:start_compact(DbName, Id),
send_json(Req, 202, {[{ok, true}]});
@@ -89,7 +155,7 @@ do_db_req(#httpd{user_ctx=UserCtx,path_parts=[DbName|_]}=Req, Fun) ->
try
Fun(Req, Db)
after
- couch_db:close(Db)
+ catch couch_db:close(Db)
end;
Error ->
throw(Error)
@@ -258,7 +324,7 @@ db_req(#httpd{method='POST',path_parts=[_,<<"_all_docs">>]}=Req, Db) ->
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,
@@ -285,28 +351,29 @@ db_req(#httpd{method='GET',path_parts=[_,<<"_all_docs_by_seq">>]}=Req, Db) ->
fun(DocInfo, Offset, Acc) ->
#doc_info{
id=Id,
- rev=Rev,
- update_seq=UpdateSeq,
- deleted=Deleted,
- conflict_revs=ConflictRevs,
- deleted_conflict_revs=DelConflictRevs
+ 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">>, couch_doc:rev_to_strs(ConflictRevs)}]
+ [] -> [];
+ _ -> [{<<"conflicts">>, ConflictRevs}]
end ++
case DelConflictRevs of
- [] -> [];
- _ -> [{<<"deleted_conflicts">>, couch_doc:rev_to_strs(DelConflictRevs)}]
+ [] -> [];
+ _ -> [{<<"deleted_conflicts">>, DelConflictRevs}]
end ++
case Deleted of
- true -> [{<<"deleted">>, true}];
- false -> []
+ true -> [{<<"deleted">>, true}];
+ false -> []
end
},
- FoldlFun({{UpdateSeq, Id}, Json}, Offset, Acc)
+ FoldlFun({{Seq, Id}, Json}, Offset, Acc)
end, {Limit, SkipCount, undefined, []}),
couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult})
end);
@@ -412,9 +479,9 @@ all_docs_view(Req, Db, Keys) ->
}),
AdapterFun = fun(#full_doc_info{id=Id}=FullDocInfo, Offset, Acc) ->
case couch_doc:to_doc_info(FullDocInfo) of
- #doc_info{deleted=false, rev=Rev} ->
+ #doc_info{revs=[#rev_info{deleted=false, rev=Rev}|_]} ->
FoldlFun({{Id, Id}, {[{rev, couch_doc:rev_to_str(Rev)}]}}, Offset, Acc);
- #doc_info{deleted=true} ->
+ #doc_info{revs=[#rev_info{deleted=true}|_]} ->
{ok, Acc}
end
end,
@@ -436,9 +503,9 @@ all_docs_view(Req, Db, Keys) ->
fun(Key, {ok, FoldAcc}) ->
DocInfo = (catch couch_db:get_doc_info(Db, Key)),
Doc = case DocInfo of
- {ok, #doc_info{id=Id, rev=Rev, deleted=false}} = DocInfo ->
+ {ok, #doc_info{id=Id, revs=[#rev_info{deleted=false, rev=Rev}|_]}} ->
{{Id, Id}, {[{rev, couch_doc:rev_to_str(Rev)}]}};
- {ok, #doc_info{id=Id, rev=Rev, deleted=true}} = DocInfo ->
+ {ok, #doc_info{id=Id, revs=[#rev_info{deleted=true, rev=Rev}|_]}} ->
{{Id, Id}, {[{rev, couch_doc:rev_to_str(Rev)}, {deleted, true}]}};
not_found ->
{{Key, error}, not_found};