summaryrefslogtreecommitdiff
path: root/src/couch_views/test/couch_views_upgrade_test.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/couch_views/test/couch_views_upgrade_test.erl')
-rw-r--r--src/couch_views/test/couch_views_upgrade_test.erl400
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