summaryrefslogtreecommitdiff
path: root/share/server
diff options
context:
space:
mode:
Diffstat (limited to 'share/server')
-rw-r--r--share/server/filter.js19
-rw-r--r--share/server/loop.js145
-rw-r--r--share/server/render.js558
-rw-r--r--share/server/state.js36
-rw-r--r--share/server/util.js91
-rw-r--r--share/server/validate.js5
-rw-r--r--share/server/views.js104
7 files changed, 519 insertions, 439 deletions
diff --git a/share/server/filter.js b/share/server/filter.js
index a683146a..8ba77e64 100644
--- a/share/server/filter.js
+++ b/share/server/filter.js
@@ -11,17 +11,14 @@
// the License.
var Filter = {
- filter : function(funSrc, docs, req, userCtx) {
- var filterFun = compileFunction(funSrc);
-
+ filter : function(fun, ddoc, args) {
var results = [];
- try {
- for (var i=0; i < docs.length; i++) {
- results.push((filterFun(docs[i], req, userCtx) && true) || false);
- };
- respond([true, results]);
- } catch (error) {
- respond(error);
- }
+ var docs = args[0];
+ var req = args[1];
+ var userCtx = args[2];
+ for (var i=0; i < docs.length; i++) {
+ results.push((fun.apply(ddoc, [docs[i], req, userCtx]) && true) || false);
+ };
+ respond([true, results]);
}
};
diff --git a/share/server/loop.js b/share/server/loop.js
index 33e87c98..84e35dc5 100644
--- a/share/server/loop.js
+++ b/share/server/loop.js
@@ -12,21 +12,21 @@
var sandbox = null;
-var init_sandbox = function() {
+function init_sandbox() {
try {
// if possible, use evalcx (not always available)
sandbox = evalcx('');
- sandbox.emit = emit;
- sandbox.sum = sum;
+ sandbox.emit = Views.emit;
+ sandbox.sum = Views.sum;
sandbox.log = log;
- sandbox.toJSON = toJSON;
- sandbox.provides = provides;
- sandbox.registerType = registerType;
- sandbox.start = start;
- sandbox.send = send;
- sandbox.getRow = getRow;
+ sandbox.toJSON = Couch.toJSON;
+ sandbox.provides = Mime.provides;
+ sandbox.registerType = Mime.registerType;
+ sandbox.start = Render.start;
+ sandbox.send = Render.send;
+ sandbox.getRow = Render.getRow;
} catch (e) {
- log(toJSON(e));
+ log(e.toSource());
}
};
init_sandbox();
@@ -36,37 +36,104 @@ init_sandbox();
//
// Responses are json values followed by a new line ("\n")
-var line, cmd, cmdkey;
+var DDoc = (function() {
+ var ddoc_dispatch = {
+ "lists" : Render.list,
+ "shows" : Render.show,
+ "filters" : Filter.filter,
+ "updates" : Render.update,
+ "validate_doc_update" : Validate.validate
+ };
+ var ddocs = {};
+ return {
+ ddoc : function() {
+ var args = [];
+ for (var i=0; i < arguments.length; i++) {
+ args.push(arguments[i]);
+ };
+ var ddocId = args.shift();
+ if (ddocId == "new") {
+ // get the real ddocId.
+ ddocId = args.shift();
+ // store the ddoc, functions are lazily compiled.
+ ddocs[ddocId] = args.shift();
+ print("true");
+ } else {
+ // Couch makes sure we know this ddoc already.
+ var ddoc = ddocs[ddocId];
+ if (!ddoc) throw(["fatal", "query_protocol_error", "uncached design doc: "+ddocId]);
+ var funPath = args.shift();
+ var cmd = funPath[0];
+ // the first member of the fun path determines the type of operation
+ var funArgs = args.shift();
+ if (ddoc_dispatch[cmd]) {
+ // get the function, call the command with it
+ var point = ddoc;
+ for (var i=0; i < funPath.length; i++) {
+ if (i+1 == funPath.length) {
+ fun = point[funPath[i]]
+ if (typeof fun != "function") {
+ fun = Couch.compileFunction(fun);
+ // cache the compiled fun on the ddoc
+ point[funPath[i]] = fun
+ };
+ } else {
+ point = point[funPath[i]]
+ }
+ };
-var dispatch = {
- "reset" : State.reset,
- "add_fun" : State.addFun,
- "map_doc" : Views.mapDoc,
- "reduce" : Views.reduce,
- "rereduce" : Views.rereduce,
- "validate" : Validate.validate,
- "show" : Render.show,
- "update" : Render.update,
- "list" : Render.list,
- "filter" : Filter.filter
-};
+ // run the correct responder with the cmd body
+ ddoc_dispatch[cmd].apply(null, [fun, ddoc, funArgs]);
+ } else {
+ // unknown command, quit and hope the restarted version is better
+ throw(["fatal", "unknown_command", "unknown ddoc command '" + cmd + "'"]);
+ }
+ }
+ }
+ };
+})();
-while (line = eval(readline())) {
- cmd = eval(line);
- line_length = line.length;
- try {
- cmdkey = cmd.shift();
- if (dispatch[cmdkey]) {
- // run the correct responder with the cmd body
- dispatch[cmdkey].apply(this, cmd);
+var Loop = function() {
+ var line, cmd, cmdkey, dispatch = {
+ "ddoc" : DDoc.ddoc,
+ // "view" : Views.handler,
+ "reset" : State.reset,
+ "add_fun" : State.addFun,
+ "map_doc" : Views.mapDoc,
+ "reduce" : Views.reduce,
+ "rereduce" : Views.rereduce
+ };
+ function handleError(e) {
+ var type = e[0];
+ if (type == "fatal") {
+ e[0] = "error"; // we tell the client it was a fatal error by dying
+ respond(e);
+ quit(-1);
+ } else if (type == "error") {
+ respond(e);
+ } else if (e.error && e.reason) {
+ // compatibility with old error format
+ respond(["error", e.error, e.reason]);
} else {
- // unknown command, quit and hope the restarted version is better
- respond({
- error: "query_server_error",
- reason: "unknown command '" + cmdkey + "'"});
- quit();
+ respond(["error","unnamed_error",e.toSource()]);
}
- } catch(e) {
- respond(e);
- }
+ };
+ while (line = readline()) {
+ cmd = eval('('+line+')');
+ State.line_length = line.length;
+ try {
+ cmdkey = cmd.shift();
+ if (dispatch[cmdkey]) {
+ // run the correct responder with the cmd body
+ dispatch[cmdkey].apply(null, cmd);
+ } else {
+ // unknown command, quit and hope the restarted version is better
+ throw(["fatal", "unknown_command", "unknown command '" + cmdkey + "'"]);
+ }
+ } catch(e) {
+ handleError(e);
+ }
+ };
};
+
+Loop();
diff --git a/share/server/render.js b/share/server/render.js
index f147af89..e19f31c4 100644
--- a/share/server/render.js
+++ b/share/server/render.js
@@ -11,152 +11,115 @@
// the License.
-// registerType(name, mime-type, mime-type, ...)
-//
-// Available in query server sandbox. TODO: The list is cleared on reset.
-// This registers a particular name with the set of mimetypes it can handle.
-// Whoever registers last wins.
-//
-// Example:
-// registerType("html", "text/html; charset=utf-8");
-
-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; charset=utf-8", "txt");
-registerType("html", "text/html; charset=utf-8");
-registerType("xhtml", "application/xhtml+xml", "xhtml");
-registerType("xml", "application/xml", "text/xml", "application/x-xml");
-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");
-// 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() {
- startResp = applyContentType((startResp || {}), responseContentType);
- respond(["start", chunks, startResp]);
- chunks = [];
- startResp = {};
-}
-
-function applyContentType(resp, responseContentType) {
- resp["headers"] = resp["headers"] || {};
- if (responseContentType) {
- resp["headers"]["Content-Type"] = resp["headers"]["Content-Type"] || responseContentType;
- }
- return resp;
-}
-
-// 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];
-};
-
-var mimeFuns = [], providesUsed, responseContentType;
-function provides(type, fun) {
- providesUsed = true;
- mimeFuns.push([type, fun]);
-};
-
-function runProvides(req) {
- var supportedMimes = [], bestFun, bestKey = null, accept = req.headers["Accept"];
- if (req.query && req.query.format) {
- bestKey = req.query.format;
- responseContentType = mimesByKey[bestKey][0];
- } else if (accept) {
- // log("using accept header: "+accept);
- mimeFuns.reverse().forEach(function(mimeFun) {
- var mimeKey = mimeFun[0];
- if (mimesByKey[mimeKey]) {
- supportedMimes = supportedMimes.concat(mimesByKey[mimeKey]);
- }
- });
- responseContentType = Mimeparse.bestMatch(supportedMimes, accept);
- bestKey = keysByMime[responseContentType];
- } else {
- // just do the first one
- bestKey = mimeFuns[0][0];
- responseContentType = mimesByKey[bestKey][0];
+var Mime = (function() {
+ // registerType(name, mime-type, mime-type, ...)
+ //
+ // Available in query server sandbox. TODO: The list is cleared on reset.
+ // This registers a particular name with the set of mimetypes it can handle.
+ // Whoever registers last wins.
+ //
+ // Example:
+ // registerType("html", "text/html; charset=utf-8");
+
+ var mimesByKey = {};
+ var keysByMime = {};
+ function registerType() {
+ 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; charset=utf-8", "txt");
+ registerType("html", "text/html; charset=utf-8");
+ registerType("xhtml", "application/xhtml+xml", "xhtml");
+ registerType("xml", "application/xml", "text/xml", "application/x-xml");
+ 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");
+ // http://www.ietf.org/rfc/rfc4627.txt
+ registerType("json", "application/json", "text/x-json");
- if (bestKey) {
- for (var i=0; i < mimeFuns.length; i++) {
- if (mimeFuns[i][0] == bestKey) {
- bestFun = mimeFuns[i][1];
- break;
- }
+
+ var mimeFuns = [];
+ function provides(type, fun) {
+ Mime.providesUsed = true;
+ mimeFuns.push([type, fun]);
+ };
+
+ function resetProvides() {
+ // set globals
+ Mime.providesUsed = false;
+ mimeFuns = [];
+ Mime.responseContentType = null;
+ };
+
+ function runProvides(req) {
+ var supportedMimes = [], bestFun, bestKey = null, accept = req.headers["Accept"];
+ if (req.query && req.query.format) {
+ bestKey = req.query.format;
+ Mime.responseContentType = mimesByKey[bestKey][0];
+ } else if (accept) {
+ // log("using accept header: "+accept);
+ mimeFuns.reverse().forEach(function(mimeFun) {
+ var mimeKey = mimeFun[0];
+ if (mimesByKey[mimeKey]) {
+ supportedMimes = supportedMimes.concat(mimesByKey[mimeKey]);
+ }
+ });
+ Mime.responseContentType = Mimeparse.bestMatch(supportedMimes, accept);
+ bestKey = keysByMime[Mime.responseContentType];
+ } else {
+ // just do the first one
+ bestKey = mimeFuns[0][0];
+ Mime.responseContentType = mimesByKey[bestKey][0];
+ }
+
+ if (bestKey) {
+ for (var i=0; i < mimeFuns.length; i++) {
+ if (mimeFuns[i][0] == bestKey) {
+ bestFun = mimeFuns[i][1];
+ break;
+ }
+ };
};
+
+ if (bestFun) {
+ return bestFun();
+ } else {
+ var supportedTypes = mimeFuns.map(function(mf) {return mimesByKey[mf[0]].join(', ') || mf[0]});
+ throw(["error","not_acceptable",
+ "Content-Type "+(accept||bestKey)+" not supported, try one of: "+supportedTypes.join(', ')]);
+ }
};
+
- if (bestFun) {
- // log("responding with: "+bestKey);
- return bestFun();
- } else {
- var supportedTypes = mimeFuns.map(function(mf) {return mimesByKey[mf[0]].join(', ') || mf[0]});
- throw({error:"not_acceptable", reason:"Content-Type "+(accept||bestKey)+" not supported, try one of: "+supportedTypes.join(', ')});
- }
-};
+ return {
+ registerType : registerType,
+ provides : provides,
+ resetProvides : resetProvides,
+ runProvides : runProvides
+ }
+})();
+
@@ -167,151 +130,202 @@ function runProvides(req) {
////
////
-var Render = {
- show : function(funSrc, doc, req) {
- var showFun = compileFunction(funSrc);
- runShow(showFun, doc, req, funSrc);
- },
- update : function(funSrc, doc, req) {
- var upFun = compileFunction(funSrc);
- runUpdate(upFun, doc, req, funSrc);
- },
- list : function(head, req) {
- runList(funs[0], head, req, funsrc[0]);
+var Render = (function() {
+ var chunks = [];
+
+
+ // Start chunks
+ var startResp = {};
+ function start(resp) {
+ startResp = resp || {};
+ };
+
+ function sendStart() {
+ startResp = applyContentType((startResp || {}), Mime.responseContentType);
+ respond(["start", chunks, startResp]);
+ chunks = [];
+ startResp = {};
}
-};
-function maybeWrapResponse(resp) {
- var type = typeof resp;
- if ((type == "string") || (type == "xml")) {
- return {body:resp};
- } else {
+ function applyContentType(resp, responseContentType) {
+ resp["headers"] = resp["headers"] || {};
+ if (responseContentType) {
+ resp["headers"]["Content-Type"] = resp["headers"]["Content-Type"] || responseContentType;
+ }
return resp;
}
-};
-function resetProvides() {
- // set globals
- providesUsed = false;
- mimeFuns = [];
- responseContentType = null;
-};
+ 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") {
+ throw(["fatal", "list_error", "not a row '" + json[0] + "'"]);
+ }
+ return json[1];
+ };
+
+
+ function maybeWrapResponse(resp) {
+ var type = typeof resp;
+ if ((type == "string") || (type == "xml")) {
+ return {body:resp};
+ } else {
+ return resp;
+ }
+ };
-// from http://javascript.crockford.com/remedial.html
-function typeOf(value) {
+ // from http://javascript.crockford.com/remedial.html
+ function typeOf(value) {
var s = typeof value;
if (s === 'object') {
- if (value) {
- if (value instanceof Array) {
- s = 'array';
- }
- } else {
- s = 'null';
+ if (value) {
+ if (value instanceof Array) {
+ s = 'array';
}
+ } else {
+ s = 'null';
+ }
}
return s;
-};
-
-function runShow(showFun, doc, req, funSrc) {
- try {
- resetProvides();
- var resp = showFun.apply(null, [doc, req]);
-
- if (providesUsed) {
- resp = runProvides(req);
- resp = applyContentType(maybeWrapResponse(resp), responseContentType);
- }
+ };
- var type = typeOf(resp);
- if (type == 'object' || type == 'string') {
- respond(["resp", maybeWrapResponse(resp)]);
- } else {
- renderError("undefined response from show function");
- }
- } catch(e) {
- respondError(e, funSrc, true);
- }
-};
-
-function runUpdate(renderFun, doc, req, funSrc) {
- try {
- var result = renderFun.apply(null, [doc, req]);
- var doc = result[0];
- var resp = result[1];
- if (resp) {
- respond(["up", doc, maybeWrapResponse(resp)]);
- } else {
- renderError("undefined response from update function");
+ function runShow(fun, ddoc, args) {
+ try {
+ Mime.resetProvides();
+ var resp = fun.apply(ddoc, args);
+
+ if (Mime.providesUsed) {
+ resp = Mime.runProvides(args[1]);
+ resp = applyContentType(maybeWrapResponse(resp), Mime.responseContentType);
+ }
+
+ var type = typeOf(resp);
+ if (type == 'object' || type == 'string') {
+ respond(["resp", maybeWrapResponse(resp)]);
+ } else {
+ throw(["error", "render_error", "undefined response from show function"]);
+ }
+ } catch(e) {
+ renderError(e, fun.toSource());
}
- } catch(e) {
- respondError(e, funSrc, true);
- }
-};
-
-function resetList() {
- gotRow = false;
- lastRow = false;
- chunks = [];
- startResp = {};
-};
-
-function runList(listFun, head, req, funSrc) {
- try {
- if (listFun.arity > 2) {
- throw("the list API has changed for CouchDB 0.10, please upgrade your code");
+ };
+
+ function runUpdate(fun, ddoc, args) {
+ try {
+ var verb = args[1].verb;
+ // for analytics logging applications you might want to remove the next line
+ if (verb == "GET") throw(["error","method_not_allowed","Update functions do not allow GET"]);
+ var result = fun.apply(ddoc, args);
+ var doc = result[0];
+ var resp = result[1];
+ var type = typeOf(resp);
+ if (type == 'object' || type == 'string') {
+ respond(["up", doc, maybeWrapResponse(resp)]);
+ } else {
+ throw(["error", "render_error", "undefined response from update function"]);
+ }
+ } catch(e) {
+ renderError(e, fun.toSource());
}
-
- resetProvides();
- resetList();
-
- var tail = listFun.apply(null, [head, req]);
-
- if (providesUsed) {
- tail = runProvides(req);
+ };
+
+ function resetList() {
+ gotRow = false;
+ lastRow = false;
+ chunks = [];
+ startResp = {};
+ };
+
+ function runList(listFun, ddoc, args) {
+ try {
+ Mime.resetProvides();
+ resetList();
+ head = args[0]
+ req = args[1]
+ var tail = listFun.apply(ddoc, args);
+
+ if (Mime.providesUsed) {
+ tail = Mime.runProvides(req);
+ }
+ if (!gotRow) getRow();
+ if (typeof tail != "undefined") {
+ chunks.push(tail);
+ }
+ blowChunks("end");
+ } catch(e) {
+ renderError(e, listFun.toSource());
}
-
- if (!gotRow) {
- getRow();
+ };
+
+ function renderError(e, funSrc) {
+ if (e.error && e.reason || e[0] == "error" || e[0] == "fatal") {
+ throw(e);
+ } else {
+ var logMessage = "function raised error: "+e.toSource()+" \nstacktrace: "+e.stack;
+ log(logMessage);
+ throw(["error", "render_error", logMessage]);
}
- if (typeof tail != "undefined") {
- chunks.push(tail);
+ };
+
+ function escapeHTML(string) {
+ return string && string.replace(/&/g, "&amp;")
+ .replace(/</g, "&lt;")
+ .replace(/>/g, "&gt;");
+ };
+
+
+ return {
+ start : start,
+ send : send,
+ getRow : getRow,
+ show : function(fun, ddoc, args) {
+ // var showFun = Couch.compileFunction(funSrc);
+ runShow(fun, ddoc, args);
+ },
+ update : function(fun, ddoc, args) {
+ // var upFun = Couch.compileFunction(funSrc);
+ runUpdate(fun, ddoc, args);
+ },
+ list : function(fun, ddoc, args) {
+ runList(fun, ddoc, args);
}
- blowChunks("end");
- } catch(e) {
- respondError(e, funSrc, false);
- }
-};
-
-function renderError(m) {
- respond({error : "render_error", reason : m});
-}
-
-function respondError(e, funSrc, htmlErrors) {
- if (e.error && e.reason) {
- respond(e);
- } else {
- var logMessage = "function raised error: "+e.toString();
- log(logMessage);
- log("stacktrace: "+e.stack);
- var errorMessage = htmlErrors ? htmlRenderError(e, funSrc) : logMessage;
- renderError(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};
-};
+ };
+})();
+
+// send = Render.send;
+// getRow = Render.getRow;
+// start = Render.start;
+
+// unused. this will be handled in the Erlang side of things.
+// 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};
+// };
diff --git a/share/server/state.js b/share/server/state.js
index b9bd87aa..9af9e475 100644
--- a/share/server/state.js
+++ b/share/server/state.js
@@ -10,26 +10,18 @@
// 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 query_config = {};
-var State = (function() {
- return {
- reset : function(config) {
- // clear the globals and run gc
- funs = [];
- funsrc = [];
- query_config = config;
- init_sandbox();
- 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");
- }
+var State = {
+ reset : function(config) {
+ // clear the globals and run gc
+ State.funs = [];
+ State.query_config = config || {};
+ init_sandbox();
+ gc();
+ print("true"); // indicates success
+ },
+ addFun : function(newFun) {
+ // Compile to a function and add it to funs array
+ State.funs.push(Couch.compileFunction(newFun));
+ print("true");
}
-})();
+}
diff --git a/share/server/util.js b/share/server/util.js
index 1f69bf16..bd4abc1d 100644
--- a/share/server/util.js
+++ b/share/server/util.js
@@ -10,13 +10,50 @@
// License for the specific language governing permissions and limitations under
// the License.
-toJSON.subs = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f',
+var Couch = {
+ // moving this away from global so we can move to json2.js later
+ toJSON : function (val) {
+ if (typeof(val) == "undefined") {
+ throw "Cannot encode 'undefined' value as JSON";
+ }
+ if (typeof(val) == "xml") { // E4X support
+ val = val.toXMLString();
+ }
+ if (val === null) { return "null"; }
+ return (Couch.toJSON.dispatcher[val.constructor.name])(val);
+ },
+ compileFunction : function(source) {
+ if (!source) throw(["error","not_found","missing function"]);
+ try {
+ var functionObject = sandbox ? evalcx(source, sandbox) : eval(source);
+ } catch (err) {
+ throw(["error", "compilation_error", err.toSource() + " (" + source + ")"]);
+ };
+ if (typeof(functionObject) == "function") {
+ return functionObject;
+ } else {
+ throw(["error","compilation_error",
+ "Expression does not eval to a function. (" + source.toSource() + ")"]);
+ };
+ },
+ recursivelySeal : function(obj) {
+ // seal() is broken in current Spidermonkey
+ seal(obj);
+ for (var propname in obj) {
+ if (typeof doc[propname] == "object") {
+ recursivelySeal(doc[propname]);
+ }
+ }
+ }
+}
+
+Couch.toJSON.subs = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f',
'\r': '\\r', '"' : '\\"', '\\': '\\\\'};
-toJSON.dispatcher = {
+Couch.toJSON.dispatcher = {
"Array": function(v) {
var buf = [];
for (var i = 0; i < v.length; i++) {
- buf.push(toJSON(v[i]));
+ buf.push(Couch.toJSON(v[i]));
}
return "[" + buf.join(",") + "]";
},
@@ -42,14 +79,14 @@ toJSON.dispatcher = {
if (!v.hasOwnProperty(k) || typeof(k) !== "string" || v[k] === undefined) {
continue;
}
- buf.push(toJSON(k) + ": " + toJSON(v[k]));
+ buf.push(Couch.toJSON(k) + ": " + Couch.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 = toJSON.subs[b];
+ var c = Couch.toJSON.subs[b];
if (c) return c;
c = b.charCodeAt();
return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
@@ -59,56 +96,22 @@ toJSON.dispatcher = {
}
};
-function toJSON(val) {
- if (typeof(val) == "undefined") {
- throw "Cannot encode 'undefined' value as JSON";
- }
- if (typeof(val) == "xml") { // E4X support
- val = val.toXMLString();
- }
- if (val === null) { return "null"; }
- return (toJSON.dispatcher[val.constructor.name])(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]);
- }
- }
-}
-
// prints the object as JSON, and rescues and logs any toJSON() related errors
function respond(obj) {
try {
- print(toJSON(obj));
+ print(Couch.toJSON(obj));
} catch(e) {
log("Error converting object to JSON: " + e.toString());
+ log("error on obj: "+ obj.toSource());
}
};
-log = function(message) {
- // return;
+function log(message) {
+ // return; // idea: query_server_config option for log level
if (typeof message == "undefined") {
message = "Error: attempting to log message of 'undefined'.";
} else if (typeof message != "string") {
- message = toJSON(message);
+ message = Couch.toJSON(message);
}
respond(["log", message]);
};
diff --git a/share/server/validate.js b/share/server/validate.js
index 5e5e5f9f..76a14129 100644
--- a/share/server/validate.js
+++ b/share/server/validate.js
@@ -11,10 +11,9 @@
// the License.
var Validate = {
- validate : function(funSrc, newDoc, oldDoc, userCtx) {
- var validateFun = compileFunction(funSrc);
+ validate : function(fun, ddoc, args) {
try {
- validateFun(newDoc, oldDoc, userCtx);
+ fun.apply(ddoc, args);
print("1");
} catch (error) {
respond(error);
diff --git a/share/server/views.js b/share/server/views.js
index 1f12ad2b..ffe63377 100644
--- a/share/server/views.js
+++ b/share/server/views.js
@@ -10,58 +10,76 @@
// 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() {
+ var map_results = []; // holds temporary emitted values during doc map
+
function runReduce(reduceFuns, keys, values, rereduce) {
for (var i in reduceFuns) {
- reduceFuns[i] = compileFunction(reduceFuns[i]);
- }
+ reduceFuns[i] = Couch.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 + ")");
+ handleViewError(err);
+ // if the error is not fatal, ignore the results and continue
reductions[i] = null;
}
- }
- var reduce_line = toJSON(reductions);
+ };
+ var reduce_line = Couch.toJSON(reductions);
var reduce_length = reduce_line.length;
- if (query_config && query_config.reduce_limit &&
- reduce_length > 200 && ((reduce_length * 2) > line.length)) {
- var reduce_preview = "Current output: '"+(reduce_line.substring(0,100) + "'... (first 100 of "+reduce_length+' bytes)');
-
- throw {
- error:"reduce_overflow_error",
- reason: "Reduce output must shrink more rapidly: "+reduce_preview+""
- };
+ // TODO make reduce_limit config into a number
+ if (State.query_config && State.query_config.reduce_limit &&
+ reduce_length > 200 && ((reduce_length * 2) > State.line_length)) {
+ var reduce_preview = "Current output: '"+(reduce_line.substring(0,100) + "'... (first 100 of "+reduce_length+" bytes)");
+ throw(["error",
+ "reduce_overflow_error",
+ "Reduce output must shrink more rapidly: "+reduce_preview]);
} else {
print("[true," + reduce_line + "]");
}
};
+ function handleViewError(err, doc) {
+ 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.
+ //
+ // In this case we abort map processing but don't destroy the
+ // JavaScript process. If you need to destroy the JavaScript
+ // process, throw the error form matched by the block below.
+ throw(["error", "map_runtime_error", "function raised 'fatal_error'"]);
+ } else if (err[0] == "fatal") {
+ // Throwing errors of the form ["fatal","error_key","reason"]
+ // will kill the OS process. This is not normally what you want.
+ throw(err);
+ }
+ var message = "function raised exception " + err.toSource();
+ if (doc) message += " with doc._id " + doc._id;
+ log(message);
+ };
+
return {
+ // 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;
+ },
reduce : function(reduceFuns, kvs) {
var keys = new Array(kvs.length);
var values = new Array(kvs.length);
@@ -101,25 +119,15 @@ var Views = (function() {
recursivelySeal(doc); // seal to prevent map functions from changing doc
*/
var buf = [];
- for (var i = 0; i < funs.length; i++) {
+ for (var i = 0; i < State.funs.length; i++) {
map_results = [];
try {
- funs[i](doc);
- buf.push(toJSON(map_results));
+ State.funs[i](doc);
+ buf.push(Couch.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);
+ handleViewError(err, doc);
+ // If the error is not fatal, we treat the doc as if it
+ // did not emit anything, by buffering an empty array.
buf.push("[]");
}
}