Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mnesia: add select_reverse/1-4 #9475

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
260 changes: 243 additions & 17 deletions lib/mnesia/src/mnesia.erl
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ are checked, and finally, the default value is chosen.
read/1, read/2, wread/1, read/3, read/5,
match_object/1, match_object/3, match_object/5,
select/1,select/2,select/3,select/4,select/5,select/6,
select_reverse/1,select_reverse/2,select_reverse/3,select_reverse/4,select_reverse/5,select_reverse/6,
all_keys/1, all_keys/4,
index_match_object/2, index_match_object/4, index_match_object/6,
index_read/3, index_read/6,
Expand All @@ -313,6 +314,7 @@ are checked, and finally, the default value is chosen.
%% Dirty access regardless of activities - Read
dirty_read/1, dirty_read/2,
dirty_select/2,
dirty_select_reverse/2,
dirty_match_object/1, dirty_match_object/2, dirty_all_keys/1,
dirty_index_match_object/2, dirty_index_match_object/3,
dirty_index_read/3, dirty_slot/2,
Expand Down Expand Up @@ -372,7 +374,8 @@ are checked, and finally, the default value is chosen.
%% Module internal callback functions
raw_table_info/2, % Not for public use
remote_dirty_match_object/2, % Not for public use
remote_dirty_select/2 % Not for public use
remote_dirty_select/2, % Not for public use
remote_dirty_select_reverse/2 % Not for public use
]).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Expand Down Expand Up @@ -2441,28 +2444,197 @@ select(Cont) ->
abort(no_transaction)
end.

% select_reverse
-doc(#{equiv => select_reverse(Tab, MatchSpec, read)}).
-spec select_reverse(Tab, MatchSpec) -> [Match] when
Tab::table(), MatchSpec::ets:match_spec(), Match::term().
select_reverse(Tab, Pat) ->
select_reverse(Tab, Pat, read).
-doc """
Works like `select/3`, but for table type `ordered_set`, traversing is done
starting at the last object in Erlang term order, and moves to the first. For
all other table types, the return value is identical to that of `select/3`.

See `select/3` for more information.
""".
-spec select_reverse(Tab, Spec, LockKind) -> [Match] when
Tab::table(), Spec::ets:match_spec(),
Match::term(),LockKind::lock_kind().
select_reverse(Tab, Pat, LockKind)
when is_atom(Tab), Tab /= schema, is_list(Pat) ->
case get(mnesia_activity_state) of
{?DEFAULT_ACCESS, Tid, Ts} ->
select_reverse(Tid, Ts, Tab, Pat, LockKind);
{Mod, Tid, Ts} ->
Mod:select_reverse(Tid, Ts, Tab, Pat, LockKind);
_ ->
abort(no_transaction)
end;
select_reverse(Tab, Pat, _Lock) ->
abort({badarg, Tab, Pat}).

-doc false.
select_reverse(Tid, Ts, Tab, Spec, LockKind) ->
SelectFun = fun(FixedSpec) -> dirty_select_reverse(Tab, FixedSpec) end,
fun_select_reverse(Tid, Ts, Tab, Spec, LockKind, Tab, SelectFun).

-doc false.
fun_select_reverse(Tid, Ts, Tab, Spec, LockKind, TabPat, SelectFun) ->
case element(1, Tid) of
ets ->
mnesia_lib:db_select_rev(ram_copies, Tab, Spec);
tid ->
select_lock(Tid,Ts,LockKind,Spec,Tab),
Store = Ts#tidstore.store,
Written = ?ets_match_object(Store, {{TabPat, '_'}, '_', '_'}),
case Written of
[] ->
%% Nothing changed in the table during this transaction,
%% Simple case get results from [d]ets
SelectFun(Spec);
_ ->
%% Hard (slow case) records added or deleted earlier
%% in the transaction, have to cope with that.
Type = val({Tab, setorbag}),
FixedSpec = get_record_pattern(Spec),
TabRecs = SelectFun(FixedSpec),
FixedRes = add_match(Written, TabRecs, Type),
CMS = ets:match_spec_compile(Spec),
ets:match_spec_run(FixedRes, CMS)
end;
_Protocol ->
SelectFun(Spec)
end.

%% Breakable Select Reverse
-doc """
Select the objects in `Tab` against `MatchSpec` in reverse order.

Matches the objects in table `Tab` using a `match_spec` as described in the
[ERTS](`e:erts:index.html`) User's Guide, and returns a chunk of terms and a
continuation. The wanted number of returned terms is specified by argument
`NObjects`. The lock argument can be `read` or `write`. The continuation is to
be used as argument to `mnesia:select_reverse/1`, if more or all answers are needed.

Notice that for best performance, `select_reverse` is to be used before any modifying
operations are done on that table in the same transaction. That is, do not use
`mnesia:write` or `mnesia:delete` before a `mnesia:select_reverse`. For efficiency,
`NObjects` is a recommendation only and the result can contain anything from an
empty list to all available results.
""".
-spec select_reverse(Tab, Spec, N, LockKind) -> {[Match], Cont} | '$end_of_table' when
Tab::table(), Spec::ets:match_spec(),
Match::term(), N::non_neg_integer(),
LockKind::lock_kind(),
Cont::select_continuation().
select_reverse(Tab, Pat, NObjects, LockKind)
when is_atom(Tab), Tab /= schema, is_list(Pat), is_integer(NObjects) ->
case get(mnesia_activity_state) of
{?DEFAULT_ACCESS, Tid, Ts} ->
select_reverse(Tid, Ts, Tab, Pat, NObjects, LockKind);
{Mod, Tid, Ts} ->
Mod:select_reverse(Tid, Ts, Tab, Pat, NObjects, LockKind);
_ ->
abort(no_transaction)
end;
select_reverse(Tab, Pat, NObjects, _Lock) ->
abort({badarg, Tab, Pat, NObjects}).

-doc false.
select_reverse(Tid, Ts, Tab, Spec, NObjects, LockKind) ->
Where = val({Tab,where_to_read}),
Type = mnesia_lib:storage_type_at_node(Where,Tab),
InitFun = fun(FixedSpec) -> dirty_sel_init(Where,Tab,FixedSpec,NObjects,Type,reverse) end,
fun_select_reverse(Tid,Ts,Tab,Spec,LockKind,Tab,InitFun,NObjects,Where,Type).

-doc false.
fun_select_reverse(Tid, Ts, Tab, Spec, LockKind, TabPat, Init, NObjects, Node, Storage) ->
Def = #mnesia_select{tid=Tid,node=Node,storage=Storage,tab=Tab,orig=Spec},
case element(1, Tid) of
ets ->
select_state(mnesia_lib:db_select_rev_init(ram_copies,Tab,Spec,NObjects),Def);
tid ->
select_lock(Tid,Ts,LockKind,Spec,Tab),
Store = Ts#tidstore.store,
do_fixtable(Tab, Store),

Written0 = ?ets_match_object(Store, {{TabPat, '_'}, '_', '_'}),
case Written0 of
[] ->
%% Nothing changed in the table during this transaction,
%% Simple case get results from [d]ets
select_state(Init(Spec),Def);
_ ->
%% Hard (slow case) records added or deleted earlier
%% in the transaction, have to cope with that.
Type = val({Tab, setorbag}),
Written =
if Type == ordered_set -> %% Sort stable, in descending order
lists:sort(fun(A, B) -> element(1, A) > element(1, B) end, Written0);
true ->
Written0
end,
FixedSpec = get_record_pattern(Spec),
CMS = ets:match_spec_compile(Spec),
trans_select(Init(FixedSpec),
Def#mnesia_select{written=Written,spec=CMS,type=Type, orig=FixedSpec})
end;
_Protocol ->
select_state(Init(Spec),Def)
end.

-doc """
Continue selecting objects.

Selects more objects with the match specification initiated by
`mnesia:select_reverse/4`.

Notice that any modifying operations, that is, `mnesia:write` or
`mnesia:delete`, that are done between the `mnesia:select_reverse/4` and
`mnesia:select_reverse/1` calls are not visible in the result.
""".
-spec select_reverse(Cont) -> {[Match], Cont} | '$end_of_table' when
Match::term(),
Cont::select_continuation().
select_reverse(Cont) ->
case get(mnesia_activity_state) of
{?DEFAULT_ACCESS, Tid, Ts} ->
select_cont(Tid,Ts,Cont,reverse);
{Mod, Tid, Ts} ->
Mod:select_cont(Tid,Ts,Cont,reverse);
_ ->
abort(no_transaction)
end.

-doc false.
select_cont(_Tid,_Ts,'$end_of_table') ->
select_cont(Tid,Ts,State) ->
select_cont(Tid,Ts,State,forward).
select_cont(_Tid,_Ts,'$end_of_table',_Dir) ->
'$end_of_table';
select_cont(Tid,_Ts,State=#mnesia_select{tid=Tid,cont=Cont, orig=Ms})
select_cont(Tid,_Ts,State=#mnesia_select{tid=Tid,cont=Cont, orig=Ms},Dir)
when element(1,Tid) == ets ->
case Cont of
'$end_of_table' -> '$end_of_table';
_ -> select_state(mnesia_lib:db_select_cont(ram_copies,Cont,Ms),State)
_ ->
Result = case Dir of
forward -> mnesia_lib:db_select_cont(ram_copies,Cont,Ms);
reverse -> mnesia_lib:db_select_rev_cont(ram_copies,Cont,Ms)
end,
select_state(Result,State)
end;
select_cont(Tid,_,State=#mnesia_select{tid=Tid,written=[]}) ->
select_state(dirty_sel_cont(State),State);
select_cont(Tid,_Ts,State=#mnesia_select{tid=Tid}) ->
trans_select(dirty_sel_cont(State), State);
select_cont(Tid2,_,#mnesia_select{tid=_Tid1})
select_cont(Tid,_,State=#mnesia_select{tid=Tid,written=[]},Dir) ->
select_state(dirty_sel_cont(State,Dir),State);
select_cont(Tid,_Ts,State=#mnesia_select{tid=Tid},Dir) ->
trans_select(dirty_sel_cont(State,Dir), State);
select_cont(Tid2,_,#mnesia_select{tid=_Tid1},_Dir)
when element(1,Tid2) == tid -> % Mismatching tids
abort(wrong_transaction);
select_cont(Tid,Ts,State=#mnesia_select{}) ->
select_cont(Tid,Ts,State=#mnesia_select{},Dir) ->
% Repair mismatching tids in non-transactional contexts
RepairedState = State#mnesia_select{tid = Tid, written = [],
spec = undefined, type = undefined},
select_cont(Tid,Ts,RepairedState);
select_cont(_,_,Cont) ->
select_cont(Tid,Ts,RepairedState,Dir);
select_cont(_,_,Cont,_Dir) ->
abort({badarg, Cont}).

trans_select('$end_of_table', #mnesia_select{written=Written0,spec=CMS,type=Type}) ->
Expand Down Expand Up @@ -2881,13 +3053,67 @@ remote_dirty_select(Tab, [{HeadPat,_, _}] = Spec, [Pos | Tail])
remote_dirty_select(Tab, Spec, _) ->
mnesia_lib:db_select(Tab, Spec).

-doc """
Dirty equivalent to `mnesia:select_reverse/2`.
""".
-spec dirty_select_reverse(Tab, Spec) -> [Match] when
Tab::table(), Spec::ets:match_spec(), Match::term().
dirty_select_reverse(Tab, Spec) when is_atom(Tab), Tab /= schema, is_list(Spec) ->
dirty_rpc(Tab, ?MODULE, remote_dirty_select_reverse, [Tab, Spec]);
dirty_select_reverse(Tab, Spec) ->
abort({bad_type, Tab, Spec}).

-doc false.
dirty_sel_init(Node,Tab,Spec,NObjects,Type) ->
do_dirty_rpc(Tab,Node,mnesia_lib,db_select_init,[Type,Tab,Spec,NObjects]).
remote_dirty_select_reverse(Tab, Spec) ->
case Spec of
[{HeadPat, _, _}] when is_tuple(HeadPat), tuple_size(HeadPat) > 2 ->
Key = element(2, HeadPat),
case has_var(Key) of
false ->
mnesia_lib:db_select_rev(Tab, Spec);
true ->
PosList = regular_indexes(Tab),
remote_dirty_select_reverse(Tab, Spec, PosList)
end;
_ ->
mnesia_lib:db_select_rev(Tab, Spec)
end.

dirty_sel_cont(#mnesia_select{cont='$end_of_table'}) -> '$end_of_table';
dirty_sel_cont(#mnesia_select{node=Node,tab=Tab,storage=Type,cont=Cont,orig=Ms}) ->
do_dirty_rpc(Tab,Node,mnesia_lib,db_select_cont,[Type,Cont,Ms]).
remote_dirty_select_reverse(Tab, [{HeadPat,_, _}] = Spec, [Pos | Tail])
when is_tuple(HeadPat), tuple_size(HeadPat) > 2, Pos =< tuple_size(HeadPat) ->
Key = element(Pos, HeadPat),
case has_var(Key) of
false ->
Recs = mnesia_index:dirty_select(Tab, HeadPat, Pos),
%% Returns the records without applying the match spec
%% The actual filtering is handled by the caller
CMS = ets:match_spec_compile(Spec),
case val({Tab, setorbag}) of
ordered_set ->
DescFun = fun(A, B) -> A > B end,
ets:match_spec_run(lists:sort(DescFun, Recs), CMS);
_ ->
ets:match_spec_run(Recs, CMS)
end;
true ->
remote_dirty_select_reverse(Tab, Spec, Tail)
end;
remote_dirty_select_reverse(Tab, Spec, _) ->
mnesia_lib:db_select_rev(Tab, Spec).

-doc false.
dirty_sel_init(Node,Tab,Spec,NObjects,Type) ->
dirty_sel_init(Node,Tab,Spec,NObjects,Type,forward).
dirty_sel_init(Node,Tab,Spec,NObjects,Type,forward) ->
do_dirty_rpc(Tab,Node,mnesia_lib,db_select_init,[Type,Tab,Spec,NObjects]);
dirty_sel_init(Node,Tab,Spec,NObjects,Type,reverse) ->
do_dirty_rpc(Tab,Node,mnesia_lib,db_select_rev_init,[Type,Tab,Spec,NObjects]).

dirty_sel_cont(#mnesia_select{cont='$end_of_table'},_Dir) -> '$end_of_table';
dirty_sel_cont(#mnesia_select{node=Node,tab=Tab,storage=Type,cont=Cont,orig=Ms},forward) ->
do_dirty_rpc(Tab,Node,mnesia_lib,db_select_cont,[Type,Cont,Ms]);
dirty_sel_cont(#mnesia_select{node=Node,tab=Tab,storage=Type,cont=Cont,orig=Ms},reverse) ->
do_dirty_rpc(Tab,Node,mnesia_lib,db_select_rev_cont,[Type,Cont,Ms]).

-doc """
Dirty equivalent to `mnesia:all_keys/1`.
Expand Down
36 changes: 36 additions & 0 deletions lib/mnesia/src/mnesia_lib.erl
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,12 @@
db_put/3,
db_select/2,
db_select/3,
db_select_rev/2,
db_select_rev/3,
db_select_init/4,
db_select_rev_init/4,
db_select_cont/3,
db_select_rev_cont/3,
db_slot/2,
db_slot/3,
db_update_counter/3,
Expand Down Expand Up @@ -1192,6 +1196,21 @@ db_select(Storage, Tab, Pat) ->
db_fixtable(Storage, Tab, false)
end.

db_select_rev(Tab, Pat) ->
db_select_rev(val({Tab, storage_type}), Tab, Pat).

db_select_rev(Storage, Tab, Pat) ->
db_fixtable(Storage, Tab, true),
try
case Storage of
disc_only_copies -> dets:select(Tab, Pat);
{ext, Alias, Mod} -> Mod:select_reverse(Alias, Tab, Pat);
_ -> ets:select_reverse(Tab, Pat)
end
after
db_fixtable(Storage, Tab, false)
end.

db_select_init({ext, Alias, Mod}, Tab, Pat, Limit) ->
Mod:select(Alias, Tab, Pat, Limit);
db_select_init(disc_only_copies, Tab, Pat, Limit) ->
Expand Down Expand Up @@ -1222,6 +1241,23 @@ db_fixtable(disc_only_copies, Tab, Bool) ->
db_fixtable({ext, Alias, Mod}, Tab, Bool) ->
Mod:fixtable(Alias, Tab, Bool).

db_select_rev_init({ext, Alias, Mod}, Tab, Pat, Limit) ->
Mod:select_reverse(Alias, Tab, Pat, Limit);
db_select_rev_init(disc_only_copies, Tab, Pat, Limit) ->
dets:select(Tab, Pat, Limit);
db_select_rev_init(_, Tab, Pat, Limit) ->
ets:select_reverse(Tab, Pat, Limit).

db_select_rev_cont({ext, _Alias, Mod}, Cont0, Ms) ->
Cont = Mod:repair_continuation(Cont0, Ms),
Mod:select_reverse(Cont);
db_select_rev_cont(disc_only_copies, Cont0, Ms) ->
Cont = dets:repair_continuation(Cont0, Ms),
dets:select(Cont);
db_select_rev_cont(_, Cont0, Ms) ->
Cont = ets:repair_continuation(Cont0, Ms),
ets:select_reverse(Cont).

db_erase(Tab, Key) ->
db_erase(val({Tab, storage_type}), Tab, Key).
db_erase(ram_copies, Tab, Key) -> ?ets_delete(Tab, Key), ok;
Expand Down
Loading