Skip to content

mnesia: add select_reverse/1-4 #9475

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

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

Bentheburrito
Copy link

Hello, this PR adds mnesia:select_reverse/1-4 and supporting functions in mnesia_lib, bringing the mnesia API closer to ets's.

Related issue: #8993

Much of this is copy/paste of select/1-6, with the needed tweaks to call the new mnesia_lib:*_rev_* functions and lists:sort in descending order. The tests are also nearly identical to select's tests, only they assert on the expected reverse ordering.

@CLAassistant
Copy link

CLAassistant commented Feb 24, 2025

CLA assistant check
All committers have signed the CLA.

Copy link
Contributor

github-actions bot commented Feb 24, 2025

CT Test Results

  2 files   59 suites   18m 56s ⏱️
687 tests 537 ✅ 150 💤 0 ❌
741 runs  576 ✅ 165 💤 0 ❌

Results for commit 711a508.

♻️ This comment has been updated with latest results.

To speed up review, make sure that you have read Contributing to Erlang/OTP and that all checks pass.

See the TESTING and DEVELOPMENT HowTo guides for details about how to run test locally.

Artifacts

// Erlang/OTP Github Action Bot

@jhogberg jhogberg added the team:PS Assigned to OTP team PS label Feb 24, 2025
@Bentheburrito Bentheburrito force-pushed the mnesia-select-reverse branch 2 times, most recently from 3767c4f to 4d892c5 Compare March 6, 2025 01:40
Copy link
Contributor

@Mikaka27 Mikaka27 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, I've executed code coverage on this change, and we're missing things. Some cases of select_reverse are not covered by tests.
While not everything in existing code is covered by tests, we should keep it at least as good as before.
You can run code coverage like this (change path to mnesia.cover file):
make mnesia_test ARGS="-cover /workspaces/otp/lib/mnesia/test/mnesia.cover"

The other problematic thing is documentation:
You can see that your select_reverse functions in mnesia can execute Mod:select_reverse functions, so they can use mnesia_access callback module.
In at least those 2 places you should document the select_reverse functions:

Another thing is that mnesia_access behavior is implemented in mnesia_frag.erl so you should add select_reverse functions there as well.
Fragmented tables do not support ordering, so it should be fine to do:
select_reverse(...) -> select(...). in mnesia_frag.erl, but it should be covered by a test case as well.

@IngelaAndin IngelaAndin added the waiting waiting for changes/input from author label Mar 11, 2025
@Bentheburrito
Copy link
Author

Bentheburrito commented Mar 12, 2025

Hi, thanks for the review, I believe i've updated the docs + mnesia_frag.erl to your suggestions, and most of the highlighted coverage deficits.

While making these changes, I noticed there's a mnesia_backend_type behaviour, should I be making some kind of change there too? (maybe not, I assume it's different than mnesia_access?)

There are a couple of places I'm a bit confused on how to get coverage. The main one being in mnesia_lib - I tried to update {ext_test,ext_test_server}.erl to implement select_reverse, but found that mnesia_evil_coverage_test:record_name_dirty_access_xets would blow up with $ make mnesia_test ARGS="-suite mnesia_SUITE -group evil -cover $ERL_TOP/lib/mnesia/test/mnesia.cover" for reasons unclear to me...I'll attach the error report and the patch that causes it below in case you can spot what's wrong (the errors aren't very descriptive but I could also just be doing something silly)
disregard - was just missing a param and the error message didn't make that obvious

odd record_name_dirty_access_xets failure

=== Test case: [mnesia_evil_coverage_test:record_name_dirty_access_xets/1](file:///media/storage/Desktop/Erlang/otp/lib/mnesia/make_test_dir/ct_logs/[email protected]_22.02.26/make_test_dir.mnesia_test.mnesia_SUITE.groups.logs/run.2025-03-11_22.02.28/mnesia_evil_coverage_test.src.html#record_name_dirty_access_xets-1) (click for source code)

=== Config value:

    [{watchdog,<0.698.0>},
     {tc_logfile,"/media/storage/Desktop/Erlang/otp/lib/mnesia/make_test_dir/ct_logs/[email protected]_22.02.26/make_test_dir.mnesia_test.mnesia_SUITE.groups.logs/run.2025-03-11_22.02.28/mnesia_evil_coverage_test.record_name_dirty_access_xets.html"},
     {tc_group_properties,[{name,record_name_dirty_access}]},
     {tc_group_path,[[{name,record_name}],
                     [{suite,mnesia_evil_coverage_test}],
                     [{name,evil}],
                     [{name,light}]]},
     {data_dir,"/media/storage/Desktop/Erlang/otp/lib/mnesia/make_test_dir/mnesia_test/mnesia_evil_coverage_test_data/"},
     {priv_dir,"/media/storage/Desktop/Erlang/otp/lib/mnesia/make_test_dir/ct_logs/[email protected]_22.02.26/make_test_dir.mnesia_test.mnesia_SUITE.groups.logs/run.2025-03-11_22.02.28/log_private/"},
     {nodenames,[nod11741755749438275@laptop,
                 nod21741755749438270@laptop]}]

=== Current directory is "/media/storage/Desktop/Erlang/otp/lib/mnesia/make_test_dir/ct_logs/[email protected]_22.02.26"

=== Started at 2025-03-11 22:02:40




*** System report during mnesia_evil_coverage_test:record_name_dirty_access_xets/1 in record_name_dirty_access 2025-03-11 22:02:40.044 ***
[🔗](file:///media/storage/Desktop/Erlang/otp/lib/mnesia/make_test_dir/ct_logs/[email protected]_22.02.26/make_test_dir.mnesia_test.mnesia_SUITE.groups.logs/run.2025-03-11_22.02.28/mnesia_evil_coverage_test.record_name_dirty_access_xets.html#e-10)

=INFO REPORT==== 11-Mar-2025::22:02:40.044628 ===
    application: mnesia
    exited: normal
    type: temporary


mnesia_evil_coverage_test.erl(2473): <>ERROR<>
Not Matching Actual result was:
 {'EXIT',{aborted,{badarg,[record_name_dirty_access_ext_ram_copies,
                           {some_record,'_',10}]}}}
 [{mnesia,abort,1,[{file,"mnesia.erl"},{line,687}]},
  {mnesia_evil_coverage_test,record_name_dirty_access,2,
                             [{file,"mnesia_evil_coverage_test.erl"},
                              {line,2473}]},
  {test_server,ts_tc,3,[{file,"test_server.erl"},{line,1794}]},
  {test_server,run_test_case_eval1,6,[{file,"test_server.erl"},{line,1303}]},
  {test_server,run_test_case_eval,9,[{file,"test_server.erl"},{line,1235}]}]

mnesia_evil_coverage_test.erl(2474): <>ERROR<>
Not Matching Actual result was:
 {'EXIT',{aborted,{badarg,[record_name_dirty_access_ext_ram_copies,
                           [{{some_record,'_',10},[],['$_']}]]}}}
 [{mnesia,abort,1,[{file,"mnesia.erl"},{line,687}]},
  {mnesia_evil_coverage_test,record_name_dirty_access,2,
                             [{file,"mnesia_evil_coverage_test.erl"},
                              {line,2474}]},
  {test_server,ts_tc,3,[{file,"test_server.erl"},{line,1794}]},
  {test_server,run_test_case_eval1,6,[{file,"test_server.erl"},{line,1303}]},
  {test_server,run_test_case_eval,9,[{file,"test_server.erl"},{line,1235}]}]


*** System report during mnesia_evil_coverage_test:record_name_dirty_access_xets/1 in record_name_dirty_access 2025-03-11 22:02:59.487 ***
[🔗](file:///media/storage/Desktop/Erlang/otp/lib/mnesia/make_test_dir/ct_logs/[email protected]_22.02.26/make_test_dir.mnesia_test.mnesia_SUITE.groups.logs/run.2025-03-11_22.02.28/mnesia_evil_coverage_test.record_name_dirty_access_xets.html#e-11)

=INFO REPORT==== 11-Mar-2025::22:02:59.487449 ===
    application: mnesia
    exited: stopped
    type: temporary



*** System report during mnesia_evil_coverage_test:record_name_dirty_access_xets/1 in record_name_dirty_access 2025-03-11 22:03:04.544 ***
[🔗](file:///media/storage/Desktop/Erlang/otp/lib/mnesia/make_test_dir/ct_logs/[email protected]_22.02.26/make_test_dir.mnesia_test.mnesia_SUITE.groups.logs/run.2025-03-11_22.02.28/mnesia_evil_coverage_test.record_name_dirty_access_xets.html#e-12)

=SUPERVISOR REPORT==== 11-Mar-2025::22:03:04.544446 ===
    supervisor: {local,mnesia_kernel_sup}
    errorContext: child_terminated
    reason: killed
    offender: [{pid,<0.1198.0>},
               {id,mnesia_locker},
               {mfargs,{mnesia_locker,start,[]}},
               {restart_type,permanent},
               {significant,false},
               {shutdown,3000},
               {child_type,worker}]


<>WARNING<> Wait for tables test_server@laptop: [record_name_dirty_access_ext_ram_copies]


*** System report during mnesia_evil_coverage_test:record_name_dirty_access_xets/1 in record_name_dirty_access 2025-03-11 22:03:04.544 ***
[🔗](file:///media/storage/Desktop/Erlang/otp/lib/mnesia/make_test_dir/ct_logs/[email protected]_22.02.26/make_test_dir.mnesia_test.mnesia_SUITE.groups.logs/run.2025-03-11_22.02.28/mnesia_evil_coverage_test.record_name_dirty_access_xets.html#e-13)

=SUPERVISOR REPORT==== 11-Mar-2025::22:03:04.544686 ===
    supervisor: {local,mnesia_kernel_sup}
    errorContext: shutdown
    reason: reached_max_restart_intensity
    offender: [{pid,<0.1198.0>},
               {id,mnesia_locker},
               {mfargs,{mnesia_locker,start,[]}},
               {restart_type,permanent},
               {significant,false},
               {shutdown,3000},
               {child_type,worker}]


mnesia_evil_coverage_test.erl(2486): <>ERROR<>
Not Matching Actual result was:
 {'EXIT',{aborted,{no_exists,[record_name_dirty_access_ext_ram_copies,
                              {some_record,'_',10},
                              3]}}}
 [{mnesia,abort,1,[{file,"mnesia.erl"},{line,687}]},
  {mnesia_evil_coverage_test,record_name_dirty_access,2,
                             [{file,"mnesia_evil_coverage_test.erl"},
                              {line,2486}]},
  {test_server,ts_tc,3,[{file,"test_server.erl"},{line,1794}]},
  {test_server,run_test_case_eval1,6,[{file,"test_server.erl"},{line,1303}]},
  {test_server,run_test_case_eval,9,[{file,"test_server.erl"},{line,1235}]}]


*** System report during mnesia_evil_coverage_test:record_name_dirty_access_xets/1 in record_name_dirty_access 2025-03-11 22:03:05.382 ***
[🔗](file:///media/storage/Desktop/Erlang/otp/lib/mnesia/make_test_dir/ct_logs/[email protected]_22.02.26/make_test_dir.mnesia_test.mnesia_SUITE.groups.logs/run.2025-03-11_22.02.28/mnesia_evil_coverage_test.record_name_dirty_access_xets.html#e-14)

=SUPERVISOR REPORT==== 11-Mar-2025::22:03:05.382689 ===
    supervisor: {local,mnesia_sup}
    errorContext: child_terminated
    reason: shutdown
    offender: [{pid,<0.1195.0>},
               {id,mnesia_kernel_sup},
               {mfargs,{mnesia_kernel_sup,start,[]}},
               {restart_type,permanent},
               {significant,false},
               {shutdown,infinity},
               {child_type,supervisor}]



*** CT Error Notification 2025-03-11 22:03:05.383 ***
[🔗](file:///media/storage/Desktop/Erlang/otp/lib/mnesia/make_test_dir/ct_logs/[email protected]_22.02.26/make_test_dir.mnesia_test.mnesia_SUITE.groups.logs/run.2025-03-11_22.02.28/mnesia_evil_coverage_test.record_name_dirty_access_xets.html#e-16)

mnesia:abort failed on line 687
Reason: {aborted,{node_not_running,test_server@laptop}}

[Full error description and stacktrace](file:///media/storage/Desktop/Erlang/otp/lib/mnesia/make_test_dir/ct_logs/[email protected]_22.02.26/make_test_dir.mnesia_test.mnesia_SUITE.groups.logs/run.2025-03-11_22.02.28/mnesia_evil_coverage_test.record_name_dirty_access_xets.html#end)

Mnesia(test_server@laptop): ** ERROR ** (core dumped to file: "/media/storage/Desktop/Erlang/otp/lib/mnesia/make_test_dir/ct_logs/[email protected]_22.02.26/MnesiaCore.test_server@laptop")
 ** FATAL ** Cannot create table [record_name_dirty_access_ext_ram_copies,
                                  {ext,ext_ram_copies,ext_test}]: {mktab,
                                                                   {timeout,
                                                                    {gen_server,
                                                                     call,
                                                                     [{global,
                                                                       ext_test_server_test_server@laptop},
                                                                      {create_table,
                                                                       ext_ram_copies,
                                                                       record_name_dirty_access_ext_ram_copies,
                                                                       [{name,
                                                                         record_name_dirty_access_ext_ram_copies},
                                                                        {type,
                                                                         bag},
                                                                        {ram_copies,
                                                                         []},
                                                                        {disc_copies,
                                                                         []},
                                                                        {disc_only_copies,
                                                                         []},
                                                                        {ext_ram_copies,
                                                                         [test_server@laptop,
                                                                          nod11741755749438275@laptop]},
                                                                        {load_order,
                                                                         0},
                                                                        {access_mode,
                                                                         read_write},
                                                                        {majority,
                                                                         false},
                                                                        {index,
                                                                         [{3,
                                                                           ordered}]},
                                                                        {snmp,
                                                                         []},
                                                                        {local_content,
                                                                         false},
                                                                        {record_name,
                                                                         some_record},
                                                                        {attributes,
                                                                         [key,
                                                                          val]},
                                                                        {user_properties,
                                                                         []},
                                                                        {frag_properties,
                                                                         []},
                                                                        {storage_properties,
                                                                         []},
                                                                        {cookie,
                                                                         {{1741755762373999598,
                                                                           -576460752303420414,
                                                                           1},
                                                                          test_server@laptop}},
                                                                        {version,
                                                                         {{2,
                                                                           0},
                                                                          []}}]}]}}}


*** System report during mnesia_evil_coverage_test:record_name_dirty_access_xets/1 in record_name_dirty_access 2025-03-11 22:03:05.383 ***
[🔗](file:///media/storage/Desktop/Erlang/otp/lib/mnesia/make_test_dir/ct_logs/[email protected]_22.02.26/make_test_dir.mnesia_test.mnesia_SUITE.groups.logs/run.2025-03-11_22.02.28/mnesia_evil_coverage_test.record_name_dirty_access_xets.html#e-15)

=SUPERVISOR REPORT==== 11-Mar-2025::22:03:05.382910 ===
    supervisor: {local,mnesia_sup}
    errorContext: shutdown
    reason: reached_max_restart_intensity
    offender: [{pid,<0.1195.0>},
               {id,mnesia_kernel_sup},
               {mfargs,{mnesia_kernel_sup,start,[]}},
               {restart_type,permanent},
               {significant,false},
               {shutdown,infinity},
               {child_type,supervisor}]



*** System report during mnesia_evil_coverage_test:record_name_dirty_access_xets/1 in record_name_dirty_access 2025-03-11 22:03:05.383 ***
[🔗](file:///media/storage/Desktop/Erlang/otp/lib/mnesia/make_test_dir/ct_logs/[email protected]_22.02.26/make_test_dir.mnesia_test.mnesia_SUITE.groups.logs/run.2025-03-11_22.02.28/mnesia_evil_coverage_test.record_name_dirty_access_xets.html#e-17)

=ERROR REPORT==== 11-Mar-2025::22:03:05.383181 ===
Mnesia(test_server@laptop): ** ERROR ** (core dumped to file: "/media/storage/Desktop/Erlang/otp/lib/mnesia/make_test_dir/ct_logs/[email protected]_22.02.26/MnesiaCore.test_server@laptop_1741_755784_544413")
 ** FATAL ** Cannot create table [record_name_dirty_access_ext_ram_copies,
                                  {ext,ext_ram_copies,ext_test}]: {mktab,
                                                                   {timeout,
                                                                    {gen_server,
                                                                     call,
                                                                     [{global,
                                                                       ext_test_server_test_server@laptop},
                                                                      {create_table,
                                                                       ext_ram_copies,
                                                                       record_name_dirty_access_ext_ram_copies,
                                                                       [{name,
                                                                         record_name_dirty_access_ext_ram_copies},
                                                                        {type,
                                                                         bag},
                                                                        {ram_copies,
                                                                         []},
                                                                        {disc_copies,
                                                                         []},
                                                                        {disc_only_copies,
                                                                         []},
                                                                        {ext_ram_copies,
                                                                         [test_server@laptop,
                                                                          nod11741755749438275@laptop]},
                                                                        {load_order,
                                                                         0},
                                                                        {access_mode,
                                                                         read_write},
                                                                        {majority,
                                                                         false},
                                                                        {index,
                                                                         [{3,
                                                                           ordered}]},
                                                                        {snmp,
                                                                         []},
                                                                        {local_content,
                                                                         false},
                                                                        {record_name,
                                                                         some_record},
                                                                        {attributes,
                                                                         [key,
                                                                          val]},
                                                                        {user_properties,
                                                                         []},
                                                                        {frag_properties,
                                                                         []},
                                                                        {storage_properties,
                                                                         []},
                                                                        {cookie,
                                                                         {{1741755762373999598,
                                                                           -576460752303420414,
                                                                           1},
                                                                          test_server@laptop}},
                                                                        {version,
                                                                         {{2,
                                                                           0},
                                                                          []}}]}]}}}





=== Ended at 2025-03-11 22:03:05
=== Location: [{mnesia,abort,687},
              {mnesia_schema,attr_tab_to_pos,966},
              {mnesia,dirty_index_read,3187},
              {mnesia_evil_coverage_test,record_name_dirty_access,2487},
              {test_server,ts_tc,1794},
              {test_server,run_test_case_eval1,1303},
              {test_server,run_test_case_eval,1235}]
=== === Reason: {aborted,{node_not_running,test_server@laptop}}


[Test run history ](file:///media/storage/Desktop/Erlang/otp/lib/mnesia/make_test_dir/ct_logs/all_runs.html)| [Top level test index ](file:///media/storage/Desktop/Erlang/otp/lib/mnesia/make_test_dir/ct_logs/index.html)| [Latest test result](file:///media/storage/Desktop/Erlang/otp/lib/mnesia/make_test_dir/ct_logs/suite.log.latest.html)
Copyright © 2025 [Open Telecom Platform](http://www.erlang.org/)
Updated: Tue Mar 11 2025 22:02:40

git patch that causes above error

diff --git a/lib/mnesia/test/ext_test.erl b/lib/mnesia/test/ext_test.erl
index 7d57955511..9e2b07589d 100644
--- a/lib/mnesia/test/ext_test.erl
+++ b/lib/mnesia/test/ext_test.erl
@@ -47,7 +47,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;
@@ -246,6 +247,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 22a1ec8f0a..6f3e7e5007 100644
--- a/lib/mnesia/test/ext_test_server.erl
+++ b/lib/mnesia/test/ext_test_server.erl
@@ -121,28 +121,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) ->
+select(Alias, Tab, Ms, State, Dir) ->
     Res = select(Alias, Tab, Ms, 100000, State),
-    select_1(Alias, Res).
+    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).

@@ -375,12 +384,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) ->
@@ -425,4 +456,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.

By the way, do you know if it's possible to configure the test cases to use a specific data dir? The tests are a bit harsh (and slow) on my hard drive, so being able to point it at some tmpfs in memory would be helpful. Also, is there a way to run the tests in parallel? (I didn't see anything in TESTING.md about it)

@Mikaka27
Copy link
Contributor

Hi the patch you sent does not apply for me, you seem to have pushed commit to your branch today, could you send updated patch file?
Regarding putting logs somewhere else, there is -logdir parameter specified here, is that sufficient for you? https://www.erlang.org/doc/apps/common_test/run_test_chapter.html

@Bentheburrito Bentheburrito force-pushed the mnesia-select-reverse branch from 60e8147 to 774c2eb Compare March 13, 2025 05:58
@Bentheburrito
Copy link
Author

Hi the patch you sent does not apply for me, you seem to have pushed commit to your branch today, could you send updated patch file?

ah sorry, it was something silly, you can disregard the patch/error - mnesia_lib should have coverage on the access module clauses now.

there is -logdir parameter

thanks! That and -mnesia dir '"/path/to/data/dir"' helped speed things up

@IngelaAndin IngelaAndin removed the waiting waiting for changes/input from author label Mar 18, 2025
@Bentheburrito
Copy link
Author

Seems like an ephemeral error in CI? The mnesia check looks good on my fork

@Mikaka27
Copy link
Contributor

Mikaka27 commented Mar 20, 2025

Seems like an ephemeral error in CI? The mnesia check looks good on my fork

Hi, there was some configuration changed quite recently and I've had similar issues on my PR's as well. You can try to rebase on latest master and force push to your branch again.

I've closed some conversations when the issue was resolved, and added comments in existing ones, where issue is still present.

@Mikaka27 Mikaka27 added the waiting waiting for changes/input from author label Apr 1, 2025
@Mikaka27
Copy link
Contributor

Hi @Bentheburrito are you planning on continuing this PR?

@Bentheburrito
Copy link
Author

Hi @Bentheburrito are you planning on continuing this PR?

Yes, just need to make time to address the rest of your comments

Adds mnesia:select_reverse/1-4 and supporting functions in mnesia_lib,
bringing the mnesia API closer to ets's.

Related GH issue: erlang#8993
also fix an apparent bug in ext_test_server's repair_continuation
handle_call clause
@Bentheburrito Bentheburrito force-pushed the mnesia-select-reverse branch from 774c2eb to 9f5a4f5 Compare April 11, 2025 18:22
@Mikaka27 Mikaka27 removed the waiting waiting for changes/input from author label Apr 15, 2025
Copy link
Contributor

@Mikaka27 Mikaka27 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, I've added more comments. I've set old conversations as resolved, since the line numbers have changed, so it might be easier that way :)

{?DEFAULT_ACCESS, Tid, Ts} ->
select_reverse(Tid, Ts, Tab, Pat, NObjects, LockKind);
{Mod, Tid, Ts} ->
Mod:select_reverse(Tid, Ts, Tab, Pat, NObjects, LockKind);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see this line is still not covered.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This (and the other coverage comments below) appear to be covered for me running mnesia_evil_coverage_test + mnesia_frag_test. Can you share the test command you're running that shows the missing coverage in these spots?

for reference, what I'm doing is temporarily adding {mnesia_frag_test, all}, to the all/0 list in mnesia_evil_coverage_test.erl, then I run the evil tests with

make mnesia_test ARGS="-mnesia dir '"/mnesia_test_tmp"' -logdir /mnesia_test_tmp -suite mnesia_SUITE -group evil -cover $ERL_TOP/lib/mnesia/test/mnesia.cover"

(the -mnesia dir and -logdir probably aren't relevant, but just including for completeness)

image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will check then, maybe some test that covers this is disabled or something.
The command I'm using is:
make mnesia_test ARGS="-cover /workspaces/otp/lib/mnesia/test/mnesia.cover"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, I've checked this, and there are some tests which should be running, but they're not for some reason.
I will try to fix this, but we don't have to wait for the fix with this PR.

I will check this PR again, and also run this through our CI, and let you know if it's okay to merge.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh I see, sounds good, happy to rebase if you happen to fix the tests before then

@Mikaka27 Mikaka27 added the testing currently being tested, tag is used by OTP internal CI label Apr 23, 2025
Copy link
Contributor

@Mikaka27 Mikaka27 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi,
I've consulted with another team member, and there is still a problem here.
For example in select_reverse/1 and select/1 we're trying to pass direction, but it doesn't matter at all.

I've tried this in a test like this:

test(Config) when is_list(Config) ->
    [Node1] = Nodes = ?acquire_nodes(1, Config),
    Tab = select_reverse_tab,
    Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node1]}, {type, ordered_set}],
    ?match({atomic, ok},  mnesia:create_table(Schema)),

    OneRec = {Tab, 1, 2},
    TwoRec = {Tab, 2, 3},
    ThreeRec = {Tab, 3, 4},
    AllPat = [{'_', [], ['$_']}],

    ?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(ThreeRec) end)),

    % {[ThreeRec], Cont1} = ets:select_reverse(Tab, AllPat, 1),
    % {[TwoRec], Cont2} = ets:select(Cont1),
    % {[ThreeRec], _} = ets:select(Cont2),

    ?match({atomic, ThreeRec},
           mnesia:transaction(fun() ->
                                      {[ThreeRec], Cont1} = mnesia:select_reverse(Tab, AllPat, 1, read),
                                      {[TwoRec], Cont2} = mnesia:select_reverse(Cont1),
                                      {[ThreeRec], _} = mnesia:select(Cont2),
                                      ThreeRec
                              end)),

    ?verify_mnesia(Nodes, []).

You can see that this will fail, because you cannot change direction after you got your continuation (at least in this simple case). You can uncomment the ets case, and it also does not allow to change direction.

We think we can do the following here:

  1. get rid of mnesia:select_reverse/1 implementation and just make it call mnesia:select/1
  2. store direction alongside continuation in mnesia_select record, and don't allow to try to change direction

Sorry for not noticing this earlier.

@Mikaka27 Mikaka27 removed the testing currently being tested, tag is used by OTP internal CI label Apr 29, 2025
@Bentheburrito
Copy link
Author

That makes sense, I also noticed this at some point and didn't mention it since it matches how ETS works. I'll look at removing mnesia:select_reverse/1 in favor of select/1 though. Is there concern for backwards compatibility if I add the direction to the mnesia_select record? I've not done much in terms of hot-code upgrades, so correct me if I'm wrong: I think adding a new field would break old continuations?

@Mikaka27
Copy link
Contributor

Mikaka27 commented May 1, 2025

Hi,
It's good that it works like ets, I don't think we need to make it throw some exception if you try to reverse direction mid select since ets doesn't do that.

We only thought that you can get rid of implementation of select_reverse/1, and just do select_reverse(Cont) -> select(Cont).
I wouldn't remove mnesia:select_reverse/1 function itself, because ets has it, and it might be easier for users.

Regarding the hot code upgrades, yes it will break old continuations.
Typically how it's handled is that you handle old records as a tuple with one less element (without new added one).

I think in your case it would be easiest to do something like this:
At the beginning of mnesia:select_cont/3 check if State is a tuple with 10 elements (as there are currently 9 fields in a mnesia_select record and first tuple element is record name), and create a new record with 1 new field for direction, example:

select_cont(Tid,Ts,{mnesia_select,Tab,Tid,Node,Storage,Cont,Written,Spec,Type,Orig}) ->
Rec = #mnesia_select{tab = Tab, tid = Tid, node = Node, storage = Storage, cont = Cont, written = Written, spec = Spec, type = Type, orig = Orig, dir = forward},
select_cont(Tid, Ts, Rec);

At least I would try to do it like this, I hope I didn't make a mistake, I didn't check if that example would compile, sorry :(

@Bentheburrito
Copy link
Author

We only thought that you can get rid of implementation of select_reverse/1, and just do select_reverse(Cont) -> select(Cont).
I wouldn't remove mnesia:select_reverse/1 function itself

Thanks for clarifying, I misread that before. I pushed this change

Actually could you elaborate on the need to add Dir to the mnesia_select record? Would it make sense to let mnesia backends handle storing the direction in their continuation data structure (similar to how ETS seems to do this)?

@Bentheburrito
Copy link
Author

Hi @Mikaka27, just wanted to follow up to see if my last comment makes sense. To elaborate a little more: my thinking is, by not storing a Dir on the mnesia_select record, the table backend could have control over whether or not to support "direction switching" by alternating calls between select/1 and select_reverse/1.

I guess one assumption is that the backend documents this behavior difference from ets. What do you think?

Comment on lines 252 to 254
select_reverse(Continuation) ->
?DBG(Continuation),
call({?FUNCTION_NAME, Continuation}).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you removed mnesia_lib:db_select_rev_cont this will never be called, I think you can remove it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed it, you can check @Bentheburrito

@Mikaka27
Copy link
Contributor

Mikaka27 commented Jun 9, 2025

Hi @Mikaka27, just wanted to follow up to see if my last comment makes sense. To elaborate a little more: my thinking is, by not storing a Dir on the mnesia_select record, the table backend could have control over whether or not to support "direction switching" by alternating calls between select/1 and select_reverse/1.

I guess one assumption is that the backend documents this behavior difference from ets. What do you think?

We've talked about this in the team, and we shouldn't do it, it might be confusing to the users. It's better to stick to the behavior of ets.
The mnesia plugin system is already undocumented and some operations behave differently/don't work, we want to limit this as much as possible.

@IngelaAndin IngelaAndin added the waiting waiting for changes/input from author label Jun 17, 2025
@Mikaka27 Mikaka27 added the testing currently being tested, tag is used by OTP internal CI label Jun 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
team:PS Assigned to OTP team PS testing currently being tested, tag is used by OTP internal CI waiting waiting for changes/input from author
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants