From eac49cf0811273a8f155894dc12319fbe3d64096 Mon Sep 17 00:00:00 2001 From: ieQu1 <99872536+ieQu1@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:10:15 +0200 Subject: [PATCH 1/2] fix: Verify dirty writes to merge tables --- src/mria.erl | 6 ++++++ src/mria_upstream.erl | 30 ++++++++++++++++++++---------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/mria.erl b/src/mria.erl index 625c65a..d61650f 100644 --- a/src/mria.erl +++ b/src/mria.erl @@ -488,6 +488,8 @@ dirty_write(Record) -> -spec dirty_write(mria:table(), tuple()) -> ok. dirty_write(Tab, Record) -> + mria_upstream:verify_merge_table_record(Tab, Record) orelse + error({bad_record_node, Tab, Record}), call_backend_rw_dirty(dirty_write, Tab, [Record]). -spec dirty_write_sync(tuple()) -> ok. @@ -496,6 +498,8 @@ dirty_write_sync(Record) -> -spec dirty_write_sync(mria:table(), tuple()) -> ok. dirty_write_sync(Tab, Record) -> + mria_upstream:verify_merge_table_record(Tab, Record) orelse + error({bad_record_node, Tab, Record}), Shard = mria_config:shard_rlookup(Tab), call_backend_rw(Shard, mria_upstream, dirty_write_sync, [Tab, Record]). @@ -517,6 +521,8 @@ dirty_delete({Tab, Key}) -> -spec dirty_delete_object(mria:table(), tuple()) -> ok. dirty_delete_object(Tab, Record) -> + mria_upstream:verify_merge_table_record(Tab, Record) orelse + error({bad_record_node, Tab, Record}), call_backend_rw_dirty(dirty_delete_object, Tab, [Record]). -spec dirty_delete_object(tuple()) -> ok. diff --git a/src/mria_upstream.erl b/src/mria_upstream.erl index 888354f..7af16a8 100644 --- a/src/mria_upstream.erl +++ b/src/mria_upstream.erl @@ -29,6 +29,7 @@ , sync_dummy_wrapper/2 , dirty_wrapper/4 , dirty_write_sync/2 + , verify_merge_table_record/2 ]). -export_type([]). @@ -90,6 +91,20 @@ dirty_wrapper(Module, Function, Table, Args) -> {EC, Err} end. +-spec verify_merge_table_record(mria:table(), tuple()) -> boolean(). +verify_merge_table_record(Table, Record) -> + case mria_schema:get_merged_table_check_spec(Table) of + {ok, MatchSpec} -> + case ets:match_spec_run([Record], MatchSpec) of + [true] -> + true; + _ -> + false + end; + _ -> + true + end. + %%================================================================================ %% Internal functions %%================================================================================ @@ -118,16 +133,11 @@ ensure_no_ops_outside_node_for_merged_table(TxStore, Shard) -> verify_merge_table_update({{Table, _Key}, Record, Op} = Entry, Acc) when Op =:= write; Op =:= delete_object -> - case mria_schema:get_merged_table_check_spec(Table) of - {ok, MatchSpec} -> - case ets:match_spec_run([Record], MatchSpec) of - [true] -> - Acc; - _ -> - [Entry | Acc] - end; - _ -> - Acc + case verify_merge_table_record(Table, Record) of + true -> + Acc; + false -> + [Entry | Acc] end; verify_merge_table_update(_Op, Acc) -> %% TODO: deletions and other operations are more tricky. From 81971c4326d986c74d36bbdf4f49e0c45aca91ca Mon Sep 17 00:00:00 2001 From: ieQu1 <99872536+ieQu1@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:10:38 +0200 Subject: [PATCH 2/2] fix(merge_table): Do sanity check of node pattern --- src/mria_schema.erl | 29 +++++++++++++++++++++++++++++ test/mria_SUITE.erl | 13 +++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/mria_schema.erl b/src/mria_schema.erl index 96e0de0..e40e4fc 100644 --- a/src/mria_schema.erl +++ b/src/mria_schema.erl @@ -391,6 +391,16 @@ verify_merge_table(#?schema{mnesia_table = Table, shard = Shard, config = Conf, , merge_table => MergeTab , merge_shard => MergeShard }); + {true, {ok, Pattern}, true} -> + case check_node_pattern(Pattern) of + true -> + ok; + false -> + mnesia:abort(#{ reason => invalid_node_pattern + , table => Table + , node_pattern => Pattern + }) + end; _ -> ok end. @@ -569,3 +579,22 @@ entry_get_node_pattern(#?schema{config = Conf}) -> none -> undefined end. + +check_node_pattern(Patterns) -> + lists:all(fun is_node_pattern/1, Patterns). + +is_node_pattern('$1') -> + true; +is_node_pattern(T) when is_tuple(T) -> + is_node_pattern(tuple_to_list(T)); +is_node_pattern(L) when is_list(L) -> + case [1 || I <- L, is_node_pattern(I)] of + [_] -> + true; + _ -> + %% Having more than one '$1' in the match pattern in not + %% allowed. + false + end; +is_node_pattern(_) -> + false. diff --git a/test/mria_SUITE.erl b/test/mria_SUITE.erl index 774b584..a6407bd 100644 --- a/test/mria_SUITE.erl +++ b/test/mria_SUITE.erl @@ -1629,6 +1629,19 @@ t_merge_table_schema(_) -> , {node_pattern, {'_', '$1'}} ]))) || N <- Nodes], + %% Try to create a table with invalid node pattern: + [?assertMatch( + {aborted, #{reason := invalid_node_pattern}}, + ?ON(N, mria:create_table(merge_table6, + [ {merge_table, true} + , {node_pattern, Pattern} + , {rlog_shard, MergeShard} + ]))) + || N <- Nodes, + Pattern <- [ {'_', '_'} %% No node variable + , {'_', '$1', {'_', '$1'}} %% More than one node variable + , {'_', [['$1']], {{'_', {'$1'}, '_'}}} + ]], %% Verify schema cache in persistent term: mria_mnesia_test_util:wait_tables( [ normal_table1, normal_table2