File 2452-mnesia-add-l-select_reverse-coverage-docs-and-mnesia.patch of Package erlang

From cda466cebfa809e9fcf7a629538b82018affe4d3 Mon Sep 17 00:00:00 2001
From: Bentheburrito <github.arson718@passmail.net>
Date: Tue, 11 Mar 2025 22:59:48 -0700
Subject: [PATCH 2/8] mnesia: add'l select_reverse coverage, docs, and
 mnesia_frag impl

also fix an apparent bug in ext_test_server's repair_continuation
handle_call clause
---
 lib/mnesia/doc/guides/mnesia_app_b.md         | 10 +++
 lib/mnesia/doc/guides/mnesia_chap4.md         |  9 +++
 lib/mnesia/src/mnesia_frag.erl                |  9 +++
 lib/mnesia/test/ext_test.erl                  | 15 ++++-
 lib/mnesia/test/ext_test_server.erl           | 61 ++++++++++++-----
 lib/mnesia/test/mnesia_evil_coverage_test.erl |  6 ++
 lib/mnesia/test/mnesia_frag_test.erl          | 35 ++++++++++
 lib/mnesia/test/mnesia_trans_access_test.erl  | 66 +++++++++++++++++--
 8 files changed, 190 insertions(+), 21 deletions(-)

diff --git a/lib/mnesia/doc/guides/mnesia_app_b.md b/lib/mnesia/doc/guides/mnesia_app_b.md
index 926d1f0e46..bddbd4a26e 100644
--- a/lib/mnesia/doc/guides/mnesia_app_b.md
+++ b/lib/mnesia/doc/guides/mnesia_app_b.md
@@ -32,6 +32,8 @@ limitations under the License.
          write/5, delete/5, delete_object/5,
          read/5, match_object/5, all_keys/4,
          select/5, select/6, select_cont/3,
+         select_cont/4, select_reverse/5,
+         select_reverse/6,
          index_match_object/6, index_read/6,
          foldl/6, foldr/6, table_info/4,
          first/3, next/4, prev/4, last/3,
@@ -82,6 +84,14 @@ select(ActivityId, Opaque, Tab, MatchSpec, LockKind) ->
 select(ActivityId, Opaque, Tab, MatchSpec, Limit, LockKind) ->
     init_select(ActivityId, Opaque, Tab, MatchSpec, Limit, LockKind).
 
+select_reverse(ActivityId, Opaque, Tab, MatchSpec, LockKind) ->
+    select(ActivityId, Opaque, Tab, MatchSpec, LockKind).
+
+select_reverse(ActivityId, Opaque, Tab, MatchSpec, Limit, LockKind) ->
+    select(ActivityId, Opaque, Tab, MatchSpec, Limit, LockKind).
+
+select_cont(Tid,Ts,Cont,_Dir) -> select_cont(Tid,Ts,Cont).
+
 select_cont(_Tid,_,{frag_cont, '$end_of_table', [],_}) -> '$end_of_table';
 select_cont(Tid,Ts,{frag_cont, '$end_of_table', [{Tab,Node,Type}|Rest],Args}) ->
     {Spec,LockKind,Limit} = Args,
diff --git a/lib/mnesia/doc/guides/mnesia_chap4.md b/lib/mnesia/doc/guides/mnesia_chap4.md
index cfc9217029..e50b21b60a 100644
--- a/lib/mnesia/doc/guides/mnesia_chap4.md
+++ b/lib/mnesia/doc/guides/mnesia_chap4.md
@@ -453,6 +453,8 @@ locks are acquired. The following functions are available:
   equivalent of `mnesia:match_object/1`.
 - [`mnesia:dirty_select(Tab, Pat)`](`mnesia:dirty_select/2`) is the dirty
   equivalent of `mnesia:select/2`.
+- [`mnesia:dirty_select_reverse(Tab, Pat)`](`mnesia:dirty_select_reverse/2`) is
+  the dirty equivalent of `mnesia:select_reverse/2`.
 - [`mnesia:dirty_index_match_object(Pat, Pos)`](`mnesia:dirty_index_match_object/2`)
   is the dirty equivalent of `mnesia:index_match_object/2`.
 - [`mnesia:dirty_index_read(Tab, SecondaryKey, Pos)`](`mnesia:dirty_index_read/3`)
@@ -577,6 +579,8 @@ operations, as listed here, can be passed on as arguments to the function
 - `mnesia:read/3` (`mnesia:read/1`, `mnesia:wread/1`)
 - [`mnesia:match_object/2`](`mnesia:match_object/3`) (`mnesia:match_object/1`)
 - [`mnesia:select/3`](`mnesia:select/2`) (`mnesia:select/2`)
+- [`mnesia:select_reverse/3`](`mnesia:select_reverse/2`)
+  (`mnesia:select_reverse/2`)
 - `mnesia:foldl/3` (`mnesia:foldl/4`, `mnesia:foldr/3`, `mnesia:foldr/4`)
 - `mnesia:all_keys/1`
 - `mnesia:index_match_object/4` (`mnesia:index_match_object/2`)
@@ -845,6 +849,11 @@ or less results than specified with `NObjects` can be returned in the result
 list, even the empty list can be returned even if there are more results to
 collect.
 
+There is also [`select_reverse/1,2,3,4`](`mnesia:select_reverse/2`), should you
+want to traverse from the end of the result set. This is only applicable to
+`ram_copies` or `disc_copies` tables of type `ordered_set`. For other table
+configurations, it behaves the same as `mnesia:select/1,2,3,4`.
+
 > #### Warning {: .warning }
 >
 > There is a severe performance penalty in using `mnesia:select/1,2,3,4` after
diff --git a/lib/mnesia/src/mnesia_frag.erl b/lib/mnesia/src/mnesia_frag.erl
index 6973fe1a5f..d0422d17d4 100644
--- a/lib/mnesia/src/mnesia_frag.erl
+++ b/lib/mnesia/src/mnesia_frag.erl
@@ -37,6 +37,8 @@
 	 write/5, delete/5, delete_object/5,
 	 read/5, match_object/5, all_keys/4,
 	 select/5,select/6,select_cont/3,
+	 select_cont/4, select_reverse/5,
+	 select_reverse/6,
 	 index_match_object/6, index_read/6,
 	 foldl/6, foldr/6, table_info/4,
 	 first/3, next/4, prev/4, last/3,
@@ -118,6 +120,11 @@ select(ActivityId, Opaque, Tab, MatchSpec, LockKind) ->
 select(ActivityId, Opaque, Tab, MatchSpec, Limit, LockKind) ->
     init_select(ActivityId, Opaque, Tab, MatchSpec, Limit, LockKind).
 
+select_reverse(ActivityId, Opaque, Tab, MatchSpec, LockKind) ->
+    select(ActivityId, Opaque, Tab, MatchSpec, LockKind).
+
+select_reverse(ActivityId, Opaque, Tab, MatchSpec, Limit, LockKind) ->
+    select(ActivityId, Opaque, Tab, MatchSpec, Limit, LockKind).
 
 all_keys(ActivityId, Opaque, Tab, LockKind) ->
     Match = [mnesia:all_keys(ActivityId, Opaque, Frag, LockKind)
@@ -336,6 +343,8 @@ init_select(Tid,Opaque,Tab,Pat,Limit,LockKind) ->
 	    frag_sel_cont(Res, NameNodes, {Pat,LockKind,Limit})
     end.
 
+select_cont(Tid,Ts,Cont,_Dir) -> select_cont(Tid,Ts,Cont).
+
 select_cont(_Tid,_,{frag_cont, '$end_of_table', [],_}) -> '$end_of_table';
 select_cont(Tid,Ts,{frag_cont, '$end_of_table', [{Tab,Node,Type}|Rest],Args}) ->
     {Spec,LockKind,Limit} = Args,
diff --git a/lib/mnesia/test/ext_test.erl b/lib/mnesia/test/ext_test.erl
index 711503fc87..92d073751d 100644
--- a/lib/mnesia/test/ext_test.erl
+++ b/lib/mnesia/test/ext_test.erl
@@ -49,7 +49,8 @@
 	 insert/3, update_counter/4,
 	 lookup/3,
 	 delete/3, match_delete/3,
-	 select/1, select/3, select/4, repair_continuation/2
+	 select/1, select/3, select/4, select_reverse/1,
+	 select_reverse/3, select_reverse/4, repair_continuation/2
 	]).
 
 semantics(ext_ram_copies, storage) -> ram_copies;
@@ -248,6 +249,18 @@ select(Alias, Tab, Ms, Limit) ->
     ?DBG({Alias, ext_test_server:tab_to_list(Tab), Ms, Limit}),
     call({?FUNCTION_NAME, Alias, Tab, Ms, Limit}).
 
+select_reverse(Continuation) ->
+    ?DBG(Continuation),
+    call({?FUNCTION_NAME, Continuation}).
+
+select_reverse(Alias, Tab, Ms) ->
+    ?DBG({Alias, ext_test_server:tab_to_list(Tab), Ms}),
+    call({?FUNCTION_NAME, Alias, Tab, Ms}).
+
+select_reverse(Alias, Tab, Ms, Limit) ->
+    ?DBG({Alias, ext_test_server:tab_to_list(Tab), Ms, Limit}),
+    call({?FUNCTION_NAME, Alias, Tab, Ms, Limit}).
+
 repair_continuation(Cont, Ms) ->
     ?DBG({Cont, Ms}),
     call({?FUNCTION_NAME, Cont, Ms}).
diff --git a/lib/mnesia/test/ext_test_server.erl b/lib/mnesia/test/ext_test_server.erl
index 7a87895e80..7e022da266 100644
--- a/lib/mnesia/test/ext_test_server.erl
+++ b/lib/mnesia/test/ext_test_server.erl
@@ -123,28 +123,37 @@ receive_data(Data, Alias, Tab, Sender, {Name, Sender} = MnesiaState, State) ->
     ?DBG({Data, Alias, tab_to_list(Tab), State}),
     receive_data(Data, Alias, Tab, Sender, {Name, Tab, Sender}, State).
 
-select(Alias, Tab, Ms, State) ->
-    Res = select(Alias, Tab, Ms, 100000, State),
-    select_1(Alias, Res).
+select(Alias, Tab, Ms, State, Dir) ->
+    Res = select(Alias, Tab, Ms, 100000, State, Dir),
+    select_1(Alias, Res, Dir).
 
-select_1(_Alias, '$end_of_table') -> [];
-select_1(ext_ram_copies, {Acc, C}) ->
+select_1(_Alias, '$end_of_table', _Dir) -> [];
+select_1(ext_ram_copies, {Acc, C}, forward) ->
     case ets:select(C) of
         '$end_of_table' -> Acc;
         {New, Cont} ->
-            select_1(ext_ram_copies, {New ++ Acc, Cont})
+            select_1(ext_ram_copies, {New ++ Acc, Cont}, forward)
     end;
-select_1(ext_disc_only_copies, {Acc, C}) ->
+select_1(ext_ram_copies, {Acc, C}, reverse) ->
+    case ets:select_reverse(C) of
+        '$end_of_table' -> Acc;
+        {New, Cont} ->
+            select_1(ext_ram_copies, {New ++ Acc, Cont}, reverse)
+    end;
+select_1(ext_disc_only_copies, {Acc, C}, Dir) ->
     case dets:select(C) of
         '$end_of_table' -> Acc;
         {New, Cont} ->
-            select_1(ext_disc_only_copies, {New ++ Acc, Cont})
+            select_1(ext_disc_only_copies, {New ++ Acc, Cont}, Dir)
     end.
 
-select(ext_ram_copies, Tab, Ms, Limit, State) when is_integer(Limit); Limit =:= infinity ->
+select(ext_ram_copies, Tab, Ms, Limit, State, forward) when is_integer(Limit); Limit =:= infinity ->
     ?DBG({ext_ram_copies, tab_to_list(Tab), Ms, Limit}),
     ets:select(tab_to_tid(Tab, State), Ms, Limit);
-select(ext_disc_only_copies, Tab, Ms, Limit, State) when is_integer(Limit); Limit =:= infinity ->
+select(ext_ram_copies, Tab, Ms, Limit, State, reverse) when is_integer(Limit); Limit =:= infinity ->
+    ?DBG({ext_ram_copies, tab_to_list(Tab), Ms, Limit}),
+    ets:select_reverse(tab_to_tid(Tab, State), Ms, Limit);
+select(ext_disc_only_copies, Tab, Ms, Limit, State, _Dir) when is_integer(Limit); Limit =:= infinity ->
     ?DBG({ext_disc_only_copies, tab_to_list(Tab), Ms, Limit}),
     dets:select(tab_to_tid(Tab, State), Ms, Limit).
 
@@ -377,12 +386,34 @@ handle_call({select, C}, _From, State) ->
 
 handle_call({select, Alias, Tab, Ms}, _From, State) ->
     ?DBG({select, Alias, tab_to_list(Tab), Ms}),
-    Res = ?TRY(select(Alias, Tab, Ms, State)),
+    Res = ?TRY(select(Alias, Tab, Ms, State, forward)),
     {reply, Res, State};
 
 handle_call({select, Alias, Tab, Ms, Limit}, _From, State) ->
     ?DBG({select, Alias, tab_to_list(Tab), Ms, Limit}),
-    Res = ?TRY(select(Alias, Tab, Ms, Limit, State)),
+    Res = ?TRY(select(Alias, Tab, Ms, Limit, State, forward)),
+    {reply, Res, State};
+
+handle_call({select_reverse, '$end_of_table' = End}, _From, State) ->
+    ?DBG({select_reverse, End}),
+    {reply, End, State};
+handle_call({select_reverse, C}, _From, State) when element(1, C) == dets_cont ->
+    ?DBG({select_reverse, {ext_disc_only_copies, C}}),
+    Res = ?TRY(dets:select(C)),
+    {reply, Res, State};
+handle_call({select_reverse, C}, _From, State) ->
+    ?DBG({select_reverse, {ext_ram_copies, C}}),
+    Res = ?TRY(ets:select_reverse(C)),
+    {reply, Res, State};
+
+handle_call({select_reverse, Alias, Tab, Ms}, _From, State) ->
+    ?DBG({select_reverse, Alias, tab_to_list(Tab), Ms}),
+    Res = ?TRY(select(Alias, Tab, Ms, State, reverse)),
+    {reply, Res, State};
+
+handle_call({select_reverse, Alias, Tab, Ms, Limit}, _From, State) ->
+    ?DBG({select_reverse, Alias, tab_to_list(Tab), Ms, Limit}),
+    Res = ?TRY(select(Alias, Tab, Ms, Limit, State, reverse)),
     {reply, Res, State};
 
 handle_call({repair_continuation, '$end_of_table' = Cont, _Ms}, _From, State) ->
@@ -392,8 +423,8 @@ handle_call({repair_continuation, Cont, Ms}, _From, State) when element(1, Cont)
     ?DBG({repair_continuation, ext_disc_only_copies, Cont, Ms}),
     Res = ?TRY(dets:repair_continuation(Cont, Ms)),
     {reply, Res, State};
-handle_call({repair_continuation, ext_ram_copies, Cont, Ms}, _From, State) ->
-    ?DBG({repair_continuation, Cont, Ms}),
+handle_call({repair_continuation, Cont, Ms}, _From, State) ->
+    ?DBG({repair_continuation, ext_ram_copies, Cont, Ms}),
     Res = ?TRY(ets:repair_continuation(Cont, Ms)),
     {reply, Res, State}.
 
@@ -427,4 +458,4 @@ tab_to_filename(Tab) ->
 
 tab_to_tid(Tab, #state{tables = Tables}) ->
     Table = maps:get(Tab, Tables),
-    Table#table.tid.
\ No newline at end of file
+    Table#table.tid.
diff --git a/lib/mnesia/test/mnesia_evil_coverage_test.erl b/lib/mnesia/test/mnesia_evil_coverage_test.erl
index 1d5e30211b..31df7a5864 100644
--- a/lib/mnesia/test/mnesia_evil_coverage_test.erl
+++ b/lib/mnesia/test/mnesia_evil_coverage_test.erl
@@ -2476,9 +2476,11 @@ record_name_dirty_access(Storage, Config) ->
     ?match(ok, mnesia:dirty_delete_object(Tab, {RecName, 2, 21})),
 
     Tens = ?sort([{RecName, 1, 10}, {RecName, 3, 10}]),
+    RevTens = lists:reverse(Tens),
     TenPat = {RecName, '_', 10},
     ?match(Tens, ?sort(mnesia:dirty_match_object(Tab, TenPat))),
     ?match(Tens, ?sort(mnesia:dirty_select(Tab, [{TenPat, [], ['$_']}]))),
+    ?match(RevTens, lists:reverse(?sort(mnesia:dirty_select_reverse(Tab, [{TenPat, [], ['$_']}])))),
 
     %% Subscription test
     E = mnesia_table_event,
@@ -2510,6 +2512,10 @@ record_name_dirty_access(Storage, Config) ->
     ?match(Twos, ?sort(mnesia:dirty_select(Tab, 
 					   [{mnesia:table_info(Tab, wild_pattern),
 					     [],['$_']}]))),
+    RevTwos = lists:reverse(Twos),
+    ?match(RevTwos, lists:reverse(?sort(mnesia:dirty_select_reverse(Tab,
+					   [{mnesia:table_info(Tab, wild_pattern),
+					     [],['$_']}])))),
 
     %% Traverse backup test
 
diff --git a/lib/mnesia/test/mnesia_frag_test.erl b/lib/mnesia/test/mnesia_frag_test.erl
index f2553faa07..09a9486f3e 100644
--- a/lib/mnesia/test/mnesia_frag_test.erl
+++ b/lib/mnesia/test/mnesia_frag_test.erl
@@ -701,6 +701,7 @@ evil_loop(Config) when is_list(Config) ->
 
     Match  = fun(Tab) -> mnesia:match_object(Tab, {'_', '_', '_'}, write) end,
     Select = fun(Tab) -> mnesia:select(Tab, [{'_', [], ['$_']}]) end,
+    SelectRev = fun(Tab) -> mnesia:select_reverse(Tab, [{'_', [], ['$_']}]) end,
     Trans  = fun(Fun, Args) -> mnesia:activity(transaction, Fun, Args, mnesia_frag) end,
     LoopHelp = fun('$end_of_table',_) ->
 		       [];
@@ -712,6 +713,16 @@ evil_loop(Config) when is_list(Config) ->
 		      Sel = mnesia:select(Table, [{'_', [], ['$_']}], 1, read),
 		      LoopHelp(Sel, LoopHelp)
 	      end,
+    RevLoopHelp = fun('$end_of_table',_) ->
+		       [];
+		  ({Res,Cont},Fun) ->
+		       Sel = mnesia:select_reverse(Cont),
+		       Res ++ Fun(Sel, Fun)
+	       end,
+    SelRevLoop = fun(Table) ->
+		      Sel = mnesia:select_reverse(Table, [{'_', [], ['$_']}], 1, read),
+		      RevLoopHelp(Sel, RevLoopHelp)
+	      end,
 
     R1 = {RecName, 2, 2},
     R2 = {RecName, 4, 4},
@@ -725,13 +736,19 @@ evil_loop(Config) when is_list(Config) ->
 	 end,
     S1 = lists:sort([R1, R2| Recs]),
     ?match(S1, sort_res(Trans(W1, [Tab1, Select]))),
+    ?match(S1, sort_res(Trans(W1, [Tab1, SelectRev]))),
     ?match(S1, sort_res(Trans(W1, [Tab1, Match]))),
     ?match(S1, sort_res(Trans(W1, [Tab1, SelLoop]))),
+    ?match(S1, sort_res(Trans(W1, [Tab1, SelRevLoop]))),
     ?match(S1, sort_res(Trans(W1, [Tab2, Select]))),
+    ?match(S1, sort_res(Trans(W1, [Tab2, SelectRev]))),
     ?match(S1, sort_res(Trans(W1, [Tab2, SelLoop]))),
+    ?match(S1, sort_res(Trans(W1, [Tab2, SelRevLoop]))),
     ?match(S1, sort_res(Trans(W1, [Tab2, Match]))),
     ?match(S1, sort_res(Trans(W1, [Tab3, Select]))),
+    ?match(S1, sort_res(Trans(W1, [Tab3, SelectRev]))),
     ?match(S1, sort_res(Trans(W1, [Tab3, SelLoop]))),
+    ?match(S1, sort_res(Trans(W1, [Tab3, SelRevLoop]))),
     ?match(S1, sort_res(Trans(W1, [Tab3, Match]))),
     [mnesia:dirty_delete_object(Frag, R) || R <- [R1, R2], 
 					   Tab <- Tabs,
@@ -746,14 +763,20 @@ evil_loop(Config) when is_list(Config) ->
     S2Bag = lists:sort([R1, R3 | Recs]),
     io:format("S2 = ~p\n", [S2]),
     ?match(S2, sort_res(Trans(W2, [Tab1, Select]))),
+    ?match(S2, sort_res(Trans(W2, [Tab1, SelectRev]))),
     ?match(S2, sort_res(Trans(W2, [Tab1, SelLoop]))),
+    ?match(S2, sort_res(Trans(W2, [Tab1, SelRevLoop]))),
     ?match(S2, sort_res(Trans(W2, [Tab1, Match]))),
     ?match(S2, sort_res(Trans(W2, [Tab2, Select]))),
+    ?match(S2, sort_res(Trans(W2, [Tab2, SelectRev]))),
     ?match(S2, sort_res(Trans(W2, [Tab2, SelLoop]))),
+    ?match(S2, sort_res(Trans(W2, [Tab2, SelRevLoop]))),
     ?match(S2, sort_res(Trans(W2, [Tab2, Match]))),
     io:format("S2Bag = ~p\n", [S2Bag]),
     ?match(S2Bag, sort_res(Trans(W2, [Tab3, Select]))),
+    ?match(S2Bag, sort_res(Trans(W2, [Tab3, SelectRev]))),
     ?match(S2Bag, sort_res(Trans(W2, [Tab3, SelLoop]))),
+    ?match(S2Bag, sort_res(Trans(W2, [Tab3, SelRevLoop]))),
     ?match(S2Bag, sort_res(Trans(W2, [Tab3, Match]))),
 
     W3 = fun(Tab,Search) -> 
@@ -764,13 +787,19 @@ evil_loop(Config) when is_list(Config) ->
     S3Bag = lists:sort([R4 | lists:delete(R1, Recs)]),
     S3 = lists:delete({RecName, 3, 3}, S3Bag),
     ?match(S3, sort_res(Trans(W3, [Tab1, Select]))),
+    ?match(S3, sort_res(Trans(W3, [Tab1, SelectRev]))),
     ?match(S3, sort_res(Trans(W3, [Tab1, SelLoop]))),
+    ?match(S3, sort_res(Trans(W3, [Tab1, SelRevLoop]))),
     ?match(S3, sort_res(Trans(W3, [Tab1, Match]))),
     ?match(S3, sort_res(Trans(W3, [Tab2, SelLoop]))),
+    ?match(S3, sort_res(Trans(W3, [Tab2, SelRevLoop]))),
     ?match(S3, sort_res(Trans(W3, [Tab2, Select]))),
+    ?match(S3, sort_res(Trans(W3, [Tab2, SelectRev]))),
     ?match(S3, sort_res(Trans(W3, [Tab2, Match]))),
     ?match(S3Bag, sort_res(Trans(W3, [Tab3, Select]))),
+    ?match(S3Bag, sort_res(Trans(W3, [Tab3, SelectRev]))),
     ?match(S3Bag, sort_res(Trans(W3, [Tab3, SelLoop]))),
+    ?match(S3Bag, sort_res(Trans(W3, [Tab3, SelRevLoop]))),
     ?match(S3Bag, sort_res(Trans(W3, [Tab3, Match]))),
 
     W4 = fun(Tab,Search) -> 
@@ -787,13 +816,19 @@ evil_loop(Config) when is_list(Config) ->
     S4Bag = lists:sort([R5 | S3Bag]),
     S4    = lists:sort([R5 | S3]),
     ?match(S4, sort_res(Trans(W4, [Tab1, Select]))),
+    ?match(S4, sort_res(Trans(W4, [Tab1, SelectRev]))),
     ?match(S4, sort_res(Trans(W4, [Tab1, SelLoop]))),
+    ?match(S4, sort_res(Trans(W4, [Tab1, SelRevLoop]))),
     ?match(S4, sort_res(Trans(W4, [Tab1, Match]))),
     ?match(S4, sort_res(Trans(W4, [Tab2, Select]))),
+    ?match(S4, sort_res(Trans(W4, [Tab2, SelectRev]))),
     ?match(S4, sort_res(Trans(W4, [Tab2, SelLoop]))),
+    ?match(S4, sort_res(Trans(W4, [Tab2, SelRevLoop]))),
     ?match(S4, sort_res(Trans(W4, [Tab2, Match]))),
     ?match(S4Bag, sort_res(Trans(W4, [Tab3, Select]))),
+    ?match(S4Bag, sort_res(Trans(W4, [Tab3, SelectRev]))),
     ?match(S4Bag, sort_res(Trans(W4, [Tab3, SelLoop]))),
+    ?match(S4Bag, sort_res(Trans(W4, [Tab3, SelRevLoop]))),
     ?match(S4Bag, sort_res(Trans(W4, [Tab3, Match]))),
     [mnesia:dirty_delete_object(Tab, R) || R <- [{RecName, 3, 3}, R5], Tab <- Tabs],
 
diff --git a/lib/mnesia/test/mnesia_trans_access_test.erl b/lib/mnesia/test/mnesia_trans_access_test.erl
index 1dddd3ed34..3347388d28 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, select_reverse/1, select_reverse14/1, all_keys/1,
+         match_object/1, select/1, select14/1, select_reverse/1,
+         select_reverse14/1, select_reverse_index/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,
@@ -68,7 +69,8 @@ end_per_testcase(Func, Conf) ->
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 all() ->
     [write, read, wread, delete, delete_object_bag, delete_object_set,
-     match_object, select, select14, select_reverse, select_reverse14, all_keys,
+     match_object, select, select14, select_reverse, select_reverse14,
+     select_reverse_index, all_keys,
      transaction, transaction_counters,
      {group, nested_activities}, {group, index_tabs},
      {group, index_lifecycle}].
@@ -477,7 +479,10 @@ select_reverse(Config) when is_list(Config) ->
 
     OneRec = {Tab, 1, 2},
     TwoRec = {Tab, 2, 3},
+    ThreeRec = {Tab, 3, 4},
     OnePat = [{{Tab, '$1', 2}, [], ['$_']}],
+    All = [ThreeRec,TwoRec,OneRec],
+    AllPat = [{'_', [], ['$_']}],
     ?match({atomic, []},
 	   mnesia:transaction(fun() -> mnesia:select_reverse(Tab, OnePat) end)),
     ?match({atomic, ok},
@@ -486,6 +491,13 @@ select_reverse(Config) when is_list(Config) ->
 	   mnesia:transaction(fun() -> mnesia:write(TwoRec) end)),
     ?match({atomic, [OneRec]},
 	   mnesia:transaction(fun() -> mnesia:select_reverse(Tab, OnePat) end)),
+    %% Need to rpc:call here, as mnesia:ets assumes the ETS table is local to the node
+    ?match([OneRec], rpc:call(Node1, mnesia, ets, [fun() -> mnesia:select_reverse(Tab, OnePat) end])),
+
+    ?match({atomic, [OneRec]}, mnesia:transaction(fun() ->
+	    mnesia:write(ThreeRec),
+	    mnesia:select_reverse(Tab, OnePat)
+    end)),
 
     ?match({aborted, _},
 	   mnesia:transaction(fun() -> mnesia:select_reverse(Tab, {match, '$1', 2}) end)),
@@ -503,10 +515,12 @@ select_reverse14(Config) when is_list(Config) ->
     Tab2 = select_reverse14_dets,
     Tab3 = select_reverse14_remote,
     Tab4 = select_reverse14_remote_dets,
+    Tab5 = select_reverse14_xets,
     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]}]],
+	       [{name, Tab4}, {attributes, [k, v]}, {disc_only_copies, [Node2]}],
+	       [{name, Tab5}, {type, ordered_set}, {attributes, [k, v]}, {ext_ram_copies, [Node1]}]],
     [?match({atomic, ok},  mnesia:create_table(Schema)) || Schema <- Schemas],
 
     %% Some Helpers
@@ -535,12 +549,13 @@ select_reverse14(Config) when is_list(Config) ->
 		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, ok},       mnesia:transaction(fun() -> mnesia:write(OneRec) end)),
+		?match({atomic, [OneRec]}, mnesia:transaction(fun() -> mnesia:write(TwoRec), Loop(Tab, OnePat) 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({'EXIT', {aborted, no_transaction}}, mnesia:select_reverse(ContOne)),
 		?match({aborted, wrong_transaction}, Trans(fun() -> mnesia:select_reverse(ContOne) end)),
 		?match('$end_of_table',              Dirty(fun() -> mnesia:select_reverse(ContOne) end)),
 
@@ -571,6 +586,47 @@ select_reverse14(Config) when is_list(Config) ->
     Test(Tab2, dets),
     Test(Tab3, ets),
     Test(Tab4, dets),
+    Test(Tab5, ets),
+
+    OneRec = {Tab1, 1, 2},
+    TwoRec = {Tab1, 2, 3},
+    All = [TwoRec,OneRec],
+    AllPat = [{'_', [], ['$_']}],
+    ?match(All, rpc:call(Node1, mnesia, ets, [fun() -> Loop(Tab1, AllPat) end])),
+
+    ?verify_mnesia(Nodes, []).
+
+%% select_reverse, lookup with an index
+select_reverse_index(suite) -> [];
+select_reverse_index(Config) when is_list(Config) ->
+    [Node1] = Nodes = ?acquire_nodes(1, Config),
+    Tab1 = select_reverse_index_tab_ordset,
+    Tab2 = select_reverse_index_tab_set,
+    Schema1 = [{name, Tab1}, {attributes, [k, v, alt_key]}, {index, [alt_key]},
+			  {type, ordered_set}, {ram_copies, [Node1]}],
+    Schema2 = [{name, Tab2}, {attributes, [k, v, alt_key]}, {index, [alt_key]},
+			  {type, set}, {ram_copies, [Node1]}],
+    ?match({atomic, ok},  mnesia:create_table(Schema1)),
+    ?match({atomic, ok},  mnesia:create_table(Schema2)),
+
+    Test =
+	fun(Tab) ->
+        OneRec = {Tab, 1, 2, 3},
+        TwoRec = {Tab, 2, 3, 4},
+        OnePat = [{{Tab, '$1', '_', 3}, [], ['$_']}],
+        ?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))
+    end,
+
+    Test(Tab1),
+    Test(Tab2),
+
     ?verify_mnesia(Nodes, []).
 
 %% Pick all keys from table
-- 
2.43.0

openSUSE Build Service is sponsored by