diff options
Diffstat (limited to 'src/couch_views/test/couch_views_upgrade_test.erl')
-rw-r--r-- | src/couch_views/test/couch_views_upgrade_test.erl | 400 |
1 files changed, 400 insertions, 0 deletions
diff --git a/src/couch_views/test/couch_views_upgrade_test.erl b/src/couch_views/test/couch_views_upgrade_test.erl new file mode 100644 index 000000000..556a76297 --- /dev/null +++ b/src/couch_views/test/couch_views_upgrade_test.erl @@ -0,0 +1,400 @@ +% 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. + +-module(couch_views_upgrade_test). + +-include_lib("eunit/include/eunit.hrl"). +-include_lib("couch/include/couch_db.hrl"). +-include_lib("couch/include/couch_eunit.hrl"). +-include_lib("couch_mrview/include/couch_mrview.hrl"). +-include_lib("couch_views/include/couch_views.hrl"). +-include_lib("fabric/include/fabric2.hrl"). +-include_lib("fabric/test/fabric2_test.hrl"). + + +-define(MAP_FUN1, <<"map_fun1">>). +-define(MAP_FUN2, <<"map_fun2">>). + + +upgrade_test_() -> + { + "Test view upgrades", + { + setup, + fun setup/0, + fun cleanup/1, + { + foreach, + fun foreach_setup/0, + fun foreach_teardown/1, + [ + ?TDEF_FE(empty_state), + ?TDEF_FE(indexed_state), + ?TDEF_FE(upgrade_non_interactive), + ?TDEF_FE(upgrade_unbuilt_interactive), + ?TDEF_FE(upgrade_partially_built_interactive), + ?TDEF_FE(upgrade_built_interactive) + ] + } + } + }. + + +setup() -> + Ctx = test_util:start_couch([ + fabric, + couch_jobs, + couch_js, + couch_views + ]), + Ctx. + + +cleanup(Ctx) -> + test_util:stop_couch(Ctx). + + +foreach_setup() -> + {ok, Db} = fabric2_db:create(?tempdb(), [{user_ctx, ?ADMIN_USER}]), + Db. + + +foreach_teardown(Db) -> + meck:unload(), + config:delete("couch_views", "change_limit"), + ok = fabric2_db:delete(fabric2_db:name(Db), []). + + +empty_state(Db) -> + DDoc = create_ddoc(), + {ok, Mrst} = couch_views_util:ddoc_to_mrst(fabric2_db:name(Db), DDoc), + State = fabric2_fdb:transactional(Db, fun(TxDb) -> + couch_views_fdb:get_view_state(TxDb, Mrst) + end), + + Expect = #{ + version => ?CURRENT_VIEW_IMPL_VERSION, + view_seq => <<>>, + view_vs => not_found, + build_status => not_found + }, + ?assertEqual(Expect, State), + assert_fdb_state(Db, Mrst, Expect). + + +indexed_state(Db) -> + DDoc = create_ddoc(), + Doc1 = doc(0), + + {ok, _} = fabric2_db:update_doc(Db, DDoc, []), + {ok, _} = fabric2_db:update_doc(Db, Doc1, []), + + {ok, Out} = run_query(Db, DDoc, ?MAP_FUN1), + ?assertEqual([row(<<"0">>, 0, 0)], Out), + + assert_fdb_state(Db, DDoc, #{ + version => ?CURRENT_VIEW_IMPL_VERSION, + view_seq => fabric2_db:get_update_seq(Db), + view_vs => not_found, + build_status => not_found + }). + + +upgrade_non_interactive(Db) -> + DDoc = create_ddoc(), + Doc1 = doc(0), + + {ok, _} = fabric2_db:update_docs(Db, [DDoc, Doc1], []), + DbSeq = fabric2_db:get_update_seq(Db), + + init_fdb_state(Db, DDoc, #{view_seq => DbSeq}), + + {ok, Out} = run_query(Db, DDoc, ?MAP_FUN1), + ?assertEqual([row(<<"0">>, 0, 0)], Out), + + assert_fdb_state(Db, DDoc, #{ + version => ?CURRENT_VIEW_IMPL_VERSION, + view_seq => DbSeq, + view_vs => not_found, + build_status => not_found + }). + + +upgrade_unbuilt_interactive(Db) -> + DDoc = create_ddoc(), + {ok, Mrst} = couch_views_util:ddoc_to_mrst(fabric2_db:name(Db), DDoc), + Doc1 = doc(0), + + {ok, _} = fabric2_db:update_docs(Db, [DDoc, Doc1], []), + DbSeq = fabric2_db:get_update_seq(Db), + + init_fdb_state(Db, DDoc, #{ + view_vs => fabric2_fdb:seq_to_vs(DbSeq), + build_status => ?INDEX_BUILDING + }), + + % Trigger an upgrade + fabric2_fdb:transactional(Db, fun(TxDb) -> + couch_views_fdb:get_view_state(TxDb, Mrst) + end), + + assert_fdb_state(Db, DDoc, #{ + version => ?CURRENT_VIEW_IMPL_VERSION, + view_seq => <<>>, + view_vs => fabric2_fdb:seq_to_vs(DbSeq), + build_status => ?INDEX_BUILDING + }), + + % Build the view + {ok, Out} = run_query(Db, DDoc, ?MAP_FUN1), + ?assertEqual([row(<<"0">>, 0, 0)], Out), + + assert_fdb_state(Db, DDoc, #{ + version => ?CURRENT_VIEW_IMPL_VERSION, + view_seq => DbSeq, + view_vs => fabric2_fdb:seq_to_vs(DbSeq), + build_status => ?INDEX_READY + }). + + +upgrade_partially_built_interactive(Db) -> + DDoc = create_ddoc(), + {ok, Mrst} = couch_views_util:ddoc_to_mrst(fabric2_db:name(Db), DDoc), + {ok, _} = fabric2_db:update_doc(Db, DDoc, []), + + MidSeq = fabric2_db:get_update_seq(Db), + + Doc1 = doc(0), + {ok, _} = fabric2_db:update_doc(Db, Doc1, []), + + DbSeq = fabric2_db:get_update_seq(Db), + + init_fdb_state(Db, DDoc, #{ + view_seq => MidSeq, + view_vs => fabric2_fdb:seq_to_vs(DbSeq), + build_status => ?INDEX_BUILDING + }), + + % Trigger an upgrade + fabric2_fdb:transactional(Db, fun(TxDb) -> + couch_views_fdb:get_view_state(TxDb, Mrst) + end), + + assert_fdb_state(Db, DDoc, #{ + version => ?CURRENT_VIEW_IMPL_VERSION, + view_seq => <<>>, + view_vs => fabric2_fdb:seq_to_vs(DbSeq), + build_status => ?INDEX_BUILDING + }), + + % Build the view + {ok, Out} = run_query(Db, DDoc, ?MAP_FUN1), + ?assertEqual([row(<<"0">>, 0, 0)], Out), + + assert_fdb_state(Db, DDoc, #{ + version => ?CURRENT_VIEW_IMPL_VERSION, + view_seq => DbSeq, + view_vs => fabric2_fdb:seq_to_vs(DbSeq), + build_status => ?INDEX_READY + }). + + +upgrade_built_interactive(Db) -> + DDoc = create_ddoc(), + Doc1 = doc(0), + + {ok, Mrst} = couch_views_util:ddoc_to_mrst(fabric2_db:name(Db), DDoc), + {ok, _} = fabric2_db:update_doc(Db, DDoc, []), + {ok, _} = fabric2_db:update_doc(Db, Doc1, []), + + DbSeq = fabric2_db:get_update_seq(Db), + + init_fdb_state(Db, DDoc, #{ + view_seq => DbSeq, + view_vs => fabric2_fdb:seq_to_vs(DbSeq), + build_status => ?INDEX_READY + }), + + % Trigger an upgrade + fabric2_fdb:transactional(Db, fun(TxDb) -> + couch_views_fdb:get_view_state(TxDb, Mrst) + end), + + assert_fdb_state(Db, DDoc, #{ + version => ?CURRENT_VIEW_IMPL_VERSION, + view_seq => <<>>, + view_vs => fabric2_fdb:seq_to_vs(DbSeq), + build_status => ?INDEX_BUILDING + }), + + % Build the view + {ok, Out} = run_query(Db, DDoc, ?MAP_FUN1), + ?assertEqual([row(<<"0">>, 0, 0)], Out), + + assert_fdb_state(Db, DDoc, #{ + version => ?CURRENT_VIEW_IMPL_VERSION, + view_seq => DbSeq, + view_vs => fabric2_fdb:seq_to_vs(DbSeq), + build_status => ?INDEX_READY + }). + + +init_fdb_state(Db, #doc{} = DDoc, Values) -> + {ok, Mrst} = couch_views_util:ddoc_to_mrst(fabric2_db:name(Db), DDoc), + init_fdb_state(Db, Mrst, Values); +init_fdb_state(Db, #mrst{sig = Sig}, Values) -> + init_fdb_state(Db, Sig, Values); +init_fdb_state(Db, Sig, Values) -> + VersionRow = case maps:get(version, Values, undefined) of + undefined -> []; + Version -> [{pack(Db, key(version, Sig)), pack({Version})}] + end, + + SeqRow = case maps:get(view_seq, Values, undefined) of + undefined -> []; + Seq -> [{pack(Db, key(seq, Sig)), Seq}] + end, + + VSRow = case maps:get(view_vs, Values, undefined) of + undefined -> []; + VS -> [{pack(Db, key(vs, Sig)), pack({VS})}] + end, + + BSRow = case maps:get(build_status, Values, undefined) of + undefined -> []; + BS -> [{pack(Db, key(bs, Sig)), BS}] + end, + + Rows = VersionRow ++ SeqRow ++ VSRow ++ BSRow, + + fabric2_fdb:transactional(Db, fun(TxDb) -> + #{ + tx := Tx + } = TxDb, + lists:foreach(fun({K, V}) -> + erlfdb:set(Tx, K, V) + end, Rows) + end). + + +assert_fdb_state(Db, #doc{} = DDoc, Expect) -> + {ok, Mrst} = couch_views_util:ddoc_to_mrst(fabric2_db:name(Db), DDoc), + assert_fdb_state(Db, Mrst, Expect); +assert_fdb_state(Db, #mrst{sig = Sig}, Expect) -> + assert_fdb_state(Db, Sig, Expect); +assert_fdb_state(Db, Sig, Expect) -> + #{ + version := Version, + view_seq := ViewSeq, + view_vs := ViewVS, + build_status := BuildStatus + } = Expect, + + VersionRow = case Version of + not_found -> []; + _ -> [{pack(Db, key(version, Sig)), pack({Version})}] + end, + + SeqRow = case ViewSeq of + <<>> -> []; + _ -> [{pack(Db, key(seq, Sig)), ViewSeq}] + end, + + VSRow = case ViewVS of + not_found -> []; + _ -> [{pack(Db, key(vs, Sig)), pack({ViewVS})}] + end, + + BSRow = case BuildStatus of + not_found -> []; + _ -> [{pack(Db, key(bs, Sig)), BuildStatus}] + end, + + ExpectRows = lists:sort(VersionRow ++ SeqRow ++ VSRow ++ BSRow), + + RawExistingRows = fabric2_fdb:transactional(Db, fun(TxDb) -> + #{ + tx := Tx, + db_prefix := DbPrefix + } = TxDb, + RangePrefix = erlfdb_tuple:pack({?DB_VIEWS, ?VIEW_INFO}, DbPrefix), + erlfdb:wait(erlfdb:get_range_startswith(Tx, RangePrefix)) + end), + + % Ignore the KV size key in the view info rows + KVSizeKey = pack(Db, key(kv_size, Sig)), + ExistingRows = lists:keydelete(KVSizeKey, 1, RawExistingRows), + + ?assertEqual(ExpectRows, ExistingRows). + + +key(version, Sig) -> {?DB_VIEWS, ?VIEW_INFO, ?VIEW_IMPL_VERSION, Sig}; +key(seq, Sig) -> {?DB_VIEWS, ?VIEW_INFO, ?VIEW_UPDATE_SEQ, Sig}; +key(kv_size, Sig) -> {?DB_VIEWS, ?VIEW_INFO, ?VIEW_KV_SIZE, Sig}; +key(vs, Sig) -> {?DB_VIEWS, ?VIEW_INFO, ?VIEW_CREATION_VS, Sig}; +key(bs, Sig) -> {?DB_VIEWS, ?VIEW_INFO, ?VIEW_BUILD_STATUS, Sig}. + + +pack(Db, Key) -> + #{ + db_prefix := DbPrefix + } = Db, + erlfdb_tuple:pack(Key, DbPrefix). + + +pack(Value) -> + erlfdb_tuple:pack(Value). + + +row(Id, Key, Value) -> + {row, [ + {id, Id}, + {key, Key}, + {value, Value} + ]}. + + +fold_fun({meta, _Meta}, Acc) -> + {ok, Acc}; +fold_fun({row, _} = Row, Acc) -> + {ok, [Row | Acc]}; +fold_fun(complete, Acc) -> + {ok, lists:reverse(Acc)}. + + +create_ddoc() -> + couch_doc:from_json_obj({[ + {<<"_id">>, <<"_design/bar">>}, + {<<"views">>, {[ + {?MAP_FUN1, {[ + {<<"map">>, <<"function(doc) {emit(doc.val, doc.val);}">>} + ]}}, + {?MAP_FUN2, {[ + {<<"map">>, <<"function(doc) {}">>} + ]}} + ]}} + ]}). + + +doc(Id) -> + doc(Id, Id). + + +doc(Id, Val) -> + couch_doc:from_json_obj({[ + {<<"_id">>, list_to_binary(integer_to_list(Id))}, + {<<"val">>, Val} + ]}). + + +run_query(#{} = Db, DDoc, <<_/binary>> = View) -> + couch_views:query(Db, DDoc, View, fun fold_fun/2, [], #mrargs{}).
\ No newline at end of file |