summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Lehnardt <jan@apache.org>2009-08-12 18:48:25 +0000
committerJan Lehnardt <jan@apache.org>2009-08-12 18:48:25 +0000
commitd6cb0bc17d834675a69620940036490b909a4b0d (patch)
treeacfcc1dc0c9acb7e27085f10fbecace4e2b5eace
parenta975795f527de1f535a4536e602aa9040d975e80 (diff)
add native /db/_conflicts view, patch by Adam Kocolosk, closes COUCHDB-462
git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@803663 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r--share/www/script/couch.js12
-rw-r--r--share/www/script/couch_tests.js1
-rw-r--r--share/www/script/test/view_builtin.js74
-rw-r--r--src/couchdb/couch_db.hrl3
-rw-r--r--src/couchdb/couch_httpd_db.erl211
-rw-r--r--src/couchdb/couch_httpd_view.erl6
6 files changed, 242 insertions, 65 deletions
diff --git a/share/www/script/couch.js b/share/www/script/couch.js
index f354f52a..af3bb8fb 100644
--- a/share/www/script/couch.js
+++ b/share/www/script/couch.js
@@ -191,10 +191,18 @@ function CouchDB(name, httpHeaders) {
}
this.allDocs = function(options,keys) {
+ return this.builtinView("_all_docs", options, keys)
+ }
+
+ this.conflicts = function(options,keys) {
+ return this.builtinView("_conflicts", options, keys)
+ }
+
+ this.builtinView = function(name, options, keys) {
if(!keys) {
- this.last_req = this.request("GET", this.uri + "_all_docs" + encodeOptions(options));
+ this.last_req = this.request("GET", this.uri + name + encodeOptions(options));
} else {
- this.last_req = this.request("POST", this.uri + "_all_docs" + encodeOptions(options), {
+ this.last_req = this.request("POST", this.uri + name + encodeOptions(options), {
headers: {"Content-Type": "application/json"},
body: JSON.stringify({keys:keys})
});
diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js
index 86c65bb7..3d415952 100644
--- a/share/www/script/couch_tests.js
+++ b/share/www/script/couch_tests.js
@@ -51,6 +51,7 @@ loadTest("design_paths.js");
loadTest("content_negotiation.js");
loadTest("design_docs.js");
loadTest("invalid_docids.js");
+loadTest("view_builtin.js");
loadTest("view_collation.js");
loadTest("view_conflicts.js");
loadTest("view_errors.js");
diff --git a/share/www/script/test/view_builtin.js b/share/www/script/test/view_builtin.js
new file mode 100644
index 00000000..c0f7b2e5
--- /dev/null
+++ b/share/www/script/test/view_builtin.js
@@ -0,0 +1,74 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.view_builtin = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ // test _conflicts view
+
+ // no rows
+ var result = db.conflicts();
+ TEquals(0, result.rows.length, "should return 0 conflicts");
+
+ // one doc with a conflict
+ var doc_a = db.save({_id:"a", a:1});
+
+ // create conflict
+ var bulk_result = db.bulkSave([{_id:"a",a:2}], {all_or_nothing:true});
+ var result = db.conflicts();
+ TEquals(1, result.rows.length, "should return 1 conflicts");
+ TEquals("a", result.rows[0].id, "should return row id 'a'");
+ TEquals("a", result.rows[0].key, "should return row key 'a'");
+ TEquals(bulk_result[0].rev, result.rows[0].rev,
+ "should return row key 'a'");
+
+ // multiple docs with conflicts
+ var doc_b = db.save({_id:"b", a:3});
+ var bulk_result = db.bulkSave([{_id:"b",a:4}], {all_or_nothing:true});
+ var result = db.conflicts();
+ TEquals(2, result.rows.length, "should return 2 conflicts");
+
+ // key, startkey, endkey, skip & count
+ var result = db.conflicts({key:"b"});
+ TEquals(1, result.rows.length, "should return 1 conflicts");
+
+ var result = db.conflicts({startkey:"b"});
+ TEquals(1, result.rows.length, "should return 1 conflicts");
+
+ var result = db.conflicts({startkey:"a", endkey:"b"});
+ TEquals(2, result.rows.length, "should return 2 conflicts");
+
+ var result = db.conflicts({startkey:"c"});
+ TEquals(0, result.rows.length, "should return 0 conflicts");
+
+ var result = db.conflicts({limit:1});
+ TEquals(1, result.rows.length, "should return 1 conflicts");
+
+ var result = db.conflicts({skip:1});
+ TEquals(1, result.rows.length, "should return 1 conflicts");
+ TEquals("b", result.rows[0].key, "should return row key 'b'");
+
+ // POST is not allowed yet
+ try {
+ var result = db.conflicts({}, ["a"]);
+ } catch (e) {
+ TEquals("method_not_allowed", e.error, "should not allow POST requests");
+ }
+
+ // multi key get
+ // var result = db.conflicts({}, ["a"]);
+ // TEquals(1, result.rows.length, "should return 1 conflicts");
+ // TEquals("a", result.rows[0].key, "should return row key 'a'");
+};
diff --git a/src/couchdb/couch_db.hrl b/src/couchdb/couch_db.hrl
index a487300f..46725ed1 100644
--- a/src/couchdb/couch_db.hrl
+++ b/src/couchdb/couch_db.hrl
@@ -175,7 +175,8 @@
stale = false,
multi_get = false,
callback = nil,
- list = nil
+ list = nil,
+ deleted = false
}).
-record(view_fold_helper_funs, {
diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl
index 159bcbe8..c93c736d 100644
--- a/src/couchdb/couch_httpd_db.erl
+++ b/src/couchdb/couch_httpd_db.erl
@@ -383,76 +383,23 @@ db_req(#httpd{method='GET',path_parts=[_,<<"_all_docs">>]}=Req, Db) ->
all_docs_view(Req, Db, nil);
db_req(#httpd{method='POST',path_parts=[_,<<"_all_docs">>]}=Req, Db) ->
- {Fields} = couch_httpd:json_body_obj(Req),
- case proplists:get_value(<<"keys">>, Fields, nil) of
- nil ->
- ?LOG_DEBUG("POST to _all_docs with no keys member.", []),
- all_docs_view(Req, Db, nil);
- Keys when is_list(Keys) ->
- all_docs_view(Req, Db, Keys);
- _ ->
- throw({bad_request, "`keys` member must be a array."})
- end;
+ post_keys_to_view(Req, Db, fun all_docs_view/3);
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,
- 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, FoldResult} = couch_db:enum_docs_since(Db, StartKey2, Dir,
- 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
- },
- FoldlFun({{Seq, Id}, Json}, Offset, Acc)
- end, {Limit, SkipCount, undefined, [], nil}),
- couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult})
- end);
+ all_docs_by_seq_view(Req, Db);
db_req(#httpd{path_parts=[_,<<"_all_docs_by_seq">>]}=Req, _Db) ->
send_method_not_allowed(Req, "GET,HEAD");
+db_req(#httpd{method='GET',path_parts=[_,<<"_conflicts">>]}=Req, Db) ->
+ conflicts_view(Req, Db, nil);
+
+db_req(#httpd{path_parts=[_,<<"_conflicts">>]}=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],
@@ -511,6 +458,18 @@ db_req(#httpd{path_parts=[_, DocId]}=Req, Db) ->
db_req(#httpd{path_parts=[_, DocId | FileNameParts]}=Req, Db) ->
db_attachment_req(Req, Db, DocId, FileNameParts).
+post_keys_to_view(Req, Db, ViewFun) ->
+ {Fields} = couch_httpd:json_body_obj(Req),
+ case proplists:get_value(<<"keys">>, Fields, nil) of
+ nil ->
+ ?LOG_DEBUG("POST to view with no keys member.", []),
+ ViewFun(Req, Db, nil);
+ Keys when is_list(Keys) ->
+ ViewFun(Req, Db, Keys);
+ _ ->
+ throw({bad_request, "`keys` member must be a array."})
+ end.
+
all_docs_view(Req, Db, Keys) ->
#view_query_args{
start_key = StartKey,
@@ -597,6 +556,136 @@ all_docs_view(Req, Db, Keys) ->
end
end).
+all_docs_by_seq_view(Req, Db) ->
+ #view_query_args{
+ start_key = StartKey,
+ 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, FoldResult} = couch_db:enum_docs_since(Db, StartKey2, Dir,
+ 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
+ },
+ FoldlFun({{Seq, Id}, Json}, Offset, Acc)
+ end, {Limit, SkipCount, undefined, [], nil}),
+ couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult})
+ end).
+
+conflicts_view(Req, Db, nil) ->
+ #view_query_args{
+ start_key = StartKey,
+ limit = Limit,
+ skip = SkipCount,
+ direction = Dir,
+ deleted = ShowDeletedConflicts
+ } = QueryArgs = couch_httpd_view:parse_view_params(Req, nil, map),
+
+ StartResp = fun(Req2, Etag, _TotalViewCount, _Offset, _Acc) ->
+ {ok, Resp} = couch_httpd:start_json_response(Req2, 200, [{"Etag",Etag}]),
+ {ok, Resp, "{\"rows\":[\r\n"}
+ end,
+
+ SendRow = fun(Resp, _Db, {{Id,Rev}, Value}, _IncludeDocs, RowFront) ->
+ {IsDeleted, Conflicts, DelConflicts} = Value,
+ JsonProps = lists:flatten([{key, Id},{id, Id}, {rev, Rev},
+ case IsDeleted of true -> {deleted, true}; _ -> [] end,
+ case Conflicts of [] -> []; _ -> {conflicts, Conflicts} end,
+ case DelConflicts of
+ [] -> [];
+ _ -> {deleted_conflicts, DelConflicts}
+ end
+ ]),
+ send_chunk(Resp, RowFront ++ ?JSON_ENCODE({JsonProps})),
+ {ok, ",\r\n"}
+ end,
+
+ CurrentEtag = couch_httpd:make_etag(couch_db:get_update_seq(Db)),
+ couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
+ HelperFuns = #view_fold_helper_funs{
+ start_response = StartResp,
+ send_row = SendRow,
+ reduce_count = fun couch_db:enum_docs_reduce_to_count/1
+ },
+ FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs,
+ CurrentEtag, Db, 0, HelperFuns),
+
+ EnumFun = fun(FullDocInfo, Offset, Acc) ->
+ IsDeleted = FullDocInfo#full_doc_info.deleted,
+ #doc_info{
+ id = Id,
+ revs = [RevInfo | ConflictInfo]
+ } = couch_doc:to_doc_info(FullDocInfo),
+ RevStr = couch_doc:rev_to_str(RevInfo#rev_info.rev),
+ Conflicts = couch_doc:rev_to_strs(
+ [Rev || #rev_info{deleted=false, rev=Rev} <- ConflictInfo]),
+ DelConflicts = couch_doc:rev_to_strs(
+ [Rev || #rev_info{deleted=true, rev=Rev} <- ConflictInfo]),
+ case {ShowDeletedConflicts, Conflicts, DelConflicts} of
+ {_, [], []} ->
+ {ok, Acc};
+ {false, [], _} ->
+ {ok, Acc};
+ {true, _, _} ->
+ Value = {IsDeleted, Conflicts, DelConflicts},
+ FoldlFun({{Id,RevStr}, Value}, Offset, Acc);
+ {false, _, _} ->
+ Value = {IsDeleted, Conflicts, []},
+ FoldlFun({{Id,RevStr}, Value}, Offset, Acc)
+ end
+ end,
+
+ Acc0 = {Limit, SkipCount, undefined, [], nil},
+ case couch_db:enum_docs(Db, StartKey, Dir, EnumFun, Acc0) of
+ {ok, {_, _, undefined, _, _}} ->
+ % nothing found in the view, send empty view
+ send_json(Req, 200, {[{rows, []}]});
+ {ok, {_, _, Resp, _, _}} ->
+ % end the view
+ send_chunk(Resp, "\r\n]}"),
+ end_json_response(Resp);
+ Error ->
+ throw(Error)
+ end
+ end).
+
db_doc_req(#httpd{method='DELETE'}=Req, Db, DocId) ->
% check for the existence of the doc to handle the 404 case.
couch_doc_open(Db, DocId, nil, []),
diff --git a/src/couchdb/couch_httpd_view.erl b/src/couchdb/couch_httpd_view.erl
index 8264186b..da9d478d 100644
--- a/src/couchdb/couch_httpd_view.erl
+++ b/src/couchdb/couch_httpd_view.erl
@@ -314,6 +314,8 @@ parse_view_param("include_docs", Value) ->
[{include_docs, parse_bool_param(Value)}];
parse_view_param("list", Value) ->
[{list, ?l2b(Value)}];
+parse_view_param("deleted", Value) ->
+ [{deleted, parse_bool_param(Value)}];
parse_view_param("callback", _) ->
[]; % Verified in the JSON response functions
parse_view_param(Key, Value) ->
@@ -398,7 +400,9 @@ validate_view_query(include_docs, true, Args) ->
validate_view_query(include_docs, _Value, Args) ->
Args;
validate_view_query(extra, _Value, Args) ->
- Args.
+ Args;
+validate_view_query(deleted, Value, Args) ->
+ Args#view_query_args{deleted = Value}.
make_view_fold_fun(Req, QueryArgs, Etag, Db, TotalViewCount, HelperFuns) ->
#view_query_args{