diff options
-rw-r--r-- | share/www/script/couch.js | 6 | ||||
-rw-r--r-- | share/www/script/test/all_docs.js | 50 | ||||
-rw-r--r-- | share/www/script/test/changes.js | 13 | ||||
-rw-r--r-- | share/www/script/test/conflicts.js | 4 | ||||
-rw-r--r-- | share/www/script/test/etags_views.js | 6 | ||||
-rw-r--r-- | share/www/script/test/replication.js | 4 | ||||
-rw-r--r-- | src/couchdb/couch_db.erl | 8 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_db.erl | 132 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_view.erl | 20 |
9 files changed, 105 insertions, 138 deletions
diff --git a/share/www/script/couch.js b/share/www/script/couch.js index 81e25f1b..1c529183 100644 --- a/share/www/script/couch.js +++ b/share/www/script/couch.js @@ -207,12 +207,12 @@ function CouchDB(name, httpHeaders) { return this.allDocs({startkey:"_design", endkey:"_design0"}); }; - this.allDocsBySeq = function(options,keys) { + this.changes = function(options,keys) { var req = null; if(!keys) { - req = this.request("GET", this.uri + "_all_docs_by_seq" + encodeOptions(options)); + req = this.request("GET", this.uri + "_changes" + encodeOptions(options)); } else { - req = this.request("POST", this.uri + "_all_docs_by_seq" + encodeOptions(options), { + req = this.request("POST", this.uri + "_changes" + encodeOptions(options), { headers: {"Content-Type": "application/json"}, body: JSON.stringify({keys:keys}) }); diff --git a/share/www/script/test/all_docs.js b/share/www/script/test/all_docs.js index c695e24c..34c20921 100644 --- a/share/www/script/test/all_docs.js +++ b/share/www/script/test/all_docs.js @@ -42,54 +42,50 @@ couchTests.all_docs = function(debug) { T(all.offset == 2); // check that the docs show up in the seq view in the order they were created - var all_seq = db.allDocsBySeq(); + var changes = db.changes(); var ids = ["0","3","1","2"]; - for (var i=0; i < all_seq.rows.length; i++) { - var row = all_seq.rows[i]; - T(row.id == ids[i]); + for (var i=0; i < changes.results.length; i++) { + var row = changes.results[i]; + T(row.id == ids[i], "seq order"); }; // it should work in reverse as well - all_seq = db.allDocsBySeq({descending:true}); + changes = db.changes({descending:true}); ids = ["2","1","3","0"]; - for (var i=0; i < all_seq.rows.length; i++) { - var row = all_seq.rows[i]; - T(row.id == ids[i]); + for (var i=0; i < changes.results.length; i++) { + var row = changes.results[i]; + T(row.id == ids[i], "descending=true"); }; // check that deletions also show up right var doc1 = db.open("1"); var deleted = db.deleteDoc(doc1); T(deleted.ok); - all_seq = db.allDocsBySeq(); - + changes = db.changes(); // the deletion should make doc id 1 have the last seq num - T(all_seq.rows.length == 4); - T(all_seq.rows[3].id == "1"); - T(all_seq.rows[3].value.deleted); - - // is this a bug? - // T(all_seq.rows.length == all_seq.total_rows); + T(changes.results.length == 4); + T(changes.results[3].id == "1"); + // we've removed deletions from the changes feed as they are on the doc record not the doc_info + T(!changes.results[3].deleted); // do an update var doc2 = db.open("3"); doc2.updated = "totally"; db.save(doc2); - all_seq = db.allDocsBySeq(); + changes = db.changes(); // the update should make doc id 3 have the last seq num - T(all_seq.rows.length == 4); - T(all_seq.rows[3].id == "3"); + T(changes.results.length == 4); + T(changes.results[3].id == "3"); // ok now lets see what happens with include docs - all_seq = db.allDocsBySeq({include_docs: true}); - T(all_seq.rows.length == 4); - T(all_seq.rows[3].id == "3"); - T(all_seq.rows[3].doc.updated == "totally"); - - // and on the deleted one, no doc - T(all_seq.rows[2].value.deleted); - T(!all_seq.rows[2].doc); + changes = db.changes({include_docs: true}); + T(changes.results.length == 4); + T(changes.results[3].id == "3"); + T(changes.results[3].doc.updated == "totally"); + + T(changes.results[2].doc); + T(changes.results[2].doc._deleted); // test the all docs collates sanely db.save({_id: "Z", foo: "Z"}); diff --git a/share/www/script/test/changes.js b/share/www/script/test/changes.js index 690727af..a6ce51d5 100644 --- a/share/www/script/test/changes.js +++ b/share/www/script/test/changes.js @@ -12,7 +12,7 @@ function jsonp(obj) { T(jsonp_flag == 0); - T(obj.results.length == 1 && obj.last_seq==1) + T(obj.results.length == 1 && obj.last_seq==1, "jsonp") jsonp_flag = 1; } @@ -25,15 +25,15 @@ couchTests.changes = function(debug) { var req = CouchDB.request("GET", "/test_suite_db/_changes"); var resp = JSON.parse(req.responseText); - T(resp.results.length == 0 && resp.last_seq==0) + T(resp.results.length == 0 && resp.last_seq==0, "empty db") var docFoo = {_id:"foo", bar:1}; - db.save(docFoo); + T(db.save(docFoo).ok); req = CouchDB.request("GET", "/test_suite_db/_changes"); var resp = JSON.parse(req.responseText); - T(resp.results.length == 1 && resp.last_seq==1) + T(resp.results.length == 1 && resp.last_seq==1, "one doc db") T(resp.results[0].changes[0].rev == docFoo._rev) // test with callback @@ -232,6 +232,9 @@ couchTests.changes = function(debug) { function() { var authOpts = {"headers":{"WWW-Authenticate": "X-Couch-Test-Auth Chris Anderson:mp3"}}; + + var req = CouchDB.request("GET", "/_session", authOpts); + var resp = JSON.parse(req.responseText); T(db.save({"user" : "Noah Slater"}).ok); var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/userCtx", authOpts); @@ -242,7 +245,7 @@ couchTests.changes = function(debug) { T(docResp.ok); req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/userCtx", authOpts); resp = JSON.parse(req.responseText); - T(resp.results.length == 1); + T(resp.results.length == 1, "userCtx"); T(resp.results[0].id == docResp.id); }); diff --git a/share/www/script/test/conflicts.js b/share/www/script/test/conflicts.js index fa8162ab..b8b93946 100644 --- a/share/www/script/test/conflicts.js +++ b/share/www/script/test/conflicts.js @@ -42,9 +42,9 @@ couchTests.conflicts = function(debug) { T(e.error == "conflict"); } - var bySeq = db.allDocsBySeq(); + var changes = db.changes(); - T( bySeq.rows.length == 1) + T( changes.results.length == 1) // Now clear out the _rev member and save. This indicates this document is // new, not based on an existing revision. diff --git a/share/www/script/test/etags_views.js b/share/www/script/test/etags_views.js index c52be232..8d248983 100644 --- a/share/www/script/test/etags_views.js +++ b/share/www/script/test/etags_views.js @@ -72,11 +72,11 @@ couchTests.etags_views = function(debug) { }); T(xhr.status == 304); - // by seq - xhr = CouchDB.request("GET", "/test_suite_db/_all_docs_by_seq"); + // _changes + xhr = CouchDB.request("GET", "/test_suite_db/_changes"); T(xhr.status == 200); var etag = xhr.getResponseHeader("etag"); - xhr = CouchDB.request("GET", "/test_suite_db/_all_docs_by_seq", { + xhr = CouchDB.request("GET", "/test_suite_db/_changes", { headers: {"if-none-match": etag} }); T(xhr.status == 304); diff --git a/share/www/script/test/replication.js b/share/www/script/test/replication.js index 10b3ce73..c08d128b 100644 --- a/share/www/script/test/replication.js +++ b/share/www/script/test/replication.js @@ -100,12 +100,12 @@ couchTests.replication = function(debug) { }; this.afterAB1 = function(dbA, dbB) { - var rows = dbB.allDocsBySeq().rows; + var rows = dbB.changes({include_docs:true}).results; var rowCnt = 0; for (var i=0; i < rows.length; i++) { if (rows[i].id == "del1") { rowCnt += 1; - T(rows[i].value.deleted == true); + T(rows[i].doc._deleted == true); } }; T(rowCnt == 1); diff --git a/src/couchdb/couch_db.erl b/src/couchdb/couch_db.erl index 60d16904..9cace709 100644 --- a/src/couchdb/couch_db.erl +++ b/src/couchdb/couch_db.erl @@ -24,7 +24,7 @@ -export([increment_update_seq/1,get_purge_seq/1,purge_docs/2,get_last_purged/1]). -export([start_link/3,open_doc_int/3,set_admins/2,get_admins/1,ensure_full_commit/1]). -export([init/1,terminate/2,handle_call/3,handle_cast/2,code_change/3,handle_info/2]). --export([changes_since/5,read_doc/2,new_revid/1]). +-export([changes_since/5,changes_since/6,read_doc/2,new_revid/1]). -include("couch_db.hrl"). @@ -694,6 +694,9 @@ enum_docs_reduce_to_count(Reds) -> Count. changes_since(Db, Style, StartSeq, Fun, Acc) -> + changes_since(Db, Style, StartSeq, Fun, [], Acc). + +changes_since(Db, Style, StartSeq, Fun, Options, Acc) -> Wrapper = fun(DocInfo, _Offset, Acc2) -> #doc_info{revs=Revs} = DocInfo, case Style of @@ -706,7 +709,8 @@ changes_since(Db, Style, StartSeq, Fun, Acc) -> end, Fun(Infos, Acc2) end, - {ok, _LastReduction, AccOut} = couch_btree:fold(Db#db.docinfo_by_seq_btree, Wrapper, Acc, [{start_key, StartSeq + 1}]), + {ok, _LastReduction, AccOut} = couch_btree:fold(Db#db.docinfo_by_seq_btree, + Wrapper, Acc, [{start_key, StartSeq + 1}] ++ Options), {ok, AccOut}. count_changes_since(Db, SinceSeq) -> diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl index 614e1d64..d0960936 100644 --- a/src/couchdb/couch_httpd_db.erl +++ b/src/couchdb/couch_httpd_db.erl @@ -20,7 +20,8 @@ -import(couch_httpd, [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2, - start_json_response/2,send_chunk/2,end_json_response/1, + start_json_response/2,start_json_response/3, + send_chunk/2,end_json_response/1, start_chunked_response/3, absolute_uri/2, send/2, start_response_length/4]). @@ -73,11 +74,18 @@ start_sending_changes(Resp, _Else) -> handle_changes_req(#httpd{method='GET',path_parts=[DbName|_]}=Req, Db) -> {FilterFun, EndFilterFun} = make_filter_funs(Req, Db), - StartSeq = list_to_integer(couch_httpd:qs_value(Req, "since", "0")), - {ok, Resp} = start_json_response(Req, 200), + {Dir, StartSeq} = case couch_httpd:qs_value(Req, "descending", "false") of + "false" -> + {fwd, list_to_integer(couch_httpd:qs_value(Req, "since", "0"))}; + "true" -> + {rev, 1000000000000000}; % super big value, should use current db seq + _Bad -> throw({bad_request, "descending must be true or false"}) + end, ResponseType = couch_httpd:qs_value(Req, "feed", "normal"), - start_sending_changes(Resp, ResponseType), if ResponseType == "continuous" orelse ResponseType == "longpoll" -> + {ok, Resp} = start_json_response(Req, 200), + start_sending_changes(Resp, ResponseType), + Self = self(), {ok, Notify} = couch_db_update_notifier:start_link( fun({_, DbName0}) when DbName0 == DbName -> @@ -96,10 +104,17 @@ handle_changes_req(#httpd{method='GET',path_parts=[DbName|_]}=Req, Db) -> get_rest_db_updated() % clean out any remaining update messages end; true -> - {ok, {LastSeq, _Prepend, _, _, _}} = - send_changes(Req, Resp, Db, StartSeq, <<"">>, "normal", - FilterFun, EndFilterFun), - end_sending_changes(Resp, LastSeq, ResponseType) + {ok, Info} = couch_db:get_db_info(Db), + CurrentEtag = couch_httpd:make_etag(Info), + couch_httpd:etag_respond(Req, CurrentEtag, fun() -> + % send the etag + {ok, Resp} = start_json_response(Req, 200, [{"Etag", CurrentEtag}]), + start_sending_changes(Resp, ResponseType), + {ok, {_, LastSeq, _Prepend, _, _, _, _}} = + send_changes(Req, Resp, Db, Dir, StartSeq, <<"">>, "normal", + FilterFun, EndFilterFun), + end_sending_changes(Resp, LastSeq, ResponseType) + end) end; handle_changes_req(#httpd{path_parts=[_,<<"_changes">>]}=Req, _Db) -> @@ -129,7 +144,7 @@ end_sending_changes(Resp, EndSeq, _Else) -> keep_sending_changes(#httpd{user_ctx=UserCtx,path_parts=[DbName|_]}=Req, Resp, Db, StartSeq, Prepend, Timeout, TimeoutFun, ResponseType, Filter, End) -> - {ok, {EndSeq, Prepend2, _, _, _}} = send_changes(Req, Resp, Db, StartSeq, + {ok, {_, EndSeq, Prepend2, _, _, _, _}} = send_changes(Req, Resp, Db, fwd, StartSeq, Prepend, ResponseType, Filter, End), couch_db:close(Db), if @@ -146,37 +161,47 @@ keep_sending_changes(#httpd{user_ctx=UserCtx,path_parts=[DbName|_]}=Req, Resp, end end. -changes_enumerator(DocInfos, {_, _, FilterFun, Resp, "continuous"}) -> - [#doc_info{id=Id, high_seq=Seq}|_] = DocInfos, +changes_enumerator(DocInfos, {Db, _, _, FilterFun, Resp, "continuous", IncludeDocs}) -> + [#doc_info{id=Id, high_seq=Seq, revs=[#rev_info{rev=Rev}|_]}|_] = DocInfos, Results0 = [FilterFun(DocInfo) || DocInfo <- DocInfos], Results = [Result || Result <- Results0, Result /= null], case Results of [] -> - {ok, {Seq, nil, FilterFun, Resp, "continuous"}}; + {ok, {Db, Seq, nil, FilterFun, Resp, "continuous", IncludeDocs}}; _ -> - send_chunk(Resp, [?JSON_ENCODE({[{seq,Seq},{id,Id},{changes,Results}]}) + send_chunk(Resp, [?JSON_ENCODE(changes_row(Db, Seq, Id, Results, Rev, IncludeDocs)) |"\n"]), - {ok, {Seq, nil, FilterFun, Resp, "continuous"}} + {ok, {Db, Seq, nil, FilterFun, Resp, "continuous", IncludeDocs}} end; -changes_enumerator(DocInfos, {_, Prepend, FilterFun, Resp, _}) -> - [#doc_info{id=Id, high_seq=Seq}|_] = DocInfos, +changes_enumerator(DocInfos, {Db, _, Prepend, FilterFun, Resp, _, IncludeDocs}) -> + [#doc_info{id=Id, high_seq=Seq, revs=[#rev_info{rev=Rev}|_]}|_] = DocInfos, Results0 = [FilterFun(DocInfo) || DocInfo <- DocInfos], Results = [Result || Result <- Results0, Result /= null], case Results of [] -> - {ok, {Seq, Prepend, FilterFun, Resp, nil}}; + {ok, {Db, Seq, Prepend, FilterFun, Resp, nil, IncludeDocs}}; _ -> - send_chunk(Resp, [Prepend, ?JSON_ENCODE({[{seq,Seq},{id,Id}, - {changes,Results}]})]), - {ok, {Seq, <<",\n">>, FilterFun, Resp, nil}} + send_chunk(Resp, [Prepend, ?JSON_ENCODE( + changes_row(Db, Seq, Id, Results, Rev, IncludeDocs))]), + {ok, {Db, Seq, <<",\n">>, FilterFun, Resp, nil, IncludeDocs}} end. -send_changes(Req, Resp, Db, StartSeq, Prepend, ResponseType, FilterFun, End) -> +changes_row(Db, Seq, Id, Results, Rev, true) -> + {[{seq,Seq},{id,Id}, + {changes,Results}] ++ + couch_httpd_view:doc_member(Db, Id, Rev)}; +changes_row(_, Seq, Id, Results, _, false) -> + {[{seq,Seq},{id,Id}, + {changes,Results}]}. + +send_changes(Req, Resp, Db, Dir, StartSeq, Prepend, ResponseType, FilterFun, End) -> Style = list_to_existing_atom( couch_httpd:qs_value(Req, "style", "main_only")), + IncludeDocs = list_to_existing_atom( + couch_httpd:qs_value(Req, "include_docs", "false")), try - couch_db:changes_since(Db, Style, StartSeq, fun changes_enumerator/2, - {StartSeq, Prepend, FilterFun, Resp, ResponseType}) + couch_db:changes_since(Db, Style, StartSeq, fun changes_enumerator/2, + [{dir, Dir}], {Db, StartSeq, Prepend, FilterFun, Resp, ResponseType, IncludeDocs}) after End() end. @@ -457,67 +482,6 @@ db_req(#httpd{method='POST',path_parts=[_,<<"_all_docs">>]}=Req, Db) -> db_req(#httpd{path_parts=[_,<<"_all_docs">>]}=Req, _Db) -> send_method_not_allowed(Req, "GET,HEAD,POST"); -db_req(#httpd{method='GET',path_parts=[_,<<"_all_docs_by_seq">>]}=Req, Db) -> - #view_query_args{ - start_key = StartKey, - end_key = EndKey, - limit = Limit, - skip = SkipCount, - direction = Dir - } = QueryArgs = couch_httpd_view:parse_view_params(Req, nil, map), - - {ok, Info} = couch_db:get_db_info(Db), - CurrentEtag = couch_httpd:make_etag(Info), - couch_httpd:etag_respond(Req, CurrentEtag, fun() -> - TotalRowCount = proplists:get_value(doc_count, Info), - FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, - TotalRowCount, #view_fold_helper_funs{ - reduce_count = fun couch_db:enum_docs_since_reduce_to_count/1 - }), - StartKey2 = case StartKey of - nil -> 0; - <<>> -> 100000000000; - {} -> 100000000000; - StartKey when is_integer(StartKey) -> StartKey - end, - {ok, LastOffset, FoldResult} = couch_db:enum_docs_since(Db, StartKey2, - fun(DocInfo, Offset, Acc) -> - #doc_info{ - id=Id, - high_seq=Seq, - revs=[#rev_info{rev=Rev,deleted=Deleted} | RestInfo] - } = DocInfo, - ConflictRevs = couch_doc:rev_to_strs( - [Rev1 || #rev_info{deleted=false, rev=Rev1} <- RestInfo]), - DelConflictRevs = couch_doc:rev_to_strs( - [Rev1 || #rev_info{deleted=true, rev=Rev1} <- RestInfo]), - Json = { - [{<<"rev">>, couch_doc:rev_to_str(Rev)}] ++ - case ConflictRevs of - [] -> []; - _ -> [{<<"conflicts">>, ConflictRevs}] - end ++ - case DelConflictRevs of - [] -> []; - _ -> [{<<"deleted_conflicts">>, DelConflictRevs}] - end ++ - case Deleted of - true -> [{<<"deleted">>, true}]; - false -> [] - end - }, - if (Seq > EndKey) -> - {stop, Acc}; - true -> - FoldlFun({{Seq, Id}, Json}, Offset, Acc) - end - end, {Limit, SkipCount, undefined, []}, [{dir, Dir}]), - couch_httpd_view:finish_view_fold(Req, TotalRowCount, LastOffset, FoldResult) - end); - -db_req(#httpd{path_parts=[_,<<"_all_docs_by_seq">>]}=Req, _Db) -> - send_method_not_allowed(Req, "GET,HEAD"); - db_req(#httpd{method='POST',path_parts=[_,<<"_missing_revs">>]}=Req, Db) -> {JsonDocIdRevs} = couch_httpd:json_body_obj(Req), JsonDocIdRevs2 = [{Id, [couch_doc:parse_rev(RevStr) || RevStr <- RevStrs]} || {Id, RevStrs} <- JsonDocIdRevs], diff --git a/src/couchdb/couch_httpd_view.erl b/src/couchdb/couch_httpd_view.erl index 35484823..5d7b396a 100644 --- a/src/couchdb/couch_httpd_view.erl +++ b/src/couchdb/couch_httpd_view.erl @@ -18,7 +18,7 @@ -export([get_stale_type/1, get_reduce_type/1, parse_view_params/3]). -export([make_view_fold_fun/6, finish_view_fold/4, view_row_obj/3]). -export([view_group_etag/2, view_group_etag/3, make_reduce_fold_funs/5]). --export([design_doc_view/5, parse_bool_param/1]). +-export([design_doc_view/5, parse_bool_param/1, doc_member/3]). -import(couch_httpd, [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,send_chunk/2, @@ -594,18 +594,18 @@ view_row_obj(_Db, {{Key, DocId}, Value}, _IncludeDocs) -> {[{id, DocId}, {key, Key}, {value, Value}]}. view_row_with_doc(Db, {{Key, DocId}, Value}, Rev) -> + {[{id, DocId}, {key, Key}, {value, Value}] ++ doc_member(Db, DocId, Rev)}. + +doc_member(Db, DocId, Rev) -> ?LOG_DEBUG("Include Doc: ~p ~p", [DocId, Rev]), case (catch couch_httpd_db:couch_doc_open(Db, DocId, Rev, [])) of - {{not_found, missing}, _RevId} -> - {[{id, DocId}, {key, Key}, {value, Value}, {error, missing}]}; - {not_found, missing} -> - {[{id, DocId}, {key, Key}, {value, Value}, {error, missing}]}; - {not_found, deleted} -> - {[{id, DocId}, {key, Key}, {value, Value}]}; - Doc -> - JsonDoc = couch_doc:to_json_obj(Doc, []), - {[{id, DocId}, {key, Key}, {value, Value}, {doc, JsonDoc}]} + #doc{} = Doc -> + JsonDoc = couch_doc:to_json_obj(Doc, []), + [{doc, JsonDoc}]; + _Else -> + [{error, missing}] end. + finish_view_fold(Req, TotalRows, Offset, FoldResult) -> case FoldResult of |