diff options
author | Jan Lehnardt <jan@apache.org> | 2022-06-25 11:28:53 +0200 |
---|---|---|
committer | Jan Lehnardt <jan@apache.org> | 2022-12-16 16:56:18 +0100 |
commit | 9d3fe316d68da55967ade592205f49e9d0a89735 (patch) | |
tree | abf850c2dfcfa8ce0e42b15405a65b238d65523e | |
parent | 8c3e295ce9c03304250308822834309320b00c97 (diff) | |
download | couchdb-9d3fe316d68da55967ade592205f49e9d0a89735.tar.gz |
feat(access): add mrview machinery
-rw-r--r-- | src/couch_index/src/couch_index_updater.erl | 35 | ||||
-rw-r--r-- | src/couch_mrview/include/couch_mrview.hrl | 3 | ||||
-rw-r--r-- | src/couch_mrview/src/couch_mrview.erl | 112 | ||||
-rw-r--r-- | src/couch_mrview/src/couch_mrview_updater.erl | 46 | ||||
-rw-r--r-- | src/couch_mrview/src/couch_mrview_util.erl | 9 |
5 files changed, 186 insertions, 19 deletions
diff --git a/src/couch_index/src/couch_index_updater.erl b/src/couch_index/src/couch_index_updater.erl index fe2150505..66d760622 100644 --- a/src/couch_index/src/couch_index_updater.erl +++ b/src/couch_index/src/couch_index_updater.erl @@ -123,8 +123,8 @@ update(Idx, Mod, IdxState) -> IncludeDesign = lists:member(include_design, UpdateOpts), DocOpts = case lists:member(local_seq, UpdateOpts) of - true -> [conflicts, deleted_conflicts, local_seq]; - _ -> [conflicts, deleted_conflicts] + true -> [conflicts, deleted_conflicts, local_seq, deleted]; + _ -> [conflicts, deleted_conflicts,local_seq, deleted] end, couch_util:with_db(DbName, fun(Db) -> @@ -142,23 +142,36 @@ update(Idx, Mod, IdxState) -> end, GetInfo = fun - (#full_doc_info{id = Id, update_seq = Seq, deleted = Del} = FDI) -> - {Id, Seq, Del, couch_doc:to_doc_info(FDI)}; - (#doc_info{id = Id, high_seq = Seq, revs = [RI | _]} = DI) -> - {Id, Seq, RI#rev_info.deleted, DI} + (#full_doc_info{id=Id, update_seq=Seq, deleted=Del,access=Access}=FDI) -> + {Id, Seq, Del, couch_doc:to_doc_info(FDI), Access}; + (#doc_info{id=Id, high_seq=Seq, revs=[RI|_],access=Access}=DI) -> + {Id, Seq, RI#rev_info.deleted, DI, Access} end, LoadDoc = fun(DI) -> - {DocId, Seq, Deleted, DocInfo} = GetInfo(DI), + {DocId, Seq, Deleted, DocInfo, Access} = GetInfo(DI), case {IncludeDesign, DocId} of {false, <<"_design/", _/binary>>} -> {nil, Seq}; - _ when Deleted -> - {#doc{id = DocId, deleted = true}, Seq}; _ -> - {ok, Doc} = couch_db:open_doc_int(Db, DocInfo, DocOpts), - {Doc, Seq} + case IndexName of % TODO: move into outer case statement + <<"_design/_access">> -> + {ok, Doc} = couch_db:open_doc_int(Db, DocInfo, DocOpts), + % TODO: hande conflicted docs in _access index + % probably remove + [RevInfo|_] = DocInfo#doc_info.revs, + Doc1 = Doc#doc{ + meta = [{body_sp, RevInfo#rev_info.body_sp}], + access = Access + }, + {Doc1, Seq}; + _ when Deleted -> + {#doc{id=DocId, deleted=true}, Seq}; + _ -> + {ok, Doc} = couch_db:open_doc_int(Db, DocInfo, DocOpts), + {Doc, Seq} + end end end, diff --git a/src/couch_mrview/include/couch_mrview.hrl b/src/couch_mrview/include/couch_mrview.hrl index b31463c53..ef987595d 100644 --- a/src/couch_mrview/include/couch_mrview.hrl +++ b/src/couch_mrview/include/couch_mrview.hrl @@ -83,7 +83,8 @@ conflicts, callback, sorted = true, - extra = [] + extra = [], + deleted = false }). -record(vacc, { diff --git a/src/couch_mrview/src/couch_mrview.erl b/src/couch_mrview/src/couch_mrview.erl index d8640c903..79b2b8bec 100644 --- a/src/couch_mrview/src/couch_mrview.erl +++ b/src/couch_mrview/src/couch_mrview.erl @@ -13,7 +13,7 @@ -module(couch_mrview). -export([validate/2]). --export([query_all_docs/2, query_all_docs/4]). +-export([query_all_docs/2, query_all_docs/4, query_changes_access/5]). -export([query_view/3, query_view/4, query_view/6, get_view_index_pid/4]). -export([get_info/2]). -export([trigger_update/2, trigger_update/3]). @@ -259,6 +259,116 @@ query_all_docs(Db, Args) -> 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) -> + case couch_db:has_access_enabled(Db) and not couch_db:is_admin(Db) of + true -> query_all_docs_access(Db, Args0, Callback, Acc); + false -> query_all_docs_admin(Db, Args0, Callback, Acc) + end. +access_ddoc() -> + #doc{ + id = <<"_design/_access">>, + body = {[ + {<<"language">>,<<"_access">>}, + {<<"options">>, {[ + {<<"include_design">>, true} + ]}}, + {<<"views">>, {[ + {<<"_access_by_id">>, {[ + {<<"map">>, <<"_access/by-id-map">>}, + {<<"reduce">>, <<"_count">>} + ]}}, + {<<"_access_by_seq">>, {[ + {<<"map">>, <<"_access/by-seq-map">>}, + {<<"reduce">>, <<"_count">>} + ]}} + ]}} + ]} + }. +query_changes_access(Db, StartSeq, Fun, Options, Acc) -> + DDoc = access_ddoc(), + UserCtx = couch_db:get_user_ctx(Db), + UserName = UserCtx#user_ctx.name, + %% % TODO: add roles + Args1 = prefix_startkey_endkey(UserName, #mrargs{}, fwd), + Args2 = Args1#mrargs{deleted=true}, + Args = Args2#mrargs{reduce=false}, + %% % filter out the user-prefix from the key, so _all_docs looks normal + %% % this isn’t a separate function because I’m binding Callback0 and I don’t + %% % know the Erlang equivalent of JS’s fun.bind(this, newarg) + Callback = fun + ({meta, _}, Acc0) -> + {ok, Acc0}; % ignore for now + ({row, Props}, Acc0) -> + % turn row into FDI + Value = couch_util:get_value(value, Props), + [Owner, Seq] = couch_util:get_value(key, Props), + Rev = couch_util:get_value(rev, Value), + Deleted = couch_util:get_value(deleted, Value, false), + BodySp = couch_util:get_value(body_sp, Value), + [Pos, RevId] = string:split(?b2l(Rev), "-"), + FDI = #full_doc_info{ + id = proplists:get_value(id, Props), + rev_tree = [{list_to_integer(Pos), {?l2b(RevId), #leaf{deleted=Deleted, ptr=BodySp, seq=Seq, sizes=#size_info{}}, []}}], + deleted = Deleted, + update_seq = 0, + sizes = #size_info{}, + access = [Owner] + }, + Fun(FDI, Acc0); + (_Else, Acc0) -> + {ok, Acc0} % ignore for now + end, + VName = <<"_access_by_seq">>, + query_view(Db, DDoc, VName, Args, Callback, Acc). + +query_all_docs_access(Db, Args0, Callback0, Acc) -> + % query our not yest existing, home-grown _access view. + % use query_view for this. + DDoc = access_ddoc(), + UserCtx = couch_db:get_user_ctx(Db), + UserName = UserCtx#user_ctx.name, + Args1 = prefix_startkey_endkey(UserName, Args0, Args0#mrargs.direction), + Args = Args1#mrargs{reduce=false, extra=Args1#mrargs.extra ++ [{all_docs_access, true}]}, + Callback = fun + ({row, Props}, Acc0) -> + % filter out the user-prefix from the key, so _all_docs looks normal + % this isn’t a separate function because I’m binding Callback0 and I + % don’t know the Erlang equivalent of JS’s fun.bind(this, newarg) + [_User, Key] = proplists:get_value(key, Props), + Row0 = proplists:delete(key, Props), + Row = [{key, Key} | Row0], + Callback0({row, Row}, Acc0); + (Row, Acc0) -> + Callback0(Row, Acc0) + end, + VName = <<"_access_by_id">>, + query_view(Db, DDoc, VName, Args, Callback, Acc). + +prefix_startkey_endkey(UserName, Args, fwd) -> + #mrargs{start_key=StartKey, end_key=EndKey} = Args, + Args#mrargs { + start_key = case StartKey of + undefined -> [UserName]; + StartKey -> [UserName, StartKey] + end, + end_key = case EndKey of + undefined -> [UserName, {}]; + EndKey -> [UserName, EndKey, {}] + end + }; + +prefix_startkey_endkey(UserName, Args, rev) -> + #mrargs{start_key=StartKey, end_key=EndKey} = Args, + Args#mrargs { + end_key = case StartKey of + undefined -> [UserName]; + StartKey -> [UserName, StartKey] + end, + start_key = case EndKey of + undefined -> [UserName, {}]; + EndKey -> [UserName, EndKey, {}] + end + }. +query_all_docs_admin(Db, Args0, Callback, Acc) -> Sig = couch_util:with_db(Db, fun(WDb) -> {ok, Info} = couch_db:get_db_info(WDb), couch_index_util:hexsig(couch_hash:md5_hash(term_to_binary(Info))) diff --git a/src/couch_mrview/src/couch_mrview_updater.erl b/src/couch_mrview/src/couch_mrview_updater.erl index 969a82028..5d58ab05d 100644 --- a/src/couch_mrview/src/couch_mrview_updater.erl +++ b/src/couch_mrview/src/couch_mrview_updater.erl @@ -124,8 +124,9 @@ process_doc(Doc, Seq, #mrst{doc_acc = Acc} = State) when length(Acc) > 100 -> 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]}}; +% TODO: re-evaluate why this is commented out +% 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]}}. @@ -149,6 +150,14 @@ finish_update(#mrst{doc_acc = Acc} = State) -> }} end. +make_deleted_body({Props}, Meta, Seq) -> + BodySp = couch_util:get_value(body_sp, Meta), + Result = [{<<"_seq">>, Seq}, {<<"_body_sp">>, BodySp}], + case couch_util:get_value(<<"_access">>, Props) of + undefined -> Result; + Access -> [{<<"_access">>, Access} | Result] + 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 @@ -167,11 +176,38 @@ map_docs(Parent, #mrst{db_name = DbName, idx_name = IdxName} = State0) -> DocFun = fun ({nil, Seq, _}, {SeqAcc, Results}) -> {erlang:max(Seq, SeqAcc), Results}; - ({Id, Seq, deleted}, {SeqAcc, Results}) -> - {erlang:max(Seq, SeqAcc), [{Id, []} | Results]}; + ({Id, Seq, Rev, #doc{deleted=true, body=Body, meta=Meta}}, {SeqAcc, Results}) -> + % _access needs deleted docs + case IdxName of + <<"_design/_access">> -> + % splice in seq + {Start, Rev1} = Rev, + Doc = #doc{ + id = Id, + revs = {Start, [Rev1]}, + body = {make_deleted_body(Body, Meta, Seq)}, %% todo: only keep _access and add _seq + deleted = true + }, + {ok, Res} = couch_query_servers:map_doc_raw(QServer, Doc), + {erlang:max(Seq, SeqAcc), [{Id, Seq, Rev, Res} | Results]}; + _Else -> + {erlang:max(Seq, SeqAcc), [{Id, Seq, Rev, []} | Results]} + end; ({Id, Seq, Doc}, {SeqAcc, Results}) -> couch_stats:increment_counter([couchdb, mrview, map_doc]), - {ok, Res} = couch_query_servers:map_doc_raw(QServer, Doc), + % IdxName: ~p, Doc: ~p~n~n", [IdxName, Doc]), + Doc0 = case IdxName of + <<"_design/_access">> -> + % splice in seq + {Props} = Doc#doc.body, + BodySp = couch_util:get_value(body_sp, Doc#doc.meta), + Doc#doc{ + body = {Props++[{<<"_seq">>, Seq}, {<<"_body_sp">>, BodySp}]} + }; + _Else -> + Doc + end, + {ok, Res} = couch_query_servers:map_doc_raw(QServer, Doc0), {erlang:max(Seq, SeqAcc), [{Id, Res} | Results]} end, FoldFun = fun(Docs, Acc) -> diff --git a/src/couch_mrview/src/couch_mrview_util.erl b/src/couch_mrview/src/couch_mrview_util.erl index e1e75f34f..7991487ff 100644 --- a/src/couch_mrview/src/couch_mrview_util.erl +++ b/src/couch_mrview/src/couch_mrview_util.erl @@ -21,6 +21,7 @@ -export([index_file/2, compaction_file/2, open_file/1]). -export([delete_files/2, delete_index_file/2, delete_compaction_file/2]). -export([get_row_count/1, all_docs_reduce_to_count/1, reduce_to_count/1]). +-export([get_access_row_count/2]). -export([all_docs_key_opts/1, all_docs_key_opts/2, key_opts/1, key_opts/2]). -export([fold/4, fold_reduce/4]). -export([temp_view_to_ddoc/1]). @@ -437,6 +438,11 @@ reduce_to_count(Reductions) -> FinalReduction = couch_btree:final_reduce(CountReduceFun, Reductions), get_count(FinalReduction). +get_access_row_count(#mrview{btree=Bt}, UserName) -> + couch_btree:full_reduce_with_options(Bt, [ + {start_key, UserName} + ]). + fold(#mrview{btree = Bt}, Fun, Acc, Opts) -> WrapperFun = fun(KV, Reds, Acc2) -> fold_fun(Fun, expand_dups([KV], []), Reds, Acc2) @@ -479,8 +485,9 @@ validate_args(#mrst{} = State, Args0) -> ViewPartitioned = State#mrst.partitioned, Partition = get_extra(Args, partition), + AllDocsAccess = get_extra(Args, all_docs_access, false), - case {ViewPartitioned, Partition} of + case {ViewPartitioned and not AllDocsAccess, Partition} of {true, undefined} -> Msg1 = << "`partition` parameter is mandatory " |