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.erl104
1 files changed, 69 insertions, 35 deletions
diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl
index 0ec1cbda..9d7dda6b 100644
--- a/src/couchdb/couch_httpd_db.erl
+++ b/src/couchdb/couch_httpd_db.erl
@@ -66,33 +66,38 @@ get_changes_timeout(Req, Resp) ->
handle_changes_req(#httpd{method='GET',path_parts=[DbName|_]}=Req, Db) ->
StartSeq = list_to_integer(couch_httpd:qs_value(Req, "since", "0")),
+ Filter = couch_httpd:qs_value(Req, "filter", nil),
+ {ok, FilterFun, EndFilterFun} = make_filter_funs(Req, Db, Filter),
+ try
+ {ok, Resp} = start_json_response(Req, 200),
+ send_chunk(Resp, "{\"results\":[\n"),
+ case couch_httpd:qs_value(Req, "continuous", "false") of
+ "true" ->
+ Self = self(),
+ {ok, Notify} = couch_db_update_notifier:start_link(
+ fun({_, DbName0}) when DbName0 == DbName ->
+ Self ! db_updated;
+ (_) ->
+ ok
+ end),
+ {Timeout, TimeoutFun} = get_changes_timeout(Req, Resp),
+ couch_stats_collector:track_process_count(Self,
+ {httpd, clients_requesting_changes}),
+ try
+ keep_sending_changes(Req, Resp, Db, StartSeq, <<"">>, Timeout, TimeoutFun, FilterFun)
+ after
+ couch_db_update_notifier:stop(Notify),
+ get_rest_db_updated() % clean out any remaining update messages
+ end;
- {ok, Resp} = start_json_response(Req, 200),
- send_chunk(Resp, "{\"results\":[\n"),
- case couch_httpd:qs_value(Req, "continuous", "false") of
- "true" ->
- Self = self(),
- {ok, Notify} = couch_db_update_notifier:start_link(
- fun({_, DbName0}) when DbName0 == DbName ->
- Self ! db_updated;
- (_) ->
- ok
- end),
- {Timeout, TimeoutFun} = get_changes_timeout(Req, Resp),
- couch_stats_collector:track_process_count(Self,
- {httpd, clients_requesting_changes}),
- try
- keep_sending_changes(Req, Resp, Db, StartSeq, <<"">>, Timeout, TimeoutFun)
- after
- couch_db_update_notifier:stop(Notify),
- get_rest_db_updated() % 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, "")
+ "false" ->
+ {ok, {LastSeq, _Prepend}} =
+ send_changes(Req, Resp, Db, StartSeq, <<"">>, FilterFun),
+ send_chunk(Resp, io_lib:format("\n],\n\"last_seq\":~w}\n", [LastSeq])),
+ send_chunk(Resp, "")
+ end
+ after
+ EndFilterFun()
end;
handle_changes_req(#httpd{path_parts=[_,<<"_changes">>]}=Req, _Db) ->
@@ -113,27 +118,23 @@ get_rest_db_updated() ->
after 0 -> updated
end.
-keep_sending_changes(#httpd{user_ctx=UserCtx,path_parts=[DbName|_]}=Req, Resp, Db, StartSeq, Prepend, Timeout, TimeoutFun) ->
- {ok, {EndSeq, Prepend2}} = send_changes(Req, Resp, Db, StartSeq, Prepend),
+keep_sending_changes(#httpd{user_ctx=UserCtx,path_parts=[DbName|_]}=Req, Resp, Db, StartSeq, Prepend, Timeout, TimeoutFun, FilterFun) ->
+ {ok, {EndSeq, Prepend2}} = send_changes(Req, Resp, Db, StartSeq, Prepend, FilterFun),
couch_db:close(Db),
case wait_db_updated(Timeout, TimeoutFun) of
updated ->
{ok, Db2} = couch_db:open(DbName, [{user_ctx, UserCtx}]),
- keep_sending_changes(Req, Resp, Db2, EndSeq, Prepend2, Timeout, TimeoutFun);
+ keep_sending_changes(Req, Resp, Db2, EndSeq, Prepend2, Timeout, TimeoutFun, FilterFun);
stop ->
send_chunk(Resp, io_lib:format("\n],\n\"last_seq\":~w}\n", [EndSeq])),
send_chunk(Resp, "")
end.
-send_changes(Req, Resp, Db, StartSeq, Prepend0) ->
+send_changes(Req, Resp, Db, StartSeq, Prepend0, FilterFun) ->
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
@@ -147,6 +148,39 @@ send_changes(Req, Resp, Db, StartSeq, Prepend0) ->
end
end, {StartSeq, Prepend0}).
+make_filter_funs(_Req, _Db, nil) ->
+ {ok, fun(#doc_info{revs=[#rev_info{rev=Rev}|_]}) ->
+ {[{rev, couch_doc:rev_to_str(Rev)}]}
+ end,
+ fun() -> ok end};
+make_filter_funs(Req, Db, Filter) ->
+ case [list_to_binary(couch_httpd:unquote(Part))
+ || Part <- string:tokens(Filter, "/")] of
+ [DName, FName] ->
+ DesignId = <<"_design/", DName/binary>>,
+ #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, nil, []),
+ Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),
+ FilterSrc = couch_util:get_nested_json_value({Props}, [<<"filters">>, FName]),
+ {ok, Pid} = couch_query_servers:start_filter(Lang, FilterSrc),
+ FilterFun = fun(DInfo = #doc_info{revs=[#rev_info{rev=Rev}|_]}) ->
+ {ok, Doc} = couch_db:open_doc(Db, DInfo),
+ {ok, Pass} = couch_query_servers:filter_doc(Pid, Doc, Req, Db),
+ case Pass of
+ true ->
+ {[{rev, couch_doc:rev_to_str(Rev)}]};
+ false ->
+ null
+ end
+ end,
+ EndFilterFun = fun() ->
+ couch_query_servers:end_filter(Pid)
+ end,
+ {ok, FilterFun, EndFilterFun};
+ _Else ->
+ throw({bad_request,
+ "filter parameter must be of the form `designname/filtername`"})
+ end.
+
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}]});
@@ -747,7 +781,7 @@ couch_doc_from_req(Req, DocId, Json) ->
% Useful for debugging
% couch_doc_open(Db, DocId) ->
-% couch_doc_open(Db, DocId, [], []).
+% couch_doc_open(Db, DocId, nil, []).
couch_doc_open(Db, DocId, Rev, Options) ->
case Rev of