From 0a46c330072a3811d98a5c989d4c6486cff83df2 Mon Sep 17 00:00:00 2001 From: John Christopher Anderson Date: Fri, 23 Jan 2009 00:53:05 +0000 Subject: View list functions can stream views in any format. See list_views test for details. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@736876 13f79535-47bb-0310-9956-ffa450edef68 --- share/server/main.js | 54 +++++--- share/www/script/couch_tests.js | 296 +++++++++++++++++++++++++++++----------- 2 files changed, 257 insertions(+), 93 deletions(-) (limited to 'share') diff --git a/share/server/main.js b/share/server/main.js index 38ca326c..d2b9b7e0 100644 --- a/share/server/main.js +++ b/share/server/main.js @@ -332,28 +332,32 @@ while (cmd = eval(readline())) { validateFun(newDoc, oldDoc, userCtx); print("1"); } catch (error) { - print(toJSON(error)); + respond(error); } break; case "show_doc": var funSrc = cmd[1]; var doc = cmd[2]; var req = cmd[3]; - try { - var formFun = compileFunction(funSrc); - var rendered = formFun(doc, req); - print(toJSON(rendered)); - } catch (error) { - // Available error fields: - // message, fileName, lineNumber, stack, name - log("doc show function raised error: "+error.toString()); - log("stacktrace: "+error.stack); - try { - print(toJSON(error)); - } catch (e) { - print({"error":error.toString()}); - } - } + var formFun = compileFunction(funSrc); + runRenderFunction(formFun, [doc, req]); + break; + case "list_begin": + var listFun = funs[0]; + var head = cmd[1]; + var req = cmd[2]; + runRenderFunction(listFun, [head, null, req]); + break; + case "list_row": + var listFun = funs[0]; + var row = cmd[1]; + var req = cmd[2]; + runRenderFunction(listFun, [null, row, req]); + break; + case "list_tail": + var listFun = funs[0]; + var req = cmd[1]; + runRenderFunction(listFun, [null, null, req]); break; default: print(toJSON({error: "query_server_error", @@ -365,6 +369,24 @@ while (cmd = eval(readline())) { } } +function runRenderFunction(renderFun, args) { + try { + var result = renderFun.apply(null, args); + respond(result); + } catch(e) { + log("function raised error: "+e.toString()); + log("stacktrace: "+e.stack); + } +}; + +// prints the object as JSON, and rescues and logs any toJSON() related errors +function respond(obj) { + try { + print(toJSON(obj)); + } catch(e) { + log("Error converting object to JSON: " + e.toString()); + } +} function compileFunction(source) { try { diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js index dbdc27ce..9fea3be7 100644 --- a/share/www/script/couch_tests.js +++ b/share/www/script/couch_tests.js @@ -2301,44 +2301,43 @@ var tests = { var designDoc = { _id:"_design/template", language: "javascript", - show: { - docs: { - "hello" : stringFun(function() { - return { - body : "Hello World" - }; - }), - "just-name" : stringFun(function(doc, req) { - return { - body : "Just " + doc.name - }; - }), - "req-info" : stringFun(function(doc, req) { - return { - json : req - } - }), - "xml-type" : stringFun(function(doc, req) { - return { - "headers" : { - "Content-Type" : "application/xml" - }, - "body" : new XML('') - } - }), - "no-set-etag" : stringFun(function(doc, req) { + shows: { + "hello" : stringFun(function() { + return { + body : "Hello World" + }; + }), + "just-name" : stringFun(function(doc, req) { + return { + body : "Just " + doc.name + }; + }), + "req-info" : stringFun(function(doc, req) { + return { + json : req + } + }), + "xml-type" : stringFun(function(doc, req) { + return { + "headers" : { + "Content-Type" : "application/xml" + }, + "body" : new XML('') + } + }), + "no-set-etag" : stringFun(function(doc, req) { + return { + headers : { + "Etag" : "skipped" + }, + "body" : "something" + } + }), + "accept-switch" : stringFun(function(doc, req) { + if (req.headers["Accept"].match(/image/)) { return { - headers : { - "Etag" : "skipped" - }, - "body" : "something" - } - }), - "accept-switch" : stringFun(function(doc, req) { - if (req.headers["Accept"].match(/image/)) { - return { - // a 16x16 px version of the CouchDB logo - "base64" : + // a 16x16 px version of the CouchDB logo + "base64" : ["iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAsV", "BMVEUAAAD////////////////////////5ur3rEBn////////////////wDBL/", "AADuBAe9EB3IEBz/7+//X1/qBQn2AgP/f3/ilpzsDxfpChDtDhXeCA76AQH/v7", @@ -2348,47 +2347,47 @@ var tests = { "zMxw/4OleiJlHeUtv2X6RbNO1Uqj9g0RMCuQO0vBIg4vMFeOpCWIWmDOw82fZx", "vaND1c8OG4vrdOqD8YwgpDYDxRgkSm5rwu0nQVBJuMg++pLXZyr5jnc1BaH4GT", "LvEliY253nA3pVhQqdPt0f/erJkMGMB8xucAAAAASUVORK5CYII="].join(''), - headers : { - "Content-Type" : "image/png", - "Vary" : "Accept" // we set this for proxy caches - } + headers : { + "Content-Type" : "image/png", + "Vary" : "Accept" // we set this for proxy caches + } + }; + } else { + return { + "body" : "accepting text requests", + headers : { + "Content-Type" : "text/html", + "Vary" : "Accept" + } + }; + } + }), + "respondWith" : stringFun(function(doc, req) { + registerType("foo", "application/foo","application/x-foo"); + return respondWith(req, { + html : function() { + return { + body:"Ha ha, you said \"" + doc.word + "\"." }; - } else { + }, + xml : function() { + var xml = new XML(''); + // Becase Safari can't stand to see that dastardly + // E4X outside of a string. Outside of tests you + // can just use E4X literals. + this.eval('xml.node.@foo = doc.word'); return { - "body" : "accepting text requests", - headers : { - "Content-Type" : "text/html", - "Vary" : "Accept" - } + body: xml }; - } - }), - "respondWith" : stringFun(function(doc, req) { - registerType("foo", "application/foo","application/x-foo"); - return respondWith(req, { - html : function() { - return { - body:"Ha ha, you said \"" + doc.word + "\"." - }; - }, - xml : function() { - var xml = new XML(''); - // becase Safari can't stand to see that dastardly - // E4X outside of a string. - this.eval('xml.node.@foo = doc.word'); - return { - body: xml - }; - }, - foo : function() { - return { - body: "foofoo" - }; - }, - fallback : "html" - }); - }) - } + }, + foo : function() { + return { + body: "foofoo" + }; + }, + fallback : "html" + }); + }) } }; T(db.save(designDoc).ok); @@ -2418,12 +2417,15 @@ var tests = { T(resp.error == "not_found"); T(resp.reason == "missing"); + // show with missing func + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/missing/"+docid); + T(xhr.status == 404); + // missing design doc xhr = CouchDB.request("GET", "/test_suite_db/_show/missingdoc/just-name/"+docid); T(xhr.status == 404); var resp = JSON.parse(xhr.responseText); T(resp.error == "not_found"); - T(resp.reason == "missing_design_doc"); // query parameters xhr = CouchDB.request("GET", "/test_suite_db/_show/template/req-info/"+docid+"?foo=bar", { @@ -2504,7 +2506,7 @@ var tests = { T(xhr.status == 304); // update design doc function - designDoc.show.docs["just-name"] = (function(doc, req) { + designDoc.shows["just-name"] = (function(doc, req) { return { body : "Just old " + doc.name }; @@ -2553,6 +2555,146 @@ var tests = { T(xhr.responseText.match(/foofoo/)); }, + list_views : function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + function stringFun(fun) { + var string = fun.toSource ? fun.toSource() : "(" + fun.toString() + ")"; + return string; + } + + var designDoc = { + _id:"_design/lists", + language: "javascript", + views : { + basicView : { + map : stringFun(function(doc) { + emit(doc.integer, doc.string); + }) + }, + withReduce : { + map : stringFun(function(doc) { + emit(doc.integer, doc.string); + }), + reduce : stringFun(function(keys, values, rereduce) { + if (rereduce) { + return sum(values); + } else { + return values.length; + } + }) + } + }, + lists: { + simpleForm: stringFun(function(head, row, req) { + if (row) { + // we ignore headers on rows and tail + return {body : '\n
  • Key: '+row.key+' Value: '+row.value+'
  • '}; + } else if (head) { + // we return an object (like those used by external and show) + // so that we can specify headers + return { + body : '

    Total Rows: ' + + head.total_rows + + ' Offset: ' + head.offset + + '

    '}; + } + }), + acceptSwitch: stringFun(function(head, row, req) { + return respondWith(req, { + html : function() { + if (head) { + return {body : "HTML "}; + } + }, + xml : function() { + if (head) { + return {body:'' + +'Test XML Feed'}; + } 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. + var entry = new XML(''); + entry.id = row.id; + entry.title = row.key; + entry.content = row.value; + return {body:entry}; + } else { + return {body : ""}; + } + } + }) + }) + } + }; + + T(db.save(designDoc).ok); + + var docs = makeDocs(0, 10); + var saveResult = db.bulkSave(docs); + T(saveResult.ok); + + var view = db.view('lists/basicView'); + T(view.total_rows == 10); + + // standard get + var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/basicView"); + T(xhr.status == 200); + T(/Total Rows/.test(xhr.responseText)); + T(/Key: 1/.test(xhr.responseText)); + + // get with query params + var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/basicView?startkey=3"); + T(xhr.status == 200); + T(/Total Rows/.test(xhr.responseText)); + T(!(/Key: 1/.test(xhr.responseText))); + + // with 0 rows + var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/basicView?startkey=30"); + T(xhr.status == 200); + T(/Total Rows/.test(xhr.responseText)); + T(/Offset: null/.test(xhr.responseText)); + + // when there is a reduce present, but not used + var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/withReduce?reduce=false"); + T(xhr.status == 200); + T(/Total Rows/.test(xhr.responseText)); + T(/Key: 1/.test(xhr.responseText)); + + // with accept headers for HTML + xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/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/_list/lists/acceptSwitch/basicView", { + headers: { + "Accept": 'application/xml' + } + }); + T(xhr.getResponseHeader("Content-Type") == "application/xml"); + T(xhr.responseText.match(/XML/)); + T(xhr.responseText.match(/entry/)); + }, + compact: function(debug) { var db = new CouchDB("test_suite_db"); db.deleteDb(); -- cgit v1.2.3