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/04/18 22:15:45 UTC

svn commit: r766383 - in /couchdb/trunk: share/server/ share/www/script/test/ src/couchdb/

Author: jchris
Date: Sat Apr 18 20:15:44 2009
New Revision: 766383

URL: http://svn.apache.org/viewvc?rev=766383&view=rev
Log:
refactor main.js into many files and improve show/list error handling

Added:
    couchdb/trunk/share/server/loop.js   (with props)
    couchdb/trunk/share/server/mainjs.sh   (with props)
    couchdb/trunk/share/server/render.js   (with props)
    couchdb/trunk/share/server/state.js   (with props)
    couchdb/trunk/share/server/util.js   (with props)
    couchdb/trunk/share/server/validate.js   (with props)
    couchdb/trunk/share/server/views.js   (with props)
Modified:
    couchdb/trunk/share/server/main.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_httpd_view.erl

Added: couchdb/trunk/share/server/loop.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/server/loop.js?rev=766383&view=auto
==============================================================================
--- couchdb/trunk/share/server/loop.js (added)
+++ couchdb/trunk/share/server/loop.js Sat Apr 18 20:15:44 2009
@@ -0,0 +1,62 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License.  You may obtain a copy
+// of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+var sandbox = null;
+
+try {
+  // if possible, use evalcx (not always available)
+  sandbox = evalcx('');
+  sandbox.emit = emit;
+  sandbox.sum = sum;
+  sandbox.log = log;
+  sandbox.toJSON = toJSON;
+  sandbox.respondWith = respondWith;
+  sandbox.registerType = registerType;
+} catch (e) {}
+
+// Commands are in the form of json arrays:
+// ["commandname",..optional args...]\n
+//
+// Responses are json values followed by a new line ("\n")
+
+var 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 
+};
+
+while (cmd = eval(readline())) {
+  try {
+    cmdkey = cmd.shift();
+    if (dispatch[cmdkey]) {
+      // run the correct responder with the cmd body
+      dispatch[cmdkey].apply(this, cmd);
+    } else {
+      // unknown command, quit and hope the restarted version is better
+      respond({
+        error: "query_server_error",
+        reason: "unknown command '" + cmdkey + "'"});
+      quit();
+    }
+  } catch(e) {
+    respond(e);
+  }
+};

Propchange: couchdb/trunk/share/server/loop.js
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: couchdb/trunk/share/server/main.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/server/main.js?rev=766383&r1=766382&r2=766383&view=diff
==============================================================================
--- couchdb/trunk/share/server/main.js [utf-8] (original)
+++ couchdb/trunk/share/server/main.js [utf-8] Sat Apr 18 20:15:44 2009
@@ -10,33 +10,6 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-var cmd;
-var funs = [];        // holds functions used for computation
-var map_results = []; // holds temporary emitted values during doc map
-var row_line = {}; // holds row number in list per func
-
-var sandbox = null;
-
-emit = function(key, value) {
-  map_results.push([key, value]);
-}
-
-sum = function(values) {
-  var rv = 0;
-  for (var i in values) {
-    rv += values[i];
-  }
-  return rv;
-}
-
-log = function(message) {
-  if (typeof message == "undefined") {
-    message = "Error: attempting to log message of 'undefined'.";
-  } else if (typeof message != "string") {
-    message = toJSON(message);
-  }
-  print(toJSON({log: message}));
-}
 
 // mimeparse.js
 // http://code.google.com/p/mimeparse/
@@ -46,12 +19,12 @@
 
 var Mimeparse = (function() {
   function strip(string) {
-    return string.replace(/^\s+/, '').replace(/\s+$/, '')
+    return string.replace(/^\s+/, '').replace(/\s+$/, '');
   };
   function parseRanges(ranges) {
     var parsedRanges = [], rangeParts = ranges.split(",");
     for (var i=0; i < rangeParts.length; i++) {
-      parsedRanges.push(publicMethods.parseMediaRange(rangeParts[i]))
+      parsedRanges.push(publicMethods.parseMediaRange(rangeParts[i]));
     };
     return parsedRanges;
   };
@@ -130,12 +103,12 @@
       var parsedHeader = parseRanges(header);
       var weighted = [];
       for (var i=0; i < supported.length; i++) {
-        weighted.push([publicMethods.fitnessAndQualityParsed(supported[i], parsedHeader), supported[i]])
+        weighted.push([publicMethods.fitnessAndQualityParsed(supported[i], parsedHeader), supported[i]]);
       };
       weighted.sort();
       return weighted[weighted.length-1][0][1] ? weighted[weighted.length-1][1] : '';
     }
-  }
+  };
   return publicMethods;
 })();
 
@@ -163,7 +136,7 @@
   } else {
     throw({code:406, body:"Not Acceptable: "+accept});    
   }
-}
+};
 
 // whoever registers last wins.
 mimesByKey = {};
@@ -203,204 +176,33 @@
 registerType("multipart_form", "multipart/form-data");
 registerType("url_encoded_form", "application/x-www-form-urlencoded");
 
-// ok back to business.
-
-try {
-  // if possible, use evalcx (not always available)
-  sandbox = evalcx('');
-  sandbox.emit = emit;
-  sandbox.sum = sum;
-  sandbox.log = log;
-  sandbox.toJSON = toJSON;
-  sandbox.respondWith = respondWith;
-  sandbox.registerType = registerType;
-} catch (e) {}
-
-// Commands are in the form of json arrays:
-// ["commandname",..optional args...]\n
-//
-// Responses are json values followed by a new line ("\n")
 
-while (cmd = eval(readline())) {
-  try {
-    switch (cmd[0]) {
-      case "reset":
-        // clear the globals and run gc
-        funs = [];
-        gc();
-        print("true"); // indicates success
-        break;
-      case "add_fun":
-        // The second arg is a string that will compile to a function.
-        // and then we add it to funs array
-        funs.push(compileFunction(cmd[1]));
-        print("true");
-        break;
-      case "map_doc":
-        // The second arg is a document. We compute all the map functions against
-        // it.
-        //
-        // Each function can output multiple keys value, pairs for each document
-        //
-        // Example output of map_doc after three functions set by add_fun cmds:
-        // [
-        //  [["Key","Value"]],                    <- fun 1 returned 1 key value
-        //  [],                                   <- fun 2 returned 0 key values
-        //  [["Key1","Value1"],["Key2","Value2"]] <- fun 3 returned 2 key values
-        // ]
-        //
-        var doc = cmd[1];
-        /*
-        Immutable document support temporarily removed.
-        
-        Removed because the seal function no longer works on JS 1.8 arrays,
-        instead returning an error. The sealing is meant to prevent map
-        functions from modifying the same document that is passed to other map
-        functions. However, only map functions in the same design document are
-        run together, so we have a reasonable expectation they can trust each
-        other. Any map fun that can't be trusted can be placed in its own
-        design document, and it cannot affect other map functions.
-        
-        recursivelySeal(doc); // seal to prevent map functions from changing doc
-        */
-        var buf = [];
-        for (var i = 0; i < funs.length; i++) {
-          map_results = [];
-          try {
-            funs[i](doc);
-            buf.push(toJSON(map_results));
-          } catch (err) {
-            if (err == "fatal_error") {
-              // Only if it's a "fatal_error" do we exit. What's a fatal error?
-              // That's for the query to decide.
-              //
-              // This will make it possible for queries to completely error out,
-              // by catching their own local exception and rethrowing a
-              // fatal_error. But by default if they don't do error handling we
-              // just eat the exception and carry on.
-              throw {error: "map_runtime_error",
-                  reason: "function raised fatal exception"};
-            }
-            print(toJSON({log: "function raised exception (" + err 
-              + ") with doc._id " + doc._id}));
-            buf.push("[]");
-          }
-        }
-        print("[" + buf.join(", ") + "]");
-        break;
-
-      case "rereduce":
-      case "reduce":
-        {
-        var keys = null;
-        var values = null;
-        var reduceFuns = cmd[1];
-        var rereduce = false;
-        
-        if (cmd[0] == "reduce") {
-          var kvs = cmd[2];
-          keys = new Array(kvs.length);
-          values = new Array(kvs.length);
-          for(var i = 0; i < kvs.length; i++) {
-              keys[i] = kvs[i][0];
-              values[i] = kvs[i][1];
-          }
-        } else {
-          values = cmd[2];
-          rereduce = true;
-        }
-
-        for (var i in reduceFuns) {
-          reduceFuns[i] = compileFunction(reduceFuns[i]);
-        }
-
-        var reductions = new Array(funs.length);
-        for(var i = 0; i < reduceFuns.length; i++) {
-          try {
-            reductions[i] = reduceFuns[i](keys, values, rereduce);
-          } catch (err) {
-            if (err == "fatal_error") {
-              throw {error: "reduce_runtime_error",
-                  reason: "function raised fatal exception"};
-            }
-            print(toJSON({log: "function raised exception (" + err + ")"}));
-            reductions[i] = null;
-          }
-        }
-        print("[true," + toJSON(reductions) + "]");
-        }
-        break;
-      case "validate":
-        var funSrc = cmd[1];
-        var newDoc = cmd[2];
-        var oldDoc = cmd[3];
-        var userCtx = cmd[4];
-        var validateFun = compileFunction(funSrc);
-        try {
-          validateFun(newDoc, oldDoc, userCtx);
-          print("1");
-        } catch (error) {
-          respond(error);
-        }
-        break;
-      case "show_doc":
-        var funSrc = cmd[1];
-        var doc = cmd[2];
-        var req = cmd[3];
-        var formFun = compileFunction(funSrc);
-        runRenderFunction(formFun, [doc, req]);
-        break;
-      case "list_begin":
-        var listFun = funs[0];
-        var head = cmd[1];
-        var req = cmd[2];
-        row_line[listFun] = { first_key: null, row_number: 0, prev_key: null };
-        runRenderFunction(listFun, [head, null, req, null]);
-        break;
-      case "list_row":
-        var listFun = funs[0];
-        var row = cmd[1];
-        var req = cmd[2];
-        var row_info = row_line[listFun];
-        runRenderFunction(listFun, [null, row, req, row_info]);
-        if (row_info.first_key == null) {
-          row_info.first_key = row.key;
-        }
-        row_info.prev_key = row.key;
-        row_info.row_number++;
-        row_line[listFun] = row_info;
-        break;
-      case "list_tail":
-        var listFun = funs[0];
-        var req = cmd[1];
-        var row_info = null;
-        try {
-            row_info = row_line[listFun];
-            delete row_line[listFun];
-        } catch (e) {}
-        runRenderFunction(listFun, [null, null, req, row_info]);
-        break;
-      default:
-        print(toJSON({error: "query_server_error",
-            reason: "unknown command '" + cmd[0] + "'"}));
-        quit();
+var Render = (function() {
+  var row_info;
+  return {
+    showDoc : function(funSrc, doc, req) {
+      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++;
+    },
+    listTail : function(req) {
+      runRenderFunction(funs[0], [null, null, req, row_info], funsrc[0]);
     }
-  } catch (exception) {
-    print(toJSON(exception));
-  }
-}
-
-function maybeWrapResponse(resp) {
-  var type = typeof resp;
-  if ((type == "string") || (type == "xml")) {
-    return {body:resp};
-  } else {
-    return resp;
   }
-};
+})();
 
-var responseSent;
-function runRenderFunction(renderFun, args) {
+function runRenderFunction(renderFun, args, funSrc, htmlErrors) {
   responseSent = false;
   try {
     var resp = renderFun.apply(null, args);
@@ -412,46 +214,85 @@
       }      
     }
   } catch(e) {
-    log("function raised error: "+e.toString());
-    log("stacktrace: "+e.stack);
-    var errorMessage = "JavaScript function raised error: "+e.toString()+"\nSee CouchDB logfile for stacktrace.";
-    respond({error:"render_error",reason:errorMessage});
+    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});
   }
 };
 
-// prints the object as JSON, and rescues and logs any toJSON() related errors
-function respond(obj) {
-  responseSent = true;
-  try {
-    print(toJSON(obj));  
-  } catch(e) {
-    log("Error converting object to JSON: " + e.toString());
-  }
+function escapeHTML(string) {
+  return string.replace(/&/g, "&amp;")
+               .replace(/</g, "&lt;")
+               .replace(/>/g, "&gt;");
 }
 
-function compileFunction(source) {
-  try {
-    var functionObject = sandbox ? evalcx(source, sandbox) : eval(source);
-  } catch (err) {
-    throw {error: "compilation_error",
-      reason: err.toString() + " (" + source + ")"};
-  }
-  if (typeof(functionObject) == "function") {
-    return functionObject;
+function htmlRenderError(e, funSrc) {
+  var msg = ["<html><body><h1>Render Error</h1>",
+    "<p>JavaScript function raised error: ",
+    e.toString(),
+    "</p><h2>Stacktrace:</h2><code><pre>",
+    escapeHTML(e.stack),
+    "</pre></code><h2>Function source:</h2><code><pre>",
+    escapeHTML(funSrc),
+    "</pre></code></body></html>"].join('');
+  return {body:msg};
+};
+
+function maybeWrapResponse(resp) {
+  var type = typeof resp;
+  if ((type == "string") || (type == "xml")) {
+    return {body:resp};
   } else {
-    throw {error: "compilation_error",
-      reason: "expression does not eval to a function. (" + source + ")"};
+    return resp;
   }
-}
+};
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License.  You may obtain a copy
+// of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+// License for the specific language governing permissions and limitations under
+// the License.
 
-function recursivelySeal(obj) {
-  seal(obj);
-  for (var propname in obj) {
-    if (typeof doc[propname] == "object") {
-      recursivelySeal(doc[propname]);
+// globals used by other modules and functions
+var funs = [];        // holds functions used for computation
+var funsrc = [];      // holds function source for debug info
+var State = (function() {
+  return {
+    reset : function() {
+      // clear the globals and run gc
+      funs = [];
+      funsrc = [];
+      gc();
+      print("true"); // indicates success
+    },
+    addFun : function(newFun) {
+      // Compile to a function and add it to funs array
+      funsrc.push(newFun);
+      funs.push(compileFunction(newFun));
+      print("true");
     }
   }
-}
+})();
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License.  You may obtain a copy
+// of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+// License for the specific language governing permissions and limitations under
+// the License.
 
 function toJSON(val) {
   if (typeof(val) == "undefined") {
@@ -474,7 +315,7 @@
       return v.toString();
     },
     "Date": function(v) {
-      var f = function(n) { return n < 10 ? '0' + n : n }
+      var f = function(n) { return n < 10 ? '0' + n : n };
       return '"' + v.getUTCFullYear()   + '-' +
                  f(v.getUTCMonth() + 1) + '-' +
                  f(v.getUTCDate())      + 'T' +
@@ -509,3 +350,248 @@
     }
   }[val != null ? val.constructor.name : "Object"](val);
 }
+
+function compileFunction(source) {
+  try {
+    var functionObject = sandbox ? evalcx(source, sandbox) : eval(source);
+  } catch (err) {
+    throw {error: "compilation_error",
+      reason: err.toString() + " (" + source + ")"};
+  }
+  if (typeof(functionObject) == "function") {
+    return functionObject;
+  } else {
+    throw {error: "compilation_error",
+      reason: "expression does not eval to a function. (" + source + ")"};
+  }
+}
+
+function recursivelySeal(obj) {
+  seal(obj);
+  for (var propname in obj) {
+    if (typeof doc[propname] == "object") {
+      recursivelySeal(doc[propname]);
+    }
+  }
+}
+
+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) {
+    log("Error converting object to JSON: " + e.toString());
+  }
+};
+
+log = function(message) {
+  if (typeof message == "undefined") {
+    message = "Error: attempting to log message of 'undefined'.";
+  } else if (typeof message != "string") {
+    message = toJSON(message);
+  }
+  print(toJSON({log: message}));
+};
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License.  You may obtain a copy
+// of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+var Validate = {
+  validate : function(funSrc, newDoc, oldDoc, userCtx) {
+    var validateFun = compileFunction(funSrc);
+    try {
+      validateFun(newDoc, oldDoc, userCtx);
+      print("1");
+    } catch (error) {
+      respond(error);
+    }
+  }
+};// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License.  You may obtain a copy
+// of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+// globals used by views
+var map_results = []; // holds temporary emitted values during doc map
+
+// view helper functions
+emit = function(key, value) {
+  map_results.push([key, value]);
+}
+
+sum = function(values) {
+  var rv = 0;
+  for (var i in values) {
+    rv += values[i];
+  }
+  return rv;
+}
+
+var Views = (function() {
+    
+  function runReduce(reduceFuns, keys, values, rereduce) {
+    for (var i in reduceFuns) {
+      reduceFuns[i] = compileFunction(reduceFuns[i]);
+    }
+    var reductions = new Array(reduceFuns.length);
+    for(var i = 0; i < reduceFuns.length; i++) {
+      try {
+        reductions[i] = reduceFuns[i](keys, values, rereduce);
+      } catch (err) {
+        if (err == "fatal_error") {
+          throw {
+            error: "reduce_runtime_error",
+            reason: "function raised fatal exception"};
+        }
+        log("function raised exception (" + err + ")");
+        reductions[i] = null;
+      }
+    }
+    print("[true," + toJSON(reductions) + "]");
+  };
+  
+  return {
+    reduce : function(reduceFuns, kvs) {
+      var keys = new Array(kvs.length);
+      var values = new Array(kvs.length);
+      for(var i = 0; i < kvs.length; i++) {
+          keys[i] = kvs[i][0];
+          values[i] = kvs[i][1];
+      }
+      runReduce(reduceFuns, keys, values, false);
+    },
+    rereduce : function(reduceFuns, values) {
+      runReduce(reduceFuns, null, values, true);
+    },
+    mapDoc : function(doc) {
+      // Compute all the map functions against the document.
+      //
+      // Each function can output multiple key/value pairs for each document.
+      //
+      // Example output of map_doc after three functions set by add_fun cmds:
+      // [
+      //  [["Key","Value"]],                    <- fun 1 returned 1 key value
+      //  [],                                   <- fun 2 returned 0 key values
+      //  [["Key1","Value1"],["Key2","Value2"]] <- fun 3 returned 2 key values
+      // ]
+      //
+
+      /*
+      Immutable document support temporarily removed.
+
+      Removed because the seal function no longer works on JS 1.8 arrays,
+      instead returning an error. The sealing is meant to prevent map
+      functions from modifying the same document that is passed to other map
+      functions. However, only map functions in the same design document are
+      run together, so we have a reasonable expectation they can trust each
+      other. Any map fun that can't be trusted can be placed in its own
+      design document, and it cannot affect other map functions.
+
+      recursivelySeal(doc); // seal to prevent map functions from changing doc
+      */
+      var buf = [];
+      for (var i = 0; i < funs.length; i++) {
+        map_results = [];
+        try {
+          funs[i](doc);
+          buf.push(toJSON(map_results));
+        } catch (err) {
+          if (err == "fatal_error") {
+            // Only if it's a "fatal_error" do we exit. What's a fatal error?
+            // That's for the query to decide.
+            //
+            // This will make it possible for queries to completely error out,
+            // by catching their own local exception and rethrowing a
+            // fatal_error. But by default if they don't do error handling we
+            // just eat the exception and carry on.
+            throw {
+              error: "map_runtime_error",
+              reason: "function raised fatal exception"};
+          }
+          log("function raised exception (" + err + ") with doc._id " + doc._id);
+          buf.push("[]");
+        }
+      }
+      print("[" + buf.join(", ") + "]");
+    }
+  }
+})();
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License.  You may obtain a copy
+// of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+var sandbox = null;
+
+try {
+  // if possible, use evalcx (not always available)
+  sandbox = evalcx('');
+  sandbox.emit = emit;
+  sandbox.sum = sum;
+  sandbox.log = log;
+  sandbox.toJSON = toJSON;
+  sandbox.respondWith = respondWith;
+  sandbox.registerType = registerType;
+} catch (e) {}
+
+// Commands are in the form of json arrays:
+// ["commandname",..optional args...]\n
+//
+// Responses are json values followed by a new line ("\n")
+
+var 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 
+};
+
+while (cmd = eval(readline())) {
+  try {
+    cmdkey = cmd.shift();
+    if (dispatch[cmdkey]) {
+      // run the correct responder with the cmd body
+      dispatch[cmdkey].apply(this, cmd);
+    } else {
+      // unknown command, quit and hope the restarted version is better
+      respond({
+        error: "query_server_error",
+        reason: "unknown command '" + cmdkey + "'"});
+      quit();
+    }
+  } catch(e) {
+    respond(e);
+  }
+};

Added: couchdb/trunk/share/server/mainjs.sh
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/server/mainjs.sh?rev=766383&view=auto
==============================================================================
--- couchdb/trunk/share/server/mainjs.sh (added)
+++ couchdb/trunk/share/server/mainjs.sh Sat Apr 18 20:15:44 2009
@@ -0,0 +1,24 @@
+#! /bin/sh -e
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License.  You may obtain a copy of
+# the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+dirname=`dirname $0`
+
+cat \
+  $dirname/render.js \
+  $dirname/state.js \
+  $dirname/util.js \
+  $dirname/validate.js \
+  $dirname/views.js \
+  $dirname/loop.js \
+  > $dirname/main.js

Propchange: couchdb/trunk/share/server/mainjs.sh
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: couchdb/trunk/share/server/mainjs.sh
------------------------------------------------------------------------------
    svn:executable = *

Added: couchdb/trunk/share/server/render.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/server/render.js?rev=766383&view=auto
==============================================================================
--- couchdb/trunk/share/server/render.js (added)
+++ couchdb/trunk/share/server/render.js Sat Apr 18 20:15:44 2009
@@ -0,0 +1,252 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License.  You may obtain a copy
+// of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+
+// mimeparse.js
+// http://code.google.com/p/mimeparse/
+// 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
+
+var Mimeparse = (function() {
+  function strip(string) {
+    return string.replace(/^\s+/, '').replace(/\s+$/, '');
+  };
+  function parseRanges(ranges) {
+    var parsedRanges = [], rangeParts = ranges.split(",");
+    for (var i=0; i < rangeParts.length; i++) {
+      parsedRanges.push(publicMethods.parseMediaRange(rangeParts[i]));
+    };
+    return parsedRanges;
+  };
+  var publicMethods = {
+    parseMimeType : function(mimeType) {
+      var fullType, typeParts, params = {}, parts = mimeType.split(';');
+      for (var i=0; i < parts.length; i++) {
+        var p = parts[i].split('=');
+        if (p.length == 2) {
+          params[strip(p[0])] = strip(p[1]);
+        }
+      };
+      fullType = parts[0].replace(/^\s+/, '').replace(/\s+$/, '');
+      if (fullType == '*') fullType = '*/*';
+      typeParts = fullType.split('/');
+      return [typeParts[0], typeParts[1], params];
+    },
+    parseMediaRange : function(range) {
+      var q, parsedType = this.parseMimeType(range);
+      if (!parsedType[2]['q']) {
+        parsedType[2]['q'] = '1';
+      } else {
+        q = parseFloat(parsedType[2]['q']);
+        if (isNaN(q)) {
+          parsedType[2]['q'] = '1';
+        } else if (q > 1 || q < 0) {
+          parsedType[2]['q'] = '1';
+        }
+      }
+      return parsedType;
+    },
+    fitnessAndQualityParsed : function(mimeType, parsedRanges) {
+      var bestFitness = -1, bestFitQ = 0, target = this.parseMediaRange(mimeType);
+      var targetType = target[0], targetSubtype = target[1], targetParams = target[2];
+
+      for (var i=0; i < parsedRanges.length; i++) {
+        var parsed = parsedRanges[i];
+        var type = parsed[0], subtype = parsed[1], params = parsed[2];
+        if ((type == targetType || type == "*" || targetType == "*") &&
+          (subtype == targetSubtype || subtype == "*" || targetSubtype == "*")) {
+          var matchCount = 0;
+          for (param in targetParams) {
+            if (param != 'q' && params[param] && params[param] == targetParams[param]) {
+              matchCount += 1;
+            }
+          }
+
+          var fitness = (type == targetType) ? 100 : 0;
+          fitness += (subtype == targetSubtype) ? 10 : 0;
+          fitness += matchCount;
+
+          if (fitness > bestFitness) {
+            bestFitness = fitness;
+            bestFitQ = params["q"];
+          }
+        }
+      };
+      return [bestFitness, parseFloat(bestFitQ)];
+    },
+    qualityParsed : function(mimeType, parsedRanges) {
+      return this.fitnessAndQualityParsed(mimeType, parsedRanges)[1];
+    },
+    quality : function(mimeType, ranges) {
+      return this.qualityParsed(mimeType, parseRanges(ranges));
+    },
+
+    // Takes a list of supported mime-types and finds the best
+    // match for all the media-ranges listed in header. The value of
+    // header must be a string that conforms to the format of the
+    // HTTP Accept: header. The value of 'supported' is a list of
+    // mime-types.
+    //
+    // >>> bestMatch(['application/xbel+xml', 'text/xml'], 'text/*;q=0.5,*/*; q=0.1')
+    // 'text/xml'
+    bestMatch : function(supported, header) {
+      var parsedHeader = parseRanges(header);
+      var weighted = [];
+      for (var i=0; i < supported.length; i++) {
+        weighted.push([publicMethods.fitnessAndQualityParsed(supported[i], parsedHeader), supported[i]]);
+      };
+      weighted.sort();
+      return weighted[weighted.length-1][0][1] ? weighted[weighted.length-1][1] : '';
+    }
+  };
+  return publicMethods;
+})();
+
+// this function provides a shortcut for managing responses by Accept header
+respondWith = function(req, responders) {
+  var bestKey = null, accept = req.headers["Accept"];
+  if (accept && !req.query.format) {
+    var provides = [];
+    for (key in responders) {
+      if (mimesByKey[key]) {
+        provides = provides.concat(mimesByKey[key]);        
+      }
+    }
+    var bestMime = Mimeparse.bestMatch(provides, accept);
+    bestKey = keysByMime[bestMime];
+  } else {
+    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);
+  } else {
+    throw({code:406, body:"Not Acceptable: "+accept});    
+  }
+};
+
+// whoever registers last wins.
+mimesByKey = {};
+keysByMime = {};
+registerType = function() {
+  var mimes = [], key = arguments[0];
+  for (var i=1; i < arguments.length; i++) {
+    mimes.push(arguments[i]);
+  };
+  mimesByKey[key] = mimes;
+  for (var i=0; i < mimes.length; i++) {
+    keysByMime[mimes[i]] = key;
+  };
+};
+
+// Some default types
+// Ported from Ruby on Rails
+// Build list of Mime types for HTTP responses
+// http://www.iana.org/assignments/media-types/
+// http://dev.rubyonrails.org/svn/rails/trunk/actionpack/lib/action_controller/mime_types.rb
+
+registerType("all", "*/*");
+registerType("text", "text/plain", "txt");
+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");
+registerType("csv", "text/csv");
+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");
+
+
+var Render = (function() {
+  var row_info;
+  return {
+    showDoc : function(funSrc, doc, req) {
+      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++;
+    },
+    listTail : function(req) {
+      runRenderFunction(funs[0], [null, null, req, row_info], funsrc[0]);
+    }
+  }
+})();
+
+function runRenderFunction(renderFun, args, funSrc, htmlErrors) {
+  responseSent = false;
+  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"});
+      }      
+    }
+  } 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});
+  }
+};
+
+function escapeHTML(string) {
+  return string.replace(/&/g, "&amp;")
+               .replace(/</g, "&lt;")
+               .replace(/>/g, "&gt;");
+}
+
+function htmlRenderError(e, funSrc) {
+  var msg = ["<html><body><h1>Render Error</h1>",
+    "<p>JavaScript function raised error: ",
+    e.toString(),
+    "</p><h2>Stacktrace:</h2><code><pre>",
+    escapeHTML(e.stack),
+    "</pre></code><h2>Function source:</h2><code><pre>",
+    escapeHTML(funSrc),
+    "</pre></code></body></html>"].join('');
+  return {body:msg};
+};
+
+function maybeWrapResponse(resp) {
+  var type = typeof resp;
+  if ((type == "string") || (type == "xml")) {
+    return {body:resp};
+  } else {
+    return resp;
+  }
+};

Propchange: couchdb/trunk/share/server/render.js
------------------------------------------------------------------------------
    svn:eol-style = native

Added: couchdb/trunk/share/server/state.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/server/state.js?rev=766383&view=auto
==============================================================================
--- couchdb/trunk/share/server/state.js (added)
+++ couchdb/trunk/share/server/state.js Sat Apr 18 20:15:44 2009
@@ -0,0 +1,32 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License.  You may obtain a copy
+// of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+// globals used by other modules and functions
+var funs = [];        // holds functions used for computation
+var funsrc = [];      // holds function source for debug info
+var State = (function() {
+  return {
+    reset : function() {
+      // clear the globals and run gc
+      funs = [];
+      funsrc = [];
+      gc();
+      print("true"); // indicates success
+    },
+    addFun : function(newFun) {
+      // Compile to a function and add it to funs array
+      funsrc.push(newFun);
+      funs.push(compileFunction(newFun));
+      print("true");
+    }
+  }
+})();

Propchange: couchdb/trunk/share/server/state.js
------------------------------------------------------------------------------
    svn:eol-style = native

Added: couchdb/trunk/share/server/util.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/server/util.js?rev=766383&view=auto
==============================================================================
--- couchdb/trunk/share/server/util.js (added)
+++ couchdb/trunk/share/server/util.js Sat Apr 18 20:15:44 2009
@@ -0,0 +1,112 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License.  You may obtain a copy
+// of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+function toJSON(val) {
+  if (typeof(val) == "undefined") {
+    throw "Cannot encode 'undefined' value as JSON";
+  }
+  var subs = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f',
+              '\r': '\\r', '"' : '\\"', '\\': '\\\\'};
+  if (typeof(val) == "xml") { // E4X support
+    val = val.toXMLString();
+  }
+  return {
+    "Array": function(v) {
+      var buf = [];
+      for (var i = 0; i < v.length; i++) {
+        buf.push(toJSON(v[i]));
+      }
+      return "[" + buf.join(",") + "]";
+    },
+    "Boolean": function(v) {
+      return v.toString();
+    },
+    "Date": function(v) {
+      var f = function(n) { return n < 10 ? '0' + n : n };
+      return '"' + v.getUTCFullYear()   + '-' +
+                 f(v.getUTCMonth() + 1) + '-' +
+                 f(v.getUTCDate())      + 'T' +
+                 f(v.getUTCHours())     + ':' +
+                 f(v.getUTCMinutes())   + ':' +
+                 f(v.getUTCSeconds())   + 'Z"';
+    },
+    "Number": function(v) {
+      return isFinite(v) ? v.toString() : "null";
+    },
+    "Object": function(v) {
+      if (v === null) return "null";
+      var buf = [];
+      for (var k in v) {
+        if (!v.hasOwnProperty(k) || typeof(k) !== "string" || v[k] === undefined) {
+          continue;
+        }
+        buf.push(toJSON(k, val) + ": " + toJSON(v[k]));
+      }
+      return "{" + buf.join(",") + "}";
+    },
+    "String": function(v) {
+      if (/["\\\x00-\x1f]/.test(v)) {
+        v = v.replace(/([\x00-\x1f\\"])/g, function(a, b) {
+          var c = subs[b];
+          if (c) return c;
+          c = b.charCodeAt();
+          return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
+        });
+      }
+      return '"' + v + '"';
+    }
+  }[val != null ? val.constructor.name : "Object"](val);
+}
+
+function compileFunction(source) {
+  try {
+    var functionObject = sandbox ? evalcx(source, sandbox) : eval(source);
+  } catch (err) {
+    throw {error: "compilation_error",
+      reason: err.toString() + " (" + source + ")"};
+  }
+  if (typeof(functionObject) == "function") {
+    return functionObject;
+  } else {
+    throw {error: "compilation_error",
+      reason: "expression does not eval to a function. (" + source + ")"};
+  }
+}
+
+function recursivelySeal(obj) {
+  seal(obj);
+  for (var propname in obj) {
+    if (typeof doc[propname] == "object") {
+      recursivelySeal(doc[propname]);
+    }
+  }
+}
+
+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) {
+    log("Error converting object to JSON: " + e.toString());
+  }
+};
+
+log = function(message) {
+  if (typeof message == "undefined") {
+    message = "Error: attempting to log message of 'undefined'.";
+  } else if (typeof message != "string") {
+    message = toJSON(message);
+  }
+  print(toJSON({log: message}));
+};

Propchange: couchdb/trunk/share/server/util.js
------------------------------------------------------------------------------
    svn:eol-style = native

Added: couchdb/trunk/share/server/validate.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/server/validate.js?rev=766383&view=auto
==============================================================================
--- couchdb/trunk/share/server/validate.js (added)
+++ couchdb/trunk/share/server/validate.js Sat Apr 18 20:15:44 2009
@@ -0,0 +1,23 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License.  You may obtain a copy
+// of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+var Validate = {
+  validate : function(funSrc, newDoc, oldDoc, userCtx) {
+    var validateFun = compileFunction(funSrc);
+    try {
+      validateFun(newDoc, oldDoc, userCtx);
+      print("1");
+    } catch (error) {
+      respond(error);
+    }
+  }
+};
\ No newline at end of file

Propchange: couchdb/trunk/share/server/validate.js
------------------------------------------------------------------------------
    svn:eol-style = native

Added: couchdb/trunk/share/server/views.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/server/views.js?rev=766383&view=auto
==============================================================================
--- couchdb/trunk/share/server/views.js (added)
+++ couchdb/trunk/share/server/views.js Sat Apr 18 20:15:44 2009
@@ -0,0 +1,117 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License.  You may obtain a copy
+// of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+// globals used by views
+var map_results = []; // holds temporary emitted values during doc map
+
+// view helper functions
+emit = function(key, value) {
+  map_results.push([key, value]);
+}
+
+sum = function(values) {
+  var rv = 0;
+  for (var i in values) {
+    rv += values[i];
+  }
+  return rv;
+}
+
+var Views = (function() {
+    
+  function runReduce(reduceFuns, keys, values, rereduce) {
+    for (var i in reduceFuns) {
+      reduceFuns[i] = compileFunction(reduceFuns[i]);
+    }
+    var reductions = new Array(reduceFuns.length);
+    for(var i = 0; i < reduceFuns.length; i++) {
+      try {
+        reductions[i] = reduceFuns[i](keys, values, rereduce);
+      } catch (err) {
+        if (err == "fatal_error") {
+          throw {
+            error: "reduce_runtime_error",
+            reason: "function raised fatal exception"};
+        }
+        log("function raised exception (" + err + ")");
+        reductions[i] = null;
+      }
+    }
+    print("[true," + toJSON(reductions) + "]");
+  };
+  
+  return {
+    reduce : function(reduceFuns, kvs) {
+      var keys = new Array(kvs.length);
+      var values = new Array(kvs.length);
+      for(var i = 0; i < kvs.length; i++) {
+          keys[i] = kvs[i][0];
+          values[i] = kvs[i][1];
+      }
+      runReduce(reduceFuns, keys, values, false);
+    },
+    rereduce : function(reduceFuns, values) {
+      runReduce(reduceFuns, null, values, true);
+    },
+    mapDoc : function(doc) {
+      // Compute all the map functions against the document.
+      //
+      // Each function can output multiple key/value pairs for each document.
+      //
+      // Example output of map_doc after three functions set by add_fun cmds:
+      // [
+      //  [["Key","Value"]],                    <- fun 1 returned 1 key value
+      //  [],                                   <- fun 2 returned 0 key values
+      //  [["Key1","Value1"],["Key2","Value2"]] <- fun 3 returned 2 key values
+      // ]
+      //
+
+      /*
+      Immutable document support temporarily removed.
+
+      Removed because the seal function no longer works on JS 1.8 arrays,
+      instead returning an error. The sealing is meant to prevent map
+      functions from modifying the same document that is passed to other map
+      functions. However, only map functions in the same design document are
+      run together, so we have a reasonable expectation they can trust each
+      other. Any map fun that can't be trusted can be placed in its own
+      design document, and it cannot affect other map functions.
+
+      recursivelySeal(doc); // seal to prevent map functions from changing doc
+      */
+      var buf = [];
+      for (var i = 0; i < funs.length; i++) {
+        map_results = [];
+        try {
+          funs[i](doc);
+          buf.push(toJSON(map_results));
+        } catch (err) {
+          if (err == "fatal_error") {
+            // Only if it's a "fatal_error" do we exit. What's a fatal error?
+            // That's for the query to decide.
+            //
+            // This will make it possible for queries to completely error out,
+            // by catching their own local exception and rethrowing a
+            // fatal_error. But by default if they don't do error handling we
+            // just eat the exception and carry on.
+            throw {
+              error: "map_runtime_error",
+              reason: "function raised fatal exception"};
+          }
+          log("function raised exception (" + err + ") with doc._id " + doc._id);
+          buf.push("[]");
+        }
+      }
+      print("[" + buf.join(", ") + "]");
+    }
+  }
+})();

Propchange: couchdb/trunk/share/server/views.js
------------------------------------------------------------------------------
    svn:eol-style = native

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=766383&r1=766382&r2=766383&view=diff
==============================================================================
--- couchdb/trunk/share/www/script/test/list_views.js (original)
+++ couchdb/trunk/share/www/script/test/list_views.js Sat Apr 18 20:15:44 2009
@@ -130,6 +130,15 @@
       }),
       emptyList: stringFun(function(head, row, req, row_info) {
         return { body: "" };
+      }),
+      rowError : stringFun(function(head, row, req, row_info) {
+        if (head) {
+          return "head";
+        } else if(row) {
+          return missingValue;
+        } else {
+          return "tail"
+        }
       })
     }
   };
@@ -272,4 +281,7 @@
   });
   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));
 };

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=766383&r1=766382&r2=766383&view=diff
==============================================================================
--- couchdb/trunk/share/www/script/test/show_documents.js (original)
+++ couchdb/trunk/share/www/script/test/show_documents.js Sat Apr 18 20:15:44 2009
@@ -54,6 +54,9 @@
           json : req
         }
       }),
+      "render-error" : stringFun(function(doc, req) {
+        return noSuchVariable;
+      }),
       "xml-type" : stringFun(function(doc, req) {
          return {
            "headers" : {
@@ -142,6 +145,10 @@
   // 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");
  
   // 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=766383&r1=766382&r2=766383&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd.erl Sat Apr 18 20:15:44 2009
@@ -422,8 +422,8 @@
 error_info(Error) ->
     {500, <<"unknown_error">>, couch_util:to_binary(Error)}.
 
-send_error(_Req, {already_sent, _Error}) ->
-    ok;
+send_error(_Req, {already_sent, Resp, _Error}) ->
+    {ok, Resp};
     
 send_error(Req, Error) ->
     {Code, ErrorStr, ReasonStr} = error_info(Error),
@@ -447,21 +447,17 @@
         {[{<<"error">>,  ErrorStr},
          {<<"reason">>, ReasonStr}]}).
 
+% give the option for list functions to output html or other raw errors
+send_chunked_error(Resp, {_Error, {[{<<"body">>, Reason}]}}) ->
+    send_chunk(Resp, Reason),
+    send_chunk(Resp, []);
+
 send_chunked_error(Resp, Error) ->
     {Code, ErrorStr, ReasonStr} = error_info(Error),
-    CType = Resp:get_header_value("Content-Type"),
-    case CType of
-        "text/html" ->
-            HtmlError = ?l2b([$\n,
-                "<html><body><h2>Error: ", ErrorStr, "</h2>",
-                "<pre>Reason: ", ReasonStr, "</pre>", $\n]),
-            send_chunk(Resp, HtmlError);
-        _Else ->
-            JsonError = {[{<<"code">>, Code},
-                {<<"error">>,  ErrorStr}, 
-                {<<"reason">>, ReasonStr}]},
-            send_chunk(Resp, ?l2b([$\n,?JSON_ENCODE(JsonError),$\n]))
-    end,
+    JsonError = {[{<<"code">>, Code},
+        {<<"error">>,  ErrorStr}, 
+        {<<"reason">>, ReasonStr}]},
+    send_chunk(Resp, ?l2b([$\n,?JSON_ENCODE(JsonError),$\n])),
     send_chunk(Resp, []).
 
 send_redirect(Req, Path) ->

Modified: couchdb/trunk/src/couchdb/couch_httpd_show.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_show.erl?rev=766383&r1=766382&r2=766383&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd_show.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd_show.erl Sat Apr 18 20:15:44 2009
@@ -19,7 +19,7 @@
 
 -import(couch_httpd,
     [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,
-    start_json_response/2,send_chunk/2,
+    start_json_response/2,send_chunk/2,send_chunked_error/2,
     start_chunked_response/3, send_error/4]).
     
 handle_doc_show_req(#httpd{
@@ -132,24 +132,30 @@
 make_map_send_row_fun(QueryServer, Req) ->
     fun(Resp, Db2, {{Key, DocId}, Value}, 
         RowFront, _IncludeDocs) ->
-        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;
-        _ ->
-            RowFront2 = case RowFront of
-            nil -> [];
-            _ -> RowFront
-            end,
-            Chunk = RowFront2 ++ binary_to_list(RowBody),
-            case Chunk of
-                [] -> {ok, Resp};
-                _ -> send_chunk(Resp, Chunk)
+        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;
+            _ ->
+                RowFront2 = case RowFront of
+                nil -> [];
+                _ -> RowFront
+                end,
+                Chunk = RowFront2 ++ binary_to_list(RowBody),
+                case Chunk of
+                    [] -> {ok, Resp};
+                    _ -> send_chunk(Resp, Chunk)
+                end
             end
+        catch
+            throw:Error ->
+                send_chunked_error(Resp, Error),
+                throw({already_sent, Resp, Error})
         end
     end.
 
@@ -241,24 +247,30 @@
 
 make_reduce_send_row_fun(QueryServer, Req, Db) ->
     fun(Resp, {Key, Value}, RowFront) ->
-        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),
-        RowFront2 = case RowFront of
-        nil -> [];
-        _ -> RowFront
-        end,
-        case StopIter of
-        true -> stop;
-        _ ->
-            Chunk = RowFront2 ++ binary_to_list(RowBody),
-            case Chunk of
-                [] -> {ok, Resp};
-                _ -> send_chunk(Resp, Chunk)
+        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),
+            RowFront2 = case RowFront of
+            nil -> [];
+            _ -> RowFront
+            end,
+            case StopIter of
+            true -> stop;
+            _ ->
+                Chunk = RowFront2 ++ binary_to_list(RowBody),
+                case Chunk of
+                    [] -> {ok, Resp};
+                    _ -> send_chunk(Resp, Chunk)
+                end
             end
+        catch
+            throw:Error ->
+                send_chunked_error(Resp, Error),
+                throw({already_sent, Resp, Error})
         end
     end.
 
@@ -417,3 +429,4 @@
             Field
         end || Field <- ExternalResponse]}
     end.
+    
\ No newline at end of file

Modified: couchdb/trunk/src/couchdb/couch_httpd_view.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_view.erl?rev=766383&r1=766382&r2=766383&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd_view.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd_view.erl Sat Apr 18 20:15:44 2009
@@ -530,7 +530,7 @@
     Helpers#view_fold_helper_funs{
         passed_end = PassedEnd2,
         start_response = StartResp2,
-        send_row = wrap_for_chunked_errors(SendRow2)
+        send_row = SendRow2
     }.
 
 apply_default_helper_funs(#reduce_fold_helper_funs{
@@ -549,7 +549,7 @@
 
     Helpers#reduce_fold_helper_funs{
         start_response = StartResp2,
-        send_row = wrap_for_chunked_errors(SendRow2)
+        send_row = SendRow2
     }.
 
 make_passed_end_fun(fwd, EndKey, EndDocId, InclusiveEnd) ->
@@ -606,27 +606,7 @@
     nil -> ",\r\n";
     _ -> RowFront
     end,
-    send_chunk(Resp, RowFront2 ++ ?JSON_ENCODE({[{key, Key}, {value, Value}]})).
-
-wrap_for_chunked_errors(Fun) when is_function(Fun, 3)->
-    fun(Resp, B, C) ->
-        try Fun(Resp, B, C)
-        catch
-            throw:Error ->
-                send_chunked_error(Resp, Error),
-                throw({already_sent, Error})
-        end
-    end;
-
-wrap_for_chunked_errors(Fun) when is_function(Fun, 5)->
-    fun(Resp, B, C, D, E) ->
-        try Fun(Resp, B, C, D, E)
-        catch
-            throw:Error ->
-                send_chunked_error(Resp, Error),
-                throw({already_sent, Error})
-        end
-    end.    
+    send_chunk(Resp, RowFront2 ++ ?JSON_ENCODE({[{key, Key}, {value, Value}]})).    
 
 view_group_etag(Group) ->
     view_group_etag(Group, nil).