From 04b2acac82d8b0ad60be485b8b1321c2fcbe6f57 Mon Sep 17 00:00:00 2001 From: John Christopher Anderson Date: Sun, 5 Oct 2008 18:54:36 +0000 Subject: include_docs option adds a doc member to view rows with the latest _rev of the document (or the _rev specified in the row value) git-svn-id: https://svn.apache.org/repos/asf/incubator/couchdb/trunk@701847 13f79535-47bb-0310-9956-ffa450edef68 --- share/www/script/couch_tests.js | 98 ++++++++++++++++++++++++++++++++++++++++ src/couchdb/couch_db.hrl | 3 +- src/couchdb/couch_httpd_db.erl | 12 ++--- src/couchdb/couch_httpd_view.erl | 82 ++++++++++++++++++++++++--------- 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, _}} -> -- cgit v1.2.3