couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jch...@apache.org
Subject svn commit: r893249 [2/2] - in /couchdb/trunk: etc/couchdb/ share/server/ share/www/script/test/ src/couchdb/ test/view_server/
Date Tue, 22 Dec 2009 18:03:46 GMT
Modified: couchdb/trunk/src/couchdb/couch_httpd_view.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_view.erl?rev=893249&r1=893248&r2=893249&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd_view.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd_view.erl Tue Dec 22 18:03:44 2009
@@ -13,21 +13,21 @@
 -module(couch_httpd_view).
 -include("couch_db.hrl").
 
--export([handle_view_req/2,handle_temp_view_req/2,handle_db_view_req/2]).
+-export([handle_view_req/3,handle_temp_view_req/2]).
 
 -export([get_stale_type/1, get_reduce_type/1, parse_view_params/3]).
 -export([make_view_fold_fun/6, finish_view_fold/4, view_row_obj/3]).
 -export([view_group_etag/2, view_group_etag/3, make_reduce_fold_funs/5]).
 -export([design_doc_view/5, parse_bool_param/1, doc_member/2]).
--export([make_key_options/1]).
+-export([make_key_options/1, load_view/4]).
 
 -import(couch_httpd,
     [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,send_chunk/2,
     start_json_response/2, start_json_response/3, end_json_response/1,
     send_chunked_error/2]).
 
-design_doc_view(Req, Db, Id, ViewName, Keys) ->
-    DesignId = <<"_design/", Id/binary>>,
+design_doc_view(Req, Db, DName, ViewName, Keys) ->
+    DesignId = <<"_design/", DName/binary>>,
     Stale = get_stale_type(Req),
     Reduce = get_reduce_type(Req),
     Result = case couch_view:get_map_view(Db, DesignId, ViewName, Stale) of
@@ -54,11 +54,11 @@
     Result.
 
 handle_view_req(#httpd{method='GET',
-        path_parts=[_Db, _Design, DName, _View, ViewName]}=Req, Db) ->
+        path_parts=[_, _, DName, _, ViewName]}=Req, Db, _DDoc) ->
     design_doc_view(Req, Db, DName, ViewName, nil);
 
 handle_view_req(#httpd{method='POST',
-        path_parts=[_Db, _Design, DName, _View, ViewName]}=Req, Db) ->
+        path_parts=[_, _, DName, _, ViewName]}=Req, Db, _DDoc) ->
     {Fields} = couch_httpd:json_body_obj(Req),
     case proplists:get_value(<<"keys">>, Fields, nil) of
     nil ->
@@ -71,50 +71,7 @@
         throw({bad_request, "`keys` member must be a array."})
     end;
 
-handle_view_req(Req, _Db) ->
-    send_method_not_allowed(Req, "GET,POST,HEAD").
-
-handle_db_view_req(#httpd{method='GET',
-        path_parts=[_Db, _View, DName, ViewName]}=Req, Db) ->
-    QueryArgs = couch_httpd_view:parse_view_params(Req, nil, nil),
-    #view_query_args{
-        list = ListName
-    } = QueryArgs,
-    ?LOG_DEBUG("ici ~p", [ListName]),
-    case ListName of
-        nil -> couch_httpd_view:design_doc_view(Req, Db, DName, ViewName, nil);
-        _ ->
-            couch_httpd_show:handle_view_list(Req, DName, ListName, DName, ViewName, Db, nil)
-    end;
-
-handle_db_view_req(#httpd{method='POST',
-        path_parts=[_Db, _View, DName, ViewName]}=Req, Db) ->
-    QueryArgs = couch_httpd_view:parse_view_params(Req, nil, nil),
-    #view_query_args{
-        list = ListName
-    } = QueryArgs,
-    case ListName of
-    nil ->
-        {Fields} = couch_httpd:json_body_obj(Req),
-        case proplists:get_value(<<"keys">>, Fields, nil) of
-        nil ->
-            Fmt = "POST to view ~p/~p in database ~p with no keys member.",
-            ?LOG_DEBUG(Fmt, [DName, ViewName, Db]),
-            couch_httpd_view:design_doc_view(Req, Db, DName, ViewName, nil);
-        Keys when is_list(Keys) ->
-            couch_httpd_view:design_doc_view(Req, Db, DName, ViewName, Keys);
-        _ ->
-            throw({bad_request, "`keys` member must be a array."})
-        end;
-    _ ->
-        ReqBody = couch_httpd:body(Req),
-        {Props2} = ?JSON_DECODE(ReqBody),
-        Keys = proplists:get_value(<<"keys">>, Props2, nil),
-        couch_httpd_show:handle_view_list(Req#httpd{req_body=ReqBody},
-            DName, ListName, DName, ViewName, Db, Keys)
-    end;
-
-handle_db_view_req(Req, _Db) ->
+handle_view_req(Req, _Db, _DDoc) ->
     send_method_not_allowed(Req, "GET,POST,HEAD").
 
 handle_temp_view_req(#httpd{method='POST'}=Req, Db) ->
@@ -236,6 +193,35 @@
 get_reduce_type(Req) ->
     list_to_atom(couch_httpd:qs_value(Req, "reduce", "true")).
 
+load_view(Req, Db, {ViewDesignId, ViewName}, Keys) ->
+    Stale = couch_httpd_view:get_stale_type(Req),
+    Reduce = couch_httpd_view:get_reduce_type(Req),
+    case couch_view:get_map_view(Db, ViewDesignId, ViewName, Stale) of
+    {ok, View, Group} ->
+        QueryArgs = couch_httpd_view:parse_view_params(Req, Keys, map),
+        {map, View, Group, QueryArgs};
+    {not_found, _Reason} ->
+        case couch_view:get_reduce_view(Db, ViewDesignId, ViewName, Stale) of
+        {ok, ReduceView, Group} ->
+            case Reduce of
+            false ->
+                QueryArgs = couch_httpd_view:parse_view_params(Req, Keys, map_red),
+                MapView = couch_view:extract_map_view(ReduceView),
+                {map, MapView, Group, QueryArgs};
+            _ ->
+                QueryArgs = couch_httpd_view:parse_view_params(Req, Keys, reduce),                
+                {reduce, ReduceView, Group, QueryArgs}
+            end;
+        {not_found, Reason} ->
+            throw({not_found, Reason})
+        end
+    end.
+
+% query_parse_error could be removed
+% we wouldn't need to pass the view type, it'd just parse params.
+% I'm not sure what to do about the error handling, but
+% it might simplify things to have a parse_view_params function
+% that doesn't throw().
 parse_view_params(Req, Keys, ViewType) ->
     QueryList = couch_httpd:qs(Req),
     QueryParams =
@@ -258,6 +244,7 @@
         {reduce, _, false} ->
             QueryArgs;
         {reduce, _, _} ->
+            % we can simplify code if we just drop this error message.
             Msg = <<"Multi-key fetchs for reduce "
                     "view must include `group=true`">>,
             throw({query_parse_error, Msg});

Modified: couchdb/trunk/src/couchdb/couch_native_process.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_native_process.erl?rev=893249&r1=893248&r2=893249&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_native_process.erl (original)
+++ couchdb/trunk/src/couchdb/couch_native_process.erl Tue Dec 22 18:03:44 2009
@@ -38,63 +38,102 @@
 % extensions will evolve which offer useful layers on top of this view server
 % to help simplify your view code.
 -module(couch_native_process).
+-behaviour(gen_server).
 
--export([start_link/0]).
--export([set_timeout/2, prompt/2, stop/1]).
+-export([start_link/0,init/1,terminate/2,handle_call/3,handle_cast/2]).
+-export([set_timeout/2, prompt/2]).
 
 -define(STATE, native_proc_state).
--record(evstate, {funs=[], query_config=[], list_pid=nil, timeout=5000}).
+-record(evstate, {ddocs, funs=[], query_config=[], list_pid=nil, timeout=5000}).
 
 -include("couch_db.hrl").
 
 start_link() ->
-    {ok, self()}.
+    gen_server:start_link(?MODULE, [], []).
 
-stop(_Pid) ->
-    ok.
-
-set_timeout(_Pid, TimeOut) ->
-    NewState = case get(?STATE) of
-    undefined ->
-        #evstate{timeout=TimeOut};
-    State ->
-        State#evstate{timeout=TimeOut}
-    end,
-    put(?STATE, NewState),
-    ok.
-
-prompt(Pid, Data) when is_pid(Pid), is_list(Data) ->
-    case get(?STATE) of
-    undefined ->
-        State = #evstate{},
-        put(?STATE, State);
-    State ->
-        State
-    end,
-    case is_pid(State#evstate.list_pid) of
-        true ->
-            case hd(Data) of
-                <<"list_row">> -> ok;
-                <<"list_end">> -> ok;
-                _ -> throw({error, query_server_error})
-            end;
-        _ ->
-            ok % Not listing
-    end,
-    {NewState, Resp} = run(State, to_binary(Data)),
-    put(?STATE, NewState),
+% this is a bit messy, see also couch_query_servers handle_info
+% stop(_Pid) ->
+%     ok.
+
+set_timeout(Pid, TimeOut) ->
+    gen_server:call(Pid, {set_timeout, TimeOut}).
+
+prompt(Pid, Data) when is_list(Data) ->
+    gen_server:call(Pid, {prompt, Data}).
+
+% gen_server callbacks
+init([]) ->
+    {ok, #evstate{ddocs=dict:new()}}.
+
+handle_call({set_timeout, TimeOut}, _From, State) ->
+    {reply, ok, State#evstate{timeout=TimeOut}};
+
+handle_call({prompt, Data}, _From, State) ->
+    ?LOG_DEBUG("Prompt native qs: ~s",[?JSON_ENCODE(Data)]),
+    {NewState, Resp} = try run(State, to_binary(Data)) of
+        {S, R} -> {S, R}
+        catch
+            throw:{error, Why} ->
+                {State, [<<"error">>, Why, Why]}
+        end,
+    
     case Resp of
         {error, Reason} ->
             Msg = io_lib:format("couch native server error: ~p", [Reason]),
-            {[{<<"error">>, list_to_binary(Msg)}]};
-        _ ->
-            Resp
+            {reply, [<<"error">>, <<"native_query_server">>, list_to_binary(Msg)], NewState};
+        [<<"error">> | Rest] ->
+            Msg = io_lib:format("couch native server error: ~p", [Rest]),
+            {reply, [<<"error">> | Rest], NewState};
+        [<<"fatal">> | Rest] ->
+            Msg = io_lib:format("couch native server error: ~p", [Rest]),
+            {stop, fatal, [<<"error">> | Rest], NewState};
+        Resp ->
+            {reply, Resp, NewState}
     end.
 
-run(_, [<<"reset">>]) ->
-    {#evstate{}, true};
-run(_, [<<"reset">>, QueryConfig]) ->
-    {#evstate{query_config=QueryConfig}, true};
+handle_cast(_Msg, State) -> {noreply, State}.
+handle_info(_Msg, State) -> {noreply, State}.
+terminate(_Reason, _State) -> ok.
+code_change(_OldVersion, State, _Extra) -> {ok, State}.
+
+run(#evstate{list_pid=Pid}=State, [<<"list_row">>, Row]) when is_pid(Pid) ->
+    Pid ! {self(), list_row, Row},
+    receive
+        {Pid, chunks, Data} ->
+            {State, [<<"chunks">>, Data]};
+        {Pid, list_end, Data} ->
+            receive
+                {'EXIT', Pid, normal} -> ok
+            after State#evstate.timeout ->
+                throw({timeout, list_cleanup})
+            end,
+            process_flag(trap_exit, erlang:get(do_trap)),
+            {State#evstate{list_pid=nil}, [<<"end">>, Data]}
+    after State#evstate.timeout ->
+        throw({timeout, list_row})
+    end;
+run(#evstate{list_pid=Pid}=State, [<<"list_end">>]) when is_pid(Pid) ->
+    Pid ! {self(), list_end},
+    Resp =
+    receive
+        {Pid, list_end, Data} ->
+            receive
+                {'EXIT', Pid, normal} -> ok
+            after State#evstate.timeout ->
+                throw({timeout, list_cleanup})
+            end,
+            [<<"end">>, Data]
+    after State#evstate.timeout ->
+        throw({timeout, list_end})
+    end,
+    process_flag(trap_exit, erlang:get(do_trap)),
+    {State#evstate{list_pid=nil}, Resp};
+run(#evstate{list_pid=Pid}=State, _Command) when is_pid(Pid) ->
+    {State, [<<"error">>, list_error, list_error]};
+run(#evstate{ddocs=DDocs}, [<<"reset">>]) ->
+    {#evstate{ddocs=DDocs}, true};
+run(#evstate{ddocs=DDocs}, [<<"reset">>, QueryConfig]) ->
+    {#evstate{ddocs=DDocs, query_config=QueryConfig}, true};
 run(#evstate{funs=Funs}=State, [<<"add_fun">> , BinFunc]) ->
     FunInfo = makefun(State, BinFunc),
     {State#evstate{funs=Funs ++ [FunInfo]}, true};
@@ -115,41 +154,55 @@
     {State, catch reduce(State, Funs, Keys2, Vals2, false)};
 run(State, [<<"rereduce">>, Funs, Vals]) ->
     {State, catch reduce(State, Funs, null, Vals, true)};
-run(State, [<<"validate">>, BFun, NDoc, ODoc, Ctx]) ->
-    {_Sig, Fun} = makefun(State, BFun),
-    {State, catch Fun(NDoc, ODoc, Ctx)};
-run(State, [<<"filter">>, Docs, Req]) ->
-    {_Sig, Fun} = hd(State#evstate.funs),
+run(#evstate{ddocs=DDocs}=State, [<<"ddoc">>, <<"new">>, DDocId, DDoc]) ->
+    DDocs2 = store_ddoc(DDocs, DDocId, DDoc),
+    {State#evstate{ddocs=DDocs2}, true};
+run(#evstate{ddocs=DDocs}=State, [<<"ddoc">>, DDocId | Rest]) ->
+    DDoc = load_ddoc(DDocs, DDocId),
+    ddoc(State, DDoc, Rest);
+run(_, Unknown) ->
+    ?LOG_ERROR("Native Process: Unknown command: ~p~n", [Unknown]),
+    throw({error, unknown_command}).
+    
+ddoc(State, {DDoc}, [FunPath, Args]) ->
+    % load fun from the FunPath
+    BFun = lists:foldl(fun
+        (Key, {Props}) when is_list(Props) ->
+            proplists:get_value(Key, Props, nil);
+        (Key, Fun) when is_binary(Fun) ->
+            Fun;
+        (Key, nil) ->
+            throw({error, not_found});
+        (Key, Fun) -> 
+            throw({error, malformed_ddoc})
+        end, {DDoc}, FunPath),
+    ddoc(State, makefun(State, BFun, {DDoc}), FunPath, Args).
+
+ddoc(State, {_, Fun}, [<<"validate_doc_update">>], Args) ->
+    {State, (catch apply(Fun, Args))};
+ddoc(State, {_, Fun}, [<<"filters">>|_], [Docs, Req]) ->
     Resp = lists:map(fun(Doc) -> (catch Fun(Doc, Req)) =:= true end, Docs),
     {State, [true, Resp]};
-run(State, [<<"show">>, BFun, Doc, Req]) ->
-    {_Sig, Fun} = makefun(State, BFun),
-    Resp = case (catch Fun(Doc, Req)) of
+ddoc(State, {_, Fun}, [<<"shows">>|_], Args) ->
+    Resp = case (catch apply(Fun, Args)) of
         FunResp when is_list(FunResp) ->
             FunResp;
-        FunResp when tuple_size(FunResp) =:= 1 ->
-            [<<"resp">>, FunResp];
+        {FunResp} ->
+            [<<"resp">>, {FunResp}];
         FunResp ->
             FunResp
     end,
     {State, Resp};
-run(State, [<<"update">>, BFun, Doc, Req]) ->
-    {_Sig, Fun} = makefun(State, BFun),
-    Resp = case (catch Fun(Doc, Req)) of
+ddoc(State, {_, Fun}, [<<"updates">>|_], Args) ->
+    Resp = case (catch apply(Fun, Args)) of
         [JsonDoc, JsonResp]  ->
             [<<"up">>, JsonDoc, JsonResp]
     end,
     {State, Resp};
-run(State, [<<"list">>, Head, Req]) ->
-    {Sig, Fun} = hd(State#evstate.funs),
-    % This is kinda dirty
-    case is_function(Fun, 2) of
-        false -> throw({error, render_error});
-        true -> ok
-    end,
+ddoc(State, {Sig, Fun}, [<<"lists">>|_], Args) ->
     Self = self(),
     SpawnFun = fun() ->
-        LastChunk = (catch Fun(Head, Req)),
+        LastChunk = (catch apply(Fun, Args)),
         case start_list_resp(Self, Sig) of
             started ->
                 receive
@@ -177,44 +230,20 @@
     after State#evstate.timeout ->
         throw({timeout, list_start})
     end,
-    {State#evstate{list_pid=Pid}, Resp};
-run(#evstate{list_pid=Pid}=State, [<<"list_row">>, Row]) when is_pid(Pid) ->
-    Pid ! {self(), list_row, Row},
-    receive
-        {Pid, chunks, Data} ->
-            {State, [<<"chunks">>, Data]};
-        {Pid, list_end, Data} ->
-            receive
-                {'EXIT', Pid, normal} -> ok
-            after State#evstate.timeout ->
-                throw({timeout, list_cleanup})
-            end,
-            process_flag(trap_exit, erlang:get(do_trap)),
-            {State#evstate{list_pid=nil}, [<<"end">>, Data]}
-    after State#evstate.timeout ->
-        throw({timeout, list_row})
-    end;
-run(#evstate{list_pid=Pid}=State, [<<"list_end">>]) when is_pid(Pid) ->
-    Pid ! {self(), list_end},
-    Resp =
-    receive
-        {Pid, list_end, Data} ->
-            receive
-                {'EXIT', Pid, normal} -> ok
-            after State#evstate.timeout ->
-                throw({timeout, list_cleanup})
-            end,
-            [<<"end">>, Data]
-    after State#evstate.timeout ->
-        throw({timeout, list_end})
-    end,
-    process_flag(trap_exit, erlang:get(do_trap)),
-    {State#evstate{list_pid=nil}, Resp};
-run(_, Unknown) ->
-    ?LOG_ERROR("Native Process: Unknown command: ~p~n", [Unknown]),
-    throw({error, query_server_error}).
+    {State#evstate{list_pid=Pid}, Resp}.
+
+store_ddoc(DDocs, DDocId, DDoc) ->
+    dict:store(DDocId, DDoc, DDocs).
+load_ddoc(DDocs, DDocId) ->
+    try dict:fetch(DDocId, DDocs) of
+        {DDoc} -> {DDoc}
+    catch
+        _:Else -> throw({error, ?l2b(io_lib:format("Native Query Server missing DDoc with Id: ~s",[DDocId]))})
+    end.
 
 bindings(State, Sig) ->
+    bindings(State, Sig, nil).
+bindings(State, Sig, DDoc) ->
     Self = self(),
 
     Log = fun(Msg) ->
@@ -262,14 +291,19 @@
    
     FoldRows = fun(Fun, Acc) -> foldrows(GetRow, Fun, Acc) end,
 
-    [
+    Bindings = [
         {'Log', Log},
         {'Emit', Emit},
         {'Start', Start},
         {'Send', Send},
         {'GetRow', GetRow},
         {'FoldRows', FoldRows}
-    ].
+    ],
+    case DDoc of
+        {Props} ->
+            Bindings ++ [{'DDoc', DDoc}];
+        _Else -> Bindings
+    end.
 
 % thanks to erlview, via:
 % http://erlang.org/pipermail/erlang-questions/2003-November/010544.html
@@ -277,8 +311,11 @@
     Sig = erlang:md5(Source),
     BindFuns = bindings(State, Sig),
     {Sig, makefun(State, Source, BindFuns)}.
-
-makefun(_State, Source, BindFuns) ->
+makefun(State, Source, {DDoc}) ->
+    Sig = erlang:md5(lists:flatten([Source, term_to_binary(DDoc)])),
+    BindFuns = bindings(State, Sig, {DDoc}),
+    {Sig, makefun(State, Source, BindFuns)};
+makefun(_State, Source, BindFuns) when is_list(BindFuns) ->
     FunStr = binary_to_list(Source),
     {ok, Tokens, _} = erl_scan:string(FunStr),
     Form = case (catch erl_parse:parse_exprs(Tokens)) of

Modified: couchdb/trunk/src/couchdb/couch_os_process.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_os_process.erl?rev=893249&r1=893248&r2=893249&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_os_process.erl (original)
+++ couchdb/trunk/src/couchdb/couch_os_process.erl Tue Dec 22 18:03:44 2009
@@ -53,7 +53,7 @@
         {ok, Result} ->
             Result;
         Error ->
-            ?LOG_ERROR("OS Process Error :: ~p",[Error]),
+            ?LOG_ERROR("OS Process Error ~p :: ~p",[Pid,Error]),
             throw(Error)
     end.
 
@@ -80,22 +80,24 @@
 
 % Standard JSON functions
 writejson(OsProc, Data) when is_record(OsProc, os_proc) ->
-    % ?LOG_DEBUG("OS Process Input :: ~p", [Data]),
-    true = writeline(OsProc, ?JSON_ENCODE(Data)).
+    JsonData = ?JSON_ENCODE(Data),
+    ?LOG_DEBUG("OS Process ~p Input  :: ~s", [OsProc#os_proc.port, JsonData]),
+    true = writeline(OsProc, JsonData).
 
 readjson(OsProc) when is_record(OsProc, os_proc) ->
     Line = readline(OsProc),
+    ?LOG_DEBUG("OS Process ~p Output :: ~s", [OsProc#os_proc.port, Line]),
     case ?JSON_DECODE(Line) of
     [<<"log">>, Msg] when is_binary(Msg) ->
         % we got a message to log. Log it and continue
-        ?LOG_INFO("OS Process :: ~s", [Msg]),
+        ?LOG_INFO("OS Process ~p Log :: ~s", [OsProc#os_proc.port, Msg]),
         readjson(OsProc);
-    {[{<<"error">>, Id}, {<<"reason">>, Reason}]} ->
+    [<<"error">>, Id, Reason] ->
         throw({list_to_atom(binary_to_list(Id)),Reason});
-    {[{<<"reason">>, Reason}, {<<"error">>, Id}]} ->
+    [<<"fatal">>, Id, Reason] ->
+        ?LOG_INFO("OS Process ~p Fatal Error :: ~s ~p",[OsProc#os_proc.port, Id, Reason]),
         throw({list_to_atom(binary_to_list(Id)),Reason});
     Result ->
-        % ?LOG_DEBUG("OS Process Output :: ~p", [Result]),
         Result
     end.
 
@@ -112,6 +114,7 @@
     },
     KillCmd = readline(BaseProc),
     Pid = self(),
+    ?LOG_DEBUG("OS Process Start :: ~p", [BaseProc#os_proc.port]),
     spawn(fun() ->
             % this ensure the real os process is killed when this process dies.
             erlang:monitor(process, Pid),
@@ -143,8 +146,12 @@
         Writer(OsProc, Data),
         {reply, {ok, Reader(OsProc)}, OsProc}
     catch
-        throw:OsError ->
-            {stop, normal, OsError, OsProc}
+        throw:{error, OsError} ->
+            {reply, OsError, OsProc};
+        throw:{fatal, OsError} ->
+            {stop, normal, OsError, OsProc};
+        throw:OtherError ->
+            {stop, normal, OtherError, OsProc}
     end.
 
 handle_cast({send, Data}, #os_proc{writer=Writer}=OsProc) ->

Modified: couchdb/trunk/src/couchdb/couch_query_servers.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_query_servers.erl?rev=893249&r1=893248&r2=893249&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_query_servers.erl (original)
+++ couchdb/trunk/src/couchdb/couch_query_servers.erl Tue Dec 22 18:03:44 2009
@@ -17,10 +17,11 @@
 
 -export([init/1, terminate/2, handle_call/3, handle_cast/2, handle_info/2,code_change/3,stop/0]).
 -export([start_doc_map/2, map_docs/2, stop_doc_map/1]).
--export([reduce/3, rereduce/3,validate_doc_update/5]).
--export([render_doc_show/6, render_doc_update/6, start_view_list/2,
-        render_list_head/4, render_list_row/4, render_list_tail/1]).
+-export([reduce/3, rereduce/3,validate_doc_update/4]).
 -export([filter_docs/5]).
+
+-export([with_ddoc_proc/2, proc_prompt/2, ddoc_prompt/3, ddoc_proc_prompt/3, json_doc/1]).
+
 % -export([test/0]).
 
 -include("couch_db.hrl").
@@ -28,6 +29,7 @@
 -record(proc, {
     pid,
     lang,
+    ddoc_keys = [],
     prompt_fun,
     set_timeout_fun,
     stop_fun
@@ -37,7 +39,7 @@
     gen_server:start_link({local, couch_query_servers}, couch_query_servers, [], []).
 
 stop() ->
-    exit(whereis(couch_query_servers), close).
+    exit(whereis(couch_query_servers), normal).
 
 start_doc_map(Lang, Functions) ->
     Proc = get_os_process(Lang),
@@ -91,21 +93,15 @@
 rereduce(_Lang, [], _ReducedValues) ->
     {ok, []};
 rereduce(Lang, RedSrcs, ReducedValues) ->
-    Proc = get_os_process(Lang),
-    Grouped = group_reductions_results(ReducedValues),
-    Results = try lists:zipwith(
+    Grouped = group_reductions_results(ReducedValues),    
+    Results = lists:zipwith(
         fun
         (<<"_", _/binary>> = FunSrc, Values) ->
             {ok, [Result]} = builtin_reduce(rereduce, [FunSrc], [[[], V] || V <- Values], []),
             Result;
         (FunSrc, Values) ->
-            [true, [Result]] =
-                proc_prompt(Proc, [<<"rereduce">>, [FunSrc], Values]),
-            Result
-        end, RedSrcs, Grouped)
-    after
-        ok = ret_os_process(Proc)
-    end,
+            os_rereduce(Lang, [FunSrc], Values)
+        end, RedSrcs, Grouped),
     {ok, Results}.
 
 reduce(_Lang, [], _KVs) ->
@@ -137,6 +133,17 @@
     end,
     {ok, OsResults}.
 
+os_rereduce(_Lang, [], _KVs) ->
+    {ok, []};
+os_rereduce(Lang, OsRedSrcs, KVs) ->
+    Proc = get_os_process(Lang),
+    try proc_prompt(Proc, [<<"rereduce">>, OsRedSrcs, KVs]) of
+        [true, [Reduction]] -> Reduction
+    after
+        ok = ret_os_process(Proc)
+    end.
+
+
 builtin_reduce(_Re, [], _KVs, Acc) ->
     {ok, lists:reverse(Acc)};
 builtin_reduce(Re, [<<"_sum">>|BuiltinReds], KVs, Acc) ->
@@ -157,92 +164,49 @@
             throw({invalid_value, <<"builtin _sum function requires map values to be numbers">>})
     end, 0, KVs).
 
-validate_doc_update(Lang, FunSrc, EditDoc, DiskDoc, Ctx) ->
-    Proc = get_os_process(Lang),
+
+% use the function stored in ddoc.validate_doc_update to test an update.
+validate_doc_update(DDoc, EditDoc, DiskDoc, Ctx) ->
     JsonEditDoc = couch_doc:to_json_obj(EditDoc, [revs]),
-    JsonDiskDoc =
-    if DiskDoc == nil ->
-        null;
-    true ->
-        couch_doc:to_json_obj(DiskDoc, [revs])
-    end,
-    try proc_prompt(Proc,
-            [<<"validate">>, FunSrc, JsonEditDoc, JsonDiskDoc, Ctx]) of
-    1 ->
-        ok;
-    {[{<<"forbidden">>, Message}]} ->
-        throw({forbidden, Message});
-    {[{<<"unauthorized">>, Message}]} ->
-        throw({unauthorized, Message})
-    after
-        ok = ret_os_process(Proc)
+    JsonDiskDoc = json_doc(DiskDoc),  
+    case ddoc_prompt(DDoc, [<<"validate_doc_update">>], [JsonEditDoc, JsonDiskDoc, Ctx]) of
+        1 ->
+            ok;
+        {[{<<"forbidden">>, Message}]} ->
+            throw({forbidden, Message});
+        {[{<<"unauthorized">>, Message}]} ->
+            throw({unauthorized, Message})
     end.
-% todo use json_apply_field
-append_docid(DocId, JsonReqIn) ->
-    [{<<"docId">>, DocId} | JsonReqIn].
 
-render_doc_show(Lang, ShowSrc, DocId, Doc, Req, Db) ->
-    Proc = get_os_process(Lang),
-    {JsonReqIn} = couch_httpd_external:json_req_obj(Req, Db),
+json_doc(nil) -> null;
+json_doc(Doc) ->
+    couch_doc:to_json_obj(Doc, [revs]).
 
-    {JsonReq, JsonDoc} = case {DocId, Doc} of
-        {nil, nil} -> {{JsonReqIn}, null};
-        {DocId, nil} -> {{append_docid(DocId, JsonReqIn)}, null};
-        _ -> {{append_docid(DocId, JsonReqIn)}, couch_doc:to_json_obj(Doc, [revs])}
-    end,
-    try proc_prompt(Proc, [<<"show">>, ShowSrc, JsonDoc, JsonReq])
-    after
-        ok = ret_os_process(Proc)
-    end.
+filter_docs(Req, Db, DDoc, FName, Docs) ->
+    JsonReq = couch_httpd_external:json_req_obj(Req, Db),
+    JsonDocs = [couch_doc:to_json_obj(Doc, [revs]) || Doc <- Docs],
+    JsonCtx = couch_util:json_user_ctx(Db),
+    [true, Passes] = ddoc_prompt(DDoc, [<<"filters">>, FName], [JsonDocs, JsonReq, JsonCtx]),
+    {ok, Passes}.
 
-render_doc_update(Lang, UpdateSrc, DocId, Doc, Req, Db) ->
-    Proc = get_os_process(Lang),
-    {JsonReqIn} = couch_httpd_external:json_req_obj(Req, Db),
+ddoc_proc_prompt({Proc, DDocId}, FunPath, Args) -> 
+    proc_prompt(Proc, [<<"ddoc">>, DDocId, FunPath, Args]).
 
-    {JsonReq, JsonDoc} = case {DocId, Doc} of
-        {nil, nil} -> {{JsonReqIn}, null};
-        {DocId, nil} -> {{append_docid(DocId, JsonReqIn)}, null};
-        _ -> {{append_docid(DocId, JsonReqIn)}, couch_doc:to_json_obj(Doc, [revs])}
-    end,
-    try proc_prompt(Proc, [<<"update">>, UpdateSrc, JsonDoc, JsonReq])
+ddoc_prompt(DDoc, FunPath, Args) ->
+    with_ddoc_proc(DDoc, fun({Proc, DDocId}) ->
+        proc_prompt(Proc, [<<"ddoc">>, DDocId, FunPath, Args])
+    end).
+
+with_ddoc_proc(#doc{id=DDocId,revs={Start, [DiskRev|_]}}=DDoc, Fun) ->
+    Rev = couch_doc:rev_to_str({Start, DiskRev}),
+    DDocKey = {DDocId, Rev},
+    Proc = get_ddoc_process(DDoc, DDocKey),
+    try Fun({Proc, DDocId})
     after
         ok = ret_os_process(Proc)
     end.
 
-start_view_list(Lang, ListSrc) ->
-    Proc = get_os_process(Lang),
-    proc_prompt(Proc, [<<"add_fun">>, ListSrc]),
-    {ok, Proc}.
-
-render_list_head(Proc, Req, Db, Head) ->
-    JsonReq = couch_httpd_external:json_req_obj(Req, Db),
-    proc_prompt(Proc, [<<"list">>, Head, JsonReq]).
-
-render_list_row(Proc, Db, {{Key, DocId}, Value}, IncludeDoc) ->
-    JsonRow = couch_httpd_view:view_row_obj(Db, {{Key, DocId}, Value}, IncludeDoc),
-    proc_prompt(Proc, [<<"list_row">>, JsonRow]);
-
-render_list_row(Proc, _, {Key, Value}, _IncludeDoc) ->
-    JsonRow = {[{key, Key}, {value, Value}]},
-    proc_prompt(Proc, [<<"list_row">>, JsonRow]).
-
-render_list_tail(Proc) ->
-    JsonResp = proc_prompt(Proc, [<<"list_end">>]),
-    ok = ret_os_process(Proc),
-    JsonResp.
-
-filter_docs(Lang, Src, Docs, Req, Db) ->
-    JsonReq = couch_httpd_external:json_req_obj(Req, Db),
-    JsonDocs = [couch_doc:to_json_obj(Doc, [revs]) || Doc <- Docs],
-    JsonCtx = couch_util:json_user_ctx(Db),
-    Proc = get_os_process(Lang),
-    [true, Passes] = proc_prompt(Proc,
-        [<<"filter">>, Src, JsonDocs, JsonReq, JsonCtx]),
-    ret_os_process(Proc),
-    {ok, Passes}.    
-
 init([]) ->
-
     % read config and register for configuration changes
 
     % just stop if one of the config settings change. couch_server_sup
@@ -282,7 +246,39 @@
 terminate(_Reason, _Server) ->
     ok.
 
-
+handle_call({get_proc, #doc{body={Props}}=DDoc, DDocKey}, _From, {Langs, PidProcs, LangProcs, InUse}=Server) ->
+    % Note to future self. Add max process limit.
+    Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),    
+    case ets:lookup(LangProcs, Lang) of
+    [{Lang, [P|Rest]}] ->
+        % find a proc in the set that has the DDoc
+        case proc_with_ddoc(DDoc, DDocKey, [P|Rest]) of
+            {ok, Proc} ->
+                % looks like the proc isn't getting dropped from the list.
+                % we need to change this to take a fun for equality checking
+                % so we can do a comparison on portnum
+                rem_from_list(LangProcs, Lang, Proc),
+                add_to_list(InUse, Lang, Proc),
+                {reply, {ok, Proc, get_query_server_config()}, Server};
+            Error -> 
+                {reply, Error, Server}
+            end;
+    _ ->
+        case (catch new_process(Langs, Lang)) of
+        {ok, Proc} ->
+            add_value(PidProcs, Proc#proc.pid, Proc),
+            case proc_with_ddoc(DDoc, DDocKey, [Proc]) of
+                {ok, Proc2} ->
+                    rem_from_list(LangProcs, Lang, Proc),
+                    add_to_list(InUse, Lang, Proc2),
+                    {reply, {ok, Proc2, get_query_server_config()}, Server};
+                Error -> 
+                    {reply, Error, Server}
+                end;
+        Error ->
+            {reply, Error, Server}
+        end
+    end;
 handle_call({get_proc, Lang}, _From, {Langs, PidProcs, LangProcs, InUse}=Server) ->
     % Note to future self. Add max process limit.
     case ets:lookup(LangProcs, Lang) of
@@ -290,12 +286,13 @@
         add_value(PidProcs, Proc#proc.pid, Proc),
         rem_from_list(LangProcs, Lang, Proc),
         add_to_list(InUse, Lang, Proc),
-        {reply, {recycled, Proc, get_query_server_config()}, Server};
+        {reply, {ok, Proc, get_query_server_config()}, Server};
     _ ->
         case (catch new_process(Langs, Lang)) of
         {ok, Proc} ->
+            add_value(PidProcs, Proc#proc.pid, Proc),
             add_to_list(InUse, Lang, Proc),
-            {reply, {new, Proc}, Server};
+            {reply, {ok, Proc, get_query_server_config()}, Server};
         Error ->
             {reply, Error, Server}
         end
@@ -350,6 +347,23 @@
         {unknown_query_language, Lang}
     end.
 
+proc_with_ddoc(DDoc, DDocKey, LangProcs) ->
+    DDocProcs = lists:filter(fun(#proc{ddoc_keys=Keys}) -> 
+            lists:any(fun(Key) ->
+                Key == DDocKey
+            end, Keys)
+        end, LangProcs),
+    case DDocProcs of
+        [DDocProc|_] ->
+            ?LOG_DEBUG("DDocProc found for DDocKey: ~p",[DDocKey]),
+            {ok, DDocProc};
+        [] ->
+            [TeachProc|_] = LangProcs,
+            ?LOG_DEBUG("Teach ddoc to new proc ~p with DDocKey: ~p",[TeachProc, DDocKey]),
+            {ok, SmartProc} = teach_ddoc(DDoc, DDocKey, TeachProc),
+            {ok, SmartProc}
+    end.
+
 proc_prompt(Proc, Args) ->
     {Mod, Func} = Proc#proc.prompt_fun,
     apply(Mod, Func, [Proc#proc.pid, Args]).
@@ -362,14 +376,44 @@
     {Mod, Func} = Proc#proc.set_timeout_fun,
     apply(Mod, Func, [Proc#proc.pid, Timeout]).
 
+teach_ddoc(DDoc, {DDocId, _Rev}=DDocKey, #proc{ddoc_keys=Keys}=Proc) ->
+    % send ddoc over the wire
+    % we only share the rev with the client we know to update code
+    % but it only keeps the latest copy, per each ddoc, around.
+    true = proc_prompt(Proc, [<<"ddoc">>, <<"new">>, DDocId, couch_doc:to_json_obj(DDoc, [])]),
+    % we should remove any other ddocs keys for this docid
+    % because the query server overwrites without the rev
+    Keys2 = [{D,R} || {D,R} <- Keys, D /= DDocId],
+    % add ddoc to the proc
+    {ok, Proc#proc{ddoc_keys=[DDocKey|Keys2]}}.
+
+get_ddoc_process(#doc{} = DDoc, DDocKey) ->
+    % remove this case statement
+    case gen_server:call(couch_query_servers, {get_proc, DDoc, DDocKey}) of
+    {ok, Proc, QueryConfig} ->
+        % process knows the ddoc
+        case (catch proc_prompt(Proc, [<<"reset">>, QueryConfig])) of
+        true ->
+            proc_set_timeout(Proc, list_to_integer(couch_config:get(
+                                "couchdb", "os_process_timeout", "5000"))),
+            link(Proc#proc.pid),
+            Proc;
+        _ ->
+            catch proc_stop(Proc),
+            get_ddoc_process(DDoc, DDocKey)
+        end;
+    Error ->
+        throw(Error)
+    end.
+
+ret_ddoc_process(Proc) ->
+    true = gen_server:call(couch_query_servers, {ret_proc, Proc}),
+    catch unlink(Proc#proc.pid),
+    ok.
+
 get_os_process(Lang) ->
     case gen_server:call(couch_query_servers, {get_proc, Lang}) of
-    {new, Proc} ->
-        proc_set_timeout(Proc, list_to_integer(couch_config:get(
-                            "couchdb", "os_process_timeout", "5000"))),
-        link(Proc#proc.pid),
-        Proc;
-    {recycled, Proc, QueryConfig} ->
+    {ok, Proc, QueryConfig} ->
         case (catch proc_prompt(Proc, [<<"reset">>, QueryConfig])) of
         true ->
             proc_set_timeout(Proc, list_to_integer(couch_config:get(
@@ -403,9 +447,20 @@
         true = ets:insert(Tid, {Key, [Value]})
     end.
 
+rem_from_list(Tid, Key, Value) when is_record(Value, proc)->
+    Pid = Value#proc.pid,
+    case ets:lookup(Tid, Key) of
+    [{Key, Vals}] ->
+        % make a new values list that doesn't include the Value arg
+        NewValues = [Val || #proc{pid=P}=Val <- Vals, P /= Pid],
+        ets:insert(Tid, {Key, NewValues});
+    [] -> ok
+    end;
 rem_from_list(Tid, Key, Value) ->
     case ets:lookup(Tid, Key) of
     [{Key, Vals}] ->
-        ets:insert(Tid, {Key, [Val || Val <- Vals, Val /= Value]});
+        % make a new values list that doesn't include the Value arg
+        NewValues = [Val || Val <- Vals, Val /= Value],
+        ets:insert(Tid, {Key, NewValues});
     [] -> ok
     end.

Modified: couchdb/trunk/test/view_server/query_server_spec.rb
URL: http://svn.apache.org/viewvc/couchdb/trunk/test/view_server/query_server_spec.rb?rev=893249&r1=893248&r2=893249&view=diff
==============================================================================
--- couchdb/trunk/test/view_server/query_server_spec.rb (original)
+++ couchdb/trunk/test/view_server/query_server_spec.rb Tue Dec 22 18:03:44 2009
@@ -12,6 +12,13 @@
 
 # to run (requires ruby and rspec):
 # spec test/view_server/query_server_spec.rb -f specdoc --color
+# 
+# environment options:
+#   QS_TRACE=true
+#     shows full output from the query server
+#   QS_LANG=lang
+#     run tests on the query server (for now, one of: js, erlang)
+# 
 
 COUCH_ROOT = "#{File.dirname(__FILE__)}/../.." unless defined?(COUCH_ROOT)
 LANGUAGE = ENV["QS_LANG"] || "js"
@@ -48,6 +55,17 @@
   def add_fun(fun)
     run(["add_fun", fun])
   end
+  def teach_ddoc(ddoc)
+    run(["ddoc", "new", ddoc_id(ddoc), ddoc])
+  end
+  def ddoc_run(ddoc, fun_path, args)
+    run(["ddoc", ddoc_id(ddoc), fun_path, args])
+  end
+  def ddoc_id(ddoc)
+    d_id = ddoc["_id"]
+    raise 'ddoc must have _id' unless d_id
+    d_id
+  end
   def get_chunks
     resp = jsgets
     raise "not a chunk" unless resp.first == "chunks"
@@ -99,7 +117,7 @@
 
   COMMANDS = {
     "js" => "#{COUCH_ROOT}/bin/couchjs_dev #{COUCH_ROOT}/share/server/main.js",
-    "erlang" => "#{COUCH_ROOT}/test/run_native_process.es"
+    "erlang" => "#{COUCH_ROOT}/test/view_server/run_native_process.es"
   }
 
   def self.run_command
@@ -113,6 +131,8 @@
   end
 end
 
+# we could organize this into a design document per language.
+# that would make testing future languages really easy.
 
 functions = {
   "emit-twice" => {
@@ -126,7 +146,11 @@
     ERLANG
   },
   "emit-once" => {
-    "js" => %{function(doc){emit("baz",doc.a)}},
+    "js" => <<-JS,
+      function(doc){
+        emit("baz",doc.a)
+      }
+      JS
     "erlang" => <<-ERLANG
         fun({Doc}) ->
             A = proplists:get_value(<<"a">>, Doc, null),
@@ -370,6 +394,8 @@
   "list-raw" => {
     "js" => <<-JS,
         function(head, req) {
+          // log(this.toSource());
+          // log(typeof send);
           send("first chunk");
           send(req.q);
           var row;
@@ -420,9 +446,47 @@
             [{Doc2}, {[{<<"body">>, <<"hello doc">>}]}]
         end.
     ERLANG
+  },
+  "error" => {
+    "js" => <<-JS,
+    function() {
+      throw(["error","error_key","testing"]);
+    }
+    JS
+    "erlang" => <<-ERLANG
+    fun(A, B) ->
+      throw([<<"error">>,<<"error_key">>,<<"testing">>])
+    end.
+    ERLANG
+  },
+  "fatal" => {
+    "js" => <<-JS,
+    function() {
+      throw(["fatal","error_key","testing"]);
+    }
+    JS
+    "erlang" => <<-ERLANG
+    fun(A, B) ->
+      throw([<<"fatal">>,<<"error_key">>,<<"testing">>])
+    end.
+    ERLANG
   }
 }
 
+def make_ddoc(fun_path, fun_str)
+  doc = {"_id"=>"foo"}
+  d = doc
+  while p = fun_path.shift
+    l = p
+    if !fun_path.empty?
+      d[p] = {}
+      d = d[p]
+    end
+  end
+  d[l] = fun_str
+  doc
+end
+
 describe "query server normal case" do
   before(:all) do
     `cd #{COUCH_ROOT} && make`
@@ -434,6 +498,17 @@
   it "should reset" do
     @qs.run(["reset"]).should == true
   end
+  it "should not erase ddocs on reset" do
+    @fun = functions["show-simple"][LANGUAGE]
+    @ddoc = make_ddoc(["shows","simple"], @fun)
+    @qs.teach_ddoc(@ddoc)
+    @qs.run(["reset"]).should == true   
+    @qs.ddoc_run(@ddoc, 
+      ["shows","simple"], 
+      [{:title => "Best ever", :body => "Doc body"}, {}]).should ==
+    ["resp", {"body" => "Best ever - Doc body"}] 
+  end
+  
   it "should run map funs" do
     @qs.reset!
     @qs.run(["add_fun", functions["emit-twice"][LANGUAGE]]).should == true
@@ -464,173 +539,222 @@
     end
   end
 
+  describe "design docs" do
+    before(:all) do
+      @ddoc = {
+        "_id" => "foo"
+      }
+      @qs.reset!
+    end
+    it "should learn design docs" do
+      @qs.teach_ddoc(@ddoc).should == true
+    end
+  end
+
   # it "should validate"
   describe "validation" do
     before(:all) do
       @fun = functions["validate-forbidden"][LANGUAGE]
-      @qs.reset!
+      @ddoc = make_ddoc(["validate_doc_update"], @fun)
+      @qs.teach_ddoc(@ddoc)
     end
     it "should allow good updates" do
-      @qs.run(["validate", @fun, {"good" => true}, {}, {}]).should == 1
+      @qs.ddoc_run(@ddoc, 
+        ["validate_doc_update"], 
+        [{"good" => true}, {}, {}]).should == 1
     end
     it "should reject invalid updates" do
-      @qs.run(["validate", @fun, {"bad" => true}, {}, {}]).should == {"forbidden"=>"bad doc"}
+      @qs.ddoc_run(@ddoc, 
+        ["validate_doc_update"], 
+        [{"bad" => true}, {}, {}]).should == {"forbidden"=>"bad doc"}
     end
   end
 
   describe "show" do
     before(:all) do
       @fun = functions["show-simple"][LANGUAGE]
-      @qs.reset!
+      @ddoc = make_ddoc(["shows","simple"], @fun)
+      @qs.teach_ddoc(@ddoc)
     end
     it "should show" do
-      @qs.rrun(["show", @fun,
-        {:title => "Best ever", :body => "Doc body"}, {}])
-      @qs.jsgets.should == ["resp", {"body" => "Best ever - Doc body"}]
+      @qs.ddoc_run(@ddoc, 
+        ["shows","simple"], 
+        [{:title => "Best ever", :body => "Doc body"}, {}]).should ==
+      ["resp", {"body" => "Best ever - Doc body"}]
     end
   end
 
   describe "show with headers" do
     before(:all) do
+      # TODO we can make real ddocs up there. 
       @fun = functions["show-headers"][LANGUAGE]
-      @qs.reset!
+      @ddoc = make_ddoc(["shows","headers"], @fun)
+      @qs.teach_ddoc(@ddoc)
     end
     it "should show headers" do
-      @qs.rrun(["show", @fun,
-        {:title => "Best ever", :body => "Doc body"}, {}])
-      @qs.jsgets.should == ["resp", {"code"=>200,"headers" => {"X-Plankton"=>"Rusty"}, "body" => "Best ever - Doc body"}]
-    end
-  end
-
-# end
-#                    LIST TESTS
-# __END__
-
-  describe "raw list with headers" do
-    before(:each) do
-      @fun = functions["show-sends"][LANGUAGE]
-      @qs.reset!
-      @qs.add_fun(@fun).should == true
-    end
-    it "should do headers proper" do
-      @qs.rrun(["list", {"total_rows"=>1000}, {"q" => "ok"}])
-      @qs.jsgets.should == ["start", ["first chunk", 'second "chunk"'], {"headers"=>{"Content-Type"=>"text/plain"}}]
-      @qs.rrun(["list_end"])
-      @qs.jsgets.should == ["end", ["tail"]]
-    end
-  end
-
-  describe "list with rows" do
-    before(:each) do
-      @fun = functions["show-while-get-rows"][LANGUAGE]
-      @qs.run(["reset"]).should == true
-      @qs.add_fun(@fun).should == true
-    end
-    it "should list em" do
-      @qs.rrun(["list", {"foo"=>"bar"}, {"q" => "ok"}])
-      @qs.jsgets.should == ["start", ["first chunk", "ok"], {"headers"=>{}}]
-      @qs.rrun(["list_row", {"key"=>"baz"}])
-      @qs.get_chunks.should == ["baz"]
-      @qs.rrun(["list_row", {"key"=>"bam"}])
-      @qs.get_chunks.should == ["bam"]
-      @qs.rrun(["list_end"])
-      @qs.jsgets.should == ["end", ["tail"]]
-    end
-    it "should work with zero rows" do
-      @qs.rrun(["list", {"foo"=>"bar"}, {"q" => "ok"}])
-      @qs.jsgets.should == ["start", ["first chunk", "ok"], {"headers"=>{}}]
-      @qs.rrun(["list_end"])
-      @qs.jsgets.should == ["end", ["tail"]]
-    end
-  end
-
-  describe "should buffer multiple chunks sent for a single row." do
-    before(:all) do
-      @fun = functions["show-while-get-rows-multi-send"][LANGUAGE]
-      @qs.reset!
-      @qs.add_fun(@fun).should == true
-    end
-    it "should should buffer em" do
-      @qs.rrun(["list", {"foo"=>"bar"}, {"q" => "ok"}])
-      @qs.jsgets.should == ["start", ["bacon"], {"headers"=>{}}]
-      @qs.rrun(["list_row", {"key"=>"baz"}])
-      @qs.get_chunks.should == ["baz", "eggs"]
-      @qs.rrun(["list_row", {"key"=>"bam"}])
-      @qs.get_chunks.should == ["bam", "eggs"]
-      @qs.rrun(["list_end"])
-      @qs.jsgets.should == ["end", ["tail"]]
-    end
-  end
-
-  describe "example list" do
-    before(:all) do
-      @fun = functions["list-simple"][LANGUAGE]
-      @qs.reset!
-      @qs.add_fun(@fun).should == true
-    end
-    it "should run normal" do
-      @qs.run(["list", {"foo"=>"bar"}, {"q" => "ok"}]).should == ["start", ["first chunk", "ok"], {"headers"=>{}}]
-      @qs.run(["list_row", {"key"=>"baz"}]).should ==  ["chunks", ["baz"]]
-      @qs.run(["list_row", {"key"=>"bam"}]).should ==  ["chunks", ["bam"]]
-      @qs.run(["list_row", {"key"=>"foom"}]).should == ["chunks", ["foom"]]
-      @qs.run(["list_row", {"key"=>"fooz"}]).should == ["chunks", ["fooz"]]
-      @qs.run(["list_row", {"key"=>"foox"}]).should == ["chunks", ["foox"]]
-      @qs.run(["list_end"]).should == ["end" , ["early"]]
+      @qs.ddoc_run(
+        @ddoc, 
+        ["shows","headers"], 
+        [{:title => "Best ever", :body => "Doc body"}, {}]
+      ).
+      should == ["resp", {"code"=>200,"headers" => {"X-Plankton"=>"Rusty"}, "body" => "Best ever - Doc body"}]
     end
   end
-
-  describe "only goes to 2 list" do
+  
+  describe "recoverable error" do
     before(:all) do
-      @fun = functions["list-chunky"][LANGUAGE]
-      @qs.reset!
-      @qs.add_fun(@fun).should == true
-    end
-    it "should end early" do
-      @qs.run(["list", {"foo"=>"bar"}, {"q" => "ok"}]).
-        should == ["start", ["first chunk", "ok"], {"headers"=>{}}]
-      @qs.run(["list_row", {"key"=>"baz"}]).
-        should ==  ["chunks", ["baz"]]
-
-      @qs.run(["list_row", {"key"=>"bam"}]).
-        should ==  ["chunks", ["bam"]]
-
-      @qs.run(["list_row", {"key"=>"foom"}]).
-        should == ["end", ["foom", "early tail"]]
-      # here's where js has to discard quit properly
-      @qs.run(["reset"]).
-        should == true
+      @fun = functions["error"][LANGUAGE]
+      @ddoc = make_ddoc(["shows","error"], @fun)
+      @qs.teach_ddoc(@ddoc)
+    end
+    it "should not exit" do
+      @qs.ddoc_run(@ddoc, ["shows","error"],
+        [{"foo"=>"bar"}, {"q" => "ok"}]).
+        should == ["error", "error_key", "testing"]
+      # still running
+      @qs.run(["reset"]).should == true
     end
   end
   
   describe "changes filter" do
     before(:all) do
       @fun = functions["filter-basic"][LANGUAGE]
-      @qs.reset!
+      @ddoc = make_ddoc(["filters","basic"], @fun)
+      @qs.teach_ddoc(@ddoc)
     end
     it "should only return true for good docs" do
-      @qs.run(["filter", @fun, [{"key"=>"bam", "good" => true}, {"foo" => "bar"}, {"good" => true}], {"req" => "foo"}]).
-        should ==  [true, [true, false, true]]
+      @qs.ddoc_run(@ddoc, 
+        ["filters","basic"], 
+        [[{"key"=>"bam", "good" => true}, {"foo" => "bar"}, {"good" => true}], {"req" => "foo"}]
+      ).
+      should == [true, [true, false, true]]
     end
   end
   
   describe "update" do
     before(:all) do
+      # in another patch we can remove this duplication
+      # by setting up the design doc for each language ahead of time.
       @fun = functions["update-basic"][LANGUAGE]
-      @qs.reset!
+      @ddoc = make_ddoc(["updates","basic"], @fun)
+      @qs.teach_ddoc(@ddoc)
     end
     it "should return a doc and a resp body" do
-      up, doc, resp = @qs.run(["update", @fun, {"foo" => "gnarly"}, {"verb" => "POST"}])
+      up, doc, resp = @qs.ddoc_run(@ddoc, 
+        ["updates","basic"], 
+        [{"foo" => "gnarly"}, {"verb" => "POST"}]
+      )
       up.should == "up"
       doc.should == {"foo" => "gnarly", "world" => "hello"}
       resp["body"].should == "hello doc"
     end
   end
-end
+
+# end
+#                    LIST TESTS
+# __END__
+
+  describe "ddoc list" do
+      before(:all) do
+        @ddoc = {
+          "_id" => "foo",
+          "lists" => {
+            "simple" => functions["list-simple"][LANGUAGE],
+            "headers" => functions["show-sends"][LANGUAGE],
+            "rows" => functions["show-while-get-rows"][LANGUAGE],
+            "buffer-chunks" => functions["show-while-get-rows-multi-send"][LANGUAGE],
+            "chunky" => functions["list-chunky"][LANGUAGE]
+          }
+        }
+        @qs.teach_ddoc(@ddoc)
+      end
+      
+      describe "example list" do
+        it "should run normal" do
+          @qs.ddoc_run(@ddoc,
+            ["lists","simple"],
+            [{"foo"=>"bar"}, {"q" => "ok"}]
+          ).should == ["start", ["first chunk", "ok"], {"headers"=>{}}]
+          @qs.run(["list_row", {"key"=>"baz"}]).should ==  ["chunks", ["baz"]]
+          @qs.run(["list_row", {"key"=>"bam"}]).should ==  ["chunks", ["bam"]]
+          @qs.run(["list_row", {"key"=>"foom"}]).should == ["chunks", ["foom"]]
+          @qs.run(["list_row", {"key"=>"fooz"}]).should == ["chunks", ["fooz"]]
+          @qs.run(["list_row", {"key"=>"foox"}]).should == ["chunks", ["foox"]]
+          @qs.run(["list_end"]).should == ["end" , ["early"]]
+        end
+      end
+      
+      describe "headers" do
+        it "should do headers proper" do
+          @qs.ddoc_run(@ddoc, ["lists","headers"], 
+            [{"total_rows"=>1000}, {"q" => "ok"}]
+          ).should == ["start", ["first chunk", 'second "chunk"'], 
+            {"headers"=>{"Content-Type"=>"text/plain"}}]
+          @qs.rrun(["list_end"])
+          @qs.jsgets.should == ["end", ["tail"]]
+        end
+      end
+
+      describe "with rows" do
+        it "should list em" do
+          @qs.ddoc_run(@ddoc, ["lists","rows"], 
+            [{"foo"=>"bar"}, {"q" => "ok"}]).
+            should == ["start", ["first chunk", "ok"], {"headers"=>{}}]
+          @qs.rrun(["list_row", {"key"=>"baz"}])
+          @qs.get_chunks.should == ["baz"]
+          @qs.rrun(["list_row", {"key"=>"bam"}])
+          @qs.get_chunks.should == ["bam"]
+          @qs.rrun(["list_end"])
+          @qs.jsgets.should == ["end", ["tail"]]
+        end
+        it "should work with zero rows" do
+          @qs.ddoc_run(@ddoc, ["lists","rows"],
+            [{"foo"=>"bar"}, {"q" => "ok"}]).
+            should == ["start", ["first chunk", "ok"], {"headers"=>{}}]
+          @qs.rrun(["list_end"])
+          @qs.jsgets.should == ["end", ["tail"]]
+        end
+      end
+      
+      describe "should buffer multiple chunks sent for a single row." do
+        it "should should buffer em" do
+          @qs.ddoc_run(@ddoc, ["lists","buffer-chunks"],
+            [{"foo"=>"bar"}, {"q" => "ok"}]).
+            should == ["start", ["bacon"], {"headers"=>{}}]
+          @qs.rrun(["list_row", {"key"=>"baz"}])
+          @qs.get_chunks.should == ["baz", "eggs"]
+          @qs.rrun(["list_row", {"key"=>"bam"}])
+          @qs.get_chunks.should == ["bam", "eggs"]
+          @qs.rrun(["list_end"])
+          @qs.jsgets.should == ["end", ["tail"]]
+        end
+      end
+      it "should end after 2" do
+        @qs.ddoc_run(@ddoc, ["lists","chunky"],
+          [{"foo"=>"bar"}, {"q" => "ok"}]).
+          should == ["start", ["first chunk", "ok"], {"headers"=>{}}]
+          
+        @qs.run(["list_row", {"key"=>"baz"}]).
+          should ==  ["chunks", ["baz"]]
+
+        @qs.run(["list_row", {"key"=>"bam"}]).
+          should ==  ["chunks", ["bam"]]
+
+        @qs.run(["list_row", {"key"=>"foom"}]).
+          should == ["end", ["foom", "early tail"]]
+        # here's where js has to discard quit properly
+        @qs.run(["reset"]).
+          should == true
+      end
+    end
+  end
+
+
 
 def should_have_exited qs
   begin
     qs.run(["reset"])
-    "raise before this".should == true
+    "raise before this (except Erlang)".should == true
   rescue RuntimeError => e
     e.message.should == "no response"
   rescue Errno::EPIPE
@@ -641,54 +765,60 @@
 describe "query server that exits" do
   before(:each) do
     @qs = QueryServerRunner.run
+    @ddoc = {
+      "_id" => "foo",
+      "lists" => {
+        "capped" => functions["list-capped"][LANGUAGE],
+        "raw" => functions["list-raw"][LANGUAGE]
+      },
+      "shows" => {
+        "fatal" => functions["fatal"][LANGUAGE]
+      }
+    }
+    @qs.teach_ddoc(@ddoc)
   end
   after(:each) do
     @qs.close
   end
 
-  if LANGUAGE == "js"
-    describe "old style list" do
-      before(:each) do
-        @fun = functions["list-old-style"][LANGUAGE]
-        @qs.reset!
-        @qs.add_fun(@fun).should == true
-      end
-      it "should get a warning" do
-        resp = @qs.run(["list", {"foo"=>"bar"}, {"q" => "ok"}])
-        resp["error"].should == "render_error"
-        resp["reason"].should include("the list API has changed")
-      end
-    end
-  end
-
   describe "only goes to 2 list" do
-    before(:each) do
-      @fun = functions["list-capped"][LANGUAGE]
-      @qs.reset!
-      @qs.add_fun(@fun).should == true
-    end
     it "should exit if erlang sends too many rows" do
-      @qs.run(["list", {"foo"=>"bar"}, {"q" => "ok"}]).should == ["start", ["bacon"], {"headers"=>{}}]
+      @qs.ddoc_run(@ddoc, ["lists","capped"],
+        [{"foo"=>"bar"}, {"q" => "ok"}]).
+        should == ["start", ["bacon"], {"headers"=>{}}]
       @qs.run(["list_row", {"key"=>"baz"}]).should ==  ["chunks", ["baz"]]
       @qs.run(["list_row", {"key"=>"foom"}]).should == ["chunks", ["foom"]]
       @qs.run(["list_row", {"key"=>"fooz"}]).should == ["end", ["fooz", "early"]]
-      @qs.rrun(["list_row", {"key"=>"foox"}])
-      @qs.jsgets["error"].should == "query_server_error"
+      e = @qs.run(["list_row", {"key"=>"foox"}])
+      e[0].should == "error"
+      e[1].should == "unknown_command"
       should_have_exited @qs
     end
   end
 
   describe "raw list" do
-    before(:each) do
-      @fun = functions["list-raw"][LANGUAGE]
-      @qs.run(["reset"]).should == true
-      @qs.add_fun(@fun).should == true
-    end
     it "should exit if it gets a non-row in the middle" do
-      @qs.rrun(["list", {"foo"=>"bar"}, {"q" => "ok"}])
-      @qs.jsgets.should == ["start", ["first chunk", "ok"], {"headers"=>{}}]
-      @qs.run(["reset"])["error"].should == "query_server_error"
+      @qs.ddoc_run(@ddoc, ["lists","raw"],
+        [{"foo"=>"bar"}, {"q" => "ok"}]).
+        should == ["start", ["first chunk", "ok"], {"headers"=>{}}]
+      e = @qs.run(["reset"])
+      e[0].should == "error"
+      e[1].should == "list_error"
+      should_have_exited @qs
+    end
+  end
+  
+  describe "fatal error" do
+    it "should exit" do
+      @qs.ddoc_run(@ddoc, ["shows","fatal"],
+        [{"foo"=>"bar"}, {"q" => "ok"}]).
+        should == ["error", "error_key", "testing"]
       should_have_exited @qs
     end
   end
 end
+
+describe "thank you for using the tests" do
+  it "for more info run with QS_TRACE=true or see query_server_spec.rb file header" do
+  end
+end
\ No newline at end of file

Modified: couchdb/trunk/test/view_server/run_native_process.es
URL: http://svn.apache.org/viewvc/couchdb/trunk/test/view_server/run_native_process.es?rev=893249&r1=893248&r2=893249&view=diff
==============================================================================
--- couchdb/trunk/test/view_server/run_native_process.es (original)
+++ couchdb/trunk/test/view_server/run_native_process.es Tue Dec 22 18:03:44 2009
@@ -15,7 +15,7 @@
 read() ->
     case io:get_line('') of
         eof -> stop;
-        Data -> mochijson2:decode(Data)
+        Data -> couch_util:json_decode(Data)
     end.
 
 send(Data) when is_binary(Data) ->
@@ -24,15 +24,19 @@
     io:format(Data ++ "\n", []).
 
 write(Data) ->
-    case (catch mochijson2:encode(Data)) of
+    % log("~p", [Data]),
+    case (catch couch_util:json_encode(Data)) of
+        % when testing, this is what prints your errors
         {json_encode, Error} -> write({[{<<"error">>, Error}]});
         Json -> send(Json)
     end.
 
-%log(Mesg) ->
+% log(Mesg) ->
 %    log(Mesg, []).
-%log(Mesg, Params) ->
+% log(Mesg, Params) ->
 %    io:format(standard_error, Mesg, Params).
+% jlog(Mesg) ->
+%     write([<<"log">>, list_to_binary(io_lib:format("~p",[Mesg]))]).
 
 loop(Pid) ->
     case read() of
@@ -40,7 +44,7 @@
         Json ->
             case (catch couch_native_process:prompt(Pid, Json)) of
                 {error, Reason} ->
-                    ok = write({[{error, Reason}]});
+                    ok = write([error, Reason, Reason]);
                 Resp ->
                     ok = write(Resp),
                     loop(Pid)



Mime
View raw message