summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--share/www/script/couch_tests.js98
-rw-r--r--src/couchdb/couch_db.hrl3
-rw-r--r--src/couchdb/couch_httpd_db.erl12
-rw-r--r--src/couchdb/couch_httpd_view.erl82
4 files changed, 167 insertions, 28 deletions
diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js
index b2b0bbb1..d8503117 100644
--- a/share/www/script/couch_tests.js
+++ b/share/www/script/couch_tests.js
@@ -1089,6 +1089,102 @@ var tests = {
T(results.total_rows == 0);
},
+ view_include_docs: function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ var docs = makeDocs(0, 100);
+ T(db.bulkSave(docs).ok);
+
+ var designDoc = {
+ _id:"_design/test",
+ language: "javascript",
+ views: {
+ all_docs: {
+ map: "function(doc) { emit(doc.integer, doc.string) }"
+ },
+ with_prev: {
+ map: "function(doc){if(doc.prev) emit(doc._id,{'_rev':doc.prev}); else emit(doc._id,{'_rev':doc._rev});}"
+ },
+ summate: {
+ map:"function (doc) {emit(doc.integer, doc.integer)};",
+ reduce:"function (keys, values) { return sum(values); };"
+ }
+ }
+ }
+ T(db.save(designDoc).ok);
+
+ var resp = db.view('test/all_docs', {include_docs: true, count: 2});
+ T(resp.rows.length == 2);
+ T(resp.rows[0].id == "0");
+ T(resp.rows[0].doc._id == "0");
+ T(resp.rows[1].id == "1");
+ T(resp.rows[1].doc._id == "1");
+
+ resp = db.view('test/all_docs', {include_docs: true}, [29, 74]);
+ T(resp.rows.length == 2);
+ T(resp.rows[0].doc._id == "29");
+ T(resp.rows[1].doc.integer == 74);
+
+ resp = db.allDocs({count: 2, skip: 1, include_docs: true});
+ T(resp.rows.length == 2);
+ T(resp.rows[0].doc.integer == 1);
+ T(resp.rows[1].doc.integer == 10);
+
+ resp = db.allDocs({include_docs: true}, ['not_a_doc']);
+ T(resp.rows.length == 1);
+ T(!resp.rows[0].doc);
+
+ resp = db.allDocs({include_docs: true}, ["1", "foo"]);
+ T(resp.rows.length == 2);
+ T(resp.rows[0].doc.integer == 1);
+ T(!resp.rows[1].doc);
+
+ resp = db.allDocs({include_docs: true, count: 0});
+ T(resp.rows.length == 0);
+
+ // No reduce support
+ try {
+ resp = db.view('test/summate', {include_docs: true});
+ alert(JSON.stringify(resp));
+ T(0==1);
+ } catch (e) {
+ T(e.error == 'query_parse_error');
+ }
+
+ // Check emitted _rev controls things
+ resp = db.allDocs({include_docs: true}, ["0"]);
+ var before = resp.rows[0].doc;
+ var after = db.open("0");
+ after.integer = 100
+ after.prev = after._rev;
+ db.save(after);
+ after = db.open("0");
+ T(after._rev != after.prev);
+ T(after.integer == 100);
+
+ // should emit the previous revision
+ resp = db.view("test/with_prev", {include_docs: true}, ["0"]);
+ T(resp.rows[0].doc._id == "0");
+ T(resp.rows[0].doc._rev == before._rev);
+ T(!resp.rows[0].doc.prev);
+ T(resp.rows[0].doc.integer == 0);
+
+ var xhr = CouchDB.request("POST", "/test_suite_db/_compact");
+ T(xhr.status == 202)
+ while (db.info().compact_running) {}
+
+ resp = db.view("test/with_prev", {include_docs: true}, ["0", "23"]);
+ T(resp.rows.length == 2);
+ T(resp.rows[0].key == "0");
+ T(resp.rows[0].id == "0");
+ T(!resp.rows[0].doc);
+ T(resp.rows[0].error == "missing");
+ T(resp.rows[1].doc.integer == 23);
+ },
+
view_multi_key_all_docs: function(debug) {
var db = new CouchDB("test_suite_db");
db.deleteDb();
@@ -1126,7 +1222,9 @@ var tests = {
rows = db.allDocs({}, [1, "i_dont_exist", "0"]).rows;
T(rows.length == 3);
T(rows[0].error == "not_found");
+ T(!rows[0].id);
T(rows[1].error == "not_found");
+ T(!rows[1].id);
T(rows[2].id == rows[2].key && rows[2].key == "0");
},
diff --git a/src/couchdb/couch_db.hrl b/src/couchdb/couch_db.hrl
index f5f4a0f1..bf98e182 100644
--- a/src/couchdb/couch_db.hrl
+++ b/src/couchdb/couch_db.hrl
@@ -124,7 +124,8 @@
end_docid = {},
skip = 0,
group_level = 0,
- reduce = true
+ reduce = true,
+ include_docs = false
}).
diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl
index 20805c85..240b2730 100644
--- a/src/couchdb/couch_httpd_db.erl
+++ b/src/couchdb/couch_httpd_db.erl
@@ -13,7 +13,7 @@
-module(couch_httpd_db).
-include("couch_db.hrl").
--export([handle_request/1, db_req/2]).
+-export([handle_request/1, db_req/2, couch_doc_open/4]).
-import(couch_httpd,
[send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,
@@ -179,8 +179,8 @@ db_req(#httpd{method='GET',path_parts=[_,<<"_all_docs_by_seq">>]}=Req, Db) ->
{ok, Info} = couch_db:get_db_info(Db),
TotalRowCount = proplists:get_value(doc_count, Info),
- FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, TotalRowCount,
- fun couch_db:enum_docs_since_reduce_to_count/1),
+ FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db,
+ TotalRowCount, fun couch_db:enum_docs_since_reduce_to_count/1),
StartKey2 = case StartKey of
nil -> 0;
<<>> -> 100000000000;
@@ -247,7 +247,7 @@ all_docs_view(Req, Db, Keys) ->
count = Count,
skip = SkipCount,
direction = Dir
- } = QueryArgs = couch_httpd_view:parse_view_query(Req, Keys),
+ } = QueryArgs = couch_httpd_view:parse_view_query(Req, Keys),
{ok, Info} = couch_db:get_db_info(Db),
TotalRowCount = proplists:get_value(doc_count, Info),
StartId = if is_binary(StartKey) -> StartKey;
@@ -257,7 +257,7 @@ all_docs_view(Req, Db, Keys) ->
case Keys of
nil ->
- FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs,
+ FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db,
TotalRowCount, fun couch_db:enum_docs_reduce_to_count/1),
AdapterFun = fun(#full_doc_info{id=Id}=FullDocInfo, Offset, Acc) ->
case couch_doc:to_doc_info(FullDocInfo) of
@@ -271,7 +271,7 @@ all_docs_view(Req, Db, Keys) ->
AdapterFun, FoldAccInit),
couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult});
_ ->
- FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs,
+ FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db,
TotalRowCount, fun(Offset) -> Offset end),
KeyFoldFun = case Dir of
fwd ->
diff --git a/src/couchdb/couch_httpd_view.erl b/src/couchdb/couch_httpd_view.erl
index de6a6af7..d281b82b 100644
--- a/src/couchdb/couch_httpd_view.erl
+++ b/src/couchdb/couch_httpd_view.erl
@@ -15,7 +15,7 @@
-export([handle_view_req/2,handle_temp_view_req/2]).
--export([parse_view_query/1,parse_view_query/2,make_view_fold_fun/4,finish_view_fold/3]).
+-export([parse_view_query/1,parse_view_query/2,make_view_fold_fun/5,finish_view_fold/3]).
-import(couch_httpd,
[send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,
@@ -26,7 +26,7 @@ design_doc_view(Req, Db, Id, ViewName, Keys) ->
<<"_design/", Id/binary>>, ViewName}) of
{ok, View} ->
QueryArgs = parse_view_query(Req, Keys),
- output_map_view(Req, View, QueryArgs, Keys);
+ output_map_view(Req, View, Db, QueryArgs, Keys);
{not_found, Reason} ->
case couch_view:get_reduce_view({couch_db:name(Db),
<<"_design/", Id/binary>>, ViewName}) of
@@ -37,7 +37,7 @@ design_doc_view(Req, Db, Id, ViewName, Keys) ->
case Reduce of
false ->
{reduce, _N, _Lang, MapView} = View,
- output_map_view(Req, MapView, QueryArgs, Keys);
+ output_map_view(Req, MapView, Db, QueryArgs, Keys);
_ ->
output_reduce_view(Req, View, QueryArgs, Keys)
end;
@@ -72,7 +72,7 @@ handle_temp_view_req(#httpd{method='POST'}=Req, Db) ->
case proplists:get_value(<<"reduce">>, Props, null) of
null ->
{ok, View} = couch_view:get_map_view({temp, couch_db:name(Db), Language, MapSrc}),
- output_map_view(Req, View, QueryArgs, Keys);
+ output_map_view(Req, View, Db, QueryArgs, Keys);
RedSrc ->
{ok, View} = couch_view:get_reduce_view(
{temp, couch_db:name(Db), Language, MapSrc, RedSrc}),
@@ -82,7 +82,7 @@ handle_temp_view_req(#httpd{method='POST'}=Req, Db) ->
handle_temp_view_req(Req, _Db) ->
send_method_not_allowed(Req, "POST").
-output_map_view(Req, View, QueryArgs, nil) ->
+output_map_view(Req, View, Db, QueryArgs, nil) ->
#view_query_args{
count = Count,
direction = Dir,
@@ -92,13 +92,13 @@ output_map_view(Req, View, QueryArgs, nil) ->
} = QueryArgs,
{ok, RowCount} = couch_view:get_row_count(View),
Start = {StartKey, StartDocId},
- FoldlFun = make_view_fold_fun(Req, QueryArgs, RowCount,
+ FoldlFun = make_view_fold_fun(Req, QueryArgs, Db, RowCount,
fun couch_view:reduce_to_count/1),
FoldAccInit = {Count, SkipCount, undefined, []},
FoldResult = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
finish_view_fold(Req, RowCount, FoldResult);
-output_map_view(Req, View, QueryArgs, Keys) ->
+output_map_view(Req, View, Db, QueryArgs, Keys) ->
#view_query_args{
count = Count,
direction = Dir,
@@ -114,7 +114,7 @@ output_map_view(Req, View, QueryArgs, Keys) ->
QueryArgs#view_query_args{
start_key = Key,
end_key = Key
- }, RowCount, fun couch_view:reduce_to_count/1),
+ }, Db, RowCount, 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).
@@ -305,6 +305,23 @@ parse_view_query(Req, Keys, IsReduce) ->
Args#view_query_args{reduce=true};
{"reduce", "false"} ->
Args#view_query_args{reduce=false};
+ {"include_docs", Value} ->
+ case IsReduce of
+ true ->
+ Msg = lists:flatten(io_lib:format("Bad URL query key for reduce operation: ~s", [Key])),
+ throw({query_parse_error, Msg});
+ _ ->
+ ok
+ end,
+ case Value of
+ "true" ->
+ Args#view_query_args{include_docs=true};
+ "false" ->
+ Args#view_query_args{include_docs=false};
+ _ ->
+ Msg1 = "Bad URL query value for 'include_docs' expected \"true\" or \"false\".",
+ throw({query_parse_error, Msg1})
+ end;
_ -> % unknown key
Msg = lists:flatten(io_lib:format(
"Bad URL query key:~s", [Key])),
@@ -331,10 +348,11 @@ parse_view_query(Req, Keys, IsReduce) ->
end.
-make_view_fold_fun(Req, QueryArgs, TotalViewCount, ReduceCountFun) ->
+make_view_fold_fun(Req, QueryArgs, Db, TotalViewCount, ReduceCountFun) ->
#view_query_args{
end_key = EndKey,
end_docid = EndDocId,
+ include_docs = IncludeDocs,
direction = Dir
} = QueryArgs,
@@ -367,26 +385,48 @@ make_view_fold_fun(Req, QueryArgs, TotalViewCount, ReduceCountFun) ->
{ok, Resp2} = start_json_response(Req, 200),
JsonBegin = io_lib:format("{\"total_rows\":~w,\"offset\":~w,\"rows\":[\r\n",
[TotalViewCount, Offset]),
- JsonObj = case DocId of
- error ->
- {[{key, Key}, {error, Value}]};
- _ ->
- {[{id, DocId}, {key, Key}, {value, Value}]}
- end,
+ JsonObj = view_row_obj(Db, {{Key, DocId}, Value}, IncludeDocs),
send_chunk(Resp2, JsonBegin ++ ?JSON_ENCODE(JsonObj)),
{ok, {AccCount - 1, 0, Resp2, AccRevRows}};
{_, AccCount, _, Resp} when (AccCount > 0) ->
- JsonObj = case DocId of
- error ->
- {[{key, Key}, {error, Value}]};
- _ ->
- {[{id, DocId}, {key, Key}, {value, Value}]}
- end,
+ JsonObj = view_row_obj(Db, {{Key, DocId}, Value}, IncludeDocs),
send_chunk(Resp, ",\r\n" ++ ?JSON_ENCODE(JsonObj)),
{ok, {AccCount - 1, 0, Resp, AccRevRows}}
end
end.
+view_row_obj(Db, {{Key, DocId}, Value}, IncludeDocs) ->
+ case DocId of
+ error ->
+ {[{key, Key}, {error, Value}]};
+ _ ->
+ case IncludeDocs of
+ true ->
+ Rev = case Value of
+ {Props} ->
+ case is_list(Props) of
+ true ->
+ proplists:get_value(<<"_rev">>, Props, []);
+ _ ->
+ []
+ end;
+ _ ->
+ []
+ end,
+ ?LOG_DEBUG("Include Doc: ~p ~p", [DocId, Rev]),
+ case (catch couch_httpd_db:couch_doc_open(Db, DocId,
+ Rev, [])) of
+ {{not_found, missing}, _} ->
+ {[{id, DocId}, {key, Key}, {value, Value}, {error, missing}]};
+ Doc ->
+ JsonDoc = couch_doc:to_json_obj(Doc, []),
+ {[{id, DocId}, {key, Key}, {value, Value}, {doc, JsonDoc}]}
+ end;
+ _ ->
+ {[{id, DocId}, {key, Key}, {value, Value}]}
+ end
+ end.
+
finish_view_fold(Req, TotalRows, FoldResult) ->
case FoldResult of
{ok, {_, _, undefined, _}} ->