summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--share/server/loop.js25
-rw-r--r--share/server/render.js183
-rw-r--r--share/server/util.js5
-rw-r--r--share/www/script/test/list_views.js311
-rw-r--r--share/www/script/test/show_documents.js14
-rw-r--r--src/couchdb/couch_httpd.erl8
-rw-r--r--src/couchdb/couch_httpd_show.erl181
-rw-r--r--src/couchdb/couch_js.c9
-rw-r--r--src/couchdb/couch_os_process.erl8
-rw-r--r--src/couchdb/couch_query_servers.erl37
-rw-r--r--test/query_server_spec.rb450
12 files changed, 740 insertions, 492 deletions
diff --git a/.gitignore b/.gitignore
index f3bef64c..ea1f9755 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,7 @@ erl_crash.dump
configure
autom4te.cache
build-aux
+*.diff
# ./configure
diff --git a/share/server/loop.js b/share/server/loop.js
index db6a9702..188692ba 100644
--- a/share/server/loop.js
+++ b/share/server/loop.js
@@ -21,6 +21,9 @@ try {
sandbox.toJSON = toJSON;
sandbox.respondWith = respondWith;
sandbox.registerType = registerType;
+ sandbox.start = start;
+ sandbox.send = send;
+ sandbox.getRow = getRow;
} catch (e) {}
// Commands are in the form of json arrays:
@@ -31,21 +34,19 @@ try {
var line, cmd, cmdkey;
var dispatch = {
- "reset" : State.reset,
- "add_fun" : State.addFun,
- "map_doc" : Views.mapDoc,
- "reduce" : Views.reduce,
- "rereduce" : Views.rereduce,
- "validate" : Validate.validate,
- "show_doc" : Render.showDoc,
- "list_begin" : Render.listBegin,
- "list_row" : Render.listRow,
- "list_tail" : Render.listTail
+ "reset" : State.reset,
+ "add_fun" : State.addFun,
+ "map_doc" : Views.mapDoc,
+ "reduce" : Views.reduce,
+ "rereduce" : Views.rereduce,
+ "validate" : Validate.validate,
+ "show" : Render.show,
+ "list" : Render.list
};
while (line = eval(readline())) {
- cmd = eval(line)
- line_length = line.length
+ cmd = eval(line);
+ line_length = line.length;
try {
cmdkey = cmd.shift();
if (dispatch[cmdkey]) {
diff --git a/share/server/render.js b/share/server/render.js
index 13ef1322..99541eab 100644
--- a/share/server/render.js
+++ b/share/server/render.js
@@ -12,9 +12,10 @@
// mimeparse.js
// http://code.google.com/p/mimeparse/
+// MIT Licensed http://www.opensource.org/licenses/mit-license.php
// Code with comments: http://mimeparse.googlecode.com/svn/trunk/mimeparse.js
// Tests: http://mimeparse.googlecode.com/svn/trunk/mimeparse-js-test.html
-// Ported from version 0.1.2
+// Ported by Chris Anderson from version 0.1.2
var Mimeparse = (function() {
function strip(string) {
@@ -111,6 +112,8 @@ var Mimeparse = (function() {
return publicMethods;
})();
+var respCT;
+var respTail;
// this function provides a shortcut for managing responses by Accept header
respondWith = function(req, responders) {
var bestKey = null, accept = req.headers["Accept"];
@@ -127,11 +130,16 @@ respondWith = function(req, responders) {
bestKey = req.query.format;
}
var rFunc = responders[bestKey || responders.fallback || "html"];
- if (rFunc) {
- var resp = maybeWrapResponse(rFunc());
- resp["headers"] = resp["headers"] || {};
- resp["headers"]["Content-Type"] = bestMime;
- respond(resp);
+ if (rFunc) {
+ if (isShow) {
+ var resp = maybeWrapResponse(rFunc());
+ resp["headers"] = resp["headers"] || {};
+ resp["headers"]["Content-Type"] = bestMime;
+ respond(["resp", resp]);
+ } else {
+ respCT = bestMime;
+ respTail = rFunc();
+ }
} else {
throw({code:406, body:"Not Acceptable: "+accept});
}
@@ -162,8 +170,6 @@ registerType("text", "text/plain", "txt");
registerType("html", "text/html");
registerType("xhtml", "application/xhtml+xml", "xhtml");
registerType("xml", "application/xml", "text/xml", "application/x-xml");
-// http://www.ietf.org/rfc/rfc4627.txt
-registerType("json", "application/json", "text/x-json");
registerType("js", "text/javascript", "application/javascript", "application/x-javascript");
registerType("css", "text/css");
registerType("ics", "text/calendar");
@@ -171,57 +177,148 @@ 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(label) {
+ startResp = startResp || {};
+ startResp["headers"] = startResp["headers"] || {};
+ startResp["headers"]["Content-Type"] = startResp["headers"]["Content-Type"] || respCT;
+
+ respond(["start", chunks, startResp]);
+ chunks = [];
+ startResp = {};
+}
+// Send chunk
+var chunks = [];
+function send(chunk) {
+ chunks.push(chunk.toString());
+};
+
+function blowChunks(label) {
+ respond([label||"chunks", chunks]);
+ chunks = [];
+};
+
+var gotRow = false, lastRow = false;
+function getRow() {
+ if (lastRow) return null;
+ if (!gotRow) {
+ gotRow = true;
+ sendStart();
+ } else {
+ blowChunks()
+ }
+ var line = readline();
+ var json = eval(line);
+ if (json[0] == "list_end") {
+ lastRow = true
+ return null;
+ }
+ if (json[0] != "list_row") {
+ respond({
+ error: "query_server_error",
+ reason: "not a row '" + json[0] + "'"});
+ quit();
+ }
+ return json[1];
+};
+
+////
+//// Render dispatcher
+////
+////
+////
+////
+var isShow = false;
var Render = (function() {
var row_info;
+
return {
- showDoc : function(funSrc, doc, req) {
+ show : function(funSrc, doc, req) {
+ isShow = true;
var formFun = compileFunction(funSrc);
- runRenderFunction(formFun, [doc, req], funSrc);
- },
- listBegin : function(head, req) {
- row_info = { first_key: null, row_number: 0, prev_key: null };
- runRenderFunction(funs[0], [head, null, req, null], funsrc[0]);
- },
- listRow : function(row, req) {
- if (row_info.first_key == null) {
- row_info.first_key = row.key;
- }
- runRenderFunction(funs[0], [null, row, req, row_info], funsrc[0], true);
- row_info.prev_key = row.key;
- row_info.row_number++;
+ runShowRenderFunction(formFun, [doc, req], funSrc, true);
},
- listTail : function(req) {
- runRenderFunction(funs[0], [null, null, req, row_info], funsrc[0]);
+ list : function(head, req) {
+ isShow = false;
+ runListRenderFunction(funs[0], [head, req], funsrc[0], false);
}
}
})();
-function runRenderFunction(renderFun, args, funSrc, htmlErrors) {
- responseSent = false;
+function maybeWrapResponse(resp) {
+ var type = typeof resp;
+ if ((type == "string") || (type == "xml")) {
+ return {body:resp};
+ } else {
+ return resp;
+ }
+};
+
+function runShowRenderFunction(renderFun, args, funSrc, htmlErrors) {
try {
var resp = renderFun.apply(null, args);
- if (!responseSent) {
- if (resp) {
- respond(maybeWrapResponse(resp));
- } else {
- respond({error:"render_error",reason:"undefined response from render function"});
- }
+ if (resp) {
+ respond(["resp", maybeWrapResponse(resp)]);
+ } else {
+ renderError("undefined response from render function");
}
} catch(e) {
- var logMessage = "function raised error: "+e.toString();
- log(logMessage);
- // log("stacktrace: "+e.stack);
- var errorMessage = htmlErrors ? htmlRenderError(e, funSrc) : logMessage;
- respond({
- error:"render_error",
- reason:errorMessage});
+ respondError(e, funSrc, htmlErrors);
}
};
+function runListRenderFunction(renderFun, args, funSrc, htmlErrors) {
+ try {
+ gotRow = false;
+ lastRow = false;
+ respTail = "";
+ if (renderFun.arity > 2) {
+ throw("the list API has changed for CouchDB 0.10, please upgrade your code");
+ }
+ var resp = renderFun.apply(null, args);
+ if (!gotRow) {
+ getRow();
+ }
+ if (typeof resp != "undefined") {
+ chunks.push(resp);
+ } else if (respTail) {
+ chunks.push(respTail);
+ }
+ blowChunks("end");
+ } catch(e) {
+ respondError(e, funSrc, htmlErrors);
+ }
+};
+
+function renderError(m) {
+ respond({error : "render_error", reason : m});
+}
+
+
+function respondError(e, funSrc, htmlErrors) {
+ var logMessage = "function raised error: "+e.toString();
+ log(logMessage);
+ log("stacktrace: "+e.stack);
+ var errorMessage = htmlErrors ? htmlRenderError(e, funSrc) : logMessage;
+ respond({
+ error:"render_error",
+ reason:errorMessage});
+}
function escapeHTML(string) {
return string.replace(/&/g, "&")
@@ -241,11 +338,3 @@ function htmlRenderError(e, funSrc) {
return {body:msg};
};
-function maybeWrapResponse(resp) {
- var type = typeof resp;
- if ((type == "string") || (type == "xml")) {
- return {body:resp};
- } else {
- return resp;
- }
-};
diff --git a/share/server/util.js b/share/server/util.js
index 7faf2f0b..13b8a779 100644
--- a/share/server/util.js
+++ b/share/server/util.js
@@ -91,10 +91,8 @@ function recursivelySeal(obj) {
}
}
-var responseSent;
// prints the object as JSON, and rescues and logs any toJSON() related errors
function respond(obj) {
- responseSent = true;
try {
print(toJSON(obj));
} catch(e) {
@@ -103,10 +101,11 @@ function respond(obj) {
};
log = function(message) {
+ // return;
if (typeof message == "undefined") {
message = "Error: attempting to log message of 'undefined'.";
} else if (typeof message != "string") {
message = toJSON(message);
}
- print(toJSON({log: message}));
+ respond(["log", message]);
};
diff --git a/share/www/script/test/list_views.js b/share/www/script/test/list_views.js
index 663a5930..2b7e4cb3 100644
--- a/share/www/script/test/list_views.js
+++ b/share/www/script/test/list_views.js
@@ -39,106 +39,127 @@ couchTests.list_views = function(debug) {
}
},
lists: {
- simpleForm: stringFun(function(head, row, req, row_info) {
- if (row) {
- // we ignore headers on rows and tail
- return {
- body : '\n<li>Key: '+row.key
- +' Value: '+row.value
- +' LineNo: '+row_info.row_number+'</li>'
- };
- } else if (head) {
- // we return an object (like those used by external and show)
- // so that we can specify headers
- return {
- body : '<h1>Total Rows: '
- + head.total_rows
- + ' Offset: ' + head.offset
- + '</h1><ul>'
- };
- } else {
- // tail
- return {body : '</ul>'+
- '<p>FirstKey: '+(row_info ? row_info.first_key : '')+
- ' LastKey: '+(row_info ? row_info.prev_key : '')+'</p>'};
+ basicBasic : stringFun(function(head, req) {
+ send("head");
+ var row;
+ while(row = getRow()) {
+ log("row: "+toJSON(row));
+ send(row.key);
+ };
+ return "tail";
+ }),
+ basicJSON : stringFun(function(head, req) {
+ start({"headers":{"Content-Type" : "application/json"}});
+ send('{"head":'+toJSON(head)+', ');
+ send('"req":'+toJSON(req)+', ');
+ send('"rows":[');
+ var row, sep = '';
+ while (row = getRow()) {
+ send(sep + toJSON(row));
+ sep = ', ';
}
+ return "]}";
}),
- acceptSwitch: stringFun(function(head, row, req, row_info) {
- return respondWith(req, {
+ simpleForm: stringFun(function(head, req) {
+ log("simpleForm");
+ send('<h1>Total Rows: '
+ // + head.total_rows
+ // + ' Offset: ' + head.offset
+ + '</h1><ul>');
+
+ // rows
+ var row, row_number = 0, prevKey, firstKey = null;
+ while (row = getRow()) {
+ row_number += 1;
+ if (!firstKey) firstKey = row.key;
+ prevKey = row.key;
+ send('\n<li>Key: '+row.key
+ +' Value: '+row.value
+ +' LineNo: '+row_number+'</li>');
+ }
+
+ // tail
+ return '</ul><p>FirstKey: '+ firstKey + ' LastKey: '+ prevKey+'</p>';
+ }),
+ acceptSwitch: stringFun(function(head, req) {
+ // respondWith takes care of setting the proper headers
+ respondWith(req, {
html : function() {
- // If you're outputting text and you're not setting
- // any headers, you can just return a string.
- if (head) {
- return "HTML <ul>";
- } else if (row) {
- return '\n<li>Key: '
- +row.key+' Value: '+row.value
- +' LineNo: '+row_info.row_number+'</li>';
- } else { // tail
- return '</ul>';
+ send("HTML <ul>");
+ var row, num = 0;
+ while (row = getRow()) {
+ num ++;
+ send('\n<li>Key: '
+ +row.key+' Value: '+row.value
+ +' LineNo: '+num+'</li>');
}
+
+ // tail
+ return '</ul>';
},
xml : function() {
- if (head) {
- return '<feed xmlns="http://www.w3.org/2005/Atom">'
- +'<title>Test XML Feed</title>';
- } else if (row) {
- // Becase Safari can't stand to see that dastardly
- // E4X outside of a string. Outside of tests you
- // can just use E4X literals.
+ send('<feed xmlns="http://www.w3.org/2005/Atom">'
+ +'<title>Test XML Feed</title>');
+
+ while (row = getRow()) {
var entry = new XML('<entry/>');
entry.id = row.id;
entry.title = row.key;
entry.content = row.value;
- // We'll also let you return just an E4X object
- // if you aren't setting headers.
- return entry;
- } else {
- return "</feed>";
+ send(entry);
}
+ return "</feed>";
}
- })
+ });
}),
- qsParams: stringFun(function(head, row, req, row_info) {
- if(head) return {body: req.query.foo};
- else return {body: "\n"};
+ qsParams: stringFun(function(head, req) {
+ return toJSON(req.query) + "\n";
}),
- stopIter: stringFun(function(head, row, req, row_info) {
- if(head) {
- return {body: "head"};
- } else if(row) {
- if(row_info.row_number > 2) return {stop: true};
- return {body: " " + row_info.row_number};
- } else {
- return {body: " tail"};
- }
+ stopIter: stringFun(function(req) {
+ send("head");
+ var row, row_number = 0;
+ while(row = getRow()) {
+ if(row_number > 2) break;
+ send(" " + row_number);
+ row_number += 1;
+ };
+ return " tail";
}),
- stopIter2: stringFun(function(head, row, req, row_info) {
- return respondWith(req, {
+ stopIter2: stringFun(function(head, req) {
+ respondWith(req, {
html: function() {
- if(head) {
- return "head";
- } else if(row) {
- if(row_info.row_number > 2) return {stop: true};
- return " " + row_info.row_number;
- } else {
- return " tail";
- }
+ send("head");
+ var row, row_number = 0;
+ while(row = getRow()) {
+ if(row_number > 2) break;
+ send(" " + row_number);
+ row_number += 1;
+ };
+ return " tail";
}
});
}),
- emptyList: stringFun(function(head, row, req, row_info) {
- return { body: "" };
+ tooManyGetRows : stringFun(function() {
+ send("head");
+ var row;
+ while(row = getRow()) {
+ send(row.key);
+ };
+ getRow();
+ getRow();
+ getRow();
+ row = getRow();
+ return "after row: "+toJSON(row);
}),
- rowError : stringFun(function(head, row, req, row_info) {
- if (head) {
- return "head";
- } else if(row) {
- return missingValue;
- } else {
- return "tail"
- }
+ emptyList: stringFun(function() {
+ return " ";
+ }),
+ rowError : stringFun(function(head, req) {
+ send("head");
+ var row = getRow();
+ send(fooBarBam); // intentional error
+ return "tail";
})
}
};
@@ -152,26 +173,40 @@ couchTests.list_views = function(debug) {
T(view.total_rows == 10);
// standard get
- var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/basicView");
+ var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/basicBasic/basicView");
T(xhr.status == 200, "standard get should be 200");
- T(/Total Rows/.test(xhr.responseText));
- T(/Key: 1/.test(xhr.responseText));
- T(/LineNo: 0/.test(xhr.responseText));
- T(/LineNo: 5/.test(xhr.responseText));
- T(/FirstKey: 0/.test(xhr.responseText));
- T(/LastKey: 9/.test(xhr.responseText));
-
-
- var lines = xhr.responseText.split('\n');
- T(/LineNo: 5/.test(lines[6]));
+ T(/head0123456789tail/.test(xhr.responseText));
// test that etags are available
var etag = xhr.getResponseHeader("etag");
- xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/basicView", {
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/basicBasic/basicView", {
headers: {"if-none-match": etag}
});
T(xhr.status == 304);
+ // test the richness of the arguments
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/basicJSON/basicView");
+ T(xhr.status == 200, "standard get should be 200");
+ var resp = JSON.parse(xhr.responseText);
+ TEquals(resp.head, {total_rows:10, offset:0});
+ T(resp.rows.length == 10);
+ TEquals(resp.rows[0], {"id": "0","key": 0,"value": "0"});
+
+ TEquals(resp.req.info.db_name, "test_suite_db");
+ TEquals(resp.req.verb, "GET");
+ TEquals(resp.req.path, [
+ "test_suite_db",
+ "_design",
+ "lists",
+ "_list",
+ "basicJSON",
+ "basicView"
+ ]);
+ T(resp.req.headers.Accept);
+ T(resp.req.headers.Host);
+ T(resp.req.headers["User-Agent"]);
+ T(resp.req.cookie);
+
// get with query params
var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/basicView?startkey=3");
T(xhr.status == 200, "with query params");
@@ -179,26 +214,30 @@ couchTests.list_views = function(debug) {
T(!(/Key: 1/.test(xhr.responseText)));
T(/FirstKey: 3/.test(xhr.responseText));
T(/LastKey: 9/.test(xhr.responseText));
-
// with 0 rows
var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/basicView?startkey=30");
T(xhr.status == 200, "0 rows");
T(/Total Rows/.test(xhr.responseText));
- T(/Offset: null/.test(xhr.responseText));
+
+ //too many Get Rows
+ var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/tooManyGetRows/basicView");
+ T(xhr.status == 200, "tooManyGetRows");
+ T(/9after row: null/.test(xhr.responseText));
+
// reduce with 0 rows
var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/withReduce?startkey=30");
T(xhr.status == 200, "reduce 0 rows");
T(/Total Rows/.test(xhr.responseText));
- T(/Offset: undefined/.test(xhr.responseText));
-
+ T(/LastKey: undefined/.test(xhr.responseText));
// when there is a reduce present, but not used
var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/withReduce?reduce=false");
T(xhr.status == 200, "reduce false");
T(/Total Rows/.test(xhr.responseText));
T(/Key: 1/.test(xhr.responseText));
+
// when there is a reduce present, and used
xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/withReduce?group=true");
@@ -221,48 +260,12 @@ couchTests.list_views = function(debug) {
headers: {"if-none-match": etag}
});
T(xhr.status == 200, "reduce etag");
-
- // with accept headers for HTML
- xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/acceptSwitch/basicView", {
- headers: {
- "Accept": 'text/html'
- }
- });
- T(xhr.getResponseHeader("Content-Type") == "text/html");
- T(xhr.responseText.match(/HTML/));
- T(xhr.responseText.match(/Value/));
-
- // now with xml
- xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/acceptSwitch/basicView", {
- headers: {
- "Accept": 'application/xml'
- }
- });
- T(xhr.getResponseHeader("Content-Type") == "application/xml");
- T(xhr.responseText.match(/XML/));
- T(xhr.responseText.match(/entry/));
-
- // now with extra qs params
- var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/qsParams/basicView?foo=blam");
- T(xhr.responseText.match(/blam/));
-
- // aborting iteration
- var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter/basicView");
- T(xhr.responseText.match(/^head 0 1 2 tail$/) && "basic stop");
- xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter2/basicView");
- T(xhr.responseText.match(/^head 0 1 2 tail$/) && "stop 2");
-
- // aborting iteration with reduce
- var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter/withReduce?group=true");
- T(xhr.responseText.match(/^head 0 1 2 tail$/) && "reduce stop");
- xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter2/withReduce?group=true");
- T(xhr.responseText.match(/^head 0 1 2 tail$/) && "reduce stop 2");
// empty list
var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/emptyList/basicView");
- T(xhr.responseText.match(/^$/));
+ T(xhr.responseText.match(/^ $/));
xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/emptyList/withReduce?group=true");
- T(xhr.responseText.match(/^$/));
+ T(xhr.responseText.match(/^ $/));
// multi-key fetch
var xhr = CouchDB.request("POST", "/test_suite_db/_design/lists/_list/simpleForm/basicView", {
@@ -281,7 +284,45 @@ couchTests.list_views = function(debug) {
});
T(xhr.status == 400);
T(/query_parse_error/.test(xhr.responseText));
-
+
var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/rowError/basicView");
- T(/<h1>Render Error<\/h1>/.test(xhr.responseText));
+ T(/ReferenceError/.test(xhr.responseText));
+
+
+ // now with extra qs params
+ var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/qsParams/basicView?foo=blam");
+ T(xhr.responseText.match(/blam/));
+
+ var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter/basicView");
+ // T(xhr.getResponseHeader("Content-Type") == "text/plain");
+ T(xhr.responseText.match(/^head 0 1 2 tail$/) && "basic stop");
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter2/basicView");
+ T(xhr.responseText.match(/^head 0 1 2 tail$/) && "stop 2");
+
+ // aborting iteration with reduce
+ var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter/withReduce?group=true");
+ T(xhr.responseText.match(/^head 0 1 2 tail$/) && "reduce stop");
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter2/withReduce?group=true");
+ T(xhr.responseText.match(/^head 0 1 2 tail$/) && "reduce stop 2");
+
+ // with accept headers for HTML
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/acceptSwitch/basicView", {
+ headers: {
+ "Accept": 'text/html'
+ }
+ });
+ T(xhr.getResponseHeader("Content-Type") == "text/html");
+ T(xhr.responseText.match(/HTML/));
+ T(xhr.responseText.match(/Value/));
+
+ // now with xml
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/acceptSwitch/basicView", {
+ headers: {
+ "Accept": 'application/xml'
+ }
+ });
+ T(xhr.getResponseHeader("Content-Type") == "application/xml");
+ T(xhr.responseText.match(/XML/));
+ T(xhr.responseText.match(/entry/));
};
diff --git a/share/www/script/test/show_documents.js b/share/www/script/test/show_documents.js
index bf12e4c6..945bd1da 100644
--- a/share/www/script/test/show_documents.js
+++ b/share/www/script/test/show_documents.js
@@ -106,9 +106,7 @@ couchTests.show_documents = function(debug) {
registerType("foo", "application/foo","application/x-foo");
return respondWith(req, {
html : function() {
- return {
- body:"Ha ha, you said \"" + doc.word + "\"."
- };
+ return "Ha ha, you said \"" + doc.word + "\".";
},
xml : function() {
var xml = new XML('<xml><node/></xml>');
@@ -145,10 +143,14 @@ couchTests.show_documents = function(debug) {
// hello template world
xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/hello/"+docid);
T(xhr.responseText == "Hello World");
+//
+// };
+//
+// function foo() {
- // error stacktraces
- xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/render-error/"+docid);
- T(JSON.parse(xhr.responseText).error == "render_error");
+ // // error stacktraces
+ // xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/render-error/"+docid);
+ // T(JSON.parse(xhr.responseText).error == "render_error");
// hello template world (no docid)
xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/hello");
diff --git a/src/couchdb/couch_httpd.erl b/src/couchdb/couch_httpd.erl
index 457ab519..3417b850 100644
--- a/src/couchdb/couch_httpd.erl
+++ b/src/couchdb/couch_httpd.erl
@@ -177,6 +177,14 @@ handle_request(MochiReq, DefaultFun,
% ?LOG_DEBUG("Minor error in HTTP request: ~p",[Error]),
% ?LOG_DEBUG("Stacktrace: ~p",[erlang:get_stacktrace()]),
send_error(HttpReq, Error);
+ error:badarg ->
+ ?LOG_ERROR("Badarg error in HTTP request",[]),
+ ?LOG_INFO("Stacktrace: ~p",[erlang:get_stacktrace()]),
+ send_error(HttpReq, badarg);
+ error:function_clause ->
+ ?LOG_ERROR("function_clause error in HTTP request",[]),
+ ?LOG_INFO("Stacktrace: ~p",[erlang:get_stacktrace()]),
+ send_error(HttpReq, function_clause);
Tag:Error ->
?LOG_ERROR("Uncaught error in HTTP request: ~p",[{Tag, Error}]),
?LOG_INFO("Stacktrace: ~p",[erlang:get_stacktrace()]),
diff --git a/src/couchdb/couch_httpd_show.erl b/src/couchdb/couch_httpd_show.erl
index 49dd88bc..ef5d41d1 100644
--- a/src/couchdb/couch_httpd_show.erl
+++ b/src/couchdb/couch_httpd_show.erl
@@ -117,47 +117,6 @@ send_view_list_response(Lang, ListSrc, ViewName, DesignId, Req, Db, Keys) ->
end
end.
-make_map_start_resp_fun(QueryServer, Db) ->
- fun(Req, CurrentEtag, TotalViewCount, Offset, _Acc) ->
- ExternalResp = couch_query_servers:render_list_head(QueryServer,
- Req, Db, TotalViewCount, Offset),
- JsonResp = apply_etag(ExternalResp, CurrentEtag),
- #extern_resp_args{
- code = Code,
- data = BeginBody,
- ctype = CType,
- headers = ExtHeaders
- } = couch_httpd_external:parse_external_response(JsonResp),
- JsonHeaders = couch_httpd_external:default_or_content_type(CType, ExtHeaders),
- {ok, Resp} = start_chunked_response(Req, Code, JsonHeaders),
- {ok, Resp, binary_to_list(BeginBody)}
- end.
-
-make_map_send_row_fun(QueryServer, Req) ->
- fun(Resp, Db2, {{Key, DocId}, Value}, _IncludeDocs, RowFront) ->
- try
- JsonResp = couch_query_servers:render_list_row(QueryServer,
- Req, Db2, {{Key, DocId}, Value}),
- #extern_resp_args{
- stop = StopIter,
- data = RowBody
- } = couch_httpd_external:parse_external_response(JsonResp),
- case StopIter of
- true -> {stop, ""};
- _ ->
- Chunk = RowFront ++ binary_to_list(RowBody),
- case Chunk of
- [] -> ok;
- _ -> send_chunk(Resp, Chunk)
- end,
- {ok, ""}
- end
- catch
- throw:Error ->
- send_chunked_error(Resp, Error),
- throw({already_sent, Resp, Error})
- end
- end.
output_map_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, QueryArgs, nil) ->
#view_query_args{
@@ -179,7 +138,7 @@ output_map_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, Quer
{ok, QueryServer} = couch_query_servers:start_view_list(Lang, ListSrc),
StartListRespFun = make_map_start_resp_fun(QueryServer, Db),
- SendListRowFun = make_map_send_row_fun(QueryServer, Req),
+ SendListRowFun = make_map_send_row_fun(QueryServer),
FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, RowCount,
#view_fold_helper_funs{
@@ -189,7 +148,7 @@ output_map_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, Quer
}),
FoldAccInit = {Limit, SkipCount, undefined, []},
{ok, FoldResult} = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
- finish_list(Req, Db, QueryServer, CurrentEtag, FoldResult, StartListRespFun, RowCount)
+ finish_list(Req, QueryServer, CurrentEtag, FoldResult, StartListRespFun, RowCount)
end);
output_map_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, QueryArgs, Keys) ->
@@ -210,7 +169,7 @@ output_map_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, Quer
{ok, QueryServer} = couch_query_servers:start_view_list(Lang, ListSrc),
StartListRespFun = make_map_start_resp_fun(QueryServer, Db),
- SendListRowFun = make_map_send_row_fun(QueryServer, Req),
+ SendListRowFun = make_map_send_row_fun(QueryServer),
FoldAccInit = {Limit, SkipCount, undefined, []},
{ok, FoldResult} = lists:foldl(
@@ -226,49 +185,64 @@ output_map_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, Quer
}),
couch_view:fold(View, {Key, StartDocId}, Dir, FoldlFun, FoldAcc)
end, {ok, FoldAccInit}, Keys),
- finish_list(Req, Db, QueryServer, CurrentEtag, FoldResult, StartListRespFun, RowCount)
+ finish_list(Req, QueryServer, CurrentEtag, FoldResult, StartListRespFun, RowCount)
end).
-make_reduce_start_resp_fun(QueryServer, Req, Db, CurrentEtag) ->
- fun(Req2, _Etag, _Acc) ->
- JsonResp = couch_query_servers:render_reduce_head(QueryServer,
- Req2, Db),
- JsonResp2 = apply_etag(JsonResp, CurrentEtag),
- #extern_resp_args{
- code = Code,
- data = BeginBody,
- ctype = CType,
- headers = ExtHeaders
- } = couch_httpd_external:parse_external_response(JsonResp2),
- JsonHeaders = couch_httpd_external:default_or_content_type(CType, ExtHeaders),
- {ok, Resp} = start_chunked_response(Req, Code, JsonHeaders),
- {ok, Resp, binary_to_list(BeginBody)}
+make_map_start_resp_fun(QueryServer, Db) ->
+ fun(Req, Etag, TotalRows, Offset, _Acc) ->
+ Head = {[{<<"total_rows">>, TotalRows}, {<<"offset">>, Offset}]},
+ start_list_resp(QueryServer, Req, Db, Head, Etag)
end.
-make_reduce_send_row_fun(QueryServer, Req, Db) ->
- fun(Resp, {Key, Value}, RowFront) ->
- try
- JsonResp = couch_query_servers:render_reduce_row(QueryServer,
- Req, Db, {Key, Value}),
- #extern_resp_args{
- stop = StopIter,
- data = RowBody
- } = couch_httpd_external:parse_external_response(JsonResp),
- case StopIter of
- true -> {stop, ""};
- _ ->
- Chunk = RowFront ++ binary_to_list(RowBody),
- case Chunk of
- [] -> ok;
- _ -> send_chunk(Resp, Chunk)
- end,
- {ok, ""}
- end
- catch
- throw:Error ->
- send_chunked_error(Resp, Error),
- throw({already_sent, Resp, Error})
+make_reduce_start_resp_fun(QueryServer, _Req, Db, _CurrentEtag) ->
+ fun(Req2, Etag, _Acc) ->
+ start_list_resp(QueryServer, Req2, Db, {[]}, Etag)
+ end.
+
+start_list_resp(QueryServer, Req, Db, Head, Etag) ->
+ [<<"start">>,Chunks,JsonResp] = couch_query_servers:render_list_head(QueryServer,
+ Req, Db, Head),
+ JsonResp2 = apply_etag(JsonResp, Etag),
+ #extern_resp_args{
+ code = Code,
+ ctype = CType,
+ headers = ExtHeaders
+ } = couch_httpd_external:parse_external_response(JsonResp2),
+ JsonHeaders = couch_httpd_external:default_or_content_type(CType, ExtHeaders),
+ {ok, Resp} = start_chunked_response(Req, Code, JsonHeaders),
+ {ok, Resp, ?b2l(?l2b(Chunks))}.
+
+make_map_send_row_fun(QueryServer) ->
+ fun(Resp, Db, Row, _IncludeDocs, RowFront) ->
+ send_list_row(Resp, QueryServer, Db, Row, RowFront)
+ end.
+
+make_reduce_send_row_fun(QueryServer, Db) ->
+ fun(Resp, Row, RowFront) ->
+ send_list_row(Resp, QueryServer, Db, Row, RowFront)
+ end.
+
+send_list_row(Resp, QueryServer, Db, Row, RowFront) ->
+ try
+ [Go,Chunks] = couch_query_servers:render_list_row(QueryServer, Db, Row),
+ Chunk = RowFront ++ ?b2l(?l2b(Chunks)),
+ send_non_empty_chunk(Resp, Chunk),
+ case Go of
+ <<"chunks">> ->
+ {ok, ""};
+ <<"end">> ->
+ {stop, stop}
end
+ catch
+ throw:Error ->
+ send_chunked_error(Resp, Error),
+ throw({already_sent, Resp, Error})
+ end.
+
+send_non_empty_chunk(Resp, Chunk) ->
+ case Chunk of
+ [] -> ok;
+ _ -> send_chunk(Resp, Chunk)
end.
output_reduce_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, QueryArgs, nil) ->
@@ -291,7 +265,7 @@ output_reduce_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, Q
CurrentEtag = couch_httpd_view:view_group_etag(Group, {Lang, ListSrc, Accept}),
couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
StartListRespFun = make_reduce_start_resp_fun(QueryServer, Req, Db, CurrentEtag),
- SendListRowFun = make_reduce_send_row_fun(QueryServer, Req, Db),
+ SendListRowFun = make_reduce_send_row_fun(QueryServer, Db),
{ok, GroupRowsFun, RespFun} = couch_httpd_view:make_reduce_fold_funs(Req,
GroupLevel, QueryArgs, CurrentEtag,
@@ -303,7 +277,7 @@ output_reduce_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, Q
{ok, FoldResult} = couch_view:fold_reduce(View, Dir, {StartKey, StartDocId},
{EndKey, EndDocId}, GroupRowsFun, RespFun,
FoldAccInit),
- finish_list(Req, Db, QueryServer, CurrentEtag, FoldResult, StartListRespFun, null)
+ finish_list(Req, QueryServer, CurrentEtag, FoldResult, StartListRespFun, null)
end);
output_reduce_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, QueryArgs, Keys) ->
@@ -325,7 +299,7 @@ output_reduce_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, Q
couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
StartListRespFun = make_reduce_start_resp_fun(QueryServer, Req, Db, CurrentEtag),
- SendListRowFun = make_reduce_send_row_fun(QueryServer, Req, Db),
+ SendListRowFun = make_reduce_send_row_fun(QueryServer, Db),
{ok, GroupRowsFun, RespFun} = couch_httpd_view:make_reduce_fold_funs(Req,
GroupLevel, QueryArgs, CurrentEtag,
@@ -339,33 +313,30 @@ output_reduce_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, Q
couch_view:fold_reduce(View, Dir, {Key, StartDocId},
{Key, EndDocId}, GroupRowsFun, RespFun, FoldAcc)
end, {ok, FoldAccInit}, Keys),
- finish_list(Req, Db, QueryServer, CurrentEtag, FoldResult, StartListRespFun, null)
+ finish_list(Req, QueryServer, CurrentEtag, FoldResult, StartListRespFun, null)
end).
-finish_list(Req, Db, QueryServer, Etag, FoldResult, StartListRespFun, TotalRows) ->
- {Resp, BeginBody} = case FoldResult of
+finish_list(Req, QueryServer, Etag, FoldResult, StartFun, TotalRows) ->
+ case FoldResult of
{_, _, undefined, _} ->
- {ok, Resp2, BeginBody2} = render_head_for_empty_list(StartListRespFun, Req, Etag, TotalRows),
- {Resp2, BeginBody2};
- {_, _, Resp0, _} ->
- {Resp0, ""}
- end,
- JsonTail = couch_query_servers:render_list_tail(QueryServer, Req, Db),
- #extern_resp_args{
- data = Tail
- } = couch_httpd_external:parse_external_response(JsonTail),
- Chunk = BeginBody ++ binary_to_list(Tail),
- case Chunk of
- [] -> ok;
- _ -> send_chunk(Resp, Chunk)
+ {ok, Resp, BeginBody} =
+ render_head_for_empty_list(StartFun, Req, Etag, TotalRows),
+ [<<"end">>, Chunks] = couch_query_servers:render_list_tail(QueryServer),
+ Chunk = BeginBody ++ ?b2l(?l2b(Chunks)),
+ send_non_empty_chunk(Resp, Chunk);
+ {_, _, Resp, stop} ->
+ ok;
+ {_, _, Resp, _} ->
+ [<<"end">>, Chunks] = couch_query_servers:render_list_tail(QueryServer),
+ send_non_empty_chunk(Resp, ?b2l(?l2b(Chunks)))
end,
send_chunk(Resp, []).
render_head_for_empty_list(StartListRespFun, Req, Etag, null) ->
- StartListRespFun(Req, Etag, []);
+ StartListRespFun(Req, Etag, []); % for reduce
render_head_for_empty_list(StartListRespFun, Req, Etag, TotalRows) ->
StartListRespFun(Req, Etag, TotalRows, null, []).
-
+
send_doc_show_response(Lang, ShowSrc, DocId, nil, #httpd{mochi_req=MReq}=Req, Db) ->
% compute etag with no doc
Headers = MReq:get(headers),
@@ -373,7 +344,7 @@ send_doc_show_response(Lang, ShowSrc, DocId, nil, #httpd{mochi_req=MReq}=Req, Db
Accept = proplists:get_value('Accept', Hlist),
CurrentEtag = couch_httpd:make_etag({Lang, ShowSrc, nil, Accept}),
couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
- ExternalResp = couch_query_servers:render_doc_show(Lang, ShowSrc,
+ [<<"resp">>, ExternalResp] = couch_query_servers:render_doc_show(Lang, ShowSrc,
DocId, nil, Req, Db),
JsonResp = apply_etag(ExternalResp, CurrentEtag),
couch_httpd_external:send_external_response(Req, JsonResp)
@@ -387,7 +358,7 @@ send_doc_show_response(Lang, ShowSrc, DocId, #doc{revs=Revs}=Doc, #httpd{mochi_r
CurrentEtag = couch_httpd:make_etag({Lang, ShowSrc, Revs, Accept}),
% We know our etag now
couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
- ExternalResp = couch_query_servers:render_doc_show(Lang, ShowSrc,
+ [<<"resp">>, ExternalResp] = couch_query_servers:render_doc_show(Lang, ShowSrc,
DocId, Doc, Req, Db),
JsonResp = apply_etag(ExternalResp, CurrentEtag),
couch_httpd_external:send_external_response(Req, JsonResp)
diff --git a/src/couchdb/couch_js.c b/src/couchdb/couch_js.c
index 045e6c7f..d95b9db0 100644
--- a/src/couchdb/couch_js.c
+++ b/src/couchdb/couch_js.c
@@ -247,13 +247,13 @@ GC(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
static JSBool
Print(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
- uintN i, n;
+ uintN i;
size_t cl, bl;
JSString *str;
jschar *chars;
char *bytes;
- for (i = n = 0; i < argc; i++) {
+ for (i = 0; i < argc; i++) {
str = JS_ValueToString(context, argv[i]);
if (!str)
return JS_FALSE;
@@ -270,9 +270,8 @@ Print(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
fprintf(stdout, "%s%s", i ? " " : "", bytes);
JS_free(context, bytes);
}
- n++;
- if (n)
- fputc('\n', stdout);
+
+ fputc('\n', stdout);
fflush(stdout);
return JS_TRUE;
}
diff --git a/src/couchdb/couch_os_process.erl b/src/couchdb/couch_os_process.erl
index 66853b65..ef2e8bfc 100644
--- a/src/couchdb/couch_os_process.erl
+++ b/src/couchdb/couch_os_process.erl
@@ -53,7 +53,7 @@ prompt(Pid, Data) ->
{ok, Result} ->
Result;
Error ->
- ?LOG_DEBUG("OS Process Error ~p",[Error]),
+ ?LOG_ERROR("OS Process Error :: ~p",[Error]),
throw(Error)
end.
@@ -81,20 +81,22 @@ readline(OsProc, Acc) when is_record(OsProc, os_proc) ->
% Standard JSON functions
writejson(OsProc, Data) when is_record(OsProc, os_proc) ->
+ % ?LOG_DEBUG("OS Process Input :: ~p", [Data]),
true = writeline(OsProc, ?JSON_ENCODE(Data)).
readjson(OsProc) when is_record(OsProc, os_proc) ->
Line = readline(OsProc),
case ?JSON_DECODE(Line) of
- {[{<<"log">>,Msg}]} when is_binary(Msg) ->
+ [<<"log">>, Msg] when is_binary(Msg) ->
% we got a message to log. Log it and continue
- ?LOG_INFO("OS Process Log Message: ~s", [Msg]),
+ ?LOG_INFO("OS Process :: ~s", [Msg]),
readjson(OsProc);
{[{<<"error">>, Id}, {<<"reason">>, Reason}]} ->
throw({list_to_atom(binary_to_list(Id)),Reason});
{[{<<"reason">>, Reason}, {<<"error">>, Id}]} ->
throw({list_to_atom(binary_to_list(Id)),Reason});
Result ->
+ % ?LOG_DEBUG("OS Process Output :: ~p", [Result]),
Result
end.
diff --git a/src/couchdb/couch_query_servers.erl b/src/couchdb/couch_query_servers.erl
index ef2bde3b..5a1dc90a 100644
--- a/src/couchdb/couch_query_servers.erl
+++ b/src/couchdb/couch_query_servers.erl
@@ -18,9 +18,8 @@
-export([init/1, terminate/2, handle_call/3, handle_cast/2, handle_info/2,code_change/3,stop/0]).
-export([start_doc_map/2, map_docs/2, stop_doc_map/1]).
-export([reduce/3, rereduce/3,validate_doc_update/5]).
--export([render_doc_show/6,start_view_list/2,render_list_head/5,
- render_list_row/4, render_list_tail/3, render_reduce_head/3,
- render_reduce_row/4]).
+-export([render_doc_show/6, start_view_list/2,
+ render_list_head/4, render_list_row/3, render_list_tail/1]).
% -export([test/0]).
-include("couch_db.hrl").
@@ -183,7 +182,7 @@ render_doc_show(Lang, ShowSrc, DocId, Doc, Req, Db) ->
_ -> {{append_docid(DocId, JsonReqIn)}, couch_doc:to_json_obj(Doc, [revs])}
end,
try couch_os_process:prompt(Pid,
- [<<"show_doc">>, ShowSrc, JsonDoc, JsonReq]) of
+ [<<"show">>, ShowSrc, JsonDoc, JsonReq]) of
FormResp ->
FormResp
after
@@ -195,32 +194,24 @@ start_view_list(Lang, ListSrc) ->
true = couch_os_process:prompt(Pid, [<<"add_fun">>, ListSrc]),
{ok, {Lang, Pid}}.
-render_list_head({_Lang, Pid}, Req, Db, TotalRows, Offset) ->
- Head = {[{<<"total_rows">>, TotalRows}, {<<"offset">>, Offset}]},
+render_list_head({_Lang, Pid}, Req, Db, Head) ->
JsonReq = couch_httpd_external:json_req_obj(Req, Db),
- couch_os_process:prompt(Pid, [<<"list_begin">>, Head, JsonReq]).
+ couch_os_process:prompt(Pid, [<<"list">>, Head, JsonReq]).
-render_list_row({_Lang, Pid}, Req, Db, {{Key, DocId}, Value}) ->
+render_list_row({_Lang, Pid}, Db, {{Key, DocId}, Value}) ->
JsonRow = couch_httpd_view:view_row_obj(Db, {{Key, DocId}, Value}, false),
- JsonReq = couch_httpd_external:json_req_obj(Req, Db),
- couch_os_process:prompt(Pid, [<<"list_row">>, JsonRow, JsonReq]).
+ couch_os_process:prompt(Pid, [<<"list_row">>, JsonRow]);
-render_list_tail({Lang, Pid}, Req, Db) ->
- JsonReq = couch_httpd_external:json_req_obj(Req, Db),
- JsonResp = couch_os_process:prompt(Pid, [<<"list_tail">>, JsonReq]),
+render_list_row({_Lang, Pid}, _, {Key, Value}) ->
+ JsonRow = {[{key, Key}, {value, Value}]},
+ couch_os_process:prompt(Pid, [<<"list_row">>, JsonRow]).
+
+render_list_tail({Lang, Pid}) ->
+ JsonResp = couch_os_process:prompt(Pid, [<<"list_end">>]),
ok = ret_os_process(Lang, Pid),
- JsonResp.
+ JsonResp.
-
-render_reduce_head({_Lang, Pid}, Req, Db) ->
- Head = {[]},
- JsonReq = couch_httpd_external:json_req_obj(Req, Db),
- couch_os_process:prompt(Pid, [<<"list_begin">>, Head, JsonReq]).
-render_reduce_row({_Lang, Pid}, Req, Db, {Key, Value}) ->
- JsonRow = {[{key, Key}, {value, Value}]},
- JsonReq = couch_httpd_external:json_req_obj(Req, Db),
- couch_os_process:prompt(Pid, [<<"list_row">>, JsonRow, JsonReq]).
init([]) ->
diff --git a/test/query_server_spec.rb b/test/query_server_spec.rb
index ac519cca..51eb3526 100644
--- a/test/query_server_spec.rb
+++ b/test/query_server_spec.rb
@@ -22,31 +22,6 @@ require 'spec'
require 'json'
-JSON_REQ = {
- "body"=>"undefined",
- "verb"=>"GET",
- "info"=>{
- "disk_format_version"=>2,
- "purge_seq"=>0,
- "doc_count"=>9082,
- "instance_start_time"=>"1243713611467271",
- "update_seq"=>9512,
- "disk_size"=>27541604,
- "compact_running"=>false,
- "db_name"=>"toast",
- "doc_del_count"=>1
- },
- "cookie"=>{},
- "form"=>{},
- "query"=>{"q"=>"stuff"},
- "path"=>["toast", "_ext"],
- "headers"=>{
- "User-Agent"=>"curl/7.18.1 (i386-apple-darwin9.2.2) libcurl/7.18.1 zlib/1.2.3",
- "Host"=>"localhost:5984",
- "Accept"=>"*/*"
- }
-}
-
class OSProcessRunner
def self.run
trace = false
@@ -78,9 +53,9 @@ class OSProcessRunner
def add_fun(fun)
run(["add_fun", fun])
end
- def get_chunk
+ def get_chunks
resp = jsgets
- raise "not a chunk" unless resp.first == "chunk"
+ raise "not a chunk" unless resp.first == "chunks"
return resp[1]
end
def run json
@@ -103,10 +78,10 @@ class OSProcessRunner
# puts "err: #{err}" if err
if resp
rj = JSON.parse("[#{resp.chomp}]")[0]
- if rj.respond_to?(:[]) && !rj.is_a?(Array) &&
- if rj["log"]
- log = rj["log"]
- puts "log: #{log}" #if @trace
+ if rj.respond_to?(:[]) && rj.is_a?(Array)
+ if rj[0] == "log"
+ log = rj[1]
+ puts "log: #{log}" if @trace
rj = jsgets
end
end
@@ -177,131 +152,300 @@ describe "query server normal case" do
@qs.run(["rereduce", [@fun], vs]).should == [true, [45]]
end
end
+
# it "should validate"
+ describe "validation" do
+ before(:all) do
+ @fun = <<-JS
+ function(newDoc, oldDoc, userCtx) {
+ if (newDoc.bad) throw({forbidden:"bad doc"});
+ "foo bar";
+ }
+ JS
+ @qs.reset!
+ end
+ it "should allow good updates" do
+ @qs.run(["validate", @fun, {"good" => true}, {}, {}]).should == 1
+ end
+ it "should reject invalid updates" do
+ @qs.run(["validate", @fun, {"bad" => true}, {}, {}]).should == {"forbidden"=>"bad doc"}
+ end
+ end
describe "show" do
- before(:all) do
- @fun = <<-JS
- function(doc, req) {
- return [doc.title, doc.body].join(' - ')
- }
- JS
- @qs.reset!
- end
- it "should show" do
- @qs.rrun(["show_doc", @fun,
- {:title => "Best ever", :body => "Doc body"}, JSON_REQ])
- @qs.jsgets.should == {"body"=>"Best ever - Doc body"}
- end
- end
-
- describe "show with headers" do
- before(:all) do
- @fun = <<-JS
- function(doc, req) {
- return {
- headers : {"X-Plankton":"Rusty"},
- body : [doc.title, doc.body].join(' - ')
- }
-
- }
- JS
- @qs.reset!
- end
- it "should show" do
- @qs.rrun(["show_doc", @fun,
- {:title => "Best ever", :body => "Doc body"}])
- @qs.jsgets.should == {"headers"=>{"X-Plankton"=>"Rusty"}, "body"=>"Best ever - Doc body"}
- end
- end
-
- describe "list with headers" do
- before(:each) do
- @fun = <<-JS
- function(head, row, req) {
- if (head) return {headers : {"Content-Type" : "text/plain"}, code : 200, "body" : "foo"};
- if (row) return 'some "text" here';
- return "tail";
- };
- JS
- @qs.reset!
- @qs.add_fun(@fun).should == true
- end
- it "should send head, row, and tail" do
- @qs.rrun(["list_begin", {"total_rows"=>1000}, {"q" => "ok"}])
- @qs.jsgets.should == {"headers"=>{"Content-Type"=>"text/plain"}, "code"=>200, "body"=>"foo"}
- @qs.run(["list_row", {"foo"=>"bar"}, {"q" => "ok"}]).should == {"body"=>"some \"text\" here"}
- @qs.run(["list_tail", {"foo"=>"bar"}, {"q" => "ok"}]).should == {"body"=>"tail"}
- end
- end
-
- describe "list with headers and rows" do
- before(:each) do
- @fun = <<-JS
- function(head, row, req) {
- if (head) return {headers : {"Content-Type" : "text/plain"}, code : 200, "body" : "foo"};
- if (row) return 'row value '+row.value;
- return "tail "+req.q;
- };
- JS
- @qs.reset!
- @qs.add_fun(@fun).should == true
- end
- it "should render rows" do
- @qs.rrun(["list_begin", {"total_rows"=>1000}, {"q" => "ok"}])
- @qs.jsgets.should == {"headers"=>{"Content-Type"=>"text/plain"}, "code"=>200, "body"=>"foo"}
- @qs.run(["list_row", {"value"=>"bar"}, {"q" => "ok"}]).should == {"body"=>"row value bar"}
- @qs.run(["list_row", {"value"=>"baz"}, {"q" => "ok"}]).should == {"body"=>"row value baz"}
- @qs.run(["list_row", {"value"=>"bam"}, {"q" => "ok"}]).should == {"body"=>"row value bam"}
- @qs.run(["list_tail", {"q" => "ok"}]).should == {"body"=>"tail ok"}
- end
- end
- end # query server normal case
+ before(:all) do
+ @fun = <<-JS
+ function(doc, req) {
+ log("ok");
+ return [doc.title, doc.body].join(' - ');
+ }
+ JS
+ @qs.reset!
+ end
+ it "should show" do
+ @qs.rrun(["show", @fun,
+ {:title => "Best ever", :body => "Doc body"}])
+ @qs.jsgets.should == ["resp", {"body" => "Best ever - Doc body"}]
+ end
+ end
+
+ describe "show with headers" do
+ before(:all) do
+ @fun = <<-JS
+ function(doc, req) {
+ var resp = {"code":200, "headers":{"X-Plankton":"Rusty"}};
+ resp.body = [doc.title, doc.body].join(' - ');
+ return resp;
+ }
+ JS
+ @qs.reset!
+ end
+ it "should show headers" do
+ @qs.rrun(["show", @fun,
+ {:title => "Best ever", :body => "Doc body"}])
+ @qs.jsgets.should == ["resp", {"code"=>200,"headers" => {"X-Plankton"=>"Rusty"}, "body" => "Best ever - Doc body"}]
+ end
+ end
+
+# end
+# LIST TESTS
+# __END__
+
+ describe "raw list with headers" do
+ before(:each) do
+ @fun = <<-JS
+ function(head, req) {
+ start({headers:{"Content-Type" : "text/plain"}});
+ send("first chunk");
+ send('second "chunk"');
+ return "tail";
+ };
+ JS
+ @qs.reset!
+ @qs.add_fun(@fun).should == true
+ end
+ it "should do headers proper" do
+ @qs.rrun(["list", {"total_rows"=>1000}, {"q" => "ok"}])
+ @qs.jsgets.should == ["start", ["first chunk", 'second "chunk"'], {"headers"=>{"Content-Type"=>"text/plain"}}]
+ @qs.rrun(["list_end"])
+ @qs.jsgets.should == ["end", ["tail"]]
+ end
+ end
+
+ describe "list with rows" do
+ before(:each) do
+ @fun = <<-JS
+ function(head, req) {
+ send("first chunk");
+ send(req.q);
+ var row;
+ log("about to getRow " + typeof(getRow));
+ while(row = getRow()) {
+ send(row.key);
+ };
+ return "tail";
+ };
+ JS
+ @qs.run(["reset"]).should == true
+ @qs.add_fun(@fun).should == true
+ end
+ it "should should list em" do
+ @qs.rrun(["list", {"foo"=>"bar"}, {"q" => "ok"}])
+ @qs.jsgets.should == ["start", ["first chunk", "ok"], {"headers"=>{}}]
+ @qs.rrun(["list_row", {"key"=>"baz"}])
+ @qs.get_chunks.should == ["baz"]
+ @qs.rrun(["list_row", {"key"=>"bam"}])
+ @qs.get_chunks.should == ["bam"]
+ @qs.rrun(["list_end"])
+ @qs.jsgets.should == ["end", ["tail"]]
+ end
+ it "should work with zero rows" do
+ @qs.rrun(["list", {"foo"=>"bar"}, {"q" => "ok"}])
+ @qs.jsgets.should == ["start", ["first chunk", "ok"], {"headers"=>{}}]
+ @qs.rrun(["list_end"])
+ @qs.jsgets.should == ["end", ["tail"]]
+ end
+ end
+
+ describe "should buffer multiple chunks sent for a single row." do
+ before(:all) do
+ @fun = <<-JS
+ function(head, req) {
+ send("bacon");
+ var row;
+ log("about to getRow " + typeof(getRow));
+ while(row = getRow()) {
+ send(row.key);
+ send("eggs");
+ };
+ return "tail";
+ };
+ JS
+ @qs.reset!
+ @qs.add_fun(@fun).should == true
+ end
+ it "should should buffer em" do
+ @qs.rrun(["list", {"foo"=>"bar"}, {"q" => "ok"}])
+ @qs.jsgets.should == ["start", ["bacon"], {"headers"=>{}}]
+ @qs.rrun(["list_row", {"key"=>"baz"}])
+ @qs.get_chunks.should == ["baz", "eggs"]
+ @qs.rrun(["list_row", {"key"=>"bam"}])
+ @qs.get_chunks.should == ["bam", "eggs"]
+ @qs.rrun(["list_end"])
+ @qs.jsgets.should == ["end", ["tail"]]
+ end
+ end
+
+ describe "example list" do
+ before(:all) do
+ @fun = <<-JS
+ function(head, req) {
+ send("first chunk");
+ send(req.q);
+ var row;
+ while(row = getRow()) {
+ send(row.key);
+ };
+ return "early";
+ };
+ JS
+ @qs.reset!
+ @qs.add_fun(@fun).should == true
+ end
+ it "should run normal" do
+ @qs.run(["list", {"foo"=>"bar"}, {"q" => "ok"}]).should == ["start", ["first chunk", "ok"], {"headers"=>{}}]
+ @qs.run(["list_row", {"key"=>"baz"}]).should == ["chunks", ["baz"]]
+ @qs.run(["list_row", {"key"=>"bam"}]).should == ["chunks", ["bam"]]
+ @qs.run(["list_row", {"key"=>"foom"}]).should == ["chunks", ["foom"]]
+ @qs.run(["list_row", {"key"=>"fooz"}]).should == ["chunks", ["fooz"]]
+ @qs.run(["list_row", {"key"=>"foox"}]).should == ["chunks", ["foox"]]
+ @qs.run(["list_end"]).should == ["end" , ["early"]]
+ end
+ end
+
+ describe "only goes to 2 list" do
+ before(:all) do
+ @fun = <<-JS
+ function(head, req) {
+ send("first chunk");
+ send(req.q);
+ var row, i=0;
+ while(row = getRow()) {
+ send(row.key);
+ i += 1;
+ if (i > 2) {
+ return('early tail');
+ }
+ };
+ };
+ JS
+ @qs.reset!
+ @qs.add_fun(@fun).should == true
+ end
+ it "should end early" do
+ @qs.run(["list", {"foo"=>"bar"}, {"q" => "ok"}]).
+ should == ["start", ["first chunk", "ok"], {"headers"=>{}}]
+ @qs.run(["list_row", {"key"=>"baz"}]).
+ should == ["chunks", ["baz"]]
- describe "query server errors" do
- before(:each) do
- @qs = QueryServerRunner.run
- end
- after(:each) do
- @qs.close
- end
-
- describe "list" do
- before(:each) do
- @fun = <<-JS
- function(head, row, req) {
- if (head) return {headers : {"Content-Type" : "text/plain"}, code : 200, "body" : "foo"};
- if (row) return 'row value '+row.value;
- return "tail "+req.q;
- };
- JS
- @qs.run(["reset"]).should == true
- @qs.add_fun(@fun).should == true
- end
- it "should reset in the middle" do
- @qs.rrun(["list_begin", {"total_rows"=>1000}, {"q" => "ok"}])
- @qs.jsgets.should == {"headers"=>{"Content-Type"=>"text/plain"}, "code"=>200, "body"=>"foo"}
- @qs.run(["list_row", {"value"=>"bar"}, {"q" => "ok"}]).should == {"body"=>"row value bar"}
- @qs.run(["reset"]).should == true
- end
- end
- end #query server that errors
+ @qs.run(["list_row", {"key"=>"bam"}]).
+ should == ["chunks", ["bam"]]
+
+ @qs.run(["list_row", {"key"=>"foom"}]).
+ should == ["end", ["foom", "early tail"]]
+ # here's where js has to discard quit properly
+ @qs.run(["reset"]).
+ should == true
+ end
+ end
+end
-## tests for the generic "echo" external
-
-# describe "running an external" do
-# before(:all) do
-# @ext = ExternalRunner.run
-#
-# end
-# it "should respond to 'info'" do
-# @ext.rrun(['info'])
-# @ext.jsgets.should == ["info", "echo", "external server that prints its arguments as JSON"]
-# end
-# it "should echo the request" do
+def should_have_exited qs
+ begin
+ qs.run(["reset"])
+ "raise before this".should == true
+ rescue RuntimeError => e
+ e.message.should == "no response"
+ rescue Errno::EPIPE
+ true.should == true
+ end
+end
-# @ext.rrun(['req', req_obj])
-# @ext.jsgets.should == ["x"]
-# end
-# end
-#
+describe "query server that exits" do
+ before(:each) do
+ @qs = QueryServerRunner.run
+ end
+ after(:each) do
+ @qs.close
+ end
+
+ describe "old style list" do
+ before(:each) do
+ @fun = <<-JS
+ function(head, req, foo, bar) {
+ return "stuff";
+ }
+ JS
+ @qs.reset!
+ @qs.add_fun(@fun).should == true
+ end
+ it "should get a warning" do
+ resp = @qs.run(["list", {"foo"=>"bar"}, {"q" => "ok"}])
+ resp["error"].should == "render_error"
+ resp["reason"].should include("the list API has changed")
+ end
+ end
+
+ describe "only goes to 2 list" do
+ before(:each) do
+ @fun = <<-JS
+ function(head, req) {
+ send("bacon")
+ var row, i = 0;
+ while(row = getRow()) {
+ send(row.key);
+ i += 1;
+ if (i > 2) {
+ return('early');
+ }
+ };
+ }
+ JS
+ @qs.reset!
+ @qs.add_fun(@fun).should == true
+ end
+ it "should exit if erlang sends too many rows" do
+ @qs.run(["list", {"foo"=>"bar"}, {"q" => "ok"}]).should == ["start", ["bacon"], {"headers"=>{}}]
+ @qs.run(["list_row", {"key"=>"baz"}]).should == ["chunks", ["baz"]]
+ @qs.run(["list_row", {"key"=>"foom"}]).should == ["chunks", ["foom"]]
+ @qs.run(["list_row", {"key"=>"fooz"}]).should == ["end", ["fooz", "early"]]
+ @qs.rrun(["list_row", {"key"=>"foox"}])
+ @qs.jsgets["error"].should == "query_server_error"
+ should_have_exited @qs
+ end
+ end
+
+ describe "raw list" do
+ before(:each) do
+ @fun = <<-JS
+ function(head, req) {
+ send("first chunk");
+ send(req.q);
+ var row;
+ while(row = getRow()) {
+ send(row.key);
+ };
+ return "tail";
+ };
+ JS
+ @qs.run(["reset"]).should == true
+ @qs.add_fun(@fun).should == true
+ end
+ it "should exit if it gets a non-row in the middle" do
+ @qs.rrun(["list", {"foo"=>"bar"}, {"q" => "ok"}])
+ @qs.jsgets.should == ["start", ["first chunk", "ok"], {"headers"=>{}}]
+ @qs.run(["reset"])["error"].should == "query_server_error"
+ should_have_exited @qs
+ end
+ end
+end