couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jch...@apache.org
Subject svn commit: r744747 - in /couchdb/trunk: etc/couchdb/local_dev.ini share/www/script/couch_tests.js src/couchdb/couch_db.hrl src/couchdb/couch_httpd_db.erl src/couchdb/couch_httpd_show.erl src/couchdb/couch_httpd_view.erl src/couchdb/couch_view.erl
Date Sun, 15 Feb 2009 20:36:53 GMT
Author: jchris
Date: Sun Feb 15 20:36:53 2009
New Revision: 744747

URL: http://svn.apache.org/viewvc?rev=744747&view=rev
Log:
View etags are now provided. See note in the source about how they could be more efficient.
Changes arity on make_view_fold_fun etc. Closes COUCHDB-4

Modified:
    couchdb/trunk/etc/couchdb/local_dev.ini
    couchdb/trunk/share/www/script/couch_tests.js
    couchdb/trunk/src/couchdb/couch_db.hrl
    couchdb/trunk/src/couchdb/couch_httpd_db.erl
    couchdb/trunk/src/couchdb/couch_httpd_show.erl
    couchdb/trunk/src/couchdb/couch_httpd_view.erl
    couchdb/trunk/src/couchdb/couch_view.erl

Modified: couchdb/trunk/etc/couchdb/local_dev.ini
URL: http://svn.apache.org/viewvc/couchdb/trunk/etc/couchdb/local_dev.ini?rev=744747&r1=744746&r2=744747&view=diff
==============================================================================
--- couchdb/trunk/etc/couchdb/local_dev.ini (original)
+++ couchdb/trunk/etc/couchdb/local_dev.ini Sun Feb 15 20:36:53 2009
@@ -12,7 +12,7 @@
 ;bind_address = 127.0.0.1
 
 [log]
-level = info
+; level = debug
 
 [update_notification]
 ;unique notifier name=/full/path/to/exe -with "cmd line arg"

Modified: couchdb/trunk/share/www/script/couch_tests.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/couch_tests.js?rev=744747&r1=744746&r2=744747&view=diff
==============================================================================
--- couchdb/trunk/share/www/script/couch_tests.js [utf-8] (original)
+++ couchdb/trunk/share/www/script/couch_tests.js [utf-8] Sun Feb 15 20:36:53 2009
@@ -2425,17 +2425,87 @@
     });
     T(xhr.status == 200);
   },
+  etags_views: function(debug) {
+    var db = new CouchDB("test_suite_db");
+    db.deleteDb();
+    db.createDb();
+    if (debug) debugger;
+    
+    var designDoc = {
+      _id:"_design/etags",
+      language: "javascript",
+      views : {
+        basicView : {
+          map : stringFun(function(doc) {
+            emit(doc.integer, doc.string);
+          })
+        },
+        withReduce : {
+          map : stringFun(function(doc) {
+            emit(doc.integer, doc.string);
+          }),
+          reduce : stringFun(function(keys, values, rereduce) {
+            if (rereduce) {
+              return sum(values);
+            } else {
+              return values.length;
+            }
+          })
+        }
+      }
+    }
+    T(db.save(designDoc).ok);
+    var xhr;
+    var docs = makeDocs(0, 10);
+    var saveResult = db.bulkSave(docs);
+    T(saveResult.ok);
+    
+    // verify get w/Etag on map view
+    xhr = CouchDB.request("GET", "/test_suite_db/_view/etags/basicView");
+    T(xhr.status == 200);
+    var etag = xhr.getResponseHeader("etag");
+    xhr = CouchDB.request("GET", "/test_suite_db/_view/etags/basicView", {
+      headers: {"if-none-match": etag}
+    });
+    T(xhr.status == 304);
+    // TODO GET with keys (when that is available)
+    
+    // reduce view
+    xhr = CouchDB.request("GET", "/test_suite_db/_view/etags/withReduce");
+    T(xhr.status == 200);
+    var etag = xhr.getResponseHeader("etag");
+    xhr = CouchDB.request("GET", "/test_suite_db/_view/etags/withReduce", {
+      headers: {"if-none-match": etag}
+    });
+    T(xhr.status == 304);
+    
+    // all docs
+    xhr = CouchDB.request("GET", "/test_suite_db/_all_docs");
+    T(xhr.status == 200);
+    var etag = xhr.getResponseHeader("etag");
+    xhr = CouchDB.request("GET", "/test_suite_db/_all_docs", {
+      headers: {"if-none-match": etag}
+    });
+    T(xhr.status == 304);
+
+    // by seq
+    xhr = CouchDB.request("GET", "/test_suite_db/_all_docs_by_seq");
+    T(xhr.status == 200);
+    var etag = xhr.getResponseHeader("etag");
+    xhr = CouchDB.request("GET", "/test_suite_db/_all_docs_by_seq", {
+      headers: {"if-none-match": etag}
+    });
+    T(xhr.status == 304);    
+
+    // list etag
+    // in the list test for now
+  },
 
    show_documents: function(debug) {
      var db = new CouchDB("test_suite_db");
      db.deleteDb();
      db.createDb();
      if (debug) debugger;
-     
-     function stringFun(fun) {
-       var string = fun.toSource ? fun.toSource() : "(" + fun.toString() + ")";
-       return string;
-     }
          
      var designDoc = {
        _id:"_design/template",
@@ -2706,11 +2776,6 @@
     db.deleteDb();
     db.createDb();
     if (debug) debugger;
-    
-    function stringFun(fun) {
-      var string = fun.toSource ? fun.toSource() : "(" + fun.toString() + ")";
-      return string;
-    }
         
     var designDoc = {
       _id:"_design/lists",
@@ -2850,6 +2915,13 @@
     var lines = xhr.responseText.split('\n');
     T(/LineNo: 5/.test(lines[6]));
 
+    // test that etags are available
+    var etag = xhr.getResponseHeader("etag");
+    xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/basicView", {
+      headers: {"if-none-match": etag}
+    });
+    T(xhr.status == 304);
+
     // get with query params
     var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/basicView?startkey=3");
     T(xhr.status == 200);
@@ -3313,6 +3385,11 @@
   }
 }
 
+function stringFun(fun) {
+  var string = fun.toSource ? fun.toSource() : "(" + fun.toString() + ")";
+  return string;
+}
+
 function restartServer() {
   CouchDB.request("POST", "/_restart");
 }

Modified: couchdb/trunk/src/couchdb/couch_db.hrl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_db.hrl?rev=744747&r1=744746&r2=744747&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_db.hrl (original)
+++ couchdb/trunk/src/couchdb/couch_db.hrl Sun Feb 15 20:36:53 2009
@@ -169,7 +169,7 @@
 }).
 
 -record(group,
-    {type=view, % can also be slow_view
+    {type=view, % can also be temp_view
     sig=nil,
     db=nil,
     fd=nil,

Modified: couchdb/trunk/src/couchdb/couch_httpd_db.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_db.erl?rev=744747&r1=744746&r2=744747&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd_db.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd_db.erl Sun Feb 15 20:36:53 2009
@@ -191,46 +191,48 @@
     } = QueryArgs = couch_httpd_view:parse_view_query(Req),
 
     {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, 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,
-                rev=Rev,
-                update_seq=UpdateSeq,
-                deleted=Deleted,
-                conflict_revs=ConflictRevs,
-                deleted_conflict_revs=DelConflictRevs
-            } = DocInfo,
-            Json = {
-                [{<<"rev">>, Rev}] ++
-                case ConflictRevs of
-                    []  ->  [];
-                    _   ->  [{<<"conflicts">>, ConflictRevs}]
-                end ++
-                case DelConflictRevs of
-                    []  ->  [];
-                    _   ->  [{<<"deleted_conflicts">>, DelConflictRevs}]
-                end ++
-                case Deleted of
-                    true -> [{<<"deleted">>, true}];
-                    false -> []
-                end
-            },
-            FoldlFun({{UpdateSeq, Id}, Json}, Offset, Acc)
-        end, {Limit, SkipCount, undefined, []}),
-    couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult});
+    CurrentEtag = couch_httpd:make_etag(proplists:get_value(update_seq, 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,
+                    rev=Rev,
+                    update_seq=UpdateSeq,
+                    deleted=Deleted,
+                    conflict_revs=ConflictRevs,
+                    deleted_conflict_revs=DelConflictRevs
+                } = DocInfo,
+                Json = {
+                    [{<<"rev">>, Rev}] ++
+                    case ConflictRevs of
+                        []  ->  [];
+                        _   ->  [{<<"conflicts">>, ConflictRevs}]
+                    end ++
+                    case DelConflictRevs of
+                        []  ->  [];
+                        _   ->  [{<<"deleted_conflicts">>, DelConflictRevs}]
+                    end ++
+                    case Deleted of
+                        true -> [{<<"deleted">>, true}];
+                        false -> []
+                    end
+                },
+                FoldlFun({{UpdateSeq, Id}, Json}, Offset, Acc)
+            end, {Limit, SkipCount, undefined, []}),
+        couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult})
+    end);
 
 db_req(#httpd{path_parts=[_,<<"_all_docs_by_seq">>]}=Req, _Db) ->
     send_method_not_allowed(Req, "GET,HEAD");
@@ -289,77 +291,81 @@
         direction = Dir
     } = 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;
-    true -> StartDocId
-    end,
-    FoldAccInit = {Limit, SkipCount, undefined, []},
+    CurrentEtag = couch_httpd:make_etag(proplists:get_value(update_seq, Info)),
+    couch_httpd:etag_respond(Req, CurrentEtag, fun() -> 
     
-    PassedEndFun = 
-    case Dir of
-    fwd ->
-        fun(ViewKey, _ViewId) ->
-            couch_db_updater:less_docid(EndKey, ViewKey)
-        end;
-    rev->
-        fun(ViewKey, _ViewId) ->
-            couch_db_updater:less_docid(ViewKey, EndKey)
-        end
-    end,
-    
-    case Keys of
-    nil ->
-        FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db,
-            TotalRowCount, #view_fold_helper_funs{
-                reduce_count = fun couch_db:enum_docs_reduce_to_count/1,
-                passed_end = PassedEndFun
-            }),
-        AdapterFun = fun(#full_doc_info{id=Id}=FullDocInfo, Offset, Acc) ->
-            case couch_doc:to_doc_info(FullDocInfo) of
-            #doc_info{deleted=false, rev=Rev} ->
-                FoldlFun({{Id, Id}, {[{rev, Rev}]}}, Offset, Acc);
-            #doc_info{deleted=true} ->
-                {ok, Acc}
-            end
+        TotalRowCount = proplists:get_value(doc_count, Info),
+        StartId = if is_binary(StartKey) -> StartKey;
+        true -> StartDocId
         end,
-        {ok, FoldResult} = couch_db:enum_docs(Db, StartId, Dir, 
-            AdapterFun, FoldAccInit),
-        couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult});
-    _ ->
-        FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db,
-            TotalRowCount, #view_fold_helper_funs{
-                reduce_count = fun(Offset) -> Offset end
-            }),
-        KeyFoldFun = case Dir of
+        FoldAccInit = {Limit, SkipCount, undefined, []},
+    
+        PassedEndFun = 
+        case Dir of
         fwd ->
-            fun lists:foldl/3;
-        rev ->
-            fun lists:foldr/3
+            fun(ViewKey, _ViewId) ->
+                couch_db_updater:less_docid(EndKey, ViewKey)
+            end;
+        rev->
+            fun(ViewKey, _ViewId) ->
+                couch_db_updater:less_docid(ViewKey, EndKey)
+            end
         end,
-        {ok, FoldResult} = KeyFoldFun(
-            fun(Key, {ok, FoldAcc}) ->
-                DocInfo = (catch couch_db:get_doc_info(Db, Key)),
-                Doc = case DocInfo of
-                {ok, #doc_info{id=Id, rev=Rev, deleted=false}} = DocInfo ->
-                    {{Id, Id}, {[{rev, Rev}]}};
-                {ok, #doc_info{id=Id, rev=Rev, deleted=true}} = DocInfo ->
-                    {{Id, Id}, {[{rev, Rev}, {deleted, true}]}};
-                not_found ->
-                    {{Key, error}, not_found};
-                _ ->
-                    ?LOG_ERROR("Invalid DocInfo: ~p", [DocInfo]),
-                    throw({error, invalid_doc_info})
-                end,
-                Acc = (catch FoldlFun(Doc, 0, FoldAcc)),
-                case Acc of
-                {stop, Acc2} ->
-                    {ok, Acc2};
-                _ ->
-                    Acc
+    
+        case Keys of
+        nil ->
+            FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db,
+                TotalRowCount, #view_fold_helper_funs{
+                    reduce_count = fun couch_db:enum_docs_reduce_to_count/1,
+                    passed_end = PassedEndFun
+                }),
+            AdapterFun = fun(#full_doc_info{id=Id}=FullDocInfo, Offset, Acc) ->
+                case couch_doc:to_doc_info(FullDocInfo) of
+                #doc_info{deleted=false, rev=Rev} ->
+                    FoldlFun({{Id, Id}, {[{rev, Rev}]}}, Offset, Acc);
+                #doc_info{deleted=true} ->
+                    {ok, Acc}
                 end
-            end, {ok, FoldAccInit}, Keys),
-        couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult})        
-    end.
+            end,
+            {ok, FoldResult} = couch_db:enum_docs(Db, StartId, Dir, 
+                AdapterFun, FoldAccInit),
+            couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult});
+        _ ->
+            FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db,
+                TotalRowCount, #view_fold_helper_funs{
+                    reduce_count = fun(Offset) -> Offset end
+                }),
+            KeyFoldFun = case Dir of
+            fwd ->
+                fun lists:foldl/3;
+            rev ->
+                fun lists:foldr/3
+            end,
+            {ok, FoldResult} = KeyFoldFun(
+                fun(Key, {ok, FoldAcc}) ->
+                    DocInfo = (catch couch_db:get_doc_info(Db, Key)),
+                    Doc = case DocInfo of
+                    {ok, #doc_info{id=Id, rev=Rev, deleted=false}} = DocInfo ->
+                        {{Id, Id}, {[{rev, Rev}]}};
+                    {ok, #doc_info{id=Id, rev=Rev, deleted=true}} = DocInfo ->
+                        {{Id, Id}, {[{rev, Rev}, {deleted, true}]}};
+                    not_found ->
+                        {{Key, error}, not_found};
+                    _ ->
+                        ?LOG_ERROR("Invalid DocInfo: ~p", [DocInfo]),
+                        throw({error, invalid_doc_info})
+                    end,
+                    Acc = (catch FoldlFun(Doc, 0, FoldAcc)),
+                    case Acc of
+                    {stop, Acc2} ->
+                        {ok, Acc2};
+                    _ ->
+                        Acc
+                    end
+                end, {ok, FoldAccInit}, Keys),
+            couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult})     
  
+        end
+    end).
 
 
 

Modified: couchdb/trunk/src/couchdb/couch_httpd_show.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_show.erl?rev=744747&r1=744746&r2=744747&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd_show.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd_show.erl Sun Feb 15 20:36:53 2009
@@ -77,15 +77,15 @@
         reduce = Reduce
     } = QueryArgs = couch_httpd_view:parse_view_query(Req, nil, nil, true),
     case couch_view:get_map_view(Db, DesignId, ViewName, Stale) of
-    {ok, View} ->    
-        output_map_list(Req, Lang, ListSrc, View, Db, QueryArgs);
+    {ok, View, Group} ->    
+        output_map_list(Req, Lang, ListSrc, View, Group, Db, QueryArgs);
     {not_found, _Reason} ->
         case couch_view:get_reduce_view(Db, DesignId, ViewName, Stale) of
-        {ok, ReduceView} ->
+        {ok, ReduceView, Group} ->
             case Reduce of
             false ->
                 MapView = couch_view:extract_map_view(ReduceView),
-                output_map_list(Req, Lang, ListSrc, MapView, Db, QueryArgs);
+                output_map_list(Req, Lang, ListSrc, MapView, Group, Db, QueryArgs);
             _ ->
                 throw({not_implemented, reduce_view_lists})
             end;
@@ -94,7 +94,7 @@
         end
     end.
     
-output_map_list(Req, Lang, ListSrc, View, Db, QueryArgs) ->
+output_map_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, QueryArgs) ->
     #view_query_args{
         limit = Limit,
         direction = Dir,
@@ -104,51 +104,59 @@
     } = QueryArgs,
     {ok, RowCount} = couch_view:get_row_count(View),
     Start = {StartKey, StartDocId},
-    % get the os process here
-    % pass it into the view fold with closures
-    {ok, QueryServer} = couch_query_servers:start_view_list(Lang, ListSrc),
-
-    StartListRespFun = fun(Req2, Code, TotalViewCount, Offset) ->
-        JsonResp = couch_query_servers:render_list_head(QueryServer, 
-            Req2, Db, TotalViewCount, Offset),
-        #extern_resp_args{
-            code = Code,
-            data = BeginBody,
-            ctype = CType,
-            headers = Headers
-        } = couch_httpd_external:parse_external_response(JsonResp),
-        JsonHeaders = couch_httpd_external:default_or_content_type(CType, Headers),
-        {ok, Resp} = start_chunked_response(Req, Code, JsonHeaders),
-        {ok, Resp, binary_to_list(BeginBody)}
-    end,
+    Headers = MReq:get(headers),
+    Hlist = mochiweb_headers:to_list(Headers),
+    Accept = proplists:get_value('Accept', Hlist),
+    CurrentEtag = couch_httpd_view:view_group_etag(Group, {Lang, ListSrc, Accept}),
+    couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
+        % get the os process here
+        % pass it into the view fold with closures
+        {ok, QueryServer} = couch_query_servers:start_view_list(Lang, ListSrc),
+
+        StartListRespFun = fun(Req2, Etag, TotalViewCount, Offset) ->
+            ExternalResp = couch_query_servers:render_list_head(QueryServer, 
+                Req2, Db, TotalViewCount, Offset),
+            JsonResp = apply_etag(ExternalResp, CurrentEtag),
+            #extern_resp_args{
+                code = Code,
+                data = BeginBody,
+                ctype = CType,
+                headers = ExtHeaders
+            } = couch_httpd_external:parse_external_response(JsonResp),
+            JsonHeaders = couch_httpd_external:default_or_content_type(CType, ExtHeaders),
+            % TODO use the Etag
+            {ok, Resp} = start_chunked_response(Req, Code, JsonHeaders),
+            {ok, Resp, binary_to_list(BeginBody)}
+        end,
     
-    SendListRowFun = fun(Resp, Db2, {{Key, DocId}, Value}, 
-        RowFront, _IncludeDocs) ->
-        JsonResp = couch_query_servers:render_list_row(QueryServer, 
-            Req, Db2, {{Key, DocId}, Value}),
-        #extern_resp_args{
-            stop = StopIter,
-            data = RowBody
-        } = couch_httpd_external:parse_external_response(JsonResp),
-        RowFront2 = case RowFront of
-        nil -> [];
-        _ -> RowFront
+        SendListRowFun = fun(Resp, Db2, {{Key, DocId}, Value}, 
+            RowFront, _IncludeDocs) ->
+            JsonResp = couch_query_servers:render_list_row(QueryServer, 
+                Req, Db2, {{Key, DocId}, Value}),
+            #extern_resp_args{
+                stop = StopIter,
+                data = RowBody
+            } = couch_httpd_external:parse_external_response(JsonResp),
+            RowFront2 = case RowFront of
+            nil -> [];
+            _ -> RowFront
+            end,
+            case StopIter of
+            true -> stop;
+            _ -> send_chunk(Resp, RowFront2 ++ binary_to_list(RowBody))
+            end
         end,
-        case StopIter of
-        true -> stop;
-        _ -> send_chunk(Resp, RowFront2 ++ binary_to_list(RowBody))
-        end
-    end,
     
-    FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db, RowCount,
-        #view_fold_helper_funs{
-            reduce_count = fun couch_view:reduce_to_count/1,
-            start_response = StartListRespFun,
-            send_row = SendListRowFun
-        }),
-    FoldAccInit = {Limit, SkipCount, undefined, []},
-    FoldResult = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
-    finish_view_list(Req, Db, QueryServer, RowCount, FoldResult, StartListRespFun).
+        FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, RowCount,
+            #view_fold_helper_funs{
+                reduce_count = fun couch_view:reduce_to_count/1,
+                start_response = StartListRespFun,
+                send_row = SendListRowFun
+            }),
+        FoldAccInit = {Limit, SkipCount, undefined, []},
+        FoldResult = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
+        finish_view_list(Req, Db, QueryServer, RowCount, FoldResult, StartListRespFun)
+    end).
 
 finish_view_list(Req, Db, QueryServer, TotalRows, 
     FoldResult, StartListRespFun) ->

Modified: couchdb/trunk/src/couchdb/couch_httpd_view.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_view.erl?rev=744747&r1=744746&r2=744747&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd_view.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd_view.erl Sun Feb 15 20:36:53 2009
@@ -15,12 +15,12 @@
 
 -export([handle_view_req/2,handle_temp_view_req/2]).
 
--export([parse_view_query/1,parse_view_query/2,parse_view_query/4,make_view_fold_fun/5,
-    finish_view_fold/3, view_row_obj/3]).
+-export([parse_view_query/1,parse_view_query/2,parse_view_query/4,make_view_fold_fun/6,
+    finish_view_fold/3, view_row_obj/3, view_group_etag/1, view_group_etag/2]).
 
 -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]).
+    [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,send_chunk/2,
+    start_json_response/2, start_json_response/3, end_json_response/1]).
 
 design_doc_view(Req, Db, Id, ViewName, Keys) ->
     #view_query_args{
@@ -29,18 +29,18 @@
     } = QueryArgs = parse_view_query(Req, Keys),
     DesignId = <<"_design/", Id/binary>>,
     case couch_view:get_map_view(Db, DesignId, ViewName, Stale) of
-    {ok, View} ->    
-        output_map_view(Req, View, Db, QueryArgs, Keys);
+    {ok, View, Group} ->    
+        output_map_view(Req, View, Group, Db, QueryArgs, Keys);
     {not_found, Reason} ->
         case couch_view:get_reduce_view(Db, DesignId, ViewName, Stale) of
-        {ok, ReduceView} ->
+        {ok, ReduceView, Group} ->
             parse_view_query(Req, Keys, true), % just for validation
             case Reduce of
             false ->
                 MapView = couch_view:extract_map_view(ReduceView),
-                output_map_view(Req, MapView, Db, QueryArgs, Keys);
+                output_map_view(Req, MapView, Group, Db, QueryArgs, Keys);
             _ ->
-                output_reduce_view(Req, ReduceView, QueryArgs, Keys)
+                output_reduce_view(Req, ReduceView, Group, QueryArgs, Keys)
             end;
         _ ->
             throw({not_found, Reason})
@@ -73,19 +73,19 @@
     Keys = proplists:get_value(<<"keys">>, Props, nil),
     case proplists:get_value(<<"reduce">>, Props, null) of
     null ->
-        {ok, View} = couch_view:get_temp_map_view(Db, Language, 
+        {ok, View, Group} = couch_view:get_temp_map_view(Db, Language, 
             DesignOptions, MapSrc),
-        output_map_view(Req, View, Db, QueryArgs, Keys);
+        output_map_view(Req, View, Group, Db, QueryArgs, Keys);
     RedSrc ->
-        {ok, View} = couch_view:get_temp_reduce_view(Db, Language, 
+        {ok, View, Group} = couch_view:get_temp_reduce_view(Db, Language, 
             DesignOptions, MapSrc, RedSrc),
-        output_reduce_view(Req, View, QueryArgs, Keys)
+        output_reduce_view(Req, View, Group, QueryArgs, Keys)
     end;
 
 handle_temp_view_req(Req, _Db) ->
     send_method_not_allowed(Req, "POST").
 
-output_map_view(Req, View, Db, QueryArgs, nil) ->
+output_map_view(Req, View, Group, Db, QueryArgs, nil) ->
     #view_query_args{
         limit = Limit,
         direction = Dir,
@@ -93,38 +93,44 @@
         start_key = StartKey,
         start_docid = StartDocId
     } = QueryArgs,
-    {ok, RowCount} = couch_view:get_row_count(View),
-    Start = {StartKey, StartDocId},
-    FoldlFun = make_view_fold_fun(Req, QueryArgs, Db, RowCount, #view_fold_helper_funs{reduce_count=fun
couch_view:reduce_to_count/1}),
-    FoldAccInit = {Limit, SkipCount, undefined, []},
-    FoldResult = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
-    finish_view_fold(Req, RowCount, FoldResult);
+    CurrentEtag = view_group_etag(Group),
+    couch_httpd:etag_respond(Req, CurrentEtag, fun() -> 
+        {ok, RowCount} = couch_view:get_row_count(View),
+        Start = {StartKey, StartDocId},
+        FoldlFun = make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, RowCount, #view_fold_helper_funs{reduce_count=fun
couch_view:reduce_to_count/1}),
+        FoldAccInit = {Limit, SkipCount, undefined, []},
+        FoldResult = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
+        finish_view_fold(Req, RowCount, FoldResult)
+    end);
     
-output_map_view(Req, View, Db, QueryArgs, Keys) ->
+output_map_view(Req, View, Group, Db, QueryArgs, Keys) ->
     #view_query_args{
         limit = Limit,
         direction = Dir,
         skip = SkipCount,
         start_docid = StartDocId
     } = QueryArgs,
-    {ok, RowCount} = couch_view:get_row_count(View),
-    FoldAccInit = {Limit, SkipCount, undefined, []},
-    FoldResult = lists:foldl(
-        fun(Key, {ok, FoldAcc}) ->
-            Start = {Key, StartDocId},
-            FoldlFun = make_view_fold_fun(Req,
-                QueryArgs#view_query_args{
-                    start_key = Key,
-                    end_key = Key
-                }, Db, RowCount, 
-                #view_fold_helper_funs{
-                    reduce_count = 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).
+    CurrentEtag = view_group_etag(Group, Keys),
+    couch_httpd:etag_respond(Req, CurrentEtag, fun() ->     
+        {ok, RowCount} = couch_view:get_row_count(View),
+        FoldAccInit = {Limit, SkipCount, undefined, []},
+        FoldResult = lists:foldl(
+            fun(Key, {ok, FoldAcc}) ->
+                Start = {Key, StartDocId},
+                FoldlFun = make_view_fold_fun(Req,
+                    QueryArgs#view_query_args{
+                        start_key = Key,
+                        end_key = Key
+                    }, CurrentEtag, Db, RowCount, 
+                    #view_fold_helper_funs{
+                        reduce_count = 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)
+    end).
 
-output_reduce_view(Req, View, QueryArgs, nil) ->
+output_reduce_view(Req, View, Group, QueryArgs, nil) ->
     #view_query_args{
         start_key = StartKey,
         end_key = EndKey,
@@ -135,15 +141,18 @@
         end_docid = EndDocId,
         group_level = GroupLevel
     } = QueryArgs,
-    {ok, Resp} = start_json_response(Req, 200),
-    {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Resp, GroupLevel),
-    send_chunk(Resp, "{\"rows\":["),
-    {ok, _} = couch_view:fold_reduce(View, Dir, {StartKey, StartDocId}, 
-        {EndKey, EndDocId}, GroupRowsFun, RespFun, {"", Skip, Limit}),
-    send_chunk(Resp, "]}"),
-    end_json_response(Resp);
+    CurrentEtag = view_group_etag(Group),
+    couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
+        {ok, Resp} = start_json_response(Req, 200, [{"Etag",CurrentEtag}]),
+        {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Resp, GroupLevel),
+        send_chunk(Resp, "{\"rows\":["),
+        {ok, _} = couch_view:fold_reduce(View, Dir, {StartKey, StartDocId}, 
+            {EndKey, EndDocId}, GroupRowsFun, RespFun, {"", Skip, Limit}),
+        send_chunk(Resp, "]}"),
+        end_json_response(Resp)
+    end);
     
-output_reduce_view(Req, View, QueryArgs, Keys) ->
+output_reduce_view(Req, View, Group, QueryArgs, Keys) ->
     #view_query_args{
         limit = Limit,
         skip = Skip,
@@ -152,19 +161,22 @@
         end_docid = EndDocId,
         group_level = GroupLevel
     } = QueryArgs,
-    {ok, Resp} = start_json_response(Req, 200),
-    {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Resp, GroupLevel),
-    send_chunk(Resp, "{\"rows\":["),
-    lists:foldl(
-        fun(Key, AccSeparator) ->
-            {ok, {NewAcc, _, _}} = couch_view:fold_reduce(View, Dir, {Key, StartDocId}, 
-                {Key, EndDocId}, GroupRowsFun, RespFun, 
-                {AccSeparator, Skip, Limit}),
-            NewAcc % Switch to comma
-        end,
-    "", Keys), % Start with no comma
-    send_chunk(Resp, "]}"),
-    end_json_response(Resp).
+    CurrentEtag = view_group_etag(Group),
+    couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
+        {ok, Resp} = start_json_response(Req, 200, [{"Etag",CurrentEtag}]),
+        {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Resp, GroupLevel),
+        send_chunk(Resp, "{\"rows\":["),
+        lists:foldl(
+            fun(Key, AccSeparator) ->
+                {ok, {NewAcc, _, _}} = couch_view:fold_reduce(View, Dir, {Key, StartDocId},

+                    {Key, EndDocId}, GroupRowsFun, RespFun, 
+                    {AccSeparator, Skip, Limit}),
+                NewAcc % Switch to comma
+            end,
+        "", Keys), % Start with no comma
+        send_chunk(Resp, "]}"),
+        end_json_response(Resp)
+    end).
     
 make_reduce_fold_funs(Resp, GroupLevel) ->
     GroupRowsFun =
@@ -381,7 +393,7 @@
         end
     end.
 
-make_view_fold_fun(Req, QueryArgs, Db,
+make_view_fold_fun(Req, QueryArgs, Etag, Db,
     TotalViewCount, HelperFuns) ->
     #view_query_args{
         end_key = EndKey,
@@ -414,7 +426,7 @@
             {ok, {AccLimit, AccSkip - 1, Resp, AccRevRows}};
         {_, _, _, undefined} ->
             Offset = ReduceCountFun(OffsetReds),
-            {ok, Resp2, BeginBody} = StartRespFun(Req, 200, 
+            {ok, Resp2, BeginBody} = StartRespFun(Req, Etag,
                 TotalViewCount, Offset),
             case SendRowFun(Resp2, Db, 
                 {{Key, DocId}, Value}, BeginBody, IncludeDocs) of
@@ -468,8 +480,8 @@
         end
     end.
 
-json_view_start_resp(Req, Code, TotalViewCount, Offset) ->
-    {ok, Resp} = couch_httpd:start_json_response(Req, Code),
+json_view_start_resp(Req, Etag, TotalViewCount, Offset) ->
+    {ok, Resp} = start_json_response(Req, 200, [{"Etag", Etag}]),
     BeginBody = io_lib:format("{\"total_rows\":~w,\"offset\":~w,\"rows\":[\r\n",
             [TotalViewCount, Offset]),
     {ok, Resp, BeginBody}.
@@ -481,7 +493,16 @@
     _ -> RowFront
     end,
     send_chunk(Resp, RowFront2 ++  ?JSON_ENCODE(JsonObj)).
+
+view_group_etag(Group) ->
+    view_group_etag(Group, nil).
     
+view_group_etag(#group{sig=Sig,current_seq=CurrentSeq}, Extra) ->
+    % This is not as granular as it could be.
+    % If there are updates to the db that do not effect the view index,
+    % they will change the Etag. For more granular Etags we'd need to keep 
+    % track of the last Db seq that caused an index change.
+    couch_httpd:make_etag({Sig, CurrentSeq, Extra}).
 
 view_row_obj(Db, {{Key, DocId}, Value}, IncludeDocs) ->
     case DocId of

Modified: couchdb/trunk/src/couchdb/couch_view.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_view.erl?rev=744747&r1=744746&r2=744747&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_view.erl (original)
+++ couchdb/trunk/src/couchdb/couch_view.erl Sun Feb 15 20:36:53 2009
@@ -61,14 +61,19 @@
     {ok, Count}.
 
 get_temp_reduce_view(Db, Type, DesignOptions, MapSrc, RedSrc) ->
-    {ok, #group{views=[View]}} = get_temp_group(Db, Type, DesignOptions, MapSrc, RedSrc),
-    {ok, {temp_reduce, View}}.
+    {ok, #group{views=[View]}=Group} = get_temp_group(Db, Type, DesignOptions, MapSrc, RedSrc),
+    {ok, {temp_reduce, View}, Group}.
 
 
 get_reduce_view(Db, GroupId, Name, Update) ->
     case get_group(Db, GroupId, Update) of
-    {ok, #group{views=Views,def_lang=Lang}} ->
-        get_reduce_view0(Name, Lang, Views);
+    {ok, #group{views=Views,def_lang=Lang}=Group} ->
+        case get_reduce_view0(Name, Lang, Views) of
+        {ok, View} ->
+            {ok, View, Group};
+        Else ->
+            Else
+        end;
     Error ->
         Error
     end.
@@ -137,13 +142,18 @@
 
 
 get_temp_map_view(Db, Type, DesignOptions, Src) ->
-    {ok, #group{views=[View]}} = get_temp_group(Db, Type, DesignOptions, Src, []),
-    {ok, View}.
+    {ok, #group{views=[View]}=Group} = get_temp_group(Db, Type, DesignOptions, Src, []),
+    {ok, View, Group}.
 
 get_map_view(Db, GroupId, Name, Stale) ->
     case get_group(Db, GroupId, Stale) of
-    {ok, #group{views=Views}} ->
-        get_map_view0(Name, Views);
+    {ok, #group{views=Views}=Group} ->
+        case get_map_view0(Name, Views) of
+        {ok, View} ->
+            {ok, View, Group};
+        Else ->
+            Else
+        end;
     Error ->
         Error
     end.



Mime
View raw message