You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by jc...@apache.org on 2009/05/10 11:11:52 UTC

svn commit: r773319 - in /couchdb/trunk: etc/couchdb/local_dev.ini share/www/script/test/list_views.js src/couchdb/couch_httpd_db.erl src/couchdb/couch_httpd_show.erl src/couchdb/couch_httpd_view.erl

Author: jchris
Date: Sun May 10 09:11:52 2009
New Revision: 773319

URL: http://svn.apache.org/viewvc?rev=773319&view=rev
Log:
Refactor the make_view_fold_function stuff. Added a proper Acc to both map and reduce view folds. Cleaned up some pattern matchers.

Modified:
    couchdb/trunk/etc/couchdb/local_dev.ini
    couchdb/trunk/share/www/script/test/list_views.js
    couchdb/trunk/src/couchdb/couch_httpd_db.erl
    couchdb/trunk/src/couchdb/couch_httpd_show.erl
    couchdb/trunk/src/couchdb/couch_httpd_view.erl

Modified: couchdb/trunk/etc/couchdb/local_dev.ini
URL: http://svn.apache.org/viewvc/couchdb/trunk/etc/couchdb/local_dev.ini?rev=773319&r1=773318&r2=773319&view=diff
==============================================================================
--- couchdb/trunk/etc/couchdb/local_dev.ini (original)
+++ couchdb/trunk/etc/couchdb/local_dev.ini Sun May 10 09:11:52 2009
@@ -12,7 +12,7 @@
 ;bind_address = 127.0.0.1
 
 [log]
-level = debug
+level = info
 
 [update_notification]
 ;unique notifier name=/full/path/to/exe -with "cmd line arg"

Modified: couchdb/trunk/share/www/script/test/list_views.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/test/list_views.js?rev=773319&r1=773318&r2=773319&view=diff
==============================================================================
--- couchdb/trunk/share/www/script/test/list_views.js (original)
+++ couchdb/trunk/share/www/script/test/list_views.js Sun May 10 09:11:52 2009
@@ -248,15 +248,15 @@
   
   // aborting iteration
   var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter/basicView");
-  T(xhr.responseText.match(/^head 0 1 2 tail$/));
+  T(xhr.responseText.match(/^head 0 1 2 tail$/) && "basic stop");
   xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter2/basicView");
-  T(xhr.responseText.match(/^head 0 1 2 tail$/));
+  T(xhr.responseText.match(/^head 0 1 2 tail$/) && "stop 2");
 
   // aborting iteration with reduce
   var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter/withReduce?group=true");
-  T(xhr.responseText.match(/^head 0 1 2 tail$/));
+  T(xhr.responseText.match(/^head 0 1 2 tail$/) && "reduce stop");
   xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter2/withReduce?group=true");
-  T(xhr.responseText.match(/^head 0 1 2 tail$/));
+  T(xhr.responseText.match(/^head 0 1 2 tail$/) && "reduce stop 2");
 
   // empty list
   var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/emptyList/basicView");

Modified: couchdb/trunk/src/couchdb/couch_httpd_db.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_db.erl?rev=773319&r1=773318&r2=773319&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd_db.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd_db.erl Sun May 10 09:11:52 2009
@@ -457,21 +457,21 @@
         true -> StartDocId
         end,
         FoldAccInit = {Limit, SkipCount, undefined, []},
-    
-        PassedEndFun = 
-        case Dir of
-        fwd ->
-            fun(ViewKey, _ViewId) ->
-                couch_db_updater:less_docid(EndKey, ViewKey)
-            end;
-        rev->
-            fun(ViewKey, _ViewId) ->
-                couch_db_updater:less_docid(ViewKey, EndKey)
-            end
-        end,
-    
+        
         case Keys of
         nil ->
+            PassedEndFun = 
+            case Dir of
+            fwd ->
+                fun(ViewKey, _ViewId) ->
+                    couch_db_updater:less_docid(EndKey, ViewKey)
+                end;
+            rev->
+                fun(ViewKey, _ViewId) ->
+                    couch_db_updater:less_docid(ViewKey, EndKey)
+                end
+            end,
+            
             FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db,
                 TotalRowCount, #view_fold_helper_funs{
                     reduce_count = fun couch_db:enum_docs_reduce_to_count/1,

Modified: couchdb/trunk/src/couchdb/couch_httpd_show.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_show.erl?rev=773319&r1=773318&r2=773319&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd_show.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd_show.erl Sun May 10 09:11:52 2009
@@ -117,10 +117,10 @@
         end
     end.
 
-make_map_start_resp_fun(QueryServer, Req, Db, CurrentEtag) ->
-    fun(Req2, _Etag, TotalViewCount, Offset) ->
+make_map_start_resp_fun(QueryServer, Db) ->
+    fun(Req, CurrentEtag, TotalViewCount, Offset, _Acc) ->
         ExternalResp = couch_query_servers:render_list_head(QueryServer, 
-            Req2, Db, TotalViewCount, Offset),
+            Req, Db, TotalViewCount, Offset),
         JsonResp = apply_etag(ExternalResp, CurrentEtag),
         #extern_resp_args{
             code = Code,
@@ -134,8 +134,7 @@
     end.
 
 make_map_send_row_fun(QueryServer, Req) ->
-    fun(Resp, Db2, {{Key, DocId}, Value}, 
-        RowFront, _IncludeDocs) ->
+    fun(Resp, Db2, {{Key, DocId}, Value}, _IncludeDocs, RowFront) ->
         try
             JsonResp = couch_query_servers:render_list_row(QueryServer, 
                 Req, Db2, {{Key, DocId}, Value}),
@@ -144,17 +143,14 @@
                 data = RowBody
             } = couch_httpd_external:parse_external_response(JsonResp),
             case StopIter of
-            true -> stop;
+            true -> {stop, ""};
             _ ->
-                RowFront2 = case RowFront of
-                nil -> [];
-                _ -> RowFront
-                end,
-                Chunk = RowFront2 ++ binary_to_list(RowBody),
+                Chunk = RowFront ++ binary_to_list(RowBody),
                 case Chunk of
-                    [] -> {ok, Resp};
+                    [] -> ok;
                     _ -> send_chunk(Resp, Chunk)
-                end
+                end,
+                {ok, ""}
             end
         catch
             throw:Error ->
@@ -182,7 +178,7 @@
         % pass it into the view fold with closures
         {ok, QueryServer} = couch_query_servers:start_view_list(Lang, ListSrc),
 
-        StartListRespFun = make_map_start_resp_fun(QueryServer, Req, Db, CurrentEtag),
+        StartListRespFun = make_map_start_resp_fun(QueryServer, Db),
         SendListRowFun = make_map_send_row_fun(QueryServer, Req),
     
         FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, RowCount,
@@ -192,7 +188,7 @@
                 send_row = SendListRowFun
             }),
         FoldAccInit = {Limit, SkipCount, undefined, []},
-        FoldResult = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
+        {ok, FoldResult} = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
         finish_list(Req, Db, QueryServer, CurrentEtag, FoldResult, StartListRespFun, RowCount)
     end);
 
@@ -213,11 +209,11 @@
         % pass it into the view fold with closures
         {ok, QueryServer} = couch_query_servers:start_view_list(Lang, ListSrc),
 
-        StartListRespFun = make_map_start_resp_fun(QueryServer, Req, Db, CurrentEtag),
+        StartListRespFun = make_map_start_resp_fun(QueryServer, Db),
         SendListRowFun = make_map_send_row_fun(QueryServer, Req),
 
         FoldAccInit = {Limit, SkipCount, undefined, []},
-        FoldResult = lists:foldl(
+        {ok, FoldResult} = lists:foldl(
             fun(Key, {ok, FoldAcc}) ->
                 FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs#view_query_args{
                         start_key = Key,
@@ -234,7 +230,7 @@
     end).
 
 make_reduce_start_resp_fun(QueryServer, Req, Db, CurrentEtag) ->
-    fun(Req2, _Etag, _, _) ->
+    fun(Req2, _Etag, _Acc) ->
         JsonResp = couch_query_servers:render_reduce_head(QueryServer, 
             Req2, Db),
         JsonResp2 = apply_etag(JsonResp, CurrentEtag),
@@ -258,18 +254,15 @@
                 stop = StopIter,
                 data = RowBody
             } = couch_httpd_external:parse_external_response(JsonResp),
-            RowFront2 = case RowFront of
-            nil -> [];
-            _ -> RowFront
-            end,
             case StopIter of
-            true -> stop;
+            true -> {stop, ""};
             _ ->
-                Chunk = RowFront2 ++ binary_to_list(RowBody),
+                Chunk = RowFront ++ binary_to_list(RowBody),
                 case Chunk of
-                    [] -> {ok, Resp};
+                    [] -> ok;
                     _ -> send_chunk(Resp, Chunk)
-                end
+                end,
+                {ok, ""}
             end
         catch
             throw:Error ->
@@ -307,7 +300,7 @@
                 send_row = SendListRowFun
             }),
         FoldAccInit = {Limit, SkipCount, undefined, []},
-        FoldResult = couch_view:fold_reduce(View, Dir, {StartKey, StartDocId},
+        {ok, FoldResult} = couch_view:fold_reduce(View, Dir, {StartKey, StartDocId},
             {EndKey, EndDocId}, GroupRowsFun, RespFun,
             FoldAccInit),
         finish_list(Req, Db, QueryServer, CurrentEtag, FoldResult, StartListRespFun, null)
@@ -341,7 +334,7 @@
                 send_row = SendListRowFun
             }),
         FoldAccInit = {Limit, SkipCount, undefined, []},
-        FoldResult = lists:foldl(
+        {ok, FoldResult} = lists:foldl(
             fun(Key, {ok, FoldAcc}) ->
                 couch_view:fold_reduce(View, Dir, {Key, StartDocId},
                     {Key, EndDocId}, GroupRowsFun, RespFun, FoldAcc)
@@ -350,29 +343,29 @@
     end).
 
 finish_list(Req, Db, QueryServer, Etag, FoldResult, StartListRespFun, TotalRows) ->
-    case FoldResult of
-    {ok, Acc} ->
-        JsonTail = couch_query_servers:render_list_tail(QueryServer, Req, Db),
-        #extern_resp_args{
-            data = Tail
-        } = couch_httpd_external:parse_external_response(JsonTail),
-        {Resp, BeginBody} = case Acc of
+    {Resp, BeginBody} = case FoldResult of
         {_, _, undefined, _} ->
-            {ok, Resp2, BeginBody2} = StartListRespFun(Req, Etag, TotalRows, null),
+            {ok, Resp2, BeginBody2} = render_head_for_empty_list(StartListRespFun, Req, Etag, TotalRows),
             {Resp2, BeginBody2};
-        {_, _, Resp2, _} ->
-            {Resp2, ""}
-        end,
-        Chunk = BeginBody ++ binary_to_list(Tail),
-        case Chunk of
-            [] -> ok;
-            _ -> send_chunk(Resp, Chunk)
-        end,
-        send_chunk(Resp, []);
-    Error ->
-        throw(Error)
-    end.
-
+        {_, _, Resp0, _} ->
+            {Resp0, ""}
+    end,
+    JsonTail = couch_query_servers:render_list_tail(QueryServer, Req, Db),
+    #extern_resp_args{
+        data = Tail
+    } = couch_httpd_external:parse_external_response(JsonTail),
+    Chunk = BeginBody ++ binary_to_list(Tail),
+    case Chunk of
+        [] -> ok;
+        _ -> send_chunk(Resp, Chunk)
+    end,
+    send_chunk(Resp, []).
+
+render_head_for_empty_list(StartListRespFun, Req, Etag, null) ->
+    StartListRespFun(Req, Etag, []);
+render_head_for_empty_list(StartListRespFun, Req, Etag, TotalRows) ->
+    StartListRespFun(Req, Etag, TotalRows, null, []).
+    
 send_doc_show_response(Lang, ShowSrc, DocId, nil, #httpd{mochi_req=MReq}=Req, Db) ->
     % compute etag with no doc
     Headers = MReq:get(headers),

Modified: couchdb/trunk/src/couchdb/couch_httpd_view.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_view.erl?rev=773319&r1=773318&r2=773319&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd_view.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd_view.erl Sun May 10 09:11:52 2009
@@ -177,91 +177,28 @@
     CurrentEtag = view_group_etag(Group),
     couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
         {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Req, GroupLevel, QueryArgs, CurrentEtag, #reduce_fold_helper_funs{}),
-        {Resp, _} = lists:foldl(
-            fun(Key, {Resp, AccSeparator}) ->
-                FoldAccInit = {Limit, Skip, Resp, AccSeparator},
-                {_, {_, _, Resp2, NewAcc}} = couch_view:fold_reduce(View, Dir, {Key, StartDocId}, 
+        {Resp, _RedAcc3} = lists:foldl(
+            fun(Key, {Resp, RedAcc}) ->
+                % run the reduce once for each key in keys, with limit etc reapplied for each key
+                FoldAccInit = {Limit, Skip, Resp, RedAcc},
+                {_, {_, _, Resp2, RedAcc2}} = couch_view:fold_reduce(View, Dir, {Key, StartDocId}, 
                     {Key, EndDocId}, GroupRowsFun, RespFun, FoldAccInit),
                 % Switch to comma
-                {Resp2, NewAcc}
+                {Resp2, RedAcc2}
             end,
         {undefined, []}, Keys), % Start with no comma
         finish_reduce_fold(Req, Resp)
     end).
-    
-make_reduce_fold_funs(Req, GroupLevel, _QueryArgs, Etag, HelperFuns) ->
-    #reduce_fold_helper_funs{
-        start_response = StartRespFun,
-        send_row = SendRowFun
-    } = apply_default_helper_funs(HelperFuns),
-
-    GroupRowsFun =
-        fun({_Key1,_}, {_Key2,_}) when GroupLevel == 0 ->
-            true;
-        ({Key1,_}, {Key2,_})
-                when is_integer(GroupLevel) and is_list(Key1) and is_list(Key2) ->
-            lists:sublist(Key1, GroupLevel) == lists:sublist(Key2, GroupLevel);
-        ({Key1,_}, {Key2,_}) ->
-            Key1 == Key2
-        end,
-    RespFun = fun(_Key, _Red, {AccLimit, AccSkip, Resp, AccSeparator}) when AccSkip > 0 ->
-        {ok, {AccLimit, AccSkip - 1, Resp, AccSeparator}};
-    (_Key, _Red, {0, 0, Resp, AccSeparator}) ->
-        {stop, {0, 0, Resp, AccSeparator}};
-    (_Key, Red, {AccLimit, 0, Resp, AccSeparator}) when GroupLevel == 0 ->
-        {ok, Resp2, RowSep} = case Resp of
-        undefined -> StartRespFun(Req, Etag, null, null);
-        _ -> {ok, Resp, nil}
-        end,
-        RowResult = case SendRowFun(Resp2, {null, Red}, RowSep) of
-        stop -> stop;
-        _ -> ok
-        end,
-        {RowResult, {AccLimit - 1, 0, Resp2, AccSeparator}};
-    (Key, Red, {AccLimit, 0, Resp, AccSeparator})
-            when is_integer(GroupLevel) 
-            andalso is_list(Key) ->
-        {ok, Resp2, RowSep} = case Resp of
-        undefined -> StartRespFun(Req, Etag, null, null);
-        _ -> {ok, Resp, nil}
-        end,
-        RowResult = case SendRowFun(Resp2, {lists:sublist(Key, GroupLevel), Red}, RowSep) of
-        stop -> stop;
-        _ -> ok
-        end,
-        {RowResult, {AccLimit - 1, 0, Resp2, AccSeparator}};
-    (Key, Red, {AccLimit, 0, Resp, AccSeparator}) ->
-        {ok, Resp2, RowSep} = case Resp of
-        undefined -> StartRespFun(Req, Etag, null, null);
-        _ -> {ok, Resp, nil}
-        end,
-        RowResult = case SendRowFun(Resp2, {Key, Red}, RowSep) of
-        stop -> stop;
-        _ -> ok
-        end,
-        {RowResult, {AccLimit - 1, 0, Resp2, AccSeparator}}
-    end,
-    {ok, GroupRowsFun, RespFun}.
-    
-
 
 reverse_key_default(nil) -> {};
 reverse_key_default({}) -> nil;
 reverse_key_default(Key) -> Key.
 
 get_stale_type(Req) ->
-    QueryList = couch_httpd:qs(Req),
-    case proplists:get_value("stale", QueryList, nil) of
-        "ok" -> ok;
-        Else -> Else
-    end.
+    list_to_atom(couch_httpd:qs_value(Req, "stale", "nil")).
 
 get_reduce_type(Req) ->
-    QueryList = couch_httpd:qs(Req),
-    case proplists:get_value("reduce", QueryList, true) of
-        "false" -> false;
-        _ -> true
-    end.
+    list_to_atom(couch_httpd:qs_value(Req, "reduce", "true")).
 
 parse_view_params(Req, Keys, ViewType, IgnoreType) ->
     QueryList = couch_httpd:qs(Req),
@@ -425,8 +362,7 @@
             Args
     end.
 
-make_view_fold_fun(Req, QueryArgs, Etag, Db,
-    TotalViewCount, HelperFuns) ->
+make_view_fold_fun(Req, QueryArgs, Etag, Db, TotalViewCount, HelperFuns) ->
     #view_query_args{
         end_key = EndKey,
         end_docid = EndDocId,
@@ -446,36 +382,92 @@
         include_docs = IncludeDocs
     } = QueryArgs,
 
-    fun({{Key, DocId}, Value}, OffsetReds,
-                      {AccLimit, AccSkip, Resp, AccRevRows}) ->
+    fun({{Key, DocId}, Value}, OffsetReds, {AccLimit, AccSkip, Resp, RowFunAcc}) ->
         PassedEnd = PassedEndFun(Key, DocId),
         case {PassedEnd, AccLimit, AccSkip, Resp} of
         {true, _, _, _} ->
             % The stop key has been passed, stop looping.
-            {stop, {AccLimit, AccSkip, Resp, AccRevRows}};
+            {stop, {AccLimit, AccSkip, Resp, RowFunAcc}};
         {_, 0, _, _} ->
             % we've done "limit" rows, stop foldling
-            {stop, {0, 0, Resp, AccRevRows}};
+            {stop, {0, 0, Resp, RowFunAcc}};
         {_, _, AccSkip, _} when AccSkip > 0 ->
-            {ok, {AccLimit, AccSkip - 1, Resp, AccRevRows}};
+            % just keep skipping
+            {ok, {AccLimit, AccSkip - 1, Resp, RowFunAcc}};
         {_, _, _, undefined} ->
+            % rendering the first row, first we start the response
             Offset = ReduceCountFun(OffsetReds),
-            {ok, Resp2, BeginBody} = StartRespFun(Req, Etag,
-                TotalViewCount, Offset),
-            case SendRowFun(Resp2, Db, 
-                {{Key, DocId}, Value}, BeginBody, IncludeDocs) of
-            stop ->  {stop, {AccLimit - 1, 0, Resp2, AccRevRows}};
-            _ -> {ok, {AccLimit - 1, 0, Resp2, AccRevRows}}
-            end;
+            {ok, Resp2, RowFunAcc0} = StartRespFun(Req, Etag,
+                TotalViewCount, Offset, RowFunAcc),
+            {Go, RowFunAcc2} = SendRowFun(Resp2, Db, {{Key, DocId}, Value}, 
+                IncludeDocs, RowFunAcc0),
+            {Go, {AccLimit - 1, 0, Resp2, RowFunAcc2}};
         {_, AccLimit, _, Resp} when (AccLimit > 0) ->
-            case SendRowFun(Resp, Db, 
-                {{Key, DocId}, Value}, nil, IncludeDocs) of
-            stop ->  {stop, {AccLimit - 1, 0, Resp, AccRevRows}};
-            _ -> {ok, {AccLimit - 1, 0, Resp, AccRevRows}}
-            end
+            % rendering all other rows
+            {Go, RowFunAcc2} = SendRowFun(Resp, Db, {{Key, DocId}, Value}, 
+                IncludeDocs, RowFunAcc),
+            {Go, {AccLimit - 1, 0, Resp, RowFunAcc2}}
         end
     end.
 
+make_reduce_fold_funs(Req, GroupLevel, _QueryArgs, Etag, HelperFuns) ->
+    #reduce_fold_helper_funs{
+        start_response = StartRespFun,
+        send_row = SendRowFun
+    } = apply_default_helper_funs(HelperFuns),
+
+    GroupRowsFun =
+        fun({_Key1,_}, {_Key2,_}) when GroupLevel == 0 ->
+            true;
+        ({Key1,_}, {Key2,_})
+                when is_integer(GroupLevel) and is_list(Key1) and is_list(Key2) ->
+            lists:sublist(Key1, GroupLevel) == lists:sublist(Key2, GroupLevel);
+        ({Key1,_}, {Key2,_}) ->
+            Key1 == Key2
+        end,
+
+    RespFun = fun
+    (_Key, _Red, {AccLimit, AccSkip, Resp, RowAcc}) when AccSkip > 0 ->
+        % keep skipping
+        {ok, {AccLimit, AccSkip - 1, Resp, RowAcc}};
+    (_Key, _Red, {0, _AccSkip, Resp, RowAcc}) ->
+        % we've exhausted limit rows, stop
+        {stop, {0, _AccSkip, Resp, RowAcc}};
+
+    (_Key, Red, {AccLimit, 0, undefined, RowAcc0}) when GroupLevel == 0 ->
+        % we haven't started responding yet and group=false
+        {ok, Resp2, RowAcc} = StartRespFun(Req, Etag, RowAcc0),
+        {Go, RowAcc2} = SendRowFun(Resp2, {null, Red}, RowAcc),
+        {Go, {AccLimit - 1, 0, Resp2, RowAcc2}};
+    (_Key, Red, {AccLimit, 0, Resp, RowAcc}) when GroupLevel == 0 ->
+        % group=false but we've already started the response
+        {Go, RowAcc2} = SendRowFun(Resp, {null, Red}, RowAcc),
+        {Go, {AccLimit - 1, 0, Resp, RowAcc2}};
+
+    (Key, Red, {AccLimit, 0, undefined, RowAcc0})
+            when is_integer(GroupLevel), is_list(Key) ->
+        % group_level and we haven't responded yet
+        {ok, Resp2, RowAcc} = StartRespFun(Req, Etag, RowAcc0),
+        {Go, RowAcc2} = SendRowFun(Resp2, {lists:sublist(Key, GroupLevel), Red}, RowAcc),    
+        {Go, {AccLimit - 1, 0, Resp2, RowAcc2}};
+    (Key, Red, {AccLimit, 0, Resp, RowAcc})
+            when is_integer(GroupLevel), is_list(Key) ->
+        % group_level and we've already started the response
+        {Go, RowAcc2} = SendRowFun(Resp, {lists:sublist(Key, GroupLevel), Red}, RowAcc),
+        {Go, {AccLimit - 1, 0, Resp, RowAcc2}};
+
+    (Key, Red, {AccLimit, 0, undefined, RowAcc0}) ->
+        % group=true and we haven't responded yet
+        {ok, Resp2, RowAcc} = StartRespFun(Req, Etag, RowAcc0),
+        {Go, RowAcc2} = SendRowFun(Resp2, {Key, Red}, RowAcc),
+        {Go, {AccLimit - 1, 0, Resp2, RowAcc2}};
+    (Key, Red, {AccLimit, 0, Resp, RowAcc}) ->
+        % group=true and we've already started the response
+        {Go, RowAcc2} = SendRowFun(Resp, {Key, Red}, RowAcc),
+        {Go, {AccLimit - 1, 0, Resp, RowAcc2}}
+    end,
+    {ok, GroupRowsFun, RespFun}.
+
 apply_default_helper_funs(#view_fold_helper_funs{
     passed_end = PassedEnd,
     start_response = StartResp,
@@ -487,7 +479,7 @@
     end,
 
     StartResp2 = case StartResp of
-    undefined -> fun json_view_start_resp/4;
+    undefined -> fun json_view_start_resp/5;
     _ -> StartResp
     end,
 
@@ -507,7 +499,7 @@
     send_row = SendRow
 }=Helpers) ->
     StartResp2 = case StartResp of
-    undefined -> fun json_reduce_start_resp/4;
+    undefined -> fun json_reduce_start_resp/3;
     _ -> StartResp
     end,
 
@@ -551,31 +543,24 @@
         end
     end.
 
-json_view_start_resp(Req, Etag, TotalViewCount, Offset) ->
+json_view_start_resp(Req, Etag, TotalViewCount, Offset, _Acc) ->
     {ok, Resp} = start_json_response(Req, 200, [{"Etag", Etag}]),
     BeginBody = io_lib:format("{\"total_rows\":~w,\"offset\":~w,\"rows\":[\r\n",
             [TotalViewCount, Offset]),
     {ok, Resp, BeginBody}.
 
-send_json_view_row(Resp, Db, {{Key, DocId}, Value}, RowFront, IncludeDocs) ->
+send_json_view_row(Resp, Db, {{Key, DocId}, Value}, IncludeDocs, RowFront) ->
     JsonObj = view_row_obj(Db, {{Key, DocId}, Value}, IncludeDocs),
-    RowFront2 = case RowFront of
-    nil -> ",\r\n";
-    _ -> RowFront
-    end,
-    send_chunk(Resp, RowFront2 ++  ?JSON_ENCODE(JsonObj)).
+    send_chunk(Resp, RowFront ++  ?JSON_ENCODE(JsonObj)),
+    {ok, ",\r\n"}.
 
-json_reduce_start_resp(Req, Etag, _, _) ->
+json_reduce_start_resp(Req, Etag, _Acc0) ->
     {ok, Resp} = start_json_response(Req, 200, [{"Etag", Etag}]),
-    BeginBody = "{\"rows\":[\r\n",
-    {ok, Resp, BeginBody}.
+    {ok, Resp, "{\"rows\":[\r\n"}.
 
 send_json_reduce_row(Resp, {Key, Value}, RowFront) ->
-    RowFront2 = case RowFront of
-    nil -> ",\r\n";
-    _ -> RowFront
-    end,
-    send_chunk(Resp, RowFront2 ++ ?JSON_ENCODE({[{key, Key}, {value, Value}]})).    
+    send_chunk(Resp, RowFront ++ ?JSON_ENCODE({[{key, Key}, {value, Value}]})),
+    {ok, ",\r\n"}.    
 
 view_group_etag(Group) ->
     view_group_etag(Group, nil).
@@ -587,41 +572,38 @@
     % track of the last Db seq that caused an index change.
     couch_httpd:make_etag({Sig, CurrentSeq, Extra}).
 
-view_row_obj(Db, {{Key, DocId}, Value}, IncludeDocs) ->
-    case DocId of
-    error ->
-        {[{key, Key}, {error, Value}]};
-    _ ->
-        case IncludeDocs of
-        true ->
-            Rev = case Value of
-            {Props} ->
-                case proplists:get_value(<<"_rev">>, Props) of
-                undefined ->
-                    nil;
-                Rev0 ->
-                    couch_doc:parse_rev(Rev0)
-                end;
-            _ ->
-                nil
-            end,
-            ?LOG_DEBUG("Include Doc: ~p ~p", [DocId, Rev]),
-            case (catch couch_httpd_db:couch_doc_open(Db, DocId, Rev, [])) of
-              {{not_found, missing}, _RevId} ->
-                  {[{id, DocId}, {key, Key}, {value, Value}, {error, missing}]};
-              {not_found, missing} ->
-                  {[{id, DocId}, {key, Key}, {value, Value}, {error, missing}]};
-              {not_found, deleted} ->
-                  {[{id, DocId}, {key, Key}, {value, Value}]};
-              Doc ->
-                JsonDoc = couch_doc:to_json_obj(Doc, []), 
-                {[{id, DocId}, {key, Key}, {value, Value}, {doc, JsonDoc}]}
-            end;
-        _ ->
-            {[{id, DocId}, {key, Key}, {value, Value}]}
-        end
+% the view row has an error
+view_row_obj(_Db, {{Key, error}, Value}, _IncludeDocs) ->
+    {[{key, Key}, {error, Value}]};
+% include docs in the view output
+view_row_obj(Db, {{Key, DocId}, {Props}}, true) ->
+    Rev = case proplists:get_value(<<"_rev">>, Props) of
+    undefined ->
+        nil;
+    Rev0 ->
+        couch_doc:parse_rev(Rev0)
+    end,
+    view_row_with_doc(Db, {{Key, DocId}, {Props}}, Rev);
+view_row_obj(Db, {{Key, DocId}, Value}, true) ->
+    view_row_with_doc(Db, {{Key, DocId}, Value}, nil);
+% the normal case for rendering a view row
+view_row_obj(_Db, {{Key, DocId}, Value}, _IncludeDocs) ->
+    {[{id, DocId}, {key, Key}, {value, Value}]}.
+
+view_row_with_doc(Db, {{Key, DocId}, Value}, Rev) ->
+    ?LOG_DEBUG("Include Doc: ~p ~p", [DocId, Rev]),
+    case (catch couch_httpd_db:couch_doc_open(Db, DocId, Rev, [])) of
+      {{not_found, missing}, _RevId} ->
+          {[{id, DocId}, {key, Key}, {value, Value}, {error, missing}]};
+      {not_found, missing} ->
+          {[{id, DocId}, {key, Key}, {value, Value}, {error, missing}]};
+      {not_found, deleted} ->
+          {[{id, DocId}, {key, Key}, {value, Value}]};
+      Doc ->
+        JsonDoc = couch_doc:to_json_obj(Doc, []), 
+        {[{id, DocId}, {key, Key}, {value, Value}, {doc, JsonDoc}]}
     end.
-
+    
 finish_view_fold(Req, TotalRows, FoldResult) ->
     case FoldResult of
     {ok, {_, _, undefined, _}} ->
@@ -631,9 +613,9 @@
             {total_rows, TotalRows},
             {rows, []}
         ]});
-    {ok, {_, _, Resp, AccRevRows}} ->
+    {ok, {_, _, Resp, _}} ->
         % end the view
-        send_chunk(Resp, AccRevRows ++ "\r\n]}"),
+        send_chunk(Resp, "\r\n]}"),
         end_json_response(Resp);
     Error ->
         throw(Error)