You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by da...@apache.org on 2010/02/18 20:55:56 UTC

svn commit: r911544 - in /couchdb/trunk: share/Makefile.am src/couchdb/couch_view.erl src/couchdb/couch_view_group.erl test/etap/150-invalid-view-seq.t test/etap/Makefile.am

Author: davisp
Date: Thu Feb 18 19:55:55 2010
New Revision: 911544

URL: http://svn.apache.org/viewvc?rev=911544&view=rev
Log:
Fixes COUCHDB-640 - Reset views with invalid update seqs.

If a database is restored to a previous version, and the view files
are not updated appropriately they will contain data from the database
after the reverted-to-update_seq. This patch checks that the update
sequence recorded in a view is less than or equal to the update
sequence of the database.

Patch is by Filipe Manana. Execellent work once again.


Added:
    couchdb/trunk/test/etap/150-invalid-view-seq.t   (with props)
Modified:
    couchdb/trunk/share/Makefile.am
    couchdb/trunk/src/couchdb/couch_view.erl
    couchdb/trunk/src/couchdb/couch_view_group.erl
    couchdb/trunk/test/etap/Makefile.am

Modified: couchdb/trunk/share/Makefile.am
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/Makefile.am?rev=911544&r1=911543&r2=911544&view=diff
==============================================================================
--- couchdb/trunk/share/Makefile.am (original)
+++ couchdb/trunk/share/Makefile.am Thu Feb 18 19:55:55 2010
@@ -34,8 +34,10 @@
 
 EXTRA_DIST = $(JS_FILE_COMPONENTS) $(JS_FILE_COMPONENTS_LAST)
 
+nobase_localdata_SCRIPTS = \
+    $(JS_FILE)
+
 nobase_dist_localdata_DATA = \
-    $(JS_FILE) \
     www/config.html \
     www/couch_tests.html \
     www/custom_test.html \

Modified: couchdb/trunk/src/couchdb/couch_view.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_view.erl?rev=911544&r1=911543&r2=911544&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_view.erl (original)
+++ couchdb/trunk/src/couchdb/couch_view.erl Thu Feb 18 19:55:55 2010
@@ -284,6 +284,15 @@
         {ok, NewPid} ->
             add_to_ets(NewPid, DbName, Sig),
             {reply, {ok, NewPid}, Server};
+        {error, invalid_view_seq} ->
+            do_reset_indexes(DbName, Root),
+            case (catch couch_view_group:start_link({Root, DbName, Group})) of
+            {ok, NewPid} ->
+                add_to_ets(NewPid, DbName, Sig),
+                {reply, {ok, NewPid}, Server};
+            Error ->
+                {reply, Error, Server}
+            end;
         Error ->
             {reply, Error, Server}
         end;
@@ -292,6 +301,10 @@
     end.
 
 handle_cast({reset_indexes, DbName}, #server{root_dir=Root}=Server) ->
+    do_reset_indexes(DbName, Root),
+    {noreply, Server}.
+
+do_reset_indexes(DbName, Root) ->
     % shutdown all the updaters and clear the files, the db got changed
     Names = ets:lookup(couch_groups_by_db, DbName),
     lists:foreach(
@@ -304,8 +317,7 @@
             end
         end, Names),
     delete_index_dir(Root, DbName),
-    file:delete(Root ++ "/." ++ binary_to_list(DbName) ++ "_temp"),
-    {noreply, Server}.
+    file:delete(Root ++ "/." ++ ?b2l(DbName) ++ "_temp").
 
 handle_info({'EXIT', FromPid, Reason}, Server) ->
     case ets:lookup(couch_groups_by_updater, FromPid) of

Modified: couchdb/trunk/src/couchdb/couch_view_group.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_view_group.erl?rev=911544&r1=911543&r2=911544&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_view_group.erl (original)
+++ couchdb/trunk/src/couchdb/couch_view_group.erl Thu Feb 18 19:55:55 2010
@@ -78,17 +78,25 @@
 init({InitArgs, ReturnPid, Ref}) ->
     process_flag(trap_exit, true),
     case prepare_group(InitArgs, false) of
-    {ok, #group{db=Db, fd=Fd}=Group} ->
-        couch_db:monitor(Db),
-        Owner = self(),
-        Pid = spawn_link(fun()-> couch_view_updater:update(Owner, Group) end),
-        {ok, RefCounter} = couch_ref_counter:start([Fd]),
-        {ok, #group_state{
-                db_name=couch_db:name(Db),
-                init_args=InitArgs,
-                updater_pid = Pid,
-                group=Group,
-                ref_counter=RefCounter}};
+    {ok, #group{db=Db, fd=Fd, current_seq=Seq}=Group} ->
+        case Seq > couch_db:get_update_seq(Db) of
+        true ->
+            ReturnPid ! {Ref, self(), {error, invalid_view_seq}},
+            ignore;
+        _ ->
+            couch_db:monitor(Db),
+            Owner = self(),
+            Pid = spawn_link(
+                fun()-> couch_view_updater:update(Owner, Group) end
+            ),
+            {ok, RefCounter} = couch_ref_counter:start([Fd]),
+            {ok, #group_state{
+                    db_name=couch_db:name(Db),
+                    init_args=InitArgs,
+                    updater_pid = Pid,
+                    group=Group,
+                    ref_counter=RefCounter}}
+        end;
     Error ->
         ReturnPid ! {Ref, self(), Error},
         ignore

Added: couchdb/trunk/test/etap/150-invalid-view-seq.t
URL: http://svn.apache.org/viewvc/couchdb/trunk/test/etap/150-invalid-view-seq.t?rev=911544&view=auto
==============================================================================
--- couchdb/trunk/test/etap/150-invalid-view-seq.t (added)
+++ couchdb/trunk/test/etap/150-invalid-view-seq.t Thu Feb 18 19:55:55 2010
@@ -0,0 +1,192 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+
+% 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.
+
+-record(user_ctx, {
+    name = null,
+    roles = [],
+    handler
+}).
+
+default_config() ->
+    test_util:build_file("etc/couchdb/default_dev.ini").
+
+test_db_name() ->
+    <<"couch_test_invalid_view_seq">>.
+
+main(_) ->
+    test_util:init_code_path(),
+
+    etap:plan(10),
+    case (catch test()) of
+        ok ->
+            etap:end_tests();
+        Other ->
+            etap:diag(io_lib:format("Test died abnormally: ~p", [Other])),
+            etap:bail(Other)
+    end,
+    ok.
+
+%% NOTE: since during the test we stop the server,
+%%       a huge and ugly but harmless stack trace is sent to stderr
+%%
+test() ->
+    couch_server_sup:start_link([default_config()]),
+    timer:sleep(1000),
+    delete_db(),
+    create_db(),
+
+    create_docs(),
+    create_design_doc(),
+
+    % make DB file backup
+    backup_db_file(),
+
+    put(addr, couch_config:get("httpd", "bind_address", "127.0.0.1")),
+    put(port, couch_config:get("httpd", "port", "5984")),
+    application:start(inets),
+
+    create_new_doc(),
+    query_view_before_restore_backup(),
+
+    % restore DB file backup after querying view
+    restore_backup_db_file(),
+
+    query_view_after_restore_backup(),
+
+    delete_db(),
+    couch_server_sup:stop(),
+    ok.
+
+admin_user_ctx() ->
+    {user_ctx, #user_ctx{roles=[<<"_admin">>]}}.
+
+create_db() ->
+    {ok, _} = couch_db:create(test_db_name(), [admin_user_ctx()]).
+
+delete_db() ->
+    couch_server:delete(test_db_name(), [admin_user_ctx()]).
+
+create_docs() ->
+    {ok, Db} = couch_db:open(test_db_name(), [admin_user_ctx()]),
+    Doc1 = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"doc1">>},
+        {<<"value">>, 1}
+
+    ]}),
+    Doc2 = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"doc2">>},
+        {<<"value">>, 2}
+
+    ]}),
+    Doc3 = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"doc3">>},
+        {<<"value">>, 3}
+
+    ]}),
+    {ok, _} = couch_db:update_docs(Db, [Doc1, Doc2, Doc3]),
+    couch_db:ensure_full_commit(Db),
+    couch_db:close(Db).
+
+create_design_doc() ->
+    {ok, Db} = couch_db:open(test_db_name(), [admin_user_ctx()]),
+    DDoc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/foo">>},
+        {<<"language">>, <<"javascript">>},
+        {<<"views">>, {[
+            {<<"bar">>, {[
+                {<<"map">>, <<"function(doc) { emit(doc.value, 1); }">>}
+            ]}}
+        ]}}
+    ]}),
+    {ok, _} = couch_db:update_docs(Db, [DDoc]),
+    couch_db:ensure_full_commit(Db),
+    couch_db:close(Db).
+
+backup_db_file() ->
+    DbFile = test_util:build_file("tmp/lib/" ++
+        binary_to_list(test_db_name()) ++ ".couch"),
+    {ok, _} = file:copy(DbFile, DbFile ++ ".backup"),
+    ok.
+
+create_new_doc() ->
+    {ok, Db} = couch_db:open(test_db_name(), [admin_user_ctx()]),
+    Doc666 = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"doc666">>},
+        {<<"value">>, 999}
+
+    ]}),
+    {ok, _} = couch_db:update_docs(Db, [Doc666]),
+    couch_db:ensure_full_commit(Db),
+    couch_db:close(Db).
+
+db_url() ->
+    "http://" ++ get(addr) ++ ":" ++ get(port) ++ "/" ++
+    binary_to_list(test_db_name()).
+
+query_view_before_restore_backup() ->
+    {ok, {{_, Code, _}, _Headers, Body}} = http:request(
+        get,
+        {db_url() ++ "/_design/foo/_view/bar", []},
+        [],
+        [{sync, true}]),
+    etap:is(Code, 200, "Got view response before restoring backup."),
+    ViewJson = couch_util:json_decode(Body),
+    Rows = couch_util:get_nested_json_value(ViewJson, [<<"rows">>]),
+    HasDoc1 = has_doc("doc1", Rows),
+    HasDoc2 = has_doc("doc2", Rows),
+    HasDoc3 = has_doc("doc3", Rows),
+    HasDoc666 = has_doc("doc666", Rows),
+    etap:is(HasDoc1, true, "Before backup restore, view has doc1"),
+    etap:is(HasDoc2, true, "Before backup restore, view has doc2"),
+    etap:is(HasDoc3, true, "Before backup restore, view has doc3"),
+    etap:is(HasDoc666, true, "Before backup restore, view has doc666"),
+    ok.
+
+has_doc(DocId1, Rows) ->
+    DocId = iolist_to_binary(DocId1),
+    lists:any(
+        fun({R}) -> lists:member({<<"id">>, DocId}, R) end,
+        Rows
+    ).
+
+restore_backup_db_file() ->
+    couch_server_sup:stop(),
+    timer:sleep(3000),
+    DbFile = test_util:build_file("tmp/lib/" ++
+        binary_to_list(test_db_name()) ++ ".couch"),
+    ok = file:delete(DbFile),
+    ok = file:rename(DbFile ++ ".backup", DbFile),
+    couch_server_sup:start_link([default_config()]),
+    timer:sleep(1000),
+    ok.
+
+query_view_after_restore_backup() ->
+    {ok, {{_, Code, _}, _Headers, Body}} = http:request(
+        get,
+        {db_url() ++ "/_design/foo/_view/bar", []},
+        [],
+        [{sync, true}]),
+    etap:is(Code, 200, "Got view response after restoring backup."),
+    ViewJson = couch_util:json_decode(Body),
+    Rows = couch_util:get_nested_json_value(ViewJson, [<<"rows">>]),
+    HasDoc1 = has_doc("doc1", Rows),
+    HasDoc2 = has_doc("doc2", Rows),
+    HasDoc3 = has_doc("doc3", Rows),
+    HasDoc666 = has_doc("doc666", Rows),
+    etap:is(HasDoc1, true, "After backup restore, view has doc1"),
+    etap:is(HasDoc2, true, "After backup restore, view has doc2"),
+    etap:is(HasDoc3, true, "After backup restore, view has doc3"),
+    etap:is(HasDoc666, false, "After backup restore, view does not have doc666"),
+    ok.

Propchange: couchdb/trunk/test/etap/150-invalid-view-seq.t
------------------------------------------------------------------------------
    svn:executable = *

Modified: couchdb/trunk/test/etap/Makefile.am
URL: http://svn.apache.org/viewvc/couchdb/trunk/test/etap/Makefile.am?rev=911544&r1=911543&r2=911544&view=diff
==============================================================================
--- couchdb/trunk/test/etap/Makefile.am (original)
+++ couchdb/trunk/test/etap/Makefile.am Thu Feb 18 19:55:55 2010
@@ -61,4 +61,8 @@
     120-stats-collect.t \
     121-stats-aggregates.cfg \
     121-stats-aggregates.ini \
-    121-stats-aggregates.t
+    121-stats-aggregates.t \
+    130-attachments-md5.t \
+    140-attachment-comp.t \
+    150-invalid-view-seq.t
+