summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--etc/couchdb/default.ini.tpl.in1
-rw-r--r--share/server/main.js54
-rw-r--r--share/www/script/couch_tests.js296
-rw-r--r--src/couchdb/couch_db.hrl15
-rw-r--r--src/couchdb/couch_httpd_db.erl13
-rw-r--r--src/couchdb/couch_httpd_external.erl13
-rw-r--r--src/couchdb/couch_httpd_show.erl168
-rw-r--r--src/couchdb/couch_httpd_view.erl100
-rw-r--r--src/couchdb/couch_query_servers.erl26
9 files changed, 516 insertions, 170 deletions
diff --git a/etc/couchdb/default.ini.tpl.in b/etc/couchdb/default.ini.tpl.in
index d16b1e71..a900a575 100644
--- a/etc/couchdb/default.ini.tpl.in
+++ b/etc/couchdb/default.ini.tpl.in
@@ -50,6 +50,7 @@ _restart = {couch_httpd_misc_handlers, handle_restart_req}
_view = {couch_httpd_view, handle_view_req}
_slow_view = {couch_httpd_view, handle_slow_view_req}
_show = {couch_httpd_show, handle_doc_show_req}
+_list = {couch_httpd_show, handle_view_list_req}
; The external module takes an optional argument allowing you to narrow it to a
; single script. Otherwise the script name is inferred from the first path section
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('<xml><node foo="bar"/></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('<xml><node foo="bar"/></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('<xml><node/></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('<xml><node/></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<li>Key: '+row.key+' Value: '+row.value+'</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>'};
+ }
+ }),
+ acceptSwitch: stringFun(function(head, row, req) {
+ return respondWith(req, {
+ html : function() {
+ if (head) {
+ return {body : "HTML <ul>"};
+ } else if (row) {
+ return {body : '\n<li>Key: '
+ +row.key+' Value: '+row.value+'</li>'};
+ } else { // tail
+ return {body : "</ul>"};
+ }
+ },
+ xml : function() {
+ if (head) {
+ return {body:'<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.
+ var entry = new XML('<entry/>');
+ entry.id = row.id;
+ entry.title = row.key;
+ entry.content = row.value;
+ return {body:entry};
+ } else {
+ return {body : "</feed>"};
+ }
+ }
+ })
+ })
+ }
+ };
+
+ 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();
diff --git a/src/couchdb/couch_db.hrl b/src/couchdb/couch_db.hrl
index 083468dc..eb449ab7 100644
--- a/src/couchdb/couch_db.hrl
+++ b/src/couchdb/couch_db.hrl
@@ -143,7 +143,6 @@
end_key = {},
limit = 10000000000, % a huge huge default number. Picked so we don't have
% to do different logic for when there is no limit
- % limit
stale = false,
direction = fwd,
start_docid = nil,
@@ -154,6 +153,20 @@
include_docs = false
}).
+-record(view_fold_helper_funs, {
+ reduce_count,
+ passed_end,
+ start_response,
+ send_row
+}).
+
+-record(extern_resp_args, {
+ code = 200,
+ data = <<>>,
+ ctype = "application/json",
+ headers = []
+}).
+
-record(group,
{type=view, % can also be slow_view
sig=nil,
diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl
index c7c9ec15..9ef7387c 100644
--- a/src/couchdb/couch_httpd_db.erl
+++ b/src/couchdb/couch_httpd_db.erl
@@ -192,7 +192,9 @@ db_req(#httpd{method='GET',path_parts=[_,<<"_all_docs_by_seq">>]}=Req, Db) ->
TotalRowCount = proplists:get_value(doc_count, Info),
FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db,
- TotalRowCount, fun couch_db:enum_docs_since_reduce_to_count/1),
+ TotalRowCount, #view_fold_helper_funs{
+ reduce_count = fun couch_db:enum_docs_since_reduce_to_count/1
+ }),
StartKey2 = case StartKey of
nil -> 0;
<<>> -> 100000000000;
@@ -306,7 +308,10 @@ all_docs_view(Req, Db, Keys) ->
case Keys of
nil ->
FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db,
- TotalRowCount, fun couch_db:enum_docs_reduce_to_count/1, PassedEndFun),
+ TotalRowCount, #view_fold_helper_funs{
+ reduce_count = fun couch_db:enum_docs_reduce_to_count/1,
+ passed_end = PassedEndFun
+ }),
AdapterFun = fun(#full_doc_info{id=Id}=FullDocInfo, Offset, Acc) ->
case couch_doc:to_doc_info(FullDocInfo) of
#doc_info{deleted=false, rev=Rev} ->
@@ -320,7 +325,9 @@ all_docs_view(Req, Db, Keys) ->
couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult});
_ ->
FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db,
- TotalRowCount, fun(Offset) -> Offset end),
+ TotalRowCount, #view_fold_helper_funs{
+ reduce_count = fun(Offset) -> Offset end
+ }),
KeyFoldFun = case Dir of
fwd ->
fun lists:foldl/3;
diff --git a/src/couchdb/couch_httpd_external.erl b/src/couchdb/couch_httpd_external.erl
index 7a682ddd..692044ba 100644
--- a/src/couchdb/couch_httpd_external.erl
+++ b/src/couchdb/couch_httpd_external.erl
@@ -14,18 +14,12 @@
-export([handle_external_req/2, handle_external_req/3]).
-export([send_external_response/2, json_req_obj/2]).
+-export([default_or_content_type/2, parse_external_response/1]).
-import(couch_httpd,[send_error/4]).
-include("couch_db.hrl").
--record(extern_resp_args, {
- code = 200,
- data = <<>>,
- ctype = "application/json",
- headers = []
-}).
-
% handle_external_req/2
% for the old type of config usage:
% _external = {couch_httpd_external, handle_external_req}
@@ -121,7 +115,10 @@ parse_external_response({Response}) ->
{<<"body">>, Value} ->
Args#extern_resp_args{data=Value, ctype="text/html"};
{<<"base64">>, Value} ->
- Args#extern_resp_args{data=couch_util:decodeBase64(Value), ctype="application/binary"};
+ Args#extern_resp_args{
+ data=couch_util:decodeBase64(Value),
+ ctype="application/binary"
+ };
{<<"headers">>, {Headers}} ->
NewHeaders = lists:map(fun({Header, HVal}) ->
{binary_to_list(Header), binary_to_list(HVal)}
diff --git a/src/couchdb/couch_httpd_show.erl b/src/couchdb/couch_httpd_show.erl
index d4b4997b..47d3fb46 100644
--- a/src/couchdb/couch_httpd_show.erl
+++ b/src/couchdb/couch_httpd_show.erl
@@ -12,7 +12,7 @@
-module(couch_httpd_show).
--export([handle_doc_show_req/2]).
+-export([handle_doc_show_req/2, handle_view_list_req/2]).
-include("couch_db.hrl").
@@ -22,50 +22,142 @@
start_json_response/2,send_chunk/2,end_json_response/1,
start_chunked_response/3, send_error/4]).
-handle_doc_show_req(#httpd{method='GET',path_parts=[_, _, DesignName, ShowName, Docid]}=Req, Db) ->
+handle_doc_show_req(#httpd{
+ method='GET',
+ path_parts=[_, _, DesignName, ShowName, Docid]
+ }=Req, Db) ->
DesignId = <<"_design/", DesignName/binary>>,
- % Anyway we can dry up this error handling?
- case (catch couch_httpd_db:couch_doc_open(Db, DesignId, [], [])) of
- {not_found, missing} ->
- throw({not_found, missing_design_doc});
- {not_found, deleted} ->
- throw({not_found, deleted_design_doc});
- DesignDoc ->
- #doc{body={Props}} = DesignDoc,
- Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),
- case proplists:get_value(<<"show">>, Props, nil) of
- {DocAndViews} ->
- case proplists:get_value(<<"docs">>, DocAndViews, nil) of
- nil ->
- throw({not_found, missing_show_docs});
- {DocShows} ->
- case proplists:get_value(ShowName, DocShows, nil) of
- nil ->
- throw({not_found, missing_show_doc_function});
- ShowSrc ->
- case (catch couch_httpd_db:couch_doc_open(
- Db, Docid, [], [])) of
- {not_found, missing} ->
- throw({not_found, missing});
- {not_found, deleted} ->
- throw({not_found, deleted});
- Doc ->
- % ok we have everythign we need. let's make it happen.
- send_doc_show_response(Lang, ShowSrc, Doc, Req, Db)
- end
- end
- end;
- nil ->
- throw({not_found, missing_show})
- end
- end;
+ #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, [], []),
+ Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),
+ ShowSrc = get_nested_json_value({Props}, [<<"shows">>, ShowName]),
+ Doc = couch_httpd_db:couch_doc_open(Db, Docid, [], []),
+ send_doc_show_response(Lang, ShowSrc, Doc, Req, Db);
handle_doc_show_req(#httpd{method='GET'}=Req, _Db) ->
- send_error(Req, 404, <<"form_error">>, <<"Invalid path.">>);
+ send_error(Req, 404, <<"show_error">>, <<"Invalid path.">>);
handle_doc_show_req(Req, _Db) ->
send_method_not_allowed(Req, "GET,HEAD").
+handle_view_list_req(#httpd{method='GET',path_parts=[_, _, DesignName, ListName, ViewName]}=Req, Db) ->
+ DesignId = <<"_design/", DesignName/binary>>,
+ #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, [], []),
+ Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),
+ ListSrc = get_nested_json_value({Props}, [<<"lists">>, ListName]),
+ send_view_list_response(Lang, ListSrc, ViewName, DesignId, Req, Db);
+
+handle_view_list_req(Req, _Db) ->
+ send_method_not_allowed(Req, "GET,HEAD").
+
+
+get_nested_json_value({Props}, [Key|Keys]) ->
+ case proplists:get_value(Key, Props, nil) of
+ nil -> throw({not_found, <<"missing json key: ", Key/binary>>});
+ Value -> get_nested_json_value(Value, Keys)
+ end;
+get_nested_json_value(Value, []) ->
+ Value;
+get_nested_json_value(_NotJSONObj, _) ->
+ throw({not_found, json_mismatch}).
+
+send_view_list_response(Lang, ListSrc, ViewName, DesignId, Req, Db) ->
+ % TODO add etags when we get view etags
+ #view_query_args{
+ stale = Stale,
+ reduce = Reduce
+ } = QueryArgs = couch_httpd_view:parse_view_query(Req),
+ case couch_view:get_map_view(Db, DesignId, ViewName, Stale) of
+ {ok, View} ->
+ output_map_list(Req, Lang, ListSrc, View, Db, QueryArgs);
+ {not_found, _Reason} ->
+ case couch_view:get_reduce_view(Db, DesignId, ViewName, Stale) of
+ {ok, ReduceView} ->
+ case Reduce of
+ false ->
+ MapView = couch_view:extract_map_view(ReduceView),
+ output_map_list(Req, Lang, ListSrc, MapView, Db, QueryArgs);
+ _ ->
+ throw({not_implemented, reduce_view_lists})
+ end;
+ {not_found, Reason} ->
+ throw({not_found, Reason})
+ end
+ end.
+
+output_map_list(Req, Lang, ListSrc, View, Db, QueryArgs) ->
+ #view_query_args{
+ limit = Limit,
+ direction = Dir,
+ skip = SkipCount,
+ start_key = StartKey,
+ start_docid = StartDocId
+ } = QueryArgs,
+ {ok, RowCount} = couch_view:get_row_count(View),
+ Start = {StartKey, StartDocId},
+ % get the os process here
+ % pass it into the view fold with closures
+ {ok, QueryServer} = couch_query_servers:start_view_list(Lang, ListSrc),
+
+ StartListRespFun = fun(Req2, Code, TotalViewCount, Offset) ->
+ JsonResp = couch_query_servers:render_list_head(QueryServer,
+ Req2, Db, TotalViewCount, Offset),
+ #extern_resp_args{
+ code = Code,
+ data = BeginBody,
+ ctype = CType,
+ headers = Headers
+ } = couch_httpd_external:parse_external_response(JsonResp),
+ JsonHeaders = couch_httpd_external:default_or_content_type(CType, Headers),
+ {ok, Resp} = start_chunked_response(Req, Code, JsonHeaders),
+ {ok, Resp, binary_to_list(BeginBody)}
+ end,
+
+ SendListRowFun = fun(Resp, Db2, {{Key, DocId}, Value},
+ RowFront, _IncludeDocs) ->
+ JsonResp = couch_query_servers:render_list_row(QueryServer,
+ Req, Db2, {{Key, DocId}, Value}),
+ #extern_resp_args{
+ data = RowBody
+ } = couch_httpd_external:parse_external_response(JsonResp),
+ RowFront2 = case RowFront of
+ nil -> [];
+ _ -> RowFront
+ end,
+ send_chunk(Resp, RowFront2 ++ binary_to_list(RowBody))
+ end,
+
+ FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, 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, []},
+ FoldResult = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
+ finish_view_list(Req, Db, QueryServer, RowCount, FoldResult, StartListRespFun).
+
+finish_view_list(Req, Db, QueryServer, TotalRows,
+ FoldResult, StartListRespFun) ->
+ case FoldResult of
+ {ok, {_, _, undefined, _}} ->
+ {ok, Resp, BeginBody} = StartListRespFun(Req, 200, TotalRows, null),
+ JsonTail = couch_query_servers:render_list_tail(QueryServer, Req, Db),
+ #extern_resp_args{
+ data = Tail
+ } = couch_httpd_external:parse_external_response(JsonTail),
+ send_chunk(Resp, BeginBody ++ Tail),
+ send_chunk(Resp, []);
+ {ok, {_, _, Resp, _AccRevRows}} ->
+ JsonTail = couch_query_servers:render_list_tail(QueryServer, Req, Db),
+ #extern_resp_args{
+ data = Tail
+ } = couch_httpd_external:parse_external_response(JsonTail),
+ send_chunk(Resp, Tail),
+ send_chunk(Resp, []);
+ Error ->
+ throw(Error)
+ end.
+
send_doc_show_response(Lang, ShowSrc, #doc{revs=[DocRev|_]}=Doc, #httpd{mochi_req=MReq}=Req, Db) ->
% make a term with etag-effecting Req components, but not always changing ones.
diff --git a/src/couchdb/couch_httpd_view.erl b/src/couchdb/couch_httpd_view.erl
index dca1e6e3..06d89a59 100644
--- a/src/couchdb/couch_httpd_view.erl
+++ b/src/couchdb/couch_httpd_view.erl
@@ -15,7 +15,8 @@
-export([handle_view_req/2,handle_slow_view_req/2]).
--export([parse_view_query/1,parse_view_query/2,make_view_fold_fun/5, make_view_fold_fun/6,finish_view_fold/3]).
+-export([parse_view_query/1,parse_view_query/2,make_view_fold_fun/5,
+ finish_view_fold/3, view_row_obj/3]).
-import(couch_httpd,
[send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,
@@ -92,8 +93,7 @@ output_map_view(Req, View, Db, QueryArgs, nil) ->
} = QueryArgs,
{ok, RowCount} = couch_view:get_row_count(View),
Start = {StartKey, StartDocId},
- FoldlFun = make_view_fold_fun(Req, QueryArgs, Db, RowCount,
- fun couch_view:reduce_to_count/1),
+ FoldlFun = make_view_fold_fun(Req, QueryArgs, Db, RowCount, #view_fold_helper_funs{reduce_count=fun couch_view:reduce_to_count/1}),
FoldAccInit = {Limit, SkipCount, undefined, []},
FoldResult = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
finish_view_fold(Req, RowCount, FoldResult);
@@ -114,7 +114,10 @@ output_map_view(Req, View, Db, QueryArgs, Keys) ->
QueryArgs#view_query_args{
start_key = Key,
end_key = Key
- }, Db, RowCount, fun couch_view:reduce_to_count/1),
+ }, Db, RowCount,
+ #view_fold_helper_funs{
+ reduce_count = fun couch_view:reduce_to_count/1
+ }),
couch_view:fold(View, Start, Dir, FoldlFun, FoldAcc)
end, {ok, FoldAccInit}, Keys),
finish_view_fold(Req, RowCount, FoldResult).
@@ -362,28 +365,21 @@ parse_view_query(Req, Keys, IsReduce) ->
end
end.
-
make_view_fold_fun(Req, QueryArgs, Db,
- TotalViewCount, ReduceCountFun) ->
+ TotalViewCount, HelperFuns) ->
#view_query_args{
end_key = EndKey,
end_docid = EndDocId,
direction = Dir
} = QueryArgs,
- PassedEndFun =
- case Dir of
- fwd ->
- fun(ViewKey, ViewId) ->
- couch_view:less_json([EndKey, EndDocId], [ViewKey, ViewId])
- end;
- rev->
- fun(ViewKey, ViewId) ->
- couch_view:less_json([ViewKey, ViewId], [EndKey, EndDocId])
- end
- end,
- make_view_fold_fun(Req, QueryArgs, Db, TotalViewCount, ReduceCountFun, PassedEndFun).
+
+ #view_fold_helper_funs{
+ passed_end = PassedEndFun,
+ start_response = StartRespFun,
+ send_row = SendRowFun,
+ reduce_count = ReduceCountFun
+ } = apply_default_helper_funs(HelperFuns, {Dir, EndKey, EndDocId}),
-make_view_fold_fun(Req, QueryArgs, Db, TotalViewCount, ReduceCountFun, PassedEndFun) ->
#view_query_args{
include_docs = IncludeDocs
} = QueryArgs,
@@ -401,20 +397,72 @@ make_view_fold_fun(Req, QueryArgs, Db, TotalViewCount, ReduceCountFun, PassedEnd
{_, _, AccSkip, _} when AccSkip > 0 ->
{ok, {AccLimit, AccSkip - 1, Resp, AccRevRows}};
{_, _, _, undefined} ->
- {ok, Resp2} = start_json_response(Req, 200),
Offset = ReduceCountFun(OffsetReds),
- JsonBegin = io_lib:format("{\"total_rows\":~w,\"offset\":~w,\"rows\":[\r\n",
- [TotalViewCount, Offset]),
- JsonObj = view_row_obj(Db, {{Key, DocId}, Value}, IncludeDocs),
- send_chunk(Resp2, JsonBegin ++ ?JSON_ENCODE(JsonObj)),
+ {ok, Resp2, BeginBody} = StartRespFun(Req, 200,
+ TotalViewCount, Offset),
+ SendRowFun(Resp2, Db,
+ {{Key, DocId}, Value}, BeginBody, IncludeDocs),
{ok, {AccLimit - 1, 0, Resp2, AccRevRows}};
{_, AccLimit, _, Resp} when (AccLimit > 0) ->
- JsonObj = view_row_obj(Db, {{Key, DocId}, Value}, IncludeDocs),
- send_chunk(Resp, ",\r\n" ++ ?JSON_ENCODE(JsonObj)),
+ SendRowFun(Resp, Db,
+ {{Key, DocId}, Value}, nil, IncludeDocs),
{ok, {AccLimit - 1, 0, Resp, AccRevRows}}
end
end.
+apply_default_helper_funs(#view_fold_helper_funs{
+ passed_end = PassedEnd,
+ start_response = StartResp,
+ send_row = SendRow
+}=Helpers, {Dir, EndKey, EndDocId}) ->
+ PassedEnd2 = case PassedEnd of
+ undefined -> make_passed_end_fun(Dir, EndKey, EndDocId);
+ _ -> PassedEnd
+ end,
+
+ StartResp2 = case StartResp of
+ undefined -> fun json_view_start_resp/4;
+ _ -> StartResp
+ end,
+
+ SendRow2 = case SendRow of
+ undefined -> fun send_json_view_row/5;
+ _ -> SendRow
+ end,
+
+ Helpers#view_fold_helper_funs{
+ passed_end = PassedEnd2,
+ start_response = StartResp2,
+ send_row = SendRow2
+ }.
+
+make_passed_end_fun(Dir, EndKey, EndDocId) ->
+ case Dir of
+ fwd ->
+ fun(ViewKey, ViewId) ->
+ couch_view:less_json([EndKey, EndDocId], [ViewKey, ViewId])
+ end;
+ rev->
+ fun(ViewKey, ViewId) ->
+ couch_view:less_json([ViewKey, ViewId], [EndKey, EndDocId])
+ end
+ end.
+
+json_view_start_resp(Req, Code, TotalViewCount, Offset) ->
+ {ok, Resp} = couch_httpd:start_json_response(Req, Code),
+ BeginBody = io_lib:format("{\"total_rows\":~w,\"offset\":~w,\"rows\":[\r\n",
+ [TotalViewCount, Offset]),
+ {ok, Resp, BeginBody}.
+
+send_json_view_row(Resp, Db, {{Key, DocId}, Value}, RowFront, IncludeDocs) ->
+ JsonObj = view_row_obj(Db, {{Key, DocId}, Value}, IncludeDocs),
+ RowFront2 = case RowFront of
+ nil -> ",\r\n";
+ _ -> RowFront
+ end,
+ send_chunk(Resp, RowFront2 ++ ?JSON_ENCODE(JsonObj)).
+
+
view_row_obj(Db, {{Key, DocId}, Value}, IncludeDocs) ->
case DocId of
error ->
diff --git a/src/couchdb/couch_query_servers.erl b/src/couchdb/couch_query_servers.erl
index 8d473381..a6bb6e06 100644
--- a/src/couchdb/couch_query_servers.erl
+++ b/src/couchdb/couch_query_servers.erl
@@ -17,7 +17,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,render_doc_show/5]).
+-export([reduce/3, rereduce/3,validate_doc_update/5]).
+-export([render_doc_show/5,start_view_list/2,render_list_head/5, render_list_row/4, render_list_tail/3]).
% -export([test/0]).
-include("couch_db.hrl").
@@ -134,6 +135,29 @@ render_doc_show(Lang, ShowSrc, Doc, Req, Db) ->
ok = ret_os_process(Lang, Pid)
end.
+start_view_list(Lang, ListSrc) ->
+ Pid = get_os_process(Lang),
+ 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}]},
+ JsonReq = couch_httpd_external:json_req_obj(Req, Db),
+ couch_os_process:prompt(Pid, [<<"list_begin">>, Head, JsonReq]).
+
+render_list_row({_Lang, Pid}, Req, 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]).
+
+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]),
+ ok = ret_os_process(Lang, Pid),
+ JsonResp.
+
+
+
init([]) ->
% read config and register for configuration changes