From 46bf4b727f0fae37b017f194983122c50d3f34e5 Mon Sep 17 00:00:00 2001 From: John Christopher Anderson Date: Mon, 20 Jul 2009 04:11:36 +0000 Subject: Initial checkin of _changes filters. The prime weak-spot for this approach is that it maintains an OS-process per connected filtered _changes consumer. I'm pretty sure we'll be able to work around this without changing the API, but it'll involve a lot of OS-process bookkeeping. Those enhancements should generally improve show & list performance as well. Punting on them for now, first wanted to get _changes filters implemented so people could give feedback. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@795687 13f79535-47bb-0310-9956-ffa450edef68 --- src/couchdb/couch_httpd_db.erl | 104 +++++++++++++++++++++++++++-------------- 1 file changed, 69 insertions(+), 35 deletions(-) (limited to 'src/couchdb/couch_httpd_db.erl') 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 -- cgit v1.2.3