Refactor to add an interface for view_state of #mrheader
The goal of this refactoring is to make it easier to understand
and maintain view_state data structure of #mrheader.
Unlike record #mrview in #mrst, the data structure that represents
view_states in #mrheader is a tuple (of tuples) and its definition
and application spreads across all couch_mrview_util.erl module.
This makes it hard to follow and understand how it used
or troubleshoot mrview in case of the problems.
This change introduces an interface for generation and accessing
view_state and also separates generation of reduction closures
from mrview's `reduce`, `reduce_to_count` and `fold_reduce` functions
as they rely on view_state's structure.
Note that this refactoring also removes explicit set of generic
ordering function on collation option "raw" as it is a default
of couch_btree:open on missing option for `less` function.
diff --git a/src/couch_mrview_util.erl b/src/couch_mrview_util.erl
index c30460c..51e51d8 100644
--- a/src/couch_mrview_util.erl
+++ b/src/couch_mrview_util.erl
@@ -200,7 +200,7 @@
purge_seq=couch_db:get_purge_seq(Db),
id_btree_state=nil,
log_btree_state=nil,
- view_states=[{nil, nil, nil, 0, 0} || _ <- Views]
+ view_states=[make_view_state(#mrview{}) || _ <- Views]
},
init_state(Db, Fd, State, Header);
% read <= 1.2.x header record and transpile it to >=1.3.x
@@ -215,8 +215,8 @@
purge_seq=PurgeSeq,
id_btree_state=IdBtreeState,
log_btree_state=nil,
- view_states=[{Bt, nil, nil, USeq, PSeq} || {Bt, USeq, PSeq} <- ViewStates]
- });
+ view_states=[make_view_state(V) || V <- ViewStates]
+ });
init_state(Db, Fd, State, Header) ->
#mrst{
language=Lang,
@@ -232,12 +232,6 @@
view_states=ViewStates
} = Header,
- StateUpdate = fun
- ({_, _, _, _, _}=St) -> St;
- (St) -> {St, nil, nil, 0, 0}
- end,
- ViewStates2 = lists:map(StateUpdate, ViewStates),
-
IdBtOpts = [{compression, couch_db:compression(Db)}],
{ok, IdBtree} = couch_btree:open(IdBtreeState, Fd, IdBtOpts),
{ok, LogBtree} = case SeqIndexed orelse KeySeqIndexed of
@@ -246,7 +240,7 @@
end,
OpenViewFun = fun(St, View) -> open_view(Db, Fd, Lang, St, View) end,
- Views2 = lists:zipwith(OpenViewFun, ViewStates2, Views),
+ Views2 = lists:zipwith(OpenViewFun, ViewStates, Views),
State#mrst{
fd=Fd,
@@ -258,45 +252,33 @@
views=Views2
}.
-open_view(Db, Fd, Lang, {BTState, SeqBTState, KSeqBTState, USeq, PSeq}, View) ->
- FunSrcs = [FunSrc || {_Name, FunSrc} <- View#mrview.reduce_funs],
- ReduceFun =
- fun(reduce, KVs) ->
- KVs2 = detuple_kvs(expand_dups(KVs, []), []),
- {ok, Result} = couch_query_servers:reduce(Lang, FunSrcs, KVs2),
- {length(KVs2), Result};
- (rereduce, Reds) ->
- Count = lists:sum([Count0 || {Count0, _} <- Reds]),
- UsrReds = [UsrRedsList || {_, UsrRedsList} <- Reds],
- {ok, Result} = couch_query_servers:rereduce(Lang, FunSrcs, UsrReds),
- {Count, Result}
- end,
-
- Less = case couch_util:get_value(<<"collation">>, View#mrview.options) of
- <<"raw">> -> fun(A, B) -> A < B end;
- _ -> fun couch_ejson_compare:less_json_ids/2
- end,
-
+open_view(Db, Fd, Lang, ViewState, View) ->
+ ReduceFun = make_reduce_fun(Lang, View#mrview.reduce_funs),
+ LessFun = maybe_define_less_fun(View),
+ Compression = couch_db:compression(Db),
+ BTState = get_key_btree_state(ViewState),
ViewBtOpts = [
- {less, Less},
+ {less, LessFun},
{reduce, ReduceFun},
- {compression, couch_db:compression(Db)}
+ {compression, Compression}
],
{ok, Btree} = couch_btree:open(BTState, Fd, ViewBtOpts),
BySeqReduceFun = fun couch_db_updater:btree_by_seq_reduce/2,
{ok, SeqBtree} = if View#mrview.seq_indexed ->
+ SeqBTState = get_seq_btree_state(ViewState),
ViewSeqBtOpts = [{reduce, BySeqReduceFun},
- {compression, couch_db:compression(Db)}],
+ {compression, Compression}],
couch_btree:open(SeqBTState, Fd, ViewSeqBtOpts);
true ->
{ok, nil}
end,
{ok, KeyBySeqBtree} = if View#mrview.keyseq_indexed ->
- KeyBySeqBtOpts = [{less, Less},
+ KSeqBTState = get_kseq_btree_state(ViewState),
+ KeyBySeqBtOpts = [{less, LessFun},
{reduce, BySeqReduceFun},
- {compression, couch_db:compression(Db)}],
+ {compression, Compression}],
couch_btree:open(KSeqBTState, Fd, KeyBySeqBtOpts);
true ->
{ok, nil}
@@ -305,8 +287,8 @@
View#mrview{btree=Btree,
seq_btree=SeqBtree,
key_byseq_btree=KeyBySeqBtree,
- update_seq=USeq,
- purge_seq=PSeq}.
+ update_seq=get_update_seq(ViewState),
+ purge_seq=get_purge_seq(ViewState)}.
temp_view_to_ddoc({Props}) ->
@@ -341,18 +323,9 @@
reduce_to_count(nil) ->
0;
reduce_to_count(Reductions) ->
- Reduce = fun
- (reduce, KVs) ->
- Counts = [
- case V of {dups, Vals} -> length(Vals); _ -> 1 end
- || {_,V} <- KVs
- ],
- {lists:sum(Counts), []};
- (rereduce, Reds) ->
- {lists:sum([Count0 || {Count0, _} <- Reds]), []}
- end,
- {Count, _} = couch_btree:final_reduce(Reduce, Reductions),
- Count.
+ CountReduceFun = fun count_reduce/2,
+ FinalReduction = couch_btree:final_reduce(CountReduceFun, Reductions),
+ get_count(FinalReduction).
%% @doc get all changes for a view
get_view_changes_count(View) ->
@@ -414,25 +387,13 @@
btree=Bt,
reduce_funs=RedFuns
} = View,
- LPad = lists:duplicate(NthRed - 1, []),
- RPad = lists:duplicate(length(RedFuns) - NthRed, []),
- {_Name, FunSrc} = lists:nth(NthRed,RedFuns),
- ReduceFun = fun
- (reduce, KVs0) ->
- KVs1 = detuple_kvs(expand_dups(KVs0, []), []),
- {ok, Red} = couch_query_servers:reduce(Lang, [FunSrc], KVs1),
- {0, LPad ++ Red ++ RPad};
- (rereduce, Reds) ->
- ExtractRed = fun({_, UReds0}) -> [lists:nth(NthRed, UReds0)] end,
- UReds = lists:map(ExtractRed, Reds),
- {ok, Red} = couch_query_servers:rereduce(Lang, [FunSrc], UReds),
- {0, LPad ++ Red ++ RPad}
- end,
+ ReduceFun = make_user_reds_reduce_fun(Lang, RedFuns, NthRed),
WrapperFun = fun({GroupedKey, _}, PartialReds, Acc0) ->
- {_, Reds} = couch_btree:final_reduce(ReduceFun, PartialReds),
- Fun(GroupedKey, lists:nth(NthRed, Reds), Acc0)
+ FinalReduction = couch_btree:final_reduce(ReduceFun, PartialReds),
+ UserReductions = get_user_reds(FinalReduction),
+ Fun(GroupedKey, lists:nth(NthRed, UserReductions), Acc0)
end,
couch_btree:fold_reduce(Bt, WrapperFun, Acc, Options).
@@ -610,37 +571,12 @@
views=Views
} = State,
- ViewStates = lists:foldr(fun(V, Acc) ->
- SeqBtState = case V#mrview.seq_indexed of
- true ->
- couch_btree:get_state(V#mrview.seq_btree);
- _ ->
- nil
- end,
- KSeqBtState = case V#mrview.keyseq_indexed of
- true ->
- couch_btree:get_state(V#mrview.key_byseq_btree);
- _ ->
- nil
- end,
- [{couch_btree:get_state(V#mrview.btree),
- SeqBtState,
- KSeqBtState,
- V#mrview.update_seq,
- V#mrview.purge_seq} | Acc]
- end, [], Views),
-
- LogBtreeState = case LogBtree of
- nil -> nil;
- _ -> couch_btree:get_state(LogBtree)
- end,
-
#mrheader{
seq=Seq,
purge_seq=PurgeSeq,
- id_btree_state=couch_btree:get_state(IdBtree),
- log_btree_state= LogBtreeState,
- view_states=ViewStates
+ id_btree_state=get_btree_state(IdBtree),
+ log_btree_state=get_btree_state(LogBtree),
+ view_states=[make_view_state(V) || V <- Views]
}.
@@ -1004,6 +940,121 @@
%% End of <= 1.2.x upgrade code.
+make_view_state(#mrview{} = View) ->
+ BTState = get_btree_state(View#mrview.btree),
+ SeqBTState = case View#mrview.seq_indexed of
+ true ->
+ get_btree_state(View#mrview.seq_btree);
+ _ ->
+ nil
+ end,
+ KSeqBTState = case View#mrview.keyseq_indexed of
+ true ->
+ get_btree_state(View#mrview.key_byseq_btree);
+ _ ->
+ nil
+ end,
+ {
+ BTState,
+ SeqBTState,
+ KSeqBTState,
+ View#mrview.update_seq,
+ View#mrview.purge_seq
+ };
+make_view_state({BTState, UpdateSeq, PurgeSeq}) ->
+ {BTState, nil, nil, UpdateSeq, PurgeSeq};
+make_view_state(nil) ->
+ {nil, nil, nil, 0, 0}.
+
+
+get_key_btree_state(ViewState) ->
+ element(1, ViewState).
+
+get_seq_btree_state(ViewState) ->
+ element(2, ViewState).
+
+get_kseq_btree_state(ViewState) ->
+ element(3, ViewState).
+
+get_update_seq(ViewState) ->
+ element(4, ViewState).
+
+get_purge_seq(ViewState) ->
+ element(5, ViewState).
+
+get_count(Reduction) ->
+ element(1, Reduction).
+
+get_user_reds(Reduction) ->
+ element(2, Reduction).
+
+
+make_reduce_fun(Lang, ReduceFuns) ->
+ FunSrcs = [FunSrc || {_, FunSrc} <- ReduceFuns],
+ fun
+ (reduce, KVs0) ->
+ KVs = detuple_kvs(expand_dups(KVs0, []), []),
+ {ok, Result} = couch_query_servers:reduce(Lang, FunSrcs, KVs),
+ {length(KVs), Result};
+ (rereduce, Reds) ->
+ ExtractFun = fun(Red, {CountsAcc0, URedsAcc0}) ->
+ CountsAcc = CountsAcc0 + get_count(Red),
+ URedsAcc = lists:append(URedsAcc0, [get_user_reds(Red)]),
+ {CountsAcc, URedsAcc}
+ end,
+ {Counts, UReds} = lists:foldl(ExtractFun, {0, []}, Reds),
+ {ok, Result} = couch_query_servers:rereduce(Lang, FunSrcs, UReds),
+ {Counts, Result}
+ end.
+
+
+maybe_define_less_fun(#mrview{options = Options}) ->
+ case couch_util:get_value(<<"collation">>, Options) of
+ <<"raw">> -> undefined;
+ _ -> fun couch_ejson_compare:less_json_ids/2
+ end.
+
+
+count_reduce(reduce, KVs) ->
+ CountFun = fun
+ ({_, {dups, Vals}}, Acc) -> Acc + length(Vals);
+ (_, Acc) -> Acc + 1
+ end,
+ Count = lists:foldl(CountFun, 0, KVs),
+ {Count, []};
+count_reduce(rereduce, Reds) ->
+ CountFun = fun(Red, Acc) ->
+ Acc + get_count(Red)
+ end,
+ Count = lists:foldl(CountFun, 0, Reds),
+ {Count, []}.
+
+
+make_user_reds_reduce_fun(Lang, ReduceFuns, NthRed) ->
+ LPad = lists:duplicate(NthRed - 1, []),
+ RPad = lists:duplicate(length(ReduceFuns) - NthRed, []),
+ {_, FunSrc} = lists:nth(NthRed, ReduceFuns),
+ fun
+ (reduce, KVs0) ->
+ KVs = detuple_kvs(expand_dups(KVs0, []), []),
+ {ok, Result} = couch_query_servers:reduce(Lang, [FunSrc], KVs),
+ {0, LPad ++ Result ++ RPad};
+ (rereduce, Reds) ->
+ ExtractFun = fun(Reds0) ->
+ [lists:nth(NthRed, get_user_reds(Reds0))]
+ end,
+ UReds = lists:map(ExtractFun, Reds),
+ {ok, Result} = couch_query_servers:rereduce(Lang, [FunSrc], UReds),
+ {0, LPad ++ Result ++ RPad}
+ end.
+
+
+get_btree_state(nil) ->
+ nil;
+get_btree_state(#btree{} = Btree) ->
+ couch_btree:get_state(Btree).
+
+
extract_view_reduce({red, {N, _Lang, #mrview{reduce_funs=Reds}}, _Ref}) ->
{_Name, FunSrc} = lists:nth(N, Reds),
FunSrc.