summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul J. Davis <paul.joseph.davis@gmail.com>2020-09-18 11:05:00 -0500
committerPaul J. Davis <paul.joseph.davis@gmail.com>2020-09-24 12:15:17 -0500
commitd035d8dcb35a245414c2463a977f7a82a8feadde (patch)
tree53717c0950b3094be7831170ad3d33bb8c1f01ec
parentbd9486b41085c408868f8b72a718481c385d8f7a (diff)
downloadcouchdb-d035d8dcb35a245414c2463a977f7a82a8feadde.tar.gz
Upgrade legacy views
-rw-r--r--src/couch_views/include/couch_views.hrl4
-rw-r--r--src/couch_views/src/couch_views_fdb.erl180
-rw-r--r--src/couch_views/src/couch_views_indexer.erl8
-rw-r--r--src/couch_views/test/couch_views_upgrade_test.erl400
4 files changed, 551 insertions, 41 deletions
diff --git a/src/couch_views/include/couch_views.hrl b/src/couch_views/include/couch_views.hrl
index 388219118..92b8f46fb 100644
--- a/src/couch_views/include/couch_views.hrl
+++ b/src/couch_views/include/couch_views.hrl
@@ -10,6 +10,9 @@
% License for the specific language governing permissions and limitations under
% the License.
+% Current implementation version
+-define(CURRENT_VIEW_IMPL_VERSION, 1).
+
% Index info/data subspaces
-define(VIEW_INFO, 0).
-define(VIEW_DATA, 1).
@@ -21,6 +24,7 @@
-define(VIEW_KV_SIZE, 2).
-define(VIEW_BUILD_STATUS, 3).
-define(VIEW_CREATION_VS, 4).
+-define(VIEW_IMPL_VERSION, 5).
% Data keys
-define(VIEW_ID_RANGE, 0).
diff --git a/src/couch_views/src/couch_views_fdb.erl b/src/couch_views/src/couch_views_fdb.erl
index f238a8f18..28a60b872 100644
--- a/src/couch_views/src/couch_views_fdb.erl
+++ b/src/couch_views/src/couch_views_fdb.erl
@@ -13,6 +13,8 @@
-module(couch_views_fdb).
-export([
+ get_view_state/2,
+
new_interactive_index/3,
new_creation_vs/3,
get_creation_vs/2,
@@ -40,52 +42,89 @@
-include_lib("fabric/include/fabric2.hrl").
-new_interactive_index(Db, Mrst, VS) ->
- couch_views_fdb:new_creation_vs(Db, Mrst, VS),
- couch_views_fdb:set_build_status(Db, Mrst, ?INDEX_BUILDING).
+get_view_state(Db, #mrst{} = Mrst) ->
+ get_view_state(Db, Mrst#mrst.sig);
+
+get_view_state(Db, Sig) when is_binary(Sig) ->
+ #{
+ tx := Tx
+ } = Db,
+
+ VersionF = erlfdb:get(Tx, version_key(Db, Sig)),
+ ViewSeqF = erlfdb:get(Tx, seq_key(Db, Sig)),
+ ViewVSF = erlfdb:get(Tx, creation_vs_key(Db, Sig)),
+ BuildStatusF = erlfdb:get(Tx, build_status_key(Db, Sig)),
+
+ Version = case erlfdb:wait(VersionF) of
+ not_found -> not_found;
+ VsnVal -> element(1, erlfdb_tuple:unpack(VsnVal))
+ end,
+
+ ViewSeq = case erlfdb:wait(ViewSeqF) of
+ not_found -> <<>>;
+ SeqVal -> SeqVal
+ end,
+
+ ViewVS = case erlfdb:wait(ViewVSF) of
+ not_found -> not_found;
+ VSVal -> element(1, erlfdb_tuple:unpack(VSVal))
+ end,
+
+ State = #{
+ version => Version,
+ view_seq => ViewSeq,
+ view_vs => ViewVS,
+ build_status => erlfdb:wait(BuildStatusF)
+ },
+
+ maybe_upgrade_view(Db, Sig, State).
+
+
+new_interactive_index(Db, #mrst{} = Mrst, VS) ->
+ new_interactive_index(Db, Mrst#mrst.sig, VS);
+
+new_interactive_index(Db, Sig, VS) ->
+ set_version(Db, Sig),
+ new_creation_vs(Db, Sig, VS),
+ set_build_status(Db, Sig, ?INDEX_BUILDING).
%Interactive View Creation Versionstamp
%(<db>, ?DB_VIEWS, ?VIEW_INFO, ?VIEW_CREATION_VS, Sig) = VS
new_creation_vs(TxDb, #mrst{} = Mrst, VS) ->
+ new_creation_vs(TxDb, Mrst#mrst.sig, VS);
+
+new_creation_vs(TxDb, Sig, VS) ->
#{
tx := Tx
} = TxDb,
- Key = creation_vs_key(TxDb, Mrst#mrst.sig),
+ Key = creation_vs_key(TxDb, Sig),
Value = erlfdb_tuple:pack_vs({VS}),
ok = erlfdb:set_versionstamped_value(Tx, Key, Value).
-get_creation_vs(TxDb, #mrst{} = Mrst) ->
- get_creation_vs(TxDb, Mrst#mrst.sig);
-
-get_creation_vs(TxDb, Sig) ->
+get_creation_vs(TxDb, MrstOrSig) ->
#{
- tx := Tx
- } = TxDb,
- Key = creation_vs_key(TxDb, Sig),
- case erlfdb:wait(erlfdb:get(Tx, Key)) of
- not_found ->
- not_found;
- EK ->
- {VS} = erlfdb_tuple:unpack(EK),
- VS
- end.
+ view_vs := ViewVS
+ } = get_view_state(TxDb, MrstOrSig),
+ ViewVS.
%Interactive View Build Status
%(<db>, ?DB_VIEWS, ?VIEW_INFO, ?VIEW_BUILD_STATUS, Sig) = INDEX_BUILDING | INDEX_READY
-get_build_status(TxDb, #mrst{sig = Sig}) ->
+get_build_status(TxDb, MrstOrSig) ->
#{
- tx := Tx
- } = TxDb,
- Key = build_status_key(TxDb, Sig),
- erlfdb:wait(erlfdb:get(Tx, Key)).
+ build_status := BuildStatus
+ } = get_view_state(TxDb, MrstOrSig),
+ BuildStatus.
-set_build_status(TxDb, #mrst{sig = Sig}, State) ->
+set_build_status(TxDb, #mrst{} = Mrst, State) ->
+ set_build_status(TxDb, Mrst#mrst.sig, State);
+
+set_build_status(TxDb, Sig, State) ->
#{
tx := Tx
} = TxDb,
@@ -98,24 +137,18 @@ set_build_status(TxDb, #mrst{sig = Sig}, State) ->
% (<db>, ?DB_VIEWS, Sig, ?VIEW_UPDATE_SEQ) = Sequence
-get_update_seq(TxDb, #mrst{sig = Sig}) ->
+get_update_seq(TxDb, MrstOrSig) ->
#{
- tx := Tx,
- db_prefix := DbPrefix
- } = TxDb,
-
- case erlfdb:wait(erlfdb:get(Tx, seq_key(DbPrefix, Sig))) of
- not_found -> <<>>;
- UpdateSeq -> UpdateSeq
- end.
+ view_seq := ViewSeq
+ } = get_view_state(TxDb, MrstOrSig),
+ ViewSeq.
set_update_seq(TxDb, Sig, Seq) ->
#{
- tx := Tx,
- db_prefix := DbPrefix
+ tx := Tx
} = TxDb,
- ok = erlfdb:set(Tx, seq_key(DbPrefix, Sig), Seq).
+ ok = erlfdb:set(Tx, seq_key(TxDb, Sig), Seq).
list_signatures(Db) ->
@@ -139,7 +172,10 @@ clear_index(Db, Signature) ->
% Get view size to remove from global counter
SizeTuple = {?DB_VIEWS, ?VIEW_INFO, ?VIEW_KV_SIZE, Signature},
SizeKey = erlfdb_tuple:pack(SizeTuple, DbPrefix),
- ViewSize = ?bin2uint(erlfdb:wait(erlfdb:get(Tx, SizeKey))),
+ ViewSize = case erlfdb:wait(erlfdb:get(Tx, SizeKey)) of
+ not_found -> 0;
+ SizeVal -> ?bin2uint(SizeVal)
+ end,
% Clear index info keys
Keys = [
@@ -207,7 +243,75 @@ update_kv_size(TxDb, Sig, OldSize, NewSize) ->
erlfdb:add(Tx, DbKey, NewSize - OldSize).
-seq_key(DbPrefix, Sig) ->
+maybe_upgrade_view(_Db, _Sig, #{version := ?CURRENT_VIEW_IMPL_VERSION} = St) ->
+ St;
+maybe_upgrade_view(Db, Sig, #{version := not_found, view_seq := <<>>} = St) ->
+ % If we haven't started building the view yet
+ % then we don't change view_vs and build_status
+ % as they're still correct.
+ set_version(Db, Sig),
+ St#{
+ version => ?CURRENT_VIEW_IMPL_VERSION,
+ view_seq => <<>>
+ };
+maybe_upgrade_view(Db, Sig, #{version := not_found} = St) ->
+ clear_index(Db, Sig),
+ set_version(Db, Sig),
+ {ViewVS, BuildStatus} = reset_interactive_index(Db, Sig, St),
+ #{
+ version => ?CURRENT_VIEW_IMPL_VERSION,
+ view_seq => <<>>,
+ view_vs => ViewVS,
+ build_status => BuildStatus
+ }.
+
+
+set_version(Db, Sig) ->
+ #{
+ tx := Tx
+ } = Db,
+ Key = version_key(Db, Sig),
+ Val = erlfdb_tuple:pack({?CURRENT_VIEW_IMPL_VERSION}),
+ erlfdb:set(Tx, Key, Val).
+
+
+reset_interactive_index(_Db, _Sig, #{view_vs := not_found}) ->
+ % Not an interactive index
+ {not_found, not_found};
+reset_interactive_index(Db, Sig, _St) ->
+ % We have to reset the creation versionstamp
+ % to the current update seq of the database
+ % or else we'll not have indexed any documents
+ % inserted since the creation of the interactive
+ % index.
+ #{
+ tx := Tx
+ } = Db,
+
+ DbSeq = fabric2_db:get_update_seq(Db),
+ VS = fabric2_fdb:seq_to_vs(DbSeq),
+ Key = creation_vs_key(Db, Sig),
+ Val = erlfdb_tuple:pack({VS}),
+ ok = erlfdb:set(Tx, Key, Val),
+
+ set_build_status(Db, Sig, ?INDEX_BUILDING),
+
+ {VS, ?INDEX_BUILDING}.
+
+
+
+version_key(Db, Sig) ->
+ #{
+ db_prefix := DbPrefix
+ } = Db,
+ Key = {?DB_VIEWS, ?VIEW_INFO, ?VIEW_IMPL_VERSION, Sig},
+ erlfdb_tuple:pack(Key, DbPrefix).
+
+
+seq_key(Db, Sig) ->
+ #{
+ db_prefix := DbPrefix
+ } = Db,
Key = {?DB_VIEWS, ?VIEW_INFO, ?VIEW_UPDATE_SEQ, Sig},
erlfdb_tuple:pack(Key, DbPrefix).
diff --git a/src/couch_views/src/couch_views_indexer.erl b/src/couch_views/src/couch_views_indexer.erl
index da2393999..2735f66b7 100644
--- a/src/couch_views/src/couch_views_indexer.erl
+++ b/src/couch_views/src/couch_views_indexer.erl
@@ -200,8 +200,8 @@ do_update(Db, Mrst0, State0) ->
tx := Tx
} = TxDb,
- Mrst1 = couch_views_trees:open(TxDb, Mrst0),
State1 = get_update_start_state(TxDb, Mrst0, State0),
+ Mrst1 = couch_views_trees:open(TxDb, Mrst0),
{ok, State2} = fold_changes(State1),
@@ -259,8 +259,10 @@ maybe_set_build_status(TxDb, Mrst1, _ViewVS, State) ->
% In the first iteration of update we need
% to populate our db and view sequences
get_update_start_state(TxDb, Mrst, #{db_seq := undefined} = State) ->
- ViewVS = couch_views_fdb:get_creation_vs(TxDb, Mrst),
- ViewSeq = couch_views_fdb:get_update_seq(TxDb, Mrst),
+ #{
+ view_vs := ViewVS,
+ view_seq := ViewSeq
+ } = couch_views_fdb:get_view_state(TxDb, Mrst),
State#{
tx_db := TxDb,
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