diff options
author | Nick Vatamaniuc <vatamane@gmail.com> | 2021-11-20 01:00:08 -0500 |
---|---|---|
committer | Nick Vatamaniuc <nickva@users.noreply.github.com> | 2021-11-22 17:31:31 -0500 |
commit | b78ccf18cb4ed6e183ed0bf4e5cbe40d7a7dc493 (patch) | |
tree | 82158f0b6c7e97e6955bf0c558aac6eb0329b410 /src/couch_mrview | |
parent | 6e87e43fae23647b281ede250ad9f1a68a8f1cde (diff) | |
download | couchdb-b78ccf18cb4ed6e183ed0bf4e5cbe40d7a7dc493.tar.gz |
Apply erlfmt formatting to source tree
These exceptions from main were ported over to 3.x
```
--- a/src/chttpd/src/chttpd.erl
+++ b/src/chttpd/src/chttpd.erl
@@ -491,6 +491,7 @@ extract_cookie(#httpd{mochi_req = MochiReq}) ->
end.
%%% end hack
+%% erlfmt-ignore
set_auth_handlers() ->
AuthenticationDefault = "{chttpd_auth, cookie_authentication_handler},
```
```
--- a/src/couch/src/couch_debug.erl
+++ b/src/couch/src/couch_debug.erl
@@ -49,6 +49,7 @@ help() ->
].
-spec help(Function :: atom()) -> ok.
+%% erlfmt-ignore
help(opened_files) ->
```
Diffstat (limited to 'src/couch_mrview')
24 files changed, 2944 insertions, 2451 deletions
diff --git a/src/couch_mrview/src/couch_mrview.erl b/src/couch_mrview/src/couch_mrview.erl index 1cdc91809..798b939c7 100644 --- a/src/couch_mrview/src/couch_mrview.erl +++ b/src/couch_mrview/src/couch_mrview.erl @@ -27,7 +27,7 @@ -record(mracc, { db, - meta_sent=false, + meta_sent = false, total_rows, offset, limit, @@ -36,82 +36,100 @@ doc_info, callback, user_acc, - last_go=ok, + last_go = ok, reduce_fun, finalizer, update_seq, args }). - - validate_ddoc_fields(DDoc) -> MapFuncType = map_function_type(DDoc), - lists:foreach(fun(Path) -> - validate_ddoc_fields(DDoc, Path) - end, [ - [{<<"filters">>, object}, {any, [object, string]}], - [{<<"language">>, string}], - [{<<"lists">>, object}, {any, [object, string]}], - [{<<"options">>, object}], - [{<<"options">>, object}, {<<"include_design">>, boolean}], - [{<<"options">>, object}, {<<"local_seq">>, boolean}], - [{<<"options">>, object}, {<<"partitioned">>, boolean}], - [{<<"rewrites">>, [string, array]}], - [{<<"shows">>, object}, {any, [object, string]}], - [{<<"updates">>, object}, {any, [object, string]}], - [{<<"validate_doc_update">>, string}], - [{<<"views">>, object}, {<<"lib">>, object}], - [{<<"views">>, object}, {any, object}, {<<"map">>, MapFuncType}], - [{<<"views">>, object}, {any, object}, {<<"reduce">>, string}] - ]), + lists:foreach( + fun(Path) -> + validate_ddoc_fields(DDoc, Path) + end, + [ + [{<<"filters">>, object}, {any, [object, string]}], + [{<<"language">>, string}], + [{<<"lists">>, object}, {any, [object, string]}], + [{<<"options">>, object}], + [{<<"options">>, object}, {<<"include_design">>, boolean}], + [{<<"options">>, object}, {<<"local_seq">>, boolean}], + [{<<"options">>, object}, {<<"partitioned">>, boolean}], + [{<<"rewrites">>, [string, array]}], + [{<<"shows">>, object}, {any, [object, string]}], + [{<<"updates">>, object}, {any, [object, string]}], + [{<<"validate_doc_update">>, string}], + [{<<"views">>, object}, {<<"lib">>, object}], + [{<<"views">>, object}, {any, object}, {<<"map">>, MapFuncType}], + [{<<"views">>, object}, {any, object}, {<<"reduce">>, string}] + ] + ), require_map_function_for_views(DDoc), ok. require_map_function_for_views({Props}) -> case couch_util:get_value(<<"views">>, Props) of - undefined -> ok; + undefined -> + ok; {Views} -> - lists:foreach(fun - ({<<"lib">>, _}) -> ok; - ({Key, {Value}}) -> - case couch_util:get_value(<<"map">>, Value) of - undefined -> throw({invalid_design_doc, - <<"View `", Key/binary, "` must contain map function">>}); - _ -> ok - end - end, Views), + lists:foreach( + fun + ({<<"lib">>, _}) -> + ok; + ({Key, {Value}}) -> + case couch_util:get_value(<<"map">>, Value) of + undefined -> + throw( + {invalid_design_doc, + <<"View `", Key/binary, "` must contain map function">>} + ); + _ -> + ok + end + end, + Views + ), ok end. validate_ddoc_fields(DDoc, Path) -> case validate_ddoc_fields(DDoc, Path, []) of - ok -> ok; + ok -> + ok; {error, {FailedPath0, Type0}} -> FailedPath = iolist_to_binary(join(FailedPath0, <<".">>)), Type = format_type(Type0), - throw({invalid_design_doc, - <<"`", FailedPath/binary, "` field must have ", - Type/binary, " type">>}) + throw( + {invalid_design_doc, + <<"`", FailedPath/binary, "` field must have ", Type/binary, " type">>} + ) end. validate_ddoc_fields(undefined, _, _) -> ok; validate_ddoc_fields(_, [], _) -> ok; -validate_ddoc_fields({KVS}=Props, [{any, Type} | Rest], Acc) -> - lists:foldl(fun - ({Key, _}, ok) -> - validate_ddoc_fields(Props, [{Key, Type} | Rest], Acc); - ({_, _}, {error, _}=Error) -> - Error - end, ok, KVS); -validate_ddoc_fields({KVS}=Props, [{Key, Type} | Rest], Acc) -> +validate_ddoc_fields({KVS} = Props, [{any, Type} | Rest], Acc) -> + lists:foldl( + fun + ({Key, _}, ok) -> + validate_ddoc_fields(Props, [{Key, Type} | Rest], Acc); + ({_, _}, {error, _} = Error) -> + Error + end, + ok, + KVS + ); +validate_ddoc_fields({KVS} = Props, [{Key, Type} | Rest], Acc) -> case validate_ddoc_field(Props, {Key, Type}) of ok -> - validate_ddoc_fields(couch_util:get_value(Key, KVS), - Rest, - [Key | Acc]); + validate_ddoc_fields( + couch_util:get_value(Key, KVS), + Rest, + [Key | Acc] + ); error -> {error, {[Key | Acc], Type}}; {error, Key1} -> @@ -123,10 +141,14 @@ validate_ddoc_field(undefined, Type) when is_atom(Type) -> validate_ddoc_field(_, any) -> ok; validate_ddoc_field(Value, Types) when is_list(Types) -> - lists:foldl(fun - (_, ok) -> ok; - (Type, _) -> validate_ddoc_field(Value, Type) - end, error, Types); + lists:foldl( + fun + (_, ok) -> ok; + (Type, _) -> validate_ddoc_field(Value, Type) + end, + error, + Types + ); validate_ddoc_field(Value, string) when is_binary(Value) -> ok; validate_ddoc_field(Value, array) when is_list(Value) -> @@ -165,36 +187,38 @@ format_type(Types) when is_list(Types) -> join(L, Sep) -> join(L, Sep, []). -join([H|[]], _, Acc) -> +join([H | []], _, Acc) -> [H | Acc]; -join([H|T], Sep, Acc) -> +join([H | T], Sep, Acc) -> join(T, Sep, [Sep, H | Acc]). - -validate(Db, DDoc) -> +validate(Db, DDoc) -> ok = validate_ddoc_fields(DDoc#doc.body), GetName = fun (#mrview{map_names = [Name | _]}) -> Name; (#mrview{reduce_funs = [{Name, _} | _]}) -> Name; (_) -> null end, - ValidateView = fun(Proc, #mrview{def=MapSrc, reduce_funs=Reds}=View) -> + ValidateView = fun(Proc, #mrview{def = MapSrc, reduce_funs = Reds} = View) -> couch_query_servers:try_compile(Proc, map, GetName(View), MapSrc), - lists:foreach(fun - ({_RedName, <<"_sum", _/binary>>}) -> - ok; - ({_RedName, <<"_count", _/binary>>}) -> - ok; - ({_RedName, <<"_stats", _/binary>>}) -> - ok; - ({_RedName, <<"_approx_count_distinct", _/binary>>}) -> - ok; - ({_RedName, <<"_", _/binary>> = Bad}) -> - Msg = ["`", Bad, "` is not a supported reduce function."], - throw({invalid_design_doc, Msg}); - ({RedName, RedSrc}) -> - couch_query_servers:try_compile(Proc, reduce, RedName, RedSrc) - end, Reds) + lists:foreach( + fun + ({_RedName, <<"_sum", _/binary>>}) -> + ok; + ({_RedName, <<"_count", _/binary>>}) -> + ok; + ({_RedName, <<"_stats", _/binary>>}) -> + ok; + ({_RedName, <<"_approx_count_distinct", _/binary>>}) -> + ok; + ({_RedName, <<"_", _/binary>> = Bad}) -> + Msg = ["`", Bad, "` is not a supported reduce function."], + throw({invalid_design_doc, Msg}); + ({RedName, RedSrc}) -> + couch_query_servers:try_compile(Proc, reduce, RedName, RedSrc) + end, + Reds + ) end, {ok, #mrst{ language = Lang, @@ -204,9 +228,12 @@ validate(Db, DDoc) -> case {couch_db:is_partitioned(Db), Partitioned} of {false, true} -> - throw({invalid_design_doc, - <<"partitioned option cannot be true in a " - "non-partitioned database.">>}); + throw( + {invalid_design_doc, << + "partitioned option cannot be true in a " + "non-partitioned database." + >>} + ); {_, _} -> ok end, @@ -220,16 +247,15 @@ validate(Db, DDoc) -> after couch_query_servers:ret_os_process(Proc) end - catch {unknown_query_language, _Lang} -> - %% Allow users to save ddocs written in unknown languages - ok + catch + {unknown_query_language, _Lang} -> + %% Allow users to save ddocs written in unknown languages + ok end. - query_all_docs(Db, Args) -> query_all_docs(Db, Args, fun default_cb/2, []). - query_all_docs(Db, Args, Callback, Acc) when is_list(Args) -> query_all_docs(Db, to_mrargs(Args), Callback, Acc); query_all_docs(Db, Args0, Callback, Acc) -> @@ -237,44 +263,41 @@ query_all_docs(Db, Args0, Callback, Acc) -> {ok, Info} = couch_db:get_db_info(WDb), couch_index_util:hexsig(couch_hash:md5_hash(term_to_binary(Info))) end), - Args1 = Args0#mrargs{view_type=map}, + Args1 = Args0#mrargs{view_type = map}, Args2 = couch_mrview_util:validate_all_docs_args(Db, Args1), - {ok, Acc1} = case Args2#mrargs.preflight_fun of - PFFun when is_function(PFFun, 2) -> PFFun(Sig, Acc); - _ -> {ok, Acc} - end, + {ok, Acc1} = + case Args2#mrargs.preflight_fun of + PFFun when is_function(PFFun, 2) -> PFFun(Sig, Acc); + _ -> {ok, Acc} + end, all_docs_fold(Db, Args2, Callback, Acc1). - query_view(Db, DDoc, VName) -> query_view(Db, DDoc, VName, #mrargs{}). - query_view(Db, DDoc, VName, Args) when is_list(Args) -> query_view(Db, DDoc, VName, to_mrargs(Args), fun default_cb/2, []); query_view(Db, DDoc, VName, Args) -> query_view(Db, DDoc, VName, Args, fun default_cb/2, []). - query_view(Db, DDoc, VName, Args, Callback, Acc) when is_list(Args) -> query_view(Db, DDoc, VName, to_mrargs(Args), Callback, Acc); query_view(Db, DDoc, VName, Args0, Callback, Acc0) -> case couch_mrview_util:get_view(Db, DDoc, VName, Args0) of {ok, VInfo, Sig, Args} -> - {ok, Acc1} = case Args#mrargs.preflight_fun of - PFFun when is_function(PFFun, 2) -> PFFun(Sig, Acc0); - _ -> {ok, Acc0} - end, + {ok, Acc1} = + case Args#mrargs.preflight_fun of + PFFun when is_function(PFFun, 2) -> PFFun(Sig, Acc0); + _ -> {ok, Acc0} + end, query_view(Db, VInfo, Args, Callback, Acc1); ddoc_updated -> Callback(ok, ddoc_updated) end. - get_view_index_pid(Db, DDoc, ViewName, Args0) -> couch_mrview_util:get_view_index_pid(Db, DDoc, ViewName, Args0). - query_view(Db, {Type, View, Ref}, Args, Callback, Acc) -> try case Type of @@ -285,12 +308,10 @@ query_view(Db, {Type, View, Ref}, Args, Callback, Acc) -> erlang:demonitor(Ref, [flush]) end. - get_info(Db, DDoc) -> {ok, Pid} = couch_index_server:get_index(couch_mrview_index, Db, DDoc), couch_index:get_info(Pid). - trigger_update(Db, DDoc) -> trigger_update(Db, DDoc, couch_db:get_update_seq(Db)). @@ -300,22 +321,27 @@ trigger_update(Db, DDoc, UpdateSeq) -> %% get informations on a view get_view_info(Db, DDoc, VName) -> - {ok, {_, View, _}, _, _Args} = couch_mrview_util:get_view(Db, DDoc, VName, - #mrargs{}), + {ok, {_, View, _}, _, _Args} = couch_mrview_util:get_view( + Db, + DDoc, + VName, + #mrargs{} + ), %% get the total number of rows - {ok, TotalRows} = couch_mrview_util:get_row_count(View), - - {ok, [{update_seq, View#mrview.update_seq}, - {purge_seq, View#mrview.purge_seq}, - {total_rows, TotalRows}]}. + {ok, TotalRows} = couch_mrview_util:get_row_count(View), + {ok, [ + {update_seq, View#mrview.update_seq}, + {purge_seq, View#mrview.purge_seq}, + {total_rows, TotalRows} + ]}. %% @doc refresh a view index -refresh(DbName, DDoc) when is_binary(DbName)-> +refresh(DbName, DDoc) when is_binary(DbName) -> UpdateSeq = couch_util:with_db(DbName, fun(WDb) -> - couch_db:get_update_seq(WDb) - end), + couch_db:get_update_seq(WDb) + end), case couch_index_server:get_index(couch_mrview_index, DbName, DDoc) of {ok, Pid} -> @@ -326,48 +352,43 @@ refresh(DbName, DDoc) when is_binary(DbName)-> Error -> {error, Error} end; - refresh(Db, DDoc) -> refresh(couch_db:name(Db), DDoc). compact(Db, DDoc) -> compact(Db, DDoc, []). - compact(Db, DDoc, Opts) -> {ok, Pid} = couch_index_server:get_index(couch_mrview_index, Db, DDoc), couch_index:compact(Pid, Opts). - cancel_compaction(Db, DDoc) -> {ok, IPid} = couch_index_server:get_index(couch_mrview_index, Db, DDoc), {ok, CPid} = couch_index:get_compactor_pid(IPid), ok = couch_index_compactor:cancel(CPid), % Cleanup the compaction file if it exists - {ok, #mrst{sig=Sig, db_name=DbName}} = couch_index:get_state(IPid, 0), + {ok, #mrst{sig = Sig, db_name = DbName}} = couch_index:get_state(IPid, 0), couch_mrview_util:delete_compaction_file(DbName, Sig), ok. - cleanup(Db) -> couch_mrview_cleanup:run(Db). - -all_docs_fold(Db, #mrargs{keys=undefined}=Args, Callback, UAcc) -> +all_docs_fold(Db, #mrargs{keys = undefined} = Args, Callback, UAcc) -> ReduceFun = get_reduce_fun(Args), Total = get_total_rows(Db, Args), UpdateSeq = get_update_seq(Db, Args), Acc = #mracc{ - db=Db, - total_rows=Total, - limit=Args#mrargs.limit, - skip=Args#mrargs.skip, - callback=Callback, - user_acc=UAcc, - reduce_fun=ReduceFun, - update_seq=UpdateSeq, - args=Args + db = Db, + total_rows = Total, + limit = Args#mrargs.limit, + skip = Args#mrargs.skip, + callback = Callback, + user_acc = UAcc, + reduce_fun = ReduceFun, + update_seq = UpdateSeq, + args = Args }, [Opts1] = couch_mrview_util:all_docs_key_opts(Args), % TODO: This is a terrible hack for now. We'll probably have @@ -375,243 +396,262 @@ all_docs_fold(Db, #mrargs{keys=undefined}=Args, Callback, UAcc) -> % a btree. For now non-btree's will just have to pass 0 or % some fake reductions to get an offset. Opts2 = [include_reductions | Opts1], - FunName = case couch_util:get_value(namespace, Args#mrargs.extra) of - <<"_design">> -> fold_design_docs; - <<"_local">> -> fold_local_docs; - _ -> fold_docs - end, + FunName = + case couch_util:get_value(namespace, Args#mrargs.extra) of + <<"_design">> -> fold_design_docs; + <<"_local">> -> fold_local_docs; + _ -> fold_docs + end, {ok, Offset, FinalAcc} = couch_db:FunName(Db, fun map_fold/3, Acc, Opts2), finish_fold(FinalAcc, [{total, Total}, {offset, Offset}]); -all_docs_fold(Db, #mrargs{direction=Dir, keys=Keys0}=Args, Callback, UAcc) -> +all_docs_fold(Db, #mrargs{direction = Dir, keys = Keys0} = Args, Callback, UAcc) -> ReduceFun = get_reduce_fun(Args), Total = get_total_rows(Db, Args), UpdateSeq = get_update_seq(Db, Args), Acc = #mracc{ - db=Db, - total_rows=Total, - limit=Args#mrargs.limit, - skip=Args#mrargs.skip, - callback=Callback, - user_acc=UAcc, - reduce_fun=ReduceFun, - update_seq=UpdateSeq, - args=Args + db = Db, + total_rows = Total, + limit = Args#mrargs.limit, + skip = Args#mrargs.skip, + callback = Callback, + user_acc = UAcc, + reduce_fun = ReduceFun, + update_seq = UpdateSeq, + args = Args }, % Backwards compatibility hack. The old _all_docs iterates keys % in reverse if descending=true was passed. Here we'll just % reverse the list instead. - Keys = if Dir =:= fwd -> Keys0; true -> lists:reverse(Keys0) end, + Keys = + if + Dir =:= fwd -> Keys0; + true -> lists:reverse(Keys0) + end, FoldFun = fun(Key, Acc0) -> DocInfo = (catch couch_db:get_doc_info(Db, Key)), - {Doc, Acc1} = case DocInfo of - {ok, #doc_info{id=Id, revs=[RevInfo | _RestRevs]}=DI} -> - Rev = couch_doc:rev_to_str(RevInfo#rev_info.rev), - Props = [{rev, Rev}] ++ case RevInfo#rev_info.deleted of - true -> [{deleted, true}]; - false -> [] - end, - {{{Id, Id}, {Props}}, Acc0#mracc{doc_info=DI}}; - not_found -> - {{{Key, error}, not_found}, Acc0} - end, + {Doc, Acc1} = + case DocInfo of + {ok, #doc_info{id = Id, revs = [RevInfo | _RestRevs]} = DI} -> + Rev = couch_doc:rev_to_str(RevInfo#rev_info.rev), + Props = + [{rev, Rev}] ++ + case RevInfo#rev_info.deleted of + true -> [{deleted, true}]; + false -> [] + end, + {{{Id, Id}, {Props}}, Acc0#mracc{doc_info = DI}}; + not_found -> + {{{Key, error}, not_found}, Acc0} + end, {_, Acc2} = map_fold(Doc, {[], [{0, 0, 0}]}, Acc1), Acc2 end, FinalAcc = lists:foldl(FoldFun, Acc, Keys), finish_fold(FinalAcc, [{total, Total}]). - map_fold(Db, View, Args, Callback, UAcc) -> {ok, Total} = couch_mrview_util:get_row_count(View), Acc = #mracc{ - db=Db, - total_rows=Total, - limit=Args#mrargs.limit, - skip=Args#mrargs.skip, - callback=Callback, - user_acc=UAcc, - reduce_fun=fun couch_mrview_util:reduce_to_count/1, - update_seq=View#mrview.update_seq, - args=Args + db = Db, + total_rows = Total, + limit = Args#mrargs.limit, + skip = Args#mrargs.skip, + callback = Callback, + user_acc = UAcc, + reduce_fun = fun couch_mrview_util:reduce_to_count/1, + update_seq = View#mrview.update_seq, + args = Args }, OptList = couch_mrview_util:key_opts(Args), - {Reds, Acc2} = lists:foldl(fun(Opts, {_, Acc0}) -> - {ok, R, A} = couch_mrview_util:fold(View, fun map_fold/3, Acc0, Opts), - {R, A} - end, {nil, Acc}, OptList), + {Reds, Acc2} = lists:foldl( + fun(Opts, {_, Acc0}) -> + {ok, R, A} = couch_mrview_util:fold(View, fun map_fold/3, Acc0, Opts), + {R, A} + end, + {nil, Acc}, + OptList + ), Offset = couch_mrview_util:reduce_to_count(Reds), finish_fold(Acc2, [{total, Total}, {offset, Offset}]). - map_fold(#full_doc_info{} = FullDocInfo, OffsetReds, Acc) -> % matches for _all_docs and translates #full_doc_info{} -> KV pair case couch_doc:to_doc_info(FullDocInfo) of - #doc_info{id=Id, revs=[#rev_info{deleted=false, rev=Rev}|_]} = DI -> + #doc_info{id = Id, revs = [#rev_info{deleted = false, rev = Rev} | _]} = DI -> Value = {[{rev, couch_doc:rev_to_str(Rev)}]}, - map_fold({{Id, Id}, Value}, OffsetReds, Acc#mracc{doc_info=DI}); - #doc_info{revs=[#rev_info{deleted=true}|_]} -> + map_fold({{Id, Id}, Value}, OffsetReds, Acc#mracc{doc_info = DI}); + #doc_info{revs = [#rev_info{deleted = true} | _]} -> {ok, Acc} end; -map_fold(_KV, _Offset, #mracc{skip=N}=Acc) when N > 0 -> - {ok, Acc#mracc{skip=N-1, last_go=ok}}; -map_fold(KV, OffsetReds, #mracc{offset=undefined}=Acc) -> +map_fold(_KV, _Offset, #mracc{skip = N} = Acc) when N > 0 -> + {ok, Acc#mracc{skip = N - 1, last_go = ok}}; +map_fold(KV, OffsetReds, #mracc{offset = undefined} = Acc) -> #mracc{ - total_rows=Total, - callback=Callback, - user_acc=UAcc0, - reduce_fun=Reduce, - update_seq=UpdateSeq, - args=Args + total_rows = Total, + callback = Callback, + user_acc = UAcc0, + reduce_fun = Reduce, + update_seq = UpdateSeq, + args = Args } = Acc, Offset = Reduce(OffsetReds), Meta = make_meta(Args, UpdateSeq, [{total, Total}, {offset, Offset}]), {Go, UAcc1} = Callback(Meta, UAcc0), - Acc1 = Acc#mracc{meta_sent=true, offset=Offset, user_acc=UAcc1, last_go=Go}, + Acc1 = Acc#mracc{meta_sent = true, offset = Offset, user_acc = UAcc1, last_go = Go}, case Go of ok -> map_fold(KV, OffsetReds, Acc1); stop -> {stop, Acc1} end; -map_fold(_KV, _Offset, #mracc{limit=0}=Acc) -> +map_fold(_KV, _Offset, #mracc{limit = 0} = Acc) -> {stop, Acc}; map_fold({{Key, Id}, Val}, _Offset, Acc) -> #mracc{ - db=Db, - limit=Limit, - doc_info=DI, - callback=Callback, - user_acc=UAcc0, - args=Args + db = Db, + limit = Limit, + doc_info = DI, + callback = Callback, + user_acc = UAcc0, + args = Args } = Acc, - Doc = case DI of - #doc_info{} -> couch_mrview_util:maybe_load_doc(Db, DI, Args); - _ -> couch_mrview_util:maybe_load_doc(Db, Id, Val, Args) - end, + Doc = + case DI of + #doc_info{} -> couch_mrview_util:maybe_load_doc(Db, DI, Args); + _ -> couch_mrview_util:maybe_load_doc(Db, Id, Val, Args) + end, Row = [{id, Id}, {key, Key}, {value, Val}] ++ Doc, {Go, UAcc1} = Callback({row, Row}, UAcc0), {Go, Acc#mracc{ - limit=Limit-1, - doc_info=undefined, - user_acc=UAcc1, - last_go=Go + limit = Limit - 1, + doc_info = undefined, + user_acc = UAcc1, + last_go = Go }}; map_fold(#doc{id = <<"_local/", _/binary>>} = Doc, _Offset, #mracc{} = Acc) -> #mracc{ - limit=Limit, - callback=Callback, - user_acc=UAcc0, - args=Args + limit = Limit, + callback = Callback, + user_acc = UAcc0, + args = Args } = Acc, #doc{ id = DocId, revs = {Pos, [RevId | _]} } = Doc, Rev = {Pos, RevId}, - Row = [ - {id, DocId}, - {key, DocId}, - {value, {[{rev, couch_doc:rev_to_str(Rev)}]}} - ] ++ if not Args#mrargs.include_docs -> []; true -> - [{doc, couch_doc:to_json_obj(Doc, Args#mrargs.doc_options)}] - end, + Row = + [ + {id, DocId}, + {key, DocId}, + {value, {[{rev, couch_doc:rev_to_str(Rev)}]}} + ] ++ + if + not Args#mrargs.include_docs -> []; + true -> [{doc, couch_doc:to_json_obj(Doc, Args#mrargs.doc_options)}] + end, {Go, UAcc1} = Callback({row, Row}, UAcc0), {Go, Acc#mracc{ - limit=Limit-1, - reduce_fun=undefined, - doc_info=undefined, - user_acc=UAcc1, - last_go=Go + limit = Limit - 1, + reduce_fun = undefined, + doc_info = undefined, + user_acc = UAcc1, + last_go = Go }}. -red_fold(Db, {NthRed, _Lang, View}=RedView, Args, Callback, UAcc) -> - Finalizer = case couch_util:get_value(finalizer, Args#mrargs.extra) of - undefined -> - {_, FunSrc} = lists:nth(NthRed, View#mrview.reduce_funs), - FunSrc; - CustomFun-> - CustomFun - end, +red_fold(Db, {NthRed, _Lang, View} = RedView, Args, Callback, UAcc) -> + Finalizer = + case couch_util:get_value(finalizer, Args#mrargs.extra) of + undefined -> + {_, FunSrc} = lists:nth(NthRed, View#mrview.reduce_funs), + FunSrc; + CustomFun -> + CustomFun + end, Acc = #mracc{ - db=Db, - total_rows=null, - limit=Args#mrargs.limit, - skip=Args#mrargs.skip, - group_level=Args#mrargs.group_level, - callback=Callback, - user_acc=UAcc, - update_seq=View#mrview.update_seq, - finalizer=Finalizer, - args=Args + db = Db, + total_rows = null, + limit = Args#mrargs.limit, + skip = Args#mrargs.skip, + group_level = Args#mrargs.group_level, + callback = Callback, + user_acc = UAcc, + update_seq = View#mrview.update_seq, + finalizer = Finalizer, + args = Args }, Grouping = {key_group_level, Args#mrargs.group_level}, OptList = couch_mrview_util:key_opts(Args, [Grouping]), - Acc2 = lists:foldl(fun(Opts, Acc0) -> - {ok, Acc1} = - couch_mrview_util:fold_reduce(RedView, fun red_fold/3, Acc0, Opts), - Acc1 - end, Acc, OptList), + Acc2 = lists:foldl( + fun(Opts, Acc0) -> + {ok, Acc1} = + couch_mrview_util:fold_reduce(RedView, fun red_fold/3, Acc0, Opts), + Acc1 + end, + Acc, + OptList + ), finish_fold(Acc2, []). red_fold({p, _Partition, Key}, Red, Acc) -> red_fold(Key, Red, Acc); -red_fold(_Key, _Red, #mracc{skip=N}=Acc) when N > 0 -> - {ok, Acc#mracc{skip=N-1, last_go=ok}}; -red_fold(Key, Red, #mracc{meta_sent=false}=Acc) -> +red_fold(_Key, _Red, #mracc{skip = N} = Acc) when N > 0 -> + {ok, Acc#mracc{skip = N - 1, last_go = ok}}; +red_fold(Key, Red, #mracc{meta_sent = false} = Acc) -> #mracc{ - args=Args, - callback=Callback, - user_acc=UAcc0, - update_seq=UpdateSeq + args = Args, + callback = Callback, + user_acc = UAcc0, + update_seq = UpdateSeq } = Acc, Meta = make_meta(Args, UpdateSeq, []), {Go, UAcc1} = Callback(Meta, UAcc0), - Acc1 = Acc#mracc{user_acc=UAcc1, meta_sent=true, last_go=Go}, + Acc1 = Acc#mracc{user_acc = UAcc1, meta_sent = true, last_go = Go}, case Go of ok -> red_fold(Key, Red, Acc1); _ -> {Go, Acc1} end; -red_fold(_Key, _Red, #mracc{limit=0} = Acc) -> +red_fold(_Key, _Red, #mracc{limit = 0} = Acc) -> {stop, Acc}; -red_fold(_Key, Red, #mracc{group_level=0} = Acc) -> +red_fold(_Key, Red, #mracc{group_level = 0} = Acc) -> #mracc{ - finalizer=Finalizer, - limit=Limit, - callback=Callback, - user_acc=UAcc0 + finalizer = Finalizer, + limit = Limit, + callback = Callback, + user_acc = UAcc0 } = Acc, Row = [{key, null}, {value, maybe_finalize(Red, Finalizer)}], {Go, UAcc1} = Callback({row, Row}, UAcc0), - {Go, Acc#mracc{user_acc=UAcc1, limit=Limit-1, last_go=Go}}; -red_fold(Key, Red, #mracc{group_level=exact} = Acc) -> + {Go, Acc#mracc{user_acc = UAcc1, limit = Limit - 1, last_go = Go}}; +red_fold(Key, Red, #mracc{group_level = exact} = Acc) -> #mracc{ - finalizer=Finalizer, - limit=Limit, - callback=Callback, - user_acc=UAcc0 + finalizer = Finalizer, + limit = Limit, + callback = Callback, + user_acc = UAcc0 } = Acc, Row = [{key, Key}, {value, maybe_finalize(Red, Finalizer)}], {Go, UAcc1} = Callback({row, Row}, UAcc0), - {Go, Acc#mracc{user_acc=UAcc1, limit=Limit-1, last_go=Go}}; -red_fold(K, Red, #mracc{group_level=I} = Acc) when I > 0, is_list(K) -> + {Go, Acc#mracc{user_acc = UAcc1, limit = Limit - 1, last_go = Go}}; +red_fold(K, Red, #mracc{group_level = I} = Acc) when I > 0, is_list(K) -> #mracc{ - finalizer=Finalizer, - limit=Limit, - callback=Callback, - user_acc=UAcc0 + finalizer = Finalizer, + limit = Limit, + callback = Callback, + user_acc = UAcc0 } = Acc, Row = [{key, lists:sublist(K, I)}, {value, maybe_finalize(Red, Finalizer)}], {Go, UAcc1} = Callback({row, Row}, UAcc0), - {Go, Acc#mracc{user_acc=UAcc1, limit=Limit-1, last_go=Go}}; -red_fold(K, Red, #mracc{group_level=I} = Acc) when I > 0 -> + {Go, Acc#mracc{user_acc = UAcc1, limit = Limit - 1, last_go = Go}}; +red_fold(K, Red, #mracc{group_level = I} = Acc) when I > 0 -> #mracc{ - finalizer=Finalizer, - limit=Limit, - callback=Callback, - user_acc=UAcc0 + finalizer = Finalizer, + limit = Limit, + callback = Callback, + user_acc = UAcc0 } = Acc, Row = [{key, K}, {value, maybe_finalize(Red, Finalizer)}], {Go, UAcc1} = Callback({row, Row}, UAcc0), - {Go, Acc#mracc{user_acc=UAcc1, limit=Limit-1, last_go=Go}}. + {Go, Acc#mracc{user_acc = UAcc1, limit = Limit - 1, last_go = Go}}. maybe_finalize(Red, null) -> Red; @@ -619,31 +659,31 @@ maybe_finalize(Red, RedSrc) -> {ok, Finalized} = couch_query_servers:finalize(RedSrc, Red), Finalized. -finish_fold(#mracc{last_go=ok, update_seq=UpdateSeq}=Acc, ExtraMeta) -> - #mracc{callback=Callback, user_acc=UAcc, args=Args}=Acc, +finish_fold(#mracc{last_go = ok, update_seq = UpdateSeq} = Acc, ExtraMeta) -> + #mracc{callback = Callback, user_acc = UAcc, args = Args} = Acc, % Possible send meta info Meta = make_meta(Args, UpdateSeq, ExtraMeta), - {Go, UAcc1} = case Acc#mracc.meta_sent of - false -> Callback(Meta, UAcc); - _ -> {ok, Acc#mracc.user_acc} - end, + {Go, UAcc1} = + case Acc#mracc.meta_sent of + false -> Callback(Meta, UAcc); + _ -> {ok, Acc#mracc.user_acc} + end, % Notify callback that the fold is complete. - {_, UAcc2} = case Go of - ok -> Callback(complete, UAcc1); - _ -> {ok, UAcc1} - end, + {_, UAcc2} = + case Go of + ok -> Callback(complete, UAcc1); + _ -> {ok, UAcc1} + end, {ok, UAcc2}; -finish_fold(#mracc{user_acc=UAcc}, _ExtraMeta) -> +finish_fold(#mracc{user_acc = UAcc}, _ExtraMeta) -> {ok, UAcc}. - make_meta(Args, UpdateSeq, Base) -> case Args#mrargs.update_seq of true -> {meta, Base ++ [{update_seq, UpdateSeq}]}; _ -> {meta, Base} end. - get_reduce_fun(#mrargs{extra = Extra}) -> case couch_util:get_value(namespace, Extra) of <<"_local">> -> @@ -652,7 +692,6 @@ get_reduce_fun(#mrargs{extra = Extra}) -> fun couch_mrview_util:all_docs_reduce_to_count/1 end. - get_total_rows(Db, #mrargs{extra = Extra}) -> case couch_util:get_value(namespace, Extra) of <<"_local">> -> @@ -665,7 +704,6 @@ get_total_rows(Db, #mrargs{extra = Extra}) -> couch_util:get_value(doc_count, Info) end. - get_update_seq(Db, #mrargs{extra = Extra}) -> case couch_util:get_value(namespace, Extra) of <<"_local">> -> @@ -674,7 +712,6 @@ get_update_seq(Db, #mrargs{extra = Extra}) -> couch_db:get_update_seq(Db) end. - default_cb(complete, Acc) -> {ok, lists:reverse(Acc)}; default_cb({final, Info}, []) -> @@ -686,13 +723,15 @@ default_cb(ok, ddoc_updated) -> default_cb(Row, Acc) -> {ok, [Row | Acc]}. - to_mrargs(KeyList) -> - lists:foldl(fun({Key, Value}, Acc) -> - Index = lookup_index(couch_util:to_existing_atom(Key)), - setelement(Index, Acc, Value) - end, #mrargs{}, KeyList). - + lists:foldl( + fun({Key, Value}, Acc) -> + Index = lookup_index(couch_util:to_existing_atom(Key)), + setelement(Index, Acc, Value) + end, + #mrargs{}, + KeyList + ). lookup_index(Key) -> Index = lists:zip( diff --git a/src/couch_mrview/src/couch_mrview_cleanup.erl b/src/couch_mrview/src/couch_mrview_cleanup.erl index e0cb1c64f..417605c55 100644 --- a/src/couch_mrview/src/couch_mrview_cleanup.erl +++ b/src/couch_mrview/src/couch_mrview_cleanup.erl @@ -14,24 +14,26 @@ -export([run/1]). - -include_lib("couch/include/couch_db.hrl"). -include_lib("couch_mrview/include/couch_mrview.hrl"). - run(Db) -> RootDir = couch_index_util:root_dir(), DbName = couch_db:name(Db), {ok, DesignDocs} = couch_db:get_design_docs(Db), - SigFiles = lists:foldl(fun(DDocInfo, SFAcc) -> - {ok, DDoc} = couch_db:open_doc_int(Db, DDocInfo, [ejson_body]), - {ok, InitState} = couch_mrview_util:ddoc_to_mrst(DbName, DDoc), - Sig = InitState#mrst.sig, - IFName = couch_mrview_util:index_file(DbName, Sig), - CFName = couch_mrview_util:compaction_file(DbName, Sig), - [IFName, CFName | SFAcc] - end, [], [DD || DD <- DesignDocs, DD#full_doc_info.deleted == false]), + SigFiles = lists:foldl( + fun(DDocInfo, SFAcc) -> + {ok, DDoc} = couch_db:open_doc_int(Db, DDocInfo, [ejson_body]), + {ok, InitState} = couch_mrview_util:ddoc_to_mrst(DbName, DDoc), + Sig = InitState#mrst.sig, + IFName = couch_mrview_util:index_file(DbName, Sig), + CFName = couch_mrview_util:compaction_file(DbName, Sig), + [IFName, CFName | SFAcc] + end, + [], + [DD || DD <- DesignDocs, DD#full_doc_info.deleted == false] + ), IdxDir = couch_index_util:index_dir(mrview, DbName), DiskFiles = filelib:wildcard(filename:join(IdxDir, "*")), @@ -39,21 +41,28 @@ run(Db) -> % We need to delete files that have no ddoc. ToDelete = DiskFiles -- SigFiles, - lists:foreach(fun(FN) -> - couch_log:debug("Deleting stale view file: ~s", [FN]), - couch_file:delete(RootDir, FN, [sync]), - case couch_mrview_util:verify_view_filename(FN) of - true -> - Sig = couch_mrview_util:get_signature_from_filename(FN), - DocId = couch_mrview_util:get_local_purge_doc_id(Sig), - case couch_db:open_doc(Db, DocId, []) of - {ok, LocalPurgeDoc} -> - couch_db:update_doc(Db, - LocalPurgeDoc#doc{deleted=true}, [?ADMIN_CTX]); - {not_found, _} -> - ok - end; - false -> ok - end - end, ToDelete), + lists:foreach( + fun(FN) -> + couch_log:debug("Deleting stale view file: ~s", [FN]), + couch_file:delete(RootDir, FN, [sync]), + case couch_mrview_util:verify_view_filename(FN) of + true -> + Sig = couch_mrview_util:get_signature_from_filename(FN), + DocId = couch_mrview_util:get_local_purge_doc_id(Sig), + case couch_db:open_doc(Db, DocId, []) of + {ok, LocalPurgeDoc} -> + couch_db:update_doc( + Db, + LocalPurgeDoc#doc{deleted = true}, + [?ADMIN_CTX] + ); + {not_found, _} -> + ok + end; + false -> + ok + end + end, + ToDelete + ), ok. diff --git a/src/couch_mrview/src/couch_mrview_compactor.erl b/src/couch_mrview/src/couch_mrview_compactor.erl index d42edc054..28e5a9b3d 100644 --- a/src/couch_mrview/src/couch_mrview_compactor.erl +++ b/src/couch_mrview/src/couch_mrview_compactor.erl @@ -18,12 +18,12 @@ -export([compact/3, swap_compacted/2, remove_compacted/1]). -record(acc, { - btree = nil, - last_id = nil, - kvs = [], - kvs_size = 0, - changes = 0, - total_changes + btree = nil, + last_id = nil, + kvs = [], + kvs_size = 0, + changes = 0, + total_changes }). -define(DEFAULT_RECOMPACT_RETRY_COUNT, 3). @@ -36,12 +36,12 @@ compact(_Db, State, Opts) -> compact(State) -> #mrst{ - db_name=DbName, - idx_name=IdxName, - sig=Sig, - update_seq=Seq, - id_btree=IdBtree, - views=Views + db_name = DbName, + idx_name = IdxName, + sig = Sig, + update_seq = Seq, + id_btree = IdBtree, + views = Views } = State, erlang:put(io_priority, {view_compact, DbName, IdxName}), @@ -65,7 +65,9 @@ compact(State) -> {ok, Kvs} = couch_mrview_util:get_row_count(View), Acc + Kvs end, - NumDocIds, Views), + NumDocIds, + Views + ), couch_task_status:add_task([ {type, view_compaction}, @@ -83,24 +85,29 @@ compact(State) -> FoldFun = fun({DocId, ViewIdKeys} = KV, Acc) -> #acc{btree = Bt, kvs = Kvs, kvs_size = KvsSize} = Acc, - NewKvs = case Kvs of - [{DocId, OldViewIdKeys} | Rest] -> - couch_log:error("Dupes of ~s in ~s ~s", - [DocId, DbName, IdxName]), - [{DocId, ViewIdKeys ++ OldViewIdKeys} | Rest]; - _ -> - [KV | Kvs] - end, + NewKvs = + case Kvs of + [{DocId, OldViewIdKeys} | Rest] -> + couch_log:error( + "Dupes of ~s in ~s ~s", + [DocId, DbName, IdxName] + ), + [{DocId, ViewIdKeys ++ OldViewIdKeys} | Rest]; + _ -> + [KV | Kvs] + end, KvsSize2 = KvsSize + ?term_size(KV), case KvsSize2 >= BufferSize of true -> {ok, Bt2} = couch_btree:add(Bt, lists:reverse(NewKvs)), Acc2 = update_task(Acc, length(NewKvs)), {ok, Acc2#acc{ - btree = Bt2, kvs = [], kvs_size = 0, last_id = DocId}}; + btree = Bt2, kvs = [], kvs_size = 0, last_id = DocId + }}; _ -> {ok, Acc#acc{ - kvs = NewKvs, kvs_size = KvsSize2, last_id = DocId}} + kvs = NewKvs, kvs_size = KvsSize2, last_id = DocId + }} end end, @@ -110,26 +117,26 @@ compact(State) -> {ok, NewIdBtree} = couch_btree:add(Bt3, lists:reverse(Uncopied)), FinalAcc2 = update_task(FinalAcc, length(Uncopied)), - - {NewViews, _} = lists:mapfoldl(fun({View, EmptyView}, Acc) -> - compact_view(View, EmptyView, BufferSize, Acc) - end, FinalAcc2, lists:zip(Views, EmptyViews)), + {NewViews, _} = lists:mapfoldl( + fun({View, EmptyView}, Acc) -> + compact_view(View, EmptyView, BufferSize, Acc) + end, + FinalAcc2, + lists:zip(Views, EmptyViews) + ), unlink(EmptyState#mrst.fd), {ok, EmptyState#mrst{ - id_btree=NewIdBtree, - views=NewViews, - update_seq=Seq + id_btree = NewIdBtree, + views = NewViews, + update_seq = Seq }}. - recompact(State) -> recompact(State, recompact_retry_count()). -recompact(#mrst{db_name=DbName, idx_name=IdxName}, 0) -> - erlang:error({exceeded_recompact_retry_count, - [{db_name, DbName}, {idx_name, IdxName}]}); - +recompact(#mrst{db_name = DbName, idx_name = IdxName}, 0) -> + erlang:error({exceeded_recompact_retry_count, [{db_name, DbName}, {idx_name, IdxName}]}); recompact(State, RetryCount) -> Self = self(), link(State#mrst.fd), @@ -159,27 +166,35 @@ recompact_retry_count() -> ?DEFAULT_RECOMPACT_RETRY_COUNT ). - %% @spec compact_view(View, EmptyView, Retry, Acc) -> {CompactView, NewAcc} -compact_view(#mrview{id_num=VID}=View, EmptyView, BufferSize, Acc0) -> - - {NewBt, FinalAcc} = compact_view_btree(View#mrview.btree, - EmptyView#mrview.btree, - VID, BufferSize, Acc0), +compact_view(#mrview{id_num = VID} = View, EmptyView, BufferSize, Acc0) -> + {NewBt, FinalAcc} = compact_view_btree( + View#mrview.btree, + EmptyView#mrview.btree, + VID, + BufferSize, + Acc0 + ), - {EmptyView#mrview{btree=NewBt, - update_seq=View#mrview.update_seq, - purge_seq=View#mrview.purge_seq}, FinalAcc}. + { + EmptyView#mrview{ + btree = NewBt, + update_seq = View#mrview.update_seq, + purge_seq = View#mrview.purge_seq + }, + FinalAcc + }. compact_view_btree(Btree, EmptyBtree, VID, BufferSize, Acc0) -> Fun = fun(KV, #acc{btree = Bt, kvs = Kvs, kvs_size = KvsSize} = Acc) -> KvsSize2 = KvsSize + ?term_size(KV), - if KvsSize2 >= BufferSize -> - {ok, Bt2} = couch_btree:add(Bt, lists:reverse([KV | Kvs])), - Acc2 = update_task(VID, Acc, 1 + length(Kvs)), - {ok, Acc2#acc{btree = Bt2, kvs = [], kvs_size = 0}}; - true -> - {ok, Acc#acc{kvs = [KV | Kvs], kvs_size = KvsSize2}} + if + KvsSize2 >= BufferSize -> + {ok, Bt2} = couch_btree:add(Bt, lists:reverse([KV | Kvs])), + Acc2 = update_task(VID, Acc, 1 + length(Kvs)), + {ok, Acc2#acc{btree = Bt2, kvs = [], kvs_size = 0}}; + true -> + {ok, Acc#acc{kvs = [KV | Kvs], kvs_size = KvsSize2}} end end, @@ -193,11 +208,18 @@ compact_view_btree(Btree, EmptyBtree, VID, BufferSize, Acc0) -> update_task(Acc, ChangesInc) -> update_task(null, Acc, ChangesInc). - -update_task(VID, #acc{changes=Changes, total_changes=Total}=Acc, ChangesInc) -> - Phase = if is_integer(VID) -> view; true -> ids end, +update_task(VID, #acc{changes = Changes, total_changes = Total} = Acc, ChangesInc) -> + Phase = + if + is_integer(VID) -> view; + true -> ids + end, Changes2 = Changes + ChangesInc, - Progress = if Total == 0 -> 0; true -> (Changes2 * 100) div Total end, + Progress = + if + Total == 0 -> 0; + true -> (Changes2 * 100) div Total + end, couch_task_status:update([ {phase, Phase}, {view, VID}, @@ -207,15 +229,14 @@ update_task(VID, #acc{changes=Changes, total_changes=Total}=Acc, ChangesInc) -> ]), Acc#acc{changes = Changes2}. - swap_compacted(OldState, NewState) -> #mrst{ fd = Fd } = OldState, #mrst{ - sig=Sig, - db_name=DbName, - fd=NewFd + sig = Sig, + db_name = DbName, + fd = NewFd } = NewState, link(NewState#mrst.fd), @@ -227,16 +248,18 @@ swap_compacted(OldState, NewState) -> {ok, Pre} = couch_file:bytes(Fd), {ok, Post} = couch_file:bytes(NewFd), - couch_log:notice("Compaction swap for view ~s ~p ~p", [IndexFName, - Pre, Post]), + couch_log:notice("Compaction swap for view ~s ~p ~p", [ + IndexFName, + Pre, + Post + ]), ok = couch_file:delete(RootDir, IndexFName), ok = file:rename(CompactFName, IndexFName), unlink(OldState#mrst.fd), erlang:demonitor(OldState#mrst.fd_monitor, [flush]), - {ok, NewState#mrst{fd_monitor=Ref}}. - + {ok, NewState#mrst{fd_monitor = Ref}}. remove_compacted(#mrst{sig = Sig, db_name = DbName} = State) -> RootDir = couch_index_util:root_dir(), @@ -244,7 +267,6 @@ remove_compacted(#mrst{sig = Sig, db_name = DbName} = State) -> ok = couch_file:delete(RootDir, CompactFName), {ok, State}. - -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). @@ -268,27 +290,28 @@ recompact_test_() -> recompact_success_after_progress() -> ?_test(begin - ok = meck:expect(couch_index_updater, update, fun - (Pid, _, #mrst{update_seq=0} = State) -> - Pid ! {'$gen_cast', {new_state, State#mrst{update_seq = 1}}}, - timer:sleep(100), - exit({updated, self(), State#mrst{update_seq = 2}}) + ok = meck:expect(couch_index_updater, update, fun(Pid, _, #mrst{update_seq = 0} = State) -> + Pid ! {'$gen_cast', {new_state, State#mrst{update_seq = 1}}}, + timer:sleep(100), + exit({updated, self(), State#mrst{update_seq = 2}}) end), - State = #mrst{fd=self(), update_seq=0}, + State = #mrst{fd = self(), update_seq = 0}, ?assertEqual({ok, State#mrst{update_seq = 2}}, recompact(State)) end). recompact_exceeded_retry_count() -> ?_test(begin - ok = meck:expect(couch_index_updater, update, + ok = meck:expect( + couch_index_updater, + update, fun(_, _, _) -> exit(error) - end), + end + ), ok = meck:expect(couch_log, warning, fun(_, _) -> ok end), - State = #mrst{fd=self(), db_name=foo, idx_name=bar}, - ExpectedError = {exceeded_recompact_retry_count, - [{db_name, foo}, {idx_name, bar}]}, - ?assertError(ExpectedError, recompact(State)) + State = #mrst{fd = self(), db_name = foo, idx_name = bar}, + ExpectedError = {exceeded_recompact_retry_count, [{db_name, foo}, {idx_name, bar}]}, + ?assertError(ExpectedError, recompact(State)) end). -endif. diff --git a/src/couch_mrview/src/couch_mrview_http.erl b/src/couch_mrview/src/couch_mrview_http.erl index 802739b82..fa3fab386 100644 --- a/src/couch_mrview/src/couch_mrview_http.erl +++ b/src/couch_mrview/src/couch_mrview_http.erl @@ -43,37 +43,41 @@ -include_lib("couch/include/couch_db.hrl"). -include_lib("couch_mrview/include/couch_mrview.hrl"). - -handle_all_docs_req(#httpd{method='GET'}=Req, Db) -> +handle_all_docs_req(#httpd{method = 'GET'} = Req, Db) -> all_docs_req(Req, Db, undefined); -handle_all_docs_req(#httpd{method='POST'}=Req, Db) -> +handle_all_docs_req(#httpd{method = 'POST'} = Req, Db) -> chttpd:validate_ctype(Req, "application/json"), Keys = couch_mrview_util:get_view_keys(chttpd:json_body_obj(Req)), all_docs_req(Req, Db, Keys); handle_all_docs_req(Req, _Db) -> chttpd:send_method_not_allowed(Req, "GET,POST,HEAD"). -handle_local_docs_req(#httpd{method='GET'}=Req, Db) -> +handle_local_docs_req(#httpd{method = 'GET'} = Req, Db) -> all_docs_req(Req, Db, undefined, <<"_local">>); -handle_local_docs_req(#httpd{method='POST'}=Req, Db) -> +handle_local_docs_req(#httpd{method = 'POST'} = Req, Db) -> chttpd:validate_ctype(Req, "application/json"), Keys = couch_mrview_util:get_view_keys(chttpd:json_body_obj(Req)), all_docs_req(Req, Db, Keys, <<"_local">>); handle_local_docs_req(Req, _Db) -> chttpd:send_method_not_allowed(Req, "GET,POST,HEAD"). -handle_design_docs_req(#httpd{method='GET'}=Req, Db) -> +handle_design_docs_req(#httpd{method = 'GET'} = Req, Db) -> all_docs_req(Req, Db, undefined, <<"_design">>); -handle_design_docs_req(#httpd{method='POST'}=Req, Db) -> +handle_design_docs_req(#httpd{method = 'POST'} = Req, Db) -> chttpd:validate_ctype(Req, "application/json"), Keys = couch_mrview_util:get_view_keys(chttpd:json_body_obj(Req)), all_docs_req(Req, Db, Keys, <<"_design">>); handle_design_docs_req(Req, _Db) -> chttpd:send_method_not_allowed(Req, "GET,POST,HEAD"). -handle_reindex_req(#httpd{method='POST', - path_parts=[_, _, DName,<<"_reindex">>]}=Req, - Db, _DDoc) -> +handle_reindex_req( + #httpd{ + method = 'POST', + path_parts = [_, _, DName, <<"_reindex">>] + } = Req, + Db, + _DDoc +) -> chttpd:validate_ctype(Req, "application/json"), ok = couch_db:check_is_admin(Db), couch_mrview:trigger_update(Db, <<"_design/", DName/binary>>), @@ -81,23 +85,30 @@ handle_reindex_req(#httpd{method='POST', handle_reindex_req(Req, _Db, _DDoc) -> chttpd:send_method_not_allowed(Req, "POST"). - -handle_view_req(#httpd{method='GET', - path_parts=[_, _, DDocName, _, VName, <<"_info">>]}=Req, - Db, _DDoc) -> +handle_view_req( + #httpd{ + method = 'GET', + path_parts = [_, _, DDocName, _, VName, <<"_info">>] + } = Req, + Db, + _DDoc +) -> DbName = couch_db:name(Db), - DDocId = <<"_design/", DDocName/binary >>, + DDocId = <<"_design/", DDocName/binary>>, {ok, Info} = couch_mrview:get_view_info(DbName, DDocId, VName), - FinalInfo = [{db_name, DbName}, - {ddoc, DDocId}, - {view, VName}] ++ Info, + FinalInfo = + [ + {db_name, DbName}, + {ddoc, DDocId}, + {view, VName} + ] ++ Info, chttpd:send_json(Req, 200, {FinalInfo}); -handle_view_req(#httpd{method='GET'}=Req, Db, DDoc) -> +handle_view_req(#httpd{method = 'GET'} = Req, Db, DDoc) -> [_, _, _, _, ViewName] = Req#httpd.path_parts, couch_stats:increment_counter([couchdb, httpd, view_reads]), design_doc_view(Req, Db, DDoc, ViewName, undefined); -handle_view_req(#httpd{method='POST'}=Req, Db, DDoc) -> +handle_view_req(#httpd{method = 'POST'} = Req, Db, DDoc) -> chttpd:validate_ctype(Req, "application/json"), [_, _, _, _, ViewName] = Req#httpd.path_parts, Props = chttpd:json_body_obj(Req), @@ -122,8 +133,7 @@ handle_view_req(#httpd{method='POST'}=Req, Db, DDoc) -> handle_view_req(Req, _Db, _DDoc) -> chttpd:send_method_not_allowed(Req, "GET,POST,HEAD"). - -handle_temp_view_req(#httpd{method='POST'}=Req, Db) -> +handle_temp_view_req(#httpd{method = 'POST'} = Req, Db) -> chttpd:validate_ctype(Req, "application/json"), ok = couch_db:check_is_admin(Db), {Body} = chttpd:json_body_obj(Req), @@ -134,19 +144,21 @@ handle_temp_view_req(#httpd{method='POST'}=Req, Db) -> handle_temp_view_req(Req, _Db) -> chttpd:send_method_not_allowed(Req, "POST"). - -handle_info_req(#httpd{method='GET'}=Req, Db, DDoc) -> +handle_info_req(#httpd{method = 'GET'} = Req, Db, DDoc) -> [_, _, Name, _] = Req#httpd.path_parts, {ok, Info} = couch_mrview:get_info(Db, DDoc), - chttpd:send_json(Req, 200, {[ - {name, Name}, - {view_index, {Info}} - ]}); + chttpd:send_json( + Req, + 200, + {[ + {name, Name}, + {view_index, {Info}} + ]} + ); handle_info_req(Req, _Db, _DDoc) -> chttpd:send_method_not_allowed(Req, "GET"). - -handle_compact_req(#httpd{method='POST'}=Req, Db, DDoc) -> +handle_compact_req(#httpd{method = 'POST'} = Req, Db, DDoc) -> chttpd:validate_ctype(Req, "application/json"), ok = couch_db:check_is_admin(Db), ok = couch_mrview:compact(Db, DDoc), @@ -154,8 +166,7 @@ handle_compact_req(#httpd{method='POST'}=Req, Db, DDoc) -> handle_compact_req(Req, _Db, _DDoc) -> chttpd:send_method_not_allowed(Req, "POST"). - -handle_cleanup_req(#httpd{method='POST'}=Req, Db) -> +handle_cleanup_req(#httpd{method = 'POST'} = Req, Db) -> chttpd:validate_ctype(Req, "application/json"), ok = couch_db:check_is_admin(Db), ok = couch_mrview:cleanup(Db), @@ -163,29 +174,30 @@ handle_cleanup_req(#httpd{method='POST'}=Req, Db) -> handle_cleanup_req(Req, _Db) -> chttpd:send_method_not_allowed(Req, "POST"). - all_docs_req(Req, Db, Keys) -> all_docs_req(Req, Db, Keys, undefined). all_docs_req(Req, Db, Keys, NS) -> case is_restricted(Db, NS) of - true -> - case (catch couch_db:check_is_admin(Db)) of - ok -> - do_all_docs_req(Req, Db, Keys, NS); - _ when NS == <<"_local">> -> - throw({forbidden, <<"Only admins can access _local_docs">>}); - _ -> - case is_public_fields_configured(Db) of - true -> + true -> + case (catch couch_db:check_is_admin(Db)) of + ok -> do_all_docs_req(Req, Db, Keys, NS); - false -> - throw({forbidden, <<"Only admins can access _all_docs", - " of system databases.">>}) - end - end; - false -> - do_all_docs_req(Req, Db, Keys, NS) + _ when NS == <<"_local">> -> + throw({forbidden, <<"Only admins can access _local_docs">>}); + _ -> + case is_public_fields_configured(Db) of + true -> + do_all_docs_req(Req, Db, Keys, NS); + false -> + throw( + {forbidden, + <<"Only admins can access _all_docs", " of system databases.">>} + ) + end + end; + false -> + do_all_docs_req(Req, Db, Keys, NS) end. is_restricted(_Db, <<"_local">>) -> @@ -196,18 +208,19 @@ is_restricted(Db, _) -> is_public_fields_configured(Db) -> DbName = ?b2l(couch_db:name(Db)), case config:get("couch_httpd_auth", "authentication_db", "_users") of - DbName -> - UsersDbPublic = chttpd_util:get_chttpd_auth_config( - "users_db_public", "false"), - PublicFields = chttpd_util:get_chttpd_auth_config("public_fields"), - case {UsersDbPublic, PublicFields} of - {"true", PublicFields} when PublicFields =/= undefined -> - true; - {_, _} -> + DbName -> + UsersDbPublic = chttpd_util:get_chttpd_auth_config( + "users_db_public", "false" + ), + PublicFields = chttpd_util:get_chttpd_auth_config("public_fields"), + case {UsersDbPublic, PublicFields} of + {"true", PublicFields} when PublicFields =/= undefined -> + true; + {_, _} -> + false + end; + _ -> false - end; - _ -> - false end. do_all_docs_req(Req, Db, Keys, NS) -> @@ -216,14 +229,16 @@ do_all_docs_req(Req, Db, Keys, NS) -> ETagFun = fun(Sig, Acc0) -> check_view_etag(Sig, Acc0, Req) end, - Args = Args1#mrargs{preflight_fun=ETagFun}, + Args = Args1#mrargs{preflight_fun = ETagFun}, {ok, Resp} = couch_httpd:etag_maybe(Req, fun() -> Max = chttpd:chunked_response_buffer_size(), - VAcc0 = #vacc{db=Db, req=Req, threshold=Max}, + VAcc0 = #vacc{db = Db, req = Req, threshold = Max}, DbName = ?b2l(couch_db:name(Db)), - UsersDbName = config:get("couch_httpd_auth", - "authentication_db", - "_users"), + UsersDbName = config:get( + "couch_httpd_auth", + "authentication_db", + "_users" + ), IsAdmin = is_admin(Db), Callback = get_view_callback(DbName, UsersDbName, IsAdmin), couch_mrview:query_all_docs(Db, Args, Callback, VAcc0) @@ -238,13 +253,12 @@ set_namespace(NS, #mrargs{extra = Extra} = Args) -> is_admin(Db) -> case catch couch_db:check_is_admin(Db) of - {unauthorized, _} -> - false; - ok -> - true + {unauthorized, _} -> + false; + ok -> + true end. - % admin users always get all fields get_view_callback(_, _, true) -> fun view_cb/2; @@ -256,16 +270,15 @@ get_view_callback(_DbName, _DbName, false) -> get_view_callback(_, _, _) -> fun view_cb/2. - design_doc_view(Req, Db, DDoc, ViewName, Keys) -> Args0 = parse_params(Req, Keys), ETagFun = fun(Sig, Acc0) -> check_view_etag(Sig, Acc0, Req) end, - Args = Args0#mrargs{preflight_fun=ETagFun}, + Args = Args0#mrargs{preflight_fun = ETagFun}, {ok, Resp} = couch_httpd:etag_maybe(Req, fun() -> Max = chttpd:chunked_response_buffer_size(), - VAcc0 = #vacc{db=Db, req=Req, threshold=Max}, + VAcc0 = #vacc{db = Db, req = Req, threshold = Max}, couch_mrview:query_view(Db, DDoc, ViewName, Args, fun view_cb/2, VAcc0) end), case is_record(Resp, vacc) of @@ -273,30 +286,36 @@ design_doc_view(Req, Db, DDoc, ViewName, Keys) -> _ -> {ok, Resp} end. - multi_query_view(Req, Db, DDoc, ViewName, Queries) -> Args0 = parse_params(Req, undefined), {ok, _, _, Args1} = couch_mrview_util:get_view(Db, DDoc, ViewName, Args0), - ArgQueries = lists:map(fun({Query}) -> - QueryArg = parse_params(Query, undefined, Args1), - couch_mrview_util:validate_args(Db, DDoc, QueryArg) - end, Queries), + ArgQueries = lists:map( + fun({Query}) -> + QueryArg = parse_params(Query, undefined, Args1), + couch_mrview_util:validate_args(Db, DDoc, QueryArg) + end, + Queries + ), {ok, Resp2} = couch_httpd:etag_maybe(Req, fun() -> Max = chttpd:chunked_response_buffer_size(), - VAcc0 = #vacc{db=Db, req=Req, prepend="\r\n", threshold=Max}, + VAcc0 = #vacc{db = Db, req = Req, prepend = "\r\n", threshold = Max}, %% TODO: proper calculation of etag Etag = [$", couch_uuids:new(), $"], Headers = [{"ETag", Etag}], FirstChunk = "{\"results\":[", {ok, Resp0} = chttpd:start_delayed_json_response(VAcc0#vacc.req, 200, Headers, FirstChunk), - VAcc1 = VAcc0#vacc{resp=Resp0}, - VAcc2 = lists:foldl(fun(Args, Acc0) -> - {ok, Acc1} = couch_mrview:query_view(Db, DDoc, ViewName, Args, fun view_cb/2, Acc0), - Acc1 - end, VAcc1, ArgQueries), + VAcc1 = VAcc0#vacc{resp = Resp0}, + VAcc2 = lists:foldl( + fun(Args, Acc0) -> + {ok, Acc1} = couch_mrview:query_view(Db, DDoc, ViewName, Args, fun view_cb/2, Acc0), + Acc1 + end, + VAcc1, + ArgQueries + ), {ok, Resp1} = chttpd:send_delayed_chunk(VAcc2#vacc.resp, "\r\n]}"), {ok, Resp2} = chttpd:end_delayed_json_response(Resp1), - {ok, VAcc2#vacc{resp=Resp2}} + {ok, VAcc2#vacc{resp = Resp2}} end), case is_record(Resp2, vacc) of true -> {ok, Resp2#vacc.resp}; @@ -304,94 +323,99 @@ multi_query_view(Req, Db, DDoc, ViewName, Queries) -> end. filtered_view_cb({row, Row0}, Acc) -> - Row1 = lists:map(fun({doc, null}) -> - {doc, null}; - ({doc, Body}) -> - Doc = couch_users_db:strip_non_public_fields(#doc{body=Body}), - {doc, Doc#doc.body}; - (KV) -> - KV - end, Row0), + Row1 = lists:map( + fun + ({doc, null}) -> + {doc, null}; + ({doc, Body}) -> + Doc = couch_users_db:strip_non_public_fields(#doc{body = Body}), + {doc, Doc#doc.body}; + (KV) -> + KV + end, + Row0 + ), view_cb({row, Row1}, Acc); filtered_view_cb(Obj, Acc) -> view_cb(Obj, Acc). - %% these clauses start (and possibly end) the response -view_cb({error, Reason}, #vacc{resp=undefined}=Acc) -> +view_cb({error, Reason}, #vacc{resp = undefined} = Acc) -> {ok, Resp} = chttpd:send_error(Acc#vacc.req, Reason), - {ok, Acc#vacc{resp=Resp}}; - -view_cb(complete, #vacc{resp=undefined}=Acc) -> + {ok, Acc#vacc{resp = Resp}}; +view_cb(complete, #vacc{resp = undefined} = Acc) -> % Nothing in view {ok, Resp} = chttpd:send_json(Acc#vacc.req, 200, {[{rows, []}]}), - {ok, Acc#vacc{resp=Resp}}; - -view_cb(Msg, #vacc{resp=undefined}=Acc) -> + {ok, Acc#vacc{resp = Resp}}; +view_cb(Msg, #vacc{resp = undefined} = Acc) -> %% Start response Headers = [], {ok, Resp} = chttpd:start_delayed_json_response(Acc#vacc.req, 200, Headers), - view_cb(Msg, Acc#vacc{resp=Resp, should_close=true}); - + view_cb(Msg, Acc#vacc{resp = Resp, should_close = true}); %% --------------------------------------------------- %% From here on down, the response has been started. -view_cb({error, Reason}, #vacc{resp=Resp}=Acc) -> +view_cb({error, Reason}, #vacc{resp = Resp} = Acc) -> {ok, Resp1} = chttpd:send_delayed_error(Resp, Reason), - {ok, Acc#vacc{resp=Resp1}}; - -view_cb(complete, #vacc{resp=Resp, buffer=Buf, threshold=Max}=Acc) -> + {ok, Acc#vacc{resp = Resp1}}; +view_cb(complete, #vacc{resp = Resp, buffer = Buf, threshold = Max} = Acc) -> % Finish view output and possibly end the response {ok, Resp1} = chttpd:close_delayed_json_object(Resp, Buf, "\r\n]}", Max), case Acc#vacc.should_close of true -> {ok, Resp2} = chttpd:end_delayed_json_response(Resp1), - {ok, Acc#vacc{resp=Resp2}}; + {ok, Acc#vacc{resp = Resp2}}; _ -> - {ok, Acc#vacc{resp=Resp1, meta_sent=false, row_sent=false, - prepend=",\r\n", buffer=[], bufsize=0}} + {ok, Acc#vacc{ + resp = Resp1, + meta_sent = false, + row_sent = false, + prepend = ",\r\n", + buffer = [], + bufsize = 0 + }} end; - -view_cb({meta, Meta}, #vacc{meta_sent=false, row_sent=false}=Acc) -> +view_cb({meta, Meta}, #vacc{meta_sent = false, row_sent = false} = Acc) -> % Sending metadata as we've not sent it or any row yet - Parts = case couch_util:get_value(total, Meta) of - undefined -> []; - Total -> [io_lib:format("\"total_rows\":~p", [Total])] - end ++ case couch_util:get_value(offset, Meta) of - undefined -> []; - Offset -> [io_lib:format("\"offset\":~p", [Offset])] - end ++ case couch_util:get_value(update_seq, Meta) of - undefined -> []; - null -> - ["\"update_seq\":null"]; - UpdateSeq when is_integer(UpdateSeq) -> - [io_lib:format("\"update_seq\":~B", [UpdateSeq])]; - UpdateSeq when is_binary(UpdateSeq) -> - [io_lib:format("\"update_seq\":\"~s\"", [UpdateSeq])] - end ++ ["\"rows\":["], + Parts = + case couch_util:get_value(total, Meta) of + undefined -> []; + Total -> [io_lib:format("\"total_rows\":~p", [Total])] + end ++ + case couch_util:get_value(offset, Meta) of + undefined -> []; + Offset -> [io_lib:format("\"offset\":~p", [Offset])] + end ++ + case couch_util:get_value(update_seq, Meta) of + undefined -> + []; + null -> + ["\"update_seq\":null"]; + UpdateSeq when is_integer(UpdateSeq) -> + [io_lib:format("\"update_seq\":~B", [UpdateSeq])]; + UpdateSeq when is_binary(UpdateSeq) -> + [io_lib:format("\"update_seq\":\"~s\"", [UpdateSeq])] + end ++ ["\"rows\":["], Chunk = [prepend_val(Acc), "{", string:join(Parts, ","), "\r\n"], {ok, AccOut} = maybe_flush_response(Acc, Chunk, iolist_size(Chunk)), - {ok, AccOut#vacc{prepend="", meta_sent=true}}; - -view_cb({meta, _Meta}, #vacc{}=Acc) -> + {ok, AccOut#vacc{prepend = "", meta_sent = true}}; +view_cb({meta, _Meta}, #vacc{} = Acc) -> %% ignore metadata {ok, Acc}; - -view_cb({row, Row}, #vacc{meta_sent=false}=Acc) -> +view_cb({row, Row}, #vacc{meta_sent = false} = Acc) -> %% sorted=false and row arrived before meta % Adding another row Chunk = [prepend_val(Acc), "{\"rows\":[\r\n", row_to_json(Row)], - maybe_flush_response(Acc#vacc{meta_sent=true, row_sent=true}, Chunk, iolist_size(Chunk)); - -view_cb({row, Row}, #vacc{meta_sent=true}=Acc) -> + maybe_flush_response(Acc#vacc{meta_sent = true, row_sent = true}, Chunk, iolist_size(Chunk)); +view_cb({row, Row}, #vacc{meta_sent = true} = Acc) -> % Adding another row Chunk = [prepend_val(Acc), row_to_json(Row)], - maybe_flush_response(Acc#vacc{row_sent=true}, Chunk, iolist_size(Chunk)). + maybe_flush_response(Acc#vacc{row_sent = true}, Chunk, iolist_size(Chunk)). - -maybe_flush_response(#vacc{bufsize=Size, threshold=Max} = Acc, Data, Len) - when Size > 0 andalso (Size + Len) > Max -> +maybe_flush_response(#vacc{bufsize = Size, threshold = Max} = Acc, Data, Len) when + Size > 0 andalso (Size + Len) > Max +-> #vacc{buffer = Buffer, resp = Resp} = Acc, {ok, R1} = chttpd:send_delayed_chunk(Resp, Buffer), {ok, Acc#vacc{prepend = ",\r\n", buffer = Data, bufsize = Len, resp = R1}}; @@ -404,7 +428,7 @@ maybe_flush_response(Acc0, Data, Len) -> }, {ok, Acc}. -prepend_val(#vacc{prepend=Prepend}) -> +prepend_val(#vacc{prepend = Prepend}) -> case Prepend of undefined -> ""; @@ -412,72 +436,83 @@ prepend_val(#vacc{prepend=Prepend}) -> Prepend end. - row_to_json(Row) -> Id = couch_util:get_value(id, Row), row_to_json(Id, Row). - row_to_json(error, Row) -> % Special case for _all_docs request with KEYS to % match prior behavior. Key = couch_util:get_value(key, Row), Val = couch_util:get_value(value, Row), Reason = couch_util:get_value(reason, Row), - ReasonProp = if Reason == undefined -> []; true -> - [{reason, Reason}] - end, + ReasonProp = + if + Reason == undefined -> []; + true -> [{reason, Reason}] + end, Obj = {[{key, Key}, {error, Val}] ++ ReasonProp}, ?JSON_ENCODE(Obj); row_to_json(Id0, Row) -> - Id = case Id0 of - undefined -> []; - Id0 -> [{id, Id0}] - end, + Id = + case Id0 of + undefined -> []; + Id0 -> [{id, Id0}] + end, Key = couch_util:get_value(key, Row, null), Val = couch_util:get_value(value, Row), - Doc = case couch_util:get_value(doc, Row) of - undefined -> []; - Doc0 -> [{doc, Doc0}] - end, + Doc = + case couch_util:get_value(doc, Row) of + undefined -> []; + Doc0 -> [{doc, Doc0}] + end, Obj = {Id ++ [{key, Key}, {value, Val}] ++ Doc}, ?JSON_ENCODE(Obj). - -parse_params(#httpd{}=Req, Keys) -> +parse_params(#httpd{} = Req, Keys) -> parse_params(chttpd:qs(Req), Keys); parse_params(Props, Keys) -> Args = #mrargs{}, parse_params(Props, Keys, Args). - parse_params(Props, Keys, Args) -> parse_params(Props, Keys, Args, []). -parse_params(Props, Keys, #mrargs{}=Args0, Options) -> +parse_params(Props, Keys, #mrargs{} = Args0, Options) -> IsDecoded = lists:member(decoded, Options), - Args1 = case lists:member(keep_group_level, Options) of - true -> - Args0; - _ -> - % group_level set to undefined to detect if explicitly set by user - Args0#mrargs{keys=Keys, group=undefined, group_level=undefined} - end, - lists:foldl(fun({K, V}, Acc) -> - parse_param(K, V, Acc, IsDecoded) - end, Args1, Props). - - -parse_body_and_query(#httpd{method='POST'} = Req, Keys) -> + Args1 = + case lists:member(keep_group_level, Options) of + true -> + Args0; + _ -> + % group_level set to undefined to detect if explicitly set by user + Args0#mrargs{keys = Keys, group = undefined, group_level = undefined} + end, + lists:foldl( + fun({K, V}, Acc) -> + parse_param(K, V, Acc, IsDecoded) + end, + Args1, + Props + ). + +parse_body_and_query(#httpd{method = 'POST'} = Req, Keys) -> Props = chttpd:json_body_obj(Req), parse_body_and_query(Req, Props, Keys); - parse_body_and_query(Req, Keys) -> - parse_params(chttpd:qs(Req), Keys, #mrargs{keys=Keys, group=undefined, - group_level=undefined}, [keep_group_level]). + parse_params( + chttpd:qs(Req), + Keys, + #mrargs{ + keys = Keys, + group = undefined, + group_level = undefined + }, + [keep_group_level] + ). parse_body_and_query(Req, {Props}, Keys) -> - Args = #mrargs{keys=Keys, group=undefined, group_level=undefined}, + Args = #mrargs{keys = Keys, group = undefined, group_level = undefined}, BodyArgs = parse_params(Props, Keys, Args, [decoded]), parse_params(chttpd:qs(Req), Keys, BodyArgs, [keep_group_level]). @@ -488,101 +523,101 @@ parse_param(Key, Val, Args, IsDecoded) -> "" -> Args; "reduce" -> - Args#mrargs{reduce=parse_boolean(Val)}; + Args#mrargs{reduce = parse_boolean(Val)}; "key" when IsDecoded -> - Args#mrargs{start_key=Val, end_key=Val}; + Args#mrargs{start_key = Val, end_key = Val}; "key" -> JsonKey = ?JSON_DECODE(Val), - Args#mrargs{start_key=JsonKey, end_key=JsonKey}; + Args#mrargs{start_key = JsonKey, end_key = JsonKey}; "keys" when IsDecoded -> - Args#mrargs{keys=Val}; + Args#mrargs{keys = Val}; "keys" -> - Args#mrargs{keys=?JSON_DECODE(Val)}; + Args#mrargs{keys = ?JSON_DECODE(Val)}; "startkey" when IsDecoded -> - Args#mrargs{start_key=Val}; + Args#mrargs{start_key = Val}; "start_key" when IsDecoded -> - Args#mrargs{start_key=Val}; + Args#mrargs{start_key = Val}; "startkey" -> - Args#mrargs{start_key=?JSON_DECODE(Val)}; + Args#mrargs{start_key = ?JSON_DECODE(Val)}; "start_key" -> - Args#mrargs{start_key=?JSON_DECODE(Val)}; + Args#mrargs{start_key = ?JSON_DECODE(Val)}; "startkey_docid" -> - Args#mrargs{start_key_docid=couch_util:to_binary(Val)}; + Args#mrargs{start_key_docid = couch_util:to_binary(Val)}; "start_key_doc_id" -> - Args#mrargs{start_key_docid=couch_util:to_binary(Val)}; + Args#mrargs{start_key_docid = couch_util:to_binary(Val)}; "endkey" when IsDecoded -> - Args#mrargs{end_key=Val}; + Args#mrargs{end_key = Val}; "end_key" when IsDecoded -> - Args#mrargs{end_key=Val}; + Args#mrargs{end_key = Val}; "endkey" -> - Args#mrargs{end_key=?JSON_DECODE(Val)}; + Args#mrargs{end_key = ?JSON_DECODE(Val)}; "end_key" -> - Args#mrargs{end_key=?JSON_DECODE(Val)}; + Args#mrargs{end_key = ?JSON_DECODE(Val)}; "endkey_docid" -> - Args#mrargs{end_key_docid=couch_util:to_binary(Val)}; + Args#mrargs{end_key_docid = couch_util:to_binary(Val)}; "end_key_doc_id" -> - Args#mrargs{end_key_docid=couch_util:to_binary(Val)}; + Args#mrargs{end_key_docid = couch_util:to_binary(Val)}; "limit" -> - Args#mrargs{limit=parse_pos_int(Val)}; + Args#mrargs{limit = parse_pos_int(Val)}; "stale" when Val == "ok" orelse Val == <<"ok">> -> - Args#mrargs{stable=true, update=false}; + Args#mrargs{stable = true, update = false}; "stale" when Val == "update_after" orelse Val == <<"update_after">> -> - Args#mrargs{stable=true, update=lazy}; + Args#mrargs{stable = true, update = lazy}; "stale" -> throw({query_parse_error, <<"Invalid value for `stale`.">>}); "stable" when Val == "true" orelse Val == <<"true">> orelse Val == true -> - Args#mrargs{stable=true}; + Args#mrargs{stable = true}; "stable" when Val == "false" orelse Val == <<"false">> orelse Val == false -> - Args#mrargs{stable=false}; + Args#mrargs{stable = false}; "stable" -> throw({query_parse_error, <<"Invalid value for `stable`.">>}); "update" when Val == "true" orelse Val == <<"true">> orelse Val == true -> - Args#mrargs{update=true}; + Args#mrargs{update = true}; "update" when Val == "false" orelse Val == <<"false">> orelse Val == false -> - Args#mrargs{update=false}; + Args#mrargs{update = false}; "update" when Val == "lazy" orelse Val == <<"lazy">> -> - Args#mrargs{update=lazy}; + Args#mrargs{update = lazy}; "update" -> throw({query_parse_error, <<"Invalid value for `update`.">>}); "descending" -> case parse_boolean(Val) of - true -> Args#mrargs{direction=rev}; - _ -> Args#mrargs{direction=fwd} + true -> Args#mrargs{direction = rev}; + _ -> Args#mrargs{direction = fwd} end; "skip" -> - Args#mrargs{skip=parse_pos_int(Val)}; + Args#mrargs{skip = parse_pos_int(Val)}; "group" -> - Args#mrargs{group=parse_boolean(Val)}; + Args#mrargs{group = parse_boolean(Val)}; "group_level" -> - Args#mrargs{group_level=parse_pos_int(Val)}; + Args#mrargs{group_level = parse_pos_int(Val)}; "inclusive_end" -> - Args#mrargs{inclusive_end=parse_boolean(Val)}; + Args#mrargs{inclusive_end = parse_boolean(Val)}; "include_docs" -> - Args#mrargs{include_docs=parse_boolean(Val)}; + Args#mrargs{include_docs = parse_boolean(Val)}; "attachments" -> case parse_boolean(Val) of - true -> - Opts = Args#mrargs.doc_options, - Args#mrargs{doc_options=[attachments|Opts]}; - false -> - Args + true -> + Opts = Args#mrargs.doc_options, + Args#mrargs{doc_options = [attachments | Opts]}; + false -> + Args end; "att_encoding_info" -> case parse_boolean(Val) of - true -> - Opts = Args#mrargs.doc_options, - Args#mrargs{doc_options=[att_encoding_info|Opts]}; - false -> - Args + true -> + Opts = Args#mrargs.doc_options, + Args#mrargs{doc_options = [att_encoding_info | Opts]}; + false -> + Args end; "update_seq" -> - Args#mrargs{update_seq=parse_boolean(Val)}; + Args#mrargs{update_seq = parse_boolean(Val)}; "conflicts" -> - Args#mrargs{conflicts=parse_boolean(Val)}; + Args#mrargs{conflicts = parse_boolean(Val)}; "callback" -> - Args#mrargs{callback=couch_util:to_binary(Val)}; + Args#mrargs{callback = couch_util:to_binary(Val)}; "sorted" -> - Args#mrargs{sorted=parse_boolean(Val)}; + Args#mrargs{sorted = parse_boolean(Val)}; "partition" -> Partition = couch_util:to_binary(Val), couch_partition:validate_partition(Partition), @@ -590,52 +625,50 @@ parse_param(Key, Val, Args, IsDecoded) -> _ -> BKey = couch_util:to_binary(Key), BVal = couch_util:to_binary(Val), - Args#mrargs{extra=[{BKey, BVal} | Args#mrargs.extra]} + Args#mrargs{extra = [{BKey, BVal} | Args#mrargs.extra]} end. - parse_boolean(true) -> true; parse_boolean(false) -> false; - parse_boolean(Val) when is_binary(Val) -> parse_boolean(?b2l(Val)); - parse_boolean(Val) -> case string:to_lower(Val) of - "true" -> true; - "false" -> false; - _ -> - Msg = io_lib:format("Invalid boolean parameter: ~p", [Val]), - throw({query_parse_error, ?l2b(Msg)}) + "true" -> + true; + "false" -> + false; + _ -> + Msg = io_lib:format("Invalid boolean parameter: ~p", [Val]), + throw({query_parse_error, ?l2b(Msg)}) end. parse_int(Val) when is_integer(Val) -> Val; parse_int(Val) -> case (catch list_to_integer(Val)) of - IntVal when is_integer(IntVal) -> - IntVal; - _ -> - Msg = io_lib:format("Invalid value for integer: ~p", [Val]), - throw({query_parse_error, ?l2b(Msg)}) + IntVal when is_integer(IntVal) -> + IntVal; + _ -> + Msg = io_lib:format("Invalid value for integer: ~p", [Val]), + throw({query_parse_error, ?l2b(Msg)}) end. parse_pos_int(Val) -> case parse_int(Val) of - IntVal when IntVal >= 0 -> - IntVal; - _ -> - Fmt = "Invalid value for positive integer: ~p", - Msg = io_lib:format(Fmt, [Val]), - throw({query_parse_error, ?l2b(Msg)}) + IntVal when IntVal >= 0 -> + IntVal; + _ -> + Fmt = "Invalid value for positive integer: ~p", + Msg = io_lib:format(Fmt, [Val]), + throw({query_parse_error, ?l2b(Msg)}) end. - check_view_etag(Sig, Acc0, Req) -> ETag = chttpd:make_etag(Sig), case chttpd:etag_match(Req, ETag) of true -> throw({etag_match, ETag}); - false -> {ok, Acc0#vacc{etag=ETag}} + false -> {ok, Acc0#vacc{etag = ETag}} end. diff --git a/src/couch_mrview/src/couch_mrview_index.erl b/src/couch_mrview/src/couch_mrview_index.erl index 68f1d2322..a024d35c8 100644 --- a/src/couch_mrview/src/couch_mrview_index.erl +++ b/src/couch_mrview/src/couch_mrview_index.erl @@ -12,7 +12,6 @@ -module(couch_mrview_index). - -export([get/2]). -export([init/2, open/2, close/1, reset/1, delete/1, shutdown/1]). -export([start_update/4, purge/4, process_doc/3, finish_update/1, commit/1]). @@ -24,7 +23,6 @@ -include_lib("couch/include/couch_db.hrl"). -include_lib("couch_mrview/include/couch_mrview.hrl"). - get(db_name, #mrst{db_name = DbName}) -> DbName; get(idx_name, #mrst{idx_name = IdxName}) -> @@ -39,9 +37,18 @@ get(update_options, #mrst{design_opts = Opts}) -> IncDesign = couch_util:get_value(<<"include_design">>, Opts, false), LocalSeq = couch_util:get_value(<<"local_seq">>, Opts, false), Partitioned = couch_util:get_value(<<"partitioned">>, Opts, false), - if IncDesign -> [include_design]; true -> [] end - ++ if LocalSeq -> [local_seq]; true -> [] end - ++ if Partitioned -> [partitioned]; true -> [] end; + if + IncDesign -> [include_design]; + true -> [] + end ++ + if + LocalSeq -> [local_seq]; + true -> [] + end ++ + if + Partitioned -> [partitioned]; + true -> [] + end; get(fd, #mrst{fd = Fd}) -> Fd; get(language, #mrst{language = Language}) -> @@ -69,11 +76,12 @@ get(info, State) -> {ok, [ {signature, list_to_binary(couch_index_util:hexsig(Sig))}, {language, Lang}, - {sizes, {[ - {file, FileSize}, - {active, ActiveSize}, - {external, ExternalSize} - ]}}, + {sizes, + {[ + {file, FileSize}, + {active, ActiveSize}, + {external, ExternalSize} + ]}}, {update_seq, UpdateSeq}, {purge_seq, PurgeSeq}, {update_options, UpdateOptions} @@ -81,16 +89,14 @@ get(info, State) -> get(Other, _) -> throw({unknown_index_property, Other}). - init(Db, DDoc) -> {ok, State} = couch_mrview_util:ddoc_to_mrst(couch_db:name(Db), DDoc), {ok, set_partitioned(Db, State)}. - open(Db, State0) -> #mrst{ - db_name=DbName, - sig=Sig + db_name = DbName, + sig = Sig } = State = set_partitioned(Db, State0), IndexFName = couch_mrview_util:index_file(DbName, Sig), @@ -128,14 +134,18 @@ open(Db, State0) -> ensure_local_purge_doc(Db, NewSt), {ok, NewSt}; {ok, {WrongSig, _}} -> - couch_log:error("~s has the wrong signature: expected: ~p but got ~p", - [IndexFName, Sig, WrongSig]), + couch_log:error( + "~s has the wrong signature: expected: ~p but got ~p", + [IndexFName, Sig, WrongSig] + ), NewSt = couch_mrview_util:reset_index(Db, Fd, State), ensure_local_purge_doc(Db, NewSt), {ok, NewSt}; {ok, Else} -> - couch_log:error("~s has a bad header: got ~p", - [IndexFName, Else]), + couch_log:error( + "~s has a bad header: got ~p", + [IndexFName, Else] + ), NewSt = couch_mrview_util:reset_index(Db, Fd, State), ensure_local_purge_doc(Db, NewSt), {ok, NewSt}; @@ -145,17 +155,17 @@ open(Db, State0) -> {ok, NewSt} end; {error, Reason} = Error -> - couch_log:error("Failed to open view file '~s': ~s", - [IndexFName, file:format_error(Reason)]), + couch_log:error( + "Failed to open view file '~s': ~s", + [IndexFName, file:format_error(Reason)] + ), Error end. - close(State) -> erlang:demonitor(State#mrst.fd_monitor, [flush]), couch_file:close(State#mrst.fd). - % This called after ddoc_updated event occurrs, and % before we shutdown couch_index process. % We unlink couch_index from corresponding couch_file and demonitor it. @@ -167,19 +177,16 @@ shutdown(State) -> erlang:demonitor(State#mrst.fd_monitor, [flush]), unlink(State#mrst.fd). - -delete(#mrst{db_name=DbName, sig=Sig}=State) -> +delete(#mrst{db_name = DbName, sig = Sig} = State) -> couch_file:close(State#mrst.fd), catch couch_mrview_util:delete_files(DbName, Sig). - reset(State) -> couch_util:with_db(State#mrst.db_name, fun(Db) -> NewState = couch_mrview_util:reset_index(Db, State#mrst.fd, State), {ok, NewState} end). - start_update(PartialDest, State, NumChanges, NumChangesDone) -> couch_mrview_updater:start_update( PartialDest, @@ -188,94 +195,93 @@ start_update(PartialDest, State, NumChanges, NumChangesDone) -> NumChangesDone ). - purge(Db, PurgeSeq, PurgedIdRevs, State) -> couch_mrview_updater:purge(Db, PurgeSeq, PurgedIdRevs, State). - process_doc(Doc, Seq, State) -> couch_mrview_updater:process_doc(Doc, Seq, State). - finish_update(State) -> couch_mrview_updater:finish_update(State). - commit(State) -> Header = {State#mrst.sig, couch_mrview_util:make_header(State)}, couch_file:write_header(State#mrst.fd, Header). - compact(Db, State, Opts) -> couch_mrview_compactor:compact(Db, State, Opts). - swap_compacted(OldState, NewState) -> couch_mrview_compactor:swap_compacted(OldState, NewState). - remove_compacted(State) -> couch_mrview_compactor:remove_compacted(State). - index_file_exists(State) -> #mrst{ - db_name=DbName, - sig=Sig + db_name = DbName, + sig = Sig } = State, IndexFName = couch_mrview_util:index_file(DbName, Sig), filelib:is_file(IndexFName). - verify_index_exists(DbName, Props) -> try Type = couch_util:get_value(<<"type">>, Props), - if Type =/= <<"mrview">> -> false; true -> - DDocId = couch_util:get_value(<<"ddoc_id">>, Props), - couch_util:with_db(DbName, fun(Db) -> - case couch_db:get_design_doc(Db, DDocId) of - {ok, #doc{} = DDoc} -> - {ok, IdxState} = couch_mrview_util:ddoc_to_mrst( - DbName, DDoc), - IdxSig = IdxState#mrst.sig, - SigInLocal = couch_util:get_value( - <<"signature">>, Props), - couch_index_util:hexsig(IdxSig) == SigInLocal; - {not_found, _} -> - false - end - end) + if + Type =/= <<"mrview">> -> + false; + true -> + DDocId = couch_util:get_value(<<"ddoc_id">>, Props), + couch_util:with_db(DbName, fun(Db) -> + case couch_db:get_design_doc(Db, DDocId) of + {ok, #doc{} = DDoc} -> + {ok, IdxState} = couch_mrview_util:ddoc_to_mrst( + DbName, DDoc + ), + IdxSig = IdxState#mrst.sig, + SigInLocal = couch_util:get_value( + <<"signature">>, Props + ), + couch_index_util:hexsig(IdxSig) == SigInLocal; + {not_found, _} -> + false + end + end) end - catch _:_ -> - false + catch + _:_ -> + false end. - set_partitioned(Db, State) -> #mrst{ design_opts = DesignOpts } = State, DbPartitioned = couch_db:is_partitioned(Db), ViewPartitioned = couch_util:get_value( - <<"partitioned">>, DesignOpts, DbPartitioned), + <<"partitioned">>, DesignOpts, DbPartitioned + ), IsPartitioned = DbPartitioned andalso ViewPartitioned, State#mrst{partitioned = IsPartitioned}. - ensure_local_purge_docs(DbName, DDocs) -> couch_util:with_db(DbName, fun(Db) -> - lists:foreach(fun(DDoc) -> - try couch_mrview_util:ddoc_to_mrst(DbName, DDoc) of - {ok, MRSt} -> - ensure_local_purge_doc(Db, MRSt) - catch _:_ -> - ok - end - end, DDocs) + lists:foreach( + fun(DDoc) -> + try couch_mrview_util:ddoc_to_mrst(DbName, DDoc) of + {ok, MRSt} -> + ensure_local_purge_doc(Db, MRSt) + catch + _:_ -> + ok + end + end, + DDocs + ) end). - -ensure_local_purge_doc(Db, #mrst{}=State) -> +ensure_local_purge_doc(Db, #mrst{} = State) -> Sig = couch_index_util:hexsig(get(signature, State)), DocId = couch_mrview_util:get_local_purge_doc_id(Sig), case couch_db:open_doc(Db, DocId, []) of @@ -285,33 +291,33 @@ ensure_local_purge_doc(Db, #mrst{}=State) -> ok end. - create_local_purge_doc(Db, State) -> PurgeSeq = couch_db:get_purge_seq(Db), update_local_purge_doc(Db, State, PurgeSeq). - update_local_purge_doc(Db, State) -> update_local_purge_doc(Db, State, get(purge_seq, State)). - update_local_purge_doc(Db, State, PSeq) -> Sig = couch_index_util:hexsig(State#mrst.sig), DocId = couch_mrview_util:get_local_purge_doc_id(Sig), {Mega, Secs, _} = os:timestamp(), NowSecs = Mega * 1000000 + Secs, - BaseDoc = couch_doc:from_json_obj({[ - {<<"_id">>, DocId}, - {<<"type">>, <<"mrview">>}, - {<<"purge_seq">>, PSeq}, - {<<"updated_on">>, NowSecs}, - {<<"ddoc_id">>, get(idx_name, State)}, - {<<"signature">>, Sig} - ]}), - Doc = case couch_db:open_doc(Db, DocId, []) of - {ok, #doc{revs = Revs}} -> - BaseDoc#doc{revs = Revs}; - {not_found, _} -> - BaseDoc - end, + BaseDoc = couch_doc:from_json_obj( + {[ + {<<"_id">>, DocId}, + {<<"type">>, <<"mrview">>}, + {<<"purge_seq">>, PSeq}, + {<<"updated_on">>, NowSecs}, + {<<"ddoc_id">>, get(idx_name, State)}, + {<<"signature">>, Sig} + ]} + ), + Doc = + case couch_db:open_doc(Db, DocId, []) of + {ok, #doc{revs = Revs}} -> + BaseDoc#doc{revs = Revs}; + {not_found, _} -> + BaseDoc + end, couch_db:update_doc(Db, Doc, []). diff --git a/src/couch_mrview/src/couch_mrview_show.erl b/src/couch_mrview/src/couch_mrview_show.erl index 0268b706e..3e95be9cc 100644 --- a/src/couch_mrview/src/couch_mrview_show.erl +++ b/src/couch_mrview/src/couch_mrview_show.erl @@ -33,23 +33,28 @@ maybe_open_doc(Db, DocId) -> {not_found, _} -> nil end. -handle_doc_show_req(#httpd{ - path_parts=[_, _, _, _, ShowName, DocId] - }=Req, Db, DDoc) -> - +handle_doc_show_req( + #httpd{ + path_parts = [_, _, _, _, ShowName, DocId] + } = Req, + Db, + DDoc +) -> % open the doc Doc = maybe_open_doc(Db, DocId), % we don't handle revs here b/c they are an internal api % returns 404 if there is no doc with DocId handle_doc_show(Req, Db, DDoc, ShowName, Doc, DocId); - -handle_doc_show_req(#httpd{ - path_parts=[_, _, _, _, ShowName, DocId|Rest] - }=Req, Db, DDoc) -> - - DocParts = [DocId|Rest], - DocId1 = ?l2b(string:join([?b2l(P)|| P <- DocParts], "/")), +handle_doc_show_req( + #httpd{ + path_parts = [_, _, _, _, ShowName, DocId | Rest] + } = Req, + Db, + DDoc +) -> + DocParts = [DocId | Rest], + DocId1 = ?l2b(string:join([?b2l(P) || P <- DocParts], "/")), % open the doc Doc = maybe_open_doc(Db, DocId1), @@ -57,13 +62,15 @@ handle_doc_show_req(#httpd{ % we don't handle revs here b/c they are an internal api % pass 404 docs to the show function handle_doc_show(Req, Db, DDoc, ShowName, Doc, DocId1); - -handle_doc_show_req(#httpd{ - path_parts=[_, _, _, _, ShowName] - }=Req, Db, DDoc) -> +handle_doc_show_req( + #httpd{ + path_parts = [_, _, _, _, ShowName] + } = Req, + Db, + DDoc +) -> % with no docid the doc is nil handle_doc_show(Req, Db, DDoc, ShowName, nil); - handle_doc_show_req(Req, _Db, _DDoc) -> chttpd:send_error(Req, 404, <<"show_error">>, <<"Invalid path.">>). @@ -77,21 +84,29 @@ handle_doc_show(Req, Db, DDoc, ShowName, Doc, DocId) -> JsonReq = chttpd_external:json_req_obj(Req, Db, DocId), JsonDoc = couch_query_servers:json_doc(Doc), [<<"resp">>, ExternalResp] = - couch_query_servers:ddoc_prompt(DDoc, [<<"shows">>, ShowName], - [JsonDoc, JsonReq]), + couch_query_servers:ddoc_prompt( + DDoc, + [<<"shows">>, ShowName], + [JsonDoc, JsonReq] + ), JsonResp = apply_etag(ExternalResp, CurrentEtag), chttpd_external:send_external_response(Req, JsonResp) end). - -show_etag(#httpd{user_ctx=UserCtx}=Req, Doc, DDoc, More) -> +show_etag(#httpd{user_ctx = UserCtx} = Req, Doc, DDoc, More) -> Accept = chttpd:header_value(Req, "Accept"), - DocPart = case Doc of - nil -> nil; - Doc -> chttpd:doc_etag(Doc) - end, - chttpd:make_etag({chttpd:doc_etag(DDoc), DocPart, Accept, - {UserCtx#user_ctx.name, UserCtx#user_ctx.roles}, More}). + DocPart = + case Doc of + nil -> nil; + Doc -> chttpd:doc_etag(Doc) + end, + chttpd:make_etag({ + chttpd:doc_etag(DDoc), + DocPart, + Accept, + {UserCtx#user_ctx.name, UserCtx#user_ctx.roles}, + More + }). % updates a doc based on a request % handle_doc_update_req(#httpd{method = 'GET'}=Req, _Db, _DDoc) -> @@ -101,20 +116,25 @@ show_etag(#httpd{user_ctx=UserCtx}=Req, Doc, DDoc, More) -> % This call is creating a new doc using an _update function to % modify the provided request body. % /db/_design/foo/_update/bar -handle_doc_update_req(#httpd{ - path_parts=[_, _, _, _, UpdateName] - }=Req, Db, DDoc) -> +handle_doc_update_req( + #httpd{ + path_parts = [_, _, _, _, UpdateName] + } = Req, + Db, + DDoc +) -> send_doc_update_response(Req, Db, DDoc, UpdateName, nil, null); - % /db/_design/foo/_update/bar/docid -handle_doc_update_req(#httpd{ - path_parts=[_, _, _, _, UpdateName | DocIdParts] - }=Req, Db, DDoc) -> +handle_doc_update_req( + #httpd{ + path_parts = [_, _, _, _, UpdateName | DocIdParts] + } = Req, + Db, + DDoc +) -> DocId = ?l2b(string:join([?b2l(P) || P <- DocIdParts], "/")), Doc = maybe_open_doc(Db, DocId), send_doc_update_response(Req, Db, DDoc, UpdateName, Doc, DocId); - - handle_doc_update_req(Req, _Db, _DDoc) -> chttpd:send_error(Req, 404, <<"update_error">>, <<"Invalid path.">>). @@ -123,32 +143,36 @@ send_doc_update_response(Req, Db, DDoc, UpdateName, Doc, DocId) -> JsonDoc = couch_query_servers:json_doc(Doc), Cmd = [<<"updates">>, UpdateName], UpdateResp = couch_query_servers:ddoc_prompt(DDoc, Cmd, [JsonDoc, JsonReq]), - JsonResp = case UpdateResp of - [<<"up">>, {NewJsonDoc}, {JsonResp0}] -> - case chttpd:header_value( - Req, "X-Couch-Full-Commit", "false") of - "true" -> - Options = [full_commit, {user_ctx, Req#httpd.user_ctx}]; - _ -> - Options = [{user_ctx, Req#httpd.user_ctx}] - end, - NewDoc = couch_db:doc_from_json_obj_validate(Db, {NewJsonDoc}), - {ok, NewRev} = couch_db:update_doc(Db, NewDoc, Options), - NewRevStr = couch_doc:rev_to_str(NewRev), - {JsonResp1} = apply_headers(JsonResp0, [ - {<<"X-Couch-Update-NewRev">>, NewRevStr}, - {<<"X-Couch-Id">>, couch_util:url_encode(NewDoc#doc.id)} - ]), - {[{<<"code">>, 201} | JsonResp1]}; - [<<"up">>, _Other, {JsonResp0}] -> - {[{<<"code">>, 200} | JsonResp0]} - end, + JsonResp = + case UpdateResp of + [<<"up">>, {NewJsonDoc}, {JsonResp0}] -> + case + chttpd:header_value( + Req, "X-Couch-Full-Commit", "false" + ) + of + "true" -> + Options = [full_commit, {user_ctx, Req#httpd.user_ctx}]; + _ -> + Options = [{user_ctx, Req#httpd.user_ctx}] + end, + NewDoc = couch_db:doc_from_json_obj_validate(Db, {NewJsonDoc}), + {ok, NewRev} = couch_db:update_doc(Db, NewDoc, Options), + NewRevStr = couch_doc:rev_to_str(NewRev), + {JsonResp1} = apply_headers(JsonResp0, [ + {<<"X-Couch-Update-NewRev">>, NewRevStr}, + {<<"X-Couch-Id">>, couch_util:url_encode(NewDoc#doc.id)} + ]), + {[{<<"code">>, 201} | JsonResp1]}; + [<<"up">>, _Other, {JsonResp0}] -> + {[{<<"code">>, 200} | JsonResp0]} + end, % todo set location field chttpd_external:send_external_response(Req, JsonResp). - -handle_view_list_req(#httpd{method=Method}=Req, Db, DDoc) - when Method =:= 'GET' orelse Method =:= 'OPTIONS' -> +handle_view_list_req(#httpd{method = Method} = Req, Db, DDoc) when + Method =:= 'GET' orelse Method =:= 'OPTIONS' +-> case Req#httpd.path_parts of [_, _, _DName, _, LName, VName] -> % Same design doc for view and list @@ -161,7 +185,7 @@ handle_view_list_req(#httpd{method=Method}=Req, Db, DDoc) _ -> chttpd:send_error(Req, 404, <<"list_error">>, <<"Bad path.">>) end; -handle_view_list_req(#httpd{method='POST'}=Req, Db, DDoc) -> +handle_view_list_req(#httpd{method = 'POST'} = Req, Db, DDoc) -> chttpd:validate_ctype(Req, "application/json"), {Props} = chttpd:json_body_obj(Req), Keys = proplists:get_value(<<"keys">>, Props), @@ -179,7 +203,6 @@ handle_view_list_req(#httpd{method='POST'}=Req, Db, DDoc) -> handle_view_list_req(Req, _Db, _DDoc) -> chttpd:send_method_not_allowed(Req, "GET,POST,HEAD"). - handle_view_list(Req, Db, DDoc, LName, VDDoc, VName, Keys) -> Args0 = couch_mrview_http:parse_body_and_query(Req, Keys), ETagFun = fun(BaseSig, Acc0) -> @@ -191,67 +214,73 @@ handle_view_list(Req, Db, DDoc, LName, VDDoc, VName, Keys) -> ETag = chttpd:make_etag({BaseSig, Parts}), case chttpd:etag_match(Req, ETag) of true -> throw({etag_match, ETag}); - false -> {ok, Acc0#lacc{etag=ETag}} + false -> {ok, Acc0#lacc{etag = ETag}} end end, - Args = Args0#mrargs{preflight_fun=ETagFun}, + Args = Args0#mrargs{preflight_fun = ETagFun}, couch_httpd:etag_maybe(Req, fun() -> couch_query_servers:with_ddoc_proc(DDoc, fun(QServer) -> - Acc = #lacc{db=Db, req=Req, qserver=QServer, lname=LName}, + Acc = #lacc{db = Db, req = Req, qserver = QServer, lname = LName}, case VName of - <<"_all_docs">> -> - couch_mrview:query_all_docs(Db, Args, fun list_cb/2, Acc); - _ -> - couch_mrview:query_view(Db, VDDoc, VName, Args, fun list_cb/2, Acc) + <<"_all_docs">> -> + couch_mrview:query_all_docs(Db, Args, fun list_cb/2, Acc); + _ -> + couch_mrview:query_view(Db, VDDoc, VName, Args, fun list_cb/2, Acc) end end) end). - -list_cb({meta, Meta}, #lacc{code=undefined} = Acc) -> - MetaProps = case couch_util:get_value(total, Meta) of - undefined -> []; - Total -> [{total_rows, Total}] - end ++ case couch_util:get_value(offset, Meta) of - undefined -> []; - Offset -> [{offset, Offset}] - end ++ case couch_util:get_value(update_seq, Meta) of - undefined -> []; - UpdateSeq -> [{update_seq, UpdateSeq}] - end, +list_cb({meta, Meta}, #lacc{code = undefined} = Acc) -> + MetaProps = + case couch_util:get_value(total, Meta) of + undefined -> []; + Total -> [{total_rows, Total}] + end ++ + case couch_util:get_value(offset, Meta) of + undefined -> []; + Offset -> [{offset, Offset}] + end ++ + case couch_util:get_value(update_seq, Meta) of + undefined -> []; + UpdateSeq -> [{update_seq, UpdateSeq}] + end, start_list_resp({MetaProps}, Acc); -list_cb({row, Row}, #lacc{code=undefined} = Acc) -> +list_cb({row, Row}, #lacc{code = undefined} = Acc) -> {ok, NewAcc} = start_list_resp({[]}, Acc), send_list_row(Row, NewAcc); list_cb({row, Row}, Acc) -> send_list_row(Row, Acc); list_cb(complete, Acc) -> #lacc{qserver = {Proc, _}, req = Req, resp = Resp0} = Acc, - if Resp0 =:= nil -> - {ok, #lacc{resp = Resp}} = start_list_resp({[]}, Acc); - true -> - Resp = Resp0 + if + Resp0 =:= nil -> + {ok, #lacc{resp = Resp}} = start_list_resp({[]}, Acc); + true -> + Resp = Resp0 end, case couch_query_servers:proc_prompt(Proc, [<<"list_end">>]) of [<<"end">>, Data, Headers] -> - Acc2 = fixup_headers(Headers, Acc#lacc{resp=Resp}), + Acc2 = fixup_headers(Headers, Acc#lacc{resp = Resp}), #lacc{resp = Resp2} = send_non_empty_chunk(Acc2, Data); [<<"end">>, Data] -> - #lacc{resp = Resp2} = send_non_empty_chunk(Acc#lacc{resp=Resp}, Data) + #lacc{resp = Resp2} = send_non_empty_chunk(Acc#lacc{resp = Resp}, Data) end, last_chunk(Req, Resp2), {ok, Resp2}. start_list_resp(Head, Acc) -> - #lacc{db=Db, req=Req, qserver=QServer, lname=LName} = Acc, + #lacc{db = Db, req = Req, qserver = QServer, lname = LName} = Acc, JsonReq = json_req_obj(Req, Db), - [<<"start">>,Chunk,JsonResp] = couch_query_servers:ddoc_proc_prompt(QServer, - [<<"lists">>, LName], [Head, JsonReq]), + [<<"start">>, Chunk, JsonResp] = couch_query_servers:ddoc_proc_prompt( + QServer, + [<<"lists">>, LName], + [Head, JsonReq] + ), Acc2 = send_non_empty_chunk(fixup_headers(JsonResp, Acc), Chunk), {ok, Acc2}. -fixup_headers(Headers, #lacc{etag=ETag} = Acc) -> +fixup_headers(Headers, #lacc{etag = ETag} = Acc) -> Headers2 = apply_etag(Headers, ETag), #extern_resp_args{ code = Code, @@ -260,61 +289,66 @@ fixup_headers(Headers, #lacc{etag=ETag} = Acc) -> } = chttpd_external:parse_external_response(Headers2), Headers3 = chttpd_external:default_or_content_type(CType, ExtHeaders), Headers4 = chttpd_util:maybe_add_csp_header("showlist", Headers3, "sandbox"), - Acc#lacc{code=Code, headers=Headers4}. + Acc#lacc{code = Code, headers = Headers4}. send_list_row(Row, #lacc{qserver = {Proc, _}, req = Req, resp = Resp} = Acc) -> - RowObj = case couch_util:get_value(id, Row) of - undefined -> []; - Id -> [{id, Id}] - end ++ case couch_util:get_value(key, Row) of - undefined -> []; - Key -> [{key, Key}] - end ++ case couch_util:get_value(value, Row) of - undefined -> []; - Val -> [{value, Val}] - end ++ case couch_util:get_value(doc, Row) of - undefined -> []; - Doc -> [{doc, Doc}] - end, + RowObj = + case couch_util:get_value(id, Row) of + undefined -> []; + Id -> [{id, Id}] + end ++ + case couch_util:get_value(key, Row) of + undefined -> []; + Key -> [{key, Key}] + end ++ + case couch_util:get_value(value, Row) of + undefined -> []; + Val -> [{value, Val}] + end ++ + case couch_util:get_value(doc, Row) of + undefined -> []; + Doc -> [{doc, Doc}] + end, try couch_query_servers:proc_prompt(Proc, [<<"list_row">>, {RowObj}]) of - [<<"chunks">>, Chunk, Headers] -> - Acc2 = send_non_empty_chunk(fixup_headers(Headers, Acc), Chunk), - {ok, Acc2}; - [<<"chunks">>, Chunk] -> - Acc2 = send_non_empty_chunk(Acc, Chunk), - {ok, Acc2}; - [<<"end">>, Chunk, Headers] -> - #lacc{resp = Resp2} = send_non_empty_chunk(fixup_headers(Headers, Acc), Chunk), - {ok, Resp3} = last_chunk(Req, Resp2), - {stop, Resp3}; - [<<"end">>, Chunk] -> - #lacc{resp = Resp2} = send_non_empty_chunk(Acc, Chunk), - {ok, Resp3} = last_chunk(Req, Resp2), - {stop, Resp3} - catch Error -> - {ok, Resp2} = case Resp of - undefined -> - {Code, _, _} = chttpd:error_info(Error), - #lacc{req=Req, headers=Headers} = Acc, - chttpd:start_chunked_response(Req, Code, Headers); - _ -> - {ok, Resp} - end, - {ok, Resp3} = chttpd:send_chunked_error(Resp2, Error), - {stop, Resp3} + [<<"chunks">>, Chunk, Headers] -> + Acc2 = send_non_empty_chunk(fixup_headers(Headers, Acc), Chunk), + {ok, Acc2}; + [<<"chunks">>, Chunk] -> + Acc2 = send_non_empty_chunk(Acc, Chunk), + {ok, Acc2}; + [<<"end">>, Chunk, Headers] -> + #lacc{resp = Resp2} = send_non_empty_chunk(fixup_headers(Headers, Acc), Chunk), + {ok, Resp3} = last_chunk(Req, Resp2), + {stop, Resp3}; + [<<"end">>, Chunk] -> + #lacc{resp = Resp2} = send_non_empty_chunk(Acc, Chunk), + {ok, Resp3} = last_chunk(Req, Resp2), + {stop, Resp3} + catch + Error -> + {ok, Resp2} = + case Resp of + undefined -> + {Code, _, _} = chttpd:error_info(Error), + #lacc{req = Req, headers = Headers} = Acc, + chttpd:start_chunked_response(Req, Code, Headers); + _ -> + {ok, Resp} + end, + {ok, Resp3} = chttpd:send_chunked_error(Resp2, Error), + {stop, Resp3} end. send_non_empty_chunk(Acc, []) -> Acc; -send_non_empty_chunk(#lacc{resp=undefined} = Acc, Chunk) -> - #lacc{req=Req, code=Code, headers=Headers} = Acc, +send_non_empty_chunk(#lacc{resp = undefined} = Acc, Chunk) -> + #lacc{req = Req, code = Code, headers = Headers} = Acc, {ok, Resp} = chttpd:start_chunked_response(Req, Code, Headers), send_non_empty_chunk(Acc#lacc{resp = Resp}, Chunk); -send_non_empty_chunk(#lacc{resp=Resp} = Acc, Chunk) -> +send_non_empty_chunk(#lacc{resp = Resp} = Acc, Chunk) -> chttpd:send_chunk(Resp, Chunk), Acc. - apply_etag(JsonResp, undefined) -> JsonResp; apply_etag({ExternalResponse}, CurrentEtag) -> @@ -332,7 +366,7 @@ apply_headers(JsonResp, []) -> apply_headers(JsonResp, NewHeaders) -> case couch_util:get_value(<<"headers">>, JsonResp) of undefined -> - {[{<<"headers">>, {NewHeaders}}| JsonResp]}; + {[{<<"headers">>, {NewHeaders}} | JsonResp]}; JsonHeaders -> Headers = apply_headers1(JsonHeaders, NewHeaders), NewKV = {<<"headers">>, Headers}, @@ -344,13 +378,11 @@ apply_headers1(JsonHeaders, [{Key, Value} | Rest]) -> apply_headers1(JsonHeaders, []) -> JsonHeaders. - % Maybe this is in the proplists API % todo move to couch_util json_apply_field(H, {L}) -> json_apply_field(H, L, []). - json_apply_field({Key, NewValue}, [{Key, _OldVal} | Headers], Acc) -> % drop matching keys json_apply_field({Key, NewValue}, Headers, Acc); @@ -359,8 +391,7 @@ json_apply_field({Key, NewValue}, [{OtherKey, OtherVal} | Headers], Acc) -> json_apply_field({Key, NewValue}, Headers, [{OtherKey, OtherVal} | Acc]); json_apply_field({Key, NewValue}, [], Acc) -> % end of list, add ours - {[{Key, NewValue}|Acc]}. - + {[{Key, NewValue} | Acc]}. % This loads the db info if we have a fully loaded db record, but we might not % have the db locally on this node, so then load the info through fabric. @@ -371,7 +402,9 @@ json_req_obj(Req, Db) -> % and json_req_obj calls fabric:get_db_info() JRO = fun() -> exit(chttpd_external:json_req_obj(Req, Db)) end, {Pid, Ref} = spawn_monitor(JRO), - receive {'DOWN', Ref, process, Pid, JsonReq} -> JsonReq end; + receive + {'DOWN', Ref, process, Pid, JsonReq} -> JsonReq + end; false -> chttpd_external:json_req_obj(Req, Db) end. @@ -381,7 +414,6 @@ last_chunk(Req, undefined) -> last_chunk(_Req, Resp) -> chttpd:send_chunk(Resp, []). - -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). @@ -395,7 +427,7 @@ apply_headers_test_() -> should_apply_headers() -> ?_test(begin JsonResp = [{<<"code">>, 201}], - Headers = [{<<"foo">>, <<"bar">>}], + Headers = [{<<"foo">>, <<"bar">>}], {Props} = apply_headers(JsonResp, Headers), JsonHeaders = couch_util:get_value(<<"headers">>, Props), ?assertEqual({Headers}, JsonHeaders) @@ -404,7 +436,7 @@ should_apply_headers() -> should_apply_headers_with_merge() -> ?_test(begin BaseHeaders = [{<<"bar">>, <<"baz">>}], - NewHeaders = [{<<"foo">>, <<"bar">>}], + NewHeaders = [{<<"foo">>, <<"bar">>}], JsonResp = [ {<<"code">>, 201}, {<<"headers">>, {BaseHeaders}} @@ -418,7 +450,7 @@ should_apply_headers_with_merge() -> should_apply_headers_with_merge_overwrite() -> ?_test(begin BaseHeaders = [{<<"foo">>, <<"bar">>}], - NewHeaders = [{<<"foo">>, <<"baz">>}], + NewHeaders = [{<<"foo">>, <<"baz">>}], JsonResp = [ {<<"code">>, 201}, {<<"headers">>, {BaseHeaders}} @@ -428,7 +460,6 @@ should_apply_headers_with_merge_overwrite() -> ?assertEqual({NewHeaders}, JsonHeaders) end). - send_list_row_test_() -> Cases = couch_tests_combinatorics:product([ [ @@ -439,26 +470,40 @@ send_list_row_test_() -> [ req, undefined - ]]), + ] + ]), {"Ensure send_list_row returns a valid response on end or error", {setup, fun setup/0, fun(_) -> meck:unload() end, [ { lists:flatten(io_lib:format("~s -- ~p", [N, R])), should_return_valid_response(F, R) - } || [{N, F}, R] <- Cases - ]} - }. + } + || [{N, F}, R] <- Cases + ]}}. setup() -> ok = application:start(config, permanent), - ok = meck:expect(chttpd, send_chunk, - fun(Resp, _) -> {ok, Resp} end), - ok = meck:expect(chttpd, send_chunked_error, - fun(Resp, _) -> {ok, Resp} end), - ok = meck:expect(chttpd, start_chunked_response, - fun(_, _, _) -> {ok, resp} end), - ok = meck:expect(chttpd_external, parse_external_response, 1, - #extern_resp_args{headers = []}). + ok = meck:expect( + chttpd, + send_chunk, + fun(Resp, _) -> {ok, Resp} end + ), + ok = meck:expect( + chttpd, + send_chunked_error, + fun(Resp, _) -> {ok, Resp} end + ), + ok = meck:expect( + chttpd, + start_chunked_response, + fun(_, _, _) -> {ok, resp} end + ), + ok = meck:expect( + chttpd_external, + parse_external_response, + 1, + #extern_resp_args{headers = []} + ). should_return_valid_response(Spec, Req) -> ?_test(begin diff --git a/src/couch_mrview/src/couch_mrview_test_util.erl b/src/couch_mrview/src/couch_mrview_test_util.erl index 2dfa64e61..918988ea3 100644 --- a/src/couch_mrview/src/couch_mrview_test_util.erl +++ b/src/couch_mrview/src/couch_mrview_test_util.erl @@ -18,17 +18,14 @@ -include_lib("couch/include/couch_db.hrl"). -include_lib("couch/include/couch_eunit.hrl"). - init_db(Name, Type) -> init_db(Name, Type, 10). - init_db(Name, Type, Count) -> {ok, Db} = new_db(Name, Type), Docs = make_docs(Type, Count), save_docs(Db, Docs). - new_db(Name, Type) when Type == local; Type == design -> couch_server:delete(Name, [?ADMIN_CTX]), couch_db:create(Name, [?ADMIN_CTX]); @@ -44,80 +41,96 @@ save_docs(Db, Docs) -> {ok, _} = couch_db:update_docs(Db, Docs, []), couch_db:reopen(Db). - make_docs(local, Count) -> [local_doc(I) || I <- lists:seq(1, Count)]; make_docs(design, Count) -> - lists:foldl(fun(I, Acc) -> - [doc(I), ddoc(I) | Acc] - end, [], lists:seq(1, Count)); + lists:foldl( + fun(I, Acc) -> + [doc(I), ddoc(I) | Acc] + end, + [], + lists:seq(1, Count) + ); make_docs(_, Count) -> [doc(I) || I <- lists:seq(1, Count)]. - make_docs(_, Since, Count) -> [doc(I) || I <- lists:seq(Since, Count)]. - ddoc(map) -> - couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/bar">>}, - {<<"views">>, {[ - {<<"baz">>, {[ - {<<"map">>, <<"function(doc) {emit(doc.val, doc.val);}">>} - ]}}, - {<<"bing">>, {[ - {<<"map">>, <<"function(doc) {}">>} - ]}}, - {<<"zing">>, {[ - {<<"map">>, << - "function(doc) {\n" - " if(doc.foo !== undefined)\n" - " emit(doc.foo, 0);\n" - "}" - >>} - ]}} - ]}} - ]}); + couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/bar">>}, + {<<"views">>, + {[ + {<<"baz">>, + {[ + {<<"map">>, <<"function(doc) {emit(doc.val, doc.val);}">>} + ]}}, + {<<"bing">>, + {[ + {<<"map">>, <<"function(doc) {}">>} + ]}}, + {<<"zing">>, + {[ + {<<"map">>, << + "function(doc) {\n" + " if(doc.foo !== undefined)\n" + " emit(doc.foo, 0);\n" + "}" + >>} + ]}} + ]}} + ]} + ); ddoc(red) -> - couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/red">>}, - {<<"views">>, {[ - {<<"baz">>, {[ - {<<"map">>, << - "function(doc) {\n" - " emit([doc.val % 2, doc.val], doc.val);\n" - "}\n" - >>}, - {<<"reduce">>, <<"function(keys, vals) {return sum(vals);}">>} - ]}}, - {<<"zing">>, {[ - {<<"map">>, << - "function(doc) {\n" - " if(doc.foo !== undefined)\n" - " emit(doc.foo, null);\n" - "}" - >>}, - {<<"reduce">>, <<"_count">>} - ]}} - ]}} - ]}); + couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/red">>}, + {<<"views">>, + {[ + {<<"baz">>, + {[ + {<<"map">>, << + "function(doc) {\n" + " emit([doc.val % 2, doc.val], doc.val);\n" + "}\n" + >>}, + {<<"reduce">>, <<"function(keys, vals) {return sum(vals);}">>} + ]}}, + {<<"zing">>, + {[ + {<<"map">>, << + "function(doc) {\n" + " if(doc.foo !== undefined)\n" + " emit(doc.foo, null);\n" + "}" + >>}, + {<<"reduce">>, <<"_count">>} + ]}} + ]}} + ]} + ); ddoc(Id) -> - couch_doc:from_json_obj({[ - {<<"_id">>, list_to_binary(io_lib:format("_design/bar~2..0b", [Id]))}, - {<<"views">>, {[]}} - ]}). - + couch_doc:from_json_obj( + {[ + {<<"_id">>, list_to_binary(io_lib:format("_design/bar~2..0b", [Id]))}, + {<<"views">>, {[]}} + ]} + ). doc(Id) -> - couch_doc:from_json_obj({[ - {<<"_id">>, list_to_binary(integer_to_list(Id))}, - {<<"val">>, Id} - ]}). - + couch_doc:from_json_obj( + {[ + {<<"_id">>, list_to_binary(integer_to_list(Id))}, + {<<"val">>, Id} + ]} + ). local_doc(Id) -> - couch_doc:from_json_obj({[ - {<<"_id">>, list_to_binary(io_lib:format("_local/~b", [Id]))}, - {<<"val">>, Id} - ]}). + couch_doc:from_json_obj( + {[ + {<<"_id">>, list_to_binary(io_lib:format("_local/~b", [Id]))}, + {<<"val">>, Id} + ]} + ). diff --git a/src/couch_mrview/src/couch_mrview_update_notifier.erl b/src/couch_mrview/src/couch_mrview_update_notifier.erl index 803d39747..ac91131a0 100644 --- a/src/couch_mrview/src/couch_mrview_update_notifier.erl +++ b/src/couch_mrview/src/couch_mrview_update_notifier.erl @@ -20,7 +20,9 @@ -include_lib("couch/include/couch_db.hrl"). start_link(Exec) -> - couch_event_sup:start_link(couch_mrview_update, {couch_mrview_update_notifier, make_ref()}, Exec). + couch_event_sup:start_link( + couch_mrview_update, {couch_mrview_update_notifier, make_ref()}, Exec + ). notify(Event) -> gen_event:notify(couch_mrview_update, Event). diff --git a/src/couch_mrview/src/couch_mrview_updater.erl b/src/couch_mrview/src/couch_mrview_updater.erl index 522367c1d..969a82028 100644 --- a/src/couch_mrview/src/couch_mrview_updater.erl +++ b/src/couch_mrview/src/couch_mrview_updater.erl @@ -26,22 +26,25 @@ start_update(Partial, State, NumChanges, NumChangesDone) -> {ok, DocQueue} = couch_work_queue:new(QueueOpts), {ok, WriteQueue} = couch_work_queue:new(QueueOpts), InitState = State#mrst{ - first_build=State#mrst.update_seq==0, - partial_resp_pid=Partial, - doc_acc=[], - doc_queue=DocQueue, - write_queue=WriteQueue + first_build = State#mrst.update_seq == 0, + partial_resp_pid = Partial, + doc_acc = [], + doc_queue = DocQueue, + write_queue = WriteQueue }, Self = self(), MapFun = fun() -> - erlang:put(io_priority, - {view_update, State#mrst.db_name, State#mrst.idx_name}), - Progress = case NumChanges of - 0 -> 0; - _ -> (NumChangesDone * 100) div NumChanges - end, + erlang:put( + io_priority, + {view_update, State#mrst.db_name, State#mrst.idx_name} + ), + Progress = + case NumChanges of + 0 -> 0; + _ -> (NumChangesDone * 100) div NumChanges + end, couch_task_status:add_task([ {indexer_pid, ?l2b(pid_to_list(Partial))}, {type, indexer}, @@ -55,8 +58,10 @@ start_update(Partial, State, NumChanges, NumChangesDone) -> map_docs(Self, InitState) end, WriteFun = fun() -> - erlang:put(io_priority, - {view_update, State#mrst.db_name, State#mrst.idx_name}), + erlang:put( + io_priority, + {view_update, State#mrst.db_name, State#mrst.idx_name} + ), write_results(Self, InitState) end, spawn_link(MapFun), @@ -64,12 +69,11 @@ start_update(Partial, State, NumChanges, NumChangesDone) -> {ok, InitState}. - purge(_Db, PurgeSeq, PurgedIdRevs, State) -> #mrst{ - id_btree=IdBtree, - views=Views, - partitioned=Partitioned + id_btree = IdBtree, + views = Views, + partitioned = Partitioned } = State, Ids = [Id || {Id, _Revs} <- PurgedIdRevs], @@ -81,10 +85,14 @@ purge(_Db, PurgeSeq, PurgedIdRevs, State) -> ({ViewNum, {Key, Seq, _Op}}, DictAcc2) -> dict:append(ViewNum, {Key, Seq, DocId}, DictAcc2); ({ViewNum, RowKey0}, DictAcc2) -> - RowKey = if not Partitioned -> RowKey0; true -> - [{RK, _}] = inject_partition([{RowKey0, DocId}]), - RK - end, + RowKey = + if + not Partitioned -> + RowKey0; + true -> + [{RK, _}] = inject_partition([{RowKey0, DocId}]), + RK + end, dict:append(ViewNum, {RowKey, DocId}, DictAcc2) end, lists:foldl(FoldFun, DictAcc, ViewNumRowKeys); @@ -93,54 +101,54 @@ purge(_Db, PurgeSeq, PurgedIdRevs, State) -> end, KeysToRemove = lists:foldl(MakeDictFun, dict:new(), Lookups), - RemKeysFun = fun(#mrview{id_num=ViewId}=View) -> + RemKeysFun = fun(#mrview{id_num = ViewId} = View) -> ToRem = couch_util:dict_find(ViewId, KeysToRemove, []), {ok, VBtree2} = couch_btree:add_remove(View#mrview.btree, [], ToRem), - NewPurgeSeq = case VBtree2 =/= View#mrview.btree of - true -> PurgeSeq; - _ -> View#mrview.purge_seq - end, - View#mrview{btree=VBtree2, purge_seq=NewPurgeSeq} + NewPurgeSeq = + case VBtree2 =/= View#mrview.btree of + true -> PurgeSeq; + _ -> View#mrview.purge_seq + end, + View#mrview{btree = VBtree2, purge_seq = NewPurgeSeq} end, Views2 = lists:map(RemKeysFun, Views), {ok, State#mrst{ - id_btree=IdBtree2, - views=Views2, - purge_seq=PurgeSeq + id_btree = IdBtree2, + views = Views2, + purge_seq = PurgeSeq }}. - -process_doc(Doc, Seq, #mrst{doc_acc=Acc}=State) when length(Acc) > 100 -> +process_doc(Doc, Seq, #mrst{doc_acc = Acc} = State) when length(Acc) > 100 -> couch_work_queue:queue(State#mrst.doc_queue, lists:reverse(Acc)), - process_doc(Doc, Seq, State#mrst{doc_acc=[]}); -process_doc(nil, Seq, #mrst{doc_acc=Acc}=State) -> - {ok, State#mrst{doc_acc=[{nil, Seq, nil} | Acc]}}; -process_doc(#doc{id=Id, deleted=true}, Seq, #mrst{doc_acc=Acc}=State) -> - {ok, State#mrst{doc_acc=[{Id, Seq, deleted} | Acc]}}; -process_doc(#doc{id=Id}=Doc, Seq, #mrst{doc_acc=Acc}=State) -> - {ok, State#mrst{doc_acc=[{Id, Seq, Doc} | Acc]}}. - - -finish_update(#mrst{doc_acc=Acc}=State) -> - if Acc /= [] -> - couch_work_queue:queue(State#mrst.doc_queue, Acc); - true -> ok + process_doc(Doc, Seq, State#mrst{doc_acc = []}); +process_doc(nil, Seq, #mrst{doc_acc = Acc} = State) -> + {ok, State#mrst{doc_acc = [{nil, Seq, nil} | Acc]}}; +process_doc(#doc{id = Id, deleted = true}, Seq, #mrst{doc_acc = Acc} = State) -> + {ok, State#mrst{doc_acc = [{Id, Seq, deleted} | Acc]}}; +process_doc(#doc{id = Id} = Doc, Seq, #mrst{doc_acc = Acc} = State) -> + {ok, State#mrst{doc_acc = [{Id, Seq, Doc} | Acc]}}. + +finish_update(#mrst{doc_acc = Acc} = State) -> + if + Acc /= [] -> + couch_work_queue:queue(State#mrst.doc_queue, Acc); + true -> + ok end, couch_work_queue:close(State#mrst.doc_queue), receive {new_state, NewState} -> {ok, NewState#mrst{ - first_build=undefined, - partial_resp_pid=undefined, - doc_acc=undefined, - doc_queue=undefined, - write_queue=undefined, - qserver=nil + first_build = undefined, + partial_resp_pid = undefined, + doc_acc = undefined, + doc_queue = undefined, + write_queue = undefined, + qserver = nil }} end. - map_docs(Parent, #mrst{db_name = DbName, idx_name = IdxName} = State0) -> erlang:put(io_priority, {view_update, DbName, IdxName}), case couch_work_queue:dequeue(State0#mrst.doc_queue) of @@ -150,10 +158,11 @@ map_docs(Parent, #mrst{db_name = DbName, idx_name = IdxName} = State0) -> {ok, Dequeued} -> % Run all the non deleted docs through the view engine and % then pass the results on to the writer process. - State1 = case State0#mrst.qserver of - nil -> start_query_server(State0); - _ -> State0 - end, + State1 = + case State0#mrst.qserver of + nil -> start_query_server(State0); + _ -> State0 + end, QServer = State1#mrst.qserver, DocFun = fun ({nil, Seq, _}, {SeqAcc, Results}) -> @@ -174,38 +183,37 @@ map_docs(Parent, #mrst{db_name = DbName, idx_name = IdxName} = State0) -> map_docs(Parent, State1) end. - write_results(Parent, #mrst{} = State) -> case accumulate_writes(State, State#mrst.write_queue, nil) of stop -> Parent ! {new_state, State}; {Go, {Seq, ViewKVs, DocIdKeys}} -> NewState = write_kvs(State, Seq, ViewKVs, DocIdKeys), - if Go == stop -> - Parent ! {new_state, NewState}; - true -> - send_partial(NewState#mrst.partial_resp_pid, NewState), - write_results(Parent, NewState) + if + Go == stop -> + Parent ! {new_state, NewState}; + true -> + send_partial(NewState#mrst.partial_resp_pid, NewState), + write_results(Parent, NewState) end end. - start_query_server(State) -> #mrst{ - language=Language, - lib=Lib, - views=Views + language = Language, + lib = Lib, + views = Views } = State, Defs = [View#mrview.def || View <- Views], {ok, QServer} = couch_query_servers:start_doc_map(Language, Defs, Lib), - State#mrst{qserver=QServer}. - + State#mrst{qserver = QServer}. accumulate_writes(State, W, Acc0) -> - {Seq, ViewKVs, DocIdKVs} = case Acc0 of - nil -> {0, [{V#mrview.id_num, []} || V <- State#mrst.views], []}; - _ -> Acc0 - end, + {Seq, ViewKVs, DocIdKVs} = + case Acc0 of + nil -> {0, [{V#mrview.id_num, []} || V <- State#mrst.views], []}; + _ -> Acc0 + end, case couch_work_queue:dequeue(W) of closed when Seq == 0 -> stop; @@ -219,15 +227,13 @@ accumulate_writes(State, W, Acc0) -> end end. - accumulate_more(NumDocIds, Acc) -> % check if we have enough items now MinItems = config:get("view_updater", "min_writer_items", "100"), MinSize = config:get("view_updater", "min_writer_size", "16777216"), CurrMem = ?term_size(Acc), - NumDocIds < list_to_integer(MinItems) - andalso CurrMem < list_to_integer(MinSize). - + NumDocIds < list_to_integer(MinItems) andalso + CurrMem < list_to_integer(MinSize). merge_results([], SeqAcc, ViewKVs, DocIdKeys) -> {SeqAcc, ViewKVs, DocIdKeys}; @@ -238,7 +244,6 @@ merge_results([{Seq, Results} | Rest], SeqAcc, ViewKVs, DocIdKeys) -> {ViewKVs1, DocIdKeys1} = lists:foldl(Fun, {ViewKVs, DocIdKeys}, Results), merge_results(Rest, erlang:max(Seq, SeqAcc), ViewKVs1, DocIdKeys1). - merge_results({DocId, []}, ViewKVs, DocIdKeys) -> {ViewKVs, [{DocId, []} | DocIdKeys]}; merge_results({DocId, RawResults}, ViewKVs, DocIdKeys) -> @@ -252,7 +257,6 @@ merge_results({DocId, RawResults}, ViewKVs, DocIdKeys) -> {ViewKVs1, [ViewIdKeys | DocIdKeys]} end. - insert_results(DocId, [], [], ViewKVs, ViewIdKeys) -> {lists:reverse(ViewKVs), {DocId, ViewIdKeys}}; insert_results(DocId, [KVs | RKVs], [{Id, VKVs} | RVKVs], VKVAcc, VIdKeys) -> @@ -266,62 +270,67 @@ insert_results(DocId, [KVs | RKVs], [{Id, VKVs} | RVKVs], VKVAcc, VIdKeys) -> end, InitAcc = {[], VIdKeys}, couch_stats:increment_counter([couchdb, mrview, emits], length(KVs)), - {Duped, VIdKeys0} = lists:foldl(CombineDupesFun, InitAcc, - lists:sort(KVs)), + {Duped, VIdKeys0} = lists:foldl( + CombineDupesFun, + InitAcc, + lists:sort(KVs) + ), FinalKVs = [{{Key, DocId}, Val} || {Key, Val} <- Duped] ++ VKVs, insert_results(DocId, RKVs, RVKVs, [{Id, FinalKVs} | VKVAcc], VIdKeys0). - write_kvs(State, UpdateSeq, ViewKVs, DocIdKeys) -> #mrst{ - id_btree=IdBtree, - first_build=FirstBuild, - partitioned=Partitioned + id_btree = IdBtree, + first_build = FirstBuild, + partitioned = Partitioned } = State, {ok, ToRemove, IdBtree2} = update_id_btree(IdBtree, DocIdKeys, FirstBuild), ToRemByView = collapse_rem_keys(ToRemove, dict:new()), - UpdateView = fun(#mrview{id_num=ViewId}=View, {ViewId, KVs0}) -> + UpdateView = fun(#mrview{id_num = ViewId} = View, {ViewId, KVs0}) -> ToRem0 = couch_util:dict_find(ViewId, ToRemByView, []), - {KVs, ToRem} = case Partitioned of - true -> - KVs1 = inject_partition(KVs0), - ToRem1 = inject_partition(ToRem0), - {KVs1, ToRem1}; - false -> - {KVs0, ToRem0} - end, + {KVs, ToRem} = + case Partitioned of + true -> + KVs1 = inject_partition(KVs0), + ToRem1 = inject_partition(ToRem0), + {KVs1, ToRem1}; + false -> + {KVs0, ToRem0} + end, {ok, VBtree2} = couch_btree:add_remove(View#mrview.btree, KVs, ToRem), - NewUpdateSeq = case VBtree2 =/= View#mrview.btree of - true -> UpdateSeq; - _ -> View#mrview.update_seq - end, + NewUpdateSeq = + case VBtree2 =/= View#mrview.btree of + true -> UpdateSeq; + _ -> View#mrview.update_seq + end, - View2 = View#mrview{btree=VBtree2, update_seq=NewUpdateSeq}, + View2 = View#mrview{btree = VBtree2, update_seq = NewUpdateSeq}, maybe_notify(State, View2, KVs, ToRem), View2 end, State#mrst{ - views=lists:zipwith(UpdateView, State#mrst.views, ViewKVs), - update_seq=UpdateSeq, - id_btree=IdBtree2 + views = lists:zipwith(UpdateView, State#mrst.views, ViewKVs), + update_seq = UpdateSeq, + id_btree = IdBtree2 }. - inject_partition(Rows) -> - lists:map(fun - ({{Key, DocId}, Value}) -> - % Adding a row to the view - {Partition, _} = couch_partition:extract(DocId), - {{{p, Partition, Key}, DocId}, Value}; - ({Key, DocId}) -> - % Removing a row based on values in id_tree - {Partition, _} = couch_partition:extract(DocId), - {{p, Partition, Key}, DocId} - end, Rows). - + lists:map( + fun + ({{Key, DocId}, Value}) -> + % Adding a row to the view + {Partition, _} = couch_partition:extract(DocId), + {{{p, Partition, Key}, DocId}, Value}; + ({Key, DocId}) -> + % Removing a row based on values in id_tree + {Partition, _} = couch_partition:extract(DocId), + {{p, Partition, Key}, DocId} + end, + Rows + ). update_id_btree(Btree, DocIdKeys, true) -> ToAdd = [{Id, DIKeys} || {Id, DIKeys} <- DocIdKeys, DIKeys /= []], @@ -332,37 +341,38 @@ update_id_btree(Btree, DocIdKeys, _) -> ToRem = [Id || {Id, DIKeys} <- DocIdKeys, DIKeys == []], couch_btree:query_modify(Btree, ToFind, ToAdd, ToRem). - collapse_rem_keys([], Acc) -> Acc; collapse_rem_keys([{ok, {DocId, ViewIdKeys}} | Rest], Acc) -> - NewAcc = lists:foldl(fun({ViewId, Key}, Acc2) -> - dict:append(ViewId, {Key, DocId}, Acc2) - end, Acc, ViewIdKeys), + NewAcc = lists:foldl( + fun({ViewId, Key}, Acc2) -> + dict:append(ViewId, {Key, DocId}, Acc2) + end, + Acc, + ViewIdKeys + ), collapse_rem_keys(Rest, NewAcc); collapse_rem_keys([{not_found, _} | Rest], Acc) -> collapse_rem_keys(Rest, Acc). - send_partial(Pid, State) when is_pid(Pid) -> gen_server:cast(Pid, {new_state, State}); send_partial(_, _) -> ok. - update_task(NumChanges) -> [Changes, Total] = couch_task_status:get([changes_done, total_changes]), Changes2 = Changes + NumChanges, - Progress = case Total of - 0 -> - % updater restart after compaction finishes - 0; - _ -> - (Changes2 * 100) div Total - end, + Progress = + case Total of + 0 -> + % updater restart after compaction finishes + 0; + _ -> + (Changes2 * 100) div Total + end, couch_task_status:update([{progress, Progress}, {changes_done, Changes2}]). - maybe_notify(State, View, KVs, ToRem) -> Updated = fun() -> [Key || {{Key, _}, _} <- KVs] diff --git a/src/couch_mrview/src/couch_mrview_util.erl b/src/couch_mrview/src/couch_mrview_util.erl index d318a3f4a..b7220f71f 100644 --- a/src/couch_mrview/src/couch_mrview_util.erl +++ b/src/couch_mrview/src/couch_mrview_util.erl @@ -38,39 +38,54 @@ -define(GET_VIEW_RETRY_DELAY, 50). -define(LOWEST_KEY, null). -define(HIGHEST_KEY, {<<255, 255, 255, 255>>}). --define(LOWEST(A, B), (if A < B -> A; true -> B end)). --define(HIGHEST(A, B), (if A > B -> A; true -> B end)). +-define(LOWEST(A, B), + (if + A < B -> A; + true -> B + end) +). +-define(HIGHEST(A, B), + (if + A > B -> A; + true -> B + end) +). -include_lib("couch/include/couch_db.hrl"). -include_lib("couch_mrview/include/couch_mrview.hrl"). - get_local_purge_doc_id(Sig) -> ?l2b(?LOCAL_DOC_PREFIX ++ "purge-mrview-" ++ Sig). - get_value_from_options(Key, Options) -> case couch_util:get_value(Key, Options) of undefined -> Reason = <<"'", Key/binary, "' must exists in options.">>, throw({bad_request, Reason}); - Value -> Value + Value -> + Value end. - verify_view_filename(FileName) -> FilePathList = filename:split(FileName), PureFN = lists:last(FilePathList), case filename:extension(PureFN) of ".view" -> Sig = filename:basename(PureFN), - case [Ch || Ch <- Sig, not (((Ch >= $0) and (Ch =< $9)) - orelse ((Ch >= $a) and (Ch =< $f)) - orelse ((Ch >= $A) and (Ch =< $F)))] == [] of + case + [ + Ch + || Ch <- Sig, + not (((Ch >= $0) and (Ch =< $9)) orelse + ((Ch >= $a) and (Ch =< $f)) orelse + ((Ch >= $A) and (Ch =< $F))) + ] == [] + of true -> true; false -> false end; - _ -> false + _ -> + false end. get_signature_from_filename(FileName) -> @@ -82,7 +97,7 @@ get_view(Db, DDoc, ViewName, Args0) -> case get_view_index_state(Db, DDoc, ViewName, Args0) of {ok, State, Args2} -> Ref = erlang:monitor(process, State#mrst.fd), - #mrst{language=Lang, views=Views} = State, + #mrst{language = Lang, views = Views} = State, {Type, View, Args3} = extract_view(Lang, Args2, ViewName, Views), check_range(Args3, view_cmp(View)), Sig = view_sig(Db, State, View, Args3), @@ -91,7 +106,6 @@ get_view(Db, DDoc, ViewName, Args0) -> ddoc_updated end. - get_view_index_pid(Db, DDoc, ViewName, Args0) -> ArgCheck = fun(InitState) -> Args1 = set_view_type(Args0, ViewName, InitState#mrst.views), @@ -99,7 +113,6 @@ get_view_index_pid(Db, DDoc, ViewName, Args0) -> end, couch_index_server:get_index(?MOD, Db, DDoc, ArgCheck). - get_view_index_state(Db, DDoc, ViewName, Args0) -> get_view_index_state(Db, DDoc, ViewName, Args0, ?GET_VIEW_RETRY_COUNT). @@ -112,17 +125,18 @@ get_view_index_state(Db, DDoc, ViewName, Args0, RetryCount) -> UpdateSeq = couch_util:with_db(Db, fun(WDb) -> couch_db:get_update_seq(WDb) end), - State = case Args#mrargs.update of - lazy -> - spawn(fun() -> - catch couch_index:get_state(Pid, UpdateSeq) - end), - couch_index:get_state(Pid, 0); - false -> - couch_index:get_state(Pid, 0); - _ -> - couch_index:get_state(Pid, UpdateSeq) - end, + State = + case Args#mrargs.update of + lazy -> + spawn(fun() -> + catch couch_index:get_state(Pid, UpdateSeq) + end), + couch_index:get_state(Pid, 0); + false -> + couch_index:get_state(Pid, 0); + _ -> + couch_index:get_state(Pid, UpdateSeq) + end, case State of {ok, State0} -> {ok, State0, Args}; ddoc_updated -> ddoc_updated; @@ -138,33 +152,37 @@ get_view_index_state(Db, DDoc, ViewName, Args0, RetryCount) -> throw(Error) end. - -ddoc_to_mrst(DbName, #doc{id=Id, body={Fields}}) -> - MakeDict = fun({Name, {MRFuns}}, DictBySrcAcc) -> - case couch_util:get_value(<<"map">>, MRFuns) of - MapSrc when MapSrc /= undefined -> - RedSrc = couch_util:get_value(<<"reduce">>, MRFuns, null), - {ViewOpts} = couch_util:get_value(<<"options">>, MRFuns, {[]}), - View = case dict:find({MapSrc, ViewOpts}, DictBySrcAcc) of - {ok, View0} -> View0; - error -> #mrview{def=MapSrc, options=ViewOpts} - end, - {MapNames, RedSrcs} = case RedSrc of - null -> - MNames = [Name | View#mrview.map_names], - {MNames, View#mrview.reduce_funs}; - _ -> - RedFuns = [{Name, RedSrc} | View#mrview.reduce_funs], - {View#mrview.map_names, RedFuns} - end, - View2 = View#mrview{map_names=MapNames, reduce_funs=RedSrcs}, - dict:store({MapSrc, ViewOpts}, View2, DictBySrcAcc); - undefined -> - DictBySrcAcc - end; +ddoc_to_mrst(DbName, #doc{id = Id, body = {Fields}}) -> + MakeDict = fun + ({Name, {MRFuns}}, DictBySrcAcc) -> + case couch_util:get_value(<<"map">>, MRFuns) of + MapSrc when MapSrc /= undefined -> + RedSrc = couch_util:get_value(<<"reduce">>, MRFuns, null), + {ViewOpts} = couch_util:get_value(<<"options">>, MRFuns, {[]}), + View = + case dict:find({MapSrc, ViewOpts}, DictBySrcAcc) of + {ok, View0} -> View0; + error -> #mrview{def = MapSrc, options = ViewOpts} + end, + {MapNames, RedSrcs} = + case RedSrc of + null -> + MNames = [Name | View#mrview.map_names], + {MNames, View#mrview.reduce_funs}; + _ -> + RedFuns = [{Name, RedSrc} | View#mrview.reduce_funs], + {View#mrview.map_names, RedFuns} + end, + View2 = View#mrview{map_names = MapNames, reduce_funs = RedSrcs}, + dict:store({MapSrc, ViewOpts}, View2, DictBySrcAcc); + undefined -> + DictBySrcAcc + end; ({Name, Else}, DictBySrcAcc) -> - couch_log:error("design_doc_to_view_group ~s views ~p", - [Name, Else]), + couch_log:error( + "design_doc_to_view_group ~s views ~p", + [Name, Else] + ), DictBySrcAcc end, {DesignOpts} = proplists:get_value(<<"options">>, Fields, {[]}), @@ -174,7 +192,7 @@ ddoc_to_mrst(DbName, #doc{id=Id, body={Fields}}) -> BySrc = lists:foldl(MakeDict, dict:new(), RawViews), NumViews = fun({_, View}, N) -> - {View#mrview{id_num=N}, N+1} + {View#mrview{id_num = N}, N + 1} end, {Views, _} = lists:mapfoldl(NumViews, 0, lists:sort(dict:to_list(BySrc))), @@ -182,17 +200,16 @@ ddoc_to_mrst(DbName, #doc{id=Id, body={Fields}}) -> Lib = couch_util:get_value(<<"lib">>, RawViews, {[]}), IdxState = #mrst{ - db_name=DbName, - idx_name=Id, - lib=Lib, - views=Views, - language=Language, - design_opts=DesignOpts, - partitioned=Partitioned + db_name = DbName, + idx_name = Id, + lib = Lib, + views = Views, + language = Language, + design_opts = DesignOpts, + partitioned = Partitioned }, SigInfo = {Views, Language, DesignOpts, couch_index_util:sort_lib(Lib)}, - {ok, IdxState#mrst{sig=couch_hash:md5_hash(term_to_binary(SigInfo))}}. - + {ok, IdxState#mrst{sig = couch_hash:md5_hash(term_to_binary(SigInfo))}}. set_view_type(_Args, _ViewName, []) -> throw({not_found, missing_named_view}); @@ -201,48 +218,44 @@ set_view_type(Args, ViewName, [View | Rest]) -> case lists:member(ViewName, RedNames) of true -> case Args#mrargs.reduce of - false -> Args#mrargs{view_type=map}; - _ -> Args#mrargs{view_type=red} + false -> Args#mrargs{view_type = map}; + _ -> Args#mrargs{view_type = red} end; false -> case lists:member(ViewName, View#mrview.map_names) of - true -> Args#mrargs{view_type=map}; + true -> Args#mrargs{view_type = map}; false -> set_view_type(Args, ViewName, Rest) end end. - set_extra(#mrargs{} = Args, Key, Value) -> Extra0 = Args#mrargs.extra, Extra1 = lists:ukeysort(1, [{Key, Value} | Extra0]), Args#mrargs{extra = Extra1}. - get_extra(#mrargs{} = Args, Key) -> couch_util:get_value(Key, Args#mrargs.extra). get_extra(#mrargs{} = Args, Key, Default) -> couch_util:get_value(Key, Args#mrargs.extra, Default). - extract_view(_Lang, _Args, _ViewName, []) -> throw({not_found, missing_named_view}); -extract_view(Lang, #mrargs{view_type=map}=Args, Name, [View | Rest]) -> +extract_view(Lang, #mrargs{view_type = map} = Args, Name, [View | Rest]) -> Names = View#mrview.map_names ++ [N || {N, _} <- View#mrview.reduce_funs], case lists:member(Name, Names) of true -> {map, View, Args}; _ -> extract_view(Lang, Args, Name, Rest) end; -extract_view(Lang, #mrargs{view_type=red}=Args, Name, [View | Rest]) -> +extract_view(Lang, #mrargs{view_type = red} = Args, Name, [View | Rest]) -> RedNames = [N || {N, _} <- View#mrview.reduce_funs], case lists:member(Name, RedNames) of true -> {red, {index_of(Name, RedNames), Lang, View}, Args}; false -> extract_view(Lang, Args, Name, Rest) end. - -view_sig(Db, State, View, #mrargs{include_docs=true}=Args) -> - BaseSig = view_sig(Db, State, View, Args#mrargs{include_docs=false}), +view_sig(Db, State, View, #mrargs{include_docs = true} = Args) -> + BaseSig = view_sig(Db, State, View, Args#mrargs{include_docs = false}), UpdateSeq = couch_db:get_update_seq(Db), PurgeSeq = couch_db:get_purge_seq(Db), Term = view_sig_term(BaseSig, UpdateSeq, PurgeSeq), @@ -254,8 +267,8 @@ view_sig(_Db, State, View, Args0) -> UpdateSeq = View#mrview.update_seq, PurgeSeq = View#mrview.purge_seq, Args = Args0#mrargs{ - preflight_fun=undefined, - extra=[] + preflight_fun = undefined, + extra = [] }, Term = view_sig_term(Sig, UpdateSeq, PurgeSeq, Args), couch_index_util:hexsig(couch_hash:md5_hash(term_to_binary(Term))). @@ -266,26 +279,25 @@ view_sig_term(BaseSig, UpdateSeq, PurgeSeq) -> view_sig_term(BaseSig, UpdateSeq, PurgeSeq, Args) -> {BaseSig, UpdateSeq, PurgeSeq, Args}. - -init_state(Db, Fd, #mrst{views=Views}=State, nil) -> +init_state(Db, Fd, #mrst{views = Views} = State, nil) -> PurgeSeq = couch_db:get_purge_seq(Db), Header = #mrheader{ - seq=0, - purge_seq=PurgeSeq, - id_btree_state=nil, - view_states=[make_view_state(#mrview{}) || _ <- Views] + seq = 0, + purge_seq = PurgeSeq, + id_btree_state = nil, + view_states = [make_view_state(#mrview{}) || _ <- Views] }, init_state(Db, Fd, State, Header); init_state(Db, Fd, State, Header) -> #mrst{ - language=Lang, - views=Views + language = Lang, + views = Views } = State, #mrheader{ - seq=Seq, - purge_seq=PurgeSeq, - id_btree_state=IdBtreeState, - view_states=ViewStates + seq = Seq, + purge_seq = PurgeSeq, + id_btree_state = IdBtreeState, + view_states = ViewStates } = maybe_update_header(Header), IdBtOpts = [ @@ -297,12 +309,12 @@ init_state(Db, Fd, State, Header) -> Views2 = lists:zipwith(OpenViewFun, ViewStates, Views), State#mrst{ - fd=Fd, - fd_monitor=erlang:monitor(process, Fd), - update_seq=Seq, - purge_seq=PurgeSeq, - id_btree=IdBtree, - views=Views2 + fd = Fd, + fd_monitor = erlang:monitor(process, Fd), + update_seq = Seq, + purge_seq = PurgeSeq, + id_btree = IdBtree, + views = Views2 }. open_view(_Db, Fd, Lang, ViewState, View) -> @@ -317,38 +329,42 @@ open_view(_Db, Fd, Lang, ViewState, View) -> ], {ok, Btree} = couch_btree:open(BTState, Fd, ViewBtOpts), - View#mrview{btree=Btree, - update_seq=get_update_seq(ViewState), - purge_seq=get_purge_seq(ViewState)}. - + View#mrview{ + btree = Btree, + update_seq = get_update_seq(ViewState), + purge_seq = get_purge_seq(ViewState) + }. temp_view_to_ddoc({Props}) -> Language = couch_util:get_value(<<"language">>, Props, <<"javascript">>), Options = couch_util:get_value(<<"options">>, Props, {[]}), View0 = [{<<"map">>, couch_util:get_value(<<"map">>, Props)}], - View1 = View0 ++ case couch_util:get_value(<<"reduce">>, Props) of - RedSrc when is_binary(RedSrc) -> [{<<"reduce">>, RedSrc}]; - _ -> [] - end, - DDoc = {[ - {<<"_id">>, couch_uuids:random()}, - {<<"language">>, Language}, - {<<"options">>, Options}, - {<<"views">>, {[ - {<<"temp">>, {View1}} - ]}} - ]}, + View1 = + View0 ++ + case couch_util:get_value(<<"reduce">>, Props) of + RedSrc when is_binary(RedSrc) -> [{<<"reduce">>, RedSrc}]; + _ -> [] + end, + DDoc = + {[ + {<<"_id">>, couch_uuids:random()}, + {<<"language">>, Language}, + {<<"options">>, Options}, + {<<"views">>, + {[ + {<<"temp">>, {View1}} + ]}} + ]}, couch_doc:from_json_obj(DDoc). - -get_row_count(#mrview{btree=Bt}) -> - Count = case couch_btree:full_reduce(Bt) of - {ok, {Count0, _Reds, _}} -> Count0; - {ok, {Count0, _Reds}} -> Count0 - end, +get_row_count(#mrview{btree = Bt}) -> + Count = + case couch_btree:full_reduce(Bt) of + {ok, {Count0, _Reds, _}} -> Count0; + {ok, {Count0, _Reds}} -> Count0 + end, {ok, Count}. - all_docs_reduce_to_count(Reductions) -> Reduce = fun couch_bt_engine:id_tree_reduce/2, {Count, _, _} = couch_btree:final_reduce(Reduce, Reductions), @@ -361,8 +377,7 @@ reduce_to_count(Reductions) -> FinalReduction = couch_btree:final_reduce(CountReduceFun, Reductions), get_count(FinalReduction). - -fold(#mrview{btree=Bt}, Fun, Acc, Opts) -> +fold(#mrview{btree = Bt}, Fun, Acc, Opts) -> WrapperFun = fun(KV, Reds, Acc2) -> fold_fun(Fun, expand_dups([KV], []), Reds, Acc2) end, @@ -370,19 +385,18 @@ fold(#mrview{btree=Bt}, Fun, Acc, Opts) -> fold_fun(_Fun, [], _, Acc) -> {ok, Acc}; -fold_fun(Fun, [KV|Rest], {KVReds, Reds}, Acc) -> +fold_fun(Fun, [KV | Rest], {KVReds, Reds}, Acc) -> case Fun(KV, {KVReds, Reds}, Acc) of {ok, Acc2} -> - fold_fun(Fun, Rest, {[KV|KVReds], Reds}, Acc2); + fold_fun(Fun, Rest, {[KV | KVReds], Reds}, Acc2); {stop, Acc2} -> {stop, Acc2} end. - -fold_reduce({NthRed, Lang, View}, Fun, Acc, Options) -> +fold_reduce({NthRed, Lang, View}, Fun, Acc, Options) -> #mrview{ - btree=Bt, - reduce_funs=RedFuns + btree = Bt, + reduce_funs = RedFuns } = View, ReduceFun = make_user_reds_reduce_fun(Lang, RedFuns, NthRed), @@ -395,13 +409,11 @@ fold_reduce({NthRed, Lang, View}, Fun, Acc, Options) -> couch_btree:fold_reduce(Bt, WrapperFun, Acc, Options). - validate_args(Db, DDoc, Args0) -> {ok, State} = couch_mrview_index:init(Db, DDoc), Args1 = apply_limit(State#mrst.partitioned, Args0), validate_args(State, Args1). - validate_args(#mrst{} = State, Args0) -> Args = validate_args(Args0), @@ -410,45 +422,55 @@ validate_args(#mrst{} = State, Args0) -> case {ViewPartitioned, Partition} of {true, undefined} -> - Msg1 = <<"`partition` parameter is mandatory " - "for queries to this view.">>, + Msg1 = << + "`partition` parameter is mandatory " + "for queries to this view." + >>, mrverror(Msg1); {true, _} -> apply_partition(Args, Partition); {false, undefined} -> Args; {false, Value} when is_binary(Value) -> - Msg2 = <<"`partition` parameter is not " - "supported in this design doc">>, + Msg2 = << + "`partition` parameter is not " + "supported in this design doc" + >>, mrverror(Msg2) end. - apply_limit(ViewPartitioned, Args) -> Options = Args#mrargs.extra, IgnorePQLimit = lists:keyfind(ignore_partition_query_limit, 1, Options), - LimitType = case {ViewPartitioned, IgnorePQLimit} of - {true, false} -> "partition_query_limit"; - {true, _} -> "query_limit"; - {false, _} -> "query_limit" - end, + LimitType = + case {ViewPartitioned, IgnorePQLimit} of + {true, false} -> "partition_query_limit"; + {true, _} -> "query_limit"; + {false, _} -> "query_limit" + end, - MaxLimit = config:get_integer("query_server_config", - LimitType, ?MAX_VIEW_LIMIT), + MaxLimit = config:get_integer( + "query_server_config", + LimitType, + ?MAX_VIEW_LIMIT + ), % Set the highest limit possible if a user has not % specified a limit - Args1 = case Args#mrargs.limit == ?MAX_VIEW_LIMIT of - true -> Args#mrargs{limit = MaxLimit}; - false -> Args - end, + Args1 = + case Args#mrargs.limit == ?MAX_VIEW_LIMIT of + true -> Args#mrargs{limit = MaxLimit}; + false -> Args + end, - if Args1#mrargs.limit =< MaxLimit -> Args1; true -> - Fmt = "Limit is too large, must not exceed ~p", - mrverror(io_lib:format(Fmt, [MaxLimit])) + if + Args1#mrargs.limit =< MaxLimit -> + Args1; + true -> + Fmt = "Limit is too large, must not exceed ~p", + mrverror(io_lib:format(Fmt, [MaxLimit])) end. - validate_all_docs_args(Db, Args0) -> Args = validate_args(Args0), @@ -465,7 +487,6 @@ validate_all_docs_args(Db, Args0) -> Args end. - validate_args(Args) -> GroupLevel = determine_group_level(Args), Reduce = Args#mrargs.reduce, @@ -480,11 +501,13 @@ validate_args(Args) -> end, case {Args#mrargs.view_type, GroupLevel, Args#mrargs.keys} of - {red, exact, _} -> ok; + {red, exact, _} -> + ok; {red, _, KeyList} when is_list(KeyList) -> Msg = <<"Multi-key fetchs for reduce views must use `group=true`">>, mrverror(Msg); - _ -> ok + _ -> + ok end, case Args#mrargs.keys of @@ -493,13 +516,18 @@ validate_args(Args) -> _ -> mrverror(<<"`keys` must be an array of strings.">>) end, - case {Args#mrargs.keys, Args#mrargs.start_key, - Args#mrargs.end_key} of - {undefined, _, _} -> ok; - {[], _, _} -> ok; - {[_|_], undefined, undefined} -> ok; - _ -> mrverror(<<"`keys` is incompatible with `key`" - ", `start_key` and `end_key`">>) + case {Args#mrargs.keys, Args#mrargs.start_key, Args#mrargs.end_key} of + {undefined, _, _} -> + ok; + {[], _, _} -> + ok; + {[_ | _], undefined, undefined} -> + ok; + _ -> + mrverror(<< + "`keys` is incompatible with `key`" + ", `start_key` and `end_key`" + >>) end, case Args#mrargs.start_key_docid of @@ -571,17 +599,19 @@ validate_args(Args) -> {red, _} -> mrverror(<<"`conflicts` is invalid for reduce views.">>) end, - SKDocId = case {Args#mrargs.direction, Args#mrargs.start_key_docid} of - {fwd, undefined} -> <<>>; - {rev, undefined} -> <<255>>; - {_, SKDocId1} -> SKDocId1 - end, + SKDocId = + case {Args#mrargs.direction, Args#mrargs.start_key_docid} of + {fwd, undefined} -> <<>>; + {rev, undefined} -> <<255>>; + {_, SKDocId1} -> SKDocId1 + end, - EKDocId = case {Args#mrargs.direction, Args#mrargs.end_key_docid} of - {fwd, undefined} -> <<255>>; - {rev, undefined} -> <<>>; - {_, EKDocId1} -> EKDocId1 - end, + EKDocId = + case {Args#mrargs.direction, Args#mrargs.end_key_docid} of + {fwd, undefined} -> <<255>>; + {rev, undefined} -> <<>>; + {_, EKDocId1} -> EKDocId1 + end, case is_boolean(Args#mrargs.sorted) of true -> ok; @@ -595,32 +625,30 @@ validate_args(Args) -> end, Args#mrargs{ - start_key_docid=SKDocId, - end_key_docid=EKDocId, - group_level=GroupLevel + start_key_docid = SKDocId, + end_key_docid = EKDocId, + group_level = GroupLevel }. - -determine_group_level(#mrargs{group=undefined, group_level=undefined}) -> +determine_group_level(#mrargs{group = undefined, group_level = undefined}) -> 0; -determine_group_level(#mrargs{group=false, group_level=undefined}) -> +determine_group_level(#mrargs{group = false, group_level = undefined}) -> 0; -determine_group_level(#mrargs{group=false, group_level=Level}) when Level > 0 -> +determine_group_level(#mrargs{group = false, group_level = Level}) when Level > 0 -> mrverror(<<"Can't specify group=false and group_level>0 at the same time">>); -determine_group_level(#mrargs{group=true, group_level=undefined}) -> +determine_group_level(#mrargs{group = true, group_level = undefined}) -> exact; -determine_group_level(#mrargs{group_level=GroupLevel}) -> +determine_group_level(#mrargs{group_level = GroupLevel}) -> GroupLevel. -apply_partition(#mrargs{keys=[{p, _, _} | _]} = Args, _Partition) -> - Args; % already applied - -apply_partition(#mrargs{keys=Keys} = Args, Partition) when Keys /= undefined -> - Args#mrargs{keys=[{p, Partition, K} || K <- Keys]}; - -apply_partition(#mrargs{start_key={p, _, _}, end_key={p, _, _}} = Args, _Partition) -> - Args; % already applied. - +apply_partition(#mrargs{keys = [{p, _, _} | _]} = Args, _Partition) -> + % already applied + Args; +apply_partition(#mrargs{keys = Keys} = Args, Partition) when Keys /= undefined -> + Args#mrargs{keys = [{p, Partition, K} || K <- Keys]}; +apply_partition(#mrargs{start_key = {p, _, _}, end_key = {p, _, _}} = Args, _Partition) -> + % already applied. + Args; apply_partition(Args, Partition) -> #mrargs{ direction = Dir, @@ -628,13 +656,22 @@ apply_partition(Args, Partition) -> end_key = EndKey } = Args, - {DefSK, DefEK} = case Dir of - fwd -> {?LOWEST_KEY, ?HIGHEST_KEY}; - rev -> {?HIGHEST_KEY, ?LOWEST_KEY} - end, + {DefSK, DefEK} = + case Dir of + fwd -> {?LOWEST_KEY, ?HIGHEST_KEY}; + rev -> {?HIGHEST_KEY, ?LOWEST_KEY} + end, - SK0 = if StartKey /= undefined -> StartKey; true -> DefSK end, - EK0 = if EndKey /= undefined -> EndKey; true -> DefEK end, + SK0 = + if + StartKey /= undefined -> StartKey; + true -> DefSK + end, + EK0 = + if + EndKey /= undefined -> EndKey; + true -> DefEK + end, Args#mrargs{ start_key = {p, Partition, SK0}, @@ -650,92 +687,101 @@ apply_all_docs_partition(#mrargs{} = Args, Partition) -> end_key = EndKey } = Args, - {DefSK, DefEK} = case Dir of - fwd -> - { - couch_partition:start_key(Partition), - couch_partition:end_key(Partition) - }; - rev -> - { - couch_partition:end_key(Partition), - couch_partition:start_key(Partition) - } - end, + {DefSK, DefEK} = + case Dir of + fwd -> + { + couch_partition:start_key(Partition), + couch_partition:end_key(Partition) + }; + rev -> + { + couch_partition:end_key(Partition), + couch_partition:start_key(Partition) + } + end, - SK0 = if StartKey == undefined -> DefSK; true -> StartKey end, - EK0 = if EndKey == undefined -> DefEK; true -> EndKey end, + SK0 = + if + StartKey == undefined -> DefSK; + true -> StartKey + end, + EK0 = + if + EndKey == undefined -> DefEK; + true -> EndKey + end, - {SK1, EK1} = case Dir of - fwd -> {?HIGHEST(DefSK, SK0), ?LOWEST(DefEK, EK0)}; - rev -> {?LOWEST(DefSK, SK0), ?HIGHEST(DefEK, EK0)} - end, + {SK1, EK1} = + case Dir of + fwd -> {?HIGHEST(DefSK, SK0), ?LOWEST(DefEK, EK0)}; + rev -> {?LOWEST(DefSK, SK0), ?HIGHEST(DefEK, EK0)} + end, Args#mrargs{ start_key = SK1, end_key = EK1 }. - -check_range(#mrargs{start_key=undefined}, _Cmp) -> +check_range(#mrargs{start_key = undefined}, _Cmp) -> ok; -check_range(#mrargs{end_key=undefined}, _Cmp) -> +check_range(#mrargs{end_key = undefined}, _Cmp) -> ok; -check_range(#mrargs{start_key=K, end_key=K}, _Cmp) -> +check_range(#mrargs{start_key = K, end_key = K}, _Cmp) -> ok; check_range(Args, Cmp) -> #mrargs{ - direction=Dir, - start_key=SK, - start_key_docid=SKD, - end_key=EK, - end_key_docid=EKD + direction = Dir, + start_key = SK, + start_key_docid = SKD, + end_key = EK, + end_key_docid = EKD } = Args, case {Dir, Cmp({SK, SKD}, {EK, EKD})} of {fwd, false} -> - throw({query_parse_error, - <<"No rows can match your key range, reverse your ", - "start_key and end_key or set descending=true">>}); + throw( + {query_parse_error, + <<"No rows can match your key range, reverse your ", + "start_key and end_key or set descending=true">>} + ); {rev, true} -> - throw({query_parse_error, - <<"No rows can match your key range, reverse your ", - "start_key and end_key or set descending=false">>}); - _ -> ok + throw( + {query_parse_error, + <<"No rows can match your key range, reverse your ", + "start_key and end_key or set descending=false">>} + ); + _ -> + ok end. - view_cmp({_Nth, _Lang, View}) -> view_cmp(View); view_cmp(View) -> fun(A, B) -> couch_btree:less(View#mrview.btree, A, B) end. - make_header(State) -> #mrst{ - update_seq=Seq, - purge_seq=PurgeSeq, - id_btree=IdBtree, - views=Views + update_seq = Seq, + purge_seq = PurgeSeq, + id_btree = IdBtree, + views = Views } = State, #mrheader{ - seq=Seq, - purge_seq=PurgeSeq, - id_btree_state=get_btree_state(IdBtree), - view_states=[make_view_state(V) || V <- Views] + seq = Seq, + purge_seq = PurgeSeq, + id_btree_state = get_btree_state(IdBtree), + view_states = [make_view_state(V) || V <- Views] }. - index_file(DbName, Sig) -> FileName = couch_index_util:hexsig(Sig) ++ ".view", couch_index_util:index_file(mrview, DbName, FileName). - compaction_file(DbName, Sig) -> FileName = couch_index_util:hexsig(Sig) ++ ".compact.view", couch_index_util:index_file(mrview, DbName, FileName). - open_file(FName) -> case couch_file:open(FName, [nologifmissing]) of {ok, Fd} -> {ok, Fd}; @@ -743,20 +789,16 @@ open_file(FName) -> Error -> Error end. - delete_files(DbName, Sig) -> delete_index_file(DbName, Sig), delete_compaction_file(DbName, Sig). - delete_index_file(DbName, Sig) -> delete_file(index_file(DbName, Sig)). - delete_compaction_file(DbName, Sig) -> delete_file(compaction_file(DbName, Sig)). - delete_file(FName) -> case filelib:is_file(FName) of true -> @@ -766,87 +808,91 @@ delete_file(FName) -> ok end. - -reset_index(Db, Fd, #mrst{sig=Sig}=State) -> +reset_index(Db, Fd, #mrst{sig = Sig} = State) -> ok = couch_file:truncate(Fd, 0), ok = couch_file:write_header(Fd, {Sig, nil}), init_state(Db, Fd, reset_state(State), nil). - reset_state(State) -> State#mrst{ - fd=nil, - qserver=nil, - update_seq=0, - id_btree=nil, - views=[View#mrview{btree=nil} || View <- State#mrst.views] + fd = nil, + qserver = nil, + update_seq = 0, + id_btree = nil, + views = [View#mrview{btree = nil} || View <- State#mrst.views] }. - all_docs_key_opts(#mrargs{extra = Extra} = Args) -> all_docs_key_opts(Args, Extra). -all_docs_key_opts(#mrargs{keys=undefined}=Args, Extra) -> - all_docs_key_opts(Args#mrargs{keys=[]}, Extra); -all_docs_key_opts(#mrargs{keys=[], direction=Dir}=Args, Extra) -> +all_docs_key_opts(#mrargs{keys = undefined} = Args, Extra) -> + all_docs_key_opts(Args#mrargs{keys = []}, Extra); +all_docs_key_opts(#mrargs{keys = [], direction = Dir} = Args, Extra) -> [[{dir, Dir}] ++ ad_skey_opts(Args) ++ ad_ekey_opts(Args) ++ Extra]; -all_docs_key_opts(#mrargs{keys=Keys, direction=Dir}=Args, Extra) -> - lists:map(fun(K) -> - [{dir, Dir}] - ++ ad_skey_opts(Args#mrargs{start_key=K}) - ++ ad_ekey_opts(Args#mrargs{end_key=K}) - ++ Extra - end, Keys). - +all_docs_key_opts(#mrargs{keys = Keys, direction = Dir} = Args, Extra) -> + lists:map( + fun(K) -> + [{dir, Dir}] ++ + ad_skey_opts(Args#mrargs{start_key = K}) ++ + ad_ekey_opts(Args#mrargs{end_key = K}) ++ + Extra + end, + Keys + ). -ad_skey_opts(#mrargs{start_key=SKey}) when is_binary(SKey) -> +ad_skey_opts(#mrargs{start_key = SKey}) when is_binary(SKey) -> [{start_key, SKey}]; -ad_skey_opts(#mrargs{start_key_docid=SKeyDocId}) -> +ad_skey_opts(#mrargs{start_key_docid = SKeyDocId}) -> [{start_key, SKeyDocId}]. - -ad_ekey_opts(#mrargs{end_key=EKey}=Args) when is_binary(EKey) -> - Type = if Args#mrargs.inclusive_end -> end_key; true -> end_key_gt end, +ad_ekey_opts(#mrargs{end_key = EKey} = Args) when is_binary(EKey) -> + Type = + if + Args#mrargs.inclusive_end -> end_key; + true -> end_key_gt + end, [{Type, EKey}]; -ad_ekey_opts(#mrargs{end_key_docid=EKeyDocId}=Args) -> - Type = if Args#mrargs.inclusive_end -> end_key; true -> end_key_gt end, +ad_ekey_opts(#mrargs{end_key_docid = EKeyDocId} = Args) -> + Type = + if + Args#mrargs.inclusive_end -> end_key; + true -> end_key_gt + end, [{Type, EKeyDocId}]. - key_opts(Args) -> key_opts(Args, []). -key_opts(#mrargs{keys=undefined, direction=Dir}=Args, Extra) -> +key_opts(#mrargs{keys = undefined, direction = Dir} = Args, Extra) -> [[{dir, Dir}] ++ skey_opts(Args) ++ ekey_opts(Args) ++ Extra]; -key_opts(#mrargs{keys=Keys, direction=Dir}=Args, Extra) -> - lists:map(fun(K) -> - [{dir, Dir}] - ++ skey_opts(Args#mrargs{start_key=K}) - ++ ekey_opts(Args#mrargs{end_key=K}) - ++ Extra - end, Keys). - +key_opts(#mrargs{keys = Keys, direction = Dir} = Args, Extra) -> + lists:map( + fun(K) -> + [{dir, Dir}] ++ + skey_opts(Args#mrargs{start_key = K}) ++ + ekey_opts(Args#mrargs{end_key = K}) ++ + Extra + end, + Keys + ). -skey_opts(#mrargs{start_key=undefined}) -> +skey_opts(#mrargs{start_key = undefined}) -> []; -skey_opts(#mrargs{start_key=SKey, start_key_docid=SKeyDocId}) -> +skey_opts(#mrargs{start_key = SKey, start_key_docid = SKeyDocId}) -> [{start_key, {SKey, SKeyDocId}}]. - -ekey_opts(#mrargs{end_key=undefined}) -> +ekey_opts(#mrargs{end_key = undefined}) -> []; -ekey_opts(#mrargs{end_key=EKey, end_key_docid=EKeyDocId}=Args) -> +ekey_opts(#mrargs{end_key = EKey, end_key_docid = EKeyDocId} = Args) -> case Args#mrargs.inclusive_end of true -> [{end_key, {EKey, EKeyDocId}}]; false -> [{end_key_gt, {EKey, reverse_key_default(EKeyDocId)}}] end. - reverse_key_default(<<>>) -> <<255>>; reverse_key_default(<<255>>) -> <<>>; reverse_key_default(Key) -> Key. - reduced_external_size(Tree) -> case couch_btree:full_reduce(Tree) of {ok, {_, _, Size}} -> Size; @@ -854,35 +900,31 @@ reduced_external_size(Tree) -> {ok, {_, _}} -> 0 end. - calculate_external_size(Views) -> SumFun = fun - (#mrview{btree=nil}, Acc) -> + (#mrview{btree = nil}, Acc) -> Acc; - (#mrview{btree=Bt}, Acc) -> + (#mrview{btree = Bt}, Acc) -> Acc + reduced_external_size(Bt) end, {ok, lists:foldl(SumFun, 0, Views)}. - calculate_active_size(Views) -> FoldFun = fun - (#mrview{btree=nil}, Acc) -> + (#mrview{btree = nil}, Acc) -> Acc; - (#mrview{btree=Bt}, Acc) -> + (#mrview{btree = Bt}, Acc) -> Acc + couch_btree:size(Bt) end, {ok, lists:foldl(FoldFun, 0, Views)}. - detuple_kvs([], Acc) -> lists:reverse(Acc); detuple_kvs([KV | Rest], Acc) -> - {{Key,Id},Value} = KV, + {{Key, Id}, Value} = KV, NKV = [[Key, Id], Value], detuple_kvs(Rest, [NKV | Acc]). - expand_dups([], Acc) -> lists:reverse(Acc); expand_dups([{Key, {dups, Vals}} | Rest], Acc) -> @@ -891,56 +933,49 @@ expand_dups([{Key, {dups, Vals}} | Rest], Acc) -> expand_dups([KV | Rest], Acc) -> expand_dups(Rest, [KV | Acc]). - -maybe_load_doc(_Db, _DI, #mrargs{include_docs=false}) -> +maybe_load_doc(_Db, _DI, #mrargs{include_docs = false}) -> []; -maybe_load_doc(Db, #doc_info{}=DI, #mrargs{conflicts=true, doc_options=Opts}) -> +maybe_load_doc(Db, #doc_info{} = DI, #mrargs{conflicts = true, doc_options = Opts}) -> doc_row(couch_index_util:load_doc(Db, DI, [conflicts]), Opts); -maybe_load_doc(Db, #doc_info{}=DI, #mrargs{doc_options=Opts}) -> +maybe_load_doc(Db, #doc_info{} = DI, #mrargs{doc_options = Opts}) -> doc_row(couch_index_util:load_doc(Db, DI, []), Opts). - -maybe_load_doc(_Db, _Id, _Val, #mrargs{include_docs=false}) -> +maybe_load_doc(_Db, _Id, _Val, #mrargs{include_docs = false}) -> []; -maybe_load_doc(Db, Id, Val, #mrargs{conflicts=true, doc_options=Opts}) -> +maybe_load_doc(Db, Id, Val, #mrargs{conflicts = true, doc_options = Opts}) -> doc_row(couch_index_util:load_doc(Db, docid_rev(Id, Val), [conflicts]), Opts); -maybe_load_doc(Db, Id, Val, #mrargs{doc_options=Opts}) -> +maybe_load_doc(Db, Id, Val, #mrargs{doc_options = Opts}) -> doc_row(couch_index_util:load_doc(Db, docid_rev(Id, Val), []), Opts). - doc_row(null, _Opts) -> [{doc, null}]; doc_row(Doc, Opts) -> [{doc, couch_doc:to_json_obj(Doc, Opts)}]. - docid_rev(Id, {Props}) -> DocId = couch_util:get_value(<<"_id">>, Props, Id), - Rev = case couch_util:get_value(<<"_rev">>, Props, nil) of - nil -> nil; - Rev0 -> couch_doc:parse_rev(Rev0) - end, + Rev = + case couch_util:get_value(<<"_rev">>, Props, nil) of + nil -> nil; + Rev0 -> couch_doc:parse_rev(Rev0) + end, {DocId, Rev}; docid_rev(Id, _) -> {Id, nil}. - index_of(Key, List) -> index_of(Key, List, 1). - index_of(_, [], _) -> throw({error, missing_named_view}); index_of(Key, [Key | _], Idx) -> Idx; index_of(Key, [_ | Rest], Idx) -> - index_of(Key, Rest, Idx+1). - + index_of(Key, Rest, Idx + 1). mrverror(Mesg) -> throw({query_parse_error, Mesg}). - %% Updates 2.x view files to 3.x or later view files %% transparently, the first time the 2.x view file is opened by %% 3.x or later. @@ -960,11 +995,11 @@ maybe_update_index_file(State) -> % open in read-only mode so we don't create % the file if it doesn't exist. case file:open(NewIndexFile, [read, raw]) of - {ok, Fd_Read} -> - % the new index file exists, there is nothing to do here. - file:close(Fd_Read); - _Error -> - update_index_file(State) + {ok, Fd_Read} -> + % the new index file exists, there is nothing to do here. + file:close(Fd_Read); + _Error -> + update_index_file(State) end. update_index_file(State) -> @@ -975,26 +1010,35 @@ update_index_file(State) -> % If we have an old index, rename it to the new position. case file:read_file_info(IndexFile) of - {ok, _FileInfo} -> - % Crash if the rename fails for any reason. - % If the target exists, e.g. the next request will find the - % new file and we are good. We might need to catch this - % further up to avoid a full server crash. - NewIndexFile = index_file(DbName, State#mrst.sig), - couch_log:notice("Attempting to update legacy view index file" - " from ~p to ~s", [IndexFile, NewIndexFile]), - ok = filelib:ensure_dir(NewIndexFile), - ok = file:rename(IndexFile, NewIndexFile), - couch_log:notice("Successfully updated legacy view index file" - " ~s", [IndexFile]), - Sig; - {error, enoent} -> - % Ignore missing index file - ok; - {error, Reason} -> - couch_log:error("Failed to update legacy view index file" - " ~s : ~s", [IndexFile, file:format_error(Reason)]), - ok + {ok, _FileInfo} -> + % Crash if the rename fails for any reason. + % If the target exists, e.g. the next request will find the + % new file and we are good. We might need to catch this + % further up to avoid a full server crash. + NewIndexFile = index_file(DbName, State#mrst.sig), + couch_log:notice( + "Attempting to update legacy view index file" + " from ~p to ~s", + [IndexFile, NewIndexFile] + ), + ok = filelib:ensure_dir(NewIndexFile), + ok = file:rename(IndexFile, NewIndexFile), + couch_log:notice( + "Successfully updated legacy view index file" + " ~s", + [IndexFile] + ), + Sig; + {error, enoent} -> + % Ignore missing index file + ok; + {error, Reason} -> + couch_log:error( + "Failed to update legacy view index file" + " ~s : ~s", + [IndexFile, file:format_error(Reason)] + ), + ok end. sig_vsn_2x(State) -> @@ -1010,21 +1054,21 @@ sig_vsn_2x(State) -> couch_hash:md5_hash(term_to_binary(SigInfo)). old_view_format(View, SI, KSI) -> -{ - mrview, - View#mrview.id_num, - View#mrview.update_seq, - View#mrview.purge_seq, - View#mrview.map_names, - View#mrview.reduce_funs, - View#mrview.def, - View#mrview.btree, - nil, - nil, - SI, - KSI, - View#mrview.options -}. + { + mrview, + View#mrview.id_num, + View#mrview.update_seq, + View#mrview.purge_seq, + View#mrview.map_names, + View#mrview.reduce_funs, + View#mrview.def, + View#mrview.btree, + nil, + nil, + SI, + KSI, + View#mrview.options + }. maybe_update_header(#mrheader{} = Header) -> Header; @@ -1050,7 +1094,6 @@ make_view_state({BTState, _SeqBTState, _KSeqBTState, UpdateSeq, PurgeSeq}) -> make_view_state(nil) -> {nil, 0, 0}. - get_key_btree_state(ViewState) -> element(1, ViewState). @@ -1066,18 +1109,14 @@ get_count(Reduction) -> get_user_reds(Reduction) -> element(2, Reduction). - % This is for backwards compatibility for seq btree reduces get_external_size_reds(Reduction) when is_integer(Reduction) -> 0; - get_external_size_reds(Reduction) when tuple_size(Reduction) == 2 -> 0; - get_external_size_reds(Reduction) when tuple_size(Reduction) == 3 -> element(3, Reduction). - make_reduce_fun(Lang, ReduceFuns) -> FunSrcs = [FunSrc || {_, FunSrc} <- ReduceFuns], fun @@ -1093,20 +1132,21 @@ make_reduce_fun(Lang, ReduceFuns) -> ExtAcc = ExtAcc0 + get_external_size_reds(Red), {CountsAcc, URedsAcc, ExtAcc} end, - {Counts, UReds, ExternalSize} = lists:foldl(ExtractFun, - {0, [], 0}, Reds), + {Counts, UReds, ExternalSize} = lists:foldl( + ExtractFun, + {0, [], 0}, + Reds + ), {ok, Result} = couch_query_servers:rereduce(Lang, FunSrcs, UReds), {Counts, Result, ExternalSize} 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); @@ -1121,7 +1161,6 @@ count_reduce(rereduce, Reds) -> 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, []), @@ -1140,18 +1179,15 @@ make_user_reds_reduce_fun(Lang, ReduceFuns, NthRed) -> {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}) -> +extract_view_reduce({red, {N, _Lang, #mrview{reduce_funs = Reds}}, _Ref}) -> {_Name, FunSrc} = lists:nth(N, Reds), FunSrc. - get_view_keys({Props}) -> case couch_util:get_value(<<"keys">>, Props) of undefined -> @@ -1162,7 +1198,6 @@ get_view_keys({Props}) -> throw({bad_request, "`keys` member must be an array."}) end. - get_view_queries({Props}) -> case couch_util:get_value(<<"queries">>, Props) of undefined -> @@ -1173,8 +1208,11 @@ get_view_queries({Props}) -> throw({bad_request, "`queries` member must be an array."}) end. - kv_external_size(KVList, Reduction) -> - lists:foldl(fun([[Key, _], Value], Acc) -> - ?term_size(Key) + ?term_size(Value) + Acc - end, ?term_size(Reduction), KVList). + lists:foldl( + fun([[Key, _], Value], Acc) -> + ?term_size(Key) + ?term_size(Value) + Acc + end, + ?term_size(Reduction), + KVList + ). diff --git a/src/couch_mrview/test/eunit/couch_mrview_all_docs_tests.erl b/src/couch_mrview/test/eunit/couch_mrview_all_docs_tests.erl index bf8eb7e5b..1a81d4f0a 100644 --- a/src/couch_mrview/test/eunit/couch_mrview_all_docs_tests.erl +++ b/src/couch_mrview/test/eunit/couch_mrview_all_docs_tests.erl @@ -17,8 +17,6 @@ -define(TIMEOUT, 1000). - - setup() -> {ok, Db} = couch_mrview_test_util:init_db(?tempdb(), map), Db. @@ -28,16 +26,17 @@ teardown(Db) -> couch_server:delete(couch_db:name(Db), [?ADMIN_CTX]), ok. - all_docs_test_() -> { "_all_docs view tests", { setup, - fun test_util:start_couch/0, fun test_util:stop_couch/1, + fun test_util:start_couch/0, + fun test_util:stop_couch/1, { foreach, - fun setup/0, fun teardown/1, + fun setup/0, + fun teardown/1, [ fun should_query/1, fun should_query_with_range/1, @@ -50,47 +49,50 @@ all_docs_test_() -> } }. - should_query(Db) -> Result = run_query(Db, []), - Expect = {ok, [ - {meta, [{total, 11}, {offset, 0}]}, - mk_row(<<"1">>, <<"1-08d53a5760b95fce6df2e2c5b008be39">>), - mk_row(<<"10">>, <<"1-a05b6ea2bc0243949f103d5b4f15f71e">>), - mk_row(<<"2">>, <<"1-b57c77a9e6f7574ca6469f0d6dcd78bb">>), - mk_row(<<"3">>, <<"1-7fbf84d56f8017880974402d60f5acd6">>), - mk_row(<<"4">>, <<"1-fcaf5852c08ffb239ac8ce16c409f253">>), - mk_row(<<"5">>, <<"1-aaac5d460fd40f9286e57b9bf12e23d2">>), - mk_row(<<"6">>, <<"1-aca21c2e7bc5f8951424fcfc5d1209d8">>), - mk_row(<<"7">>, <<"1-4374aeec17590d82f16e70f318116ad9">>), - mk_row(<<"8">>, <<"1-55b9a29311341e07ec0a7ca13bc1b59f">>), - mk_row(<<"9">>, <<"1-558c8487d9aee25399a91b5d31d90fe2">>), - mk_row(<<"_design/bar">>, <<"1-a44e1dd1994a7717bf89c894ebd1f081">>) - ]}, + Expect = + {ok, [ + {meta, [{total, 11}, {offset, 0}]}, + mk_row(<<"1">>, <<"1-08d53a5760b95fce6df2e2c5b008be39">>), + mk_row(<<"10">>, <<"1-a05b6ea2bc0243949f103d5b4f15f71e">>), + mk_row(<<"2">>, <<"1-b57c77a9e6f7574ca6469f0d6dcd78bb">>), + mk_row(<<"3">>, <<"1-7fbf84d56f8017880974402d60f5acd6">>), + mk_row(<<"4">>, <<"1-fcaf5852c08ffb239ac8ce16c409f253">>), + mk_row(<<"5">>, <<"1-aaac5d460fd40f9286e57b9bf12e23d2">>), + mk_row(<<"6">>, <<"1-aca21c2e7bc5f8951424fcfc5d1209d8">>), + mk_row(<<"7">>, <<"1-4374aeec17590d82f16e70f318116ad9">>), + mk_row(<<"8">>, <<"1-55b9a29311341e07ec0a7ca13bc1b59f">>), + mk_row(<<"9">>, <<"1-558c8487d9aee25399a91b5d31d90fe2">>), + mk_row(<<"_design/bar">>, <<"1-a44e1dd1994a7717bf89c894ebd1f081">>) + ]}, ?_assertEqual(Expect, Result). should_query_with_range(Db) -> Result = run_query(Db, [{start_key, <<"3">>}, {end_key, <<"5">>}]), - Expect = {ok, [ - {meta, [{total, 11}, {offset, 3}]}, - mk_row(<<"3">>, <<"1-7fbf84d56f8017880974402d60f5acd6">>), - mk_row(<<"4">>, <<"1-fcaf5852c08ffb239ac8ce16c409f253">>), - mk_row(<<"5">>, <<"1-aaac5d460fd40f9286e57b9bf12e23d2">>) - ]}, + Expect = + {ok, [ + {meta, [{total, 11}, {offset, 3}]}, + mk_row(<<"3">>, <<"1-7fbf84d56f8017880974402d60f5acd6">>), + mk_row(<<"4">>, <<"1-fcaf5852c08ffb239ac8ce16c409f253">>), + mk_row(<<"5">>, <<"1-aaac5d460fd40f9286e57b9bf12e23d2">>) + ]}, ?_assertEqual(Expect, Result). should_query_with_range_rev(Db) -> Result = run_query(Db, [ {direction, rev}, - {start_key, <<"5">>}, {end_key, <<"3">>}, + {start_key, <<"5">>}, + {end_key, <<"3">>}, {inclusive_end, true} ]), - Expect = {ok, [ - {meta, [{total, 11}, {offset, 5}]}, - mk_row(<<"5">>, <<"1-aaac5d460fd40f9286e57b9bf12e23d2">>), - mk_row(<<"4">>, <<"1-fcaf5852c08ffb239ac8ce16c409f253">>), - mk_row(<<"3">>, <<"1-7fbf84d56f8017880974402d60f5acd6">>) - ]}, + Expect = + {ok, [ + {meta, [{total, 11}, {offset, 5}]}, + mk_row(<<"5">>, <<"1-aaac5d460fd40f9286e57b9bf12e23d2">>), + mk_row(<<"4">>, <<"1-fcaf5852c08ffb239ac8ce16c409f253">>), + mk_row(<<"3">>, <<"1-7fbf84d56f8017880974402d60f5acd6">>) + ]}, ?_assertEqual(Expect, Result). should_query_with_limit_and_skip(Db) -> @@ -99,12 +101,13 @@ should_query_with_limit_and_skip(Db) -> {limit, 3}, {skip, 3} ]), - Expect = {ok, [ - {meta, [{total, 11}, {offset, 5}]}, - mk_row(<<"5">>, <<"1-aaac5d460fd40f9286e57b9bf12e23d2">>), - mk_row(<<"6">>, <<"1-aca21c2e7bc5f8951424fcfc5d1209d8">>), - mk_row(<<"7">>, <<"1-4374aeec17590d82f16e70f318116ad9">>) - ]}, + Expect = + {ok, [ + {meta, [{total, 11}, {offset, 5}]}, + mk_row(<<"5">>, <<"1-aaac5d460fd40f9286e57b9bf12e23d2">>), + mk_row(<<"6">>, <<"1-aca21c2e7bc5f8951424fcfc5d1209d8">>), + mk_row(<<"7">>, <<"1-4374aeec17590d82f16e70f318116ad9">>) + ]}, ?_assertEqual(Expect, Result). should_query_with_include_docs(Db) -> @@ -113,26 +116,28 @@ should_query_with_include_docs(Db) -> {end_key, <<"8">>}, {include_docs, true} ]), - Doc = {[ - {<<"_id">>,<<"8">>}, - {<<"_rev">>, <<"1-55b9a29311341e07ec0a7ca13bc1b59f">>}, - {<<"val">>, 8} - ]}, + Doc = + {[ + {<<"_id">>, <<"8">>}, + {<<"_rev">>, <<"1-55b9a29311341e07ec0a7ca13bc1b59f">>}, + {<<"val">>, 8} + ]}, Val = {[{rev, <<"1-55b9a29311341e07ec0a7ca13bc1b59f">>}]}, - Expect = {ok, [ - {meta, [{total, 11}, {offset, 8}]}, - {row, [{id, <<"8">>}, {key, <<"8">>}, {value, Val}, {doc, Doc}]} - ]}, + Expect = + {ok, [ + {meta, [{total, 11}, {offset, 8}]}, + {row, [{id, <<"8">>}, {key, <<"8">>}, {value, Val}, {doc, Doc}]} + ]}, ?_assertEqual(Expect, Result). should_query_empty_views(Db) -> Result = couch_mrview:query_view(Db, <<"_design/bar">>, <<"bing">>), - Expect = {ok, [ - {meta, [{total, 0}, {offset, 0}]} - ]}, + Expect = + {ok, [ + {meta, [{total, 0}, {offset, 0}]} + ]}, ?_assertEqual(Expect, Result). - mk_row(Id, Rev) -> {row, [{id, Id}, {key, Id}, {value, {[{rev, Rev}]}}]}. diff --git a/src/couch_mrview/test/eunit/couch_mrview_collation_tests.erl b/src/couch_mrview/test/eunit/couch_mrview_collation_tests.erl index 0d2afbe3c..c00b97b33 100644 --- a/src/couch_mrview/test/eunit/couch_mrview_collation_tests.erl +++ b/src/couch_mrview/test/eunit/couch_mrview_collation_tests.erl @@ -56,10 +56,8 @@ % Values with depth > 10 trigger the erlang collation fallback in couch_ejson_compare {[{<<"x">>, [[[[[[[[[[[<<"y">>]]]]]]]]]]]}]} - ]). - setup() -> {ok, Db1} = couch_mrview_test_util:new_db(?tempdb(), map), Docs = [couch_mrview_test_util:ddoc(red) | make_docs()], @@ -71,16 +69,17 @@ teardown(Db) -> couch_server:delete(couch_db:name(Db), [?ADMIN_CTX]), ok. - collation_test_() -> { "Collation tests", { setup, - fun test_util:start_couch/0, fun test_util:stop_couch/1, + fun test_util:start_couch/0, + fun test_util:stop_couch/1, { foreach, - fun setup/0, fun teardown/1, + fun setup/0, + fun teardown/1, [ fun should_collate_fwd/1, fun should_collate_rev/1, @@ -96,7 +95,6 @@ collation_test_() -> } }. - should_collate_fwd(Db) -> {ok, Results} = run_query(Db, []), Expect = [{meta, [{total, length(?VALUES)}, {offset, 0}]}] ++ rows(), @@ -108,100 +106,127 @@ should_collate_rev(Db) -> ?_assertEquiv(Expect, Results). should_collate_range_(Db) -> - Index = lists:zip(lists:seq(0, length(?VALUES)-1), ?VALUES), - lists:map(fun(V) -> - {ok, Results} = run_query(Db, [{start_key, V}, {end_key, V}]), - Expect = [ - {meta, [{total, length(?VALUES)}, find_offset(Index, V)]} | - find_matching_rows(Index, V) - ], - ?_assertEquiv(Expect, Results) - end, ?VALUES). + Index = lists:zip(lists:seq(0, length(?VALUES) - 1), ?VALUES), + lists:map( + fun(V) -> + {ok, Results} = run_query(Db, [{start_key, V}, {end_key, V}]), + Expect = [ + {meta, [{total, length(?VALUES)}, find_offset(Index, V)]} + | find_matching_rows(Index, V) + ], + ?_assertEquiv(Expect, Results) + end, + ?VALUES + ). find_offset(Index, Value) -> - [{Offset, _} | _] = lists:dropwhile(fun({_, V}) -> - couch_ejson_compare:less(Value, V) =/= 0 - end, Index), + [{Offset, _} | _] = lists:dropwhile( + fun({_, V}) -> + couch_ejson_compare:less(Value, V) =/= 0 + end, + Index + ), {offset, Offset}. find_matching_rows(Index, Value) -> - Matches = lists:filter(fun({_, V}) -> - couch_ejson_compare:less(Value, V) =:= 0 - end, Index), - lists:map(fun({Id, V}) -> - {row, [{id, list_to_binary(integer_to_list(Id))}, {key, V}, {value, 0}]} - end, Matches). + Matches = lists:filter( + fun({_, V}) -> + couch_ejson_compare:less(Value, V) =:= 0 + end, + Index + ), + lists:map( + fun({Id, V}) -> + {row, [{id, list_to_binary(integer_to_list(Id))}, {key, V}, {value, 0}]} + end, + Matches + ). should_collate_with_inclusive_end_fwd(Db) -> Opts = [{end_key, <<"b">>}, {inclusive_end, true}], {ok, Rows0} = run_query(Db, Opts), LastRow = lists:last(Rows0), - Expect = {row, [{id,<<"10">>}, {key,<<"b">>}, {value,0}]}, + Expect = {row, [{id, <<"10">>}, {key, <<"b">>}, {value, 0}]}, ?_assertEqual(Expect, LastRow). should_collate_with_inclusive_end_rev(Db) -> Opts = [{end_key, <<"b">>}, {inclusive_end, true}, {direction, rev}], {ok, Rows} = run_query(Db, Opts), LastRow = lists:last(Rows), - Expect = {row, [{id,<<"10">>}, {key,<<"b">>}, {value,0}]}, + Expect = {row, [{id, <<"10">>}, {key, <<"b">>}, {value, 0}]}, ?_assertEqual(Expect, LastRow). should_collate_without_inclusive_end_fwd(Db) -> Opts = [{end_key, <<"b">>}, {inclusive_end, false}], {ok, Rows0} = run_query(Db, Opts), LastRow = lists:last(Rows0), - Expect = {row, [{id,<<"9">>}, {key,<<"aa">>}, {value,0}]}, + Expect = {row, [{id, <<"9">>}, {key, <<"aa">>}, {value, 0}]}, ?_assertEqual(Expect, LastRow). should_collate_without_inclusive_end_rev(Db) -> Opts = [{end_key, <<"b">>}, {inclusive_end, false}, {direction, rev}], {ok, Rows} = run_query(Db, Opts), LastRow = lists:last(Rows), - Expect = {row, [{id,<<"11">>}, {key,<<"B">>}, {value,0}]}, + Expect = {row, [{id, <<"11">>}, {key, <<"B">>}, {value, 0}]}, ?_assertEqual(Expect, LastRow). should_collate_with_endkey_docid(Db) -> ?_test(begin {ok, Rows0} = run_query(Db, [ - {end_key, <<"b">>}, {end_key_docid, <<"10">>}, + {end_key, <<"b">>}, + {end_key_docid, <<"10">>}, {inclusive_end, false} ]), Result0 = lists:last(Rows0), - Expect0 = {row, [{id,<<"9">>}, {key,<<"aa">>}, {value,0}]}, + Expect0 = {row, [{id, <<"9">>}, {key, <<"aa">>}, {value, 0}]}, ?assertEqual(Expect0, Result0), {ok, Rows1} = run_query(Db, [ - {end_key, <<"b">>}, {end_key_docid, <<"11">>}, + {end_key, <<"b">>}, + {end_key_docid, <<"11">>}, {inclusive_end, false} ]), Result1 = lists:last(Rows1), - Expect1 = {row, [{id,<<"10">>}, {key,<<"b">>}, {value,0}]}, + Expect1 = {row, [{id, <<"10">>}, {key, <<"b">>}, {value, 0}]}, ?assertEqual(Expect1, Result1) end). should_use_collator_for_reduce_grouping(Db) -> - UniqueKeys = lists:usort(fun(A, B) -> - not couch_ejson_compare:less_json(B, A) - end, ?VALUES), - {ok, [{meta,_} | Rows]} = reduce_query(Db, [{group_level, exact}]), + UniqueKeys = lists:usort( + fun(A, B) -> + not couch_ejson_compare:less_json(B, A) + end, + ?VALUES + ), + {ok, [{meta, _} | Rows]} = reduce_query(Db, [{group_level, exact}]), ?_assertEqual(length(UniqueKeys), length(Rows)). make_docs() -> - {Docs, _} = lists:foldl(fun(V, {Docs0, Count}) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, list_to_binary(integer_to_list(Count))}, - {<<"foo">>, V} - ]}), - {[Doc | Docs0], Count+1} - end, {[], 0}, ?VALUES), + {Docs, _} = lists:foldl( + fun(V, {Docs0, Count}) -> + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, list_to_binary(integer_to_list(Count))}, + {<<"foo">>, V} + ]} + ), + {[Doc | Docs0], Count + 1} + end, + {[], 0}, + ?VALUES + ), Docs. rows() -> - {Rows, _} = lists:foldl(fun(V, {Rows0, Count}) -> - Id = list_to_binary(integer_to_list(Count)), - Row = {row, [{id, Id}, {key, V}, {value, 0}]}, - {[Row | Rows0], Count+1} - end, {[], 0}, ?VALUES), + {Rows, _} = lists:foldl( + fun(V, {Rows0, Count}) -> + Id = list_to_binary(integer_to_list(Count)), + Row = {row, [{id, Id}, {key, V}, {value, 0}]}, + {[Row | Rows0], Count + 1} + end, + {[], 0}, + ?VALUES + ), lists:reverse(Rows). run_query(Db, Opts) -> diff --git a/src/couch_mrview/test/eunit/couch_mrview_compact_tests.erl b/src/couch_mrview/test/eunit/couch_mrview_compact_tests.erl index 7664becdc..df035c649 100644 --- a/src/couch_mrview/test/eunit/couch_mrview_compact_tests.erl +++ b/src/couch_mrview/test/eunit/couch_mrview_compact_tests.erl @@ -17,7 +17,6 @@ -define(TIMEOUT, 1000). - setup() -> {ok, Db} = couch_mrview_test_util:init_db(?tempdb(), map, 1000), ok = meck:new(couch_mrview_compactor, [passthrough]), @@ -29,16 +28,17 @@ teardown(Db) -> couch_server:delete(couch_db:name(Db), [?ADMIN_CTX]), ok. - compaction_test_() -> { "Compaction tests", { setup, - fun test_util:start_couch/0, fun test_util:stop_couch/1, + fun test_util:start_couch/0, + fun test_util:stop_couch/1, { foreach, - fun setup/0, fun teardown/1, + fun setup/0, + fun teardown/1, [ fun should_swap/1, fun should_remove/1 @@ -47,7 +47,6 @@ compaction_test_() -> } }. - should_swap(Db) -> ?_test(begin couch_mrview:query_view(Db, <<"_design/bar">>, <<"baz">>), @@ -57,9 +56,12 @@ should_swap(Db) -> {'DOWN', MonRef, process, _, _} -> ok after ?TIMEOUT -> erlang:error( - {assertion_failed, - [{module, ?MODULE}, {line, ?LINE}, - {reason, "compaction failed"}]}) + {assertion_failed, [ + {module, ?MODULE}, + {line, ?LINE}, + {reason, "compaction failed"} + ]} + ) end, QPid ! {self(), continue}, receive @@ -67,13 +69,15 @@ should_swap(Db) -> ?assertEqual(1000, Count) after ?TIMEOUT -> erlang:error( - {assertion_failed, - [{module, ?MODULE}, {line, ?LINE}, - {reason, "query failed"}]}) + {assertion_failed, [ + {module, ?MODULE}, + {line, ?LINE}, + {reason, "query failed"} + ]} + ) end end). - should_remove(Db) -> ?_test(begin DDoc = <<"_design/bar">>, @@ -87,29 +91,40 @@ should_remove(Db) -> receive {'DOWN', MonRef, process, _, crash} -> meck:wait(couch_mrview_compactor, remove_compacted, '_', 100), - ?assertEqual(1, meck:num_calls( - couch_mrview_compactor, remove_compacted, '_', IndexPid)), + ?assertEqual( + 1, + meck:num_calls( + couch_mrview_compactor, remove_compacted, '_', IndexPid + ) + ), ?assert(is_process_alive(IndexPid)), ?assert(is_process_alive(CompactorPid)) after ?TIMEOUT -> erlang:error( {assertion_failed, [ - {module, ?MODULE}, {line, ?LINE}, - {reason, "compaction didn't exit :/"}]}) + {module, ?MODULE}, + {line, ?LINE}, + {reason, "compaction didn't exit :/"} + ]} + ) end end). - start_query(Db) -> Self = self(), Pid = spawn(fun() -> CB = fun - (_, wait) -> receive {Self, continue} -> {ok, 0} end; - ({row, _}, Count) -> {ok, Count+1}; - (_, Count) -> {ok, Count} + (_, wait) -> + receive + {Self, continue} -> {ok, 0} + end; + ({row, _}, Count) -> + {ok, Count + 1}; + (_, Count) -> + {ok, Count} end, {ok, Result} = - couch_mrview:query_view(Db, <<"_design/bar">>, <<"baz">>, [], CB, wait), + couch_mrview:query_view(Db, <<"_design/bar">>, <<"baz">>, [], CB, wait), Self ! {self(), Result} end), {ok, Pid}. diff --git a/src/couch_mrview/test/eunit/couch_mrview_ddoc_updated_tests.erl b/src/couch_mrview/test/eunit/couch_mrview_ddoc_updated_tests.erl index 4310157eb..2a6299448 100644 --- a/src/couch_mrview/test/eunit/couch_mrview_ddoc_updated_tests.erl +++ b/src/couch_mrview/test/eunit/couch_mrview_ddoc_updated_tests.erl @@ -17,30 +17,33 @@ -define(TIMEOUT, 1000). - setup() -> Name = ?tempdb(), couch_server:delete(Name, [?ADMIN_CTX]), {ok, Db} = couch_db:create(Name, [?ADMIN_CTX]), - DDoc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/bar">>}, - {<<"views">>, {[ - {<<"baz">>, {[ - {<<"map">>, << - "function(doc) {\n" - " emit(doc.val, doc.val);\n" - "}" - >>} - ]}} - ]}} - ]}), + DDoc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/bar">>}, + {<<"views">>, + {[ + {<<"baz">>, + {[ + {<<"map">>, << + "function(doc) {\n" + " emit(doc.val, doc.val);\n" + "}" + >>} + ]}} + ]}} + ]} + ), [Doc1 | Docs999] = couch_mrview_test_util:make_docs(map, 100), {ok, _} = couch_db:update_docs(Db, [DDoc, Doc1], []), {ok, Db2} = couch_db:reopen(Db), % run a query with 1 doc to initialize couch_index process CB = fun - ({row, _}, Count) -> {ok, Count+1}; + ({row, _}, Count) -> {ok, Count + 1}; (_, Count) -> {ok, Count} end, {ok, _} = @@ -63,16 +66,17 @@ teardown(Db) -> couch_server:delete(couch_db:name(Db), [?ADMIN_CTX]), ok. - ddoc_update_test_() -> { "Check ddoc update actions", { setup, - fun test_util:start_couch/0, fun test_util:stop_couch/1, + fun test_util:start_couch/0, + fun test_util:stop_couch/1, { foreach, - fun setup/0, fun teardown/1, + fun setup/0, + fun teardown/1, [ fun check_indexing_stops_on_ddoc_change/1 ] @@ -80,7 +84,6 @@ ddoc_update_test_() -> } }. - check_indexing_stops_on_ddoc_change(Db) -> ?_test(begin DDocID = <<"_design/bar">>, @@ -91,17 +94,20 @@ check_indexing_stops_on_ddoc_change(Db) -> ?assertEqual(1, length(AliveBefore)), {ok, DDoc} = couch_db:open_doc(Db, DDocID, [ejson_body, ?ADMIN_CTX]), - DDocJson2 = couch_doc:from_json_obj({[ - {<<"_id">>, DDocID}, - {<<"_deleted">>, true}, - {<<"_rev">>, couch_doc:rev_to_str(DDoc#doc.revs)} - ]}), + DDocJson2 = couch_doc:from_json_obj( + {[ + {<<"_id">>, DDocID}, + {<<"_deleted">>, true}, + {<<"_rev">>, couch_doc:rev_to_str(DDoc#doc.revs)} + ]} + ), % spawn a process for query Self = self(), QPid = spawn(fun() -> {ok, Result} = couch_mrview:query_view( - Db, <<"_design/bar">>, <<"baz">>, []), + Db, <<"_design/bar">>, <<"baz">>, [] + ), Self ! {self(), Result} end), @@ -112,8 +118,12 @@ check_indexing_stops_on_ddoc_change(Db) -> ?assertEqual(Msg, ddoc_updated) after ?TIMEOUT -> erlang:error( - {assertion_failed, [{module, ?MODULE}, {line, ?LINE}, - {reason, "test failed"}]}) + {assertion_failed, [ + {module, ?MODULE}, + {line, ?LINE}, + {reason, "test failed"} + ]} + ) end, %% assert that previously running indexes are gone @@ -123,11 +133,11 @@ check_indexing_stops_on_ddoc_change(Db) -> ?assertEqual(0, length(AliveAfter)) end). - get_indexes_by_ddoc(DDocID, N) -> Indexes = test_util:wait(fun() -> Indxs = ets:match_object( - couchdb_indexes_by_db, {'$1', {DDocID, '$2'}}), + couchdb_indexes_by_db, {'$1', {DDocID, '$2'}} + ), case length(Indxs) == N of true -> Indxs; @@ -135,11 +145,13 @@ get_indexes_by_ddoc(DDocID, N) -> wait end end), - lists:foldl(fun({DbName, {_DDocID, Sig}}, Acc) -> - case ets:lookup(couchdb_indexes_by_sig, {DbName, Sig}) of - [{_, Pid}] -> [Pid|Acc]; - _ -> Acc - end - end, [], Indexes). - - + lists:foldl( + fun({DbName, {_DDocID, Sig}}, Acc) -> + case ets:lookup(couchdb_indexes_by_sig, {DbName, Sig}) of + [{_, Pid}] -> [Pid | Acc]; + _ -> Acc + end + end, + [], + Indexes + ). diff --git a/src/couch_mrview/test/eunit/couch_mrview_ddoc_validation_tests.erl b/src/couch_mrview/test/eunit/couch_mrview_ddoc_validation_tests.erl index ce2be8904..3e4cbc84f 100644 --- a/src/couch_mrview/test/eunit/couch_mrview_ddoc_validation_tests.erl +++ b/src/couch_mrview/test/eunit/couch_mrview_ddoc_validation_tests.erl @@ -31,10 +31,12 @@ ddoc_validation_test_() -> "ddoc validation tests", { setup, - fun test_util:start_couch/0, fun test_util:stop_couch/1, + fun test_util:start_couch/0, + fun test_util:stop_couch/1, { foreach, - fun setup/0, fun teardown/1, + fun setup/0, + fun teardown/1, [ fun should_reject_invalid_js_map/1, fun should_reject_invalid_js_reduce/1, @@ -79,344 +81,477 @@ ddoc_validation_test_() -> }. should_reject_invalid_js_map(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_reject_invalid_js_map">>}, - {<<"views">>, {[ - {<<"foo">>, {[ - {<<"map">>, <<"function(doc) }{">>} - ]}} - ]}} - ]}), + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_reject_invalid_js_map">>}, + {<<"views">>, + {[ + {<<"foo">>, + {[ + {<<"map">>, <<"function(doc) }{">>} + ]}} + ]}} + ]} + ), ?_assertThrow( {bad_request, compilation_error, _}, - couch_db:update_doc(Db, Doc, [])). + couch_db:update_doc(Db, Doc, []) + ). should_reject_invalid_js_reduce(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_reject_invalid_js_reduce">>}, - {<<"views">>, {[ - {<<"foo">>, {[ - {<<"map">>, <<"function(doc) { emit(null); }">>}, - {<<"reduce">>, <<"function(k, v, r) }{}">>} - ]}} - ]}} - ]}), + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_reject_invalid_js_reduce">>}, + {<<"views">>, + {[ + {<<"foo">>, + {[ + {<<"map">>, <<"function(doc) { emit(null); }">>}, + {<<"reduce">>, <<"function(k, v, r) }{}">>} + ]}} + ]}} + ]} + ), ?_assertThrow( {bad_request, compilation_error, _}, - couch_db:update_doc(Db, Doc, [])). + couch_db:update_doc(Db, Doc, []) + ). should_reject_invalid_builtin_reduce(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_reject_invalid_builtin_reduce">>}, - {<<"views">>, {[ - {<<"foo">>, {[ - {<<"map">>, <<"function(doc) { emit(null); }">>}, - {<<"reduce">>, <<"_foobar">>} - ]}} - ]}} - ]}), + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_reject_invalid_builtin_reduce">>}, + {<<"views">>, + {[ + {<<"foo">>, + {[ + {<<"map">>, <<"function(doc) { emit(null); }">>}, + {<<"reduce">>, <<"_foobar">>} + ]}} + ]}} + ]} + ), ?_assertThrow( {bad_request, invalid_design_doc, _}, - couch_db:update_doc(Db, Doc, [])). + couch_db:update_doc(Db, Doc, []) + ). should_reject_non_object_options(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_reject_non_object_options">>}, - {<<"options">>, <<"invalid">>} - ]}), - ?_assertThrow({bad_request, invalid_design_doc, _}, - couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_reject_non_object_options">>}, + {<<"options">>, <<"invalid">>} + ]} + ), + ?_assertThrow( + {bad_request, invalid_design_doc, _}, + couch_db:update_doc(Db, Doc, []) + ). should_reject_non_object_filters(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_reject_non_object_filters">>}, - {<<"filters">>, <<"invalid">>} - ]}), - ?_assertThrow({bad_request, invalid_design_doc, _}, - couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_reject_non_object_filters">>}, + {<<"filters">>, <<"invalid">>} + ]} + ), + ?_assertThrow( + {bad_request, invalid_design_doc, _}, + couch_db:update_doc(Db, Doc, []) + ). should_accept_obj_in_filters(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_accept_obj_in_filters">>}, - {<<"filters">>, ?LIB} - ]}), + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_accept_obj_in_filters">>}, + {<<"filters">>, ?LIB} + ]} + ), ?_assertMatch({ok, _}, couch_db:update_doc(Db, Doc, [])). should_reject_non_object_lists(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_reject_non_object_lists">>}, - {<<"lists">>, <<"invalid">>} - ]}), - ?_assertThrow({bad_request, invalid_design_doc, _}, - couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_reject_non_object_lists">>}, + {<<"lists">>, <<"invalid">>} + ]} + ), + ?_assertThrow( + {bad_request, invalid_design_doc, _}, + couch_db:update_doc(Db, Doc, []) + ). should_reject_non_object_shows(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_reject_non_object_shows">>}, - {<<"shows">>, <<"invalid">>} - ]}), - ?_assertThrow({bad_request, invalid_design_doc, _}, - couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_reject_non_object_shows">>}, + {<<"shows">>, <<"invalid">>} + ]} + ), + ?_assertThrow( + {bad_request, invalid_design_doc, _}, + couch_db:update_doc(Db, Doc, []) + ). should_accept_obj_in_shows(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_accept_obj_in_shows">>}, - {<<"shows">>, ?LIB} - ]}), + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_accept_obj_in_shows">>}, + {<<"shows">>, ?LIB} + ]} + ), ?_assertMatch({ok, _}, couch_db:update_doc(Db, Doc, [])). should_reject_non_object_updates(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_reject_non_object_updates">>}, - {<<"updates">>, <<"invalid">>} - ]}), - ?_assertThrow({bad_request, invalid_design_doc, _}, - couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_reject_non_object_updates">>}, + {<<"updates">>, <<"invalid">>} + ]} + ), + ?_assertThrow( + {bad_request, invalid_design_doc, _}, + couch_db:update_doc(Db, Doc, []) + ). should_accept_obj_in_updates(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_accept_obj_in_updates">>}, - {<<"updates">>, ?LIB} - ]}), + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_accept_obj_in_updates">>}, + {<<"updates">>, ?LIB} + ]} + ), ?_assertMatch({ok, _}, couch_db:update_doc(Db, Doc, [])). should_reject_non_object_views(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_reject_non_object_views">>}, - {<<"views">>, <<"invalid">>} - ]}), - ?_assertThrow({bad_request, invalid_design_doc, _}, - couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_reject_non_object_views">>}, + {<<"views">>, <<"invalid">>} + ]} + ), + ?_assertThrow( + {bad_request, invalid_design_doc, _}, + couch_db:update_doc(Db, Doc, []) + ). should_reject_non_string_language(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_reject_non_string_language">>}, - {<<"language">>, 1} - ]}), - ?_assertThrow({bad_request, invalid_design_doc, _}, - couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_reject_non_string_language">>}, + {<<"language">>, 1} + ]} + ), + ?_assertThrow( + {bad_request, invalid_design_doc, _}, + couch_db:update_doc(Db, Doc, []) + ). should_reject_non_string_validate_doc_update(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_reject_non_string_vdu">>}, - {<<"validate_doc_update">>, 1} - ]}), - ?_assertThrow({bad_request, invalid_design_doc, _}, - couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_reject_non_string_vdu">>}, + {<<"validate_doc_update">>, 1} + ]} + ), + ?_assertThrow( + {bad_request, invalid_design_doc, _}, + couch_db:update_doc(Db, Doc, []) + ). should_accept_string_rewrites(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_reject_non_array_rewrites">>}, - {<<"rewrites">>, <<"function(req){}">>} - ]}), - ?_assertMatch({ok,_}, couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_reject_non_array_rewrites">>}, + {<<"rewrites">>, <<"function(req){}">>} + ]} + ), + ?_assertMatch({ok, _}, couch_db:update_doc(Db, Doc, [])). should_reject_bad_rewrites(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_reject_non_array_rewrites">>}, - {<<"rewrites">>, 42} - ]}), - ?_assertThrow({bad_request, invalid_design_doc, _}, - couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_reject_non_array_rewrites">>}, + {<<"rewrites">>, 42} + ]} + ), + ?_assertThrow( + {bad_request, invalid_design_doc, _}, + couch_db:update_doc(Db, Doc, []) + ). should_accept_option(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_accept_options">>}, - {<<"options">>, {[ {<<"option1">>, <<"function(doc,req){}">>} ]}} - ]}), - ?_assertMatch({ok,_}, couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_accept_options">>}, + {<<"options">>, {[{<<"option1">>, <<"function(doc,req){}">>}]}} + ]} + ), + ?_assertMatch({ok, _}, couch_db:update_doc(Db, Doc, [])). should_accept_any_option(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_accept_any_option">>}, - {<<"options">>, {[ {<<"option1">>, true} ]}} - ]}), - ?_assertMatch({ok,_}, couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_accept_any_option">>}, + {<<"options">>, {[{<<"option1">>, true}]}} + ]} + ), + ?_assertMatch({ok, _}, couch_db:update_doc(Db, Doc, [])). should_accept_filter(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_accept_filters">>}, - {<<"filters">>, {[ {<<"filter1">>, <<"function(doc,req){}">>} ]}} - ]}), - ?_assertMatch({ok,_}, couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_accept_filters">>}, + {<<"filters">>, {[{<<"filter1">>, <<"function(doc,req){}">>}]}} + ]} + ), + ?_assertMatch({ok, _}, couch_db:update_doc(Db, Doc, [])). should_reject_non_string_or_obj_filter_function(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_reject_non_string_or_obj_filter_function">>}, - {<<"filters">>, {[ {<<"filter1">>, 1} ]}} - ]}), - ?_assertThrow({bad_request, invalid_design_doc, _}, - couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_reject_non_string_or_obj_filter_function">>}, + {<<"filters">>, {[{<<"filter1">>, 1}]}} + ]} + ), + ?_assertThrow( + {bad_request, invalid_design_doc, _}, + couch_db:update_doc(Db, Doc, []) + ). should_accept_list(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_accept_lists">>}, - {<<"lists">>, {[ {<<"list1">>, <<"function(doc,req){}">>} ]}} - ]}), - ?_assertMatch({ok,_}, couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_accept_lists">>}, + {<<"lists">>, {[{<<"list1">>, <<"function(doc,req){}">>}]}} + ]} + ), + ?_assertMatch({ok, _}, couch_db:update_doc(Db, Doc, [])). should_reject_non_string_or_obj_list_function(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_reject_non_string_or_obj_list_function">>}, - {<<"lists">>, {[ {<<"list1">>, 1} ]}} - ]}), - ?_assertThrow({bad_request, invalid_design_doc, _}, - couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_reject_non_string_or_obj_list_function">>}, + {<<"lists">>, {[{<<"list1">>, 1}]}} + ]} + ), + ?_assertThrow( + {bad_request, invalid_design_doc, _}, + couch_db:update_doc(Db, Doc, []) + ). should_accept_obj_in_lists(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_accept_obj_in_lists">>}, - {<<"lists">>, ?LIB} - ]}), + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_accept_obj_in_lists">>}, + {<<"lists">>, ?LIB} + ]} + ), ?_assertMatch({ok, _}, couch_db:update_doc(Db, Doc, [])). - should_accept_show(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_accept_shows">>}, - {<<"shows">>, {[ {<<"show1">>, <<"function(doc,req){}">>} ]}} - ]}), - ?_assertMatch({ok,_}, couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_accept_shows">>}, + {<<"shows">>, {[{<<"show1">>, <<"function(doc,req){}">>}]}} + ]} + ), + ?_assertMatch({ok, _}, couch_db:update_doc(Db, Doc, [])). should_reject_non_string_or_obj_show_function(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_reject_non_string_or_obj_show_function">>}, - {<<"shows">>, {[ {<<"show1">>, 1} ]}} - ]}), - ?_assertThrow({bad_request, invalid_design_doc, _}, - couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_reject_non_string_or_obj_show_function">>}, + {<<"shows">>, {[{<<"show1">>, 1}]}} + ]} + ), + ?_assertThrow( + {bad_request, invalid_design_doc, _}, + couch_db:update_doc(Db, Doc, []) + ). should_accept_update(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_accept_updates">>}, - {<<"updates">>, {[ {<<"update1">>, <<"function(doc,req){}">>} ]}} - ]}), - ?_assertMatch({ok,_}, couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_accept_updates">>}, + {<<"updates">>, {[{<<"update1">>, <<"function(doc,req){}">>}]}} + ]} + ), + ?_assertMatch({ok, _}, couch_db:update_doc(Db, Doc, [])). should_reject_non_string_or_obj_update_function(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_reject_non_string_or_obj_update_function">>}, - {<<"updates">>, {[ {<<"update1">>, 1} ]}} - ]}), - ?_assertThrow({bad_request, invalid_design_doc, _}, - couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_reject_non_string_or_obj_update_function">>}, + {<<"updates">>, {[{<<"update1">>, 1}]}} + ]} + ), + ?_assertThrow( + {bad_request, invalid_design_doc, _}, + couch_db:update_doc(Db, Doc, []) + ). should_accept_view(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_accept_view">>}, - {<<"views">>, {[ - {<<"view1">>, {[{<<"map">>, <<"function(d){}">>}]}} - ]}} - ]}), - ?_assertMatch({ok,_}, couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_accept_view">>}, + {<<"views">>, + {[ + {<<"view1">>, {[{<<"map">>, <<"function(d){}">>}]}} + ]}} + ]} + ), + ?_assertMatch({ok, _}, couch_db:update_doc(Db, Doc, [])). should_accept_view_with_reduce(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_accept_view_with_reduce">>}, - {<<"views">>, {[ - {<<"view1">>, {[ - {<<"map">>, <<"function(d){}">>}, - {<<"reduce">>,<<"function(d){}">>} - ]}} - ]}} - ]}), - ?_assertMatch({ok,_}, couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_accept_view_with_reduce">>}, + {<<"views">>, + {[ + {<<"view1">>, + {[ + {<<"map">>, <<"function(d){}">>}, + {<<"reduce">>, <<"function(d){}">>} + ]}} + ]}} + ]} + ), + ?_assertMatch({ok, _}, couch_db:update_doc(Db, Doc, [])). should_accept_view_with_lib(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_accept_view_with_lib">>}, - {<<"views">>, {[ - {<<"view1">>, {[ - {<<"map">>, <<"function(d){}">>} - ]}}, - {<<"lib">>, {[ - {<<"lib1">>, <<"x=42">>} - ]}} - ]}} - ]}), - ?_assertMatch({ok,_}, couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_accept_view_with_lib">>}, + {<<"views">>, + {[ + {<<"view1">>, + {[ + {<<"map">>, <<"function(d){}">>} + ]}}, + {<<"lib">>, + {[ + {<<"lib1">>, <<"x=42">>} + ]}} + ]}} + ]} + ), + ?_assertMatch({ok, _}, couch_db:update_doc(Db, Doc, [])). should_reject_view_that_is_not_an_object(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_reject_non_object_view">>}, - {<<"views">>, {[{<<"view1">>, <<"thisisbad">>}]}} - ]}), - ?_assertThrow({bad_request, invalid_design_doc, _}, - couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_reject_non_object_view">>}, + {<<"views">>, {[{<<"view1">>, <<"thisisbad">>}]}} + ]} + ), + ?_assertThrow( + {bad_request, invalid_design_doc, _}, + couch_db:update_doc(Db, Doc, []) + ). should_reject_view_without_map_function(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_accept_view_without_map">>}, - {<<"views">>, {[ - {<<"view1">>, {[]}} - ]}} - ]}), - ?_assertThrow({bad_request, invalid_design_doc, _}, - couch_db:update_doc(Db, Doc, [])). - + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_accept_view_without_map">>}, + {<<"views">>, + {[ + {<<"view1">>, {[]}} + ]}} + ]} + ), + ?_assertThrow( + {bad_request, invalid_design_doc, _}, + couch_db:update_doc(Db, Doc, []) + ). should_reject_view_with_non_string_map_function(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_reject_view_with_nonstr_map">>}, - {<<"views">>, {[ - {<<"view1">>, {[ - {<<"map">>,{[]}} - ]}} - ]}} - ]}), - ?_assertThrow({bad_request, invalid_design_doc, _}, - couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_reject_view_with_nonstr_map">>}, + {<<"views">>, + {[ + {<<"view1">>, + {[ + {<<"map">>, {[]}} + ]}} + ]}} + ]} + ), + ?_assertThrow( + {bad_request, invalid_design_doc, _}, + couch_db:update_doc(Db, Doc, []) + ). should_reject_view_with_non_string_reduce_function(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_reject_view_with_nonstr_reduce">>}, - {<<"views">>, {[ - {<<"view1">>, {[ - {<<"map">>,<<"function(d){}">>}, - {<<"reduce">>,1} - ]}} - ]}} - ]}), - ?_assertThrow({bad_request, invalid_design_doc, _}, - couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_reject_view_with_nonstr_reduce">>}, + {<<"views">>, + {[ + {<<"view1">>, + {[ + {<<"map">>, <<"function(d){}">>}, + {<<"reduce">>, 1} + ]}} + ]}} + ]} + ), + ?_assertThrow( + {bad_request, invalid_design_doc, _}, + couch_db:update_doc(Db, Doc, []) + ). should_accept_any_in_lib(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_accept_any_in_lib">>}, - {<<"views">>, {[ - {<<"view1">>, {[ - {<<"map">>, <<"function(d){}">>} - ]}}, - {<<"lib">>, {[{<<"lib1">>, {[]}}]}} - ]}} - ]}), - ?_assertMatch({ok,_}, couch_db:update_doc(Db, Doc, [])). - + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_accept_any_in_lib">>}, + {<<"views">>, + {[ + {<<"view1">>, + {[ + {<<"map">>, <<"function(d){}">>} + ]}}, + {<<"lib">>, {[{<<"lib1">>, {[]}}]}} + ]}} + ]} + ), + ?_assertMatch({ok, _}, couch_db:update_doc(Db, Doc, [])). should_accept_map_object_for_queries(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_accept_map_objects_for_queries">>}, - {<<"language">>, <<"query">>}, - {<<"views">>, {[ - {<<"view1">>, {[ - {<<"map">>, {[ - {<<"x">>, <<"y">>} + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_accept_map_objects_for_queries">>}, + {<<"language">>, <<"query">>}, + {<<"views">>, + {[ + {<<"view1">>, + {[ + {<<"map">>, + {[ + {<<"x">>, <<"y">>} + ]}} + ]}} ]}} - ]}} - ]}} - ]}), - ?_assertMatch({ok,_}, couch_db:update_doc(Db, Doc, [])). - + ]} + ), + ?_assertMatch({ok, _}, couch_db:update_doc(Db, Doc, [])). should_reject_map_non_objects_for_queries(Db) -> - Doc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/should_reject_map_non_objects__with_nonstr_reduce">>}, - {<<"language">>, <<"query">>}, - {<<"views">>, {[ - {<<"view1">>, {[ - {<<"map">>, <<"function(d){}">>} - ]}} - ]}} - ]}), - ?_assertThrow({bad_request, invalid_design_doc, _}, - couch_db:update_doc(Db, Doc, [])). + Doc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/should_reject_map_non_objects__with_nonstr_reduce">>}, + {<<"language">>, <<"query">>}, + {<<"views">>, + {[ + {<<"view1">>, + {[ + {<<"map">>, <<"function(d){}">>} + ]}} + ]}} + ]} + ), + ?_assertThrow( + {bad_request, invalid_design_doc, _}, + couch_db:update_doc(Db, Doc, []) + ). diff --git a/src/couch_mrview/test/eunit/couch_mrview_design_docs_tests.erl b/src/couch_mrview/test/eunit/couch_mrview_design_docs_tests.erl index aedd42865..b1de2839d 100644 --- a/src/couch_mrview/test/eunit/couch_mrview_design_docs_tests.erl +++ b/src/couch_mrview/test/eunit/couch_mrview_design_docs_tests.erl @@ -17,8 +17,6 @@ -define(TIMEOUT, 1000). - - setup() -> {ok, Db} = couch_mrview_test_util:init_db(?tempdb(), design), Db. @@ -28,16 +26,17 @@ teardown(Db) -> couch_server:delete(couch_db:name(Db), [?ADMIN_CTX]), ok. - design_docs_test_() -> { "_design_docs view tests", { setup, - fun test_util:start_couch/0, fun test_util:stop_couch/1, + fun test_util:start_couch/0, + fun test_util:stop_couch/1, { foreach, - fun setup/0, fun teardown/1, + fun setup/0, + fun teardown/1, [ fun should_query/1, fun should_query_with_range/1, @@ -49,22 +48,22 @@ design_docs_test_() -> } }. - should_query(Db) -> Result = run_query(Db, []), - Expect = {ok, [ - {meta, [{total, 10}, {offset, 10}]}, - mk_row(<<"_design/bar01">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), - mk_row(<<"_design/bar02">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), - mk_row(<<"_design/bar03">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), - mk_row(<<"_design/bar04">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), - mk_row(<<"_design/bar05">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), - mk_row(<<"_design/bar06">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), - mk_row(<<"_design/bar07">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), - mk_row(<<"_design/bar08">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), - mk_row(<<"_design/bar09">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), - mk_row(<<"_design/bar10">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>) - ]}, + Expect = + {ok, [ + {meta, [{total, 10}, {offset, 10}]}, + mk_row(<<"_design/bar01">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), + mk_row(<<"_design/bar02">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), + mk_row(<<"_design/bar03">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), + mk_row(<<"_design/bar04">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), + mk_row(<<"_design/bar05">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), + mk_row(<<"_design/bar06">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), + mk_row(<<"_design/bar07">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), + mk_row(<<"_design/bar08">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), + mk_row(<<"_design/bar09">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), + mk_row(<<"_design/bar10">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>) + ]}, ?_assertEqual(Expect, Result). should_query_with_range(Db) -> @@ -72,26 +71,29 @@ should_query_with_range(Db) -> {start_key, <<"_design/bar03">>}, {end_key, <<"_design/bar05">>} ]), - Expect = {ok, [ - {meta, [{total, 10}, {offset, 12}]}, - mk_row(<<"_design/bar03">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), - mk_row(<<"_design/bar04">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), - mk_row(<<"_design/bar05">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>) - ]}, + Expect = + {ok, [ + {meta, [{total, 10}, {offset, 12}]}, + mk_row(<<"_design/bar03">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), + mk_row(<<"_design/bar04">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), + mk_row(<<"_design/bar05">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>) + ]}, ?_assertEqual(Expect, Result). should_query_with_range_rev(Db) -> Result = run_query(Db, [ {direction, rev}, - {start_key, <<"_design/bar05">>}, {end_key, <<"_design/bar03">>}, + {start_key, <<"_design/bar05">>}, + {end_key, <<"_design/bar03">>}, {inclusive_end, true} ]), - Expect = {ok, [ - {meta, [{total, 10}, {offset, 5}]}, - mk_row(<<"_design/bar05">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), - mk_row(<<"_design/bar04">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), - mk_row(<<"_design/bar03">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>) - ]}, + Expect = + {ok, [ + {meta, [{total, 10}, {offset, 5}]}, + mk_row(<<"_design/bar05">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), + mk_row(<<"_design/bar04">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), + mk_row(<<"_design/bar03">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>) + ]}, ?_assertEqual(Expect, Result). should_query_with_limit_and_skip(Db) -> @@ -100,12 +102,13 @@ should_query_with_limit_and_skip(Db) -> {limit, 3}, {skip, 3} ]), - Expect = {ok, [ - {meta, [{total, 10}, {offset, 14}]}, - mk_row(<<"_design/bar05">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), - mk_row(<<"_design/bar06">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), - mk_row(<<"_design/bar07">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>) - ]}, + Expect = + {ok, [ + {meta, [{total, 10}, {offset, 14}]}, + mk_row(<<"_design/bar05">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), + mk_row(<<"_design/bar06">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>), + mk_row(<<"_design/bar07">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>) + ]}, ?_assertEqual(Expect, Result). should_query_with_include_docs(Db) -> @@ -114,20 +117,25 @@ should_query_with_include_docs(Db) -> {end_key, <<"_design/bar08">>}, {include_docs, true} ]), - Doc = {[ - {<<"_id">>,<<"_design/bar08">>}, - {<<"_rev">>,<<"1-0b24e44a44af45e51e562fd124ce3007">>}, - {<<"views">>,{[]}} - ]}, + Doc = + {[ + {<<"_id">>, <<"_design/bar08">>}, + {<<"_rev">>, <<"1-0b24e44a44af45e51e562fd124ce3007">>}, + {<<"views">>, {[]}} + ]}, Val = {[{rev, <<"1-0b24e44a44af45e51e562fd124ce3007">>}]}, - Expect = {ok, [ - {meta, [{total, 10}, {offset, 17}]}, - {row, [{id, <<"_design/bar08">>}, {key, <<"_design/bar08">>}, - {value, Val}, {doc, Doc}]} - ]}, + Expect = + {ok, [ + {meta, [{total, 10}, {offset, 17}]}, + {row, [ + {id, <<"_design/bar08">>}, + {key, <<"_design/bar08">>}, + {value, Val}, + {doc, Doc} + ]} + ]}, ?_assertEqual(Expect, Result). - mk_row(Id, Rev) -> {row, [{id, Id}, {key, Id}, {value, {[{rev, Rev}]}}]}. diff --git a/src/couch_mrview/test/eunit/couch_mrview_http_tests.erl b/src/couch_mrview/test/eunit/couch_mrview_http_tests.erl index bd11c7ad8..bfa4965a4 100644 --- a/src/couch_mrview/test/eunit/couch_mrview_http_tests.erl +++ b/src/couch_mrview/test/eunit/couch_mrview_http_tests.erl @@ -15,14 +15,23 @@ -include_lib("couch/include/couch_eunit.hrl"). -include_lib("couch_mrview/include/couch_mrview.hrl"). - mrview_http_test_() -> [ - ?_assertEqual(#mrargs{group_level=undefined, group=true}, - couch_mrview_http:parse_params([{"group", "true"}], - undefined, #mrargs{})), + ?_assertEqual( + #mrargs{group_level = undefined, group = true}, + couch_mrview_http:parse_params( + [{"group", "true"}], + undefined, + #mrargs{} + ) + ), - ?_assertEqual(#mrargs{group_level=1, group=undefined}, - couch_mrview_http:parse_params([{"group_level", "1"}], - undefined, #mrargs{})) + ?_assertEqual( + #mrargs{group_level = 1, group = undefined}, + couch_mrview_http:parse_params( + [{"group_level", "1"}], + undefined, + #mrargs{} + ) + ) ]. diff --git a/src/couch_mrview/test/eunit/couch_mrview_index_info_tests.erl b/src/couch_mrview/test/eunit/couch_mrview_index_info_tests.erl index c4c765feb..bf64eaea3 100644 --- a/src/couch_mrview/test/eunit/couch_mrview_index_info_tests.erl +++ b/src/couch_mrview/test/eunit/couch_mrview_index_info_tests.erl @@ -17,20 +17,17 @@ -define(TIMEOUT, 1000). - setup() -> {ok, Db} = couch_mrview_test_util:init_db(?tempdb(), map), couch_mrview:query_view(Db, <<"_design/bar">>, <<"baz">>), {ok, Info} = couch_mrview:get_info(Db, <<"_design/bar">>), {Db, Info}. - teardown({Db, _}) -> couch_db:close(Db), couch_server:delete(couch_db:name(Db), [?ADMIN_CTX]), ok. - view_info_test_() -> { "Views index tests", @@ -57,50 +54,41 @@ view_info_test_() -> } }. - sig_is_binary({_, Info}) -> ?_assert(is_binary(prop(signature, Info))). - language_is_js({_, Info}) -> ?_assertEqual(<<"javascript">>, prop(language, Info)). - file_size_is_non_neg_int({_, Info}) -> ?_assert(check_non_neg_int([sizes, file], Info)). - active_size_is_non_neg_int({_, Info}) -> ?_assert(check_non_neg_int([sizes, active], Info)). - external_size_is_non_neg_int({_, Info}) -> ?_assert(check_non_neg_int([sizes, external], Info)). - active_size_less_than_file_size({_, Info}) -> ?_assert(prop([sizes, active], Info) < prop([sizes, file], Info)). - update_seq_is_non_neg_int({_, Info}) -> ?_assert(check_non_neg_int(update_seq, Info)). - purge_seq_is_non_neg_int({_, Info}) -> ?_assert(check_non_neg_int(purge_seq, Info)). - update_opts_is_bin_list({_, Info}) -> Opts = prop(update_options, Info), - ?_assert(is_list(Opts) andalso - (Opts == [] orelse lists:all([is_binary(B) || B <- Opts]))). - + ?_assert( + is_list(Opts) andalso + (Opts == [] orelse lists:all([is_binary(B) || B <- Opts])) + ). check_non_neg_int(Key, Info) -> Size = prop(Key, Info), is_integer(Size) andalso Size >= 0. - prop(Key, {Props}) when is_list(Props) -> prop(Key, Props); prop([Key], Info) -> diff --git a/src/couch_mrview/test/eunit/couch_mrview_local_docs_tests.erl b/src/couch_mrview/test/eunit/couch_mrview_local_docs_tests.erl index b0d25469a..7c812eeb0 100644 --- a/src/couch_mrview/test/eunit/couch_mrview_local_docs_tests.erl +++ b/src/couch_mrview/test/eunit/couch_mrview_local_docs_tests.erl @@ -17,8 +17,6 @@ -define(TIMEOUT, 1000). - - setup() -> {ok, Db} = couch_mrview_test_util:init_db(?tempdb(), local), Db. @@ -28,16 +26,17 @@ teardown(Db) -> couch_server:delete(couch_db:name(Db), [?ADMIN_CTX]), ok. - all_docs_test_() -> { "_local_docs view tests", { setup, - fun test_util:start_couch/0, fun test_util:stop_couch/1, + fun test_util:start_couch/0, + fun test_util:stop_couch/1, { foreach, - fun setup/0, fun teardown/1, + fun setup/0, + fun teardown/1, [ fun should_query/1, fun should_query_with_range/1, @@ -50,22 +49,22 @@ all_docs_test_() -> } }. - should_query(Db) -> Result = run_query(Db, []), - Expect = {ok, [ - {meta, [{total, null}, {offset, null}]}, - mk_row(1), - mk_row(10), - mk_row(2), - mk_row(3), - mk_row(4), - mk_row(5), - mk_row(6), - mk_row(7), - mk_row(8), - mk_row(9) - ]}, + Expect = + {ok, [ + {meta, [{total, null}, {offset, null}]}, + mk_row(1), + mk_row(10), + mk_row(2), + mk_row(3), + mk_row(4), + mk_row(5), + mk_row(6), + mk_row(7), + mk_row(8), + mk_row(9) + ]}, ?_assertEqual(Expect, Result). should_query_with_range(Db) -> @@ -73,26 +72,29 @@ should_query_with_range(Db) -> {start_key, <<"_local/3">>}, {end_key, <<"_local/5">>} ]), - Expect = {ok, [ - {meta, [{total, null}, {offset, null}]}, - mk_row(3), - mk_row(4), - mk_row(5) - ]}, + Expect = + {ok, [ + {meta, [{total, null}, {offset, null}]}, + mk_row(3), + mk_row(4), + mk_row(5) + ]}, ?_assertEqual(Expect, Result). should_query_with_range_rev(Db) -> Result = run_query(Db, [ {direction, rev}, - {start_key, <<"_local/5">>}, {end_key, <<"_local/3">>}, + {start_key, <<"_local/5">>}, + {end_key, <<"_local/3">>}, {inclusive_end, true} ]), - Expect = {ok, [ - {meta, [{total, null}, {offset, null}]}, - mk_row(5), - mk_row(4), - mk_row(3) - ]}, + Expect = + {ok, [ + {meta, [{total, null}, {offset, null}]}, + mk_row(5), + mk_row(4), + mk_row(3) + ]}, ?_assertEqual(Expect, Result). should_query_with_limit_and_skip(Db) -> @@ -101,12 +103,13 @@ should_query_with_limit_and_skip(Db) -> {limit, 3}, {skip, 3} ]), - Expect = {ok, [ - {meta, [{total, null}, {offset, null}]}, - mk_row(5), - mk_row(6), - mk_row(7) - ]}, + Expect = + {ok, [ + {meta, [{total, null}, {offset, null}]}, + mk_row(5), + mk_row(6), + mk_row(7) + ]}, ?_assertEqual(Expect, Result). should_query_with_include_docs(Db) -> @@ -116,15 +119,21 @@ should_query_with_include_docs(Db) -> {include_docs, true} ]), {row, Doc0} = mk_row(8), - Doc = Doc0 ++ [{doc, {[ - {<<"_id">>, <<"_local/8">>}, - {<<"_rev">>, <<"0-1">>}, - {<<"val">>, 8} - ]}}], - Expect = {ok, [ - {meta, [{total, null}, {offset, null}]}, - {row, Doc} - ]}, + Doc = + Doc0 ++ + [ + {doc, + {[ + {<<"_id">>, <<"_local/8">>}, + {<<"_rev">>, <<"0-1">>}, + {<<"val">>, 8} + ]}} + ], + Expect = + {ok, [ + {meta, [{total, null}, {offset, null}]}, + {row, Doc} + ]}, ?_assertEqual(Expect, Result). should_query_with_update_seq(Db) -> @@ -133,10 +142,11 @@ should_query_with_update_seq(Db) -> {limit, 1}, {update_seq, true} ]), - Expect = {ok, [ - {meta, [{total, null}, {offset, null}, {update_seq, null}]}, - mk_row(2) - ]}, + Expect = + {ok, [ + {meta, [{total, null}, {offset, null}, {update_seq, null}]}, + mk_row(2) + ]}, ?_assertEqual(Expect, Result). mk_row(IntId) -> diff --git a/src/couch_mrview/test/eunit/couch_mrview_map_views_tests.erl b/src/couch_mrview/test/eunit/couch_mrview_map_views_tests.erl index 805dc6c74..0f8357a98 100644 --- a/src/couch_mrview/test/eunit/couch_mrview_map_views_tests.erl +++ b/src/couch_mrview/test/eunit/couch_mrview_map_views_tests.erl @@ -17,7 +17,6 @@ -define(TIMEOUT, 1000). - setup() -> {ok, Db} = couch_mrview_test_util:init_db(?tempdb(), map), Db. @@ -27,16 +26,17 @@ teardown(Db) -> couch_server:delete(couch_db:name(Db), [?ADMIN_CTX]), ok. - map_views_test_() -> { "Map views", { setup, - fun test_util:start_couch/0, fun test_util:stop_couch/1, + fun test_util:start_couch/0, + fun test_util:stop_couch/1, { foreach, - fun setup/0, fun teardown/1, + fun setup/0, + fun teardown/1, [ fun should_map/1, fun should_map_with_range/1, @@ -49,36 +49,38 @@ map_views_test_() -> } }. - should_map(Db) -> Result = run_query(Db, []), - Expect = {ok, [ - {meta, [{total, 10}, {offset, 0}]}, - {row, [{id, <<"1">>}, {key, 1}, {value, 1}]}, - {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, - {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, - {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, - {row, [{id, <<"5">>}, {key, 5}, {value, 5}]}, - {row, [{id, <<"6">>}, {key, 6}, {value, 6}]}, - {row, [{id, <<"7">>}, {key, 7}, {value, 7}]}, - {row, [{id, <<"8">>}, {key, 8}, {value, 8}]}, - {row, [{id, <<"9">>}, {key, 9}, {value, 9}]}, - {row, [{id, <<"10">>}, {key, 10}, {value, 10}]} - ]}, + Expect = + {ok, [ + {meta, [{total, 10}, {offset, 0}]}, + {row, [{id, <<"1">>}, {key, 1}, {value, 1}]}, + {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, + {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, + {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, + {row, [{id, <<"5">>}, {key, 5}, {value, 5}]}, + {row, [{id, <<"6">>}, {key, 6}, {value, 6}]}, + {row, [{id, <<"7">>}, {key, 7}, {value, 7}]}, + {row, [{id, <<"8">>}, {key, 8}, {value, 8}]}, + {row, [{id, <<"9">>}, {key, 9}, {value, 9}]}, + {row, [{id, <<"10">>}, {key, 10}, {value, 10}]} + ]}, ?_assertEqual(Expect, Result). should_map_with_range(Db) -> Result = run_query(Db, [ {direction, rev}, - {start_key, 5}, {end_key, 3}, + {start_key, 5}, + {end_key, 3}, {inclusive_end, true} ]), - Expect = {ok, [ - {meta, [{total, 10}, {offset, 5}]}, - {row, [{id, <<"5">>}, {key, 5}, {value, 5}]}, - {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, - {row, [{id, <<"3">>}, {key, 3}, {value, 3}]} - ]}, + Expect = + {ok, [ + {meta, [{total, 10}, {offset, 5}]}, + {row, [{id, <<"5">>}, {key, 5}, {value, 5}]}, + {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, + {row, [{id, <<"3">>}, {key, 3}, {value, 3}]} + ]}, ?_assertEqual(Expect, Result). should_map_with_limit_and_skip(Db) -> @@ -87,12 +89,13 @@ should_map_with_limit_and_skip(Db) -> {limit, 3}, {skip, 3} ]), - Expect = {ok, [ - {meta, [{total, 10}, {offset, 4}]}, - {row, [{id, <<"5">>}, {key, 5}, {value, 5}]}, - {row, [{id, <<"6">>}, {key, 6}, {value, 6}]}, - {row, [{id, <<"7">>}, {key, 7}, {value, 7}]} - ]}, + Expect = + {ok, [ + {meta, [{total, 10}, {offset, 4}]}, + {row, [{id, <<"5">>}, {key, 5}, {value, 5}]}, + {row, [{id, <<"6">>}, {key, 6}, {value, 6}]}, + {row, [{id, <<"7">>}, {key, 7}, {value, 7}]} + ]}, ?_assertEqual(Expect, Result). should_map_with_include_docs(Db) -> @@ -101,35 +104,41 @@ should_map_with_include_docs(Db) -> {end_key, 8}, {include_docs, true} ]), - Doc = {[ - {<<"_id">>,<<"8">>}, - {<<"_rev">>, <<"1-55b9a29311341e07ec0a7ca13bc1b59f">>}, - {<<"val">>,8} - ]}, - Expect = {ok, [ - {meta, [{total, 10}, {offset, 7}]}, - {row, [{id, <<"8">>}, {key, 8}, {value, 8}, {doc, Doc}]} - ]}, + Doc = + {[ + {<<"_id">>, <<"8">>}, + {<<"_rev">>, <<"1-55b9a29311341e07ec0a7ca13bc1b59f">>}, + {<<"val">>, 8} + ]}, + Expect = + {ok, [ + {meta, [{total, 10}, {offset, 7}]}, + {row, [{id, <<"8">>}, {key, 8}, {value, 8}, {doc, Doc}]} + ]}, ?_assertEqual(Expect, Result). should_map_empty_views(Db) -> Result = couch_mrview:query_view(Db, <<"_design/bar">>, <<"bing">>), - Expect = {ok, [ - {meta, [{total, 0}, {offset, 0}]} - ]}, + Expect = + {ok, [ + {meta, [{total, 0}, {offset, 0}]} + ]}, ?_assertEqual(Expect, Result). should_give_ext_size_seq_indexed_test(Db) -> - DDoc = couch_doc:from_json_obj({[ - {<<"_id">>, <<"_design/seqdoc">>}, - {<<"options">>, {[{<<"seq_indexed">>, true}]}}, - {<<"views">>, {[ - {<<"view1">>, {[ - {<<"map">>, <<"function(doc){emit(doc._id, doc._id);}">>} + DDoc = couch_doc:from_json_obj( + {[ + {<<"_id">>, <<"_design/seqdoc">>}, + {<<"options">>, {[{<<"seq_indexed">>, true}]}}, + {<<"views">>, + {[ + {<<"view1">>, + {[ + {<<"map">>, <<"function(doc){emit(doc._id, doc._id);}">>} + ]}} ]}} - ]} - } - ]}), + ]} + ), {ok, _} = couch_db:update_doc(Db, DDoc, []), {ok, Db1} = couch_db:open_int(couch_db:name(Db), []), {ok, DDoc1} = couch_db:open_doc(Db1, <<"_design/seqdoc">>, [ejson_body]), @@ -139,6 +148,5 @@ should_give_ext_size_seq_indexed_test(Db) -> ok = couch_db:close(Db1), ?_assert(is_number(Size)). - run_query(Db, Opts) -> couch_mrview:query_view(Db, <<"_design/bar">>, <<"baz">>, Opts). diff --git a/src/couch_mrview/test/eunit/couch_mrview_purge_docs_fabric_tests.erl b/src/couch_mrview/test/eunit/couch_mrview_purge_docs_fabric_tests.erl index b2969bba0..3207a3da3 100644 --- a/src/couch_mrview/test/eunit/couch_mrview_purge_docs_fabric_tests.erl +++ b/src/couch_mrview/test/eunit/couch_mrview_purge_docs_fabric_tests.erl @@ -17,20 +17,18 @@ -include_lib("mem3/include/mem3.hrl"). -include_lib("couch_mrview/include/couch_mrview.hrl"). --define(TIMEOUT, 60). % seconds - +% seconds +-define(TIMEOUT, 60). setup_all() -> Ctx = test_util:start_couch([fabric, mem3]), meck:new(couch_mrview_index, [passthrough]), Ctx. - teardown_all(Ctx) -> meck:unload(), test_util:stop_couch(Ctx). - setup() -> DbName = ?tempdb(), ok = fabric:create_db(DbName, [?ADMIN_CTX, {q, 1}]), @@ -40,11 +38,9 @@ setup() -> end), DbName. - teardown(DbName) -> ok = fabric:delete_db(DbName, [?ADMIN_CTX]). - view_purge_fabric_test_() -> { "Map views", @@ -64,152 +60,185 @@ view_purge_fabric_test_() -> } }. - test_purge_verify_index(DbName) -> - {timeout, ?TIMEOUT, ?_test(begin - Docs1 = couch_mrview_test_util:make_docs(normal, 5), - {ok, _} = fabric:update_docs(DbName, Docs1, [?ADMIN_CTX]), - {ok, _} = fabric:update_doc( - DbName, - couch_mrview_test_util:ddoc(map), - [?ADMIN_CTX] - ), - - Result1 = fabric:query_view(DbName, <<"bar">>, <<"baz">>, #mrargs{}), - Expect1 = {ok, [ - {meta, [{total, 5}, {offset, 0}]}, - {row, [{id, <<"1">>}, {key, 1}, {value, 1}]}, - {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, - {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, - {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, - {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} - ]}, - ?assertEqual(Expect1, Result1), - - {ok, #doc{body = {Props1}}} = get_local_purge_doc(DbName), - ?assertEqual(0, couch_util:get_value(<<"purge_seq">>, Props1)), - ShardNames = [Sh || #shard{name = Sh} <- mem3:local_shards(DbName)], - [ShardDbName | _Rest ] = ShardNames, - ?assertEqual(true, couch_mrview_index:verify_index_exists( - ShardDbName, Props1)), - - purge_docs(DbName, [<<"1">>]), - - Result2 = fabric:query_view(DbName, <<"bar">>, <<"baz">>, #mrargs{}), - Expect2 = {ok, [ - {meta, [{total, 4}, {offset, 0}]}, - {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, - {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, - {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, - {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} - ]}, - ?assertEqual(Expect2, Result2), - - {ok, #doc{body = {Props2}}} = get_local_purge_doc(DbName), - ?assertEqual(1, couch_util:get_value(<<"purge_seq">>, Props2)), - ?assertEqual(true, couch_mrview_index:verify_index_exists( - ShardDbName, Props2)) - end)}. - + {timeout, ?TIMEOUT, + ?_test(begin + Docs1 = couch_mrview_test_util:make_docs(normal, 5), + {ok, _} = fabric:update_docs(DbName, Docs1, [?ADMIN_CTX]), + {ok, _} = fabric:update_doc( + DbName, + couch_mrview_test_util:ddoc(map), + [?ADMIN_CTX] + ), + + Result1 = fabric:query_view(DbName, <<"bar">>, <<"baz">>, #mrargs{}), + Expect1 = + {ok, [ + {meta, [{total, 5}, {offset, 0}]}, + {row, [{id, <<"1">>}, {key, 1}, {value, 1}]}, + {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, + {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, + {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, + {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} + ]}, + ?assertEqual(Expect1, Result1), + + {ok, #doc{body = {Props1}}} = get_local_purge_doc(DbName), + ?assertEqual(0, couch_util:get_value(<<"purge_seq">>, Props1)), + ShardNames = [Sh || #shard{name = Sh} <- mem3:local_shards(DbName)], + [ShardDbName | _Rest] = ShardNames, + ?assertEqual( + true, + couch_mrview_index:verify_index_exists( + ShardDbName, Props1 + ) + ), + + purge_docs(DbName, [<<"1">>]), + + Result2 = fabric:query_view(DbName, <<"bar">>, <<"baz">>, #mrargs{}), + Expect2 = + {ok, [ + {meta, [{total, 4}, {offset, 0}]}, + {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, + {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, + {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, + {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} + ]}, + ?assertEqual(Expect2, Result2), + + {ok, #doc{body = {Props2}}} = get_local_purge_doc(DbName), + ?assertEqual(1, couch_util:get_value(<<"purge_seq">>, Props2)), + ?assertEqual( + true, + couch_mrview_index:verify_index_exists( + ShardDbName, Props2 + ) + ) + end)}. test_purge_hook_before_compaction(DbName) -> - {timeout, ?TIMEOUT, ?_test(begin - Docs1 = couch_mrview_test_util:make_docs(normal, 5), - {ok, _} = fabric:update_docs(DbName, Docs1, [?ADMIN_CTX]), - {ok, _} = fabric:update_doc( - DbName, - couch_mrview_test_util:ddoc(map), - [?ADMIN_CTX] - ), - - Result1 = fabric:query_view(DbName, <<"bar">>, <<"baz">>, #mrargs{}), - Expect1 = {ok, [ - {meta, [{total, 5}, {offset, 0}]}, - {row, [{id, <<"1">>}, {key, 1}, {value, 1}]}, - {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, - {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, - {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, - {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} - ]}, - ?assertEqual(Expect1, Result1), - - purge_docs(DbName, [<<"1">>]), - - Result2 = fabric:query_view(DbName, <<"bar">>, <<"baz">>, #mrargs{}), - Expect2 = {ok, [ - {meta, [{total, 4}, {offset, 0}]}, - {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, - {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, - {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, - {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} - ]}, - ?assertEqual(Expect2, Result2), - - {ok, #doc{body = {Props1}}} = get_local_purge_doc(DbName), - ?assertEqual(1, couch_util:get_value(<<"purge_seq">>, Props1)), - - [ShardName | _] = local_shards(DbName), - couch_util:with_db(ShardName, fun(Db) -> - {ok, _} = couch_db:start_compact(Db) - end), - wait_compaction(ShardName, ?LINE), - - ?assertEqual(ok, meck:wait(1, couch_mrview_index, - ensure_local_purge_docs, '_', 5000) - ), - - % Make sure compaction didn't change the update seq - {ok, #doc{body = {Props1}}} = get_local_purge_doc(DbName), - ?assertEqual(1, couch_util:get_value(<<"purge_seq">>, Props1)), - - purge_docs(DbName, [<<"2">>]), - - couch_util:with_db(ShardName, fun(Db) -> - {ok, _} = couch_db:start_compact(Db) - end), - wait_compaction(ShardName, ?LINE), - - ?assertEqual(ok, meck:wait(2, couch_mrview_index, - ensure_local_purge_docs, '_', 5000) - ), - - % Make sure compaction after a purge didn't overwrite - % the local purge doc for the index - {ok, #doc{body = {Props2}}} = get_local_purge_doc(DbName), - ?assertEqual(1, couch_util:get_value(<<"purge_seq">>, Props2)), - - % Force another update to ensure that we update - % the local doc appropriate after compaction - Result3 = fabric:query_view(DbName, <<"bar">>, <<"baz">>, #mrargs{}), - Expect3 = {ok, [ - {meta, [{total, 3}, {offset, 0}]}, - {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, - {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, - {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} - ]}, - ?assertEqual(Expect3, Result3), - - {ok, #doc{body = {Props3}}} = get_local_purge_doc(DbName), - ?assertEqual(2, couch_util:get_value(<<"purge_seq">>, Props3)), - - % Check that if the local doc doesn't exist that one - % is created for the index on compaction - delete_local_purge_doc(DbName), - ?assertMatch({not_found, _}, get_local_purge_doc(DbName)), - - couch_util:with_db(ShardName, fun(Db) -> - {ok, _} = couch_db:start_compact(Db) - end), - wait_compaction(ShardName, ?LINE), - - ?assertEqual(ok, meck:wait(3, couch_mrview_index, - ensure_local_purge_docs, '_', 5000) - ), - - {ok, #doc{body = {Props4}}} = get_local_purge_doc(DbName), - ?assertEqual(2, couch_util:get_value(<<"purge_seq">>, Props4)) - end)}. - + {timeout, ?TIMEOUT, + ?_test(begin + Docs1 = couch_mrview_test_util:make_docs(normal, 5), + {ok, _} = fabric:update_docs(DbName, Docs1, [?ADMIN_CTX]), + {ok, _} = fabric:update_doc( + DbName, + couch_mrview_test_util:ddoc(map), + [?ADMIN_CTX] + ), + + Result1 = fabric:query_view(DbName, <<"bar">>, <<"baz">>, #mrargs{}), + Expect1 = + {ok, [ + {meta, [{total, 5}, {offset, 0}]}, + {row, [{id, <<"1">>}, {key, 1}, {value, 1}]}, + {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, + {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, + {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, + {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} + ]}, + ?assertEqual(Expect1, Result1), + + purge_docs(DbName, [<<"1">>]), + + Result2 = fabric:query_view(DbName, <<"bar">>, <<"baz">>, #mrargs{}), + Expect2 = + {ok, [ + {meta, [{total, 4}, {offset, 0}]}, + {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, + {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, + {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, + {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} + ]}, + ?assertEqual(Expect2, Result2), + + {ok, #doc{body = {Props1}}} = get_local_purge_doc(DbName), + ?assertEqual(1, couch_util:get_value(<<"purge_seq">>, Props1)), + + [ShardName | _] = local_shards(DbName), + couch_util:with_db(ShardName, fun(Db) -> + {ok, _} = couch_db:start_compact(Db) + end), + wait_compaction(ShardName, ?LINE), + + ?assertEqual( + ok, + meck:wait( + 1, + couch_mrview_index, + ensure_local_purge_docs, + '_', + 5000 + ) + ), + + % Make sure compaction didn't change the update seq + {ok, #doc{body = {Props1}}} = get_local_purge_doc(DbName), + ?assertEqual(1, couch_util:get_value(<<"purge_seq">>, Props1)), + + purge_docs(DbName, [<<"2">>]), + + couch_util:with_db(ShardName, fun(Db) -> + {ok, _} = couch_db:start_compact(Db) + end), + wait_compaction(ShardName, ?LINE), + + ?assertEqual( + ok, + meck:wait( + 2, + couch_mrview_index, + ensure_local_purge_docs, + '_', + 5000 + ) + ), + + % Make sure compaction after a purge didn't overwrite + % the local purge doc for the index + {ok, #doc{body = {Props2}}} = get_local_purge_doc(DbName), + ?assertEqual(1, couch_util:get_value(<<"purge_seq">>, Props2)), + + % Force another update to ensure that we update + % the local doc appropriate after compaction + Result3 = fabric:query_view(DbName, <<"bar">>, <<"baz">>, #mrargs{}), + Expect3 = + {ok, [ + {meta, [{total, 3}, {offset, 0}]}, + {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, + {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, + {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} + ]}, + ?assertEqual(Expect3, Result3), + + {ok, #doc{body = {Props3}}} = get_local_purge_doc(DbName), + ?assertEqual(2, couch_util:get_value(<<"purge_seq">>, Props3)), + + % Check that if the local doc doesn't exist that one + % is created for the index on compaction + delete_local_purge_doc(DbName), + ?assertMatch({not_found, _}, get_local_purge_doc(DbName)), + + couch_util:with_db(ShardName, fun(Db) -> + {ok, _} = couch_db:start_compact(Db) + end), + wait_compaction(ShardName, ?LINE), + + ?assertEqual( + ok, + meck:wait( + 3, + couch_mrview_index, + ensure_local_purge_docs, + '_', + 5000 + ) + ), + + {ok, #doc{body = {Props4}}} = get_local_purge_doc(DbName), + ?assertEqual(2, couch_util:get_value(<<"purge_seq">>, Props4)) + end)}. get_local_purge_doc(DbName) -> {ok, DDoc} = fabric:open_doc(DbName, <<"_design/bar">>, []), @@ -222,7 +251,6 @@ get_local_purge_doc(DbName) -> couch_db:open_doc(Db, DocId, []) end). - delete_local_purge_doc(DbName) -> {ok, DDoc} = fabric:open_doc(DbName, <<"_design/bar">>, []), {ok, IdxState} = couch_mrview_util:ddoc_to_mrst(DbName, DDoc), @@ -235,21 +263,21 @@ delete_local_purge_doc(DbName) -> {ok, _} = couch_db:update_doc(Db, NewDoc, []) end). - get_rev(#full_doc_info{} = FDI) -> #doc_info{ revs = [#rev_info{} = PrevRev | _] } = couch_doc:to_doc_info(FDI), PrevRev#rev_info.rev. - purge_docs(DbName, DocIds) -> - lists:foreach(fun(DocId) -> - FDI = fabric:get_full_doc_info(DbName, DocId, []), - Rev = get_rev(FDI), - {ok, [{ok, _}]} = fabric:purge_docs(DbName, [{DocId, [Rev]}], []) - end, DocIds). - + lists:foreach( + fun(DocId) -> + FDI = fabric:get_full_doc_info(DbName, DocId, []), + Rev = get_rev(FDI), + {ok, [{ok, _}]} = fabric:purge_docs(DbName, [{DocId, [Rev]}], []) + end, + DocIds + ). wait_compaction(DbName, Line) -> WaitFun = fun() -> @@ -260,23 +288,23 @@ wait_compaction(DbName, Line) -> end, case test_util:wait(WaitFun, 10000) of timeout -> - erlang:error({assertion_failed, [ + erlang:error( + {assertion_failed, [ {module, ?MODULE}, {line, Line}, {reason, "Timeout waiting for database compaction"} - ]}); + ]} + ); _ -> ok end. - is_compaction_running(DbName) -> {ok, DbInfo} = couch_util:with_db(DbName, fun(Db) -> couch_db:get_db_info(Db) end), couch_util:get_value(compact_running, DbInfo). - local_shards(DbName) -> try [ShardName || #shard{name = ShardName} <- mem3:local_shards(DbName)] diff --git a/src/couch_mrview/test/eunit/couch_mrview_purge_docs_tests.erl b/src/couch_mrview/test/eunit/couch_mrview_purge_docs_tests.erl index 62e1410cb..63c5de458 100644 --- a/src/couch_mrview/test/eunit/couch_mrview_purge_docs_tests.erl +++ b/src/couch_mrview/test/eunit/couch_mrview_purge_docs_tests.erl @@ -18,7 +18,6 @@ -define(TIMEOUT, 1000). - setup() -> meck:new(couch_index_updater, [passthrough]), {ok, Db} = couch_mrview_test_util:init_db(?tempdb(), map, 5), @@ -57,51 +56,52 @@ view_purge_test_() -> } }. - test_purge_single(Db) -> ?_test(begin Result = run_query(Db, []), - Expect = {ok, [ - {meta, [{total, 5}, {offset, 0}]}, - {row, [{id, <<"1">>}, {key, 1}, {value, 1}]}, - {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, - {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, - {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, - {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} - ]}, + Expect = + {ok, [ + {meta, [{total, 5}, {offset, 0}]}, + {row, [{id, <<"1">>}, {key, 1}, {value, 1}]}, + {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, + {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, + {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, + {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} + ]}, ?assertEqual(Expect, Result), FDI = couch_db:get_full_doc_info(Db, <<"1">>), Rev = get_rev(FDI), {ok, [{ok, _PRevs}]} = couch_db:purge_docs( - Db, - [{<<"UUID1">>, <<"1">>, [Rev]}] - ), + Db, + [{<<"UUID1">>, <<"1">>, [Rev]}] + ), {ok, Db2} = couch_db:reopen(Db), Result2 = run_query(Db2, []), - Expect2 = {ok, [ - {meta, [{total, 4}, {offset, 0}]}, - {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, - {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, - {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, - {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} - ]}, + Expect2 = + {ok, [ + {meta, [{total, 4}, {offset, 0}]}, + {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, + {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, + {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, + {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} + ]}, ?assertEqual(Expect2, Result2) end). - test_purge_single_for_docid_with_list(Db) -> ?_test(begin Result = run_query(Db, []), - Expect = {ok, [ - {meta, [{total, 5}, {offset, 0}]}, - {row, [{id, <<"1">>}, {key, 1}, {value, 1}]}, - {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, - {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, - {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, - {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} - ]}, + Expect = + {ok, [ + {meta, [{total, 5}, {offset, 0}]}, + {row, [{id, <<"1">>}, {key, 1}, {value, 1}]}, + {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, + {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, + {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, + {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} + ]}, ?assertEqual(Expect, Result), FDI = couch_db:get_full_doc_info(Db, <<"1">>), @@ -113,35 +113,39 @@ test_purge_single_for_docid_with_list(Db) -> {ok, Db2} = couch_db:reopen(Db), Result2 = run_query(Db2, []), - Expect2 = {ok, [ - {meta, [{total, 4}, {offset, 0}]}, - {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, - {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, - {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, - {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} - ]}, + Expect2 = + {ok, [ + {meta, [{total, 4}, {offset, 0}]}, + {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, + {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, + {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, + {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} + ]}, ?assertEqual(Expect2, Result2) end). test_purge_partial(Db) -> ?_test(begin Result = run_query(Db, []), - Expect = {ok, [ - {meta, [{total, 5}, {offset, 0}]}, - {row, [{id, <<"1">>}, {key, 1}, {value, 1}]}, - {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, - {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, - {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, - {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} - ]}, + Expect = + {ok, [ + {meta, [{total, 5}, {offset, 0}]}, + {row, [{id, <<"1">>}, {key, 1}, {value, 1}]}, + {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, + {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, + {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, + {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} + ]}, ?assertEqual(Expect, Result), - FDI1 = couch_db:get_full_doc_info(Db, <<"1">>), Rev1 = get_rev(FDI1), - Update = {[ - {'_id', <<"1">>}, - {'_rev', couch_doc:rev_to_str({1, [couch_hash:md5_hash(<<"1.2">>)]})}, - {'val', 1.2} - ]}, + FDI1 = couch_db:get_full_doc_info(Db, <<"1">>), + Rev1 = get_rev(FDI1), + Update = + {[ + {'_id', <<"1">>}, + {'_rev', couch_doc:rev_to_str({1, [couch_hash:md5_hash(<<"1.2">>)]})}, + {'val', 1.2} + ]}, {ok, [_Rev2]} = save_docs(Db, [Update], [replicated_changes]), PurgeInfos = [{<<"UUID1">>, <<"1">>, [Rev1]}], @@ -150,34 +154,38 @@ test_purge_partial(Db) -> {ok, Db2} = couch_db:reopen(Db), Result2 = run_query(Db2, []), - Expect2 = {ok, [ - {meta, [{total, 5}, {offset, 0}]}, - {row, [{id, <<"1">>}, {key, 1.2}, {value, 1.2}]}, - {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, - {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, - {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, - {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} - ]}, + Expect2 = + {ok, [ + {meta, [{total, 5}, {offset, 0}]}, + {row, [{id, <<"1">>}, {key, 1.2}, {value, 1.2}]}, + {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, + {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, + {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, + {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} + ]}, ?assertEqual(Expect2, Result2) end). - test_purge_complete(Db) -> ?_test(begin Result = run_query(Db, []), - Expect = {ok, [ - {meta, [{total, 5}, {offset, 0}]}, - {row, [{id, <<"1">>}, {key, 1}, {value, 1}]}, - {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, - {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, - {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, - {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} - ]}, + Expect = + {ok, [ + {meta, [{total, 5}, {offset, 0}]}, + {row, [{id, <<"1">>}, {key, 1}, {value, 1}]}, + {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, + {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, + {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, + {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} + ]}, ?assertEqual(Expect, Result), - FDI1 = couch_db:get_full_doc_info(Db, <<"1">>), Rev1 = get_rev(FDI1), - FDI2 = couch_db:get_full_doc_info(Db, <<"2">>), Rev2 = get_rev(FDI2), - FDI5 = couch_db:get_full_doc_info(Db, <<"5">>), Rev5 = get_rev(FDI5), + FDI1 = couch_db:get_full_doc_info(Db, <<"1">>), + Rev1 = get_rev(FDI1), + FDI2 = couch_db:get_full_doc_info(Db, <<"2">>), + Rev2 = get_rev(FDI2), + FDI5 = couch_db:get_full_doc_info(Db, <<"5">>), + Rev5 = get_rev(FDI5), PurgeInfos = [ {<<"UUID1">>, <<"1">>, [Rev1]}, @@ -188,31 +196,35 @@ test_purge_complete(Db) -> {ok, Db2} = couch_db:reopen(Db), Result2 = run_query(Db2, []), - Expect2 = {ok, [ - {meta, [{total, 2}, {offset, 0}]}, - {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, - {row, [{id, <<"4">>}, {key, 4}, {value, 4}]} - ]}, + Expect2 = + {ok, [ + {meta, [{total, 2}, {offset, 0}]}, + {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, + {row, [{id, <<"4">>}, {key, 4}, {value, 4}]} + ]}, ?assertEqual(Expect2, Result2) end). - test_purge_complete_for_docid_with_list(Db) -> ?_test(begin Result = run_query(Db, []), - Expect = {ok, [ - {meta, [{total, 5}, {offset, 0}]}, - {row, [{id, <<"1">>}, {key, 1}, {value, 1}]}, - {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, - {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, - {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, - {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} - ]}, + Expect = + {ok, [ + {meta, [{total, 5}, {offset, 0}]}, + {row, [{id, <<"1">>}, {key, 1}, {value, 1}]}, + {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, + {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, + {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, + {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} + ]}, ?assertEqual(Expect, Result), - FDI1 = couch_db:get_full_doc_info(Db, <<"1">>), Rev1 = get_rev(FDI1), - FDI2 = couch_db:get_full_doc_info(Db, <<"2">>), Rev2 = get_rev(FDI2), - FDI5 = couch_db:get_full_doc_info(Db, <<"5">>), Rev5 = get_rev(FDI5), + FDI1 = couch_db:get_full_doc_info(Db, <<"1">>), + Rev1 = get_rev(FDI1), + FDI2 = couch_db:get_full_doc_info(Db, <<"2">>), + Rev2 = get_rev(FDI2), + FDI5 = couch_db:get_full_doc_info(Db, <<"5">>), + Rev5 = get_rev(FDI5), PurgeInfos = [ {<<"UUID1">>, "1", [Rev1]}, @@ -223,26 +235,27 @@ test_purge_complete_for_docid_with_list(Db) -> {ok, Db2} = couch_db:reopen(Db), Result2 = run_query(Db2, []), - Expect2 = {ok, [ - {meta, [{total, 2}, {offset, 0}]}, - {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, - {row, [{id, <<"4">>}, {key, 4}, {value, 4}]} - ]}, + Expect2 = + {ok, [ + {meta, [{total, 2}, {offset, 0}]}, + {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, + {row, [{id, <<"4">>}, {key, 4}, {value, 4}]} + ]}, ?assertEqual(Expect2, Result2) end). - test_purge_nochange(Db) -> ?_test(begin Result = run_query(Db, []), - Expect = {ok, [ - {meta, [{total, 5}, {offset, 0}]}, - {row, [{id, <<"1">>}, {key, 1}, {value, 1}]}, - {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, - {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, - {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, - {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} - ]}, + Expect = + {ok, [ + {meta, [{total, 5}, {offset, 0}]}, + {row, [{id, <<"1">>}, {key, 1}, {value, 1}]}, + {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, + {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, + {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, + {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} + ]}, ?assertEqual(Expect, Result), FDI1 = couch_db:get_full_doc_info(Db, <<"1">>), @@ -255,40 +268,44 @@ test_purge_nochange(Db) -> {ok, Db2} = couch_db:reopen(Db), Result2 = run_query(Db2, []), - Expect2 = {ok, [ - {meta, [{total, 5}, {offset, 0}]}, - {row, [{id, <<"1">>}, {key, 1}, {value, 1}]}, - {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, - {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, - {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, - {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} - ]}, + Expect2 = + {ok, [ + {meta, [{total, 5}, {offset, 0}]}, + {row, [{id, <<"1">>}, {key, 1}, {value, 1}]}, + {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, + {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, + {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, + {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} + ]}, ?assertEqual(Expect2, Result2) end). - test_purge_index_reset(Db) -> ?_test(begin ok = couch_db:set_purge_infos_limit(Db, 2), {ok, Db1} = couch_db:reopen(Db), Result = run_query(Db1, []), - Expect = {ok, [ - {meta, [{total, 5}, {offset, 0}]}, - {row, [{id, <<"1">>}, {key, 1}, {value, 1}]}, - {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, - {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, - {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, - {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} - ]}, + Expect = + {ok, [ + {meta, [{total, 5}, {offset, 0}]}, + {row, [{id, <<"1">>}, {key, 1}, {value, 1}]}, + {row, [{id, <<"2">>}, {key, 2}, {value, 2}]}, + {row, [{id, <<"3">>}, {key, 3}, {value, 3}]}, + {row, [{id, <<"4">>}, {key, 4}, {value, 4}]}, + {row, [{id, <<"5">>}, {key, 5}, {value, 5}]} + ]}, ?assertEqual(Expect, Result), - PurgeInfos = lists:map(fun(I) -> - DocId = list_to_binary(integer_to_list(I)), - FDI = couch_db:get_full_doc_info(Db, DocId), - Rev = get_rev(FDI), - {couch_uuids:random(), DocId, [Rev]} - end, lists:seq(1, 5)), + PurgeInfos = lists:map( + fun(I) -> + DocId = list_to_binary(integer_to_list(I)), + FDI = couch_db:get_full_doc_info(Db, DocId), + Rev = get_rev(FDI), + {couch_uuids:random(), DocId, [Rev]} + end, + lists:seq(1, 5) + ), {ok, _} = couch_db:purge_docs(Db1, PurgeInfos), {ok, Db2} = couch_db:reopen(Db1), @@ -309,22 +326,22 @@ test_purge_index_reset(Db) -> {ok, Db3} = couch_db:reopen(Db2), Result2 = run_query(Db3, []), - Expect2 = {ok, [ - {meta, [{total, 0}, {offset, 0}]} - ]}, + Expect2 = + {ok, [ + {meta, [{total, 0}, {offset, 0}]} + ]}, ?assertEqual(Expect2, Result2), % Assert that we had a reset meck:wait( - 1, - couch_index_updater, - handle_info, - [{'EXIT', '_', {reset, '_'}}, '_'], - 5000 - ) + 1, + couch_index_updater, + handle_info, + [{'EXIT', '_', {reset, '_'}}, '_'], + 5000 + ) end). - test_purge_compact_size_check(Db) -> ?_test(begin DbName = couch_db:name(Db), @@ -334,24 +351,28 @@ test_purge_compact_size_check(Db) -> DiskSizeBefore = db_disk_size(DbName), PurgedDocsNum = 150, - IdsRevs = lists:foldl(fun(Id, CIdRevs) -> - Id1 = docid(Id), - FDI1 = couch_db:get_full_doc_info(Db1, Id1), - Rev1 = get_rev(FDI1), - UUID1 = uuid(Id), - [{UUID1, Id1, [Rev1]} | CIdRevs] - end, [], lists:seq(1, PurgedDocsNum)), + IdsRevs = lists:foldl( + fun(Id, CIdRevs) -> + Id1 = docid(Id), + FDI1 = couch_db:get_full_doc_info(Db1, Id1), + Rev1 = get_rev(FDI1), + UUID1 = uuid(Id), + [{UUID1, Id1, [Rev1]} | CIdRevs] + end, + [], + lists:seq(1, PurgedDocsNum) + ), {ok, _} = couch_db:purge_docs(Db1, IdsRevs), {ok, Db2} = couch_db:reopen(Db1), _Result1 = run_query(Db2, []), {ok, PurgedIdRevs} = couch_db:fold_purge_infos( - Db2, - 0, - fun fold_fun/2, - [], - [] - ), + Db2, + 0, + fun fold_fun/2, + [], + [] + ), ?assertEqual(PurgedDocsNum, length(PurgedIdRevs)), config:set("couchdb", "file_compression", "snappy", false), @@ -363,7 +384,6 @@ test_purge_compact_size_check(Db) -> ?assert(DiskSizeBefore > DiskSizeAfter) end). - test_purge_compact_for_stale_purge_cp_without_client(Db) -> ?_test(begin DbName = couch_db:name(Db), @@ -378,23 +398,27 @@ test_purge_compact_for_stale_purge_cp_without_client(Db) -> % purge 150 documents PurgedDocsNum = 150, - PurgeInfos = lists:foldl(fun(Id, CIdRevs) -> - Id1 = docid(Id), - FDI1 = couch_db:get_full_doc_info(Db1, Id1), - Rev1 = get_rev(FDI1), - UUID1 = uuid(Id), - [{UUID1, Id1, [Rev1]} | CIdRevs] - end, [], lists:seq(1, PurgedDocsNum)), + PurgeInfos = lists:foldl( + fun(Id, CIdRevs) -> + Id1 = docid(Id), + FDI1 = couch_db:get_full_doc_info(Db1, Id1), + Rev1 = get_rev(FDI1), + UUID1 = uuid(Id), + [{UUID1, Id1, [Rev1]} | CIdRevs] + end, + [], + lists:seq(1, PurgedDocsNum) + ), {ok, _} = couch_db:purge_docs(Db1, PurgeInfos), {ok, Db2} = couch_db:reopen(Db1), {ok, PurgedIdRevs} = couch_db:fold_purge_infos( - Db2, - 0, - fun fold_fun/2, - [], - [] - ), + Db2, + 0, + fun fold_fun/2, + [], + [] + ), ?assertEqual(PurgedDocsNum, length(PurgedIdRevs)), % run compaction to trigger pruning of purge tree @@ -407,16 +431,15 @@ test_purge_compact_for_stale_purge_cp_without_client(Db) -> {ok, Db4} = couch_db:reopen(Db3), OldestPSeq = couch_db:get_oldest_purge_seq(Db4), {ok, PurgedIdRevs2} = couch_db:fold_purge_infos( - Db4, - OldestPSeq - 1, - fun fold_fun/2, - [], - [] - ), + Db4, + OldestPSeq - 1, + fun fold_fun/2, + [], + [] + ), ?assertEqual(PurgedDocsLimit, length(PurgedIdRevs2)) end). - test_purge_compact_for_stale_purge_cp_with_client(Db) -> ?_test(begin DbName = couch_db:name(Db), @@ -432,36 +455,44 @@ test_purge_compact_for_stale_purge_cp_with_client(Db) -> % first purge 30 documents PurgedDocsNum1 = 30, - IdsRevs = lists:foldl(fun(Id, CIdRevs) -> - Id1 = docid(Id), - FDI1 = couch_db:get_full_doc_info(Db1, Id1), - Rev1 = get_rev(FDI1), - UUID1 = uuid(Id), - [{UUID1, Id1, [Rev1]} | CIdRevs] - end, [], lists:seq(1, PurgedDocsNum1)), + IdsRevs = lists:foldl( + fun(Id, CIdRevs) -> + Id1 = docid(Id), + FDI1 = couch_db:get_full_doc_info(Db1, Id1), + Rev1 = get_rev(FDI1), + UUID1 = uuid(Id), + [{UUID1, Id1, [Rev1]} | CIdRevs] + end, + [], + lists:seq(1, PurgedDocsNum1) + ), {ok, _} = couch_db:purge_docs(Db1, IdsRevs), {ok, Db2} = couch_db:reopen(Db1), % run query again to reflect purge request to mrview _Result1 = run_query(Db2, []), {ok, PurgedIdRevs} = couch_db:fold_purge_infos( - Db2, - 0, - fun fold_fun/2, - [], - [] - ), + Db2, + 0, + fun fold_fun/2, + [], + [] + ), ?assertEqual(PurgedDocsNum1, length(PurgedIdRevs)), % then purge 120 documents PurgedDocsNum2 = 150, - IdsRevs2 = lists:foldl(fun(Id, CIdRevs) -> - Id1 = docid(Id), - FDI1 = couch_db:get_full_doc_info(Db1, Id1), - Rev1 = get_rev(FDI1), - UUID1 = uuid(Id), - [{UUID1, Id1, [Rev1]} | CIdRevs] - end, [], lists:seq(PurgedDocsNum1 + 1, PurgedDocsNum2)), + IdsRevs2 = lists:foldl( + fun(Id, CIdRevs) -> + Id1 = docid(Id), + FDI1 = couch_db:get_full_doc_info(Db1, Id1), + Rev1 = get_rev(FDI1), + UUID1 = uuid(Id), + [{UUID1, Id1, [Rev1]} | CIdRevs] + end, + [], + lists:seq(PurgedDocsNum1 + 1, PurgedDocsNum2) + ), {ok, _} = couch_db:purge_docs(Db2, IdsRevs2), % run compaction to trigger pruning of purge tree @@ -475,16 +506,15 @@ test_purge_compact_for_stale_purge_cp_with_client(Db) -> {ok, Db4} = couch_db:reopen(Db3), OldestPSeq = couch_db:get_oldest_purge_seq(Db4), {ok, PurgedIdRevs2} = couch_db:fold_purge_infos( - Db4, - OldestPSeq - 1, - fun fold_fun/2, - [], - [] - ), + Db4, + OldestPSeq - 1, + fun fold_fun/2, + [], + [] + ), ?assertEqual(PurgedDocsNum2 - PurgedDocsNum1, length(PurgedIdRevs2)) end). - get_local_purge_doc(Db) -> {ok, DDoc} = couch_db:open_doc(Db, <<"_design/bar">>, []), {ok, IdxState} = couch_mrview_util:ddoc_to_mrst(couch_db:name(Db), DDoc), @@ -493,48 +523,50 @@ get_local_purge_doc(Db) -> DocId = couch_mrview_util:get_local_purge_doc_id(HexSig), couch_db:open_doc(Db, DocId, []). - run_query(Db, Opts) -> couch_mrview:query_view(Db, <<"_design/bar">>, <<"baz">>, Opts). - save_docs(Db, JsonDocs, Options) -> - Docs = lists:map(fun(JDoc) -> - couch_doc:from_json_obj(?JSON_DECODE(?JSON_ENCODE(JDoc))) - end, JsonDocs), + Docs = lists:map( + fun(JDoc) -> + couch_doc:from_json_obj(?JSON_DECODE(?JSON_ENCODE(JDoc))) + end, + JsonDocs + ), Opts = [full_commit | Options], case lists:member(replicated_changes, Options) of true -> {ok, []} = couch_db:update_docs( - Db, Docs, Opts, replicated_changes), - {ok, lists:map(fun(Doc) -> - {Pos, [RevId | _]} = Doc#doc.revs, - {Pos, RevId} - end, Docs)}; + Db, Docs, Opts, replicated_changes + ), + {ok, + lists:map( + fun(Doc) -> + {Pos, [RevId | _]} = Doc#doc.revs, + {Pos, RevId} + end, + Docs + )}; false -> {ok, Resp} = couch_db:update_docs(Db, Docs, Opts), {ok, [Rev || {ok, Rev} <- Resp]} end. - get_rev(#full_doc_info{} = FDI) -> #doc_info{ revs = [#rev_info{} = PrevRev | _] } = couch_doc:to_doc_info(FDI), PrevRev#rev_info.rev. - db_disk_size(DbName) -> {ok, Db} = couch_db:open_int(DbName, []), {ok, Info} = couch_db:get_db_info(Db), ok = couch_db:close(Db), active_size(Info). - active_size(Info) -> couch_util:get_nested_json_value({Info}, [sizes, active]). - wait_compaction(DbName, Kind, Line) -> WaitFun = fun() -> case is_compaction_running(DbName) of @@ -544,32 +576,32 @@ wait_compaction(DbName, Kind, Line) -> end, case test_util:wait(WaitFun, 10000) of timeout -> - erlang:error({assertion_failed, - [{module, ?MODULE}, + erlang:error( + {assertion_failed, [ + {module, ?MODULE}, {line, Line}, - {reason, "Timeout waiting for " - ++ Kind - ++ " database compaction"}]}); + {reason, + "Timeout waiting for " ++ + Kind ++ + " database compaction"} + ]} + ); _ -> ok end. - is_compaction_running(DbName) -> {ok, Db} = couch_db:open_int(DbName, []), {ok, DbInfo} = couch_db:get_db_info(Db), couch_db:close(Db), couch_util:get_value(compact_running, DbInfo). - fold_fun({_PSeq, _UUID, Id, Revs}, Acc) -> {ok, [{Id, Revs} | Acc]}. - docid(I) -> list_to_binary(integer_to_list(I)). - uuid(I) -> Str = io_lib:format("UUID~4..0b", [I]), iolist_to_binary(Str). diff --git a/src/couch_mrview/test/eunit/couch_mrview_red_views_tests.erl b/src/couch_mrview/test/eunit/couch_mrview_red_views_tests.erl index b83686113..b6042b6c7 100644 --- a/src/couch_mrview/test/eunit/couch_mrview_red_views_tests.erl +++ b/src/couch_mrview/test/eunit/couch_mrview_red_views_tests.erl @@ -17,7 +17,6 @@ -define(TIMEOUT, 1000). - setup() -> {ok, Db} = couch_mrview_test_util:init_db(?tempdb(), red), Db. @@ -27,16 +26,17 @@ teardown(Db) -> couch_server:delete(couch_db:name(Db), [?ADMIN_CTX]), ok. - reduce_views_test_() -> { "Reduce views", { setup, - fun test_util:start_couch/0, fun test_util:stop_couch/1, + fun test_util:start_couch/0, + fun test_util:stop_couch/1, { foreach, - fun setup/0, fun teardown/1, + fun setup/0, + fun teardown/1, [ fun should_reduce_basic/1, fun should_reduce_key_range/1, @@ -47,49 +47,51 @@ reduce_views_test_() -> } }. - should_reduce_basic(Db) -> Result = run_query(Db, []), - Expect = {ok, [ - {meta, []}, - {row, [{key, null}, {value, 55}]} - ]}, + Expect = + {ok, [ + {meta, []}, + {row, [{key, null}, {value, 55}]} + ]}, ?_assertEqual(Expect, Result). should_reduce_key_range(Db) -> Result = run_query(Db, [{start_key, [0, 2]}, {end_key, [0, 4]}]), - Expect = {ok, [ - {meta, []}, - {row, [{key, null}, {value, 6}]} - ]}, + Expect = + {ok, [ + {meta, []}, + {row, [{key, null}, {value, 6}]} + ]}, ?_assertEqual(Expect, Result). should_reduce_with_group_level(Db) -> Result = run_query(Db, [{group_level, 1}]), - Expect = {ok, [ - {meta, []}, - {row, [{key, [0]}, {value, 30}]}, - {row, [{key, [1]}, {value, 25}]} - ]}, + Expect = + {ok, [ + {meta, []}, + {row, [{key, [0]}, {value, 30}]}, + {row, [{key, [1]}, {value, 25}]} + ]}, ?_assertEqual(Expect, Result). should_reduce_with_group_exact(Db) -> Result = run_query(Db, [{group_level, exact}]), - Expect = {ok, [ - {meta, []}, - {row, [{key, [0, 2]}, {value, 2}]}, - {row, [{key, [0, 4]}, {value, 4}]}, - {row, [{key, [0, 6]}, {value, 6}]}, - {row, [{key, [0, 8]}, {value, 8}]}, - {row, [{key, [0, 10]}, {value, 10}]}, - {row, [{key, [1, 1]}, {value, 1}]}, - {row, [{key, [1, 3]}, {value, 3}]}, - {row, [{key, [1, 5]}, {value, 5}]}, - {row, [{key, [1, 7]}, {value, 7}]}, - {row, [{key, [1, 9]}, {value, 9}]} - ]}, + Expect = + {ok, [ + {meta, []}, + {row, [{key, [0, 2]}, {value, 2}]}, + {row, [{key, [0, 4]}, {value, 4}]}, + {row, [{key, [0, 6]}, {value, 6}]}, + {row, [{key, [0, 8]}, {value, 8}]}, + {row, [{key, [0, 10]}, {value, 10}]}, + {row, [{key, [1, 1]}, {value, 1}]}, + {row, [{key, [1, 3]}, {value, 3}]}, + {row, [{key, [1, 5]}, {value, 5}]}, + {row, [{key, [1, 7]}, {value, 7}]}, + {row, [{key, [1, 9]}, {value, 9}]} + ]}, ?_assertEqual(Expect, Result). - run_query(Db, Opts) -> couch_mrview:query_view(Db, <<"_design/red">>, <<"baz">>, Opts). diff --git a/src/couch_mrview/test/eunit/couch_mrview_util_tests.erl b/src/couch_mrview/test/eunit/couch_mrview_util_tests.erl index 7046c9bb2..a495fd82c 100644 --- a/src/couch_mrview/test/eunit/couch_mrview_util_tests.erl +++ b/src/couch_mrview/test/eunit/couch_mrview_util_tests.erl @@ -15,25 +15,23 @@ -include_lib("couch/include/couch_eunit.hrl"). -include_lib("couch_mrview/include/couch_mrview.hrl"). - - couch_mrview_util_test_() -> [ - ?_assertEqual(0, validate_group_level(undefined, undefined)), - ?_assertEqual(exact, validate_group_level(true, undefined)), - ?_assertEqual(0, validate_group_level(false, undefined)), - ?_assertEqual(1, validate_group_level(undefined, 1)), - ?_assertEqual(0, validate_group_level(true, 0)), - ?_assertEqual(0, validate_group_level(undefined, 0)), - ?_assertEqual(1, validate_group_level(true, 1)), - ?_assertEqual(0, validate_group_level(false, 0)), - ?_assertThrow({query_parse_error, - <<"Can't specify group=false and group_level>0 at the same time">>}, - validate_group_level(false,1)) + ?_assertEqual(0, validate_group_level(undefined, undefined)), + ?_assertEqual(exact, validate_group_level(true, undefined)), + ?_assertEqual(0, validate_group_level(false, undefined)), + ?_assertEqual(1, validate_group_level(undefined, 1)), + ?_assertEqual(0, validate_group_level(true, 0)), + ?_assertEqual(0, validate_group_level(undefined, 0)), + ?_assertEqual(1, validate_group_level(true, 1)), + ?_assertEqual(0, validate_group_level(false, 0)), + ?_assertThrow( + {query_parse_error, <<"Can't specify group=false and group_level>0 at the same time">>}, + validate_group_level(false, 1) + ) ]. validate_group_level(Group, GroupLevel) -> - Args0 = #mrargs{group=Group, group_level=GroupLevel, view_type=red}, + Args0 = #mrargs{group = Group, group_level = GroupLevel, view_type = red}, Args1 = couch_mrview_util:validate_args(Args0), Args1#mrargs.group_level. - |