File 2451-mnesia-add-select_reverse-1-4.patch of Package erlang
From f3a5b545810c65170fd7865d58fe4a37a34afa92 Mon Sep 17 00:00:00 2001
From: Bentheburrito <github.arson718@passmail.net>
Date: Sun, 23 Feb 2025 16:23:02 -0800
Subject: [PATCH 1/8] mnesia: add select_reverse/1-4
Adds mnesia:select_reverse/1-4 and supporting functions in mnesia_lib,
bringing the mnesia API closer to ets's.
Related GH issue: https://github.com/erlang/otp/issues/8993
---
lib/mnesia/src/mnesia.erl | 261 +++++++++++++++++--
lib/mnesia/src/mnesia_lib.erl | 36 +++
lib/mnesia/test/mnesia_trans_access_test.erl | 111 +++++++-
3 files changed, 389 insertions(+), 19 deletions(-)
diff --git a/lib/mnesia/src/mnesia.erl b/lib/mnesia/src/mnesia.erl
index f74a8ab60c..52598abdbf 100644
--- a/lib/mnesia/src/mnesia.erl
+++ b/lib/mnesia/src/mnesia.erl
@@ -297,6 +297,8 @@ 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,
@@ -315,6 +317,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,
@@ -374,7 +377,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
]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -2443,28 +2447,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_cont(_Tid,_Ts,'$end_of_table') ->
+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,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}) ->
@@ -2883,13 +3056,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`.
diff --git a/lib/mnesia/src/mnesia_lib.erl b/lib/mnesia/src/mnesia_lib.erl
index 51cb62f3f6..9354eb1b1f 100644
--- a/lib/mnesia/src/mnesia_lib.erl
+++ b/lib/mnesia/src/mnesia_lib.erl
@@ -75,8 +75,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,
@@ -1194,6 +1198,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) ->
@@ -1224,6 +1243,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;
diff --git a/lib/mnesia/test/mnesia_trans_access_test.erl b/lib/mnesia/test/mnesia_trans_access_test.erl
index 693a15fc96..1dddd3ed34 100644
--- a/lib/mnesia/test/mnesia_trans_access_test.erl
+++ b/lib/mnesia/test/mnesia_trans_access_test.erl
@@ -30,7 +30,8 @@
-export([write/1, read/1, wread/1, delete/1,
delete_object_bag/1, delete_object_set/1,
- match_object/1, select/1, select14/1, all_keys/1, transaction/1, transaction_counters/1,
+ match_object/1, select/1, select14/1, select_reverse/1, select_reverse14/1, all_keys/1,
+ transaction/1, transaction_counters/1,
basic_nested/1, mix_of_nested_activities/1,
nested_trans_both_ok/1, nested_trans_child_dies/1,
nested_trans_parent_dies/1, nested_trans_both_dies/1,
@@ -67,7 +68,8 @@ end_per_testcase(Func, Conf) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
all() ->
[write, read, wread, delete, delete_object_bag, delete_object_set,
- match_object, select, select14, all_keys, transaction, transaction_counters,
+ match_object, select, select14, select_reverse, select_reverse14, all_keys,
+ transaction, transaction_counters,
{group, nested_activities}, {group, index_tabs},
{group, index_lifecycle}].
@@ -465,6 +467,111 @@ select14(Config) when is_list(Config) ->
Test(Tab4),
?verify_mnesia(Nodes, []).
+%% select_reverse
+select_reverse(suite) -> [];
+select_reverse(Config) when is_list(Config) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = select_reverse_tab,
+ Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+
+ OneRec = {Tab, 1, 2},
+ TwoRec = {Tab, 2, 3},
+ OnePat = [{{Tab, '$1', 2}, [], ['$_']}],
+ ?match({atomic, []},
+ mnesia:transaction(fun() -> mnesia:select_reverse(Tab, OnePat) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(OneRec) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(TwoRec) end)),
+ ?match({atomic, [OneRec]},
+ mnesia:transaction(fun() -> mnesia:select_reverse(Tab, OnePat) end)),
+
+ ?match({aborted, _},
+ mnesia:transaction(fun() -> mnesia:select_reverse(Tab, {match, '$1', 2}) end)),
+ ?match({aborted, _},
+ mnesia:transaction(fun() -> mnesia:select_reverse(Tab, [{'_', [], '$1'}]) end)),
+
+ ?match({'EXIT', {aborted, no_transaction}}, mnesia:select_reverse(Tab, OnePat)),
+ ?verify_mnesia(Nodes, []).
+
+%% more select_reverse
+select_reverse14(suite) -> [];
+select_reverse14(Config) when is_list(Config) ->
+ [Node1,Node2] = Nodes = ?acquire_nodes(2, Config),
+ Tab1 = select_reverse14_ets,
+ Tab2 = select_reverse14_dets,
+ Tab3 = select_reverse14_remote,
+ Tab4 = select_reverse14_remote_dets,
+ Schemas = [[{name, Tab1}, {type, ordered_set}, {attributes, [k, v]}, {ram_copies, [Node1]}],
+ [{name, Tab2}, {attributes, [k, v]}, {disc_only_copies, [Node1]}],
+ [{name, Tab3}, {type, ordered_set}, {attributes, [k, v]}, {ram_copies, [Node2]}],
+ [{name, Tab4}, {attributes, [k, v]}, {disc_only_copies, [Node2]}]],
+ [?match({atomic, ok}, mnesia:create_table(Schema)) || Schema <- Schemas],
+
+ %% Some Helpers
+ Trans = fun(Fun) -> mnesia:transaction(Fun) end,
+ Dirty = fun(Fun) -> mnesia:async_dirty(Fun) end,
+ LoopHelp = fun('$end_of_table',_) -> [];
+ ({Recs,Cont},Fun) ->
+ Sel = mnesia:select_reverse(Cont),
+ Recs ++ Fun(Sel, Fun)
+ end,
+ Loop = fun(Table,Pattern) ->
+ Sel = mnesia:select_reverse(Table, Pattern, 1, read),
+ Res = LoopHelp(Sel,LoopHelp),
+ DescFun = fun(A, B) -> A > B end,
+ case mnesia:table_info(Table, type) of
+ ordered_set -> Res;
+ _ -> lists:sort(DescFun, Res)
+ end
+ end,
+ Test =
+ fun(Tab, TabType) ->
+ OneRec = {Tab, 1, 2},
+ TwoRec = {Tab, 2, 3},
+ OnePat = [{{Tab, '$1', 2}, [], ['$_']}],
+ All = [TwoRec,OneRec],
+ AllPat = [{'_', [], ['$_']}],
+
+ ?match({atomic, []}, Trans(fun() -> Loop(Tab, OnePat) end)),
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(OneRec) end)),
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(TwoRec) end)),
+ ?match({atomic, [OneRec]}, Trans(fun() -> Loop(Tab, OnePat) end)),
+ ?match({atomic, All}, Trans(fun() -> Loop(Tab, AllPat) end)),
+
+ {atomic,{_, ContOne}} = Trans(fun() -> mnesia:select_reverse(Tab, OnePat, 1, read) end),
+ ?match({aborted, wrong_transaction}, Trans(fun() -> mnesia:select_reverse(ContOne) end)),
+ ?match('$end_of_table', Dirty(fun() -> mnesia:select_reverse(ContOne) end)),
+
+ {atomic,{_, ContAll}} = Trans(fun() -> mnesia:select_reverse(Tab, AllPat, 1, read) end),
+ ?match({aborted, wrong_transaction}, Trans(fun() -> mnesia:select_reverse(ContAll) end)),
+ ?match({[_], _}, Dirty(fun() -> mnesia:select_reverse(ContAll) end)),
+
+ {atomic,_} = Trans(fun() ->
+ case TabType of
+ ets ->
+ {[TwoRec], Cont} = mnesia:select_reverse(Tab, AllPat, 1, read),
+ {[OneRec], _Cont} = mnesia:select_reverse(Cont);
+ dets ->
+ %% DETS will behave like a normal `select`
+ {[OneRec], Cont} = mnesia:select_reverse(Tab, AllPat, 1, read),
+ {[TwoRec], _Cont} = mnesia:select_reverse(Cont)
+ end
+ end),
+
+ ?match({aborted, _}, Trans(fun() -> mnesia:select_reverse(Tab, {match, '$1', 2},1,read) end)),
+ ?match({aborted, _}, Trans(fun() -> mnesia:select_reverse(Tab, [{'_', [], '$1'}],1,read) end)),
+ ?match({aborted, _}, Trans(fun() -> mnesia:select_reverse(sune) end)),
+ ?match({'EXIT', {aborted, no_transaction}}, mnesia:select_reverse(Tab, OnePat,1,read)),
+ ?match({aborted, {badarg,sune}},
+ Trans(fun() -> mnesia:select_reverse(sune) end))
+ end,
+ Test(Tab1, ets),
+ Test(Tab2, dets),
+ Test(Tab3, ets),
+ Test(Tab4, dets),
+ ?verify_mnesia(Nodes, []).
%% Pick all keys from table
--
2.43.0