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

openSUSE Build Service is sponsored by