summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--share/www/script/couch.js6
-rw-r--r--share/www/script/test/all_docs.js50
-rw-r--r--share/www/script/test/changes.js13
-rw-r--r--share/www/script/test/conflicts.js4
-rw-r--r--share/www/script/test/etags_views.js6
-rw-r--r--share/www/script/test/replication.js4
-rw-r--r--src/couchdb/couch_db.erl8
-rw-r--r--src/couchdb/couch_httpd_db.erl132
-rw-r--r--src/couchdb/couch_httpd_view.erl20
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