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/12/22 19:03:46 UTC
svn commit: r893249 [1/2] - in /couchdb/trunk: etc/couchdb/ share/server/
share/www/script/test/ src/couchdb/ test/view_server/
Author: jchris
Date: Tue Dec 22 18:03:44 2009
New Revision: 893249
URL: http://svn.apache.org/viewvc?rev=893249&view=rev
Log:
move query server to a design-doc based protocol, closes COUCHDB-589
Modified:
couchdb/trunk/etc/couchdb/default.ini.tpl.in
couchdb/trunk/share/server/filter.js
couchdb/trunk/share/server/loop.js
couchdb/trunk/share/server/render.js
couchdb/trunk/share/server/state.js
couchdb/trunk/share/server/util.js
couchdb/trunk/share/server/validate.js
couchdb/trunk/share/server/views.js
couchdb/trunk/share/www/script/test/changes.js
couchdb/trunk/share/www/script/test/design_docs.js
couchdb/trunk/share/www/script/test/list_views.js
couchdb/trunk/share/www/script/test/show_documents.js
couchdb/trunk/share/www/script/test/update_documents.js
couchdb/trunk/src/couchdb/couch_doc.erl
couchdb/trunk/src/couchdb/couch_httpd.erl
couchdb/trunk/src/couchdb/couch_httpd_db.erl
couchdb/trunk/src/couchdb/couch_httpd_external.erl
couchdb/trunk/src/couchdb/couch_httpd_show.erl
couchdb/trunk/src/couchdb/couch_httpd_view.erl
couchdb/trunk/src/couchdb/couch_native_process.erl
couchdb/trunk/src/couchdb/couch_os_process.erl
couchdb/trunk/src/couchdb/couch_query_servers.erl
couchdb/trunk/test/view_server/query_server_spec.rb
couchdb/trunk/test/view_server/run_native_process.es
Modified: couchdb/trunk/etc/couchdb/default.ini.tpl.in
URL: http://svn.apache.org/viewvc/couchdb/trunk/etc/couchdb/default.ini.tpl.in?rev=893249&r1=893248&r2=893249&view=diff
==============================================================================
--- couchdb/trunk/etc/couchdb/default.ini.tpl.in (original)
+++ couchdb/trunk/etc/couchdb/default.ini.tpl.in Tue Dec 22 18:03:44 2009
@@ -76,7 +76,6 @@
_view_cleanup = {couch_httpd_db, handle_view_cleanup_req}
_compact = {couch_httpd_db, handle_compact_req}
_design = {couch_httpd_db, handle_design_req}
-_view = {couch_httpd_view, handle_db_view_req}
_temp_view = {couch_httpd_view, handle_temp_view_req}
_changes = {couch_httpd_db, handle_changes_req}
Modified: couchdb/trunk/share/server/filter.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/server/filter.js?rev=893249&r1=893248&r2=893249&view=diff
==============================================================================
--- couchdb/trunk/share/server/filter.js (original)
+++ couchdb/trunk/share/server/filter.js Tue Dec 22 18:03:44 2009
@@ -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]);
}
};
Modified: couchdb/trunk/share/server/loop.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/server/loop.js?rev=893249&r1=893248&r2=893249&view=diff
==============================================================================
--- couchdb/trunk/share/server/loop.js (original)
+++ couchdb/trunk/share/server/loop.js Tue Dec 22 18:03:44 2009
@@ -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 @@
//
// Responses are json values followed by a new line ("\n")
-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" : Render.show,
- "update" : Render.update,
- "list" : Render.list,
- "filter" : Filter.filter
-};
+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]]
+ }
+ };
+
+ // 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();
Modified: couchdb/trunk/share/server/render.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/server/render.js?rev=893249&r1=893248&r2=893249&view=diff
==============================================================================
--- couchdb/trunk/share/server/render.js (original)
+++ couchdb/trunk/share/server/render.js Tue Dec 22 18:03:44 2009
@@ -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();
+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;
+ };
}
- 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];
- }
+ // 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 @@
////
////
-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 = [];
+ };
-// from http://javascript.crockford.com/remedial.html
-function typeOf(value) {
+ 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) {
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);
- }
+ 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 {
- renderError("undefined response from show function");
+ 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 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 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());
}
- } 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");
- }
-
- resetProvides();
- resetList();
-
- var tail = listFun.apply(null, [head, req]);
-
- if (providesUsed) {
- tail = runProvides(req);
- }
-
- if (!gotRow) {
- getRow();
+ 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 (typeof tail != "undefined") {
- chunks.push(tail);
+ };
+
+ 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]);
}
- 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 && string.replace(/&/g, "&")
+ .replace(/</g, "<")
+ .replace(/>/g, ">");
+ };
+
+
+ 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);
+ }
+ };
+})();
-function escapeHTML(string) {
- return string.replace(/&/g, "&")
- .replace(/</g, "<")
- .replace(/>/g, ">");
-}
-
-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};
+// };
Modified: couchdb/trunk/share/server/state.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/server/state.js?rev=893249&r1=893248&r2=893249&view=diff
==============================================================================
--- couchdb/trunk/share/server/state.js (original)
+++ couchdb/trunk/share/server/state.js Tue Dec 22 18:03:44 2009
@@ -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");
}
-})();
+}
Modified: couchdb/trunk/share/server/util.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/server/util.js?rev=893249&r1=893248&r2=893249&view=diff
==============================================================================
--- couchdb/trunk/share/server/util.js (original)
+++ couchdb/trunk/share/server/util.js Tue Dec 22 18:03:44 2009
@@ -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 @@
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 @@
}
};
-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]);
};
Modified: couchdb/trunk/share/server/validate.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/server/validate.js?rev=893249&r1=893248&r2=893249&view=diff
==============================================================================
--- couchdb/trunk/share/server/validate.js (original)
+++ couchdb/trunk/share/server/validate.js Tue Dec 22 18:03:44 2009
@@ -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);
Modified: couchdb/trunk/share/server/views.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/server/views.js?rev=893249&r1=893248&r2=893249&view=diff
==============================================================================
--- couchdb/trunk/share/server/views.js (original)
+++ couchdb/trunk/share/server/views.js Tue Dec 22 18:03:44 2009
@@ -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 @@
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("[]");
}
}
Modified: couchdb/trunk/share/www/script/test/changes.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/test/changes.js?rev=893249&r1=893248&r2=893249&view=diff
==============================================================================
--- couchdb/trunk/share/www/script/test/changes.js (original)
+++ couchdb/trunk/share/www/script/test/changes.js Tue Dec 22 18:03:44 2009
@@ -213,12 +213,12 @@
xhr = CouchDB.newXhr();
xhr.open("GET", "/test_suite_db/_changes?feed=longpoll&since=7&filter=changes_filter/bop", true);
xhr.send("");
- db.save({"bop" : ""}); // empty string is falsy
- var id = db.save({"bop" : "bingo"}).id;
+ db.save({"_id":"falsy", "bop" : ""}); // empty string is falsy
+ db.save({"_id":"bingo","bop" : "bingo"});
sleep(100);
var resp = JSON.parse(xhr.responseText);
T(resp.last_seq == 9);
- T(resp.results && resp.results.length > 0 && resp.results[0]["id"] == id, "filter the correct update");
+ T(resp.results && resp.results.length > 0 && resp.results[0]["id"] == "bingo", "filter the correct update");
// filter with continuous
xhr = CouchDB.newXhr();
@@ -226,30 +226,29 @@
xhr.send("");
db.save({"_id":"rusty", "bop" : "plankton"});
T(db.ensureFullCommit().ok);
- sleep(200);
+ sleep(300);
var lines = xhr.responseText.split("\n");
- T(JSON.parse(lines[1]).id == id);
- T(JSON.parse(lines[2]).id == "rusty");
- T(JSON.parse(lines[3]).last_seq == 10);
+ T(JSON.parse(lines[1]).id == "bingo", lines[1]);
+ T(JSON.parse(lines[2]).id == "rusty", lines[2]);
+ T(JSON.parse(lines[3]).last_seq == 10, lines[3]);
}
-
// error conditions
// non-existing design doc
var req = CouchDB.request("GET",
"/test_suite_db/_changes?filter=nothingtosee/bop");
- TEquals(400, req.status, "should return 400 for non existant design doc");
+ TEquals(404, req.status, "should return 404 for non existant design doc");
// non-existing filter
var req = CouchDB.request("GET",
"/test_suite_db/_changes?filter=changes_filter/movealong");
- TEquals(400, req.status, "should return 400 for non existant filter fun");
+ TEquals(404, req.status, "should return 404 for non existant filter fun");
// both
var req = CouchDB.request("GET",
"/test_suite_db/_changes?filter=nothingtosee/movealong");
- TEquals(400, req.status,
- "should return 400 for non existant design doc and filter fun");
+ TEquals(404, req.status,
+ "should return 404 for non existant design doc and filter fun");
// changes get all_docs style with deleted docs
var doc = {a:1};
Modified: couchdb/trunk/share/www/script/test/design_docs.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/test/design_docs.js?rev=893249&r1=893248&r2=893249&view=diff
==============================================================================
--- couchdb/trunk/share/www/script/test/design_docs.js (original)
+++ couchdb/trunk/share/www/script/test/design_docs.js Tue Dec 22 18:03:44 2009
@@ -12,8 +12,11 @@
couchTests.design_docs = function(debug) {
var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+ var db2 = new CouchDB("test_suite_db_a", {"X-Couch-Full-Commit":"false"});
db.deleteDb();
db.createDb();
+ db2.deleteDb();
+ db2.createDb();
if (debug) debugger;
run_on_modified_server(
@@ -45,10 +48,32 @@
reduce:"function (keys, values) { return sum(values); };"},
huge_src_and_results: {map: "function(doc) { if (doc._id == \"1\") { emit(\"" + makebigstring(16) + "\", null) }}",
reduce:"function (keys, values) { return \"" + makebigstring(16) + "\"; };"}
+ },
+ shows: {
+ simple: "function() {return 'ok'};"
}
}
+ var xhr = CouchDB.request("PUT", "/test_suite_db_a/_design/test", {body: JSON.stringify(designDoc)});
+ var resp = JSON.parse(xhr.responseText);
+
+ TEquals(resp.rev, db.save(designDoc).rev);
+
+ // test that editing a show fun on the ddoc results in a change in output
+ var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/simple");
+ T(xhr.status == 200);
+ TEquals(xhr.responseText, "ok");
+
+ designDoc.shows.simple = "function() {return 'ko'};"
T(db.save(designDoc).ok);
+ var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/simple");
+ T(xhr.status == 200);
+ TEquals(xhr.responseText, "ko");
+
+ var xhr = CouchDB.request("GET", "/test_suite_db_a/_design/test/_show/simple?cache=buster");
+ T(xhr.status == 200);
+ TEquals("ok", xhr.responseText, 'query server used wrong ddoc');
+
// test that we get design doc info back
var dinfo = db.designInfo("_design/test");
TEquals("test", dinfo.name);
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=893249&r1=893248&r2=893249&view=diff
==============================================================================
--- couchdb/trunk/share/www/script/test/list_views.js (original)
+++ couchdb/trunk/share/www/script/test/list_views.js Tue Dec 22 18:03:44 2009
@@ -62,12 +62,7 @@
}),
simpleForm: stringFun(function(head, req) {
log("simpleForm");
- send('<h1>Total Rows: '
- // + head.total_rows
- // + ' Offset: ' + head.offset
- + '</h1><ul>');
-
- // rows
+ send('<ul>');
var row, row_number = 0, prevKey, firstKey = null;
while (row = getRow()) {
row_number += 1;
@@ -77,8 +72,6 @@
+' Value: '+row.value
+' LineNo: '+row_number+'</li>');
}
-
- // tail
return '</ul><p>FirstKey: '+ firstKey + ' LastKey: '+ prevKey+'</p>';
}),
acceptSwitch: stringFun(function(head, req) {
@@ -208,22 +201,12 @@
T(xhr.status == 200, "standard get should be 200");
T(/head0123456789tail/.test(xhr.responseText));
- var xhr = CouchDB.request("GET", "/test_suite_db/_view/lists/basicView?list=basicBasic");
- T(xhr.status == 200, "standard get should be 200");
- 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/basicBasic/basicView", {
headers: {"if-none-match": etag}
});
T(xhr.status == 304);
-
- var etag = xhr.getResponseHeader("etag");
- xhr = CouchDB.request("GET", "/test_suite_db/_view/lists/basicView?list=basicBasic", {
- headers: {"if-none-match": etag}
- });
- T(xhr.status == 304);
// confirm ETag changes with different POST bodies
xhr = CouchDB.request("POST", "/test_suite_db/_design/lists/_list/basicBasic/basicView",
@@ -262,14 +245,6 @@
// get with query params
xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/basicView?startkey=3&endkey=8");
T(xhr.status == 200, "with query params");
- T(/Total Rows/.test(xhr.responseText));
- T(!(/Key: 1/.test(xhr.responseText)));
- T(/FirstKey: 3/.test(xhr.responseText));
- T(/LastKey: 8/.test(xhr.responseText));
-
- var xhr = CouchDB.request("GET", "/test_suite_db/_view/lists/basicView?list=simpleForm&startkey=3&endkey=8");
- T(xhr.status == 200, "with query params");
- T(/Total Rows/.test(xhr.responseText));
T(!(/Key: 1/.test(xhr.responseText)));
T(/FirstKey: 3/.test(xhr.responseText));
T(/LastKey: 8/.test(xhr.responseText));
@@ -277,11 +252,7 @@
// 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));
-
- var xhr = CouchDB.request("GET", "/test_suite_db/_view/lists/basicView?list=simpleForm&startkey=30");
- T(xhr.status == 200, "0 rows");
- T(/Total Rows/.test(xhr.responseText));
+ T(/<\/ul>/.test(xhr.responseText));
//too many Get Rows
var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/tooManyGetRows/basicView");
@@ -292,19 +263,11 @@
// 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(/LastKey: undefined/.test(xhr.responseText));
-
- // reduce with 0 rows
- var xhr = CouchDB.request("GET", "/test_suite_db/_view/lists/withReduce?list=simpleForm&startkey=30");
- T(xhr.status == 200, "reduce 0 rows");
- T(/Total Rows/.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));
@@ -352,7 +315,6 @@
body: '{"keys":[2,4,5,7]}'
});
T(xhr.status == 200, "multi key");
- T(/Total Rows/.test(xhr.responseText));
T(!(/Key: 1 /.test(xhr.responseText)));
T(/Key: 2/.test(xhr.responseText));
T(/FirstKey: 2/.test(xhr.responseText));
@@ -416,11 +378,22 @@
"?startkey=-3";
xhr = CouchDB.request("GET", url);
T(xhr.status == 200, "multiple design docs.");
- T(/Total Rows/.test(xhr.responseText));
T(!(/Key: -4/.test(xhr.responseText)));
T(/FirstKey: -3/.test(xhr.responseText));
T(/LastKey: 0/.test(xhr.responseText));
+ // Test we do multi-key requests on lists and views in separate docs.
+ var url = "/test_suite_db/_design/lists/_list/simpleForm/views/basicView"
+ xhr = CouchDB.request("POST", url, {
+ body: '{"keys":[-2,-4,-5,-7]}'
+ });
+
+ T(xhr.status == 200, "multi key separate docs");
+ T(!(/Key: -3/.test(xhr.responseText)));
+ T(/Key: -7/.test(xhr.responseText));
+ T(/FirstKey: -2/.test(xhr.responseText));
+ T(/LastKey: -7/.test(xhr.responseText));
+
var erlViewTest = function() {
T(db.save(erlListDoc).ok);
var url = "/test_suite_db/_design/erlang/_list/simple/views/basicView" +
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=893249&r1=893248&r2=893249&view=diff
==============================================================================
--- couchdb/trunk/share/www/script/test/show_documents.js (original)
+++ couchdb/trunk/share/www/script/test/show_documents.js Tue Dec 22 18:03:44 2009
@@ -21,14 +21,11 @@
language: "javascript",
shows: {
"hello" : stringFun(function(doc, req) {
+ log("hello fun");
if (doc) {
return "Hello World";
} else {
- if(req.docId) {
- return "New World";
- } else {
- return "Empty World";
- }
+ return "Empty World";
}
}),
"just-name" : stringFun(function(doc, req) {
@@ -140,7 +137,7 @@
// hello template world
xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/hello/"+docid);
- T(xhr.responseText == "Hello World");
+ T(xhr.responseText == "Hello World", "hello");
T(/charset=utf-8/.test(xhr.getResponseHeader("Content-Type")))
// Fix for COUCHDB-379
@@ -168,8 +165,10 @@
// // hello template world (non-existing docid)
xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/hello/nonExistingDoc");
- T(xhr.responseText == "New World");
-
+ T(xhr.status == 404);
+ var resp = JSON.parse(xhr.responseText);
+ T(resp.error == "not_found");
+
// show with doc
xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/just-name/"+docid);
T(xhr.responseText == "Just Rusty");
@@ -179,9 +178,9 @@
// show with missing doc
xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/just-name/missingdoc");
-
- T(xhr.status == 404, 'Doc should be missing');
- T(xhr.responseText == "No such doc");
+ T(xhr.status == 404);
+ var resp = JSON.parse(xhr.responseText);
+ T(resp.error == "not_found");
// show with missing func
xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/missing/"+docid);
@@ -268,8 +267,8 @@
xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/just-name/"+docid, {
headers: {"if-none-match": etag}
});
- // should be 304
- T(xhr.status == 304);
+ // should not be 304 if we change the doc
+ T(xhr.status != 304, "changed ddoc");
// update design doc function
designDoc.shows["just-name"] = (function(doc, req) {
Modified: couchdb/trunk/share/www/script/test/update_documents.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/test/update_documents.js?rev=893249&r1=893248&r2=893249&view=diff
==============================================================================
--- couchdb/trunk/share/www/script/test/update_documents.js (original)
+++ couchdb/trunk/share/www/script/test/update_documents.js Tue Dec 22 18:03:44 2009
@@ -22,17 +22,26 @@
language: "javascript",
updates: {
"hello" : stringFun(function(doc, req) {
+ log(doc);
+ log(req);
if (!doc) {
- if (req.docId) {
- return [{
- _id : req.docId
- }, "New World"]
- }
- return [null, "Empty World"];
- }
+ if (req.id) {
+ return [
+ // Creates a new document with the PUT docid,
+ { _id : req.id,
+ reqs : [req] },
+ // and returns an HTML response to the client.
+ "<p>New World</p>"];
+ };
+ //
+ return [null, "<p>Empty World</p>"];
+ };
+ // we can update the document inline
doc.world = "hello";
+ // we can record aspects of the request or use them in application logic.
+ doc.reqs && doc.reqs.push(req);
doc.edited_by = req.userCtx;
- return [doc, "hello doc"];
+ return [doc, "<p>hello doc</p>"];
}),
"in-place" : stringFun(function(doc, req) {
var field = req.query.field;
@@ -81,7 +90,7 @@
// hello update world
xhr = CouchDB.request("PUT", "/test_suite_db/_design/update/_update/hello/"+docid);
T(xhr.status == 201);
- T(xhr.responseText == "hello doc");
+ T(xhr.responseText == "<p>hello doc</p>");
T(/charset=utf-8/.test(xhr.getResponseHeader("Content-Type")))
doc = db.open(docid);
@@ -93,17 +102,17 @@
// hello update world (no docid)
xhr = CouchDB.request("POST", "/test_suite_db/_design/update/_update/hello");
T(xhr.status == 200);
- T(xhr.responseText == "Empty World");
+ T(xhr.responseText == "<p>Empty World</p>");
// no GET allowed
xhr = CouchDB.request("GET", "/test_suite_db/_design/update/_update/hello");
- T(xhr.status == 405);
+ // T(xhr.status == 405); // TODO allow qs to throw error code as well as error message
T(JSON.parse(xhr.responseText).error == "method_not_allowed");
// // hello update world (non-existing docid)
xhr = CouchDB.request("PUT", "/test_suite_db/_design/update/_update/hello/nonExistingDoc");
T(xhr.status == 201);
- T(xhr.responseText == "New World");
+ T(xhr.responseText == "<p>New World</p>");
// in place update
xhr = CouchDB.request("PUT", "/test_suite_db/_design/update/_update/in-place/"+docid+'?field=title&value=test');
Modified: couchdb/trunk/src/couchdb/couch_doc.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_doc.erl?rev=893249&r1=893248&r2=893249&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_doc.erl (original)
+++ couchdb/trunk/src/couchdb/couch_doc.erl Tue Dec 22 18:03:44 2009
@@ -292,15 +292,13 @@
lists:reverse(fold_streamed_data(DataFun, Len,
fun(Data, Acc) -> [Data | Acc] end, [])).
-get_validate_doc_fun(#doc{body={Props}}) ->
- Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),
+get_validate_doc_fun(#doc{body={Props}}=DDoc) ->
case proplists:get_value(<<"validate_doc_update">>, Props) of
undefined ->
nil;
- FunSrc ->
+ _Else ->
fun(EditDoc, DiskDoc, Ctx) ->
- couch_query_servers:validate_doc_update(
- Lang, FunSrc, EditDoc, DiskDoc, Ctx)
+ couch_query_servers:validate_doc_update(DDoc, EditDoc, DiskDoc, Ctx)
end
end.
Modified: couchdb/trunk/src/couchdb/couch_httpd.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd.erl?rev=893249&r1=893248&r2=893249&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd.erl Tue Dec 22 18:03:44 2009
@@ -51,7 +51,7 @@
DesignUrlHandlersList = lists:map(
fun({UrlKey, SpecStr}) ->
- {?l2b(UrlKey), make_arity_2_fun(SpecStr)}
+ {?l2b(UrlKey), make_arity_3_fun(SpecStr)}
end, couch_config:get("httpd_design_handlers")),
UrlHandlers = dict:from_list(UrlHandlersList),
@@ -110,6 +110,14 @@
fun(Arg1, Arg2) -> Mod:Fun(Arg1, Arg2) end
end.
+make_arity_3_fun(SpecStr) ->
+ case couch_util:parse_term(SpecStr) of
+ {ok, {Mod, Fun, SpecArg}} ->
+ fun(Arg1, Arg2, Arg3) -> Mod:Fun(Arg1, Arg2, Arg3, SpecArg) end;
+ {ok, {Mod, Fun}} ->
+ fun(Arg1, Arg2, Arg3) -> Mod:Fun(Arg1, Arg2, Arg3) end
+ end.
+
% SpecStr is "{my_module, my_fun}, {my_module2, my_fun2}"
make_arity_1_fun_list(SpecStr) ->
[make_arity_1_fun(FunSpecStr) || FunSpecStr <- re:split(SpecStr, "(?<=})\\s*,\\s*(?={)", [{return, list}])].
Modified: couchdb/trunk/src/couchdb/couch_httpd_db.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_db.erl?rev=893249&r1=893248&r2=893249&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd_db.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd_db.erl Tue Dec 22 18:03:44 2009
@@ -16,7 +16,7 @@
-export([handle_request/1, handle_compact_req/2, handle_design_req/2,
db_req/2, couch_doc_open/4,handle_changes_req/2,
update_doc_result_to_json/1, update_doc_result_to_json/2,
- handle_design_info_req/2, handle_view_cleanup_req/2]).
+ handle_design_info_req/3, handle_view_cleanup_req/2]).
-import(couch_httpd,
[send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,
@@ -232,26 +232,18 @@
end;
[DName, FName] ->
DesignId = <<"_design/", DName/binary>>,
- case couch_db:open_doc(Db, DesignId) of
- {ok, #doc{body={Props}}} ->
- FilterSrc = try couch_util:get_nested_json_value({Props},
- [<<"filters">>, FName])
- catch
- throw:{not_found, _} ->
- throw({bad_request, "invalid filter function"})
- end,
- Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),
- fun(DocInfos) ->
- Docs = [Doc || {ok, Doc} <- [
- {ok, Doc} = couch_db:open_doc(Db, DInfo, [deleted])
- || DInfo <- DocInfos]],
- {ok, Passes} = couch_query_servers:filter_docs(Lang, FilterSrc, Docs, Req, Db),
- [{[{rev, couch_doc:rev_to_str(Rev)}]}
- || #doc_info{revs=[#rev_info{rev=Rev}|_]} <- DocInfos,
- Pass <- Passes, Pass == true]
- end;
- _Error ->
- throw({bad_request, "invalid design doc"})
+ DDoc = couch_httpd_db:couch_doc_open(Db, DesignId, nil, []),
+ % validate that the ddoc has the filter fun
+ #doc{body={Props}} = DDoc,
+ couch_util:get_nested_json_value({Props}, [<<"filters">>, FName]),
+ fun(DocInfos) ->
+ Docs = [Doc || {ok, Doc} <- [
+ {ok, Doc} = couch_db:open_doc(Db, DInfo, [deleted])
+ || DInfo <- DocInfos]],
+ {ok, Passes} = couch_query_servers:filter_docs(Req, Db, DDoc, FName, Docs),
+ [{[{rev, couch_doc:rev_to_str(Rev)}]}
+ || #doc_info{revs=[#rev_info{rev=Rev}|_]} <- DocInfos,
+ Pass <- Passes, Pass == true]
end;
_Else ->
throw({bad_request,
@@ -279,11 +271,14 @@
handle_design_req(#httpd{
- path_parts=[_DbName,_Design,_DesName, <<"_",_/binary>> = Action | _Rest],
+ path_parts=[_DbName, _Design, DesignName, <<"_",_/binary>> = Action | _Rest],
design_url_handlers = DesignUrlHandlers
}=Req, Db) ->
+ % load ddoc
+ DesignId = <<"_design/", DesignName/binary>>,
+ DDoc = couch_httpd_db:couch_doc_open(Db, DesignId, nil, []),
Handler = couch_util:dict_find(Action, DesignUrlHandlers, fun db_req/2),
- Handler(Req, Db);
+ Handler(Req, Db, DDoc);
handle_design_req(Req, Db) ->
db_req(Req, Db).
@@ -291,7 +286,7 @@
handle_design_info_req(#httpd{
method='GET',
path_parts=[_DbName, _Design, DesignName, _]
- }=Req, Db) ->
+ }=Req, Db, _DDoc) ->
DesignId = <<"_design/", DesignName/binary>>,
{ok, GroupInfoList} = couch_view:get_group_info(Db, DesignId),
send_json(Req, 200, {[
@@ -299,7 +294,7 @@
{view_index, {GroupInfoList}}
]});
-handle_design_info_req(Req, _Db) ->
+handle_design_info_req(Req, _Db, _DDoc) ->
send_method_not_allowed(Req, "GET").
create_db_req(#httpd{user_ctx=UserCtx}=Req, DbName) ->
@@ -725,7 +720,12 @@
end;
_ ->
{DesignName, ShowName} = Format,
- couch_httpd_show:handle_doc_show(Req, DesignName, ShowName, DocId, Db)
+ % load ddoc
+ DesignId = <<"_design/", DesignName/binary>>,
+ DDoc = couch_httpd_db:couch_doc_open(Db, DesignId, nil, []),
+ % open doc
+ Doc = couch_doc_open(Db, DocId, Rev, Options),
+ couch_httpd_show:handle_doc_show(Req, Db, DDoc, ShowName, Doc)
end;
db_doc_req(#httpd{method='POST'}=Req, Db, DocId) ->
Modified: couchdb/trunk/src/couchdb/couch_httpd_external.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_external.erl?rev=893249&r1=893248&r2=893249&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd_external.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd_external.erl Tue Dec 22 18:03:44 2009
@@ -13,7 +13,7 @@
-module(couch_httpd_external).
-export([handle_external_req/2, handle_external_req/3]).
--export([send_external_response/2, json_req_obj/2]).
+-export([send_external_response/2, json_req_obj/2, json_req_obj/3]).
-export([default_or_content_type/2, parse_external_response/1]).
-import(couch_httpd,[send_error/4]).
@@ -53,12 +53,12 @@
_ ->
send_external_response(HttpReq, Response)
end.
-
+json_req_obj(Req, Db) -> json_req_obj(Req, Db, null).
json_req_obj(#httpd{mochi_req=Req,
method=Verb,
path_parts=Path,
req_body=ReqBody
- }, Db) ->
+ }, Db, DocId) ->
Body = case ReqBody of
undefined -> Req:recv_body();
Else -> Else
@@ -74,6 +74,7 @@
{ok, Info} = couch_db:get_db_info(Db),
% add headers...
{[{<<"info">>, {Info}},
+ {<<"id">>, DocId},
{<<"verb">>, Verb},
{<<"path">>, Path},
{<<"query">>, to_json_terms(Req:parse_qs())},
Modified: couchdb/trunk/src/couchdb/couch_httpd_show.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_show.erl?rev=893249&r1=893248&r2=893249&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd_show.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd_show.erl Tue Dec 22 18:03:44 2009
@@ -12,8 +12,8 @@
-module(couch_httpd_show).
--export([handle_doc_show_req/2, handle_doc_update_req/2, handle_view_list_req/2,
- handle_doc_show/5, handle_view_list/7]).
+-export([handle_doc_show_req/3, handle_doc_update_req/3, handle_view_list_req/3,
+ handle_doc_show/5, handle_view_list/6, get_fun_key/3]).
-include("couch_db.hrl").
@@ -22,217 +22,245 @@
start_json_response/2,send_chunk/2,last_chunk/1,send_chunked_error/2,
start_chunked_response/3, send_error/4]).
+% /db/_design/foo/show/bar/docid
+% show converts a json doc to a response of any content-type.
+% it looks up the doc an then passes it to the query server.
+% then it sends the response from the query server to the http client.
handle_doc_show_req(#httpd{
- method='GET',
- path_parts=[_DbName, _Design, DesignName, _Show, ShowName, DocId]
- }=Req, Db) ->
- handle_doc_show(Req, DesignName, ShowName, DocId, Db);
+ path_parts=[_, _, _, _, ShowName, DocId]
+ }=Req, Db, DDoc) ->
+ % open the doc
+ Doc = couch_httpd_db:couch_doc_open(Db, DocId, nil, [conflicts]),
+ % we don't handle revs here b/c they are an internal api
+ % returns 404 if there is no doc with DocId
+ handle_doc_show(Req, Db, DDoc, ShowName, Doc);
handle_doc_show_req(#httpd{
- path_parts=[_DbName, _Design, DesignName, _Show, ShowName]
- }=Req, Db) ->
- handle_doc_show(Req, DesignName, ShowName, nil, Db);
+ path_parts=[_, _, _, _, ShowName]
+ }=Req, Db, DDoc) ->
+ % with no docid the doc is nil
+ handle_doc_show(Req, Db, DDoc, ShowName, nil);
+
+handle_doc_show_req(Req, _Db, _DDoc) ->
+ send_error(Req, 404, <<"show_error">>, <<"Invalid path.">>).
+
+handle_doc_show(Req, Db, DDoc, ShowName, Doc) ->
+ % get responder for ddoc/showname
+ CurrentEtag = show_etag(Req, Doc, DDoc, []),
+ couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
+ JsonReq = couch_httpd_external:json_req_obj(Req, Db),
+ JsonDoc = couch_query_servers:json_doc(Doc),
+ [<<"resp">>, ExternalResp] =
+ couch_query_servers:ddoc_prompt(DDoc, [<<"shows">>, ShowName], [JsonDoc, JsonReq]),
+ JsonResp = apply_etag(ExternalResp, CurrentEtag),
+ couch_httpd_external:send_external_response(Req, JsonResp)
+ end).
-handle_doc_show_req(#httpd{method='GET'}=Req, _Db) ->
- send_error(Req, 404, <<"show_error">>, <<"Invalid path.">>);
-handle_doc_show_req(Req, _Db) ->
- send_method_not_allowed(Req, "GET,POST,HEAD").
+show_etag(#httpd{user_ctx=UserCtx}=Req, Doc, DDoc, More) ->
+ Accept = couch_httpd:header_value(Req, "Accept"),
+ DocPart = case Doc of
+ nil -> nil;
+ Doc -> couch_httpd:doc_etag(Doc)
+ end,
+ couch_httpd:make_etag({couch_httpd:doc_etag(DDoc), DocPart, Accept, UserCtx#user_ctx.roles, More}).
-handle_doc_update_req(#httpd{method = 'GET'}=Req, _Db) ->
- send_method_not_allowed(Req, "POST,PUT,DELETE,ETC");
+get_fun_key(DDoc, Type, Name) ->
+ #doc{body={Props}} = DDoc,
+ Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),
+ Src = couch_util:get_nested_json_value({Props}, [Type, Name]),
+ {Lang, Src}.
+
+% /db/_design/foo/update/bar/docid
+% updates a doc based on a request
+% handle_doc_update_req(#httpd{method = 'GET'}=Req, _Db, _DDoc) ->
+% % anything but GET
+% send_method_not_allowed(Req, "POST,PUT,DELETE,ETC");
handle_doc_update_req(#httpd{
- path_parts=[_DbName, _Design, DesignName, _Update, UpdateName, DocId]
- }=Req, Db) ->
- DesignId = <<"_design/", DesignName/binary>>,
- #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, nil, []),
- Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),
- UpdateSrc = couch_util:get_nested_json_value({Props}, [<<"updates">>, UpdateName]),
+ path_parts=[_, _, _, _, UpdateName, DocId]
+ }=Req, Db, DDoc) ->
Doc = try couch_httpd_db:couch_doc_open(Db, DocId, nil, [conflicts])
- catch
- _ -> nil
- end,
- send_doc_update_response(Lang, UpdateSrc, DocId, Doc, Req, Db);
+ catch
+ _ -> nil
+ end,
+ send_doc_update_response(Req, Db, DDoc, UpdateName, Doc, DocId);
handle_doc_update_req(#httpd{
- path_parts=[_DbName, _Design, DesignName, _Update, UpdateName]
- }=Req, Db) ->
- DesignId = <<"_design/", DesignName/binary>>,
- #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, nil, []),
- Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),
- UpdateSrc = couch_util:get_nested_json_value({Props}, [<<"updates">>, UpdateName]),
- send_doc_update_response(Lang, UpdateSrc, nil, nil, Req, Db);
+ path_parts=[_, _, _, _, UpdateName]
+ }=Req, Db, DDoc) ->
+ send_doc_update_response(Req, Db, DDoc, UpdateName, nil, null);
-handle_doc_update_req(Req, _Db) ->
+handle_doc_update_req(Req, _Db, _DDoc) ->
send_error(Req, 404, <<"update_error">>, <<"Invalid path.">>).
-
-
-handle_doc_show(Req, DesignName, ShowName, DocId, Db) ->
- DesignId = <<"_design/", DesignName/binary>>,
- #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, nil, []),
- Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),
- ShowSrc = couch_util:get_nested_json_value({Props}, [<<"shows">>, ShowName]),
- Doc = case DocId of
- nil -> nil;
- _ ->
- try couch_httpd_db:couch_doc_open(Db, DocId, nil, [conflicts])
- catch
- _ -> nil
- end
+send_doc_update_response(Req, Db, DDoc, UpdateName, Doc, DocId) ->
+ JsonReq = couch_httpd_external:json_req_obj(Req, Db, DocId),
+ JsonDoc = couch_query_servers:json_doc(Doc),
+ case couch_query_servers:ddoc_prompt(DDoc, [<<"updates">>, UpdateName], [JsonDoc, JsonReq]) of
+ [<<"up">>, {NewJsonDoc}, JsonResp] ->
+ Options = case couch_httpd:header_value(Req, "X-Couch-Full-Commit", "false") of
+ "true" ->
+ [full_commit];
+ _ ->
+ []
+ end,
+ NewDoc = couch_doc:from_json_obj({NewJsonDoc}),
+ Code = 201,
+ {ok, _NewRev} = couch_db:update_doc(Db, NewDoc, Options);
+ [<<"up">>, _Other, JsonResp] ->
+ Code = 200,
+ ok
end,
- send_doc_show_response(Lang, ShowSrc, DocId, Doc, Req, Db).
+ JsonResp2 = json_apply_field({<<"code">>, Code}, JsonResp),
+ % todo set location field
+ couch_httpd_external:send_external_response(Req, JsonResp2).
+
% view-list request with view and list from same design doc.
handle_view_list_req(#httpd{method='GET',
- path_parts=[_DbName, _Design, DesignName, _List, ListName, ViewName]}=Req, Db) ->
- handle_view_list(Req, DesignName, ListName, DesignName, ViewName, Db, nil);
+ path_parts=[_, _, DesignName, _, ListName, ViewName]}=Req, Db, DDoc) ->
+ handle_view_list(Req, Db, DDoc, ListName, {DesignName, ViewName}, nil);
% view-list request with view and list from different design docs.
handle_view_list_req(#httpd{method='GET',
- path_parts=[_DbName, _Design, DesignName, _List, ListName, ViewDesignName, ViewName]}=Req, Db) ->
- handle_view_list(Req, DesignName, ListName, ViewDesignName, ViewName, Db, nil);
+ path_parts=[_, _, _, _, ListName, ViewDesignName, ViewName]}=Req, Db, DDoc) ->
+ handle_view_list(Req, Db, DDoc, ListName, {ViewDesignName, ViewName}, nil);
-handle_view_list_req(#httpd{method='GET'}=Req, _Db) ->
+handle_view_list_req(#httpd{method='GET'}=Req, _Db, _DDoc) ->
send_error(Req, 404, <<"list_error">>, <<"Invalid path.">>);
handle_view_list_req(#httpd{method='POST',
- path_parts=[_DbName, _Design, DesignName, _List, ListName, ViewName]}=Req, Db) ->
+ path_parts=[_, _, DesignName, _, ListName, ViewName]}=Req, Db, DDoc) ->
+ % {Props2} = couch_httpd:json_body(Req),
ReqBody = couch_httpd:body(Req),
{Props2} = ?JSON_DECODE(ReqBody),
Keys = proplists:get_value(<<"keys">>, Props2, nil),
- handle_view_list(Req#httpd{req_body=ReqBody}, DesignName, ListName, DesignName, ViewName, Db, Keys);
-
-handle_view_list_req(Req, _Db) ->
- send_method_not_allowed(Req, "GET,POST,HEAD").
+ handle_view_list(Req#httpd{req_body=ReqBody}, Db, DDoc, ListName, {DesignName, ViewName}, Keys);
-handle_view_list(Req, ListDesignName, ListName, ViewDesignName, ViewName, Db, Keys) ->
- ListDesignId = <<"_design/", ListDesignName/binary>>,
- #doc{body={ListProps}} = couch_httpd_db:couch_doc_open(Db, ListDesignId, nil, []),
- if
- ViewDesignName == ListDesignName ->
- ViewDesignId = ListDesignId;
- true ->
- ViewDesignId = <<"_design/", ViewDesignName/binary>>
- end,
+handle_view_list_req(#httpd{method='POST',
+ path_parts=[_, _, _, _, ListName, ViewDesignName, ViewName]}=Req, Db, DDoc) ->
+ % {Props2} = couch_httpd:json_body(Req),
+ ReqBody = couch_httpd:body(Req),
+ {Props2} = ?JSON_DECODE(ReqBody),
+ Keys = proplists:get_value(<<"keys">>, Props2, nil),
+ handle_view_list(Req#httpd{req_body=ReqBody}, Db, DDoc, ListName, {ViewDesignName, ViewName}, Keys);
- ListLang = proplists:get_value(<<"language">>, ListProps, <<"javascript">>),
- ListSrc = couch_util:get_nested_json_value({ListProps}, [<<"lists">>, ListName]),
- send_view_list_response(ListLang, ListSrc, ViewName, ViewDesignId, Req, Db, Keys).
-
-
-send_view_list_response(Lang, ListSrc, ViewName, DesignId, Req, Db, Keys) ->
- Stale = couch_httpd_view:get_stale_type(Req),
- Reduce = couch_httpd_view:get_reduce_type(Req),
- case couch_view:get_map_view(Db, DesignId, ViewName, Stale) of
- {ok, View, Group} ->
- QueryArgs = couch_httpd_view:parse_view_params(Req, Keys, map),
- output_map_list(Req, Lang, ListSrc, View, Group, Db, QueryArgs, Keys);
- {not_found, _Reason} ->
- case couch_view:get_reduce_view(Db, DesignId, ViewName, Stale) of
- {ok, ReduceView, Group} ->
- case Reduce of
- false ->
- QueryArgs = couch_httpd_view:parse_view_params(
- Req, Keys, map_red
- ),
- MapView = couch_view:extract_map_view(ReduceView),
- output_map_list(Req, Lang, ListSrc, MapView, Group, Db, QueryArgs, Keys);
- _ ->
- QueryArgs = couch_httpd_view:parse_view_params(
- Req, Keys, reduce
- ),
- output_reduce_list(Req, Lang, ListSrc, ReduceView, Group, Db, QueryArgs, Keys)
- end;
- {not_found, Reason} ->
- throw({not_found, Reason})
- end
- end.
+handle_view_list_req(#httpd{method='POST'}=Req, _Db, _DDoc) ->
+ send_error(Req, 404, <<"list_error">>, <<"Invalid path.">>);
+handle_view_list_req(Req, _Db, _DDoc) ->
+ send_method_not_allowed(Req, "GET,POST,HEAD").
-output_map_list(#httpd{mochi_req=MReq, user_ctx=UserCtx}=Req, Lang, ListSrc, View, Group, Db, QueryArgs, nil) ->
+handle_view_list(Req, Db, DDoc, LName, {ViewDesignName, ViewName}, Keys) ->
+ ViewDesignId = <<"_design/", ViewDesignName/binary>>,
+ {ViewType, View, Group, QueryArgs} = couch_httpd_view:load_view(Req, Db, {ViewDesignId, ViewName}, Keys),
+ Etag = list_etag(Req, Db, Group, {couch_httpd:doc_etag(DDoc), Keys}),
+ couch_httpd:etag_respond(Req, Etag, fun() ->
+ output_list(ViewType, Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys)
+ end).
+
+list_etag(#httpd{user_ctx=UserCtx}=Req, Db, Group, More) ->
+ Accept = couch_httpd:header_value(Req, "Accept"),
+ couch_httpd_view:view_group_etag(Group, Db, {More, Accept, UserCtx#user_ctx.roles}).
+
+output_list(map, Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) ->
+ output_map_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys);
+output_list(reduce, Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) ->
+ output_reduce_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys).
+
+% next step:
+% use with_ddoc_proc/2 to make this simpler
+output_map_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) ->
#view_query_args{
limit = Limit,
skip = SkipCount
} = QueryArgs,
+
+ FoldAccInit = {Limit, SkipCount, undefined, []},
{ok, RowCount} = couch_view:get_row_count(View),
- Headers = MReq:get(headers),
- Hlist = mochiweb_headers:to_list(Headers),
- Accept = proplists:get_value('Accept', Hlist),
- CurrentEtag = couch_httpd_view:view_group_etag(Group, Db, {Lang, ListSrc, Accept, UserCtx}),
- couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
- % get the os process here
- % pass it into the view fold with closures
- {ok, QueryServer} = couch_query_servers:start_view_list(Lang, ListSrc),
-
- StartListRespFun = make_map_start_resp_fun(QueryServer, Db),
- SendListRowFun = make_map_send_row_fun(QueryServer),
-
- FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, RowCount,
- #view_fold_helper_funs{
- reduce_count = fun couch_view:reduce_to_count/1,
- start_response = StartListRespFun,
- send_row = SendListRowFun
- }),
- FoldAccInit = {Limit, SkipCount, undefined, []},
- {ok, _, FoldResult} = couch_view:fold(View, FoldlFun, FoldAccInit,
- couch_httpd_view:make_key_options(QueryArgs)),
- finish_list(Req, QueryServer, CurrentEtag, FoldResult, StartListRespFun, RowCount)
- end);
+
+
+ couch_query_servers:with_ddoc_proc(DDoc, fun(QServer) ->
+
+ ListFoldHelpers = #view_fold_helper_funs{
+ reduce_count = fun couch_view:reduce_to_count/1,
+ start_response = StartListRespFun = make_map_start_resp_fun(QServer, Db, LName),
+ send_row = make_map_send_row_fun(QServer)
+ },
+
+ {ok, _, FoldResult} = case Keys of
+ nil ->
+ FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Etag, Db, RowCount, ListFoldHelpers),
+ couch_view:fold(View, FoldlFun, FoldAccInit,
+ couch_httpd_view:make_key_options(QueryArgs));
+ Keys ->
+ lists:foldl(
+ fun(Key, {ok, _, FoldAcc}) ->
+ QueryArgs2 = QueryArgs#view_query_args{
+ start_key = Key,
+ end_key = Key
+ },
+ FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs2, Etag, Db, RowCount, ListFoldHelpers),
+ couch_view:fold(View, FoldlFun, FoldAcc,
+ couch_httpd_view:make_key_options(QueryArgs2))
+ end, {ok, nil, FoldAccInit}, Keys)
+ end,
+ finish_list(Req, QServer, Etag, FoldResult, StartListRespFun, RowCount)
+ end).
-output_map_list(#httpd{mochi_req=MReq, user_ctx=UserCtx}=Req, Lang, ListSrc, View, Group, Db, QueryArgs, Keys) ->
+
+output_reduce_list(Req, Db, DDoc, LName, View, QueryArgs, Etag, Keys) ->
#view_query_args{
limit = Limit,
- skip = SkipCount
+ skip = SkipCount,
+ group_level = GroupLevel
} = QueryArgs,
- {ok, RowCount} = couch_view:get_row_count(View),
- Headers = MReq:get(headers),
- Hlist = mochiweb_headers:to_list(Headers),
- Accept = proplists:get_value('Accept', Hlist),
- CurrentEtag = couch_httpd_view:view_group_etag(Group, Db, {Lang, ListSrc, Accept, UserCtx, Keys}),
- couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
- % get the os process here
- % pass it into the view fold with closures
- {ok, QueryServer} = couch_query_servers:start_view_list(Lang, ListSrc),
-
- StartListRespFun = make_map_start_resp_fun(QueryServer, Db),
- SendListRowFun = make_map_send_row_fun(QueryServer),
+ couch_query_servers:with_ddoc_proc(DDoc, fun(QServer) ->
+ StartListRespFun = make_reduce_start_resp_fun(QServer, Db, LName),
+ SendListRowFun = make_reduce_send_row_fun(QServer, Db),
+ {ok, GroupRowsFun, RespFun} = couch_httpd_view:make_reduce_fold_funs(Req,
+ GroupLevel, QueryArgs, Etag,
+ #reduce_fold_helper_funs{
+ start_response = StartListRespFun,
+ send_row = SendListRowFun
+ }),
FoldAccInit = {Limit, SkipCount, undefined, []},
- {ok, _, FoldResult} = lists:foldl(
- fun(Key, {ok, _, FoldAcc}) ->
- QueryArgs2 = QueryArgs#view_query_args{
- start_key = Key,
- end_key = Key
- },
- FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs2, CurrentEtag, Db, RowCount,
- #view_fold_helper_funs{
- reduce_count = fun couch_view:reduce_to_count/1,
- start_response = StartListRespFun,
- send_row = SendListRowFun
- }),
- couch_view:fold(View, FoldlFun, FoldAcc,
- couch_httpd_view:make_key_options(QueryArgs2))
- end, {ok, nil, FoldAccInit}, Keys),
- finish_list(Req, QueryServer, CurrentEtag, FoldResult, StartListRespFun, RowCount)
+ {ok, FoldResult} = case Keys of
+ nil ->
+ couch_view:fold_reduce(View, RespFun, FoldAccInit, [{key_group_fun, GroupRowsFun} |
+ couch_httpd_view:make_key_options(QueryArgs)]);
+ Keys ->
+ lists:foldl(
+ fun(Key, {ok, FoldAcc}) ->
+ couch_view:fold_reduce(View, RespFun, FoldAcc,
+ [{key_group_fun, GroupRowsFun} |
+ couch_httpd_view:make_key_options(
+ QueryArgs#view_query_args{start_key=Key, end_key=Key})]
+ )
+ end, {ok, FoldAccInit}, Keys)
+ end,
+ finish_list(Req, QServer, Etag, FoldResult, StartListRespFun, null)
end).
-make_map_start_resp_fun(QueryServer, Db) ->
+
+make_map_start_resp_fun(QueryServer, Db, LName) ->
fun(Req, Etag, TotalRows, Offset, _Acc) ->
Head = {[{<<"total_rows">>, TotalRows}, {<<"offset">>, Offset}]},
- start_list_resp(QueryServer, Req, Db, Head, Etag)
+ start_list_resp(QueryServer, LName, Req, Db, Head, Etag)
end.
-make_reduce_start_resp_fun(QueryServer, _Req, Db, _CurrentEtag) ->
+make_reduce_start_resp_fun(QueryServer, Db, LName) ->
fun(Req2, Etag, _Acc) ->
- start_list_resp(QueryServer, Req2, Db, {[]}, Etag)
+ start_list_resp(QueryServer, LName, 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),
+start_list_resp(QServer, LName, Req, Db, Head, Etag) ->
+ JsonReq = couch_httpd_external:json_req_obj(Req, Db),
+ [<<"start">>,Chunks,JsonResp] = couch_query_servers:ddoc_proc_prompt(QServer,
+ [<<"lists">>, LName], [Head, JsonReq]),
JsonResp2 = apply_etag(JsonResp, Etag),
#extern_resp_args{
code = Code,
@@ -255,7 +283,7 @@
send_list_row(Resp, QueryServer, Db, Row, RowFront, IncludeDoc) ->
try
- [Go,Chunks] = couch_query_servers:render_list_row(QueryServer, Db, Row, IncludeDoc),
+ [Go,Chunks] = prompt_list_row(QueryServer, Db, Row, IncludeDoc),
Chunk = RowFront ++ ?b2l(?l2b(Chunks)),
send_non_empty_chunk(Resp, Chunk),
case Go of
@@ -270,78 +298,22 @@
throw({already_sent, Resp, Error})
end.
+
+prompt_list_row({Proc, _DDocId}, Db, {{Key, DocId}, Value}, IncludeDoc) ->
+ JsonRow = couch_httpd_view:view_row_obj(Db, {{Key, DocId}, Value}, IncludeDoc),
+ couch_query_servers:proc_prompt(Proc, [<<"list_row">>, JsonRow]);
+
+prompt_list_row({Proc, _DDocId}, _, {Key, Value}, _IncludeDoc) ->
+ JsonRow = {[{key, Key}, {value, Value}]},
+ couch_query_servers:proc_prompt(Proc, [<<"list_row">>, JsonRow]).
+
send_non_empty_chunk(Resp, Chunk) ->
case Chunk of
[] -> ok;
_ -> send_chunk(Resp, Chunk)
end.
-output_reduce_list(#httpd{mochi_req=MReq, user_ctx=UserCtx}=Req, Lang, ListSrc, View, Group, Db, QueryArgs, nil) ->
- #view_query_args{
- limit = Limit,
- skip = SkipCount,
- group_level = GroupLevel
- } = QueryArgs,
- Headers = MReq:get(headers),
- Hlist = mochiweb_headers:to_list(Headers),
- Accept = proplists:get_value('Accept', Hlist),
- CurrentEtag = couch_httpd_view:view_group_etag(Group, Db, {Lang, ListSrc, Accept, UserCtx}),
- couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
- % get the os process here
- % pass it into the view fold with closures
- {ok, QueryServer} = couch_query_servers:start_view_list(Lang, ListSrc),
- StartListRespFun = make_reduce_start_resp_fun(QueryServer, Req, Db, CurrentEtag),
- SendListRowFun = make_reduce_send_row_fun(QueryServer, Db),
-
- {ok, GroupRowsFun, RespFun} = couch_httpd_view:make_reduce_fold_funs(Req,
- GroupLevel, QueryArgs, CurrentEtag,
- #reduce_fold_helper_funs{
- start_response = StartListRespFun,
- send_row = SendListRowFun
- }),
- FoldAccInit = {Limit, SkipCount, undefined, []},
- {ok, FoldResult} = couch_view:fold_reduce(View, RespFun, FoldAccInit,
- [{key_group_fun, GroupRowsFun} |
- couch_httpd_view:make_key_options(QueryArgs)]),
- finish_list(Req, QueryServer, CurrentEtag, FoldResult, StartListRespFun, null)
- end);
-
-output_reduce_list(#httpd{mochi_req=MReq, user_ctx=UserCtx}=Req, Lang, ListSrc, View, Group, Db, QueryArgs, Keys) ->
- #view_query_args{
- limit = Limit,
- skip = SkipCount,
- group_level = GroupLevel
- } = QueryArgs,
- Headers = MReq:get(headers),
- Hlist = mochiweb_headers:to_list(Headers),
- Accept = proplists:get_value('Accept', Hlist),
- CurrentEtag = couch_httpd_view:view_group_etag(Group, Db, {Lang, ListSrc, Accept, UserCtx, Keys}),
- couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
- % get the os process here
- % pass it into the view fold with closures
- {ok, QueryServer} = couch_query_servers:start_view_list(Lang, ListSrc),
- StartListRespFun = make_reduce_start_resp_fun(QueryServer, Req, Db, CurrentEtag),
- SendListRowFun = make_reduce_send_row_fun(QueryServer, Db),
-
- {ok, GroupRowsFun, RespFun} = couch_httpd_view:make_reduce_fold_funs(Req,
- GroupLevel, QueryArgs, CurrentEtag,
- #reduce_fold_helper_funs{
- start_response = StartListRespFun,
- send_row = SendListRowFun
- }),
- FoldAccInit = {Limit, SkipCount, undefined, []},
- {ok, FoldResult} = lists:foldl(
- fun(Key, {ok, FoldAcc}) ->
- couch_view:fold_reduce(View, RespFun, FoldAcc,
- [{key_group_fun, GroupRowsFun} |
- couch_httpd_view:make_key_options(
- QueryArgs#view_query_args{start_key=Key, end_key=Key})]
- )
- end, {ok, FoldAccInit}, Keys),
- finish_list(Req, QueryServer, CurrentEtag, FoldResult, StartListRespFun, null)
- end).
-
-finish_list(Req, QueryServer, Etag, FoldResult, StartFun, TotalRows) ->
+finish_list(Req, {Proc, _DDocId}, Etag, FoldResult, StartFun, TotalRows) ->
FoldResult2 = case FoldResult of
{Limit, SkipCount, Response, RowAcc} ->
{Limit, SkipCount, Response, RowAcc, nil};
@@ -352,16 +324,15 @@
{_, _, undefined, _, _} ->
{ok, Resp, BeginBody} =
render_head_for_empty_list(StartFun, Req, Etag, TotalRows),
- [<<"end">>, Chunks] = couch_query_servers:render_list_tail(QueryServer),
+ [<<"end">>, Chunks] = couch_query_servers:proc_prompt(Proc, [<<"list_end">>]),
Chunk = BeginBody ++ ?b2l(?l2b(Chunks)),
send_non_empty_chunk(Resp, Chunk);
{_, _, Resp, stop, _} ->
ok;
{_, _, Resp, _, _} ->
- [<<"end">>, Chunks] = couch_query_servers:render_list_tail(QueryServer),
+ [<<"end">>, Chunks] = couch_query_servers:proc_prompt(Proc, [<<"list_end">>]),
send_non_empty_chunk(Resp, ?b2l(?l2b(Chunks)))
end,
- couch_query_servers:stop_doc_map(QueryServer),
last_chunk(Resp).
@@ -370,53 +341,6 @@
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, user_ctx=UserCtx}=Req, Db) ->
- % compute etag with no doc
- Headers = MReq:get(headers),
- Hlist = mochiweb_headers:to_list(Headers),
- Accept = proplists:get_value('Accept', Hlist),
- CurrentEtag = couch_httpd:make_etag({Lang, ShowSrc, nil, Accept, UserCtx}),
- couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
- [<<"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)
- end);
-
-send_doc_show_response(Lang, ShowSrc, DocId, #doc{revs=Revs}=Doc, #httpd{mochi_req=MReq, user_ctx=UserCtx}=Req, Db) ->
- % calculate the etag
- Headers = MReq:get(headers),
- Hlist = mochiweb_headers:to_list(Headers),
- Accept = proplists:get_value('Accept', Hlist),
- CurrentEtag = couch_httpd:make_etag({Lang, ShowSrc, Revs, Accept, UserCtx}),
- % We know our etag now
- couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
- [<<"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)
- end).
-
-send_doc_update_response(Lang, UpdateSrc, DocId, Doc, Req, Db) ->
- case couch_query_servers:render_doc_update(Lang, UpdateSrc,
- DocId, Doc, Req, Db) of
- [<<"up">>, {NewJsonDoc}, JsonResp] ->
- Options = case couch_httpd:header_value(Req, "X-Couch-Full-Commit", "false") of
- "true" ->
- [full_commit];
- _ ->
- []
- end,
- NewDoc = couch_doc:from_json_obj({NewJsonDoc}),
- Code = 201,
- % todo set location field
- {ok, _NewRev} = couch_db:update_doc(Db, NewDoc, Options);
- [<<"up">>, _Other, JsonResp] ->
- Code = 200,
- ok
- end,
- JsonResp2 = json_apply_field({<<"code">>, Code}, JsonResp),
- couch_httpd_external:send_external_response(Req, JsonResp2).
% Maybe this is in the proplists API
% todo move to couch_util