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/06/14 20:45:49 UTC

svn commit: r784601 - in /couchdb/trunk: ./ etc/default/ share/server/ share/www/script/test/ src/couchdb/ test/

Author: jchris
Date: Sun Jun 14 18:45:49 2009
New Revision: 784601

URL: http://svn.apache.org/viewvc?rev=784601&view=rev
Log:
merge list-iterator branch to trunk. changes JavaScript _list API

Modified:
    couchdb/trunk/   (props changed)
    couchdb/trunk/.gitignore
    couchdb/trunk/etc/default/couchdb   (props changed)
    couchdb/trunk/share/server/loop.js
    couchdb/trunk/share/server/render.js
    couchdb/trunk/share/server/util.js
    couchdb/trunk/share/www/script/test/list_views.js
    couchdb/trunk/share/www/script/test/show_documents.js
    couchdb/trunk/src/couchdb/couch_httpd.erl
    couchdb/trunk/src/couchdb/couch_httpd_show.erl
    couchdb/trunk/src/couchdb/couch_js.c
    couchdb/trunk/src/couchdb/couch_os_process.erl
    couchdb/trunk/src/couchdb/couch_query_servers.erl
    couchdb/trunk/test/query_server_spec.rb

Propchange: couchdb/trunk/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Sun Jun 14 18:45:49 2009
@@ -1,4 +1,5 @@
 /couchdb/branches/0.9.x:775634
 /couchdb/branches/design_resources:751716-751803
 /couchdb/branches/form:729440-730015
+/couchdb/branches/list-iterator:782292-784593
 /couchdb/branches/tail_header:775760-778477

Modified: couchdb/trunk/.gitignore
URL: http://svn.apache.org/viewvc/couchdb/trunk/.gitignore?rev=784601&r1=784600&r2=784601&view=diff
==============================================================================
--- couchdb/trunk/.gitignore (original)
+++ couchdb/trunk/.gitignore Sun Jun 14 18:45:49 2009
@@ -10,6 +10,7 @@
 configure
 autom4te.cache
 build-aux
+*.diff
 
 # ./configure
 

Propchange: couchdb/trunk/etc/default/couchdb
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Sun Jun 14 18:45:49 2009
@@ -1,5 +1,6 @@
 /couchdb/branches/0.9.x/etc/default/couchdb:775634
 /couchdb/branches/design_resources/etc/default/couchdb:751716-751803
 /couchdb/branches/form/etc/default/couchdb:729440-730015
+/couchdb/branches/list-iterator/etc/default/couchdb:782292-784593
 /couchdb/branches/tail_header/etc/default/couchdb:775760-778477
 /incubator/couchdb/trunk/etc/default/couchdb:642419-694440

Modified: couchdb/trunk/share/server/loop.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/server/loop.js?rev=784601&r1=784600&r2=784601&view=diff
==============================================================================
--- couchdb/trunk/share/server/loop.js (original)
+++ couchdb/trunk/share/server/loop.js Sun Jun 14 18:45:49 2009
@@ -21,6 +21,9 @@
   sandbox.toJSON = toJSON;
   sandbox.respondWith = respondWith;
   sandbox.registerType = registerType;
+  sandbox.start = start;
+  sandbox.send = send;
+  sandbox.getRow = getRow;
 } catch (e) {}
 
 // Commands are in the form of json arrays:
@@ -31,21 +34,19 @@
 var line, cmd, cmdkey;
 
 var dispatch = {
-  "reset"      : State.reset,
-  "add_fun"    : State.addFun,
-  "map_doc"    : Views.mapDoc,
-  "reduce"     : Views.reduce,
-  "rereduce"   : Views.rereduce,
-  "validate"   : Validate.validate,
-  "show_doc"   : Render.showDoc,
-  "list_begin" : Render.listBegin,
-  "list_row"   : Render.listRow,
-  "list_tail"  : Render.listTail 
+  "reset"    : State.reset,
+  "add_fun"  : State.addFun,
+  "map_doc"  : Views.mapDoc,
+  "reduce"   : Views.reduce,
+  "rereduce" : Views.rereduce,
+  "validate" : Validate.validate,
+  "show"     : Render.show,
+  "list"     : Render.list
 };
 
 while (line = eval(readline())) {
-  cmd = eval(line)
-  line_length = line.length
+  cmd = eval(line);
+  line_length = line.length;
   try {
     cmdkey = cmd.shift();
     if (dispatch[cmdkey]) {

Modified: couchdb/trunk/share/server/render.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/server/render.js?rev=784601&r1=784600&r2=784601&view=diff
==============================================================================
--- couchdb/trunk/share/server/render.js (original)
+++ couchdb/trunk/share/server/render.js Sun Jun 14 18:45:49 2009
@@ -12,9 +12,10 @@
 
 // mimeparse.js
 // http://code.google.com/p/mimeparse/
+// MIT Licensed http://www.opensource.org/licenses/mit-license.php
 // Code with comments: http://mimeparse.googlecode.com/svn/trunk/mimeparse.js
 // Tests: http://mimeparse.googlecode.com/svn/trunk/mimeparse-js-test.html
-// Ported from version 0.1.2
+// Ported by Chris Anderson from version 0.1.2
 
 var Mimeparse = (function() {
   function strip(string) {
@@ -111,6 +112,8 @@
   return publicMethods;
 })();
 
+var respCT;
+var respTail;
 // this function provides a shortcut for managing responses by Accept header
 respondWith = function(req, responders) {
   var bestKey = null, accept = req.headers["Accept"];
@@ -127,11 +130,16 @@
     bestKey = req.query.format;
   }
   var rFunc = responders[bestKey || responders.fallback || "html"];
-  if (rFunc) {      
-    var resp = maybeWrapResponse(rFunc());
-    resp["headers"] = resp["headers"] || {};
-    resp["headers"]["Content-Type"] = bestMime;
-    respond(resp);
+  if (rFunc) {    
+    if (isShow) {
+      var resp = maybeWrapResponse(rFunc());
+      resp["headers"] = resp["headers"] || {};
+      resp["headers"]["Content-Type"] = bestMime;
+      respond(["resp", resp]);
+    } else {
+      respCT = bestMime;
+      respTail = rFunc();
+    }
   } else {
     throw({code:406, body:"Not Acceptable: "+accept});    
   }
@@ -162,8 +170,6 @@
 registerType("html", "text/html");
 registerType("xhtml", "application/xhtml+xml", "xhtml");
 registerType("xml", "application/xml", "text/xml", "application/x-xml");
-// http://www.ietf.org/rfc/rfc4627.txt
-registerType("json", "application/json", "text/x-json");
 registerType("js", "text/javascript", "application/javascript", "application/x-javascript");
 registerType("css", "text/css");
 registerType("ics", "text/calendar");
@@ -171,58 +177,149 @@
 registerType("rss", "application/rss+xml");
 registerType("atom", "application/atom+xml");
 registerType("yaml", "application/x-yaml", "text/yaml");
+
 // just like Rails
 registerType("multipart_form", "multipart/form-data");
 registerType("url_encoded_form", "application/x-www-form-urlencoded");
 
+// http://www.ietf.org/rfc/rfc4627.txt
+registerType("json", "application/json", "text/x-json");
+
+
 
+
+//  Start chunks
+var startResp = {};
+function start(resp) {
+  startResp = resp || {};
+};
+
+function sendStart(label) {
+  startResp = startResp || {};
+  startResp["headers"] = startResp["headers"] || {};
+  startResp["headers"]["Content-Type"] = startResp["headers"]["Content-Type"] || respCT;
+  
+  respond(["start", chunks, startResp]);
+  chunks = [];
+  startResp = {};
+}
+//  Send chunk
+var chunks = [];
+function send(chunk) {
+  chunks.push(chunk.toString());
+};
+
+function blowChunks(label) {
+  respond([label||"chunks", chunks]);
+  chunks = [];
+};
+
+var gotRow = false, lastRow = false;
+function getRow() {
+  if (lastRow) return null;
+  if (!gotRow) {
+    gotRow = true;
+    sendStart();
+  } else {
+    blowChunks()  
+  }
+  var line = readline();
+  var json = eval(line);
+  if (json[0] == "list_end") {
+    lastRow = true
+    return null;
+  }
+  if (json[0] != "list_row") {
+    respond({
+      error: "query_server_error",
+      reason: "not a row '" + json[0] + "'"});
+    quit();
+  }
+  return json[1];
+};
+
+////
+////  Render dispatcher
+////
+////
+////
+////
+var isShow = false;
 var Render = (function() {
   var row_info;
+  
   return {
-    showDoc : function(funSrc, doc, req) {
+    show : function(funSrc, doc, req) {
+      isShow = true;
       var formFun = compileFunction(funSrc);
-      runRenderFunction(formFun, [doc, req], funSrc);
-    },
-    listBegin : function(head, req) {
-      row_info = { first_key: null, row_number: 0, prev_key: null };
-      runRenderFunction(funs[0], [head, null, req, null], funsrc[0]);
-    },
-    listRow : function(row, req) {
-      if (row_info.first_key == null) {
-        row_info.first_key = row.key;
-      }
-      runRenderFunction(funs[0], [null, row, req, row_info], funsrc[0], true);
-      row_info.prev_key = row.key;
-      row_info.row_number++;
+      runShowRenderFunction(formFun, [doc, req], funSrc, true);
     },
-    listTail : function(req) {
-      runRenderFunction(funs[0], [null, null, req, row_info], funsrc[0]);
+    list : function(head, req) {
+      isShow = false;
+      runListRenderFunction(funs[0], [head, req], funsrc[0], false);
     }
   }
 })();
 
-function runRenderFunction(renderFun, args, funSrc, htmlErrors) {
-  responseSent = false;
+function maybeWrapResponse(resp) {
+  var type = typeof resp;
+  if ((type == "string") || (type == "xml")) {
+    return {body:resp};
+  } else {
+    return resp;
+  }
+};
+
+function runShowRenderFunction(renderFun, args, funSrc, htmlErrors) {
   try {
     var resp = renderFun.apply(null, args);
-    if (!responseSent) {
-      if (resp) {
-        respond(maybeWrapResponse(resp));       
-      } else {
-        respond({error:"render_error",reason:"undefined response from render function"});
-      }      
+    if (resp) {
+      respond(["resp", maybeWrapResponse(resp)]);
+    } else {
+      renderError("undefined response from render function");
     }
   } catch(e) {
-    var logMessage = "function raised error: "+e.toString();
-    log(logMessage);
-    // log("stacktrace: "+e.stack);
-    var errorMessage = htmlErrors ? htmlRenderError(e, funSrc) : logMessage;
-    respond({
-      error:"render_error",
-      reason:errorMessage});
+    respondError(e, funSrc, htmlErrors);
+  }
+};
+function runListRenderFunction(renderFun, args, funSrc, htmlErrors) {
+  try {
+    gotRow = false;
+    lastRow = false;
+    respTail = "";
+    if (renderFun.arity > 2) {
+      throw("the list API has changed for CouchDB 0.10, please upgrade your code");
+    }
+    var resp = renderFun.apply(null, args);
+    if (!gotRow) {
+      getRow();
+    }
+    if (typeof resp != "undefined") {
+      chunks.push(resp);      
+    } else if (respTail) {
+      chunks.push(respTail);      
+    }
+    blowChunks("end");      
+  } catch(e) {
+    respondError(e, funSrc, htmlErrors);
   }
 };
 
+function renderError(m) {
+  respond({error : "render_error", reason : m});
+}
+
+
+function respondError(e, funSrc, htmlErrors) {
+  var logMessage = "function raised error: "+e.toString();
+  log(logMessage);
+  log("stacktrace: "+e.stack);
+  var errorMessage = htmlErrors ? htmlRenderError(e, funSrc) : logMessage;
+  respond({
+    error:"render_error",
+    reason:errorMessage});
+}
+
 function escapeHTML(string) {
   return string.replace(/&/g, "&")
                .replace(/</g, "&lt;")
@@ -241,11 +338,3 @@
   return {body:msg};
 };
 
-function maybeWrapResponse(resp) {
-  var type = typeof resp;
-  if ((type == "string") || (type == "xml")) {
-    return {body:resp};
-  } else {
-    return resp;
-  }
-};

Modified: couchdb/trunk/share/server/util.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/server/util.js?rev=784601&r1=784600&r2=784601&view=diff
==============================================================================
--- couchdb/trunk/share/server/util.js (original)
+++ couchdb/trunk/share/server/util.js Sun Jun 14 18:45:49 2009
@@ -91,10 +91,8 @@
   }
 }
 
-var responseSent;
 // prints the object as JSON, and rescues and logs any toJSON() related errors
 function respond(obj) {
-  responseSent = true;
   try {
     print(toJSON(obj));  
   } catch(e) {
@@ -103,10 +101,11 @@
 };
 
 log = function(message) {
+  // return;
   if (typeof message == "undefined") {
     message = "Error: attempting to log message of 'undefined'.";
   } else if (typeof message != "string") {
     message = toJSON(message);
   }
-  print(toJSON({log: message}));
+  respond(["log", message]);
 };

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=784601&r1=784600&r2=784601&view=diff
==============================================================================
--- couchdb/trunk/share/www/script/test/list_views.js (original)
+++ couchdb/trunk/share/www/script/test/list_views.js Sun Jun 14 18:45:49 2009
@@ -39,106 +39,127 @@
       }
     },
     lists: {
-      simpleForm: stringFun(function(head, row, req, row_info) {
-        if (row) {
-          // we ignore headers on rows and tail
-          return {
-                  body : '\n<li>Key: '+row.key
-                  +' Value: '+row.value
-                  +' LineNo: '+row_info.row_number+'</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>'+
-              '<p>FirstKey: '+(row_info ? row_info.first_key : '')+ 
-              ' LastKey: '+(row_info ? row_info.prev_key : '')+'</p>'};
+      basicBasic : stringFun(function(head, req) {
+        send("head");
+        var row;
+        while(row = getRow()) {
+          log("row: "+toJSON(row));
+          send(row.key);        
+        };
+        return "tail";
+      }),
+      basicJSON : stringFun(function(head, req) {
+        start({"headers":{"Content-Type" : "application/json"}}); 
+        send('{"head":'+toJSON(head)+', ');
+        send('"req":'+toJSON(req)+', ');
+        send('"rows":[');
+        var row, sep = '';
+        while (row = getRow()) {
+          send(sep + toJSON(row));
+          sep = ', ';
+        }
+        return "]}";
+      }),
+      simpleForm: stringFun(function(head, req) {
+        log("simpleForm");
+        send('<h1>Total Rows: '
+              // + head.total_rows
+              // + ' Offset: ' + head.offset
+              + '</h1><ul>');
+
+        // rows
+        var row, row_number = 0, prevKey, firstKey = null;
+        while (row = getRow()) {
+          row_number += 1;
+          if (!firstKey) firstKey = row.key;
+          prevKey = row.key;
+          send('\n<li>Key: '+row.key
+          +' Value: '+row.value
+          +' LineNo: '+row_number+'</li>');
         }
+
+        // tail
+        return '</ul><p>FirstKey: '+ firstKey + ' LastKey: '+ prevKey+'</p>';
       }),
-      acceptSwitch: stringFun(function(head, row, req, row_info) {
-        return respondWith(req, {
+      acceptSwitch: stringFun(function(head, req) {
+        // respondWith takes care of setting the proper headers
+        respondWith(req, {
           html : function() {
-            // If you're outputting text and you're not setting
-            // any headers, you can just return a string.
-            if (head) {
-              return "HTML <ul>";
-            } else if (row) {
-              return '\n<li>Key: '
-                +row.key+' Value: '+row.value
-                +' LineNo: '+row_info.row_number+'</li>';
-            } else { // tail
-              return '</ul>';
+            send("HTML <ul>");
 
+            var row, num = 0;
+            while (row = getRow()) {
+              num ++;
+              send('\n<li>Key: '
+                +row.key+' Value: '+row.value
+                +' LineNo: '+num+'</li>');
             }
+
+            // tail
+            return '</ul>';
           },
           xml : function() {
-            if (head) {
-              return '<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.
+            send('<feed xmlns="http://www.w3.org/2005/Atom">'
+              +'<title>Test XML Feed</title>');
+
+            while (row = getRow()) {
               var entry = new XML('<entry/>');
               entry.id = row.id;
               entry.title = row.key;
               entry.content = row.value;
-              // We'll also let you return just an E4X object
-              // if you aren't setting headers.
-              return entry;
-            } else {
-              return "</feed>";
+              send(entry);
             }
+            return "</feed>";
           }
-        })
+        });
       }),
-      qsParams: stringFun(function(head, row, req, row_info) {
-        if(head) return {body: req.query.foo};
-        else return {body: "\n"};
-      }),
-      stopIter: stringFun(function(head, row, req, row_info) {
-        if(head) {
-          return {body: "head"};
-        } else if(row) {
-          if(row_info.row_number > 2) return {stop: true};
-          return {body: " " + row_info.row_number};
-        } else {
-          return {body: " tail"};
-        }
+      qsParams: stringFun(function(head, req) {
+        return toJSON(req.query) + "\n";
       }),
-      stopIter2: stringFun(function(head, row, req, row_info) {
-        return respondWith(req, {
+      stopIter: stringFun(function(req) {
+        send("head");
+        var row, row_number = 0;
+        while(row = getRow()) {
+          if(row_number > 2) break;
+          send(" " + row_number);
+          row_number += 1;
+        };
+        return " tail";
+      }),
+      stopIter2: stringFun(function(head, req) {
+        respondWith(req, {
           html: function() {
-            if(head) {
-              return "head";
-            } else if(row) {
-              if(row_info.row_number > 2) return {stop: true};
-              return " " + row_info.row_number;
-            } else {
-              return " tail";
-            }
+            send("head");
+            var row, row_number = 0;
+            while(row = getRow()) {
+              if(row_number > 2) break;
+              send(" " + row_number);
+              row_number += 1;
+            };
+            return " tail";
           }
         });
       }),
-      emptyList: stringFun(function(head, row, req, row_info) {
-        return { body: "" };
+      tooManyGetRows : stringFun(function() {
+        send("head");
+        var row;
+        while(row = getRow()) {
+          send(row.key);        
+        };
+        getRow();
+        getRow();
+        getRow();
+        row = getRow();
+        return "after row: "+toJSON(row);
       }),
-      rowError : stringFun(function(head, row, req, row_info) {
-        if (head) {
-          return "head";
-        } else if(row) {
-          return missingValue;
-        } else {
-          return "tail"
-        }
+      emptyList: stringFun(function() {
+        return " ";
+      }),
+      rowError : stringFun(function(head, req) {
+        send("head");
+        var row = getRow();
+        send(fooBarBam); // intentional error
+        return "tail";
       })
     }
   };
@@ -152,26 +173,40 @@
   T(view.total_rows == 10);
   
   // standard get
-  var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/basicView");
+  var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/basicBasic/basicView");
   T(xhr.status == 200, "standard get should be 200");
-  T(/Total Rows/.test(xhr.responseText));
-  T(/Key: 1/.test(xhr.responseText));
-  T(/LineNo: 0/.test(xhr.responseText));
-  T(/LineNo: 5/.test(xhr.responseText));
-  T(/FirstKey: 0/.test(xhr.responseText));
-  T(/LastKey: 9/.test(xhr.responseText));
-
-
-  var lines = xhr.responseText.split('\n');
-  T(/LineNo: 5/.test(lines[6]));
+  T(/head0123456789tail/.test(xhr.responseText));
 
   // test that etags are available
   var etag = xhr.getResponseHeader("etag");
-  xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/basicView", {
+  xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/basicBasic/basicView", {
     headers: {"if-none-match": etag}
   });
   T(xhr.status == 304);
 
+  // test the richness of the arguments
+  xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/basicJSON/basicView");
+  T(xhr.status == 200, "standard get should be 200");
+  var resp = JSON.parse(xhr.responseText);
+  TEquals(resp.head, {total_rows:10, offset:0});
+  T(resp.rows.length == 10);
+  TEquals(resp.rows[0], {"id": "0","key": 0,"value": "0"});
+
+  TEquals(resp.req.info.db_name, "test_suite_db");
+  TEquals(resp.req.verb, "GET");
+  TEquals(resp.req.path, [
+      "test_suite_db",
+      "_design",
+      "lists",
+      "_list",
+      "basicJSON",
+      "basicView"
+  ]);
+  T(resp.req.headers.Accept);
+  T(resp.req.headers.Host);
+  T(resp.req.headers["User-Agent"]);
+  T(resp.req.cookie);
+
   // get with query params
   var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/basicView?startkey=3");
   T(xhr.status == 200, "with query params");
@@ -179,26 +214,30 @@
   T(!(/Key: 1/.test(xhr.responseText)));
   T(/FirstKey: 3/.test(xhr.responseText));
   T(/LastKey: 9/.test(xhr.responseText));
-
   
   // with 0 rows
   var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/basicView?startkey=30");
   T(xhr.status == 200, "0 rows");
   T(/Total Rows/.test(xhr.responseText));
-  T(/Offset: null/.test(xhr.responseText));
+
+  //too many Get Rows
+  var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/tooManyGetRows/basicView");
+  T(xhr.status == 200, "tooManyGetRows");
+  T(/9after row: null/.test(xhr.responseText));
+
 
   // reduce with 0 rows
   var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/withReduce?startkey=30");
   T(xhr.status == 200, "reduce 0 rows");
   T(/Total Rows/.test(xhr.responseText));
-  T(/Offset: undefined/.test(xhr.responseText));
-
+  T(/LastKey: undefined/.test(xhr.responseText));
   
   // when there is a reduce present, but not used
   var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/withReduce?reduce=false");
   T(xhr.status == 200, "reduce false");
   T(/Total Rows/.test(xhr.responseText));
   T(/Key: 1/.test(xhr.responseText));
+
   
   // when there is a reduce present, and used
   xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/withReduce?group=true");
@@ -221,48 +260,12 @@
     headers: {"if-none-match": etag}
   });
   T(xhr.status == 200, "reduce etag");
-  
-  // with accept headers for HTML
-  xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/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/_design/lists/_list/acceptSwitch/basicView", {
-    headers: {
-      "Accept": 'application/xml'
-    }
-  });
-  T(xhr.getResponseHeader("Content-Type") == "application/xml");
-  T(xhr.responseText.match(/XML/));
-  T(xhr.responseText.match(/entry/));
-
-  // now with extra qs params
-  var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/qsParams/basicView?foo=blam");
-  T(xhr.responseText.match(/blam/));
-  
-  // aborting iteration
-  var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter/basicView");
-  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$/) && "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$/) && "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$/) && "reduce stop 2");
 
   // empty list
   var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/emptyList/basicView");
-  T(xhr.responseText.match(/^$/));
+  T(xhr.responseText.match(/^ $/));
   xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/emptyList/withReduce?group=true");
-  T(xhr.responseText.match(/^$/));
+  T(xhr.responseText.match(/^ $/));
 
   // multi-key fetch
   var xhr = CouchDB.request("POST", "/test_suite_db/_design/lists/_list/simpleForm/basicView", {
@@ -281,7 +284,45 @@
   });
   T(xhr.status == 400);
   T(/query_parse_error/.test(xhr.responseText));
-  
+    
   var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/rowError/basicView");
-  T(/<h1>Render Error<\/h1>/.test(xhr.responseText));
+  T(/ReferenceError/.test(xhr.responseText));
+
+
+  // now with extra qs params
+  var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/qsParams/basicView?foo=blam");
+  T(xhr.responseText.match(/blam/));
+  
+  var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter/basicView");
+  // T(xhr.getResponseHeader("Content-Type") == "text/plain");
+  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$/) && "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$/) && "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$/) && "reduce stop 2");
+  
+  // with accept headers for HTML
+  xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/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/_design/lists/_list/acceptSwitch/basicView", {
+    headers: {
+      "Accept": 'application/xml'
+    }
+  });
+  T(xhr.getResponseHeader("Content-Type") == "application/xml");
+  T(xhr.responseText.match(/XML/));
+  T(xhr.responseText.match(/entry/));
 };

Modified: couchdb/trunk/share/www/script/test/show_documents.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/test/show_documents.js?rev=784601&r1=784600&r2=784601&view=diff
==============================================================================
--- couchdb/trunk/share/www/script/test/show_documents.js (original)
+++ couchdb/trunk/share/www/script/test/show_documents.js Sun Jun 14 18:45:49 2009
@@ -106,9 +106,7 @@
         registerType("foo", "application/foo","application/x-foo");
         return respondWith(req, {
           html : function() {
-            return {
-              body:"Ha ha, you said \"" + doc.word + "\"."
-            };
+            return "Ha ha, you said \"" + doc.word + "\".";
           },
           xml : function() {
             var xml = new XML('<xml><node/></xml>');
@@ -145,10 +143,14 @@
   // hello template world
   xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/hello/"+docid);
   T(xhr.responseText == "Hello World");
-
-  // error stacktraces
-  xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/render-error/"+docid);
-  T(JSON.parse(xhr.responseText).error == "render_error");
+// 
+// };
+// 
+// function foo() {
+
+  // // error stacktraces
+  // xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/render-error/"+docid);
+  // T(JSON.parse(xhr.responseText).error == "render_error");
  
   // hello template world (no docid)
   xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/hello");

Modified: couchdb/trunk/src/couchdb/couch_httpd.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd.erl?rev=784601&r1=784600&r2=784601&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd.erl Sun Jun 14 18:45:49 2009
@@ -177,6 +177,14 @@
             % ?LOG_DEBUG("Minor error in HTTP request: ~p",[Error]),
             % ?LOG_DEBUG("Stacktrace: ~p",[erlang:get_stacktrace()]),
             send_error(HttpReq, Error);
+        error:badarg ->
+            ?LOG_ERROR("Badarg error in HTTP request",[]),
+            ?LOG_INFO("Stacktrace: ~p",[erlang:get_stacktrace()]),
+            send_error(HttpReq, badarg);
+        error:function_clause ->
+            ?LOG_ERROR("function_clause error in HTTP request",[]),
+            ?LOG_INFO("Stacktrace: ~p",[erlang:get_stacktrace()]),
+            send_error(HttpReq, function_clause);
         Tag:Error ->
             ?LOG_ERROR("Uncaught error in HTTP request: ~p",[{Tag, Error}]),
             ?LOG_INFO("Stacktrace: ~p",[erlang:get_stacktrace()]),

Modified: couchdb/trunk/src/couchdb/couch_httpd_show.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_show.erl?rev=784601&r1=784600&r2=784601&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd_show.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd_show.erl Sun Jun 14 18:45:49 2009
@@ -117,47 +117,6 @@
         end
     end.
 
-make_map_start_resp_fun(QueryServer, Db) ->
-    fun(Req, CurrentEtag, TotalViewCount, Offset, _Acc) ->
-        ExternalResp = couch_query_servers:render_list_head(QueryServer, 
-            Req, Db, TotalViewCount, Offset),
-        JsonResp = apply_etag(ExternalResp, CurrentEtag),
-        #extern_resp_args{
-            code = Code,
-            data = BeginBody,
-            ctype = CType,
-            headers = ExtHeaders
-        } = couch_httpd_external:parse_external_response(JsonResp),
-        JsonHeaders = couch_httpd_external:default_or_content_type(CType, ExtHeaders),
-        {ok, Resp} = start_chunked_response(Req, Code, JsonHeaders),
-        {ok, Resp, binary_to_list(BeginBody)}
-    end.
-
-make_map_send_row_fun(QueryServer, Req) ->
-    fun(Resp, Db2, {{Key, DocId}, Value}, _IncludeDocs, RowFront) ->
-        try
-            JsonResp = couch_query_servers:render_list_row(QueryServer, 
-                Req, Db2, {{Key, DocId}, Value}),
-            #extern_resp_args{
-                stop = StopIter,
-                data = RowBody
-            } = couch_httpd_external:parse_external_response(JsonResp),
-            case StopIter of
-            true -> {stop, ""};
-            _ ->
-                Chunk = RowFront ++ binary_to_list(RowBody),
-                case Chunk of
-                    [] -> ok;
-                    _ -> send_chunk(Resp, Chunk)
-                end,
-                {ok, ""}
-            end
-        catch
-            throw:Error ->
-                send_chunked_error(Resp, Error),
-                throw({already_sent, Resp, Error})
-        end
-    end.
 
 output_map_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, QueryArgs, nil) ->
     #view_query_args{
@@ -179,7 +138,7 @@
         {ok, QueryServer} = couch_query_servers:start_view_list(Lang, ListSrc),
 
         StartListRespFun = make_map_start_resp_fun(QueryServer, Db),
-        SendListRowFun = make_map_send_row_fun(QueryServer, Req),
+        SendListRowFun = make_map_send_row_fun(QueryServer),
     
         FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, RowCount,
             #view_fold_helper_funs{
@@ -189,7 +148,7 @@
             }),
         FoldAccInit = {Limit, SkipCount, undefined, []},
         {ok, FoldResult} = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
-        finish_list(Req, Db, QueryServer, CurrentEtag, FoldResult, StartListRespFun, RowCount)
+        finish_list(Req, QueryServer, CurrentEtag, FoldResult, StartListRespFun, RowCount)
     end);
 
 output_map_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, QueryArgs, Keys) ->
@@ -210,7 +169,7 @@
         {ok, QueryServer} = couch_query_servers:start_view_list(Lang, ListSrc),
 
         StartListRespFun = make_map_start_resp_fun(QueryServer, Db),
-        SendListRowFun = make_map_send_row_fun(QueryServer, Req),
+        SendListRowFun = make_map_send_row_fun(QueryServer),
 
         FoldAccInit = {Limit, SkipCount, undefined, []},
         {ok, FoldResult} = lists:foldl(
@@ -226,49 +185,64 @@
                     }),
                 couch_view:fold(View, {Key, StartDocId}, Dir, FoldlFun, FoldAcc)
             end, {ok, FoldAccInit}, Keys),
-        finish_list(Req, Db, QueryServer, CurrentEtag, FoldResult, StartListRespFun, RowCount)
+        finish_list(Req, QueryServer, CurrentEtag, FoldResult, StartListRespFun, RowCount)
     end).
 
-make_reduce_start_resp_fun(QueryServer, Req, Db, CurrentEtag) ->
-    fun(Req2, _Etag, _Acc) ->
-        JsonResp = couch_query_servers:render_reduce_head(QueryServer, 
-            Req2, Db),
-        JsonResp2 = apply_etag(JsonResp, CurrentEtag),
-        #extern_resp_args{
-            code = Code,
-            data = BeginBody,
-            ctype = CType,
-            headers = ExtHeaders
-        } = couch_httpd_external:parse_external_response(JsonResp2),
-        JsonHeaders = couch_httpd_external:default_or_content_type(CType, ExtHeaders),
-        {ok, Resp} = start_chunked_response(Req, Code, JsonHeaders),
-        {ok, Resp, binary_to_list(BeginBody)}
+make_map_start_resp_fun(QueryServer, Db) ->
+    fun(Req, Etag, TotalRows, Offset, _Acc) ->
+        Head = {[{<<"total_rows">>, TotalRows}, {<<"offset">>, Offset}]},
+        start_list_resp(QueryServer, Req, Db, Head, Etag)
+    end.
+
+make_reduce_start_resp_fun(QueryServer, _Req, Db, _CurrentEtag) ->
+    fun(Req2, Etag, _Acc) ->
+        start_list_resp(QueryServer, Req2, Db, {[]}, Etag)
+    end.
+
+start_list_resp(QueryServer, Req, Db, Head, Etag) ->
+    [<<"start">>,Chunks,JsonResp] = couch_query_servers:render_list_head(QueryServer, 
+        Req, Db, Head),
+    JsonResp2 = apply_etag(JsonResp, Etag),
+    #extern_resp_args{
+        code = Code,
+        ctype = CType,
+        headers = ExtHeaders
+    } = couch_httpd_external:parse_external_response(JsonResp2),
+    JsonHeaders = couch_httpd_external:default_or_content_type(CType, ExtHeaders),
+    {ok, Resp} = start_chunked_response(Req, Code, JsonHeaders),
+    {ok, Resp, ?b2l(?l2b(Chunks))}.
+
+make_map_send_row_fun(QueryServer) ->
+    fun(Resp, Db, Row, _IncludeDocs, RowFront) ->
+        send_list_row(Resp, QueryServer, Db, Row, RowFront)
+    end.
+
+make_reduce_send_row_fun(QueryServer, Db) ->
+    fun(Resp, Row, RowFront) ->
+        send_list_row(Resp, QueryServer, Db, Row, RowFront)
     end.
 
-make_reduce_send_row_fun(QueryServer, Req, Db) ->
-    fun(Resp, {Key, Value}, RowFront) ->
-        try
-            JsonResp = couch_query_servers:render_reduce_row(QueryServer, 
-                Req, Db, {Key, Value}),
-            #extern_resp_args{
-                stop = StopIter,
-                data = RowBody
-            } = couch_httpd_external:parse_external_response(JsonResp),
-            case StopIter of
-            true -> {stop, ""};
-            _ ->
-                Chunk = RowFront ++ binary_to_list(RowBody),
-                case Chunk of
-                    [] -> ok;
-                    _ -> send_chunk(Resp, Chunk)
-                end,
-                {ok, ""}
-            end
-        catch
-            throw:Error ->
-                send_chunked_error(Resp, Error),
-                throw({already_sent, Resp, Error})
+send_list_row(Resp, QueryServer, Db, Row, RowFront) ->
+    try
+        [Go,Chunks] = couch_query_servers:render_list_row(QueryServer, Db, Row),
+        Chunk = RowFront ++ ?b2l(?l2b(Chunks)),
+        send_non_empty_chunk(Resp, Chunk),
+        case Go of
+            <<"chunks">> ->
+                {ok, ""};
+            <<"end">> ->
+                {stop, stop}
         end
+    catch
+        throw:Error ->
+            send_chunked_error(Resp, Error),
+            throw({already_sent, Resp, Error})
+    end.
+
+send_non_empty_chunk(Resp, Chunk) ->
+    case Chunk of
+        [] -> ok;
+        _ -> send_chunk(Resp, Chunk)
     end.
 
 output_reduce_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, QueryArgs, nil) ->
@@ -291,7 +265,7 @@
     CurrentEtag = couch_httpd_view:view_group_etag(Group, {Lang, ListSrc, Accept}),
     couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
         StartListRespFun = make_reduce_start_resp_fun(QueryServer, Req, Db, CurrentEtag),
-        SendListRowFun = make_reduce_send_row_fun(QueryServer, Req, Db),
+        SendListRowFun = make_reduce_send_row_fun(QueryServer, Db),
     
         {ok, GroupRowsFun, RespFun} = couch_httpd_view:make_reduce_fold_funs(Req, 
             GroupLevel, QueryArgs, CurrentEtag, 
@@ -303,7 +277,7 @@
         {ok, FoldResult} = couch_view:fold_reduce(View, Dir, {StartKey, StartDocId},
             {EndKey, EndDocId}, GroupRowsFun, RespFun,
             FoldAccInit),
-        finish_list(Req, Db, QueryServer, CurrentEtag, FoldResult, StartListRespFun, null)
+        finish_list(Req, QueryServer, CurrentEtag, FoldResult, StartListRespFun, null)
     end);
 
 output_reduce_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, QueryArgs, Keys) ->
@@ -325,7 +299,7 @@
 
     couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
         StartListRespFun = make_reduce_start_resp_fun(QueryServer, Req, Db, CurrentEtag),
-        SendListRowFun = make_reduce_send_row_fun(QueryServer, Req, Db),
+        SendListRowFun = make_reduce_send_row_fun(QueryServer, Db),
     
         {ok, GroupRowsFun, RespFun} = couch_httpd_view:make_reduce_fold_funs(Req,
             GroupLevel, QueryArgs, CurrentEtag, 
@@ -339,33 +313,30 @@
                 couch_view:fold_reduce(View, Dir, {Key, StartDocId},
                     {Key, EndDocId}, GroupRowsFun, RespFun, FoldAcc)
             end, {ok, FoldAccInit}, Keys),
-        finish_list(Req, Db, QueryServer, CurrentEtag, FoldResult, StartListRespFun, null)
+        finish_list(Req, QueryServer, CurrentEtag, FoldResult, StartListRespFun, null)
     end).
 
-finish_list(Req, Db, QueryServer, Etag, FoldResult, StartListRespFun, TotalRows) ->
-    {Resp, BeginBody} = case FoldResult of
+finish_list(Req, QueryServer, Etag, FoldResult, StartFun, TotalRows) ->
+    case FoldResult of
         {_, _, undefined, _} ->
-            {ok, Resp2, BeginBody2} = render_head_for_empty_list(StartListRespFun, Req, Etag, TotalRows),
-            {Resp2, BeginBody2};
-        {_, _, 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)
+            {ok, Resp, BeginBody} = 
+                render_head_for_empty_list(StartFun, Req, Etag, TotalRows),
+            [<<"end">>, Chunks] = couch_query_servers:render_list_tail(QueryServer),
+            Chunk = BeginBody ++ ?b2l(?l2b(Chunks)),
+            send_non_empty_chunk(Resp, Chunk);
+        {_, _, Resp, stop} ->
+            ok;
+        {_, _, Resp, _} ->
+            [<<"end">>, Chunks] = couch_query_servers:render_list_tail(QueryServer),
+            send_non_empty_chunk(Resp, ?b2l(?l2b(Chunks)))
     end,
     send_chunk(Resp, []).
 
 render_head_for_empty_list(StartListRespFun, Req, Etag, null) ->
-    StartListRespFun(Req, Etag, []);
+    StartListRespFun(Req, Etag, []); % for reduce
 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),
@@ -373,7 +344,7 @@
     Accept = proplists:get_value('Accept', Hlist),
     CurrentEtag = couch_httpd:make_etag({Lang, ShowSrc, nil, Accept}),
     couch_httpd:etag_respond(Req, CurrentEtag, fun() -> 
-        ExternalResp = couch_query_servers:render_doc_show(Lang, ShowSrc, 
+        [<<"resp">>, ExternalResp] = couch_query_servers:render_doc_show(Lang, ShowSrc, 
             DocId, nil, Req, Db),
         JsonResp = apply_etag(ExternalResp, CurrentEtag),
         couch_httpd_external:send_external_response(Req, JsonResp)
@@ -387,7 +358,7 @@
     CurrentEtag = couch_httpd:make_etag({Lang, ShowSrc, Revs, Accept}),
     % We know our etag now    
     couch_httpd:etag_respond(Req, CurrentEtag, fun() -> 
-        ExternalResp = couch_query_servers:render_doc_show(Lang, ShowSrc, 
+        [<<"resp">>, ExternalResp] = couch_query_servers:render_doc_show(Lang, ShowSrc, 
             DocId, Doc, Req, Db),
         JsonResp = apply_etag(ExternalResp, CurrentEtag),
         couch_httpd_external:send_external_response(Req, JsonResp)

Modified: couchdb/trunk/src/couchdb/couch_js.c
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_js.c?rev=784601&r1=784600&r2=784601&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_js.c (original)
+++ couchdb/trunk/src/couchdb/couch_js.c Sun Jun 14 18:45:49 2009
@@ -247,13 +247,13 @@
 
 static JSBool
 Print(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
-    uintN i, n;
+    uintN i;
     size_t cl, bl;
     JSString *str;
     jschar *chars;
     char *bytes;
 
-    for (i = n = 0; i < argc; i++) {
+    for (i = 0; i < argc; i++) {
         str = JS_ValueToString(context, argv[i]);
         if (!str)
             return JS_FALSE;
@@ -270,9 +270,8 @@
         fprintf(stdout, "%s%s", i ? " " : "", bytes);
         JS_free(context, bytes);
     }
-    n++;
-    if (n)
-        fputc('\n', stdout);
+
+    fputc('\n', stdout);
     fflush(stdout);
     return JS_TRUE;
 }

Modified: couchdb/trunk/src/couchdb/couch_os_process.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_os_process.erl?rev=784601&r1=784600&r2=784601&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_os_process.erl (original)
+++ couchdb/trunk/src/couchdb/couch_os_process.erl Sun Jun 14 18:45:49 2009
@@ -53,7 +53,7 @@
         {ok, Result} ->
             Result;
         Error ->
-            ?LOG_DEBUG("OS Process Error ~p",[Error]),
+            ?LOG_ERROR("OS Process Error :: ~p",[Error]),
             throw(Error)
     end.
 
@@ -81,20 +81,22 @@
 
 % 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)).
 
 readjson(OsProc) when is_record(OsProc, os_proc) ->
     Line = readline(OsProc),
     case ?JSON_DECODE(Line) of
-    {[{<<"log">>,Msg}]} when is_binary(Msg) ->
+    [<<"log">>, Msg] when is_binary(Msg) ->
         % we got a message to log. Log it and continue
-        ?LOG_INFO("OS Process Log Message: ~s", [Msg]),
+        ?LOG_INFO("OS Process :: ~s", [Msg]),
         readjson(OsProc);
     {[{<<"error">>, Id}, {<<"reason">>, Reason}]} ->
         throw({list_to_atom(binary_to_list(Id)),Reason});
     {[{<<"reason">>, Reason}, {<<"error">>, Id}]} ->
         throw({list_to_atom(binary_to_list(Id)),Reason});
     Result ->
+        % ?LOG_DEBUG("OS Process Output :: ~p", [Result]),
         Result
     end.
 

Modified: couchdb/trunk/src/couchdb/couch_query_servers.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_query_servers.erl?rev=784601&r1=784600&r2=784601&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_query_servers.erl (original)
+++ couchdb/trunk/src/couchdb/couch_query_servers.erl Sun Jun 14 18:45:49 2009
@@ -18,9 +18,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]).
--export([render_doc_show/6,start_view_list/2,render_list_head/5, 
-        render_list_row/4, render_list_tail/3, render_reduce_head/3, 
-        render_reduce_row/4]).
+-export([render_doc_show/6, start_view_list/2, 
+        render_list_head/4, render_list_row/3, render_list_tail/1]).
 % -export([test/0]).
 
 -include("couch_db.hrl").
@@ -183,7 +182,7 @@
         _ -> {{append_docid(DocId, JsonReqIn)}, couch_doc:to_json_obj(Doc, [revs])}
     end,
     try couch_os_process:prompt(Pid, 
-        [<<"show_doc">>, ShowSrc, JsonDoc, JsonReq]) of
+        [<<"show">>, ShowSrc, JsonDoc, JsonReq]) of
     FormResp ->
         FormResp
     after
@@ -195,32 +194,24 @@
     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}]},
+render_list_head({_Lang, Pid}, Req, Db, Head) ->
     JsonReq = couch_httpd_external:json_req_obj(Req, Db),
-    couch_os_process:prompt(Pid, [<<"list_begin">>, Head, JsonReq]).
+    couch_os_process:prompt(Pid, [<<"list">>, Head, JsonReq]).
 
-render_list_row({_Lang, Pid}, Req, Db, {{Key, DocId}, Value}) ->
+render_list_row({_Lang, Pid}, 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]).
+    couch_os_process:prompt(Pid, [<<"list_row">>, JsonRow]);
 
-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]),
+render_list_row({_Lang, Pid}, _, {Key, Value}) ->
+    JsonRow = {[{key, Key}, {value, Value}]},
+    couch_os_process:prompt(Pid, [<<"list_row">>, JsonRow]).
+
+render_list_tail({Lang, Pid}) ->
+    JsonResp = couch_os_process:prompt(Pid, [<<"list_end">>]),
     ok = ret_os_process(Lang, Pid),
-    JsonResp.
+    JsonResp.    
     
-    
-render_reduce_head({_Lang, Pid}, Req, Db) ->
-    Head = {[]},
-    JsonReq = couch_httpd_external:json_req_obj(Req, Db),
-    couch_os_process:prompt(Pid, [<<"list_begin">>, Head, JsonReq]).
 
-render_reduce_row({_Lang, Pid}, Req, Db, {Key, Value}) ->
-    JsonRow = {[{key, Key}, {value, Value}]},
-    JsonReq = couch_httpd_external:json_req_obj(Req, Db),
-    couch_os_process:prompt(Pid, [<<"list_row">>, JsonRow, JsonReq]).
 
 
 init([]) ->

Modified: couchdb/trunk/test/query_server_spec.rb
URL: http://svn.apache.org/viewvc/couchdb/trunk/test/query_server_spec.rb?rev=784601&r1=784600&r2=784601&view=diff
==============================================================================
--- couchdb/trunk/test/query_server_spec.rb (original)
+++ couchdb/trunk/test/query_server_spec.rb Sun Jun 14 18:45:49 2009
@@ -22,31 +22,6 @@
 require 'json'
 
 
-JSON_REQ = {
-  "body"=>"undefined", 
-  "verb"=>"GET", 
-  "info"=>{
-    "disk_format_version"=>2, 
-    "purge_seq"=>0, 
-    "doc_count"=>9082, 
-    "instance_start_time"=>"1243713611467271", 
-    "update_seq"=>9512, 
-    "disk_size"=>27541604, 
-    "compact_running"=>false, 
-    "db_name"=>"toast", 
-    "doc_del_count"=>1
-  }, 
-  "cookie"=>{}, 
-  "form"=>{}, 
-  "query"=>{"q"=>"stuff"}, 
-  "path"=>["toast", "_ext"], 
-  "headers"=>{
-    "User-Agent"=>"curl/7.18.1 (i386-apple-darwin9.2.2) libcurl/7.18.1 zlib/1.2.3", 
-    "Host"=>"localhost:5984", 
-    "Accept"=>"*/*"
-  }
-}
-
 class OSProcessRunner
   def self.run
     trace = false
@@ -78,9 +53,9 @@
   def add_fun(fun)
     run(["add_fun", fun])
   end
-  def get_chunk
+  def get_chunks
     resp = jsgets
-    raise "not a chunk" unless resp.first == "chunk"
+    raise "not a chunk" unless resp.first == "chunks"
     return resp[1]
   end
   def run json
@@ -103,10 +78,10 @@
     # puts "err: #{err}" if err
     if resp
       rj = JSON.parse("[#{resp.chomp}]")[0]
-      if rj.respond_to?(:[]) && !rj.is_a?(Array) && 
-        if rj["log"]
-          log = rj["log"]
-          puts "log: #{log}" #if @trace
+      if rj.respond_to?(:[]) && rj.is_a?(Array)
+        if rj[0] == "log"
+          log = rj[1]
+          puts "log: #{log}" if @trace
           rj = jsgets
         end
       end
@@ -177,131 +152,300 @@
       @qs.run(["rereduce", [@fun], vs]).should == [true, [45]]
     end
   end
+  
   # it "should validate"
+  describe "validation" do
+    before(:all) do
+      @fun = <<-JS
+        function(newDoc, oldDoc, userCtx) {
+          if (newDoc.bad) throw({forbidden:"bad doc"});
+          "foo bar";
+        }
+        JS
+      @qs.reset!
+    end
+    it "should allow good updates" do
+      @qs.run(["validate", @fun, {"good" => true}, {}, {}]).should == 1
+    end
+    it "should reject invalid updates" do
+      @qs.run(["validate", @fun, {"bad" => true}, {}, {}]).should == {"forbidden"=>"bad doc"}
+    end
+  end
   
   describe "show" do
-     before(:all) do
-       @fun = <<-JS
-         function(doc, req) {
-           return [doc.title, doc.body].join(' - ')
-         }
-         JS
-       @qs.reset!
-     end
-     it "should show" do
-       @qs.rrun(["show_doc", @fun, 
-         {:title => "Best ever", :body => "Doc body"}, JSON_REQ])
-       @qs.jsgets.should == {"body"=>"Best ever - Doc body"}
-     end
-   end
-   
-   describe "show with headers" do
-     before(:all) do
-       @fun = <<-JS
-         function(doc, req) {
-           return {
-             headers : {"X-Plankton":"Rusty"},
-             body : [doc.title, doc.body].join(' - ')
-           }
-           
-         }
-         JS
-       @qs.reset!
-     end
-     it "should show" do
-       @qs.rrun(["show_doc", @fun, 
-         {:title => "Best ever", :body => "Doc body"}])
-       @qs.jsgets.should == {"headers"=>{"X-Plankton"=>"Rusty"}, "body"=>"Best ever - Doc body"}
-     end
-   end
-     
-   describe "list with headers" do
-     before(:each) do
-       @fun = <<-JS
-         function(head, row, req) {
-           if (head) return {headers : {"Content-Type" : "text/plain"}, code : 200, "body" : "foo"};
-           if (row) return 'some "text" here';
-           return "tail";
-         };
-         JS
-       @qs.reset!
-       @qs.add_fun(@fun).should == true
-     end
-     it "should send head, row, and tail" do
-       @qs.rrun(["list_begin", {"total_rows"=>1000}, {"q" => "ok"}])
-       @qs.jsgets.should == {"headers"=>{"Content-Type"=>"text/plain"}, "code"=>200, "body"=>"foo"}
-       @qs.run(["list_row", {"foo"=>"bar"}, {"q" => "ok"}]).should == {"body"=>"some \"text\" here"}
-       @qs.run(["list_tail", {"foo"=>"bar"}, {"q" => "ok"}]).should == {"body"=>"tail"}
-     end
-   end
-   
-   describe "list with headers and rows" do
-     before(:each) do
-       @fun = <<-JS
-         function(head, row, req) {
-           if (head) return {headers : {"Content-Type" : "text/plain"}, code : 200, "body" : "foo"};
-           if (row) return 'row value '+row.value;
-           return "tail "+req.q;
-         };
-         JS
-       @qs.reset!
-       @qs.add_fun(@fun).should == true
-     end
-     it "should render rows" do
-       @qs.rrun(["list_begin", {"total_rows"=>1000}, {"q" => "ok"}])
-       @qs.jsgets.should == {"headers"=>{"Content-Type"=>"text/plain"}, "code"=>200, "body"=>"foo"}
-       @qs.run(["list_row", {"value"=>"bar"}, {"q" => "ok"}]).should == {"body"=>"row value bar"}
-       @qs.run(["list_row", {"value"=>"baz"}, {"q" => "ok"}]).should == {"body"=>"row value baz"}
-       @qs.run(["list_row", {"value"=>"bam"}, {"q" => "ok"}]).should == {"body"=>"row value bam"}
-       @qs.run(["list_tail", {"q" => "ok"}]).should == {"body"=>"tail ok"}
-     end
-   end
- end # query server normal case
+    before(:all) do
+      @fun = <<-JS
+        function(doc, req) {
+          log("ok");
+          return [doc.title, doc.body].join(' - ');
+        }
+        JS
+      @qs.reset!
+    end
+    it "should show" do
+      @qs.rrun(["show", @fun, 
+        {:title => "Best ever", :body => "Doc body"}])
+      @qs.jsgets.should == ["resp", {"body" => "Best ever - Doc body"}]
+    end
+  end
+  
+  describe "show with headers" do
+    before(:all) do
+      @fun = <<-JS
+        function(doc, req) {
+          var resp = {"code":200, "headers":{"X-Plankton":"Rusty"}};
+          resp.body = [doc.title, doc.body].join(' - ');
+          return resp;
+        }
+        JS
+      @qs.reset!
+    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 = <<-JS
+        function(head, req) {
+          start({headers:{"Content-Type" : "text/plain"}});
+          send("first chunk");
+          send('second "chunk"');
+          return "tail";
+        };
+        JS
+      @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 = <<-JS
+        function(head, req) {
+          send("first chunk");
+          send(req.q);
+          var row;
+          log("about to getRow " + typeof(getRow));
+          while(row = getRow()) {
+            send(row.key);        
+          };
+          return "tail";
+        };
+        JS
+      @qs.run(["reset"]).should == true    
+      @qs.add_fun(@fun).should == true
+    end
+    it "should 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 = <<-JS
+        function(head, req) {
+          send("bacon");
+          var row;
+          log("about to getRow " + typeof(getRow));
+          while(row = getRow()) {
+            send(row.key);        
+            send("eggs");        
+          };
+          return "tail";
+        };
+        JS
+      @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 = <<-JS
+        function(head, req) {
+          send("first chunk");
+          send(req.q);
+          var row;
+          while(row = getRow()) {
+            send(row.key);    
+          };
+          return "early";
+        };
+        JS
+      @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"]]
+    end
+  end
+  
+  describe "only goes to 2 list" do
+    before(:all) do
+      @fun = <<-JS
+        function(head, req) {
+          send("first chunk");
+          send(req.q);
+          var row, i=0;
+          while(row = getRow()) {
+            send(row.key);  
+            i += 1;
+            if (i > 2) {
+              return('early tail');
+            }  
+          };
+        };
+        JS
+      @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"]]
 
- describe "query server errors" do
-   before(:each) do
-     @qs = QueryServerRunner.run
-   end
-   after(:each) do
-     @qs.close
-   end
-   
-   describe "list" do
-     before(:each) do
-       @fun = <<-JS
-         function(head, row, req) {
-           if (head) return {headers : {"Content-Type" : "text/plain"}, code : 200, "body" : "foo"};
-           if (row) return 'row value '+row.value;
-           return "tail "+req.q;
-         };
-         JS
-       @qs.run(["reset"]).should == true    
-       @qs.add_fun(@fun).should == true
-     end
-     it "should reset in the middle" do
-       @qs.rrun(["list_begin", {"total_rows"=>1000}, {"q" => "ok"}])
-       @qs.jsgets.should == {"headers"=>{"Content-Type"=>"text/plain"}, "code"=>200, "body"=>"foo"}
-       @qs.run(["list_row", {"value"=>"bar"}, {"q" => "ok"}]).should == {"body"=>"row value bar"}
-       @qs.run(["reset"]).should == true
-     end
-   end  
- end #query server that errors
+      @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
 
-## tests for the generic "echo" external
- 
-# describe "running an external" do
-#   before(:all) do
-#     @ext = ExternalRunner.run
-#     
-#   end
-#   it "should respond to 'info'" do
-#     @ext.rrun(['info'])
-#     @ext.jsgets.should == ["info", "echo", "external server that prints its arguments as JSON"]
-#   end
-#   it "should echo the request" do
+def should_have_exited qs
+  begin
+    qs.run(["reset"])
+    "raise before this".should == true
+  rescue RuntimeError => e
+    e.message.should == "no response"
+  rescue Errno::EPIPE
+    true.should == true
+  end
+end
 
-#     @ext.rrun(['req', req_obj])
-#     @ext.jsgets.should == ["x"]
-#   end
-# end
-# 
+describe "query server that exits" do
+  before(:each) do
+    @qs = QueryServerRunner.run
+  end
+  after(:each) do
+    @qs.close
+  end
+  
+  describe "old style list" do
+    before(:each) do
+      @fun = <<-JS
+        function(head, req, foo, bar) {
+          return "stuff";
+        }
+        JS
+      @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
+  
+  describe "only goes to 2 list" do
+    before(:each) do
+      @fun = <<-JS
+        function(head, req) {
+          send("bacon")
+          var row, i = 0;
+          while(row = getRow()) {
+            send(row.key);        
+            i += 1;
+            if (i > 2) {
+              return('early');
+            }
+          };
+        }
+        JS
+      @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.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"
+      should_have_exited @qs
+    end
+  end
+  
+  describe "raw list" do
+    before(:each) do
+      @fun = <<-JS
+        function(head, req) {
+          send("first chunk");
+          send(req.q);
+          var row;
+          while(row = getRow()) {
+            send(row.key);        
+          };
+          return "tail";
+        };
+        JS
+      @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"
+      should_have_exited @qs
+    end
+  end  
+end