diff options
| author | Jan Lehnardt <jan@apache.org> | 2009-08-12 18:48:25 +0000 | 
|---|---|---|
| committer | Jan Lehnardt <jan@apache.org> | 2009-08-12 18:48:25 +0000 | 
| commit | d6cb0bc17d834675a69620940036490b909a4b0d (patch) | |
| tree | acfcc1dc0c9acb7e27085f10fbecace4e2b5eace | |
| parent | a975795f527de1f535a4536e602aa9040d975e80 (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.js | 12 | ||||
| -rw-r--r-- | share/www/script/couch_tests.js | 1 | ||||
| -rw-r--r-- | share/www/script/test/view_builtin.js | 74 | ||||
| -rw-r--r-- | src/couchdb/couch_db.hrl | 3 | ||||
| -rw-r--r-- | src/couchdb/couch_httpd_db.erl | 211 | ||||
| -rw-r--r-- | src/couchdb/couch_httpd_view.erl | 6 | 
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{ | 
