Skip to content

Commit

Permalink
Merge pull request #46 from SergeTupchiy/add-mnesia-match_delete-OTP-26
Browse files Browse the repository at this point in the history
Add mnesia match_delete/2
  • Loading branch information
SergeTupchiy committed Dec 18, 2023
2 parents dc2ae54 + 6630e28 commit be6661c
Show file tree
Hide file tree
Showing 10 changed files with 261 additions and 16 deletions.
2 changes: 1 addition & 1 deletion OTP_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
26.1.2-2
26.1.2-3
15 changes: 10 additions & 5 deletions lib/mnesia/src/mnesia.erl
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
read_table_property/2, write_table_property/2, delete_table_property/2,
change_table_frag/2,
clear_table/1, clear_table/4,
match_delete/2,

%% Table load
dump_tables/1, wait_for_tables/2, force_load_table/1,
Expand Down Expand Up @@ -2808,21 +2809,25 @@ change_table_copy_type(T, N, S) ->

-spec clear_table(Tab::table()) -> t_result('ok').
clear_table(Tab) ->
match_delete(Tab, '_').

-spec match_delete(Tab::table(), ets:match_pattern()) -> t_result('ok').
match_delete(Tab, Pattern) ->
case get(mnesia_activity_state) of
State = {Mod, Tid, _Ts} when element(1, Tid) =/= tid ->
transaction(State, fun() -> do_clear_table(Tab) end, [], infinity, Mod, sync);
transaction(State, fun() -> do_clear_table(Tab, Pattern) end, [], infinity, Mod, sync);
undefined ->
transaction(undefined, fun() -> do_clear_table(Tab) end, [], infinity, ?DEFAULT_ACCESS, sync);
transaction(undefined, fun() -> do_clear_table(Tab, Pattern) end, [], infinity, ?DEFAULT_ACCESS, sync);
_ -> %% Not allowed for clear_table
mnesia:abort({aborted, nested_transaction})
end.

do_clear_table(Tab) ->
do_clear_table(Tab, Pattern) ->
case get(mnesia_activity_state) of
{?DEFAULT_ACCESS, Tid, Ts} ->
clear_table(Tid, Ts, Tab, '_');
clear_table(Tid, Ts, Tab, Pattern);
{Mod, Tid, Ts} ->
Mod:clear_table(Tid, Ts, Tab, '_');
Mod:clear_table(Tid, Ts, Tab, Pattern);
_ ->
abort(no_transaction)
end.
Expand Down
9 changes: 6 additions & 3 deletions lib/mnesia/src/mnesia_checkpoint.erl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
tm_prepare/1,
tm_retain/4,
tm_retain/5,
tm_retain/6,
tm_enter_pending/1,
tm_enter_pending/3,
tm_exit_pending/1
Expand Down Expand Up @@ -148,7 +149,6 @@ enter_still_pending([Tid | Tids], Tab) ->
enter_still_pending([], _Tab) ->
ok.


%% Looks up checkpoints for functions in mnesia_tm.
tm_retain(Tid, Tab, Key, Op) ->
case val({Tab, commit_work}) of
Expand All @@ -157,11 +157,14 @@ tm_retain(Tid, Tab, Key, Op) ->
_ ->
undefined
end.

tm_retain(Tid, Tab, Key, Op, Checkpoints) ->
tm_retain(Tid, Tab, Key, Op, Checkpoints, '_').

tm_retain(Tid, Tab, Key, Op, Checkpoints, Obj) ->
case Op of
clear_table ->
OldRecs = mnesia_lib:db_match_object(Tab, '_'),
OldRecs = mnesia_lib:db_match_object(Tab, Obj),
send_group_retain(OldRecs, Checkpoints, Tid, Tab, []),
OldRecs;
_ ->
Expand Down
8 changes: 6 additions & 2 deletions lib/mnesia/src/mnesia_dumper.erl
Original file line number Diff line number Diff line change
Expand Up @@ -404,8 +404,12 @@ dets_insert(Op,Tab,Key,Val, Storage0) ->
dets_updated(Tab,Key),
mnesia_lib:db_match_erase(Storage, Tab, Val);
clear_table ->
dets_cleared(Tab),
ok = mnesia_lib:db_match_erase(Storage, Tab, '_')
%% Val is a match_delete pattern
case Val of
'_' -> dets_cleared(Tab);
_ -> dets_updated(Tab, Val)
end,
ok = mnesia_lib:db_match_erase(Storage, Tab, Val)
end.

dets_updated(Tab,Key) ->
Expand Down
7 changes: 6 additions & 1 deletion lib/mnesia/src/mnesia_log.erl
Original file line number Diff line number Diff line change
Expand Up @@ -1024,9 +1024,14 @@ add_recs([LogH|Rest], N)
LogH#log_header.log_kind == dcl_log,
LogH#log_header.log_version >= "1.0" ->
add_recs(Rest, N);
add_recs([{{Tab, _Key}, _Val, clear_table} | Rest], N) ->
add_recs([{{Tab, _Key}, '_', clear_table} | Rest], N) ->
Size = ets:info(Tab, size),
true = ets:delete_all_objects(Tab),
add_recs(Rest, N+Size);
add_recs([{{Tab, _Key}, Pattern, clear_table} | Rest], N) ->
SizeBefore = ets:info(Tab, size),
true = ets:match_delete(Tab, Pattern),
SizeAfter = ets:info(Tab, size),
add_recs(Rest, N+SizeBefore-SizeAfter);
add_recs([], N) ->
N.
11 changes: 10 additions & 1 deletion lib/mnesia/src/mnesia_subscr.erl
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ report_table_event(Tab, Tid, Obj, Op) ->
report_table_event(Subscr, Tab, Tid, Obj, Op) ->
report_table_event(Subscr, Tab, Tid, Obj, Op, undefined).

report_table_event({subscribers, S1, S2}, Tab, Tid, _Obj, clear_table, _Old) ->
report_table_event({subscribers, S1, S2}, Tab, Tid, '_' = _Obj, clear_table, _Old) ->
What = {delete, {schema, Tab}, Tid},
deliver(S1, {mnesia_table_event, What}),
TabDef = mnesia_schema:cs2list(?catch_val({Tab, cstruct})),
Expand All @@ -163,6 +163,15 @@ report_table_event({subscribers, S1, S2}, Tab, Tid, _Obj, clear_table, _Old) ->
What4 = {write, schema, {schema, Tab, TabDef}, [], Tid},
deliver(S2, {mnesia_table_event, What4});

report_table_event({subscribers, S1, _S2}, Tab, Tid, Obj, clear_table, _Old) ->
%% Obj is a match pattern here.
%% Sending delete_object event is compatible with `mnesia_loader`,
%% that uses `db_match_erase/2` which actually removes records by pattern.
%% Extended event is omitted: it's possible to match and get `OldRecords`,
%% but the list can be quite large.
Standard = {delete_object, patch_record(Tab, Obj), Tid},
deliver(S1, {mnesia_table_event, Standard});

report_table_event({subscribers, Subscr, []}, Tab, Tid, Obj, Op, _Old) ->
What = {Op, patch_record(Tab, Obj), Tid},
deliver(Subscr, {mnesia_table_event, What});
Expand Down
2 changes: 1 addition & 1 deletion lib/mnesia/src/mnesia_tm.erl
Original file line number Diff line number Diff line change
Expand Up @@ -1955,7 +1955,7 @@ commit_del_object([H|R], Tid, Storage, Tab, K, Obj) when element(1, H) == index

commit_clear([], _, _, _, _, _) -> ok;
commit_clear([{checkpoints, CpList}|R], Tid, Storage, Tab, K, Obj) ->
mnesia_checkpoint:tm_retain(Tid, Tab, K, clear_table, CpList),
mnesia_checkpoint:tm_retain(Tid, Tab, K, clear_table, CpList, Obj),
commit_clear(R, Tid, Storage, Tab, K, Obj);
commit_clear([H|R], Tid, Storage, Tab, K, Obj)
when element(1, H) == subscribers ->
Expand Down
3 changes: 2 additions & 1 deletion lib/mnesia/test/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ MODULES= \
mnesia_cost \
mnesia_dbn_meters \
ext_test \
mnesia_index_plugin_test
mnesia_index_plugin_test \
mnesia_match_delete_test

DocExamplesDir := ../doc/src/

Expand Down
3 changes: 2 additions & 1 deletion lib/mnesia/test/mnesia_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ groups() ->
[{light, [],
[{group, install}, {group, nice}, {group, evil},
{group, mnesia_frag_test, light}, {group, qlc}, {group, index_plugins},
{group, registry}, {group, config}, {group, examples}]},
{group, registry}, {group, config}, {group, examples}, {group, match_delete}]},
{install, [], [{mnesia_install_test, all}]},
{nice, [], [{mnesia_nice_coverage_test, all}]},
{evil, [], [{mnesia_evil_coverage_test, all}]},
Expand All @@ -79,6 +79,7 @@ groups() ->
{registry, [], [{mnesia_registry_test, all}]},
{config, [], [{mnesia_config_test, all}]},
{examples, [], [{mnesia_examples_test, all}]},
{match_delete, [], [{mnesia_match_delete_test, all}]},
%% The 'medium' test suite verfies the ACID (atomicity, consistency
%% isolation and durability) properties and various recovery scenarios
%% These tests may take quite while to run.
Expand Down
217 changes: 217 additions & 0 deletions lib/mnesia/test/mnesia_match_delete_test.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%

%%
-module(mnesia_match_delete_test).
-include("mnesia_test_lib.hrl").

-export([all/0, groups/0,
init_per_group/2, end_per_group/2,
init_per_testcase/2, end_per_testcase/2]).

-export([match_delete/1,
match_delete_checkpoint/1,
match_delete_subscribe/1,
match_delete_index/1,
match_delete_restart/1,
match_delete_dump_restart/1,
match_delete_frag/1]).

all() ->
[match_delete,
match_delete_checkpoint,
match_delete_subscribe,
match_delete_index,
match_delete_restart,
match_delete_dump_restart,
match_delete_frag].

groups() ->
[].

init_per_group(_GroupName, Config) ->
Config.

end_per_group(_GroupName, Config) ->
Config.

init_per_testcase(Func, Conf) ->
mnesia_test_lib:init_per_testcase(Func, Conf).

end_per_testcase(Func, Conf) ->
mnesia_test_lib:end_per_testcase(Func, Conf).

match_delete(suite) -> [];
match_delete(Config) when is_list(Config) ->
[Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
Tab = match_delete_tab,
Def = [{ram_copies, [Node1]}, {disc_copies, [Node2]}, {disc_only_copies, [Node3]}],
?match({atomic, ok}, mnesia:create_table(Tab, Def)),
?match({atomic, ok}, write(Tab)),
?match({atomic, ok}, mnesia:match_delete(Tab, {Tab, '_', bar})),
?match({atomic, [1,2,5]}, ?sort(mnesia:transaction(fun() -> mnesia:all_keys(Tab) end))),
?verify_mnesia(Nodes, []).

match_delete_checkpoint(suite) -> [];
match_delete_checkpoint(Config) when is_list(Config) ->
[Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
Tab = match_delete_retain_tab,
Def = [{disc_copies, [Node1, Node2]}, {disc_only_copies, [Node3]}],
Checkpoint = ?FUNCTION_NAME,
?match({atomic, ok}, mnesia:create_table(Tab, Def)),
?match({atomic, ok}, write(Tab)),

?match({ok, Checkpoint, _}, mnesia:activate_checkpoint([{name, Checkpoint}, {max, [Tab]}])),
?match({atomic, ok}, mnesia:match_delete(Tab, {Tab, '_', bar})),
?match({atomic, [1,2,5]}, ?sort(mnesia:transaction(fun() -> mnesia:all_keys(Tab) end))),

File = "match_delete_backup.BUP",
?match(ok, mnesia:backup_checkpoint(Checkpoint, File)),
?match(ok, mnesia:deactivate_checkpoint(?FUNCTION_NAME)),

?match({atomic, [Tab]}, mnesia:restore(File, [{default_op, clear_tables}])),
?match({atomic, [1,2,3,4,5]}, ?sort(mnesia:transaction(fun() -> mnesia:all_keys(Tab) end))),

?match(ok, file:delete(File)),
?verify_mnesia(Nodes, []).

match_delete_subscribe(suite) -> [];
match_delete_subscribe(Config) when is_list(Config) ->
Nodes = ?acquire_nodes(3, Config),
Tab = match_delete_sub_tab,
Def = [{ram_copies, Nodes}],
?match({atomic, ok}, mnesia:create_table(Tab, Def)),
?match({atomic, ok}, write(Tab)),
Pattern = {Tab, '_', bar},
?match({ok, _}, mnesia:subscribe({table, Tab})),
?match({atomic, ok}, mnesia:match_delete(Tab, Pattern)),
?match_receive({mnesia_table_event, {delete_object, Pattern, _}}),
?match({atomic, [1,2,5]}, ?sort(mnesia:transaction(fun() -> mnesia:all_keys(Tab) end))),
?verify_mnesia(Nodes, []).

match_delete_index(suite) -> [];
match_delete_index(Config) when is_list(Config) ->
Nodes = ?acquire_nodes(3, Config),
{atomic, ok} = mnesia:create_table(match_delete_index,
[{index, [ix]}, {attributes, [key, ix, val]},
{disc_copies, Nodes}]),
{atomic, ok} = mnesia:create_table(match_delete_index_ram,
[{index, [ix]}, {attributes, [key, ix, val]},
{ram_copies, Nodes}]),
{atomic, ok} = mnesia:create_table(match_delete_index_do,
[{index, [ix]}, {attributes, [key, ix, val]},
{disc_only_copies, Nodes}]),
Test = fun(Tab) ->
Rec = {Tab, 1, 4, data},
Rec2 = {Tab, 2, 5, data},
Rec3 = {Tab, 3, 5, data},
Rec4 = {Tab, 4, 6, data},
Pattern = {Tab, '_', 5, '_'},

{atomic, ok} = mnesia:transaction(fun() -> mnesia:write(Rec),
mnesia:write(Rec2),
mnesia:write(Rec3),
mnesia:write(Rec4)
end),

?match({atomic, ok}, mnesia:match_delete(Tab, Pattern)),

?match([Rec], mnesia:dirty_index_read(Tab, 4, ix)),
?match([Rec4], mnesia:dirty_index_read(Tab, 6, ix)),
?match({atomic, [Rec]}, mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ix) end)),
?match({atomic, [Rec4]}, mnesia:transaction(fun() -> mnesia:index_read(Tab, 6, ix) end)),

?match([], mnesia:dirty_index_match_object(Pattern, ix)),
?match({atomic, []}, mnesia:transaction(fun() -> mnesia:index_match_object(Pattern, ix) end)),

?match([Rec], mnesia:dirty_index_match_object({Tab, '_', 4, '_'}, ix)),
?match({atomic, [Rec4]},
mnesia:transaction(fun() -> mnesia:index_match_object({Tab, '_', 6, data}, ix) end))
end,
[Test(Tab) || Tab <- [match_delete_index, match_delete_index_ram, match_delete_index_do]],
?verify_mnesia(Nodes, []).

match_delete_restart(suite) -> [];
match_delete_restart(Config) when is_list(Config) ->
Nodes = ?acquire_nodes(1, Config),
Tab = match_delete_log_tab,
Def = [{disc_copies, Nodes}],
?match({atomic, ok}, mnesia:create_table(Tab, Def)),
?match({atomic, ok}, write(Tab)),
Pattern = {Tab, '_', bar},
?match({atomic, ok}, mnesia:match_delete(Tab, Pattern)),
%% Restart Mnesia right after calling match_delete/2 to verify that
%% the table is correctly loaded
?match([], mnesia_test_lib:stop_mnesia(Nodes)),
?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab])),
?match({atomic, [1,2,5]}, ?sort(mnesia:transaction(fun() -> mnesia:all_keys(Tab) end))),
?verify_mnesia(Nodes, []).

match_delete_dump_restart(suite) -> [];
match_delete_dump_restart(Config) when is_list(Config) ->
[Node1] = Nodes = ?acquire_nodes(1, Config),
Tab = match_delete_dump_tab,
Def = [{disc_copies, Nodes}],
?match({atomic, ok}, mnesia:create_table(Tab, Def)),
?match({atomic, ok}, write(Tab)),
Pattern = {Tab, '_', bar},
?match({atomic, ok}, mnesia:match_delete(Tab, Pattern)),
dumped = rpc:call(Node1, mnesia, dump_log, []),
?match({atomic, [1,2,5]}, ?sort(mnesia:transaction(fun() -> mnesia:all_keys(Tab) end))),
?match([], mnesia_test_lib:stop_mnesia(Nodes)),
?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab])),
?match({atomic, [1,2,5]}, ?sort(mnesia:transaction(fun() -> mnesia:all_keys(Tab) end))),
?verify_mnesia(Nodes, []).

match_delete_frag(suite) -> [];
match_delete_frag(Config) when is_list(Config) ->
Nodes = ?acquire_nodes(2, Config),
Tab = match_delete_frag_tab,
FragProps = [{n_fragments, 2}, {node_pool, Nodes}],
Def = [{frag_properties, FragProps}, {ram_copies, Nodes}],
?match({atomic, ok}, mnesia:create_table(Tab, Def)),
KVs = [{1, foo}, {2, foo},
{3, bar}, {4, bar},
{5, baz}, {6, baz},
{7, foo}, {8, foo}],
?match([ok, ok | _], frag_write(Tab, KVs)),
Pattern = {Tab, '_', bar},
%% match_delete/2 is a transaction itself
?match({atomic, ok},
mnesia:activity(
async_dirty, fun(P) -> mnesia:match_delete(Tab, P) end, [Pattern], mnesia_frag)
),
Keys = mnesia:activity(transaction, fun() -> mnesia:all_keys(Tab) end, [], mnesia_frag),
?match([1,2,5,6,7,8], ?sort(Keys)),
?verify_mnesia(Nodes, []).

frag_write(Tab, KVs) ->
Fun = fun(KVs1) -> [mnesia:write(Tab, {Tab, K, V}, write) || {K, V} <- KVs1] end,
mnesia:activity(transaction, Fun, [KVs], mnesia_frag).

write(Tab) ->
mnesia:transaction(
fun() ->
mnesia:write({Tab, 1, foo}),
mnesia:write({Tab, 2, foo}),
mnesia:write({Tab, 3, bar}),
mnesia:write({Tab, 4, bar}),
mnesia:write({Tab, 5, baz})
end).

0 comments on commit be6661c

Please sign in to comment.