couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jch...@apache.org
Subject svn commit: r736876 - in /couchdb/trunk: etc/couchdb/ share/server/ share/www/script/ src/couchdb/
Date Fri, 23 Jan 2009 00:53:05 GMT
Author: jchris
Date: Thu Jan 22 16:53:05 2009
New Revision: 736876

URL: http://svn.apache.org/viewvc?rev=736876&view=rev
Log:
View list functions can stream views in any format. See list_views test for details.

Modified:
    couchdb/trunk/etc/couchdb/default.ini.tpl.in
    couchdb/trunk/share/server/main.js
    couchdb/trunk/share/www/script/couch_tests.js
    couchdb/trunk/src/couchdb/couch_db.hrl
    couchdb/trunk/src/couchdb/couch_httpd_db.erl
    couchdb/trunk/src/couchdb/couch_httpd_external.erl
    couchdb/trunk/src/couchdb/couch_httpd_show.erl
    couchdb/trunk/src/couchdb/couch_httpd_view.erl
    couchdb/trunk/src/couchdb/couch_query_servers.erl

Modified: couchdb/trunk/etc/couchdb/default.ini.tpl.in
URL: http://svn.apache.org/viewvc/couchdb/trunk/etc/couchdb/default.ini.tpl.in?rev=736876&r1=736875&r2=736876&view=diff
==============================================================================
--- couchdb/trunk/etc/couchdb/default.ini.tpl.in (original)
+++ couchdb/trunk/etc/couchdb/default.ini.tpl.in Thu Jan 22 16:53:05 2009
@@ -50,6 +50,7 @@
 _view = {couch_httpd_view, handle_view_req}
 _slow_view = {couch_httpd_view, handle_slow_view_req}
 _show = {couch_httpd_show, handle_doc_show_req}
+_list = {couch_httpd_show, handle_view_list_req}
 
 ; The external module takes an optional argument allowing you to narrow it to a
 ; single script. Otherwise the script name is inferred from the first path section 

Modified: couchdb/trunk/share/server/main.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/server/main.js?rev=736876&r1=736875&r2=736876&view=diff
==============================================================================
--- couchdb/trunk/share/server/main.js [utf-8] (original)
+++ couchdb/trunk/share/server/main.js [utf-8] Thu Jan 22 16:53:05 2009
@@ -332,28 +332,32 @@
           validateFun(newDoc, oldDoc, userCtx);
           print("1");
         } catch (error) {
-          print(toJSON(error));
+          respond(error);
         }
         break;
       case "show_doc":
         var funSrc = cmd[1];
         var doc = cmd[2];
         var req = cmd[3];
-        try {
-          var formFun = compileFunction(funSrc);
-          var rendered = formFun(doc, req);
-          print(toJSON(rendered));
-        } catch (error) {
-          // Available error fields: 
-          // message, fileName, lineNumber, stack, name
-          log("doc show function raised error: "+error.toString());
-          log("stacktrace: "+error.stack);
-          try {
-            print(toJSON(error));            
-          } catch (e) {
-            print({"error":error.toString()});
-          }
-        }
+        var formFun = compileFunction(funSrc);
+        runRenderFunction(formFun, [doc, req]);
+        break;
+      case "list_begin":
+        var listFun = funs[0];
+        var head = cmd[1];
+        var req = cmd[2];
+        runRenderFunction(listFun, [head, null, req]);
+        break;
+      case "list_row":
+        var listFun = funs[0];
+        var row = cmd[1];
+        var req = cmd[2];
+        runRenderFunction(listFun, [null, row, req]);
+        break;
+      case "list_tail":
+        var listFun = funs[0];
+        var req = cmd[1];
+        runRenderFunction(listFun, [null, null, req]);
         break;
       default:
         print(toJSON({error: "query_server_error",
@@ -365,6 +369,24 @@
   }
 }
 
+function runRenderFunction(renderFun, args) {
+  try {
+    var result = renderFun.apply(null, args);
+    respond(result); 
+  } catch(e) {
+    log("function raised error: "+e.toString());
+    log("stacktrace: "+e.stack);
+  }
+};
+
+// prints the object as JSON, and rescues and logs any toJSON() related errors
+function respond(obj) {
+  try {
+    print(toJSON(obj));    
+  } catch(e) {
+    log("Error converting object to JSON: " + e.toString());
+  }
+}
 
 function compileFunction(source) {
   try {

Modified: couchdb/trunk/share/www/script/couch_tests.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/couch_tests.js?rev=736876&r1=736875&r2=736876&view=diff
==============================================================================
--- couchdb/trunk/share/www/script/couch_tests.js [utf-8] (original)
+++ couchdb/trunk/share/www/script/couch_tests.js [utf-8] Thu Jan 22 16:53:05 2009
@@ -2301,44 +2301,43 @@
      var designDoc = {
        _id:"_design/template",
        language: "javascript",
-       show: {
-         docs: {
-           "hello" : stringFun(function() { 
-             return {
-               body : "Hello World"
-             };
-           }),
-           "just-name" : stringFun(function(doc, req) {
-             return {
-               body : "Just " + doc.name
-             };
-           }),
-           "req-info" : stringFun(function(doc, req) {
-             return {
-               json : req
-             }
-           }),
-           "xml-type" : stringFun(function(doc, req) {
-              return {
-                "headers" : {
-                  "Content-Type" : "application/xml"
-                },
-                "body" : new XML('<xml><node foo="bar"/></xml>')
-              }
-            }),
-           "no-set-etag" : stringFun(function(doc, req) {
+       shows: {
+         "hello" : stringFun(function() { 
+           return {
+             body : "Hello World"
+           };
+         }),
+         "just-name" : stringFun(function(doc, req) {
+           return {
+             body : "Just " + doc.name
+           };
+         }),
+         "req-info" : stringFun(function(doc, req) {
+           return {
+             json : req
+           }
+         }),
+         "xml-type" : stringFun(function(doc, req) {
+            return {
+              "headers" : {
+                "Content-Type" : "application/xml"
+              },
+              "body" : new XML('<xml><node foo="bar"/></xml>')
+            }
+          }),
+         "no-set-etag" : stringFun(function(doc, req) {
+           return {
+             headers : {
+               "Etag" : "skipped"
+             },
+             "body" : "something"
+           }
+         }),
+         "accept-switch" : stringFun(function(doc, req) {
+           if (req.headers["Accept"].match(/image/)) {
              return {
-               headers : {
-                 "Etag" : "skipped"
-               },
-               "body" : "something"
-             }
-           }),
-           "accept-switch" : stringFun(function(doc, req) {
-             if (req.headers["Accept"].match(/image/)) {
-               return {
-                 // a 16x16 px version of the CouchDB logo
-                 "base64" : 
+               // a 16x16 px version of the CouchDB logo
+               "base64" : 
 ["iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAsV",
 "BMVEUAAAD////////////////////////5ur3rEBn////////////////wDBL/",
 "AADuBAe9EB3IEBz/7+//X1/qBQn2AgP/f3/ilpzsDxfpChDtDhXeCA76AQH/v7",
@@ -2348,47 +2347,47 @@
 "zMxw/4OleiJlHeUtv2X6RbNO1Uqj9g0RMCuQO0vBIg4vMFeOpCWIWmDOw82fZx",
 "vaND1c8OG4vrdOqD8YwgpDYDxRgkSm5rwu0nQVBJuMg++pLXZyr5jnc1BaH4GT",
 "LvEliY253nA3pVhQqdPt0f/erJkMGMB8xucAAAAASUVORK5CYII="].join(''),
-                 headers : {
-                   "Content-Type" : "image/png",
-                   "Vary" : "Accept" // we set this for proxy caches
-                 }
+               headers : {
+                 "Content-Type" : "image/png",
+                 "Vary" : "Accept" // we set this for proxy caches
+               }
+             };
+           } else {
+             return {
+               "body" : "accepting text requests",
+               headers : {
+                 "Content-Type" : "text/html",
+                 "Vary" : "Accept"
+               }
+             };
+           }
+         }),
+         "respondWith" : stringFun(function(doc, req) {
+           registerType("foo", "application/foo","application/x-foo");
+           return respondWith(req, {
+             html : function() {
+               return {
+                 body:"Ha ha, you said \"" + doc.word + "\"."
+               };
+             },
+             xml : function() {
+               var xml = new XML('<xml><node/></xml>');
+               // Becase Safari can't stand to see that dastardly
+               // E4X outside of a string. Outside of tests you
+               // can just use E4X literals.
+               this.eval('xml.node.@foo = doc.word');
+               return {
+                 body: xml
                };
-             } else {
+             },
+             foo : function() {
                return {
-                 "body" : "accepting text requests",
-                 headers : {
-                   "Content-Type" : "text/html",
-                   "Vary" : "Accept"
-                 }
+                 body: "foofoo"
                };
-             }
-           }),
-           "respondWith" : stringFun(function(doc, req) {
-             registerType("foo", "application/foo","application/x-foo");
-             return respondWith(req, {
-               html : function() {
-                 return {
-                   body:"Ha ha, you said \"" + doc.word + "\"."
-                 };
-               },
-               xml : function() {
-                 var xml = new XML('<xml><node/></xml>');
-                 // becase Safari can't stand to see that dastardly
-                 // E4X outside of a string.
-                 this.eval('xml.node.@foo = doc.word');
-                 return {
-                   body: xml
-                 };
-               },
-               foo : function() {
-                 return {
-                   body: "foofoo"
-                 };
-               },
-               fallback : "html"
-             });
-           })
-         }
+             },
+             fallback : "html"
+           });
+         })
        }
      };
      T(db.save(designDoc).ok);
@@ -2418,12 +2417,15 @@
      T(resp.error == "not_found");
      T(resp.reason == "missing");
      
+     // show with missing func
+     xhr = CouchDB.request("GET", "/test_suite_db/_show/template/missing/"+docid);
+     T(xhr.status == 404);
+     
      // missing design doc
      xhr = CouchDB.request("GET", "/test_suite_db/_show/missingdoc/just-name/"+docid);
      T(xhr.status == 404);
      var resp = JSON.parse(xhr.responseText);
      T(resp.error == "not_found");
-     T(resp.reason == "missing_design_doc");
      
      // query parameters
      xhr = CouchDB.request("GET", "/test_suite_db/_show/template/req-info/"+docid+"?foo=bar",
{
@@ -2504,7 +2506,7 @@
      T(xhr.status == 304);
      
      // update design doc function
-     designDoc.show.docs["just-name"] = (function(doc, req) {
+     designDoc.shows["just-name"] = (function(doc, req) {
        return {
          body : "Just old " + doc.name
        };
@@ -2553,6 +2555,146 @@
      T(xhr.responseText.match(/foofoo/));
    },
 
+  list_views : function(debug) {
+    var db = new CouchDB("test_suite_db");
+    db.deleteDb();
+    db.createDb();
+    if (debug) debugger;
+    
+    function stringFun(fun) {
+      var string = fun.toSource ? fun.toSource() : "(" + fun.toString() + ")";
+      return string;
+    }
+        
+    var designDoc = {
+      _id:"_design/lists",
+      language: "javascript",
+      views : {
+        basicView : {
+          map : stringFun(function(doc) {
+            emit(doc.integer, doc.string);
+          })
+        },
+        withReduce : {
+          map : stringFun(function(doc) {
+            emit(doc.integer, doc.string);
+          }),
+          reduce : stringFun(function(keys, values, rereduce) {
+            if (rereduce) {
+              return sum(values);
+            } else {
+              return values.length;
+            }
+          })
+        }
+      },
+      lists: {
+        simpleForm: stringFun(function(head, row, req) {
+          if (row) {
+            // we ignore headers on rows and tail
+            return {body : '\n<li>Key: '+row.key+' Value: '+row.value+'</li>'};
+          } else if (head) {
+            // we return an object (like those used by external and show)
+            // so that we can specify headers
+            return {
+              body : '<h1>Total Rows: '
+                + head.total_rows
+                + ' Offset: ' + head.offset
+                + '</h1><ul>'
+            };
+          } else {
+            // tail
+            return {body : '</ul>'};
+          }
+        }),
+        acceptSwitch: stringFun(function(head, row, req) {
+          return respondWith(req, {
+            html : function() {
+              if (head) {
+                return {body : "HTML <ul>"};
+              } else if (row) {
+                return {body : '\n<li>Key: '
+                  +row.key+' Value: '+row.value+'</li>'};
+              } else { // tail
+                return {body : "</ul>"};
+              }
+            },
+            xml : function() {
+              if (head) {
+                return {body:'<feed xmlns="http://www.w3.org/2005/Atom">'
+                  +'<title>Test XML Feed</title>'};
+              } else if (row) {
+                // Becase Safari can't stand to see that dastardly
+                // E4X outside of a string. Outside of tests you
+                // can just use E4X literals.
+                var entry = new XML('<entry/>');
+                entry.id = row.id;
+                entry.title = row.key;
+                entry.content = row.value;
+                return {body:entry};
+              } else {
+                return {body : "</feed>"};
+              }
+            }
+          })
+        })
+      }
+    };
+
+    T(db.save(designDoc).ok);
+    
+    var docs = makeDocs(0, 10);
+    var saveResult = db.bulkSave(docs);
+    T(saveResult.ok);
+    
+    var view = db.view('lists/basicView');
+    T(view.total_rows == 10);
+    
+    // standard get
+    var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/basicView");
+    T(xhr.status == 200);
+    T(/Total Rows/.test(xhr.responseText));
+    T(/Key: 1/.test(xhr.responseText));
+    
+    // get with query params
+    var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/basicView?startkey=3");
+    T(xhr.status == 200);
+    T(/Total Rows/.test(xhr.responseText));
+    T(!(/Key: 1/.test(xhr.responseText)));
+    
+    // with 0 rows
+    var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/basicView?startkey=30");
+    T(xhr.status == 200);
+    T(/Total Rows/.test(xhr.responseText));
+    T(/Offset: null/.test(xhr.responseText));
+    
+    // when there is a reduce present, but not used
+    var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/withReduce?reduce=false");
+    T(xhr.status == 200);
+    T(/Total Rows/.test(xhr.responseText));
+    T(/Key: 1/.test(xhr.responseText));
+    
+    // with accept headers for HTML
+    xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/acceptSwitch/basicView", {
+      headers: {
+        "Accept": 'text/html'
+      }
+    });
+    T(xhr.getResponseHeader("Content-Type") == "text/html");
+    T(xhr.responseText.match(/HTML/));
+    T(xhr.responseText.match(/Value/));
+
+    // now with xml
+    xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/acceptSwitch/basicView", {
+      headers: {
+        "Accept": 'application/xml'
+      }
+    });
+    T(xhr.getResponseHeader("Content-Type") == "application/xml");
+    T(xhr.responseText.match(/XML/));
+    T(xhr.responseText.match(/entry/));
+  },
+
   compact: function(debug) {
     var db = new CouchDB("test_suite_db");
     db.deleteDb();

Modified: couchdb/trunk/src/couchdb/couch_db.hrl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_db.hrl?rev=736876&r1=736875&r2=736876&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_db.hrl (original)
+++ couchdb/trunk/src/couchdb/couch_db.hrl Thu Jan 22 16:53:05 2009
@@ -143,7 +143,6 @@
     end_key = {},
     limit = 10000000000, % a huge huge default number. Picked so we don't have
                          % to do different logic for when there is no limit
-                         % limit
     stale = false,
     direction = fwd,
     start_docid = nil,
@@ -154,6 +153,20 @@
     include_docs = false
 }).
 
+-record(view_fold_helper_funs, {
+    reduce_count,
+    passed_end,
+    start_response,
+    send_row
+}).
+
+-record(extern_resp_args, {
+    code = 200,
+    data = <<>>,
+    ctype = "application/json",
+    headers = []
+}).
+
 -record(group,
     {type=view, % can also be slow_view
     sig=nil,

Modified: couchdb/trunk/src/couchdb/couch_httpd_db.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_db.erl?rev=736876&r1=736875&r2=736876&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd_db.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd_db.erl Thu Jan 22 16:53:05 2009
@@ -192,7 +192,9 @@
     TotalRowCount = proplists:get_value(doc_count, Info),
 
     FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db,
-        TotalRowCount, fun couch_db:enum_docs_since_reduce_to_count/1),
+        TotalRowCount, #view_fold_helper_funs{
+            reduce_count = fun couch_db:enum_docs_since_reduce_to_count/1
+        }),
     StartKey2 = case StartKey of
         nil -> 0;
         <<>> -> 100000000000;
@@ -306,7 +308,10 @@
     case Keys of
     nil ->
         FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db,
-            TotalRowCount, fun couch_db:enum_docs_reduce_to_count/1, PassedEndFun),
+            TotalRowCount, #view_fold_helper_funs{
+                reduce_count = fun couch_db:enum_docs_reduce_to_count/1,
+                passed_end = PassedEndFun
+            }),
         AdapterFun = fun(#full_doc_info{id=Id}=FullDocInfo, Offset, Acc) ->
             case couch_doc:to_doc_info(FullDocInfo) of
             #doc_info{deleted=false, rev=Rev} ->
@@ -320,7 +325,9 @@
         couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult});
     _ ->
         FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db,
-            TotalRowCount, fun(Offset) -> Offset end),
+            TotalRowCount, #view_fold_helper_funs{
+                reduce_count = fun(Offset) -> Offset end
+            }),
         KeyFoldFun = case Dir of
         fwd ->
             fun lists:foldl/3;

Modified: couchdb/trunk/src/couchdb/couch_httpd_external.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_external.erl?rev=736876&r1=736875&r2=736876&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd_external.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd_external.erl Thu Jan 22 16:53:05 2009
@@ -14,18 +14,12 @@
 
 -export([handle_external_req/2, handle_external_req/3]).
 -export([send_external_response/2, json_req_obj/2]).
+-export([default_or_content_type/2, parse_external_response/1]).
 
 -import(couch_httpd,[send_error/4]).
 
 -include("couch_db.hrl").
 
--record(extern_resp_args, {
-    code = 200,
-    data = <<>>,
-    ctype = "application/json",
-    headers = []
-}).
-
 % handle_external_req/2
 % for the old type of config usage:
 % _external = {couch_httpd_external, handle_external_req}
@@ -121,7 +115,10 @@
             {<<"body">>, Value} ->
                 Args#extern_resp_args{data=Value, ctype="text/html"};
             {<<"base64">>, Value} ->
-                Args#extern_resp_args{data=couch_util:decodeBase64(Value), ctype="application/binary"};
+                Args#extern_resp_args{
+                    data=couch_util:decodeBase64(Value),        
+                    ctype="application/binary"
+                };
             {<<"headers">>, {Headers}} ->
                 NewHeaders = lists:map(fun({Header, HVal}) ->
                     {binary_to_list(Header), binary_to_list(HVal)}

Modified: couchdb/trunk/src/couchdb/couch_httpd_show.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_show.erl?rev=736876&r1=736875&r2=736876&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd_show.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd_show.erl Thu Jan 22 16:53:05 2009
@@ -12,7 +12,7 @@
 
 -module(couch_httpd_show).
     
--export([handle_doc_show_req/2]).
+-export([handle_doc_show_req/2, handle_view_list_req/2]).
 
 
 -include("couch_db.hrl").
@@ -22,50 +22,142 @@
     start_json_response/2,send_chunk/2,end_json_response/1,
     start_chunked_response/3, send_error/4]).
     
-handle_doc_show_req(#httpd{method='GET',path_parts=[_, _, DesignName, ShowName, Docid]}=Req,
Db) ->
+handle_doc_show_req(#httpd{
+        method='GET',
+        path_parts=[_, _, DesignName, ShowName, Docid]
+    }=Req, Db) ->
     DesignId = <<"_design/", DesignName/binary>>,
-    % Anyway we can dry up this error handling?
-    case (catch couch_httpd_db:couch_doc_open(Db, DesignId, [], [])) of
-    {not_found, missing} ->
-        throw({not_found, missing_design_doc});
-    {not_found, deleted} ->
-        throw({not_found, deleted_design_doc});
-    DesignDoc ->
-        #doc{body={Props}} = DesignDoc,
-        Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),
-        case proplists:get_value(<<"show">>, Props, nil) of
-        {DocAndViews} ->
-            case proplists:get_value(<<"docs">>, DocAndViews, nil) of 
-            nil ->
-                throw({not_found, missing_show_docs});
-            {DocShows} ->
-                case proplists:get_value(ShowName, DocShows, nil) of
-                nil ->
-                    throw({not_found, missing_show_doc_function});
-                ShowSrc ->
-                    case (catch couch_httpd_db:couch_doc_open(
-                        Db, Docid, [], [])) of
-                    {not_found, missing} ->
-                        throw({not_found, missing});
-                    {not_found, deleted} ->
-                        throw({not_found, deleted});
-                    Doc ->
-                        % ok we have everythign we need. let's make it happen.
-                        send_doc_show_response(Lang, ShowSrc, Doc, Req, Db)
-                    end
-                end
-            end;
-        nil ->
-            throw({not_found, missing_show})
-        end
-    end;
+    #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, [], []),
+    Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),
+    ShowSrc = get_nested_json_value({Props}, [<<"shows">>, ShowName]),
+    Doc = couch_httpd_db:couch_doc_open(Db, Docid, [], []),
+    send_doc_show_response(Lang, ShowSrc, Doc, Req, Db);
 
 handle_doc_show_req(#httpd{method='GET'}=Req, _Db) ->
-    send_error(Req, 404, <<"form_error">>, <<"Invalid path.">>);
+    send_error(Req, 404, <<"show_error">>, <<"Invalid path.">>);
 
 handle_doc_show_req(Req, _Db) ->
     send_method_not_allowed(Req, "GET,HEAD").
 
+handle_view_list_req(#httpd{method='GET',path_parts=[_, _, DesignName, ListName, ViewName]}=Req,
Db) ->
+    DesignId = <<"_design/", DesignName/binary>>,
+    #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, [], []),
+    Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),
+    ListSrc = get_nested_json_value({Props}, [<<"lists">>, ListName]),
+    send_view_list_response(Lang, ListSrc, ViewName, DesignId, Req, Db);
+
+handle_view_list_req(Req, _Db) ->
+    send_method_not_allowed(Req, "GET,HEAD").
+
+
+get_nested_json_value({Props}, [Key|Keys]) ->
+    case proplists:get_value(Key, Props, nil) of
+    nil -> throw({not_found, <<"missing json key: ", Key/binary>>});
+    Value -> get_nested_json_value(Value, Keys)
+    end;
+get_nested_json_value(Value, []) ->
+    Value;
+get_nested_json_value(_NotJSONObj, _) ->
+    throw({not_found, json_mismatch}).
+
+send_view_list_response(Lang, ListSrc, ViewName, DesignId, Req, Db) ->
+    % TODO add etags when we get view etags
+    #view_query_args{
+        stale = Stale,
+        reduce = Reduce
+    } = QueryArgs = couch_httpd_view:parse_view_query(Req),
+    case couch_view:get_map_view(Db, DesignId, ViewName, Stale) of
+    {ok, View} ->    
+        output_map_list(Req, Lang, ListSrc, View, Db, QueryArgs);
+    {not_found, _Reason} ->
+        case couch_view:get_reduce_view(Db, DesignId, ViewName, Stale) of
+        {ok, ReduceView} ->
+            case Reduce of
+            false ->
+                MapView = couch_view:extract_map_view(ReduceView),
+                output_map_list(Req, Lang, ListSrc, MapView, Db, QueryArgs);
+            _ ->
+                throw({not_implemented, reduce_view_lists})
+            end;
+        {not_found, Reason} ->
+            throw({not_found, Reason})
+        end
+    end.
+    
+output_map_list(Req, Lang, ListSrc, View, Db, QueryArgs) ->
+    #view_query_args{
+        limit = Limit,
+        direction = Dir,
+        skip = SkipCount,
+        start_key = StartKey,
+        start_docid = StartDocId
+    } = QueryArgs,
+    {ok, RowCount} = couch_view:get_row_count(View),
+    Start = {StartKey, StartDocId},
+    % get the os process here
+    % pass it into the view fold with closures
+    {ok, QueryServer} = couch_query_servers:start_view_list(Lang, ListSrc),
+
+    StartListRespFun = fun(Req2, Code, TotalViewCount, Offset) ->
+        JsonResp = couch_query_servers:render_list_head(QueryServer, 
+            Req2, Db, TotalViewCount, Offset),
+        #extern_resp_args{
+            code = Code,
+            data = BeginBody,
+            ctype = CType,
+            headers = Headers
+        } = couch_httpd_external:parse_external_response(JsonResp),
+        JsonHeaders = couch_httpd_external:default_or_content_type(CType, Headers),
+        {ok, Resp} = start_chunked_response(Req, Code, JsonHeaders),
+        {ok, Resp, binary_to_list(BeginBody)}
+    end,
+    
+    SendListRowFun = fun(Resp, Db2, {{Key, DocId}, Value}, 
+        RowFront, _IncludeDocs) ->
+        JsonResp = couch_query_servers:render_list_row(QueryServer, 
+            Req, Db2, {{Key, DocId}, Value}),
+        #extern_resp_args{
+            data = RowBody
+        } = couch_httpd_external:parse_external_response(JsonResp),
+        RowFront2 = case RowFront of
+        nil -> [];
+        _ -> RowFront
+        end,
+        send_chunk(Resp, RowFront2 ++ binary_to_list(RowBody))
+    end,
+    
+    FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db, RowCount,
+        #view_fold_helper_funs{
+            reduce_count = fun couch_view:reduce_to_count/1,
+            start_response = StartListRespFun,
+            send_row = SendListRowFun
+        }),
+    FoldAccInit = {Limit, SkipCount, undefined, []},
+    FoldResult = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
+    finish_view_list(Req, Db, QueryServer, RowCount, FoldResult, StartListRespFun).
+
+finish_view_list(Req, Db, QueryServer, TotalRows, 
+    FoldResult, StartListRespFun) ->
+    case FoldResult of
+    {ok, {_, _, undefined, _}} ->
+        {ok, Resp, BeginBody} = StartListRespFun(Req, 200, TotalRows, null),
+        JsonTail = couch_query_servers:render_list_tail(QueryServer, Req, Db),
+        #extern_resp_args{
+            data = Tail
+        } = couch_httpd_external:parse_external_response(JsonTail),
+        send_chunk(Resp, BeginBody ++ Tail),
+        send_chunk(Resp, []);
+    {ok, {_, _, Resp, _AccRevRows}} ->
+        JsonTail = couch_query_servers:render_list_tail(QueryServer, Req, Db),
+        #extern_resp_args{
+            data = Tail
+        } = couch_httpd_external:parse_external_response(JsonTail),
+        send_chunk(Resp, Tail),
+        send_chunk(Resp, []);
+    Error ->
+        throw(Error)
+    end.
+
 
 send_doc_show_response(Lang, ShowSrc, #doc{revs=[DocRev|_]}=Doc, #httpd{mochi_req=MReq}=Req,
Db) ->
     % make a term with etag-effecting Req components, but not always changing ones.

Modified: couchdb/trunk/src/couchdb/couch_httpd_view.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_view.erl?rev=736876&r1=736875&r2=736876&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd_view.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd_view.erl Thu Jan 22 16:53:05 2009
@@ -15,7 +15,8 @@
 
 -export([handle_view_req/2,handle_slow_view_req/2]).
 
--export([parse_view_query/1,parse_view_query/2,make_view_fold_fun/5, make_view_fold_fun/6,finish_view_fold/3]).
+-export([parse_view_query/1,parse_view_query/2,make_view_fold_fun/5,
+    finish_view_fold/3, view_row_obj/3]).
 
 -import(couch_httpd,
     [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,
@@ -92,8 +93,7 @@
     } = QueryArgs,
     {ok, RowCount} = couch_view:get_row_count(View),
     Start = {StartKey, StartDocId},
-    FoldlFun = make_view_fold_fun(Req, QueryArgs, Db, RowCount,
-            fun couch_view:reduce_to_count/1),
+    FoldlFun = make_view_fold_fun(Req, QueryArgs, Db, RowCount, #view_fold_helper_funs{reduce_count=fun
couch_view:reduce_to_count/1}),
     FoldAccInit = {Limit, SkipCount, undefined, []},
     FoldResult = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
     finish_view_fold(Req, RowCount, FoldResult);
@@ -114,7 +114,10 @@
                 QueryArgs#view_query_args{
                     start_key = Key,
                     end_key = Key
-                }, Db, RowCount, fun couch_view:reduce_to_count/1),
+                }, Db, RowCount, 
+                #view_fold_helper_funs{
+                    reduce_count = fun couch_view:reduce_to_count/1
+                }),
             couch_view:fold(View, Start, Dir, FoldlFun, FoldAcc)
         end, {ok, FoldAccInit}, Keys),
     finish_view_fold(Req, RowCount, FoldResult).
@@ -362,28 +365,21 @@
         end
     end.
 
-
 make_view_fold_fun(Req, QueryArgs, Db,
-    TotalViewCount, ReduceCountFun) ->
+    TotalViewCount, HelperFuns) ->
     #view_query_args{
         end_key = EndKey,
         end_docid = EndDocId,
         direction = Dir
     } = QueryArgs,
-    PassedEndFun =
-    case Dir of
-    fwd ->
-        fun(ViewKey, ViewId) ->
-            couch_view:less_json([EndKey, EndDocId], [ViewKey, ViewId])
-        end;
-    rev->
-        fun(ViewKey, ViewId) ->
-            couch_view:less_json([ViewKey, ViewId], [EndKey, EndDocId])
-        end
-    end,
-    make_view_fold_fun(Req, QueryArgs, Db, TotalViewCount, ReduceCountFun, PassedEndFun).
+    
+    #view_fold_helper_funs{
+        passed_end = PassedEndFun,
+        start_response = StartRespFun,
+        send_row = SendRowFun,
+        reduce_count = ReduceCountFun
+    } = apply_default_helper_funs(HelperFuns, {Dir, EndKey, EndDocId}),
 
-make_view_fold_fun(Req, QueryArgs, Db, TotalViewCount, ReduceCountFun, PassedEndFun) ->
     #view_query_args{
         include_docs = IncludeDocs
     } = QueryArgs,
@@ -401,20 +397,72 @@
         {_, _, AccSkip, _} when AccSkip > 0 ->
             {ok, {AccLimit, AccSkip - 1, Resp, AccRevRows}};
         {_, _, _, undefined} ->
-            {ok, Resp2} = start_json_response(Req, 200),
             Offset = ReduceCountFun(OffsetReds),
-            JsonBegin = io_lib:format("{\"total_rows\":~w,\"offset\":~w,\"rows\":[\r\n",
-                    [TotalViewCount, Offset]),
-            JsonObj = view_row_obj(Db, {{Key, DocId}, Value}, IncludeDocs),
-            send_chunk(Resp2, JsonBegin ++ ?JSON_ENCODE(JsonObj)),
+            {ok, Resp2, BeginBody} = StartRespFun(Req, 200, 
+                TotalViewCount, Offset),
+            SendRowFun(Resp2, Db, 
+                {{Key, DocId}, Value}, BeginBody, IncludeDocs),
             {ok, {AccLimit - 1, 0, Resp2, AccRevRows}};
         {_, AccLimit, _, Resp} when (AccLimit > 0) ->
-            JsonObj = view_row_obj(Db, {{Key, DocId}, Value}, IncludeDocs),
-            send_chunk(Resp, ",\r\n" ++  ?JSON_ENCODE(JsonObj)),
+            SendRowFun(Resp, Db, 
+                {{Key, DocId}, Value}, nil, IncludeDocs),
             {ok, {AccLimit - 1, 0, Resp, AccRevRows}}
         end
     end.
 
+apply_default_helper_funs(#view_fold_helper_funs{
+    passed_end = PassedEnd,
+    start_response = StartResp,
+    send_row = SendRow
+}=Helpers, {Dir, EndKey, EndDocId}) ->
+    PassedEnd2 = case PassedEnd of
+    undefined -> make_passed_end_fun(Dir, EndKey, EndDocId);
+    _ -> PassedEnd
+    end,
+
+    StartResp2 = case StartResp of
+    undefined -> fun json_view_start_resp/4;
+    _ -> StartResp
+    end,
+
+    SendRow2 = case SendRow of
+    undefined -> fun send_json_view_row/5;
+    _ -> SendRow
+    end,
+
+    Helpers#view_fold_helper_funs{
+        passed_end = PassedEnd2,
+        start_response = StartResp2,
+        send_row = SendRow2
+    }.
+
+make_passed_end_fun(Dir, EndKey, EndDocId) ->
+    case Dir of
+    fwd ->
+        fun(ViewKey, ViewId) ->
+            couch_view:less_json([EndKey, EndDocId], [ViewKey, ViewId])
+        end;
+    rev->
+        fun(ViewKey, ViewId) ->
+            couch_view:less_json([ViewKey, ViewId], [EndKey, EndDocId])
+        end
+    end.
+
+json_view_start_resp(Req, Code, TotalViewCount, Offset) ->
+    {ok, Resp} = couch_httpd:start_json_response(Req, Code),
+    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) ->
+    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)).
+    
+
 view_row_obj(Db, {{Key, DocId}, Value}, IncludeDocs) ->
     case DocId of
     error ->

Modified: couchdb/trunk/src/couchdb/couch_query_servers.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_query_servers.erl?rev=736876&r1=736875&r2=736876&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_query_servers.erl (original)
+++ couchdb/trunk/src/couchdb/couch_query_servers.erl Thu Jan 22 16:53:05 2009
@@ -17,7 +17,8 @@
 
 -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,render_doc_show/5]).
+-export([reduce/3, rereduce/3,validate_doc_update/5]).
+-export([render_doc_show/5,start_view_list/2,render_list_head/5, render_list_row/4, render_list_tail/3]).
 % -export([test/0]).
 
 -include("couch_db.hrl").
@@ -134,6 +135,29 @@
         ok = ret_os_process(Lang, Pid)
     end.
 
+start_view_list(Lang, ListSrc) ->
+    Pid = get_os_process(Lang),
+    true = couch_os_process:prompt(Pid, [<<"add_fun">>, ListSrc]),
+    {ok, {Lang, Pid}}.
+
+render_list_head({_Lang, Pid}, Req, Db, TotalRows, Offset) ->
+    Head = {[{<<"total_rows">>, TotalRows}, {<<"offset">>, Offset}]},
+    JsonReq = couch_httpd_external:json_req_obj(Req, Db),
+    couch_os_process:prompt(Pid, [<<"list_begin">>, Head, JsonReq]).
+
+render_list_row({_Lang, Pid}, Req, Db, {{Key, DocId}, Value}) ->
+    JsonRow = couch_httpd_view:view_row_obj(Db, {{Key, DocId}, Value}, false),
+    JsonReq = couch_httpd_external:json_req_obj(Req, Db),
+    couch_os_process:prompt(Pid, [<<"list_row">>, JsonRow, JsonReq]).
+
+render_list_tail({Lang, Pid}, Req, Db) ->
+    JsonReq = couch_httpd_external:json_req_obj(Req, Db),
+    JsonResp = couch_os_process:prompt(Pid, [<<"list_tail">>, JsonReq]),
+    ok = ret_os_process(Lang, Pid),
+    JsonResp.
+    
+    
+
 init([]) ->
     
     % read config and register for configuration changes



Mime
View raw message