diff options
Diffstat (limited to 'lib/mnesia/src')
35 files changed, 3166 insertions, 1384 deletions
diff --git a/lib/mnesia/src/Makefile b/lib/mnesia/src/Makefile index dae0df76f3..7d316df263 100644 --- a/lib/mnesia/src/Makefile +++ b/lib/mnesia/src/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 1996-2013. All Rights Reserved. +# Copyright Ericsson AB 1996-2017. 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. @@ -43,6 +43,8 @@ RELSYSDIR = $(RELEASE_PATH)/lib/mnesia-$(VSN) # ---------------------------------------------------- MODULES= \ mnesia \ + mnesia_app \ + mnesia_backend_type \ mnesia_backup \ mnesia_bup \ mnesia_checkpoint \ @@ -50,9 +52,9 @@ MODULES= \ mnesia_controller \ mnesia_dumper\ mnesia_event \ + mnesia_ext_sup \ mnesia_frag \ mnesia_frag_hash \ - mnesia_frag_old_hash \ mnesia_index \ mnesia_kernel_sup \ mnesia_late_loader \ @@ -65,7 +67,6 @@ MODULES= \ mnesia_registry \ mnesia_schema\ mnesia_snmp_hook \ - mnesia_snmp_sup \ mnesia_subscr \ mnesia_sup \ mnesia_sp \ diff --git a/lib/mnesia/src/mnesia.app.src b/lib/mnesia/src/mnesia.app.src index c78a7cba1e..c755b4d4b9 100644 --- a/lib/mnesia/src/mnesia.app.src +++ b/lib/mnesia/src/mnesia.app.src @@ -3,6 +3,8 @@ {vsn, "%VSN%"}, {modules, [ mnesia, + mnesia_app, + mnesia_backend_type, mnesia_backup, mnesia_bup, mnesia_checkpoint, @@ -10,9 +12,9 @@ mnesia_controller, mnesia_dumper, mnesia_event, + mnesia_ext_sup, mnesia_frag, mnesia_frag_hash, - mnesia_frag_old_hash, mnesia_index, mnesia_kernel_sup, mnesia_late_loader, @@ -25,7 +27,6 @@ mnesia_registry, mnesia_schema, mnesia_snmp_hook, - mnesia_snmp_sup, mnesia_subscr, mnesia_sup, mnesia_sp, @@ -47,7 +48,5 @@ mnesia_tm ]}, {applications, [kernel, stdlib]}, - {mod, {mnesia_sup, []}}, - {runtime_dependencies, ["stdlib-2.0","kernel-3.0","erts-7.0"]}]}. - - + {mod, {mnesia_app, []}}, + {runtime_dependencies, ["stdlib-3.4","kernel-5.3","erts-9.0"]}]}. diff --git a/lib/mnesia/src/mnesia.erl b/lib/mnesia/src/mnesia.erl index 2fdc7afacd..1842769778 100644 --- a/lib/mnesia/src/mnesia.erl +++ b/lib/mnesia/src/mnesia.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2014. All Rights Reserved. +%% Copyright Ericsson AB 1996-2017. 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. @@ -82,7 +82,8 @@ system_info/0, % Not for public use %% Database mgt - create_schema/1, delete_schema/1, + create_schema/1, create_schema/2, delete_schema/1, + add_backend_type/2, backup/1, backup/2, traverse_backup/4, traverse_backup/6, install_fallback/1, install_fallback/2, uninstall_fallback/0, uninstall_fallback/1, @@ -137,6 +138,37 @@ -include("mnesia.hrl"). -import(mnesia_lib, [verbose/2]). +-type create_option() :: + {'access_mode', 'read_write' | 'read_only'} | + {'attributes', [atom()]} | + {'disc_copies', [node()]} | + {'disc_only_copies', [node]} | + {'index', [index_attr()]} | + {'load_order', non_neg_integer()} | + {'majority', boolean()} | + {'ram_copies', [node()]} | + {'record_name', atom()} | + {'snmp', SnmpStruct::term()} | + {'storage_properties', [{Backend::module(), [BackendProp::_]}]} | + {'type', 'set' | 'ordered_set' | 'bag'} | + {'local_content', boolean()} | + {'user_properties', proplists:proplist()}. + +-type t_result(Res) :: {'atomic', Res} | {'aborted', Reason::term()}. +-type activity() :: 'ets' | 'async_dirty' | 'sync_dirty' | 'transaction' | 'sync_transaction' | + {'transaction', Retries::non_neg_integer()} | + {'sync_transaction', Retries::non_neg_integer()}. +-type table() :: atom(). +-type storage_type() :: 'ram_copies' | 'disc_copies' | 'disc_only_copies'. +-type index_attr() :: atom() | non_neg_integer(). +-type write_locks() :: 'write' | 'sticky_write'. +-type read_locks() :: 'read'. +-type lock_kind() :: write_locks() | read_locks(). +-type select_continuation() :: term(). +-type snmp_struct() :: [{atom(), snmp_type() | tuple_of(snmp_type())}]. +-type snmp_type() :: 'fix_string' | 'string' | 'integer'. +-type tuple_of(_T) :: tuple(). + -define(DEFAULT_ACCESS, ?MODULE). %% Select @@ -195,8 +227,11 @@ e_has_var(X, Pos) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Start and stop - +-spec start() -> 'ok' | {'error', term()}. start() -> + start([]). + +start_() -> {Time , Res} = timer:tc(application, start, [?APPLICATION, temporary]), Secs = Time div 1000000, @@ -212,6 +247,7 @@ start() -> {error, R} end. +-spec start([{Option::atom(), Value::_}]) -> 'ok' | {'error', term()}. start(ExtraEnv) when is_list(ExtraEnv) -> case mnesia_lib:ensure_loaded(?APPLICATION) of ok -> @@ -232,8 +268,9 @@ patched_start([{Env, Val} | Tail]) when is_atom(Env) -> patched_start([Head | _]) -> {error, {bad_type, Head}}; patched_start([]) -> - start(). + start_(). +-spec stop() -> 'stopped' | {'error', term()}. stop() -> case application:stop(?APPLICATION) of ok -> stopped; @@ -241,6 +278,7 @@ stop() -> Other -> Other end. +-spec change_config(Config::atom(), Value::_) -> ok | {error, term()}. change_config(extra_db_nodes, Ns) when is_list(Ns) -> mnesia_controller:connect_nodes(Ns); change_config(dc_dump_limit, N) when is_number(N), N > 0 -> @@ -269,6 +307,7 @@ kill() -> ms() -> [ mnesia, + mnesia_app, mnesia_backup, mnesia_bup, mnesia_checkpoint, @@ -278,7 +317,6 @@ ms() -> mnesia_loader, mnesia_frag, mnesia_frag_hash, - mnesia_frag_old_hash, mnesia_index, mnesia_kernel_sup, mnesia_late_loader, @@ -297,6 +335,7 @@ ms() -> %% Keep these last in the list, so %% mnesia_sup kills these last + mnesia_ext_sup, mnesia_monitor, mnesia_event ]. @@ -306,12 +345,12 @@ ms() -> %% Activity mgt -spec abort(_) -> no_return(). - abort(Reason = {aborted, _}) -> exit(Reason); abort(Reason) -> exit({aborted, Reason}). +-spec is_transaction() -> boolean(). is_transaction() -> case get(mnesia_activity_state) of {_, Tid, _Ts} when element(1,Tid) == tid -> @@ -320,29 +359,52 @@ is_transaction() -> false end. +-spec transaction(Fun) -> t_result(Res) when + Fun :: fun(() -> Res). transaction(Fun) -> transaction(get(mnesia_activity_state), Fun, [], infinity, ?DEFAULT_ACCESS, async). + +-spec transaction(Fun, Retries) -> t_result(Res) when + Fun :: fun(() -> Res), + Retries :: non_neg_integer() | 'infinity'; + (Fun, [Arg::_]) -> t_result(Res) when + Fun :: fun((...) -> Res). transaction(Fun, Retries) when is_integer(Retries), Retries >= 0 -> transaction(get(mnesia_activity_state), Fun, [], Retries, ?DEFAULT_ACCESS, async); transaction(Fun, Retries) when Retries == infinity -> transaction(get(mnesia_activity_state), Fun, [], Retries, ?DEFAULT_ACCESS, async); transaction(Fun, Args) -> transaction(get(mnesia_activity_state), Fun, Args, infinity, ?DEFAULT_ACCESS, async). + +-spec transaction(Fun, [Arg::_], Retries) -> t_result(Res) when + Fun :: fun((...) -> Res), + Retries :: non_neg_integer() | 'infinity'. transaction(Fun, Args, Retries) -> transaction(get(mnesia_activity_state), Fun, Args, Retries, ?DEFAULT_ACCESS, async). +-spec sync_transaction(Fun) -> t_result(Res) when + Fun :: fun(() -> Res). sync_transaction(Fun) -> transaction(get(mnesia_activity_state), Fun, [], infinity, ?DEFAULT_ACCESS, sync). + +-spec sync_transaction(Fun, Retries) -> t_result(Res) when + Fun :: fun(() -> Res), + Retries :: non_neg_integer() | 'infinity'; + (Fun, [Arg::_]) -> t_result(Res) when + Fun :: fun((...) -> Res). sync_transaction(Fun, Retries) when is_integer(Retries), Retries >= 0 -> transaction(get(mnesia_activity_state), Fun, [], Retries, ?DEFAULT_ACCESS, sync); sync_transaction(Fun, Retries) when Retries == infinity -> transaction(get(mnesia_activity_state), Fun, [], Retries, ?DEFAULT_ACCESS, sync); sync_transaction(Fun, Args) -> transaction(get(mnesia_activity_state), Fun, Args, infinity, ?DEFAULT_ACCESS, sync). + +-spec sync_transaction(Fun, [Arg::_], Retries) -> t_result(Res) when + Fun :: fun((...) -> Res), + Retries :: non_neg_integer() | 'infinity'. sync_transaction(Fun, Args, Retries) -> transaction(get(mnesia_activity_state), Fun, Args, Retries, ?DEFAULT_ACCESS, sync). - transaction(State, Fun, Args, Retries, Mod, Kind) when is_function(Fun), is_list(Args), Retries == infinity, is_atom(Mod) -> mnesia_tm:transaction(State, Fun, Args, Retries, Mod, Kind); @@ -358,28 +420,60 @@ non_transaction(State, Fun, Args, ActivityKind, Mod) non_transaction(_State, Fun, Args, _ActivityKind, _Mod) -> {aborted, {badarg, Fun, Args}}. +-spec async_dirty(Fun) -> Res | no_return() when + Fun :: fun(() -> Res). async_dirty(Fun) -> async_dirty(Fun, []). + +-spec async_dirty(Fun, [Arg::_]) -> Res | no_return() when + Fun :: fun((...) -> Res). async_dirty(Fun, Args) -> non_transaction(get(mnesia_activity_state), Fun, Args, async_dirty, ?DEFAULT_ACCESS). +-spec sync_dirty(Fun) -> Res | no_return() when + Fun :: fun(() -> Res). sync_dirty(Fun) -> sync_dirty(Fun, []). + +-spec sync_dirty(Fun, [Arg::_]) -> Res | no_return() when + Fun :: fun((...) -> Res). sync_dirty(Fun, Args) -> non_transaction(get(mnesia_activity_state), Fun, Args, sync_dirty, ?DEFAULT_ACCESS). +-spec ets(Fun) -> Res | no_return() when + Fun :: fun(() -> Res). ets(Fun) -> ets(Fun, []). + +-spec ets(Fun, [Arg::_]) -> Res | no_return() when + Fun :: fun((...) -> Res). ets(Fun, Args) -> non_transaction(get(mnesia_activity_state), Fun, Args, ets, ?DEFAULT_ACCESS). +-spec activity(Kind, Fun) -> t_result(Res) | Res when + Kind :: activity(), + Fun :: fun(() -> Res). activity(Kind, Fun) -> activity(Kind, Fun, []). + +-spec activity(Kind, Fun, [Arg::_]) -> t_result(Res) | Res when + Kind :: activity(), + Fun :: fun((...) -> Res); + (Kind, Fun, Mod) -> t_result(Res) | Res when + Kind :: activity(), + Fun :: fun(() -> Res), + Mod :: atom(). + activity(Kind, Fun, Args) when is_list(Args) -> activity(Kind, Fun, Args, mnesia_monitor:get_env(access_module)); activity(Kind, Fun, Mod) -> activity(Kind, Fun, [], Mod). +-spec activity(Kind, Fun, [Arg::_], Mod) -> t_result(Res) | Res when + Kind :: activity(), + Fun :: fun((...) -> Res), + Mod :: atom(). + activity(Kind, Fun, Args, Mod) -> State = get(mnesia_activity_state), case Kind of @@ -409,7 +503,11 @@ wrap_trans(State, Fun, Args, Retries, Mod, Kind) -> %% Nodes may either be a list of nodes or one node as an atom %% Mnesia on all Nodes must be connected to each other, but %% it is not neccessary that they are up and running. - +-spec lock(LockItem, LockKind) -> list() | tuple() | no_return() when + LockItem :: {'record', table(), Key::term()} | + {'table', table()} | + {'global', Key::term(), MnesiaNodes::[node()]}, + LockKind :: lock_kind() | 'load'. lock(LockItem, LockKind) -> case get(mnesia_activity_state) of {?DEFAULT_ACCESS, Tid, Ts} -> @@ -420,6 +518,9 @@ lock(LockItem, LockKind) -> abort(no_transaction) end. +-spec lock_table(Tab::table(), LockKind) -> [MnesiaNode] | no_return() when + MnesiaNode :: node(), + LockKind :: lock_kind() | load. lock_table(Tab, LockKind) -> lock({table, Tab}, LockKind). @@ -441,11 +542,13 @@ lock(Tid, Ts, LockItem, LockKind) -> end. %% Grab a read lock on a whole table +-spec read_lock_table(Tab::table()) -> ok. read_lock_table(Tab) -> lock({table, Tab}, read), ok. %% Grab a write lock on a whole table +-spec write_lock_table(Tab::table()) -> ok. write_lock_table(Tab) -> lock({table, Tab}, write), ok. @@ -510,17 +613,19 @@ good_global_nodes(Nodes) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Access within an activity - updates - +-spec write(Record::tuple()) -> 'ok'. write(Val) when is_tuple(Val), tuple_size(Val) > 2 -> Tab = element(1, Val), write(Tab, Val, write); write(Val) -> abort({bad_type, Val}). +-spec s_write(Record::tuple()) -> 'ok'. s_write(Val) when is_tuple(Val), tuple_size(Val) > 2 -> Tab = element(1, Val), write(Tab, Val, sticky_write). +-spec write(Tab::table(), Record::tuple(), LockKind::write_locks()) -> 'ok'. write(Tab, Val, LockKind) -> case get(mnesia_activity_state) of {?DEFAULT_ACCESS, Tid, Ts} -> @@ -556,33 +661,30 @@ write(_Tid, _Ts, Tab, Val, LockKind) -> abort({bad_type, Tab, Val, LockKind}). write_to_store(Tab, Store, Oid, Val) -> - case ?catch_val({Tab, record_validation}) of - {RecName, Arity, Type} - when tuple_size(Val) == Arity, RecName == element(1, Val) -> - case Type of - bag -> - ?ets_insert(Store, {Oid, Val, write}); - _ -> - ?ets_delete(Store, Oid), - ?ets_insert(Store, {Oid, Val, write}) - end, - ok; - {'EXIT', _} -> - abort({no_exists, Tab}); - _ -> - abort({bad_type, Val}) - end. + {_, _, Type} = mnesia_lib:validate_record(Tab, Val), + Oid = {Tab, element(2, Val)}, + case Type of + bag -> + ?ets_insert(Store, {Oid, Val, write}); + _ -> + ?ets_delete(Store, Oid), + ?ets_insert(Store, {Oid, Val, write}) + end, + ok. +-spec delete({Tab::table(), Key::_}) -> 'ok'. delete({Tab, Key}) -> delete(Tab, Key, write); delete(Oid) -> abort({bad_type, Oid}). +-spec s_delete({Tab::table(), Key::_}) -> 'ok'. s_delete({Tab, Key}) -> delete(Tab, Key, sticky_write); s_delete(Oid) -> abort({bad_type, Oid}). +-spec delete(Tab::table(), Key::_, LockKind::write_locks()) -> 'ok'. delete(Tab, Key, LockKind) -> case get(mnesia_activity_state) of {?DEFAULT_ACCESS, Tid, Ts} -> @@ -619,18 +721,21 @@ delete(Tid, Ts, Tab, Key, LockKind) delete(_Tid, _Ts, Tab, _Key, _LockKind) -> abort({bad_type, Tab}). +-spec delete_object(Rec::tuple()) -> 'ok'. delete_object(Val) when is_tuple(Val), tuple_size(Val) > 2 -> Tab = element(1, Val), delete_object(Tab, Val, write); delete_object(Val) -> abort({bad_type, Val}). +-spec s_delete_object(Rec::tuple()) -> 'ok'. s_delete_object(Val) when is_tuple(Val), tuple_size(Val) > 2 -> Tab = element(1, Val), delete_object(Tab, Val, sticky_write); s_delete_object(Val) -> abort({bad_type, Val}). +-spec delete_object(Tab::table(), Rec::tuple(), LockKind::write_locks()) -> 'ok'. delete_object(Tab, Val, LockKind) -> case get(mnesia_activity_state) of {?DEFAULT_ACCESS, Tid, Ts} -> @@ -690,19 +795,23 @@ do_delete_object(Tid, Ts, Tab, Val, LockKind) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Access within an activity - read +-spec read(Tab::table(), Key::_) -> [tuple()]. read(Tab, Key) -> read(Tab, Key, read). +-spec read({Tab::table(), Key::_}) -> [tuple()]. read({Tab, Key}) -> read(Tab, Key, read); read(Oid) -> abort({bad_type, Oid}). +-spec wread({Tab::table(), Key::_}) -> [tuple()]. wread({Tab, Key}) -> read(Tab, Key, write); wread(Oid) -> abort({bad_type, Oid}). +-spec read(Tab::table(), Key::_, LockKind::lock_kind()) -> [tuple()]. read(Tab, Key, LockKind) -> case get(mnesia_activity_state) of {?DEFAULT_ACCESS, Tid, Ts} -> @@ -739,6 +848,7 @@ read(Tid, Ts, Tab, Key, LockKind) read(_Tid, _Ts, Tab, _Key, _LockKind) -> abort({bad_type, Tab}). +-spec first(Tab::table()) -> Key::term(). first(Tab) -> case get(mnesia_activity_state) of {?DEFAULT_ACCESS, Tid, Ts} -> @@ -766,6 +876,7 @@ first(Tid, Ts, Tab) first(_Tid, _Ts,Tab) -> abort({bad_type, Tab}). +-spec last(Tab::table()) -> Key::term(). last(Tab) -> case get(mnesia_activity_state) of {?DEFAULT_ACCESS, Tid, Ts} -> @@ -793,6 +904,7 @@ last(Tid, Ts, Tab) last(_Tid, _Ts,Tab) -> abort({bad_type, Tab}). +-spec next(Tab::table(), Key::term()) -> NextKey::term(). next(Tab,Key) -> case get(mnesia_activity_state) of {?DEFAULT_ACCESS,Tid,Ts} -> @@ -819,6 +931,7 @@ next(Tid,Ts,Tab,Key) next(_Tid, _Ts,Tab,_) -> abort({bad_type, Tab}). +-spec prev(Tab::table(), Key::term()) -> PrevKey::term(). prev(Tab,Key) -> case get(mnesia_activity_state) of {?DEFAULT_ACCESS,Tid,Ts} -> @@ -953,6 +1066,8 @@ ts_keys_1([], Acc) -> %%%%%%%%%%%%%%%%%%%%% %% Iterators +-spec foldl(Fun, Acc0, Tab::table()) -> Acc when + Fun::fun((Record::tuple(), Acc0) -> Acc). foldl(Fun, Acc, Tab) -> foldl(Fun, Acc, Tab, read). @@ -993,6 +1108,8 @@ do_foldl(A, O, Tab, Key, Fun, Acc, Type, Stored) -> %% Type is set or bag {_, Tid, Ts} = get(mnesia_activity_state), do_foldl(Tid, Ts, Tab, dirty_next(Tab, Key), Fun, NewAcc, Type, NewStored). +-spec foldr(Fun, Acc0, Tab::table()) -> Acc when + Fun::fun((Record::tuple(), Acc0) -> Acc). foldr(Fun, Acc, Tab) -> foldr(Fun, Acc, Tab, read). foldr(Fun, Acc, Tab, LockKind) when is_function(Fun) -> @@ -1106,12 +1223,15 @@ add_written_to_bag([{_, _ , delete} | Tail], _Objs, _Ack) -> add_written_to_bag([{_, Val, delete_object} | Tail], Objs, Ack) -> add_written_to_bag(Tail, lists:delete(Val, Objs), lists:delete(Val, Ack)). +-spec match_object(Pattern::tuple()) -> [Record::tuple()]. match_object(Pat) when is_tuple(Pat), tuple_size(Pat) > 2 -> Tab = element(1, Pat), match_object(Tab, Pat, read); match_object(Pat) -> abort({bad_type, Pat}). +-spec match_object(Tab,Pattern,LockKind) -> [Record] when + Tab::table(),Pattern::tuple(),LockKind::lock_kind(),Record::tuple(). match_object(Tab, Pat, LockKind) -> case get(mnesia_activity_state) of {?DEFAULT_ACCESS, Tid, Ts} -> @@ -1271,9 +1391,13 @@ deloid(Oid, [H | T]) -> %%%%%%%%%%%%%%%%%% % select - +-spec select(Tab, Spec) -> [Match] when + Tab::table(), Spec::ets:match_spec(), Match::term(). select(Tab, Pat) -> select(Tab, Pat, read). +-spec select(Tab, Spec, LockKind) -> [Match] when + Tab::table(), Spec::ets:match_spec(), + Match::term(),LockKind::lock_kind(). select(Tab, Pat, LockKind) when is_atom(Tab), Tab /= schema, is_list(Pat) -> case get(mnesia_activity_state) of @@ -1332,6 +1456,11 @@ select_lock(Tid,Ts,LockKind,Spec,Tab) -> end. %% Breakable Select +-spec select(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(Tab, Pat, NObjects, LockKind) when is_atom(Tab), Tab /= schema, is_list(Pat), is_integer(NObjects) -> case get(mnesia_activity_state) of @@ -1388,6 +1517,9 @@ fun_select(Tid, Ts, Tab, Spec, LockKind, TabPat, Init, NObjects, Node, Storage) select_state(Init(Spec),Def) end. +-spec select(Cont) -> {[Match], Cont} | '$end_of_table' when + Match::term(), + Cont::select_continuation(). select(Cont) -> case get(mnesia_activity_state) of {?DEFAULT_ACCESS, Tid, Ts} -> @@ -1410,8 +1542,14 @@ 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}) -> % Missmatching tids +select_cont(Tid2,_,#mnesia_select{tid=_Tid1}) + when element(1,Tid2) == tid -> % Mismatching tids abort(wrong_transaction); +select_cont(Tid,Ts,State=#mnesia_select{}) -> + % 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) -> abort({badarg, Cont}). @@ -1431,6 +1569,7 @@ get_record_pattern([]) -> []; get_record_pattern([{M,C,_B}|R]) -> [{M,C,['$_']} | get_record_pattern(R)]. +-spec all_keys(Tab::table()) -> [Key::term()]. all_keys(Tab) -> case get(mnesia_activity_state) of {?DEFAULT_ACCESS, Tid, Ts} -> @@ -1455,12 +1594,20 @@ all_keys(Tid, Ts, Tab, LockKind) all_keys(_Tid, _Ts, Tab, _LockKind) -> abort({bad_type, Tab}). +-spec index_match_object(Pattern, Attr) -> [Record] when + Pattern::tuple(), Attr::index_attr(), Record::tuple(). index_match_object(Pat, Attr) when is_tuple(Pat), tuple_size(Pat) > 2 -> Tab = element(1, Pat), index_match_object(Tab, Pat, Attr, read); index_match_object(Pat, _Attr) -> abort({bad_type, Pat}). +-spec index_match_object(Tab, Pattern, Attr, LockKind) -> [Record] when + Tab::table(), + Pattern::tuple(), + Attr::index_attr(), + LockKind::lock_kind(), + Record::tuple(). index_match_object(Tab, Pat, Attr, LockKind) -> case get(mnesia_activity_state) of {?DEFAULT_ACCESS, Tid, Ts} -> @@ -1497,6 +1644,11 @@ index_match_object(Tid, Ts, Tab, Pat, Attr, LockKind) index_match_object(_Tid, _Ts, Tab, Pat, _Attr, _LockKind) -> abort({bad_type, Tab, Pat}). +-spec index_read(Tab, Key, Attr) -> [Record] when + Tab::table(), + Key::term(), + Attr::index_attr(), + Record::tuple(). index_read(Tab, Key, Attr) -> case get(mnesia_activity_state) of {?DEFAULT_ACCESS, Tid, Ts} -> @@ -1536,36 +1688,32 @@ index_read(_Tid, _Ts, Tab, _Key, _Attr, _LockKind) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Dirty access regardless of activities - updates - +-spec dirty_write(Record::tuple()) -> 'ok'. dirty_write(Val) when is_tuple(Val), tuple_size(Val) > 2 -> Tab = element(1, Val), dirty_write(Tab, Val); dirty_write(Val) -> abort({bad_type, Val}). +-spec dirty_write(Tab::table(), Record::tuple()) -> 'ok'. dirty_write(Tab, Val) -> do_dirty_write(async_dirty, Tab, Val). do_dirty_write(SyncMode, Tab, Val) when is_atom(Tab), Tab /= schema, is_tuple(Val), tuple_size(Val) > 2 -> - case ?catch_val({Tab, record_validation}) of - {RecName, Arity, _Type} - when tuple_size(Val) == Arity, RecName == element(1, Val) -> - Oid = {Tab, element(2, Val)}, - mnesia_tm:dirty(SyncMode, {Oid, Val, write}); - {'EXIT', _} -> - abort({no_exists, Tab}); - _ -> - abort({bad_type, Val}) - end; + {_, _, _} = mnesia_lib:validate_record(Tab, Val), + Oid = {Tab, element(2, Val)}, + mnesia_tm:dirty(SyncMode, {Oid, Val, write}); do_dirty_write(_SyncMode, Tab, Val) -> abort({bad_type, Tab, Val}). +-spec dirty_delete({Tab::table(), Key::_}) -> 'ok'. dirty_delete({Tab, Key}) -> dirty_delete(Tab, Key); dirty_delete(Oid) -> abort({bad_type, Oid}). +-spec dirty_delete(Tab::table(), Key::_) -> 'ok'. dirty_delete(Tab, Key) -> do_dirty_delete(async_dirty, Tab, Key). @@ -1575,12 +1723,14 @@ do_dirty_delete(SyncMode, Tab, Key) when is_atom(Tab), Tab /= schema -> do_dirty_delete(_SyncMode, Tab, _Key) -> abort({bad_type, Tab}). +-spec dirty_delete_object(Record::tuple()) -> 'ok'. dirty_delete_object(Val) when is_tuple(Val), tuple_size(Val) > 2 -> Tab = element(1, Val), dirty_delete_object(Tab, Val); dirty_delete_object(Val) -> abort({bad_type, Val}). +-spec dirty_delete_object(Tab::table(), Record::tuple()) -> 'ok'. dirty_delete_object(Tab, Val) -> do_dirty_delete_object(async_dirty, Tab, Val). @@ -1598,19 +1748,22 @@ do_dirty_delete_object(_SyncMode, Tab, Val) -> abort({bad_type, Tab, Val}). %% A Counter is an Oid being {CounterTab, CounterName} - +-spec dirty_update_counter({Tab::table(), Key::_}, Incr::integer()) -> + NewVal::integer(). dirty_update_counter({Tab, Key}, Incr) -> dirty_update_counter(Tab, Key, Incr); dirty_update_counter(Counter, _Incr) -> abort({bad_type, Counter}). +-spec dirty_update_counter(Tab::table(), Key::_, Incr::integer()) -> + NewVal::integer(). dirty_update_counter(Tab, Key, Incr) -> do_dirty_update_counter(async_dirty, Tab, Key, Incr). do_dirty_update_counter(SyncMode, Tab, Key, Incr) when is_atom(Tab), Tab /= schema, is_integer(Incr) -> - case ?catch_val({Tab, record_validation}) of - {RecName, 3, set} -> + case mnesia_lib:validate_key(Tab, Key) of + {RecName, 3, Type} when Type == set; Type == ordered_set -> Oid = {Tab, Key}, mnesia_tm:dirty(SyncMode, {Oid, {RecName, Incr}, update_counter}); _ -> @@ -1622,23 +1775,28 @@ do_dirty_update_counter(_SyncMode, Tab, _Key, Incr) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Dirty access regardless of activities - read +-spec dirty_read({Tab::table(), Key::_}) -> [tuple()]. dirty_read({Tab, Key}) -> dirty_read(Tab, Key); dirty_read(Oid) -> abort({bad_type, Oid}). +-spec dirty_read(Tab::table(), Key::_) -> [tuple()]. dirty_read(Tab, Key) when is_atom(Tab), Tab /= schema -> dirty_rpc(Tab, mnesia_lib, db_get, [Tab, Key]); dirty_read(Tab, _Key) -> abort({bad_type, Tab}). +-spec dirty_match_object(Pattern::tuple()) -> [Record::tuple()]. dirty_match_object(Pat) when is_tuple(Pat), tuple_size(Pat) > 2 -> Tab = element(1, Pat), dirty_match_object(Tab, Pat); dirty_match_object(Pat) -> abort({bad_type, Pat}). +-spec dirty_match_object(Tab,Pattern) -> [Record] when + Tab::table(), Pattern::tuple(), Record::tuple(). dirty_match_object(Tab, Pat) when is_atom(Tab), Tab /= schema, is_tuple(Pat), tuple_size(Pat) > 2 -> dirty_rpc(Tab, ?MODULE, remote_dirty_match_object, [Tab, Pat]); @@ -1668,6 +1826,8 @@ remote_dirty_match_object(Tab, Pat, []) -> remote_dirty_match_object(Tab, Pat, _PosList) -> abort({bad_type, Tab, Pat}). +-spec dirty_select(Tab, Spec) -> [Match] when + Tab::table(), Spec::ets:match_spec(), Match::term(). dirty_select(Tab, Spec) when is_atom(Tab), Tab /= schema, is_list(Spec) -> dirty_rpc(Tab, ?MODULE, remote_dirty_select, [Tab, Spec]); dirty_select(Tab, Spec) -> @@ -1716,6 +1876,7 @@ 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]). +-spec dirty_all_keys(Tab::table()) -> [Key::term()]. dirty_all_keys(Tab) when is_atom(Tab), Tab /= schema -> case ?catch_val({Tab, wild_pattern}) of {'EXIT', _} -> @@ -1731,12 +1892,19 @@ dirty_all_keys(Tab) when is_atom(Tab), Tab /= schema -> dirty_all_keys(Tab) -> abort({bad_type, Tab}). +-spec dirty_index_match_object(Pattern, Attr) -> [Record] when + Pattern::tuple(), Attr::index_attr(), Record::tuple(). dirty_index_match_object(Pat, Attr) when is_tuple(Pat), tuple_size(Pat) > 2 -> Tab = element(1, Pat), dirty_index_match_object(Tab, Pat, Attr); dirty_index_match_object(Pat, _Attr) -> abort({bad_type, Pat}). +-spec dirty_index_match_object(Tab, Pattern, Attr) -> [Record] when + Tab::table(), + Pattern::tuple(), + Attr::index_attr(), + Record::tuple(). dirty_index_match_object(Tab, Pat, Attr) when is_atom(Tab), Tab /= schema, is_tuple(Pat), tuple_size(Pat) > 2 -> case mnesia_schema:attr_tab_to_pos(Tab, Attr) of @@ -1760,6 +1928,11 @@ dirty_index_match_object(Tab, Pat, Attr) dirty_index_match_object(Tab, Pat, _Attr) -> abort({bad_type, Tab, Pat}). +-spec dirty_index_read(Tab, Key, Attr) -> [Record] when + Tab::table(), + Key::term(), + Attr::index_attr(), + Record::tuple(). dirty_index_read(Tab, Key, Attr) when is_atom(Tab), Tab /= schema -> Pos = mnesia_schema:attr_tab_to_pos(Tab, Attr), case has_var(Key) of @@ -1771,26 +1944,31 @@ dirty_index_read(Tab, Key, Attr) when is_atom(Tab), Tab /= schema -> dirty_index_read(Tab, _Key, _Attr) -> abort({bad_type, Tab}). +%% do not use only for backwards compatibility dirty_slot(Tab, Slot) when is_atom(Tab), Tab /= schema, is_integer(Slot) -> dirty_rpc(Tab, mnesia_lib, db_slot, [Tab, Slot]); dirty_slot(Tab, Slot) -> abort({bad_type, Tab, Slot}). +-spec dirty_first(Tab::table()) -> Key::term(). dirty_first(Tab) when is_atom(Tab), Tab /= schema -> dirty_rpc(Tab, mnesia_lib, db_first, [Tab]); dirty_first(Tab) -> abort({bad_type, Tab}). +-spec dirty_last(Tab::table()) -> Key::term(). dirty_last(Tab) when is_atom(Tab), Tab /= schema -> dirty_rpc(Tab, mnesia_lib, db_last, [Tab]); dirty_last(Tab) -> abort({bad_type, Tab}). +-spec dirty_next(Tab::table(), Key::_) -> NextKey::term(). dirty_next(Tab, Key) when is_atom(Tab), Tab /= schema -> dirty_rpc(Tab, mnesia_lib, db_next_key, [Tab, Key]); dirty_next(Tab, _Key) -> abort({bad_type, Tab}). +-spec dirty_prev(Tab::table(), Key::_) -> PrevKey::term(). dirty_prev(Tab, Key) when is_atom(Tab), Tab /= schema -> dirty_rpc(Tab, mnesia_lib, db_prev_key, [Tab, Key]); dirty_prev(Tab, _Key) -> @@ -1841,7 +2019,7 @@ do_dirty_rpc(Tab, Node, M, F, Args) -> %% Info %% Info about one table --spec table_info(atom(), any()) -> any(). +-spec table_info(Tab::table(), Item::term()) -> Info::term(). table_info(Tab, Item) -> case get(mnesia_activity_state) of undefined -> @@ -1884,9 +2062,10 @@ any_table_info(Tab, Item) when is_atom(Tab) -> [] -> abort({no_exists, Tab, Item}); Props -> - lists:map(fun({setorbag, Type}) -> {type, Type}; - (Prop) -> Prop end, - Props) + Rename = fun ({setorbag, Type}) -> {type, Type}; + (Prop) -> Prop + end, + lists:sort(lists:map(Rename, Props)) end; name -> Tab; @@ -1910,6 +2089,8 @@ raw_table_info(Tab, Item) -> info_reply(?ets_info(Tab, Item), Tab, Item); disc_only_copies -> info_reply(dets:info(Tab, Item), Tab, Item); + {ext, Alias, Mod} -> + info_reply(catch Mod:info(Alias, Tab, Item), Tab, Item); unknown -> bad_info_reply(Tab, Item) end @@ -1927,16 +2108,20 @@ bad_info_reply(_Tab, memory) -> 0; bad_info_reply(Tab, Item) -> abort({no_exists, Tab, Item}). %% Raw info about all tables +-spec schema() -> ok. schema() -> mnesia_schema:info(). %% Raw info about one tables +-spec schema(Tab::table()) -> ok. schema(Tab) -> mnesia_schema:info(Tab). +-spec error_description(Error::term()) -> string(). error_description(Err) -> mnesia_lib:error_desc(Err). +-spec info() -> ok. info() -> case mnesia_lib:is_running() of yes -> @@ -2022,15 +2207,26 @@ display_tab_info() -> MasterTabs = mnesia_recover:get_master_node_tables(), io:format("master node tables = ~p~n", [lists:sort(MasterTabs)]), + case get_backend_types() of + [] -> ok; + Ts -> list_backend_types(Ts, "backend types = ") + end, + + case get_index_plugins() of + [] -> ok; + Ps -> list_index_plugins(Ps, "index plugins = ") + end, + Tabs = system_info(tables), - {Unknown, Ram, Disc, DiscOnly} = - lists:foldl(fun storage_count/2, {[], [], [], []}, Tabs), + {Unknown, Ram, Disc, DiscOnly, Ext} = + lists:foldl(fun storage_count/2, {[], [], [], [], []}, Tabs), io:format("remote = ~p~n", [lists:sort(Unknown)]), io:format("ram_copies = ~p~n", [lists:sort(Ram)]), io:format("disc_copies = ~p~n", [lists:sort(Disc)]), io:format("disc_only_copies = ~p~n", [lists:sort(DiscOnly)]), + [io:format("~-19s= ~p~n", [atom_to_list(A), Ts]) || {A,Ts} <- Ext], Rfoldl = fun(T, Acc) -> Rpat = @@ -2038,7 +2234,7 @@ display_tab_info() -> read_only -> lists:sort([{A, read_only} || A <- val({T, active_replicas})]); read_write -> - table_info(T, where_to_commit) + [fix_wtc(W) || W <- table_info(T, where_to_commit)] end, case lists:keysearch(Rpat, 1, Acc) of {value, {_Rpat, Rtabs}} -> @@ -2051,14 +2247,65 @@ display_tab_info() -> Rdisp = fun({Rpat, Rtabs}) -> io:format("~p = ~p~n", [Rpat, Rtabs]) end, lists:foreach(Rdisp, lists:sort(Repl)). -storage_count(T, {U, R, D, DO}) -> +-spec get_backend_types() -> [BackendType::term()]. +get_backend_types() -> + case ?catch_val({schema, user_property, mnesia_backend_types}) of + {'EXIT', _} -> + []; + {mnesia_backend_types, Ts} -> + lists:sort(Ts) + end. + +-spec get_index_plugins() -> [IndexPlugins::term()]. +get_index_plugins() -> + case ?catch_val({schema, user_property, mnesia_index_plugins}) of + {'EXIT', _} -> + []; + {mnesia_index_plugins, Ps} -> + lists:sort(Ps) + end. + + +list_backend_types([{A,M} | T] = Ts, Legend) -> + Indent = [$\s || _ <- Legend], + W = integer_to_list( + lists:foldl(fun({Alias,_}, Wa) -> + erlang:max(Wa, length(atom_to_list(Alias))) + end, 0, Ts)), + io:fwrite(Legend ++ "~-" ++ W ++ "s - ~s~n", + [atom_to_list(A), atom_to_list(M)]), + [io:fwrite(Indent ++ "~-" ++ W ++ "s - ~s~n", + [atom_to_list(A1), atom_to_list(M1)]) + || {A1,M1} <- T]. + +list_index_plugins([{N,M,F} | T] = Ps, Legend) -> + Indent = [$\s || _ <- Legend], + W = integer_to_list( + lists:foldl(fun({N1,_,_}, Wa) -> + erlang:max(Wa, length(pp_ix_name(N1))) + end, 0, Ps)), + io:fwrite(Legend ++ "~-" ++ W ++ "s - ~s:~ts~n", + [pp_ix_name(N), atom_to_list(M), atom_to_list(F)]), + [io:fwrite(Indent ++ "~-" ++ W ++ "s - ~s:~ts~n", + [pp_ix_name(N1), atom_to_list(M1), atom_to_list(F1)]) + || {N1,M1,F1} <- T]. + +pp_ix_name(N) -> + lists:flatten(io_lib:fwrite("~w", [N])). + +fix_wtc({N, {ext,A,_}}) -> {N, A}; +fix_wtc({N,A}) when is_atom(A) -> {N, A}. + +storage_count(T, {U, R, D, DO, Ext}) -> case table_info(T, storage_type) of - unknown -> {[T | U], R, D, DO}; - ram_copies -> {U, [T | R], D, DO}; - disc_copies -> {U, R, [T | D], DO}; - disc_only_copies -> {U, R, D, [T | DO]} + unknown -> {[T | U], R, D, DO, Ext}; + ram_copies -> {U, [T | R], D, DO, Ext}; + disc_copies -> {U, R, [T | D], DO, Ext}; + disc_only_copies -> {U, R, D, [T | DO], Ext}; + {ext, A, _} -> {U, R, D, DO, orddict:append(A, T, Ext)} end. +-spec system_info(Iterm::term()) -> Info::term(). system_info(Item) -> try system_info2(Item) catch _:Error -> abort(Error) @@ -2071,9 +2318,10 @@ system_info2(all) -> system_info2(db_nodes) -> DiscNs = ?catch_val({schema, disc_copies}), RamNs = ?catch_val({schema, ram_copies}), + ExtNs = ?catch_val({schema, external_copies}), if - is_list(DiscNs), is_list(RamNs) -> - DiscNs ++ RamNs; + is_list(DiscNs), is_list(RamNs), is_list(ExtNs) -> + DiscNs ++ RamNs ++ ExtNs; true -> case mnesia_schema:read_nodes() of {ok, Nodes} -> Nodes; @@ -2177,6 +2425,7 @@ system_info2(access_module) -> mnesia_monitor:get_env(access_module); system_info2(auto_repair) -> mnesia_monitor:get_env(auto_repair); system_info2(is_running) -> mnesia_lib:is_running(); system_info2(backup_module) -> mnesia_monitor:get_env(backup_module); +system_info2(backend_types) -> mnesia_schema:backend_types(); system_info2(event_module) -> mnesia_monitor:get_env(event_module); system_info2(debug) -> mnesia_monitor:get_env(debug); system_info2(dump_log_load_regulation) -> mnesia_monitor:get_env(dump_log_load_regulation); @@ -2213,6 +2462,7 @@ system_info_items(yes) -> [ access_module, auto_repair, + backend_types, backup_module, checkpoints, db_nodes, @@ -2305,77 +2555,134 @@ load_mnesia_or_abort() -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Database mgt +-spec create_schema(Ns::[node()]) -> 'ok' | {'error', Reason::term()}. create_schema(Ns) -> - mnesia_bup:create_schema(Ns). + create_schema(Ns, []). +-spec create_schema(Ns::[node()], [Prop]) -> 'ok' | {'error', Reason::term()} when + Prop :: BackendType | IndexPlugin, + BackendType :: {backend_types, [{Name::atom(), Module::module()}]}, + IndexPlugin :: {index_plugins, [{{Name::atom()}, Module::module(), Function::atom()}]}. +create_schema(Ns, Properties) -> + mnesia_bup:create_schema(Ns, Properties). + +-spec delete_schema(Ns::[node()]) -> 'ok' | {'error', Reason::term()}. delete_schema(Ns) -> mnesia_schema:delete_schema(Ns). +-spec add_backend_type(Name::atom(), Module::module()) -> t_result('ok'). +add_backend_type(Alias, Module) -> + mnesia_schema:add_backend_type(Alias, Module). + +-spec backup(Dest::term()) -> 'ok' | {'error', Reason::term()}. backup(Opaque) -> mnesia_log:backup(Opaque). +-spec backup(Dest::term(), Mod::module()) -> + 'ok' | {'error', Reason::term()}. backup(Opaque, Mod) -> mnesia_log:backup(Opaque, Mod). +-spec traverse_backup(Src::term(), Dest::term(), Fun, Acc) -> + {'ok', Acc} | {'error', Reason::term()} when + Fun :: fun((Items, Acc) -> {Items,Acc}). traverse_backup(S, T, Fun, Acc) -> mnesia_bup:traverse_backup(S, T, Fun, Acc). +-spec traverse_backup(Src::term(), SrcMod::module(), + Dest::term(), DestMod::module(), + Fun, Acc) -> + {'ok', Acc} | {'error', Reason::term()} when + Fun :: fun((Items, Acc) -> {Items,Acc}). traverse_backup(S, SM, T, TM, F, A) -> mnesia_bup:traverse_backup(S, SM, T, TM, F, A). +-spec install_fallback(Src::term()) -> 'ok' | {'error', Reason::term()}. install_fallback(Opaque) -> mnesia_bup:install_fallback(Opaque). +-spec install_fallback(Src::term(), Mod::module()|[Opt]) -> + 'ok' | {'error', Reason::term()} when + Opt :: Module | Scope | Dir, + Module :: {'module', Mod::module()}, + Scope :: {'scope', 'global' | 'local'}, + Dir :: {'mnesia_dir', Dir::string()}. install_fallback(Opaque, Mod) -> mnesia_bup:install_fallback(Opaque, Mod). +-spec uninstall_fallback() -> 'ok' | {'error', Reason::term()}. uninstall_fallback() -> mnesia_bup:uninstall_fallback(). +-spec uninstall_fallback(Args) -> 'ok' | {'error', Reason::term()} when + Args :: [{'mnesia_dir', Dir::string()}]. uninstall_fallback(Args) -> mnesia_bup:uninstall_fallback(Args). +-spec activate_checkpoint([Arg]) -> {'ok', Name, [node()]} | {'error', Reason::term()} when + Arg :: {'name', Name} | {'max', [table()]} | {'min', [table()]} | + {'allow_remote', boolean()} | {'ram_overrides_dump', boolean()}. activate_checkpoint(Args) -> mnesia_checkpoint:activate(Args). +-spec deactivate_checkpoint(Name::_) -> 'ok' | {'error', Reason::term()}. deactivate_checkpoint(Name) -> mnesia_checkpoint:deactivate(Name). +-spec backup_checkpoint(Name::_, Dest::_) -> 'ok' | {'error', Reason::term()}. backup_checkpoint(Name, Opaque) -> mnesia_log:backup_checkpoint(Name, Opaque). +-spec backup_checkpoint(Name::_, Dest::_, Mod::module()) -> + 'ok' | {'error', Reason::term()}. backup_checkpoint(Name, Opaque, Mod) -> mnesia_log:backup_checkpoint(Name, Opaque, Mod). +-spec restore(Src::_, [Arg]) -> t_result([table()]) when + Op :: 'skip_tables' | 'clear_tables' | 'keep_tables' | 'restore_tables', + Arg :: {'module', module()} | {Op, [table()]} | {'default_op', Op}. restore(Opaque, Args) -> mnesia_schema:restore(Opaque, Args). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Table mgt - +-spec create_table([Arg]) -> t_result('ok') when + Arg :: {'name', table()} | create_option(). create_table(Arg) -> mnesia_schema:create_table(Arg). + +-spec create_table(Name::table(), [create_option()]) -> t_result('ok'). create_table(Name, Arg) when is_list(Arg) -> mnesia_schema:create_table([{name, Name}| Arg]); create_table(Name, Arg) -> {aborted, badarg, Name, Arg}. +-spec delete_table(Tab::table()) -> t_result('ok'). delete_table(Tab) -> mnesia_schema:delete_table(Tab). +-spec add_table_copy(Tab::table(), N::node(), ST::storage_type()) -> t_result(ok). add_table_copy(Tab, N, S) -> mnesia_schema:add_table_copy(Tab, N, S). + +-spec del_table_copy(Tab::table(), N::node()) -> t_result(ok). del_table_copy(Tab, N) -> mnesia_schema:del_table_copy(Tab, N). +-spec move_table_copy(Tab::table(), From::node(), To::node()) -> t_result(ok). move_table_copy(Tab, From, To) -> mnesia_schema:move_table(Tab, From, To). +-spec add_table_index(Tab::table(), I::index_attr()) -> t_result(ok). add_table_index(Tab, Ix) -> mnesia_schema:add_table_index(Tab, Ix). +-spec del_table_index(Tab::table(), I::index_attr()) -> t_result(ok). del_table_index(Tab, Ix) -> mnesia_schema:del_table_index(Tab, Ix). +-spec transform_table(Tab::table(), Fun, [Attr]) -> t_result(ok) when + Attr :: atom(), + Fun:: fun((Record::tuple()) -> Transformed::tuple()). transform_table(Tab, Fun, NewA) -> try val({Tab, record_name}) of OldRN -> mnesia_schema:transform_table(Tab, Fun, NewA, OldRN) @@ -2383,12 +2690,18 @@ transform_table(Tab, Fun, NewA) -> mnesia:abort(Reason) end. +-spec transform_table(Tab::table(), Fun, [Attr], RecName) -> t_result(ok) when + RecName :: atom(), + Attr :: atom(), + Fun:: fun((Record::tuple()) -> Transformed::tuple()). transform_table(Tab, Fun, NewA, NewRN) -> mnesia_schema:transform_table(Tab, Fun, NewA, NewRN). +-spec change_table_copy_type(Tab::table(), Node::node(), To::storage_type()) -> t_result(ok). change_table_copy_type(T, N, S) -> mnesia_schema:change_table_copy_type(T, N, S). +-spec clear_table(Tab::table()) -> t_result(ok). clear_table(Tab) -> case get(mnesia_activity_state) of State = {Mod, Tid, _Ts} when element(1, Tid) =/= tid -> @@ -2418,19 +2731,22 @@ clear_table(Tid, Ts, Tab, Obj) when element(1, Tid) =:= tid -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Table mgt - user properties - +-spec read_table_property(Tab::table(), PropKey::term()) -> Res::tuple(). read_table_property(Tab, PropKey) -> val({Tab, user_property, PropKey}). +-spec write_table_property(Tab::table(), Prop::tuple()) -> t_result(ok). write_table_property(Tab, Prop) -> mnesia_schema:write_table_property(Tab, Prop). +-spec delete_table_property(Tab::table(), PropKey::term()) -> t_result(ok). delete_table_property(Tab, PropKey) -> mnesia_schema:delete_table_property(Tab, PropKey). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Table mgt - user properties +-spec change_table_frag(Tab::table(), FP::term()) -> t_result(ok). change_table_frag(Tab, FragProp) -> mnesia_schema:change_table_frag(Tab, FragProp). @@ -2438,28 +2754,38 @@ change_table_frag(Tab, FragProp) -> %% Table mgt - table load %% Dump a ram table to disc +-spec dump_tables([Tab::table()]) -> t_result(ok). dump_tables(Tabs) -> mnesia_schema:dump_tables(Tabs). %% allow the user to wait for some tables to be loaded +-spec wait_for_tables([Tab::table()], TMO::timeout()) -> + 'ok' | {'timeout', [table()]} | {'error', Reason::term()}. wait_for_tables(Tabs, Timeout) -> mnesia_controller:wait_for_tables(Tabs, Timeout). +-spec force_load_table(Tab::table()) -> 'yes' | {'error', Reason::term()}. force_load_table(Tab) -> case mnesia_controller:force_load_table(Tab) of ok -> yes; % Backwards compatibility Other -> Other end. +-spec change_table_access_mode(Tab::table(), Mode) -> t_result(ok) when + Mode :: 'read_only'|'read_write'. change_table_access_mode(T, Access) -> mnesia_schema:change_table_access_mode(T, Access). +-spec change_table_load_order(Tab::table(), Order) -> t_result(ok) when + Order :: non_neg_integer(). change_table_load_order(T, O) -> mnesia_schema:change_table_load_order(T, O). +-spec change_table_majority(Tab::table(), M::boolean()) -> t_result(ok). change_table_majority(T, M) -> mnesia_schema:change_table_majority(T, M). +-spec set_master_nodes(Ns::[node()]) -> 'ok' | {'error', Reason::term()}. set_master_nodes(Nodes) when is_list(Nodes) -> UseDir = system_info(use_dir), IsRunning = system_info(is_running), @@ -2498,6 +2824,8 @@ log_valid_master_nodes(Cstructs, Nodes, UseDir, IsRunning) -> Args = lists:map(Fun, Cstructs), mnesia_recover:log_master_nodes(Args, UseDir, IsRunning). +-spec set_master_nodes(Tab::table(), Ns::[node()]) -> + 'ok' | {'error', Reason::term()}. set_master_nodes(Tab, Nodes) when is_list(Nodes) -> UseDir = system_info(use_dir), IsRunning = system_info(is_running), @@ -2548,31 +2876,39 @@ set_master_nodes(Tab, Nodes) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Misc admin - +-spec dump_log() -> 'dumped'. dump_log() -> mnesia_controller:sync_dump_log(user). +-spec sync_log() -> 'ok' | {'error', Reason::term()}. sync_log() -> mnesia_monitor:sync_log(latest_log). +-spec subscribe(What) -> {'ok', node()} | {'error', Reason::term()} when + What :: 'system' | 'activity' | {'table', table(), 'simple' | 'detailed'}. subscribe(What) -> mnesia_subscr:subscribe(self(), What). +-spec unsubscribe(What) -> {'ok', node()} | {'error', Reason::term()} when + What :: 'system' | 'activity' | {'table', table(), 'simple' | 'detailed'}. unsubscribe(What) -> mnesia_subscr:unsubscribe(self(), What). +-spec report_event(Event::_) -> 'ok'. report_event(Event) -> mnesia_lib:report_system_event({mnesia_user, Event}). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Snmp - +-spec snmp_open_table(Tab::table(), Snmp::snmp_struct()) -> ok. snmp_open_table(Tab, Us) -> mnesia_schema:add_snmp(Tab, Us). +-spec snmp_close_table(Tab::table()) -> ok. snmp_close_table(Tab) -> mnesia_schema:del_snmp(Tab). +-spec snmp_get_row(Tab::table(), [integer()]) -> {'ok', Row::tuple()} | 'undefined'. snmp_get_row(Tab, RowIndex) when is_atom(Tab), Tab /= schema, is_list(RowIndex) -> case get(mnesia_activity_state) of {Mod, Tid, Ts=#tidstore{store=Store}} when element(1, Tid) =:= tid -> @@ -2608,7 +2944,7 @@ snmp_get_row(Tab, _RowIndex) -> abort({bad_type, Tab}). %%%%%%%%%%%%% - +-spec snmp_get_next_index(Tab::table(), [integer()]) -> {'ok', [integer()]} | 'endOfTable'. snmp_get_next_index(Tab, RowIndex) when is_atom(Tab), Tab /= schema, is_list(RowIndex) -> {Next,OrigKey} = dirty_rpc(Tab, mnesia_snmp_hook, get_next_index, [Tab, RowIndex]), case get(mnesia_activity_state) of @@ -2650,7 +2986,7 @@ get_ordered_snmp_key(_, []) -> endOfTable. %%%%%%%%%% - +-spec snmp_get_mnesia_key(Tab::table(), [integer()]) -> {'ok', Key::term()} | 'undefined'. snmp_get_mnesia_key(Tab, RowIndex) when is_atom(Tab), Tab /= schema, is_list(RowIndex) -> case get(mnesia_activity_state) of {_Mod, Tid, Ts} when element(1, Tid) =:= tid -> @@ -2714,17 +3050,27 @@ snmp_filter_key(undefined, RowIndex, Tab, Store) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Textfile access - +-spec load_textfile(File::file:filename()) -> t_result(ok) | {'error', term()}. load_textfile(F) -> mnesia_text:load_textfile(F). + +-spec dump_to_textfile(File :: file:filename()) -> 'ok' | 'error' | {'error', term()}. dump_to_textfile(F) -> mnesia_text:dump_to_textfile(F). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% QLC Handles %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec table(Tab::table()) -> qlc:query_handle(). table(Tab) -> table(Tab, []). + +-spec table(Tab::table(), Options) -> qlc:query_handle() when + Options :: Option | [Option], + Option :: MnesiaOpt | QlcOption, + MnesiaOpt :: {'traverse', SelectOp} | {lock, lock_kind()} | {n_objects, non_neg_integer()}, + SelectOp :: 'select' | {'select', ets:match_spec()}, + QlcOption :: {'key_equality', '==' | '=:='}. table(Tab,Opts) -> {[Trav,Lock,NObjects],QlcOptions0} = qlc_opts(Opts,[{traverse,select},{lock,read},{n_objects,100}]), diff --git a/lib/mnesia/src/mnesia.hrl b/lib/mnesia/src/mnesia.hrl index 72940b4729..da7e662288 100644 --- a/lib/mnesia/src/mnesia.hrl +++ b/lib/mnesia/src/mnesia.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2014. All Rights Reserved. +%% Copyright Ericsson AB 1996-2016. 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. @@ -49,12 +49,12 @@ %% It's important that counter is first, since we compare tid's --record(tid, +-record(tid, {counter, %% serial no for tid pid}). %% owner of tid --record(tidstore, +-record(tidstore, {store, %% current ets table for tid up_stores = [], %% list of upper layer stores for nested trans level = 1}). %% transaction level @@ -68,6 +68,7 @@ ram_copies = [], % [Node] disc_copies = [], % [Node] disc_only_copies = [], % [Node] + external_copies = [], % [{{Alias,Mod},[Node]}] load_order = 0, % Integer access_mode = read_write, % read_write | read_only majority = false, % true | false @@ -103,7 +104,7 @@ ram_copies = [], disc_copies = [], disc_only_copies = [], - snmp = [], + ext = [], schema_ops = [] }). @@ -127,5 +128,4 @@ mnesia_lib:eval_debug_fun(I, C, ?FILE, ?LINE)). -else. -define(eval_debug_fun(I, C), ok). --endif. - +-endif. diff --git a/lib/mnesia/src/mnesia_app.erl b/lib/mnesia/src/mnesia_app.erl new file mode 100644 index 0000000000..4d89011db2 --- /dev/null +++ b/lib/mnesia/src/mnesia_app.erl @@ -0,0 +1,41 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2016. 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_app). + +-behaviour(application). + +-export([start/2, stop/1]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% application callback functions + +start(normal, Args) -> + case mnesia_sup:start_link(Args) of + {ok, Pid} -> + {ok, Pid, {normal, Args}}; + Error -> + Error + end; +start(_, _) -> + {error, badarg}. + +stop(_StartArgs) -> + ok. diff --git a/lib/mnesia/src/mnesia_backend_type.erl b/lib/mnesia/src/mnesia_backend_type.erl new file mode 100644 index 0000000000..4791b82a01 --- /dev/null +++ b/lib/mnesia/src/mnesia_backend_type.erl @@ -0,0 +1,115 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2015. 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% +%% + +%% +%% Behaviour definition for mnesia backend types. +%% + +%%%header_doc_include + +-module(mnesia_backend_type). + +-export([behaviour_info/1]). + +%%%header_doc_include + +%%%impl_doc_include + +%% Note that mnesia considers all callbacks mandatory!! +%% +behaviour_info(callbacks) -> + [ + {add_aliases, 1}, % (Aliases) -> ok + {check_definition, 4}, % (TypeAlias, Tab, Nodes, Properties) -> ok + {close_table, 2}, % (TypeAlias, Tab) -> ok + {create_table, 3}, % (TypeAlias, Tab, Properties) -> ok + {delete, 3}, % (TypeAlias, Tab, Key) -> true | ok + {delete_table, 2}, % (TypeAlias, Tab) -> ok + {first, 2}, % (TypeAlias, Tab) -> Key::Term | '$end_of_table' + {fixtable, 3}, % (TypeAlias, Tab, Bool) -> ok | true + {last, 2}, % (TypeAlias, Tab) -> Key::Term | '$end_of_table' + {index_is_consistent,3}, % (TypeAlias, IxTag, Bool) -> ok + {init_backend, 0}, % () -> ok + {info, 3}, % (TypeAlias, Tab, Item) -> Term + {insert, 3}, % (TypeAlias, Tab, Object) -> ok + {lookup, 3}, % (TypeAlias, Tab, Key) -> [Objects] + {is_index_consistent,2}, % (TypeAlias, IxTag) -> Bool + {load_table, 4}, % (TypeAlias, Tab, Reason, CsList) -> ok + {match_delete, 3}, % (TypeAlias, Tab, Pattern) -> ok + {next, 3}, % (TypeAlias, Tab, Key) -> Key::Term | '$end_of_table' + {prev, 3}, % (TypeAlias, Tab, Key) -> Key::Term | '$end_of_table' + {receiver_first_message, 4}, % (Sender, FirstMsg, Alias, Tab) -> {Size, State} + {receive_data, 5}, % (Data, Alias, Name, Sender, State) -> {more, State} | {{more, Msg}, State} + {receive_done, 4}, % (Alias, Tab, Sender, State) -> ok + {real_suffixes, 0}, % () -> [FileSuffix] + {remove_aliases, 1}, % (Aliases) -> ok + {repair_continuation, 2}, % (Continuation, MatchSpec) -> Continuation + {select, 1}, % (Continuation) -> {[Match], Continuation'} | '$end_of_table' + {select, 3}, % (TypeAlias, Tab, Pattern) -> {[Match], Continuation'} | '$end_of_table' + {select, 4}, % (TypeAlias, Tab, MatchSpec, Limit) {[Match], Continuation'} | '$end_of_table' + {sender_init, 4}, % (TypeAlias, Tab, LoadReason, Pid) -> + % {standard, Init(), Chunk()} | {Init(), Chunk()} + {semantics, 2}, % (TypeAlias, storage | types | index_fun | index_types) -> + % ram_copies | disc_copies, set | ordered_set | bag, fun(), ordered | bag + {slot, 3}, % (TypeAlias, Tab, Pos) -> '$end_of_table' | Objects | {error, Reason} + {sync_close_table, 2}, % (TypeAlias, Tab) -> ok + {tmp_suffixes, 0}, % () -> [FileSuffix] + {update_counter, 4}, % (TypeAlias, Tab, Counter, Val) -> NewVal + {validate_key, 6}, % (TypeAlias, Tab, RecName, Arity, Type, Key) -> {RecName, Arity, Type} + {validate_record, 6} % (TypeAlias, Tab, RecName, Arity, Type, Obj) -> {RecName, Arity, Type} + ]. + +%%%impl_doc_include + +%% -type tab() :: atom(). +%% -type alias() :: atom(). +%% -type rec_name() :: atom(). +%% -type type() :: set | bag | ordered_set. +%% -type proplist() :: [{atom(), any()}]. +%% -type key() :: any(). +%% -type db_object() :: tuple(). + +%% -type matchspec() :: ets:match_spec(). +%% -type limit() :: integer() | infinity. + +%% -type cont_fun() :: any(). +%% -type cont() :: '$end_of_table' | cont_fun(). + +%% -callback check_definition(alias(), tab(), [node()], proplist()) -> ok. +%% -callback create_table(alias(), tab(), proplist()) -> tab(). +%% -callback load_table(alias(), tab(), any()) -> ok. +%% -callback delete_table(alias(), tab()) -> ok. +%% -callback first(alias(), tab()) -> key(). +%% -callback last(alias(), tab()) -> key(). +%% -callback next(alias(), tab(), key()) -> key(). +%% -callback prev(alias(), tab(), key()) -> key(). +%% -callback insert(alias(), tab(), db_object()) -> ok. +%% -callback lookup(alias(), tab(), key()) -> [db_object()]. +%% -callback delete(alias(), tab(), key()) -> ok. +%% -callback update_counter(alias(), tab(), key(), integer()) -> integer(). +%% -callback select(cont()) -> {list(), cont()}. +%% -callback select(alias(), tab(), matchspec()) -> list() | '$end_of_table'. +%% -callback select(alias(), tab(), matchspec(), limit()) -> {list(), cont()}. +%% -callback slot(alias(), tab(), integer()) -> key(). +%% -callback validate_key(alias(), tab(), rec_name(), arity(), type(), key()) -> +%% {rec_name(), arity(), type()}. +%% -callback validate_record(alias(),tab(),rec_name(),arity(),type(),db_obj()) -> +%% {rec_name(), arity(), type()}. +%% -callback repair_continuation(cont(), matchspec()) -> cont(). diff --git a/lib/mnesia/src/mnesia_backup.erl b/lib/mnesia/src/mnesia_backup.erl index 78107bda56..719e010c17 100644 --- a/lib/mnesia/src/mnesia_backup.erl +++ b/lib/mnesia/src/mnesia_backup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2011. All Rights Reserved. +%% Copyright Ericsson AB 1996-2016. 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. diff --git a/lib/mnesia/src/mnesia_bup.erl b/lib/mnesia/src/mnesia_bup.erl index 0e653f2bc4..34f16f178b 100644 --- a/lib/mnesia/src/mnesia_bup.erl +++ b/lib/mnesia/src/mnesia_bup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2011. All Rights Reserved. +%% Copyright Ericsson AB 1996-2016. 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. @@ -28,6 +28,7 @@ fallback_exists/0, tm_fallback_start/1, create_schema/1, + create_schema/2, install_fallback/1, install_fallback/2, uninstall_fallback/0, @@ -46,7 +47,7 @@ uninstall_fallback_master/2, local_uninstall_fallback/2, do_traverse_backup/7, - trav_apply/4 + trav_apply/5 ]). -include("mnesia.hrl"). @@ -81,7 +82,8 @@ iterate(Mod, Fun, Opaque, Acc) -> R = #restore{bup_module = Mod, bup_data = Opaque}, try read_schema_section(R) of {R2, {Header, Schema, Rest}} -> - try iter(R2, Header, Schema, Fun, Acc, Rest) of + Ext = get_ext_types(Schema), + try iter(R2, Header, Schema, Ext, Fun, Acc, Rest) of {ok, R3, Res} -> close_read(R3), {ok, Res} @@ -96,23 +98,38 @@ iterate(Mod, Fun, Opaque, Acc) -> Err end. -iter(R, Header, Schema, Fun, Acc, []) -> +get_ext_types(Schema) -> + try + List = lookup_schema(schema, Schema), + case lists:keyfind(user_properties, 1, List) of + {_, Props} -> + proplists:get_value( + mnesia_backend_types, Props, []); + false -> + [] + end + catch + throw:{error, {"Cannot lookup",_}} -> + [] + end. + +iter(R, Header, Schema, Ext, Fun, Acc, []) -> case safe_apply(R, read, [R#restore.bup_data]) of {R2, []} -> - Res = Fun([], Header, Schema, Acc), + Res = Fun([], Header, Schema, Ext, Acc), {ok, R2, Res}; {R2, BupItems} -> - iter(R2, Header, Schema, Fun, Acc, BupItems) + iter(R2, Header, Schema, Ext, Fun, Acc, BupItems) end; -iter(R, Header, Schema, Fun, Acc, BupItems) -> - Acc2 = Fun(BupItems, Header, Schema, Acc), - iter(R, Header, Schema, Fun, Acc2, []). +iter(R, Header, Schema, Ext, Fun, Acc, BupItems) -> + Acc2 = Fun(BupItems, Header, Schema, Ext, Acc), + iter(R, Header, Schema, Ext, Fun, Acc2, []). -spec safe_apply(#restore{}, atom(), list()) -> tuple(). safe_apply(R, write, [_, Items]) when Items =:= [] -> R; safe_apply(R, What, Args) -> - Abort = fun(Re) -> abort_restore(R, What, Args, Re) end, + Abort = abort_restore_fun(R, What, Args), Mod = R#restore.bup_module, try apply(Mod, What, Args) of {ok, Opaque, Items} when What =:= read -> @@ -127,6 +144,10 @@ safe_apply(R, What, Args) -> Abort(Re) end. +-spec abort_restore_fun(_, _, _) -> fun((_) -> no_return()). +abort_restore_fun(R, What, Args) -> + fun(Re) -> abort_restore(R, What, Args, Re) end. + abort_restore(R = #restore{bup_module=Mod}, What, Args, Reason) -> dbg_out("Restore aborted. ~p:~p~p -> ~p~n", [Mod, What, Args, Reason]), @@ -157,10 +178,11 @@ fallback_to_schema(Fname) -> read_schema(Mod, Opaque) -> R = #restore{bup_module = Mod, bup_data = Opaque}, try read_schema_section(R) of - {_, {_Header, Schema, _}} -> Schema + {R2, {_Header, Schema, _}} -> + close_read(R2), + Schema catch throw:{error,_} = Error -> Error - after close_read(R) end. %% Open backup media and extract schema @@ -173,8 +195,13 @@ read_schema_section(R) -> do_read_schema_section(R) -> R2 = safe_apply(R, open_read, [R#restore.bup_data]), - {R3, RawSchema} = safe_apply(R2, read, [R2#restore.bup_data]), - do_read_schema_section(R3, verify_header(RawSchema), []). + try + {R3, RawSchema} = safe_apply(R2, read, [R2#restore.bup_data]), + do_read_schema_section(R3, verify_header(RawSchema), []) + catch T:E -> + close_read(R2), + erlang:raise(T,E,erlang:get_stacktrace()) + end. do_read_schema_section(R, {ok, B, C, []}, Acc) -> case safe_apply(R, read, [R#restore.bup_data]) of @@ -252,7 +279,7 @@ convert_0_1(Schema) -> Cs = mnesia_schema:list2cs(List), convert_0_1(Schema2, [], Cs); false -> - List = mnesia_schema:get_initial_schema(disc_copies, [node()]), + List = mnesia_schema:get_initial_schema(disc_copies, [node()], []), Cs = mnesia_schema:list2cs(List), convert_0_1(Schema, [], Cs) end. @@ -300,16 +327,19 @@ schema2bup({schema, Tab, TableDef}) -> %% Create schema on the given nodes %% Requires that old schemas has been deleted %% Returns ok | {error, Reason} -create_schema([]) -> - create_schema([node()]); -create_schema(Ns) when is_list(Ns) -> +create_schema(Nodes) -> + create_schema(Nodes, []). + +create_schema([], Props) -> + create_schema([node()], Props); +create_schema(Ns, Props) when is_list(Ns), is_list(Props) -> case is_set(Ns) of true -> - create_schema(Ns, mnesia_schema:ensure_no_schema(Ns)); + create_schema(Ns, mnesia_schema:ensure_no_schema(Ns), Props); false -> {error, {combine_error, Ns}} end; -create_schema(Ns) -> +create_schema(Ns, _Props) -> {error, {badarg, Ns}}. is_set(List) when is_list(List) -> @@ -317,7 +347,7 @@ is_set(List) when is_list(List) -> is_set(_) -> false. -create_schema(Ns, ok) -> +create_schema(Ns, ok, Props) -> %% Ensure that we access the intended Mnesia %% directory. This function may not be called %% during startup since it will cause the @@ -336,7 +366,7 @@ create_schema(Ns, ok) -> Str = mk_str(), File = mnesia_lib:dir(Str), file:delete(File), - try make_initial_backup(Ns, File, Mod) of + try make_initial_backup(Ns, File, Mod, Props) of {ok, _Res} -> case do_install_fallback(File, Mod) of ok -> @@ -353,9 +383,9 @@ create_schema(Ns, ok) -> {error, Reason} -> {error, Reason} end; -create_schema(_Ns, {error, Reason}) -> +create_schema(_Ns, {error, Reason}, _) -> {error, Reason}; -create_schema(_Ns, Reason) -> +create_schema(_Ns, Reason, _) -> {error, Reason}. mk_str() -> @@ -363,7 +393,10 @@ mk_str() -> lists:concat([node()] ++ Now ++ ".TMP"). make_initial_backup(Ns, Opaque, Mod) -> - Orig = mnesia_schema:get_initial_schema(disc_copies, Ns), + make_initial_backup(Ns, Opaque, Mod, []). + +make_initial_backup(Ns, Opaque, Mod, Props) -> + Orig = mnesia_schema:get_initial_schema(disc_copies, Ns, Props), Modded = proplists:delete(storage_properties, proplists:delete(majority, Orig)), Schema = [{schema, schema, Modded}], O2 = do_apply(Mod, open_write, [Opaque], Opaque), @@ -476,15 +509,15 @@ install_fallback_master(ClientPid, FA) -> State = {start, FA}, Opaque = FA#fallback_args.opaque, Mod = FA#fallback_args.module, - Res = iterate(Mod, fun restore_recs/4, Opaque, State), + Res = iterate(Mod, fun restore_recs/5, Opaque, State), unlink(ClientPid), ClientPid ! {self(), Res}, exit(shutdown). -restore_recs(_, _, _, stop) -> +restore_recs(_, _, _, _, stop) -> throw({error, "restore_recs already stopped"}); -restore_recs(Recs, Header, Schema, {start, FA}) -> +restore_recs(Recs, Header, Schema, Ext, {start, FA}) -> %% No records in backup Schema2 = convert_schema(Header#log_header.log_version, Schema), CreateList = lookup_schema(schema, Schema2), @@ -495,19 +528,19 @@ restore_recs(Recs, Header, Schema, {start, FA}) -> Args = [self(), FA], Pids = [spawn_link(N, ?MODULE, fallback_receiver, Args) || N <- Ns], send_fallback(Pids, {start, Header, Schema2}), - Res = restore_recs(Recs, Header, Schema2, Pids), + Res = restore_recs(Recs, Header, Schema2, Ext, Pids), global:del_lock({{mnesia_table_lock, schema}, self()}, Ns), Res catch _:Reason -> throw({error, {"Bad schema in restore_recs", Reason}}) end; -restore_recs([], _Header, _Schema, Pids) -> +restore_recs([], _Header, _Schema, _Ext, Pids) -> send_fallback(Pids, swap), send_fallback(Pids, stop), stop; -restore_recs(Recs, _, _, Pids) -> +restore_recs(Recs, _, _, _, Pids) -> send_fallback(Pids, {records, Recs}), Pids. @@ -706,7 +739,7 @@ do_fallback_start(true, false) -> BupFile = fallback_bup(), Mod = mnesia_backup, LocalTabs = ?ets_new_table(mnesia_local_tables, [set, public, {keypos, 2}]), - case iterate(Mod, fun restore_tables/4, BupFile, {start, LocalTabs}) of + case iterate(Mod, fun restore_tables/5, BupFile, {start, LocalTabs}) of {ok, _Res} -> ?SAFE(dets:close(schema)), TmpSchema = mnesia_lib:tab2tmp(schema), @@ -727,23 +760,24 @@ do_fallback_start(true, false) -> {error, {"Cannot start from fallback", Reason}} end. -restore_tables(All=[Rec | Recs], Header, Schema, State={local, LocalTabs, LT}) -> +restore_tables(All=[Rec | Recs], Header, Schema, Ext, + State={local, LocalTabs, LT}) -> Tab = element(1, Rec), if Tab =:= LT#local_tab.name -> Key = element(2, Rec), (LT#local_tab.add)(Tab, Key, Rec, LT), - restore_tables(Recs, Header, Schema, State); + restore_tables(Recs, Header, Schema, Ext, State); true -> NewState = {new, LocalTabs}, - restore_tables(All, Header, Schema, NewState) + restore_tables(All, Header, Schema, Ext, NewState) end; -restore_tables(All=[Rec | Recs], Header, Schema, {new, LocalTabs}) -> +restore_tables(All=[Rec | Recs], Header, Schema, Ext, {new, LocalTabs}) -> Tab = element(1, Rec), case ?ets_lookup(LocalTabs, Tab) of [] -> State = {not_local, LocalTabs, Tab}, - restore_tables(Recs, Header, Schema, State); + restore_tables(Recs, Header, Schema, Ext, State); [LT] when is_record(LT, local_tab) -> State = {local, LocalTabs, LT}, case LT#local_tab.opened of @@ -752,38 +786,39 @@ restore_tables(All=[Rec | Recs], Header, Schema, {new, LocalTabs}) -> (LT#local_tab.open)(Tab, LT), ?ets_insert(LocalTabs,LT#local_tab{opened=true}) end, - restore_tables(All, Header, Schema, State) + restore_tables(All, Header, Schema, Ext, State) end; -restore_tables(All=[Rec | Recs], Header, Schema, S = {not_local, LocalTabs, PrevTab}) -> +restore_tables(All=[Rec | Recs], Header, Schema, Ext, + S = {not_local, LocalTabs, PrevTab}) -> Tab = element(1, Rec), if Tab =:= PrevTab -> - restore_tables(Recs, Header, Schema, S); + restore_tables(Recs, Header, Schema, Ext, S); true -> State = {new, LocalTabs}, - restore_tables(All, Header, Schema, State) + restore_tables(All, Header, Schema, Ext, State) end; -restore_tables(Recs, Header, Schema, {start, LocalTabs}) -> +restore_tables(Recs, Header, Schema, Ext, {start, LocalTabs}) -> Dir = mnesia_lib:dir(), OldDir = filename:join([Dir, "OLD_DIR"]), mnesia_schema:purge_dir(OldDir, []), mnesia_schema:purge_dir(Dir, [fallback_name()]), - init_dat_files(Schema, LocalTabs), + init_dat_files(Schema, Ext, LocalTabs), State = {new, LocalTabs}, - restore_tables(Recs, Header, Schema, State); -restore_tables([], _Header, _Schema, State) -> + restore_tables(Recs, Header, Schema, Ext, State); +restore_tables([], _Header, _Schema, _Ext, State) -> State. %% Creates all neccessary dat files and inserts %% the table definitions in the schema table %% %% Returns a list of local_tab tuples for all local tables -init_dat_files(Schema, LocalTabs) -> +init_dat_files(Schema, Ext, LocalTabs) -> TmpFile = mnesia_lib:tab2tmp(schema), Args = [{file, TmpFile}, {keypos, 2}, {type, set}], case dets:open_file(schema, Args) of % Assume schema lock {ok, _} -> - create_dat_files(Schema, LocalTabs), + create_dat_files(Schema, Ext, LocalTabs), ok = dets:close(schema), LocalTab = #local_tab{name = schema, storage_type = disc_copies, @@ -798,10 +833,10 @@ init_dat_files(Schema, LocalTabs) -> throw({error, {"Cannot open file", schema, Args, Reason}}) end. -create_dat_files([{schema, schema, TabDef} | Tail], LocalTabs) -> +create_dat_files([{schema, schema, TabDef} | Tail], Ext, LocalTabs) -> ok = dets:insert(schema, {schema, schema, TabDef}), - create_dat_files(Tail, LocalTabs); -create_dat_files([{schema, Tab, TabDef} | Tail], LocalTabs) -> + create_dat_files(Tail, Ext, LocalTabs); +create_dat_files([{schema, Tab, TabDef} | Tail], Ext, LocalTabs) -> TmpFile = mnesia_lib:tab2tmp(Tab), DatFile = mnesia_lib:tab2dat(Tab), DclFile = mnesia_lib:tab2dcl(Tab), @@ -814,56 +849,21 @@ create_dat_files([{schema, Tab, TabDef} | Tail], LocalTabs) -> mnesia_lib:dets_sync_close(Tab), file:delete(TmpFile), - Cs = mnesia_schema:list2cs(TabDef), + Cs = mnesia_schema:list2cs(TabDef, Ext), ok = dets:insert(schema, {schema, Tab, TabDef}), RecName = Cs#cstruct.record_name, Storage = mnesia_lib:cs_to_storage_type(node(), Cs), + delete_ext(Storage, Tab), + Semantics = mnesia_lib:semantics(Storage, storage), if - Storage =:= unknown -> + Semantics =:= undefined -> ok = dets:delete(schema, {schema, Tab}), - create_dat_files(Tail, LocalTabs); - Storage =:= disc_only_copies -> - Args = [{file, TmpFile}, {keypos, 2}, - {type, mnesia_lib:disk_type(Tab, Cs#cstruct.type)}], - Open = fun(T, LT) when T =:= LT#local_tab.name -> - case mnesia_lib:dets_sync_open(T, Args) of - {ok, _} -> - ok; - {error, Reason} -> - throw({error, {"Cannot open file", T, Args, Reason}}) - end - end, - Add = fun(T, Key, Rec, LT) when T =:= LT#local_tab.name -> - case Rec of - {_T, Key} -> - ok = dets:delete(T, Key); - (Rec) when T =:= RecName -> - ok = dets:insert(Tab, Rec); - (Rec) -> - Rec2 = setelement(1, Rec, RecName), - ok = dets:insert(T, Rec2) - end - end, - Close = fun(T, LT) when T =:= LT#local_tab.name -> - mnesia_lib:dets_sync_close(T) - end, - Swap = fun(T, LT) when T =:= LT#local_tab.name -> - Expunge(), - case LT#local_tab.opened of - true -> - Close(T,LT); - false -> - Open(T,LT), - Close(T,LT) - end, - case file:rename(TmpFile, DatFile) of - ok -> - ok; - {error, Reason} -> - mnesia_lib:fatal("Cannot rename file ~p -> ~p: ~p~n", - [TmpFile, DatFile, Reason]) - end - end, + create_dat_files(Tail, Ext, LocalTabs); + Semantics =:= disc_only_copies -> + Open = disc_only_open_fun(Storage, Cs), + Add = disc_only_add_fun(Storage, Cs), + Close = disc_only_close_fun(Storage), + Swap = disc_only_swap_fun(Storage, Expunge, Open, Close), LocalTab = #local_tab{name = Tab, storage_type = Storage, open = Open, @@ -873,8 +873,8 @@ create_dat_files([{schema, Tab, TabDef} | Tail], LocalTabs) -> record_name = RecName, opened = false}, ?ets_insert(LocalTabs, LocalTab), - create_dat_files(Tail, LocalTabs); - Storage =:= ram_copies; Storage =:= disc_copies -> + create_dat_files(Tail, Ext, LocalTabs); + Semantics =:= ram_copies; Storage =:= disc_copies -> Open = fun(T, LT) when T =:= LT#local_tab.name -> mnesia_log:open_log({?MODULE, T}, mnesia_log:dcl_log_header(), @@ -920,7 +920,7 @@ create_dat_files([{schema, Tab, TabDef} | Tail], LocalTabs) -> ok -> ok; {error, Reason} -> - mnesia_lib:fatal("Cannot rename file ~p -> ~p: ~p~n", + mnesia_lib:fatal("Cannot rename file ~tp -> ~tp: ~tp~n", [TmpFile, DclFile, Reason]) end end @@ -935,18 +935,97 @@ create_dat_files([{schema, Tab, TabDef} | Tail], LocalTabs) -> opened = false }, ?ets_insert(LocalTabs, LocalTab), - create_dat_files(Tail, LocalTabs) + create_dat_files(Tail, Ext, LocalTabs); + true -> + error({unknown_semantics, [{semantics, Semantics}, + {tabdef, TabDef}, + {ext, Ext}]}) end; -create_dat_files([{schema, Tab} | Tail], LocalTabs) -> +create_dat_files([{schema, Tab} | Tail], Ext, LocalTabs) -> ?ets_delete(LocalTabs, Tab), ok = dets:delete(schema, {schema, Tab}), TmpFile = mnesia_lib:tab2tmp(Tab), mnesia_lib:dets_sync_close(Tab), file:delete(TmpFile), - create_dat_files(Tail, LocalTabs); -create_dat_files([], _LocalTabs) -> + create_dat_files(Tail, Ext, LocalTabs); +create_dat_files([], _Ext, _LocalTabs) -> ok. +delete_ext({ext, Alias, Mod}, Tab) -> + Mod:close_table(Alias, Tab), + Mod:delete_table(Alias, Tab), + ok; +delete_ext(_, _) -> + ok. + + +disc_only_open_fun(disc_only_copies, #cstruct{name = Tab} =Cs) -> + TmpFile = mnesia_lib:tab2tmp(Tab), + Args = [{file, TmpFile}, {keypos, 2}, + {type, mnesia_lib:disk_type(Tab, Cs#cstruct.type)}], + fun(T, LT) when T =:= LT#local_tab.name -> + case mnesia_lib:dets_sync_open(T, Args) of + {ok, _} -> + ok; + {error, Reason} -> + throw({error, {"Cannot open file", T, Args, Reason}}) + end + end; +disc_only_open_fun({ext,Alias,Mod}, Cs) -> + fun(T, LT) when T =:= LT#local_tab.name -> + ok = Mod:load_table(Alias, T, restore, mnesia_schema:cs2list(Cs)) + end. + +disc_only_add_fun(Storage, #cstruct{name = Tab, + record_name = RecName}) -> + fun(T, Key, Rec, #local_tab{name = T}) when T =:= Tab-> + case Rec of + {_T, Key} -> + ok = mnesia_lib:db_erase(Storage, T, Key); + (Rec) when T =:= RecName -> + ok = mnesia_lib:db_put(Storage, T, Rec); + (Rec) -> + ok = mnesia_lib:db_put(Storage, T, + setelement(1, Rec, RecName)) + end + end. + +disc_only_close_fun(disc_only_copies) -> + fun(T, LT) when T =:= LT#local_tab.name -> + mnesia_lib:dets_sync_close(T) + end; +disc_only_close_fun({ext, Alias, Mod}) -> + fun(T, _LT) -> + Mod:sync_close_table(Alias, T) + end. + + +disc_only_swap_fun(disc_only_copies, Expunge, Open, Close) -> + fun(T, LT) when T =:= LT#local_tab.name -> + TmpFile = mnesia_lib:tab2tmp(T), + DatFile = mnesia_lib:tab2dat(T), + Expunge(), + case LT#local_tab.opened of + true -> + Close(T,LT); + false -> + Open(T,LT), + Close(T,LT) + end, + case file:rename(TmpFile, DatFile) of + ok -> + ok; + {error, Reason} -> + mnesia_lib:fatal("Cannot rename file ~tp -> ~tp: ~tp~n", + [TmpFile, DatFile, Reason]) + end + end; +disc_only_swap_fun({ext, _Alias, _Mod}, _Expunge, _Open, Close) -> + fun(T, #local_tab{name = T} = LT) -> + Close(T, LT) + end. + + uninstall_fallback() -> uninstall_fallback([{scope, global}]). @@ -1123,7 +1202,7 @@ do_traverse_backup(ClientPid, Source, SourceMod, Target, TargetMod, Fun, Acc) -> end, A = {start, Fun, Acc, TargetMod, Iter}, Res = - case iterate(SourceMod, fun trav_apply/4, Source, A) of + case iterate(SourceMod, fun trav_apply/5, Source, A) of {ok, {iter, _, Acc2, _, Iter2}} when TargetMod =/= read_only -> try do_apply(TargetMod, commit_write, [Iter2], Iter2), @@ -1142,7 +1221,7 @@ do_traverse_backup(ClientPid, Source, SourceMod, Target, TargetMod, Fun, Acc) -> unlink(ClientPid), ClientPid ! {iter_done, self(), Res}. -trav_apply(Recs, _Header, _Schema, {iter, Fun, Acc, Mod, Iter}) -> +trav_apply(Recs, _Header, _Schema, _Ext, {iter, Fun, Acc, Mod, Iter}) -> {NewRecs, Acc2} = filter_foldl(Fun, Acc, Recs), if Mod =/= read_only, NewRecs =/= [] -> @@ -1151,7 +1230,7 @@ trav_apply(Recs, _Header, _Schema, {iter, Fun, Acc, Mod, Iter}) -> true -> {iter, Fun, Acc2, Mod, Iter} end; -trav_apply(Recs, Header, Schema, {start, Fun, Acc, Mod, Iter}) -> +trav_apply(Recs, Header, Schema, Ext, {start, Fun, Acc, Mod, Iter}) -> Iter2 = if Mod =/= read_only -> @@ -1159,8 +1238,9 @@ trav_apply(Recs, Header, Schema, {start, Fun, Acc, Mod, Iter}) -> true -> Iter end, - TravAcc = trav_apply(Schema, Header, Schema, {iter, Fun, Acc, Mod, Iter2}), - trav_apply(Recs, Header, Schema, TravAcc). + TravAcc = trav_apply(Schema, Header, Schema, Ext, + {iter, Fun, Acc, Mod, Iter2}), + trav_apply(Recs, Header, Schema, Ext, TravAcc). filter_foldl(Fun, Acc, [Head|Tail]) -> case Fun(Head, Acc) of diff --git a/lib/mnesia/src/mnesia_checkpoint.erl b/lib/mnesia/src/mnesia_checkpoint.erl index 1407bc0557..8112378ffd 100644 --- a/lib/mnesia/src/mnesia_checkpoint.erl +++ b/lib/mnesia/src/mnesia_checkpoint.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2013 +%% Copyright Ericsson AB 1996-2017 %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -675,12 +675,22 @@ tab2retainer({Tab, Name}) -> FlatName = lists:flatten(io_lib:write(Name)), mnesia_lib:dir(lists:concat([?MODULE, "_", Tab, "_", FlatName, ".RET"])). +retainer_create(_Cp, R, Tab, Name, Ext = {ext, Alias, Mod}) -> + T = {Tab, retainer, Name}, + P = mnesia_schema:cs2list(val({Tab, cstruct})), + Mod:delete_table(Alias, T), + ok = Mod:create_table(Alias, T, P), + Cs = val({Tab, cstruct}), + Mod:load_table(Alias, T, {retainer, create_table}, + mnesia_schema:cs2list(Cs)), + dbg_out("Checkpoint retainer created ~p ~tp~n", [Name, Tab]), + R#retainer{store = {Ext, T}, really_retain = true}; retainer_create(_Cp, R, Tab, Name, disc_only_copies) -> Fname = tab2retainer({Tab, Name}), file:delete(Fname), Args = [{file, Fname}, {type, set}, {keypos, 2}, {repair, false}], {ok, _} = mnesia_lib:dets_sync_open({Tab, Name}, Args), - dbg_out("Checkpoint retainer created ~p ~p~n", [Name, Tab]), + dbg_out("Checkpoint retainer created ~p ~tp~n", [Name, Tab]), R#retainer{store = {dets, {Tab, Name}}, really_retain = true}; retainer_create(Cp, R, Tab, Name, Storage) -> T = ?ets_new_table(mnesia_retainer, [set, public, {keypos, 2}]), @@ -688,7 +698,7 @@ retainer_create(Cp, R, Tab, Name, Storage) -> ReallyR = R#retainer.really_retain, ReallyCp = lists:member(Tab, Overriders), ReallyR2 = prepare_ram_tab(Tab, T, Storage, ReallyR, ReallyCp), - dbg_out("Checkpoint retainer created ~p ~p~n", [Name, Tab]), + dbg_out("Checkpoint retainer created ~p ~tp~n", [Name, Tab]), R#retainer{store = {ets, T}, really_retain = ReallyR2}. %% Copy the dumped table into retainer if needed @@ -734,15 +744,23 @@ traverse_dcd({Cont, Recs}, Log, Fun) -> %% trashed data?? traverse_dcd(eof, _Log, _Fun) -> ok. +retainer_get({{ext, Alias, Mod}, Store}, Key) -> + Mod:lookup(Alias, Store, Key); retainer_get({ets, Store}, Key) -> ?ets_lookup(Store, Key); retainer_get({dets, Store}, Key) -> dets:lookup(Store, Key). +retainer_put({{ext, Alias, Mod}, Store}, Val) -> + Mod:insert(Alias, Store, Val); retainer_put({ets, Store}, Val) -> ?ets_insert(Store, Val); retainer_put({dets, Store}, Val) -> dets:insert(Store, Val). +retainer_first({{ext, Alias, Mod}, Store}) -> + Mod:first(Alias, Store); retainer_first({ets, Store}) -> ?ets_first(Store); retainer_first({dets, Store}) -> dets:first(Store). +retainer_next({{ext, Alias, Mod}, Store}, Key) -> + Mod:next(Alias, Store, Key); retainer_next({ets, Store}, Key) -> ?ets_next(Store, Key); retainer_next({dets, Store}, Key) -> dets:next(Store, Key). @@ -761,11 +779,16 @@ retainer_next({dets, Store}, Key) -> dets:next(Store, Key). retainer_fixtable(Tab, Bool) when is_atom(Tab) -> mnesia_lib:db_fixtable(val({Tab, storage_type}), Tab, Bool); +retainer_fixtable({Ext = {ext, _, _}, Tab}, Bool) -> + mnesia_lib:db_fixtable(Ext, Tab, Bool); retainer_fixtable({ets, Tab}, Bool) -> mnesia_lib:db_fixtable(ram_copies, Tab, Bool); retainer_fixtable({dets, Tab}, Bool) -> mnesia_lib:db_fixtable(disc_only_copies, Tab, Bool). +retainer_delete({{ext, Alias, Mod}, Store}) -> + Mod:close_table(Alias, Store), + Mod:delete_table(Alias, Store); retainer_delete({ets, Store}) -> ?ets_delete_table(Store); retainer_delete({dets, Store}) -> @@ -826,7 +849,7 @@ retainer_loop(Cp = #checkpoint_args{is_activated=false, name=Name}) -> retainer_loop(Cp#checkpoint_args{iterators = Iters}); {system, From, Msg} -> - dbg_out("~p got {system, ~p, ~p}~n", [?MODULE, From, Msg]), + dbg_out("~p got {system, ~p, ~tp}~n", [?MODULE, From, Msg]), sys:handle_system_msg(Msg, From, Cp#checkpoint_args.supervisor, ?MODULE, [], Cp) end; @@ -834,9 +857,9 @@ retainer_loop(Cp = #checkpoint_args{is_activated=false, name=Name}) -> retainer_loop(Cp = #checkpoint_args{name=Name}) -> receive {_From, {retain, Tid, Tab, Key, OldRecs}} -> - R = val({Tab, {retainer, Name}}), + R = ?catch_val({Tab, {retainer, Name}}), PendingTab = Cp#checkpoint_args.pending_tab, - case R#retainer.really_retain of + case is_record(R, retainer) andalso R#retainer.really_retain of true -> Store = R#retainer.store, try true = ets:member(PendingTab, Tid), @@ -886,7 +909,7 @@ retainer_loop(Cp = #checkpoint_args{name=Name}) -> retainer_loop(Cp2); {From, {iter_end, Iter}} -> - retainer_fixtable(Iter#iter.oid_tab, false), + ?SAFE(retainer_fixtable(Iter#iter.oid_tab, false)), Iters = Cp#checkpoint_args.iterators -- [Iter], reply(From, Name, ok), retainer_loop(Cp#checkpoint_args{iterators = Iters}); @@ -915,11 +938,11 @@ retainer_loop(Cp = #checkpoint_args{name=Name}) -> retainer_loop(Cp#checkpoint_args{iterators = Iters}); {system, From, Msg} -> - dbg_out("~p got {system, ~p, ~p}~n", [?MODULE, From, Msg]), + dbg_out("~p got {system, ~p, ~tp}~n", [?MODULE, From, Msg]), sys:handle_system_msg(Msg, From, Cp#checkpoint_args.supervisor, ?MODULE, [], Cp); Msg -> - dbg_out("~p got ~p~n", [?MODULE, Msg]) + dbg_out("~p got ~tp~n", [?MODULE, Msg]) end. maybe_activate(Cp) @@ -948,7 +971,8 @@ do_stop(Cp) -> unset({checkpoint, Name}), lists:foreach(fun deactivate_tab/1, Cp#checkpoint_args.retainers), Iters = Cp#checkpoint_args.iterators, - lists:foreach(fun(I) -> retainer_fixtable(I#iter.oid_tab, false) end, Iters). + [?SAFE(retainer_fixtable(Tab, false)) || #iter{main_tab=Tab} <- Iters], + ok. deactivate_tab(R) -> Name = R#retainer.cp_name, @@ -1128,7 +1152,7 @@ do_change_copy(Cp, Tab, FromType, ToType) -> Cp#checkpoint_args{retainers = Rs, nodes = writers(Rs)}. check_iter(From, Iter) when Iter#iter.pid == From -> - retainer_fixtable(Iter#iter.oid_tab, false), + ?SAFE(retainer_fixtable(Iter#iter.oid_tab, false)), false; check_iter(_From, _Iter) -> true. diff --git a/lib/mnesia/src/mnesia_checkpoint_sup.erl b/lib/mnesia/src/mnesia_checkpoint_sup.erl index 680cd86182..ae3f77f37e 100644 --- a/lib/mnesia/src/mnesia_checkpoint_sup.erl +++ b/lib/mnesia/src/mnesia_checkpoint_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2009. All Rights Reserved. +%% Copyright Ericsson AB 1997-2016. 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. diff --git a/lib/mnesia/src/mnesia_controller.erl b/lib/mnesia/src/mnesia_controller.erl index 02c175760e..77013489b3 100644 --- a/lib/mnesia/src/mnesia_controller.erl +++ b/lib/mnesia/src/mnesia_controller.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2014. All Rights Reserved. +%% Copyright Ericsson AB 1996-2017. 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. @@ -78,7 +78,7 @@ change_table_majority/1, del_active_replica/2, wait_for_tables/2, - get_network_copy/2, + get_network_copy/3, merge_schema/0, start_remote_sender/4, schedule_late_disc_load/2 @@ -329,14 +329,14 @@ release_schema_commit_lock() -> unlink(whereis(?SERVER_NAME)). %% Special for preparation of add table copy -get_network_copy(Tab, Cs) -> +get_network_copy(Tid, Tab, Cs) -> % We can't let the controller queue this one % because that may cause a deadlock between schema_operations % and initial tableloadings which both takes schema locks. % But we have to get copier_done msgs when the other side % goes down. call({add_other, self()}), - Reason = {dumper,add_table_copy}, + Reason = {dumper,{add_table_copy, Tid}}, Work = #net_load{table = Tab,reason = Reason,cstruct = Cs}, %% I'll need this cause it's linked trough the subscriber %% might be solved by using monitor in subscr instead. @@ -385,6 +385,8 @@ force_load_table(Tab) when is_atom(Tab), Tab /= schema -> do_force_load_table(Tab); disc_only_copies -> do_force_load_table(Tab); + {ext, _, _} -> + do_force_load_table(Tab); unknown -> set({Tab, load_by_force}, true), cast({force_load_updated, Tab}), @@ -444,7 +446,7 @@ try_schedule_late_disc_load(Tabs, Reason, MsgTag) -> [BadNodes]), try_schedule_late_disc_load(Tabs, Reason, MsgTag); {aborted, AbortReason} -> - fatal("Cannot late_load_tables~p: ~p~n", + fatal("Cannot late_load_tables ~tp: ~tp~n", [[Tabs, Reason, MsgTag], AbortReason]) end. @@ -533,7 +535,7 @@ try_merge_schema(Nodes, Told0, UserFun) -> end, try_merge_schema(Nodes, Told, UserFun); {atomic, {"Cannot get cstructs", Node, Reason}} -> - dbg_out("Cannot get cstructs, Node ~p ~p~n", [Node, Reason]), + dbg_out("Cannot get cstructs, Node ~p ~tp~n", [Node, Reason]), timer:sleep(300), % Avoid a endless loop look alike try_merge_schema(Nodes, Told0, UserFun); {aborted, {shutdown, _}} -> %% One of the nodes is going down @@ -775,7 +777,7 @@ handle_call({net_load, Tab, Cs}, From, State) -> true -> Worker = #net_load{table = Tab, opt_reply_to = From, - reason = {dumper,add_table_copy}, + reason = {dumper,{add_table_copy, unknown}}, cstruct = Cs }, add_worker(Worker, State); @@ -824,12 +826,12 @@ handle_call({del_other, Who}, _From, State = #state{others=Others0}) -> {reply, ok, State#state{others=Others}}; handle_call(Msg, _From, State) -> - error("~p got unexpected call: ~p~n", [?SERVER_NAME, Msg]), + error("~p got unexpected call: ~tp~n", [?SERVER_NAME, Msg]), noreply(State). late_disc_load(TabsR, Reason, RemoteLoaders, From, State = #state{loader_queue = LQ, late_loader_queue = LLQ}) -> - verbose("Intend to load tables: ~p~n", [TabsR]), + verbose("Intend to load tables: ~tp~n", [TabsR]), ?eval_debug_fun({?MODULE, late_disc_load}, [{tabs, TabsR}, {reason, Reason}, @@ -1116,7 +1118,7 @@ handle_cast({adopt_orphans, Node, Tabs}, State) -> noreply(State2); handle_cast(Msg, State) -> - error("~p got unexpected cast: ~p~n", [?SERVER_NAME, Msg]), + error("~p got unexpected cast: ~tp~n", [?SERVER_NAME, Msg]), noreply(State). handle_sync_tabs([Tab | Tabs], From) -> @@ -1164,7 +1166,7 @@ handle_info(#dumper_done{worker_pid=Pid, worker_res=Res}, State) -> State3 = opt_start_worker(State2), noreply(State3); true -> - fatal("Dumper failed: ~p~n state: ~p~n", [Res, State]), + fatal("Dumper failed: ~p~n state: ~tp~n", [Res, State]), {stop, fatal, State} end; @@ -1180,11 +1182,11 @@ handle_info(Done = #loader_done{worker_pid=WPid, table_name=Tab}, State0) -> Done#loader_done.needs_announce == true, Done#loader_done.needs_reply == true -> i_have_tab(Tab), - %% Should be {dumper,add_table_copy} only + %% Should be {dumper,{add_table_copy, _}} only reply(Done#loader_done.reply_to, Done#loader_done.reply); Done#loader_done.needs_reply == true -> - %% Should be {dumper,add_table_copy} only + %% Should be {dumper,{add_table_copy,_}} only reply(Done#loader_done.reply_to, Done#loader_done.reply); Done#loader_done.needs_announce == true, Tab == schema -> @@ -1247,7 +1249,7 @@ handle_info(#sender_done{worker_pid=Pid, worker_res=Res}, State) -> true -> %% No need to send any message to the table receiver %% since it will soon get a mnesia_down anyway - fatal("Sender failed: ~p~n state: ~p~n", [Res, State]), + fatal("Sender failed: ~p~n state: ~tp~n", [Res, State]), {stop, fatal, State} end; @@ -1255,7 +1257,7 @@ handle_info({'EXIT', Pid, R}, State) when Pid == State#state.supervisor -> ?SAFE(set(mnesia_status, stopping)), case State#state.dumper_pid of undefined -> - dbg_out("~p was ~p~n", [?SERVER_NAME, R]), + dbg_out("~p was ~tp~n", [?SERVER_NAME, R]), {stop, shutdown, State}; _ -> noreply(State#state{is_stopping = true}) @@ -1264,12 +1266,12 @@ handle_info({'EXIT', Pid, R}, State) when Pid == State#state.supervisor -> handle_info({'EXIT', Pid, R}, State) when Pid == State#state.dumper_pid -> case State#state.dumper_queue of [#schema_commit_lock{}|Workers] -> %% Schema trans crashed or was killed - dbg_out("WARNING: Dumper ~p exited ~p~n", [Pid, R]), + dbg_out("WARNING: Dumper ~p exited ~tp~n", [Pid, R]), State2 = State#state{dumper_queue = Workers, dumper_pid = undefined}, State3 = opt_start_worker(State2), noreply(State3); _Other -> - fatal("Dumper or schema commit crashed: ~p~n state: ~p~n", [R, State]), + fatal("Dumper or schema commit crashed: ~p~n state: ~tp~n", [R, State]), {stop, fatal, State} end; @@ -1278,15 +1280,15 @@ handle_info(Msg = {'EXIT', Pid, R}, State) when R /= wait_for_tables_timeout -> true -> %% No need to send any message to the table receiver %% since it will soon get a mnesia_down anyway - fatal("Sender crashed: ~p~n state: ~p~n", [{Pid,R}, State]), + fatal("Sender crashed: ~p~n state: ~tp~n", [{Pid,R}, State]), {stop, fatal, State}; false -> case lists:keymember(Pid, 1, get_loaders(State)) of true -> - fatal("Loader crashed: ~p~n state: ~p~n", [R, State]), + fatal("Loader crashed: ~p~n state: ~tp~n", [R, State]), {stop, fatal, State}; false -> - error("~p got unexpected info: ~p~n", [?SERVER_NAME, Msg]), + error("~p got unexpected info: ~tp~n", [?SERVER_NAME, Msg]), noreply(State) end end; @@ -1306,7 +1308,7 @@ handle_info({'EXIT', Pid, wait_for_tables_timeout}, State) -> noreply(State); handle_info(Msg, State) -> - error("~p got unexpected info: ~p~n", [?SERVER_NAME, Msg]), + error("~p got unexpected info: ~tp~n", [?SERVER_NAME, Msg]), noreply(State). sync_tab_timeout(Pid, [{{sync_tab, Tab}, Pids} | Tail]) -> @@ -1533,8 +1535,8 @@ update_whereabouts(Tab, Node, State) -> Storage == unknown -> %% No own copy, continue to read remotely add_active_replica(Tab, Node), - NodeST = mnesia_lib:storage_type_at_node(Node, Tab), - ReadST = mnesia_lib:storage_type_at_node(Read, Tab), + NodeST = mnesia_lib:semantics(mnesia_lib:storage_type_at_node(Node, Tab), storage), + ReadST = mnesia_lib:semantics(mnesia_lib:storage_type_at_node(Read, Tab), storage), if %% Avoid reading from disc_only_copies NodeST == disc_only_copies -> ignore; @@ -1588,6 +1590,7 @@ last_consistent_replica(Tab, Downs) -> Ram = Cs#cstruct.ram_copies, Disc = Cs#cstruct.disc_copies, DiscOnly = Cs#cstruct.disc_only_copies, + Ext = Cs#cstruct.external_copies, BetterCopies0 = mnesia_lib:remote_copy_holders(Cs) -- Downs, BetterCopies = BetterCopies0 -- Ram, AccessMode = Cs#cstruct.access_mode, @@ -1620,7 +1623,7 @@ last_consistent_replica(Tab, Downs) -> false; Storage == ram_copies -> if - Disc == [], DiscOnly == [] -> + Disc == [], DiscOnly == [], Ext == [] -> %% Nobody has copy on disc {true, {Tab, ram_only}}; true -> @@ -1700,9 +1703,10 @@ add_active_replica(Tab, Node, Cs = #cstruct{}) -> block_table(Tab) -> Var = {Tab, where_to_commit}, - Old = val(Var), - New = {blocked, Old}, - set(Var, New). % where_to_commit + case is_tab_blocked(val(Var)) of + {true, _} -> ok; + {false, W2C} -> set(Var, mark_blocked_tab(true, W2C)) + end. unblock_table(Tab) -> call({unblock_table, Tab}). @@ -1865,6 +1869,11 @@ info([Tab | Tail]) -> dets:info(Tab, size), dets:info(Tab, file_size), "bytes on disc"); + {ext, Alias, Mod} -> + info_format(Tab, + Mod:info(Alias, Tab, size), + Mod:info(Alias, Tab, memory), + "words of mem"); _ -> info_format(Tab, ?ets_info(Tab, size), @@ -1879,7 +1888,7 @@ info_format(Tab, Size, Mem, Media) -> StrT = mnesia_lib:pad_name(atom_to_list(Tab), 15, []), StrS = mnesia_lib:pad_name(integer_to_list(Size), 8, []), StrM = mnesia_lib:pad_name(integer_to_list(Mem), 8, []), - io:format("~s: with ~s records occupying ~s ~s~n", + io:format("~ts: with ~s records occupying ~s ~s~n", [StrT, StrS, StrM, Media]). %% Handle early arrived messages @@ -2045,7 +2054,7 @@ opt_start_sender2([Sender|R], Pids, Kept, LoaderQ) -> Pid = spawn_link(?MODULE, send_and_reply,[self(), Sender]), opt_start_sender2(R,[{Pid,Sender}|Pids],Kept,LoaderQ); true -> - verbose("Send table failed ~p not active on this node ~n", [Tab]), + verbose("Send table failed ~tp not active on this node ~n", [Tab]), Sender#send_table.receiver_pid ! {copier_done, node()}, opt_start_sender2(R,Pids, Kept, LoaderQ) end. @@ -2148,6 +2157,10 @@ load_table_fun(#net_load{cstruct=Cs, table=Tab, reason=Reason, opt_reply_to=Repl reply_to = ReplyTo, reply = {loaded, ok} }, + AddTableCopy = case Reason of + {dumper,{add_table_copy,_}} -> true; + _ -> false + end, if ReadNode == node() -> %% Already loaded locally @@ -2157,7 +2170,7 @@ load_table_fun(#net_load{cstruct=Cs, table=Tab, reason=Reason, opt_reply_to=Repl Res = mnesia_loader:disc_load_table(Tab, load_local_content), Done#loader_done{reply = Res, needs_announce = true, needs_sync = true} end; - AccessMode == read_only, Reason /= {dumper,add_table_copy} -> + AccessMode == read_only, not AddTableCopy -> fun() -> disc_load_table(Tab, Reason, ReplyTo) end; true -> fun() -> @@ -2226,7 +2239,7 @@ disc_load_table(Tab, Reason, ReplyTo) -> Done#loader_done{is_loaded = false, reply = Res}; true -> - fatal("Cannot load table ~p from disc: ~p~n", [Tab, Res]) + fatal("Cannot load table ~tp from disc: ~tp~n", [Tab, Res]) end. filter_active(Tab) -> diff --git a/lib/mnesia/src/mnesia_dumper.erl b/lib/mnesia/src/mnesia_dumper.erl index 0e954d869d..f0ed7aef4a 100644 --- a/lib/mnesia/src/mnesia_dumper.erl +++ b/lib/mnesia/src/mnesia_dumper.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2014. All Rights Reserved. +%% Copyright Ericsson AB 1996-2016. 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. @@ -38,6 +38,8 @@ needs_dump_ets/1, raw_dump_table/2, raw_named_dump_table/2, + dump_to_logfile/2, + load_from_logfile/3, start_regulator/0, opt_dump_log/1, update/3, @@ -191,7 +193,7 @@ do_perform_dump(Cont, InPlace, InitBy, Regulator, OldVersion) -> do_perform_dump(C2, InPlace, InitBy, Regulator, Version) catch _:R when R =/= fatal -> ST = erlang:get_stacktrace(), - Reason = {"Transaction log dump error: ~p~n", [{R, ST}]}, + Reason = {"Transaction log dump error: ~tp~n", [{R, ST}]}, close_files(InPlace, {error, Reason}, InitBy), exit(Reason) end; @@ -227,11 +229,11 @@ insert_rec(Rec, InPlace, InitBy, LogV) when is_record(Rec, commit) -> D = Rec#commit.decision, case mnesia_recover:wait_for_decision(D, InitBy) of {Tid, committed} -> - do_insert_rec(Tid, Rec, InPlace, InitBy, LogV); + do_insert_rec(Tid, mnesia_tm:new_cr_format(Rec), InPlace, InitBy, LogV); {Tid, aborted} -> case InitBy of startup -> - mnesia_schema:undo_prepare_commit(Tid, Rec); + mnesia_schema:undo_prepare_commit(Tid, mnesia_tm:new_cr_format(Rec)); _ -> ok end @@ -271,15 +273,30 @@ do_insert_rec(Tid, Rec, InPlace, InitBy, LogV) -> end end, D = Rec#commit.disc_copies, + ExtOps = commit_ext(Rec), insert_ops(Tid, disc_copies, D, InPlace, InitBy, LogV), + [insert_ops(Tid, Ext, Ops, InPlace, InitBy, LogV) || + {Ext, Ops} <- ExtOps, + storage_semantics(Ext) == disc_copies], case InitBy of startup -> DO = Rec#commit.disc_only_copies, - insert_ops(Tid, disc_only_copies, DO, InPlace, InitBy, LogV); + insert_ops(Tid, disc_only_copies, DO, InPlace, InitBy, LogV), + [insert_ops(Tid, Ext, Ops, InPlace, InitBy, LogV) || + {Ext, Ops} <- ExtOps, storage_semantics(Ext) == disc_only_copies]; _ -> ignore end. +commit_ext(#commit{ext = []}) -> []; +commit_ext(#commit{ext = Ext}) -> + case lists:keyfind(ext_copies, 1, Ext) of + {_, C} -> + lists:foldl(fun({Ext0, Op}, D) -> + orddict:append(Ext0, Op, D) + end, orddict:new(), C); + false -> [] + end. update(_Tid, [], _DumperMode) -> dumped; @@ -312,7 +329,7 @@ perform_update(Tid, SchemaOps, _DumperMode, _UseDir) -> ST = erlang:get_stacktrace(), Error = {error, {"Schema update error", {Reason, ST}}}, close_files(InPlace, Error, InitBy), - fatal("Schema update error ~p ~p", [{Reason,ST}, SchemaOps]) + fatal("Schema update error ~tp ~tp", [{Reason,ST}, SchemaOps]) end. insert_ops(_Tid, _Storage, [], _InPlace, _InitBy, _) -> ok; @@ -330,14 +347,15 @@ insert_ops(Tid, Storage, [Op | Ops], InPlace, InitBy, Ver) when Ver < "4.3" -> %% Normal ops disc_insert(_Tid, Storage, Tab, Key, Val, Op, InPlace, InitBy) -> - case open_files(Tab, Storage, InPlace, InitBy) of + Semantics = storage_semantics(Storage), + case open_files(Tab, Semantics, Storage, InPlace, InitBy) of true -> - case Storage of + case Semantics of disc_copies when Tab /= schema -> mnesia_log:append({?MODULE,Tab}, {{Tab, Key}, Val, Op}), ok; _ -> - dets_insert(Op,Tab,Key,Val) + dets_insert(Op,Tab,Key,Val,Storage) end; false -> ignore @@ -349,34 +367,37 @@ disc_insert(_Tid, Storage, Tab, Key, Val, Op, InPlace, InitBy) -> %% Otherwise we will get a double increment. %% This is perfect but update_counter is a dirty op. -dets_insert(Op,Tab,Key,Val) -> +dets_insert(Op,Tab,Key,Val, Storage0) -> + Storage = if Tab == schema -> disc_only_copies; + true -> Storage0 + end, case Op of write -> dets_updated(Tab,Key), - ok = dets:insert(Tab, Val); + ok = mnesia_lib:db_put(Storage, Tab, Val); delete -> dets_updated(Tab,Key), - ok = dets:delete(Tab, Key); + ok = mnesia_lib:db_erase(Storage, Tab, Key); update_counter -> case dets_incr_counter(Tab,Key) of true -> {RecName, Incr} = Val, - try _ = dets:update_counter(Tab, Key, Incr) + try _ = mnesia_lib:db_update_counter(Storage, Tab, Key, Incr) catch error:_ when Incr < 0 -> Zero = {RecName, Key, 0}, - ok = dets:insert(Tab, Zero); + ok = mnesia_lib:db_put(Storage, Tab, Zero); error:_ -> Init = {RecName, Key, Incr}, - ok = dets:insert(Tab, Init) + ok = mnesia_lib:db_put(Storage, Tab, Init) end; false -> ok end; delete_object -> dets_updated(Tab,Key), - ok = dets:delete_object(Tab, Val); + mnesia_lib:db_match_erase(Storage, Tab, Val); clear_table -> dets_cleared(Tab), - ok = dets:delete_all_objects(Tab) + ok = mnesia_lib:db_match_erase(Storage, Tab, '_') end. dets_updated(Tab,Key) -> @@ -431,23 +452,29 @@ insert(_Tid, _Storage, _Tab, _Key, [], _Op, _InPlace, _InitBy) -> ok; insert(Tid, Storage, Tab, Key, Val, Op, InPlace, InitBy) -> + Semantics = storage_semantics(Storage), Item = {{Tab, Key}, Val, Op}, case InitBy of startup -> disc_insert(Tid, Storage, Tab, Key, Val, Op, InPlace, InitBy); - _ when Storage == ram_copies -> + _ when Semantics == ram_copies -> mnesia_tm:do_update_op(Tid, Storage, Item), Snmp = mnesia_tm:prepare_snmp(Tab, Key, [Item]), mnesia_tm:do_snmp(Tid, Snmp); - _ when Storage == disc_copies -> + _ when Semantics == disc_copies -> disc_insert(Tid, Storage, Tab, Key, Val, Op, InPlace, InitBy), mnesia_tm:do_update_op(Tid, Storage, Item), Snmp = mnesia_tm:prepare_snmp(Tab, Key, [Item]), mnesia_tm:do_snmp(Tid, Snmp); - _ when Storage == disc_only_copies -> + _ when Semantics == disc_only_copies -> + mnesia_tm:do_update_op(Tid, Storage, Item), + Snmp = mnesia_tm:prepare_snmp(Tab, Key, [Item]), + mnesia_tm:do_snmp(Tid, Snmp); + + _ when element(1, Storage) == ext -> mnesia_tm:do_update_op(Tid, Storage, Item), Snmp = mnesia_tm:prepare_snmp(Tab, Key, [Item]), mnesia_tm:do_snmp(Tid, Snmp); @@ -456,6 +483,9 @@ insert(Tid, Storage, Tab, Key, Val, Op, InPlace, InitBy) -> ignore end. +disc_delete_table(Tab, {ext, Alias, Mod}) -> + Mod:close_table(Alias, Tab), + Mod:delete_table(Alias, Tab); disc_delete_table(Tab, Storage) -> case mnesia_monitor:use_dir() of true -> @@ -485,11 +515,14 @@ disc_delete_table(Tab, Storage) -> ok end. -disc_delete_indecies(_Tab, _Cs, Storage) when Storage /= disc_only_copies -> - ok; -disc_delete_indecies(Tab, Cs, disc_only_copies) -> - Indecies = Cs#cstruct.index, - mnesia_index:del_transient(Tab, Indecies, disc_only_copies). +disc_delete_indecies(Tab, Cs, Storage) -> + case storage_semantics(Storage) of + disc_only_copies -> + Indecies = Cs#cstruct.index, + mnesia_index:del_transient(Tab, Indecies, Storage); + _ -> + ok + end. insert_op(Tid, Storage, {{Tab, Key}, Val, Op}, InPlace, InitBy) -> %% Propagate to disc only @@ -515,6 +548,8 @@ insert_op(Tid, _, {op, change_table_copy_type, N, FromS, ToS, TabDef}, InPlace, Cs = mnesia_schema:list2cs(TabDef), Val = mnesia_schema:insert_cstruct(Tid, Cs, true), % Update ram only {schema, Tab, _} = Val, + FromSem = storage_semantics(FromS), + ToSem = storage_semantics(ToS), case lists:member(N, val({current, db_nodes})) of true when InitBy /= startup -> mnesia_controller:add_active_replica(Tab, N, Cs); @@ -527,62 +562,118 @@ insert_op(Tid, _, {op, change_table_copy_type, N, FromS, ToS, TabDef}, InPlace, Dat = mnesia_lib:tab2dat(Tab), Dcd = mnesia_lib:tab2dcd(Tab), Dcl = mnesia_lib:tab2dcl(Tab), + Logtmp = mnesia_lib:tab2logtmp(Tab), case {FromS, ToS} of - {ram_copies, disc_copies} when Tab == schema -> - ok = ensure_rename(Dmp, Dat); - {ram_copies, disc_copies} -> - file:delete(Dcl), - ok = ensure_rename(Dmp, Dcd); - {disc_copies, ram_copies} when Tab == schema -> - mnesia_lib:set(use_dir, false), - mnesia_monitor:unsafe_close_dets(Tab), - ok = file:delete(Dat); - {disc_copies, ram_copies} -> - _ = file:delete(Dcl), - _ = file:delete(Dcd), - ok; - {ram_copies, disc_only_copies} -> - ok = ensure_rename(Dmp, Dat), - true = open_files(Tab, disc_only_copies, InPlace, InitBy), - %% ram_delete_table must be done before init_indecies, - %% it uses info which is reset in init_indecies, - %% it doesn't matter, because init_indecies don't use - %% the ram replica of the table when creating the disc - %% index; Could be improved :) - mnesia_schema:ram_delete_table(Tab, FromS), - PosList = Cs#cstruct.index, - mnesia_index:init_indecies(Tab, disc_only_copies, PosList); - {disc_only_copies, ram_copies} -> - mnesia_monitor:unsafe_close_dets(Tab), - disc_delete_indecies(Tab, Cs, disc_only_copies), - case InitBy of - startup -> - ignore; + {{ext,_FromAlias,_FromMod},{ext,ToAlias,ToMod}} -> + disc_delete_table(Tab, FromS), + ok = ToMod:delete_table(ToAlias, Tab), + ok = mnesia_monitor:unsafe_create_external(Tab, ToAlias, ToMod, TabDef), + ok = ToMod:load_table(ToAlias, Tab, {dumper,change_table_copy_type}, TabDef), + ok = load_from_logfile(ToS, Tab, Logtmp), + file:delete(Logtmp), + restore_indexes(Tab, ToS, Cs); + + {_,{ext,ToAlias,ToMod}} -> + case FromSem of + ram_copies -> + mnesia_schema:ram_delete_table(Tab, FromS); _ -> - mnesia_controller:get_disc_copy(Tab), - ok + if FromSem == disc_copies -> + mnesia_schema:ram_delete_table( + Tab, FromS); + true -> ok + end, + disc_delete_table(Tab, FromS) end, - disc_delete_table(Tab, disc_only_copies); - {disc_copies, disc_only_copies} -> - ok = ensure_rename(Dmp, Dat), - true = open_files(Tab, disc_only_copies, InPlace, InitBy), - mnesia_schema:ram_delete_table(Tab, FromS), - PosList = Cs#cstruct.index, - mnesia_index:init_indecies(Tab, disc_only_copies, PosList), - _ = file:delete(Dcl), - _ = file:delete(Dcd), - ok; - {disc_only_copies, disc_copies} -> - mnesia_monitor:unsafe_close_dets(Tab), - disc_delete_indecies(Tab, Cs, disc_only_copies), - case InitBy of - startup -> - ignore; - _ -> - mnesia_log:ets2dcd(Tab), - mnesia_controller:get_disc_copy(Tab), - disc_delete_table(Tab, disc_only_copies) - end + + ok = ToMod:delete_table(ToAlias, Tab), + ok = mnesia_monitor:unsafe_create_external(Tab, ToAlias, ToMod, TabDef), + ok = ToMod:load_table(ToAlias, Tab, {dumper,change_table_copy_type}, TabDef), + ok = load_from_logfile(ToS, Tab, Logtmp), + file:delete(Logtmp), + restore_indexes(Tab, ToS, Cs); + + {{ext,_FromAlias,_FromMod} = FromS, ToS} -> + disc_delete_table(Tab, FromS), + case ToS of + ram_copies -> + change_disc_to_ram( + Tab, Cs, FromS, ToS, Logtmp, InitBy); + disc_copies -> + Args = [{keypos, 2}, public, named_table, + Cs#cstruct.type], + mnesia_monitor:mktab(Tab, Args), + ok = load_from_logfile(ToS, Tab, Logtmp), + file:delete(Logtmp); + disc_only_copies -> + %% ok = ensure_rename(Dmp, Dat), + true = open_files(Tab, ToS, InPlace, InitBy), + ok = load_from_logfile(ToS, Tab, Logtmp), + file:delete(Logtmp) + end, + restore_indexes(Tab, ToS, Cs); + + _NoneAreExt -> + + case {FromSem, ToSem} of + {ram_copies, disc_copies} when Tab == schema -> + ok = ensure_rename(Dmp, Dat); + {ram_copies, disc_copies} -> + file:delete(Dcl), + ok = ensure_rename(Dmp, Dcd); + {disc_copies, ram_copies} when Tab == schema -> + mnesia_lib:set(use_dir, false), + mnesia_monitor:unsafe_close_dets(Tab), + ok = file:delete(Dat); + {disc_copies, ram_copies} -> + _ = file:delete(Dcl), + _ = file:delete(Dcd), + ok; + {ram_copies, disc_only_copies} -> + ok = ensure_rename(Dmp, Dat), + true = open_files(Tab, ToS, InPlace, InitBy), + %% ram_delete_table must be done before + %% init_indecies, it uses info which is reset + %% in init_indecies, it doesn't matter, because + %% init_indecies don't use the ram replica of + %% the table when creating the disc index; + %% Could be improved :) + mnesia_schema:ram_delete_table(Tab, FromS), + restore_indexes(Tab, ToS, Cs); + {disc_only_copies, ram_copies} when FromS == disc_only_copies -> + mnesia_monitor:unsafe_close_dets(Tab), + disc_delete_indecies(Tab, Cs, FromS), + case InitBy of + startup -> + ignore; + _ -> + mnesia_controller:get_disc_copy(Tab), + ok + end, + disc_delete_table(Tab, FromS); + {disc_only_copies, ram_copies} when element(1, FromS) == ext -> + change_disc_to_ram( + Tab, Cs, FromS, ToS, Logtmp, InitBy); + {disc_copies, disc_only_copies} -> + ok = ensure_rename(Dmp, Dat), + true = open_files(Tab, ToS, InPlace, InitBy), + mnesia_schema:ram_delete_table(Tab, FromS), + restore_indexes(Tab, ToS, Cs), + _ = file:delete(Dcl), + _ = file:delete(Dcd), + ok; + {disc_only_copies, disc_copies} -> + mnesia_monitor:unsafe_close_dets(Tab), + disc_delete_indecies(Tab, Cs, disc_only_copies), + case InitBy of + startup -> + ignore; + _ -> + mnesia_log:ets2dcd(Tab), + mnesia_controller:get_disc_copy(Tab), + disc_delete_table(Tab, disc_only_copies) + end + end end; true -> ignore @@ -607,6 +698,9 @@ insert_op(Tid, _, {op, restore_recreate, TabDef}, InPlace, InitBy) -> Tab = Cs#cstruct.name, Type = Cs#cstruct.type, Storage = mnesia_lib:cs_to_storage_type(node(), Cs), + Semantics = if Storage==unknown -> unknown; + true -> storage_semantics(Storage) + end, %% Delete all possibly existing files and tables disc_delete_table(Tab, Storage), disc_delete_indecies(Tab, Cs, Storage), @@ -625,13 +719,13 @@ insert_op(Tid, _, {op, restore_recreate, TabDef}, InPlace, InitBy) -> %% And create new ones.. if - (InitBy == startup) or (Storage == unknown) -> + (InitBy == startup) or (Semantics == unknown) -> ignore; - Storage == ram_copies -> + Semantics == ram_copies -> EtsProps = proplists:get_value(ets, StorageProps, []), Args = [{keypos, 2}, public, named_table, Type | EtsProps], mnesia_monitor:mktab(Tab, Args); - Storage == disc_copies -> + Semantics == disc_copies -> EtsProps = proplists:get_value(ets, StorageProps, []), Args = [{keypos, 2}, public, named_table, Type | EtsProps], mnesia_monitor:mktab(Tab, Args), @@ -640,7 +734,7 @@ insert_op(Tid, _, {op, restore_recreate, TabDef}, InPlace, InitBy) -> {repair, false}, {mode, read_write}], {ok, Log} = mnesia_monitor:open_log(FArg), mnesia_monitor:unsafe_close_log(Log); - Storage == disc_only_copies -> + Storage == disc_only_copies -> % note: Storage, not Semantics File = mnesia_lib:tab2dat(Tab), file:delete(File), DetsProps = proplists:get_value(dets, StorageProps, []), @@ -649,7 +743,10 @@ insert_op(Tid, _, {op, restore_recreate, TabDef}, InPlace, InitBy) -> {keypos, 2}, {repair, mnesia_monitor:get_env(auto_repair)} | DetsProps ], - mnesia_monitor:open_dets(Tab, Args) + mnesia_monitor:open_dets(Tab, Args); + element(1,Storage) == ext -> + {ext, Alias, Mod} = Storage, + Mod:create_table(Alias, Tab, []) end, insert_op(Tid, ignore, {op, create_table, TabDef}, InPlace, InitBy); @@ -659,9 +756,10 @@ insert_op(Tid, _, {op, create_table, TabDef}, InPlace, InitBy) -> Tab = Cs#cstruct.name, Storage = mnesia_lib:cs_to_storage_type(node(), Cs), StorageProps = Cs#cstruct.storage_properties, + Semantics = storage_semantics(Storage), case InitBy of startup -> - case Storage of + case Semantics of unknown -> ignore; ram_copies -> @@ -679,20 +777,10 @@ insert_op(Tid, _, {op, create_table, TabDef}, InPlace, InitBy) -> read_write), mnesia_log:unsafe_close_log(temp) end; - _ -> + disc_only_copies -> DetsProps = proplists:get_value(dets, StorageProps, []), - Args = [{file, mnesia_lib:tab2dat(Tab)}, - {type, mnesia_lib:disk_type(Tab, Cs#cstruct.type)}, - {keypos, 2}, - {repair, mnesia_monitor:get_env(auto_repair)} - | DetsProps ], - case mnesia_monitor:open_dets(Tab, Args) of - {ok, _} -> - mnesia_monitor:unsafe_close_dets(Tab); - {error, Error} -> - exit({"Failed to create dets table", Error}) - end + try_create_disc_only_copy(Storage, Tab, Cs, DetsProps) end; _ -> Copies = mnesia_lib:copy_holders(Cs), @@ -715,7 +803,7 @@ insert_op(Tid, _, {op, create_table, TabDef}, InPlace, InitBy) -> false -> mnesia_lib:set({Tab, where_to_read}, node()) end, - case Storage of + case Semantics of ram_copies -> ignore; _ -> @@ -838,7 +926,7 @@ insert_op(Tid, _, {op, del_table_copy, Storage, Node, TabDef}, InPlace, InitBy) insert_cstruct(Tid, Cs, true, InPlace, InitBy); Tab /= schema -> mnesia_controller:del_active_replica(Tab, Node), - mnesia_lib:del({Tab, Storage}, Node), + mnesia_lib:del({Tab, storage_alias(Storage)}, Node), if Node == node() -> case Cs#cstruct.local_content of @@ -896,9 +984,10 @@ insert_op(Tid, _, {op, add_index, Pos, TabDef}, InPlace, InitBy) -> Cs = mnesia_schema:list2cs(TabDef), Tab = insert_cstruct(Tid, Cs, true, InPlace, InitBy), Storage = mnesia_lib:cs_to_storage_type(node(), Cs), + Semantics = storage_semantics(Storage), case InitBy of - startup when Storage == disc_only_copies -> - true = open_files(Tab, Storage, InPlace, InitBy), + startup when Semantics == disc_only_copies -> + true = open_files(Tab, Semantics, Storage, InPlace, InitBy), mnesia_index:init_indecies(Tab, Storage, [Pos]); startup -> ignore; @@ -919,6 +1008,8 @@ insert_op(Tid, _, {op, del_index, Pos, TabDef}, InPlace, InitBy) -> mnesia_index:del_index_table(Tab, Storage, Pos); startup -> ignore; + _ when element(1, Storage) == ext -> + mnesia_index:del_index_table(Tab, Storage, Pos); _ -> mnesia_index:del_index_table(Tab, Storage, Pos) end, @@ -958,19 +1049,68 @@ insert_op(Tid, _, {op, change_table_frag, _Change, TabDef}, InPlace, InitBy) -> Cs = mnesia_schema:list2cs(TabDef), insert_cstruct(Tid, Cs, true, InPlace, InitBy). -open_files(Tab, Storage, UpdateInPlace, InitBy) - when Storage /= unknown, Storage /= ram_copies -> + +storage_semantics({ext, Alias, Mod}) -> + Mod:semantics(Alias, storage); +storage_semantics(Storage) when is_atom(Storage) -> + Storage. + +storage_alias({ext, Alias, _}) -> + Alias; +storage_alias(Storage) when is_atom(Storage) -> + Storage. + +change_disc_to_ram(Tab, Cs, FromS, ToS, Logtmp, InitBy) -> + disc_delete_indecies(Tab, Cs, FromS), + case InitBy of + startup -> + ignore; + _ -> + %% ram table will already have been created + Tab = ets:info(Tab, name), %% assertion + load_from_logfile(ToS, Tab, Logtmp), + PosList = Cs#cstruct.index, + mnesia_index:init_indecies(Tab, ToS, PosList) + end, + disc_delete_table(Tab, FromS). + + +try_create_disc_only_copy({ext,Alias,Mod}, Tab, Cs, _) -> + Mod:create_table(Alias, Tab, mnesia_schema:cs2list(Cs)); +try_create_disc_only_copy(disc_only_copies, Tab, Cs, DetsProps) -> + Args = [{file, mnesia_lib:tab2dat(Tab)}, + {type, mnesia_lib:disk_type(Tab, Cs#cstruct.type)}, + {keypos, 2}, + {repair, mnesia_monitor:get_env(auto_repair)} + | DetsProps], + case mnesia_monitor:open_dets(Tab, Args) of + {ok, _} -> + mnesia_monitor:unsafe_close_dets(Tab); + {error, Error} -> + exit({"Failed to create dets table", Error}) + end. + +restore_indexes(Tab, ToS, Cs) -> + PosList = Cs#cstruct.index, + mnesia_index:init_indecies(Tab, ToS, PosList). + + +open_files(Tab, Storage, UpdateInPlace, InitBy) -> + open_files(Tab, storage_semantics(Storage), Storage, UpdateInPlace, InitBy). + +open_files(Tab, Semantics, Storage, UpdateInPlace, InitBy) + when Storage /= unknown, Semantics /= ram_copies -> case get({?MODULE, Tab}) of undefined -> case ?catch_val({Tab, setorbag}) of {'EXIT', _} -> false; Type -> - case Storage of - disc_copies when Tab /= schema -> + Cs = val({Tab, cstruct}), + if Semantics == disc_copies, Tab /= schema -> Bool = open_disc_copies(Tab, InitBy), Bool; - _ -> + Storage == disc_only_copies; Tab == schema -> Props = val({Tab, storage_properties}), DetsProps = proplists:get_value(dets, Props, []), Fname = prepare_open(Tab, UpdateInPlace), @@ -981,6 +1121,12 @@ open_files(Tab, Storage, UpdateInPlace, InitBy) | DetsProps], {ok, _} = mnesia_monitor:open_dets(Tab, Args), put({?MODULE, Tab}, {opened_dumper, dat}), + true; + element(1, Storage) == ext -> + {ext, Alias, Mod} = Storage, + Mod:load_table(Alias, Tab, InitBy, + mnesia_schema:cs2list(Cs)), + put({?MODULE, Tab}, {opened_dumper, ext}), true end end; @@ -989,7 +1135,7 @@ open_files(Tab, Storage, UpdateInPlace, InitBy) {opened_dumper, _} -> true end; -open_files(_Tab, _Storage, _UpdateInPlace, _InitBy) -> +open_files(_Tab, _Semantics, _Storage, _UpdateInPlace, _InitBy) -> false. open_disc_copies(Tab, InitBy) -> @@ -1020,7 +1166,7 @@ needs_dump_ets(Tab) -> DcdF = mnesia_lib:tab2dcd(Tab), case file:read_file_info(DcdF) of {error, Reason} -> - mnesia_lib:dbg_out("File ~p info_error ~p ~n", + mnesia_lib:dbg_out("File ~tp info_error ~tp ~n", [DcdF, Reason]), true; {ok, DcdInfo} -> @@ -1059,7 +1205,7 @@ prepare_open(Tab, UpdateInPlace) -> Tmp = mnesia_lib:tab2tmp(Tab), try ok = mnesia_lib:copy_file(Dat, Tmp) catch error:Error -> - fatal("Cannot copy dets file ~p to ~p: ~p~n", + fatal("Cannot copy dets file ~tp to ~tp: ~tp~n", [Dat, Tmp, Error]) end, Tmp @@ -1076,12 +1222,13 @@ close_files(InPlace, Outcome, InitBy, [{{?MODULE, Tab}, already_dumped} | Tail]) close_files(InPlace, Outcome, InitBy, Tail); close_files(InPlace, Outcome, InitBy, [{{?MODULE, Tab}, {opened_dumper, Type}} | Tail]) -> erase({?MODULE, Tab}), - case val({Tab, storage_type}) of + Storage = val({Tab, storage_type}), + case storage_semantics(Storage) of disc_only_copies when InitBy /= startup -> ignore; - disc_copies when Tab /= schema -> + disc_copies when Storage /= unknown, Tab /= schema -> mnesia_log:close_log({?MODULE,Tab}); - Storage -> + _ -> do_close(InPlace, Outcome, Tab, Type, Storage) end, close_files(InPlace, Outcome, InitBy, Tail); @@ -1093,11 +1240,16 @@ close_files(_, _, _InitBy, []) -> %% If storage is unknown during close clean up files, this can happen if timing %% is right and dirty_write conflicts with schema operations. +do_close(_, _, Tab, ext, {ext,Alias,Mod}) -> + Mod:close_table(Alias, Tab); do_close(_, _, Tab, dcl, unknown) -> mnesia_log:close_log({?MODULE,Tab}), file:delete(mnesia_lib:tab2dcl(Tab)); do_close(_, _, Tab, dcl, _) -> %% To be safe, can it happen? mnesia_log:close_log({?MODULE,Tab}); +do_close(_, _, _Tab, ext, unknown) -> + %% Not sure what to do here, but let's not crash + ok; do_close(InPlace, Outcome, Tab, dat, Storage) -> mnesia_monitor:close_dets(Tab), @@ -1148,7 +1300,8 @@ temp_set_master_nodes() -> Tabs = val({schema, local_tables}), Masters = [{Tab, (val({Tab, disc_copies}) ++ val({Tab, ram_copies}) ++ - val({Tab, disc_only_copies})) -- [node()]} + val({Tab, disc_only_copies}) ++ + external_copies(Tab)) -- [node()]} || Tab <- Tabs], %% UseDir = false since we don't want to remember these %% masternodes and we are running (really soon anyway) since we want this @@ -1156,6 +1309,13 @@ temp_set_master_nodes() -> mnesia_recover:log_master_nodes(Masters, false, yes), ok. +external_copies(Tab) -> + case ?catch_val({Tab, external_copies}) of + {'EXIT',_} -> []; + Ext -> + lists:concat([Ns || {_, Ns} <- Ext]) + end. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Raw dump of table. Dumper must have unique access to the ets table. @@ -1207,6 +1367,59 @@ raw_named_dump_table(Tab, Ftype) -> raw_dump_table(DetsRef, EtsRef) -> dets:from_ets(DetsRef, EtsRef). +dump_to_logfile(Storage, Tab) -> + case mnesia_monitor:use_dir() of + true -> + Logtmp = mnesia_lib:tab2logtmp(Tab), + file:delete(Logtmp), + case disk_log:open([{name, make_ref()}, + {file, Logtmp}, + {repair, false}, + {linkto, self()}]) of + {ok, Fd} -> + mnesia_lib:db_fixtable(Storage, Tab, true), + try do_dump_to_logfile(Storage, Tab, Fd) + after + mnesia_lib:db_fixtable(Storage, Tab, false) + end; + {error, _} = Error -> + Error + end; + false -> + {error, {has_no_disc, node()}} + end. + +do_dump_to_logfile(Storage, Tab, Fd) -> + Pat = [{'_',[],['$_']}], + log_terms(mnesia_lib:db_select_init(Storage, Tab, Pat, 100), Storage, Tab, Pat, Fd). + +log_terms({Objs, Cont}, Storage, Tab, Pat, Fd) -> + ok = disk_log:alog_terms(Fd, Objs), + log_terms(mnesia_lib:db_select_cont(Storage, Cont, '_'), Storage, Tab, Pat, Fd); +log_terms('$end_of_table', _, _, _, Fd) -> + disk_log:close(Fd). + +load_from_logfile(Storage, Tab, F) -> + case disk_log:open([{name, make_ref()}, + {file, F}, + {repair, true}, + {linkto, self()}]) of + {ok, Fd} -> + chunk_from_log(disk_log:chunk(Fd, start), Fd, Storage, Tab); + {repaired, Fd, _, _} -> + chunk_from_log(disk_log:chunk(Fd, start), Fd, Storage, Tab); + {error, _} = E -> + E + end. + +chunk_from_log({Cont, Terms}, Fd, Storage, Tab) -> + _ = [mnesia_lib:db_put(Storage, Tab, T) || T <- Terms], + chunk_from_log(disk_log:chunk(Fd, Cont), Fd, Storage, Tab); +chunk_from_log(eof, _, _, _) -> + ok. + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Load regulator %% @@ -1228,7 +1441,7 @@ start_regulator() -> {ok, Pid} -> Pid; {error, Reason} -> - fatal("Failed to start ~n: ~p~n", [N, Reason]) + fatal("Failed to start ~n: ~tp~n", [N, Reason]) end end. diff --git a/lib/mnesia/src/mnesia_event.erl b/lib/mnesia/src/mnesia_event.erl index 58806793e5..49b3990086 100644 --- a/lib/mnesia/src/mnesia_event.erl +++ b/lib/mnesia/src/mnesia_event.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2014. All Rights Reserved. +%% Copyright Ericsson AB 1997-2017. 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. @@ -103,18 +103,19 @@ handle_any_event({mnesia_system_event, Event}, State) -> handle_any_event({mnesia_table_event, Event}, State) -> handle_table_event(Event, State); handle_any_event(Msg, State) -> - report_error("~p got unexpected event: ~p~n", [?MODULE, Msg]), + report_error("~p got unexpected event: ~tp~n", [?MODULE, Msg]), {ok, State}. handle_table_event({Oper, Record, TransId}, State) -> - report_info("~p performed by ~p on record:~n\t~p~n", + report_info("~p performed by ~p on record:~n\t~tp~n", [Oper, TransId, Record]), {ok, State}. handle_system_event({mnesia_checkpoint_activated, _Checkpoint}, State) -> {ok, State}; -handle_system_event({mnesia_checkpoint_deactivated, _Checkpoint}, State) -> +handle_system_event({mnesia_checkpoint_deactivated, Checkpoint}, State) -> + report_error("Checkpoint '~p' has been deactivated, last table copy deleted.\n",[Checkpoint]), {ok, State}; handle_system_event({mnesia_up, Node}, State) -> @@ -154,7 +155,7 @@ handle_system_event({mnesia_down, Node}, State) -> end; handle_system_event({mnesia_overload, Details}, State) -> - report_warning("Mnesia is overloaded: ~w~n", [Details]), + report_warning("Mnesia is overloaded: ~tw~n", [Details]), {ok, State}; handle_system_event({mnesia_info, Format, Args}, State) -> @@ -174,16 +175,16 @@ handle_system_event({mnesia_fatal, Format, Args, BinaryCore}, State) -> {ok, State#state{dumped_core = true}}; handle_system_event({inconsistent_database, Reason, Node}, State) -> - report_error("mnesia_event got {inconsistent_database, ~w, ~w}~n", + report_error("mnesia_event got {inconsistent_database, ~tw, ~w}~n", [Reason, Node]), {ok, State}; handle_system_event({mnesia_user, Event}, State) -> - report_info("User event: ~p~n", [Event]), + report_info("User event: ~tp~n", [Event]), {ok, State}; handle_system_event(Msg, State) -> - report_error("mnesia_event got unexpected system event: ~p~n", [Msg]), + report_error("mnesia_event got unexpected system event: ~tp~n", [Msg]), {ok, State}. report_info(Format0, Args0) -> diff --git a/lib/mnesia/src/mnesia_ext_sup.erl b/lib/mnesia/src/mnesia_ext_sup.erl new file mode 100644 index 0000000000..3e6c72161c --- /dev/null +++ b/lib/mnesia/src/mnesia_ext_sup.erl @@ -0,0 +1,59 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2015. 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% +%% + +%% +%% This module implements a supervisor for external (plug-in) processes + +-module(mnesia_ext_sup). +-behaviour(supervisor). + +-export([start/0, + init/1, + start_proc/4, start_proc/5, + stop_proc/1]). + +-define(SHUTDOWN, 120000). % 2 minutes + +start() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +start_proc(Name, M, F, A) -> + start_proc(Name, M, F, A, []). + +start_proc(Name, M, F, A, Opts) -> + [Restart, Shutdown, Type, Modules] = + [proplists:get_value(K, Opts, Default) + || {K, Default} <- [{restart, transient}, + {shutdown, ?SHUTDOWN}, + {type, worker}, + {modules, [M]}]], + case supervisor:start_child( + ?MODULE, {Name, {M,F,A}, Restart, Shutdown, Type, Modules}) of + {error, already_present} -> + supervisor:restart_child(?MODULE, Name); + Other -> + Other + end. + +stop_proc(Name) -> + supervisor:terminate_child(?MODULE, Name). + +init(_) -> + {ok, {{one_for_all, 10, 60}, []}}. diff --git a/lib/mnesia/src/mnesia_frag.erl b/lib/mnesia/src/mnesia_frag.erl index cbb0dfb3be..c39f30e140 100644 --- a/lib/mnesia/src/mnesia_frag.erl +++ b/lib/mnesia/src/mnesia_frag.erl @@ -1,7 +1,7 @@ %%% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2011. All Rights Reserved. +%% Copyright Ericsson AB 1998-2016. 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. @@ -58,9 +58,7 @@ -include("mnesia.hrl"). --define(OLD_HASH_MOD, mnesia_frag_old_hash). -define(DEFAULT_HASH_MOD, mnesia_frag_hash). -%%-define(DEFAULT_HASH_MOD, ?OLD_HASH_MOD). %% BUGBUG: New should be default -record(frag_state, {foreign_key, @@ -80,7 +78,7 @@ lock(ActivityId, Opaque, {table , Tab}, LockKind) -> case frag_names(Tab) of [Tab] -> - mnesia:lock(ActivityId, Opaque, {table, Tab}, LockKind); + mnesia:lock(ActivityId, Opaque, {table, Tab}, LockKind); Frags -> DeepNs = [mnesia:lock(ActivityId, Opaque, {table, F}, LockKind) || F <- Frags], @@ -183,6 +181,8 @@ table_info2(ActivityId, Opaque, Tab, Frag, Item) -> length(val({Tab, disc_copies})); n_disc_only_copies -> length(val({Tab, disc_only_copies})); + n_external_copies -> + length(val({Tab, external_copies})); frag_names -> frag_names(Tab); @@ -319,7 +319,7 @@ init_select(Tid,Opaque,Tab,Pat,Limit,LockKind) -> {'EXIT', _} -> mnesia:select(Tid, Opaque, Tab, Pat, Limit,LockKind); FH -> - FragNumbers = verify_numbers(FH,Pat), + FragNumbers = verify_numbers(FH,Pat), Fun = fun(Num) -> Name = n_to_frag_name(Tab, Num), Node = val({Name, where_to_read}), @@ -334,19 +334,19 @@ init_select(Tid,Opaque,Tab,Pat,Limit,LockKind) -> end. 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}) -> +select_cont(Tid,Ts,{frag_cont, '$end_of_table', [{Tab,Node,Type}|Rest],Args}) -> {Spec,LockKind,Limit} = Args, InitFun = fun(FixedSpec) -> mnesia:dirty_sel_init(Node,Tab,FixedSpec,Limit,Type) end, Res = mnesia:fun_select(Tid,Ts,Tab,Spec,LockKind,Tab,InitFun,Limit,Node,Type), frag_sel_cont(Res, Rest, Args); -select_cont(Tid,Ts,{frag_cont, Cont, TabL, Args}) -> +select_cont(Tid,Ts,{frag_cont, Cont, TabL, Args}) -> frag_sel_cont(mnesia:select_cont(Tid,Ts,Cont),TabL,Args); select_cont(Tid,Ts,Else) -> %% Not a fragmented table mnesia:select_cont(Tid,Ts,Else). frag_sel_cont('$end_of_table', [],_) -> '$end_of_table'; -frag_sel_cont('$end_of_table', TabL,Args) -> +frag_sel_cont('$end_of_table', TabL,Args) -> {[], {frag_cont, '$end_of_table', TabL,Args}}; frag_sel_cont({Recs,Cont}, TabL,Args) -> {Recs, {frag_cont, Cont, TabL,Args}}. @@ -356,9 +356,9 @@ do_select(ActivityId, Opaque, Tab, MatchSpec, LockKind) -> {'EXIT', _} -> mnesia:select(ActivityId, Opaque, Tab, MatchSpec, LockKind); FH -> - FragNumbers = verify_numbers(FH,MatchSpec), + FragNumbers = verify_numbers(FH,MatchSpec), Fun = fun(Num) -> - Name = n_to_frag_name(Tab, Num), + Name = n_to_frag_name(Tab, Num), Node = val({Name, where_to_read}), mnesia:lock(ActivityId, Opaque, {table, Name}, LockKind), {Name, Node} @@ -396,7 +396,7 @@ do_select(ActivityId, Opaque, Tab, MatchSpec, LockKind) -> verify_numbers(FH,MatchSpec) -> HashState = FH#frag_state.hash_state, - FragNumbers = + FragNumbers = case FH#frag_state.hash_module of HashMod when HashMod == ?DEFAULT_HASH_MOD -> ?DEFAULT_HASH_MOD:match_spec_to_frag_numbers(HashState, MatchSpec); @@ -432,7 +432,7 @@ local_select(ReplyTo, Ref, RemoteNameNodes, MatchSpec) -> end, unlink(ReplyTo), exit(normal). - + remote_select(ReplyTo, Ref, NameNodes, MatchSpec) -> do_remote_select(ReplyTo, Ref, NameNodes, MatchSpec). @@ -498,13 +498,13 @@ expand_cstruct(Cs, Mode) -> %% Verify keys ValidKeys = [foreign_key, n_fragments, node_pool, n_ram_copies, n_disc_copies, n_disc_only_copies, - hash_module, hash_state], + n_external_copies, hash_module, hash_state], Keys = mnesia_schema:check_keys(Tab, Props, ValidKeys), mnesia_schema:check_duplicates(Tab, Keys), %% Pick fragmentation props ForeignKey = mnesia_schema:pick(Tab, foreign_key, Props, undefined), - {ForeignKey2, N, Pool, DefaultNR, DefaultND, DefaultNDO} = + {ForeignKey2, N, Pool, DefaultNR, DefaultND, DefaultNDO, DefaultNExt} = pick_props(Tab, Cs, ForeignKey), %% Verify node_pool @@ -518,6 +518,7 @@ expand_cstruct(Cs, Mode) -> NR = mnesia_schema:pick(Tab, n_ram_copies, Props, 0), ND = mnesia_schema:pick(Tab, n_disc_copies, Props, 0), NDO = mnesia_schema:pick(Tab, n_disc_only_copies, Props, 0), + NExt = mnesia_schema:pick(Tab, n_external_copies, Props, 0), PosInt = fun(I) when is_integer(I), I >= 0 -> true; (_I) -> false @@ -528,6 +529,8 @@ expand_cstruct(Cs, Mode) -> {bad_type, Tab, {n_disc_copies, ND}}), mnesia_schema:verify(true, PosInt(NDO), {bad_type, Tab, {n_disc_only_copies, NDO}}), + mnesia_schema:verify(true, PosInt(NExt), + {bad_type, Tab, {n_external_copies, NDO}}), %% Verify n_fragments Cs2 = verify_n_fragments(N, Cs, Mode), @@ -542,13 +545,13 @@ expand_cstruct(Cs, Mode) -> hash_module = HashMod, hash_state = HashState2}, if - NR == 0, ND == 0, NDO == 0 -> - do_expand_cstruct(Cs2, FH, N, Pool, DefaultNR, DefaultND, DefaultNDO, Mode); + NR == 0, ND == 0, NDO == 0, NExt == 0 -> + do_expand_cstruct(Cs2, FH, N, Pool, DefaultNR, DefaultND, DefaultNDO, DefaultNExt, Mode); true -> - do_expand_cstruct(Cs2, FH, N, Pool, NR, ND, NDO, Mode) + do_expand_cstruct(Cs2, FH, N, Pool, NR, ND, NDO, NExt, Mode) end. -do_expand_cstruct(Cs, FH, N, Pool, NR, ND, NDO, Mode) -> +do_expand_cstruct(Cs, FH, N, Pool, NR, ND, NDO, NExt, Mode) -> Tab = Cs#cstruct.name, LC = Cs#cstruct.local_content, @@ -562,14 +565,15 @@ do_expand_cstruct(Cs, FH, N, Pool, NR, ND, NDO, Mode) -> %% Add empty fragments CommonProps = [{base_table, Tab}], Cs2 = Cs#cstruct{frag_properties = lists:sort(CommonProps)}, - expand_frag_cstructs(N, NR, ND, NDO, Cs2, Pool, Pool, FH, Mode). + expand_frag_cstructs(N, NR, ND, NDO, NExt, Cs2, Pool, Pool, FH, Mode). verify_n_fragments(N, Cs, Mode) when is_integer(N), N >= 1 -> case Mode of create -> Cs#cstruct{ram_copies = [], disc_copies = [], - disc_only_copies = []}; + disc_only_copies = [], + external_copies = []}; activate -> Reason = {combine_error, Cs#cstruct.name, {n_fragments, N}}, mnesia_schema:verify(1, N, Reason), @@ -607,7 +611,8 @@ pick_props(Tab, Cs, {ForeignTab, Attr}) -> DefaultNR = length(val({ForeignTab, ram_copies})), DefaultND = length(val({ForeignTab, disc_copies})), DefaultNDO = length(val({ForeignTab, disc_only_copies})), - {Key, N, Pool, DefaultNR, DefaultND, DefaultNDO}; + DefaultNExt = length(val({ForeignTab, external_copies})), + {Key, N, Pool, DefaultNR, DefaultND, DefaultNDO, DefaultNExt}; pick_props(Tab, Cs, undefined) -> Props = Cs#cstruct.frag_properties, DefaultN = 1, @@ -617,22 +622,23 @@ pick_props(Tab, Cs, undefined) -> DefaultNR = 1, DefaultND = 0, DefaultNDO = 0, - {undefined, N, Pool, DefaultNR, DefaultND, DefaultNDO}; + DefaultNExt = 0, + {undefined, N, Pool, DefaultNR, DefaultND, DefaultNDO, DefaultNExt}; pick_props(Tab, _Cs, BadKey) -> mnesia:abort({bad_type, Tab, {foreign_key, BadKey}}). -expand_frag_cstructs(N, NR, ND, NDO, CommonCs, Dist, Pool, FH, Mode) +expand_frag_cstructs(N, NR, ND, NDO, NExt, CommonCs, Dist, Pool, FH, Mode) when N > 1, Mode == create -> Frag = n_to_frag_name(CommonCs#cstruct.name, N), Cs = CommonCs#cstruct{name = Frag}, - {Cs2, RevModDist, RestDist} = set_frag_nodes(NR, ND, NDO, Cs, Dist, []), + {Cs2, RevModDist, RestDist} = set_frag_nodes(NR, ND, NDO, NExt, Cs, Dist, []), ModDist = lists:reverse(RevModDist), Dist2 = rearrange_dist(Cs, ModDist, RestDist, Pool), %% Adjusts backwards, but it doesn't matter. {FH2, _FromFrags, _AdditionalWriteFrags} = adjust_before_split(FH), - CsList = expand_frag_cstructs(N - 1, NR, ND, NDO, CommonCs, Dist2, Pool, FH2, Mode), + CsList = expand_frag_cstructs(N - 1, NR, ND, NDO, NExt, CommonCs, Dist2, Pool, FH2, Mode), [Cs2 | CsList]; -expand_frag_cstructs(1, NR, ND, NDO, CommonCs, Dist, Pool, FH, Mode) -> +expand_frag_cstructs(1, NR, ND, NDO, NExt, CommonCs, Dist, Pool, FH, Mode) -> BaseProps = CommonCs#cstruct.frag_properties ++ [{foreign_key, FH#frag_state.foreign_key}, {hash_module, FH#frag_state.hash_module}, @@ -645,25 +651,29 @@ expand_frag_cstructs(1, NR, ND, NDO, CommonCs, Dist, Pool, FH, Mode) -> activate -> [BaseCs]; create -> - {BaseCs2, _, _} = set_frag_nodes(NR, ND, NDO, BaseCs, Dist, []), + {BaseCs2, _, _} = set_frag_nodes(NR, ND, NDO, NExt, BaseCs, Dist, []), [BaseCs2] end. -set_frag_nodes(NR, ND, NDO, Cs, [Head | Tail], Acc) when NR > 0 -> +set_frag_nodes(NR, ND, NDO, NExt, Cs, [Head | Tail], Acc) when NR > 0 -> Pos = #cstruct.ram_copies, {Cs2, Head2} = set_frag_node(Cs, Pos, Head), - set_frag_nodes(NR - 1, ND, NDO, Cs2, Tail, [Head2 | Acc]); -set_frag_nodes(NR, ND, NDO, Cs, [Head | Tail], Acc) when ND > 0 -> + set_frag_nodes(NR - 1, ND, NDO, NExt, Cs2, Tail, [Head2 | Acc]); +set_frag_nodes(NR, ND, NDO, NExt, Cs, [Head | Tail], Acc) when ND > 0 -> Pos = #cstruct.disc_copies, {Cs2, Head2} = set_frag_node(Cs, Pos, Head), - set_frag_nodes(NR, ND - 1, NDO, Cs2, Tail, [Head2 | Acc]); -set_frag_nodes(NR, ND, NDO, Cs, [Head | Tail], Acc) when NDO > 0 -> + set_frag_nodes(NR, ND - 1, NDO, NExt, Cs2, Tail, [Head2 | Acc]); +set_frag_nodes(NR, ND, NDO, NExt, Cs, [Head | Tail], Acc) when NDO > 0 -> Pos = #cstruct.disc_only_copies, {Cs2, Head2} = set_frag_node(Cs, Pos, Head), - set_frag_nodes(NR, ND, NDO - 1, Cs2, Tail, [Head2 | Acc]); -set_frag_nodes(0, 0, 0, Cs, RestDist, ModDist) -> + set_frag_nodes(NR, ND, NDO - 1, NExt, Cs2, Tail, [Head2 | Acc]); +set_frag_nodes(NR, ND, NDO, NExt, Cs, [Head | Tail], Acc) when NExt > 0 -> + Pos = #cstruct.external_copies, + {Cs2, Head2} = set_frag_node(Cs, Pos, Head), + set_frag_nodes(NR, ND, NDO, NExt - 1, Cs2, Tail, [Head2 | Acc]); +set_frag_nodes(0, 0, 0, 0, Cs, RestDist, ModDist) -> {Cs, ModDist, RestDist}; -set_frag_nodes(_, _, _, Cs, [], _) -> +set_frag_nodes(_, _, _, _, Cs, [], _) -> mnesia:abort({combine_error, Cs#cstruct.name, "Too few nodes in node_pool"}). set_frag_node(Cs, Pos, Head) -> @@ -793,22 +803,22 @@ make_deactivate(Tab) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Add a fragment to a fragmented table and fill it with half of %% the records from one of the old fragments - + make_multi_add_frag(Tab, SortedNs) when is_list(SortedNs) -> verify_multi(Tab), Ops = make_add_frag(Tab, SortedNs), %% Propagate to foreigners MoreOps = [make_add_frag(T, SortedNs) || T <- lookup_foreigners(Tab)], - [Ops | MoreOps]; + [Ops | MoreOps]; make_multi_add_frag(Tab, SortedNs) -> mnesia:abort({bad_type, Tab, SortedNs}). verify_multi(Tab) -> FH = lookup_frag_hash(Tab), ForeignKey = FH#frag_state.foreign_key, - mnesia_schema:verify(undefined, ForeignKey, - {combine_error, Tab, + mnesia_schema:verify(undefined, ForeignKey, + {combine_error, Tab, "Op only allowed via foreign table", {foreign_key, ForeignKey}}). @@ -827,7 +837,7 @@ make_frag_names_and_acquire_locks(Tab, N, FragIndecies, DoNotLockN) -> end, FragNames = erlang:make_tuple(N, undefined), lists:foldl(Fun, FragNames, FragIndecies). - + make_add_frag(Tab, SortedNs) -> Cs = mnesia_schema:incr_version(val({Tab, cstruct})), mnesia_schema:ensure_active(Cs), @@ -837,16 +847,18 @@ make_add_frag(Tab, SortedNs) -> FragNames = make_frag_names_and_acquire_locks(Tab, N, WriteIndecies, true), NewFrag = element(N, FragNames), - NR = length(Cs#cstruct.ram_copies), - ND = length(Cs#cstruct.disc_copies), + NR = length(Cs#cstruct.ram_copies), + ND = length(Cs#cstruct.disc_copies), NDO = length(Cs#cstruct.disc_only_copies), + NExt = length(Cs#cstruct.external_copies), NewCs = Cs#cstruct{name = NewFrag, frag_properties = [{base_table, Tab}], ram_copies = [], disc_copies = [], - disc_only_copies = []}, - - {NewCs2, _, _} = set_frag_nodes(NR, ND, NDO, NewCs, SortedNs, []), + disc_only_copies = [], + external_copies = []}, + + {NewCs2, _, _} = set_frag_nodes(NR, ND, NDO, NExt, NewCs, SortedNs, []), [NewOp] = mnesia_schema:make_create_table(NewCs2), SplitOps = split(Tab, FH2, FromIndecies, FragNames, []), @@ -930,7 +942,7 @@ do_split(FH, OldN, FragNames, [Rec | Recs], Ops) -> Key = element(2, Rec), NewOid = {NewFrag, Key}, OldOid = {OldFrag, Key}, - Ops2 = [{op, rec, unknown, {NewOid, [Rec], write}}, + Ops2 = [{op, rec, unknown, {NewOid, [Rec], write}}, {op, rec, unknown, {OldOid, [OldOid], delete}} | Ops], do_split(FH, OldN, FragNames, Recs, Ops2); _NewFrag -> @@ -944,7 +956,7 @@ do_split(_FH, _OldN, _FragNames, [], Ops) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Delete a fragment from a fragmented table %% and merge its records with another fragment - + make_multi_del_frag(Tab) -> verify_multi(Tab), Ops = make_del_frag(Tab), @@ -1050,7 +1062,7 @@ do_merge(FH, OldN, FragNames, [Rec | Recs], Ops) -> Key = element(2, Rec), NewOid = {NewFrag, Key}, OldOid = {OldFrag, Key}, - Ops2 = [{op, rec, unknown, {NewOid, [Rec], write}}, + Ops2 = [{op, rec, unknown, {NewOid, [Rec], write}}, {op, rec, unknown, {OldOid, [OldOid], delete}} | Ops], do_merge(FH, OldN, FragNames, Recs, Ops2); _NewFrag -> @@ -1063,7 +1075,7 @@ do_merge(FH, OldN, FragNames, [Rec | Recs], Ops) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Add a node to the node pool of a fragmented table - + make_multi_add_node(Tab, Node) -> verify_multi(Tab), Ops = make_add_node(Tab, Node), @@ -1071,7 +1083,7 @@ make_multi_add_node(Tab, Node) -> %% Propagate to foreigners MoreOps = [make_add_node(T, Node) || T <- lookup_foreigners(Tab)], [Ops | MoreOps]. - + make_add_node(Tab, Node) when is_atom(Node) -> Pool = lookup_prop(Tab, node_pool), case lists:member(Node, Pool) of @@ -1100,7 +1112,7 @@ make_multi_del_node(Tab, Node) -> %% Propagate to foreigners MoreOps = [make_del_node(T, Node) || T <- lookup_foreigners(Tab)], [Ops | MoreOps]. - + make_del_node(Tab, Node) when is_atom(Node) -> Cs = mnesia_schema:incr_version(val({Tab, cstruct})), mnesia_schema:ensure_active(Cs), @@ -1133,8 +1145,8 @@ remove_node(Node, Cs) -> case lists:member(Node, Pool) of true -> Pool2 = Pool -- [Node], - Props = lists:keyreplace(node_pool, 1, - Cs#cstruct.frag_properties, + Props = lists:keyreplace(node_pool, 1, + Cs#cstruct.frag_properties, {node_pool, Pool2}), {Cs#cstruct{frag_properties = Props}, true}; false -> @@ -1166,18 +1178,10 @@ props_to_frag_hash(Tab, Props) -> T when T == Tab -> Foreign = mnesia_schema:pick(Tab, foreign_key, Props, must), N = mnesia_schema:pick(Tab, n_fragments, Props, must), - case mnesia_schema:pick(Tab, hash_module, Props, undefined) of undefined -> - Split = mnesia_schema:pick(Tab, next_n_to_split, Props, must), - Doubles = mnesia_schema:pick(Tab, n_doubles, Props, must), - FH = {frag_hash, Foreign, N, Split, Doubles}, - HashState = ?OLD_HASH_MOD:init_state(Tab, FH), - #frag_state{foreign_key = Foreign, - n_fragments = N, - hash_module = ?OLD_HASH_MOD, - hash_state = HashState}; - HashMod -> + no_hash; + HashMod -> HashState = mnesia_schema:pick(Tab, hash_state, Props, must), #frag_state{foreign_key = Foreign, n_fragments = N, @@ -1202,13 +1206,9 @@ lookup_frag_hash(Tab) -> case ?catch_val({Tab, frag_hash}) of FH when is_record(FH, frag_state) -> FH; - {frag_hash, K, N, _S, _D} = FH -> + {frag_hash, _K, _N, _S, _D} -> %% Old style. Kept for backwards compatibility. - HashState = ?OLD_HASH_MOD:init_state(Tab, FH), - #frag_state{foreign_key = K, - n_fragments = N, - hash_module = ?OLD_HASH_MOD, - hash_state = HashState}; + mnesia:abort({no_hash, Tab, frag_properties, frag_hash}); {'EXIT', _} -> mnesia:abort({no_exists, Tab, frag_properties, frag_hash}) end. @@ -1235,10 +1235,10 @@ key_pos(FH) -> case FH#frag_state.foreign_key of undefined -> 2; - {_ForeignTab, Pos} -> + {_ForeignTab, Pos} -> Pos end. - + %% Returns name of fragment table key_to_frag_name({BaseTab, _} = Tab, Key) -> N = key_to_frag_number(Tab, Key), @@ -1319,7 +1319,8 @@ count_frag([Frag | Frags], Dist) -> Dist2 = incr_nodes(val({Frag, ram_copies}), Dist), Dist3 = incr_nodes(val({Frag, disc_copies}), Dist2), Dist4 = incr_nodes(val({Frag, disc_only_copies}), Dist3), - count_frag(Frags, Dist4); + Dist5 = incr_nodes(val({Frag, external_copies}), Dist4), + count_frag(Frags, Dist5); count_frag([], Dist) -> Dist. diff --git a/lib/mnesia/src/mnesia_frag_hash.erl b/lib/mnesia/src/mnesia_frag_hash.erl index 2e4ebb98b0..ae4105382e 100644 --- a/lib/mnesia/src/mnesia_frag_hash.erl +++ b/lib/mnesia/src/mnesia_frag_hash.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2011. All Rights Reserved. +%% Copyright Ericsson AB 2002-2016. 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. diff --git a/lib/mnesia/src/mnesia_frag_old_hash.erl b/lib/mnesia/src/mnesia_frag_old_hash.erl deleted file mode 100644 index 707be6d121..0000000000 --- a/lib/mnesia/src/mnesia_frag_old_hash.erl +++ /dev/null @@ -1,133 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2002-2009. 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% -%% - -%% -%%%---------------------------------------------------------------------- -%%% Purpose : Implements hashing functionality for fragmented tables -%%%---------------------------------------------------------------------- - --module(mnesia_frag_old_hash). -%%-behaviour(mnesia_frag_hash). - --compile({nowarn_deprecated_function, {erlang,hash,2}}). - -%% Hashing callback functions --export([ - init_state/2, - add_frag/1, - del_frag/1, - key_to_frag_number/2, - match_spec_to_frag_numbers/2 - ]). - --record(old_hash_state, - {n_fragments, - next_n_to_split, - n_doubles}). - -%% Old style. Kept for backwards compatibility. --record(frag_hash, - {foreign_key, - n_fragments, - next_n_to_split, - n_doubles}). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -init_state(_Tab, InitialState) when InitialState == undefined -> - #old_hash_state{n_fragments = 1, - next_n_to_split = 1, - n_doubles = 0}; -init_state(_Tab, FH) when is_record(FH, frag_hash) -> - %% Old style. Kept for backwards compatibility. - #old_hash_state{n_fragments = FH#frag_hash.n_fragments, - next_n_to_split = FH#frag_hash.next_n_to_split, - n_doubles = FH#frag_hash.n_doubles}. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -add_frag(State) when is_record(State, old_hash_state) -> - SplitN = State#old_hash_state.next_n_to_split, - P = SplitN + 1, - L = State#old_hash_state.n_doubles, - NewN = State#old_hash_state.n_fragments + 1, - State2 = case trunc(math:pow(2, L)) + 1 of - P2 when P2 == P -> - State#old_hash_state{n_fragments = NewN, - next_n_to_split = 1, - n_doubles = L + 1}; - _ -> - State#old_hash_state{n_fragments = NewN, - next_n_to_split = P} - end, - {State2, [SplitN], [NewN]}. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -del_frag(State) when is_record(State, old_hash_state) -> - P = State#old_hash_state.next_n_to_split - 1, - L = State#old_hash_state.n_doubles, - N = State#old_hash_state.n_fragments, - if - P < 1 -> - L2 = L - 1, - MergeN = trunc(math:pow(2, L2)), - State2 = State#old_hash_state{n_fragments = N - 1, - next_n_to_split = MergeN, - n_doubles = L2}, - {State2, [N], [MergeN]}; - true -> - MergeN = P, - State2 = State#old_hash_state{n_fragments = N - 1, - next_n_to_split = MergeN}, - {State2, [N], [MergeN]} - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -key_to_frag_number(State, Key) when is_record(State, old_hash_state) -> - L = State#old_hash_state.n_doubles, - A = erlang:hash(Key, trunc(math:pow(2, L))), - P = State#old_hash_state.next_n_to_split, - if - A < P -> - erlang:hash(Key, trunc(math:pow(2, L + 1))); - true -> - A - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -match_spec_to_frag_numbers(State, MatchSpec) when is_record(State, old_hash_state) -> - case MatchSpec of - [{HeadPat, _, _}] when is_tuple(HeadPat), tuple_size(HeadPat) > 2 -> - KeyPat = element(2, HeadPat), - case has_var(KeyPat) of - false -> - [key_to_frag_number(State, KeyPat)]; - true -> - lists:seq(1, State#old_hash_state.n_fragments) - end; - _ -> - lists:seq(1, State#old_hash_state.n_fragments) - end. - -has_var(Pat) -> - mnesia:has_var(Pat). diff --git a/lib/mnesia/src/mnesia_index.erl b/lib/mnesia/src/mnesia_index.erl index 81ed5a8f1a..d121bd01e9 100644 --- a/lib/mnesia/src/mnesia_index.erl +++ b/lib/mnesia/src/mnesia_index.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2014. All Rights Reserved. +%% Copyright Ericsson AB 1996-2016. 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. @@ -23,8 +23,8 @@ -module(mnesia_index). -export([read/5, - add_index/5, - delete_index/3, + add_index/6, + delete_index/4, del_object_index/5, clear_index/4, dirty_match_object/3, @@ -39,19 +39,21 @@ get_index_table/3, tab2filename/2, - tab2tmp_filename/2, init_index/2, init_indecies/3, del_transient/2, del_transient/3, - del_index_table/3]). + del_index_table/3, + + index_info/2, + ext_index_instances/1]). -import(mnesia_lib, [val/1, verbose/2]). -include("mnesia.hrl"). -record(index, {setorbag, pos_list}). -%% read an object list throuh its index table +%% read an object list through its index table %% we assume that table Tab has index on attribute number Pos read(Tid, Store, Tab, IxKey, Pos) -> @@ -64,62 +66,103 @@ read(Tid, Store, Tab, IxKey, Pos) -> ResList end. -add_index(Index, Tab, Key, Obj, Old) -> - add_index2(Index#index.pos_list, Index#index.setorbag, Tab, Key, Obj, Old). - -add_index2([{Pos, Ixt} |Tail], bag, Tab, K, Obj, OldRecs) -> - db_put(Ixt, {element(Pos, Obj), K}), - add_index2(Tail, bag, Tab, K, Obj, OldRecs); -add_index2([{Pos, Ixt} |Tail], Type, Tab, K, Obj, OldRecs) -> +ext_index_instances(Tab) -> + #index{pos_list = PosL} = val({Tab, index_info}), + lists:foldr( + fun({_, {{ext,Alias,Mod}, Tag}}, Acc) -> + [{Alias, Mod, Tag}|Acc]; + (_, Acc) -> + Acc + end, [], PosL). + + +add_index(#index{pos_list = PosL, setorbag = SorB}, + Storage, Tab, Key, Obj, Old) -> + add_index2(PosL, SorB, Storage, Tab, Key, Obj, Old). + +add_index2([{{Pos,Type}, Ixt} |Tail], bag, Storage, Tab, K, Obj, OldRecs) -> + ValsF = index_vals_f(Storage, Tab, Pos), + Vals = ValsF(Obj), + put_index_vals(Type, Ixt, Vals, K), + add_index2(Tail, bag, Storage, Tab, K, Obj, OldRecs); +add_index2([{{Pos, Type}, Ixt} |Tail], SorB, Storage, Tab, K, Obj, OldRecs0) -> %% Remove old tuples in index if Tab is updated - case OldRecs of - undefined -> - Old = mnesia_lib:db_get(Tab, K), - del_ixes(Ixt, Old, Pos, K); - Old -> - del_ixes(Ixt, Old, Pos, K) - end, - db_put(Ixt, {element(Pos, Obj), K}), - add_index2(Tail, Type, Tab, K, Obj, OldRecs); -add_index2([], _, _Tab, _K, _Obj, _) -> ok. - -delete_index(Index, Tab, K) -> - delete_index2(Index#index.pos_list, Tab, K). - -delete_index2([{Pos, Ixt} | Tail], Tab, K) -> - DelObjs = mnesia_lib:db_get(Tab, K), - del_ixes(Ixt, DelObjs, Pos, K), - delete_index2(Tail, Tab, K); -delete_index2([], _Tab, _K) -> ok. - - -del_ixes(_Ixt, [], _Pos, _L) -> ok; -del_ixes(Ixt, [Obj | Tail], Pos, Key) -> - db_match_erase(Ixt, {element(Pos, Obj), Key}), - del_ixes(Ixt, Tail, Pos, Key). - -del_object_index(Index, Tab, K, Obj, Old) -> - del_object_index2(Index#index.pos_list, Index#index.setorbag, Tab, K, Obj, Old). - -del_object_index2([], _, _Tab, _K, _Obj, _Old) -> ok; -del_object_index2([{Pos, Ixt} | Tail], SoB, Tab, K, Obj, Old) -> + ValsF = index_vals_f(Storage, Tab, Pos), NewVals = ValsF(Obj), + OldRecs1 = case OldRecs0 of + undefined -> mnesia_lib:db_get(Storage, Tab, K); + _ -> OldRecs0 + end, + IdxVal = ValsF(Obj), + case [Old || Old <- OldRecs1, ValsF(Old) =/= IdxVal] of + [] when OldRecs1 =:= [] -> % Write + put_index_vals(Type, Ixt, NewVals, K), + add_index2(Tail, SorB, Storage, Tab, K, Obj, OldRecs1); + [] -> %% when OldRecs1 =/= [] Update without modifying index field + add_index2(Tail, SorB, Storage, Tab, K, Obj, OldRecs1); + OldRecs -> %% Update + put_index_vals(Type, Ixt, NewVals, K), + [del_ixes(Type, Ixt, ValsF, OldObj, K) || OldObj <- OldRecs], + add_index2(Tail, SorB, Storage, Tab, K, Obj, OldRecs1) + end; +add_index2([], _, _, _Tab, _K, _Obj, _) -> ok. + +delete_index(Index, Storage, Tab, K) -> + delete_index2(Index#index.pos_list, Storage, Tab, K). + +delete_index2([{{Pos, Type}, Ixt} | Tail], Storage, Tab, K) -> + DelObjs = mnesia_lib:db_get(Storage, Tab, K), + ValsF = index_vals_f(Storage, Tab, Pos), + [del_ixes(Type, Ixt, ValsF, Obj, K) || Obj <- DelObjs], + delete_index2(Tail, Storage, Tab, K); +delete_index2([], _Storage, _Tab, _K) -> ok. + +put_index_vals(ordered, Ixt, Vals, K) -> + [db_put(Ixt, {{V, K}}) || V <- Vals]; +put_index_vals(bag, Ixt, Vals, K) -> + [db_put(Ixt, {V, K}) || V <- Vals]. + + +del_ixes(bag, Ixt, ValsF, Obj, Key) -> + Vals = ValsF(Obj), + [db_match_erase(Ixt, {V, Key}) || V <- Vals]; +del_ixes(ordered, Ixt, ValsF, Obj, Key) -> + Vals = ValsF(Obj), + [db_erase(Ixt, {V,Key}) || V <- Vals]. + +del_object_index(#index{pos_list = PosL, setorbag = SorB}, Storage, Tab, K, Obj) -> + del_object_index2(PosL, SorB, Storage, Tab, K, Obj). + +del_object_index2([], _, _Storage, _Tab, _K, _Obj) -> ok; +del_object_index2([{{Pos, Type}, Ixt} | Tail], SoB, Storage, Tab, K, Obj) -> + ValsF = index_vals_f(Storage, Tab, Pos), case SoB of bag -> - del_object_bag(Tab, K, Obj, Pos, Ixt, Old); + del_object_bag(Type, ValsF, Tab, K, Obj, Ixt); _ -> %% If set remove the tuple in index table - del_ixes(Ixt, [Obj], Pos, K) + del_ixes(Type, Ixt, ValsF, Obj, K) end, - del_object_index2(Tail, SoB, Tab, K, Obj, Old). - -del_object_bag(Tab, Key, Obj, Pos, Ixt, undefined) -> - IxKey = element(Pos, Obj), - Old = [X || X <- mnesia_lib:db_get(Tab, Key), element(Pos, X) =:= IxKey], - del_object_bag(Tab, Key, Obj, Pos, Ixt, Old); -%% If Tab type is bag we need remove index identifier if the object being -%% deleted was the last one -del_object_bag(_Tab, Key, Obj, Pos, Ixt, Old) when Old =:= [Obj] -> - del_ixes(Ixt, [Obj], Pos, Key); -del_object_bag(_Tab, _Key, _Obj, _Pos, _Ixt, _Old) -> ok. + del_object_index2(Tail, SoB, Storage, Tab, K, Obj). + +del_object_bag(Type, ValsF, Tab, Key, Obj, Ixt) -> + IxKeys = ValsF(Obj), + Found = [{X, ValsF(X)} || X <- mnesia_lib:db_get(Tab, Key)], + del_object_bag_(IxKeys, Found, Type, Tab, Key, Obj, Ixt). + +del_object_bag_([IxK|IxKs], Found, Type, Tab, Key, Obj, Ixt) -> + case [X || {X, Ixes} <- Found, lists:member(IxK, Ixes)] of + [Old] when Old =:= Obj -> + case Type of + bag -> + db_match_erase(Ixt, {IxK, Key}); + ordered -> + db_erase(Ixt, {{IxK, Key}}) + end; + _ -> + ok + end, + del_object_bag_(IxKs, Found, Type, Tab, Key, Obj, Ixt); +del_object_bag_([], _, _, _, _, _, _) -> + ok. clear_index(Index, Tab, K, Obj) -> clear_index2(Index#index.pos_list, Tab, K, Obj). @@ -131,8 +174,9 @@ clear_index2([{_Pos, Ixt} | Tail], Tab, K, Obj) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -dirty_match_object(Tab, Pat, Pos) -> +dirty_match_object(Tab, Pat, Pos) when is_integer(Pos) -> %% Assume that we are on the node where the replica is + %% Cannot use index plugins here, as they don't map to match patterns case element(2, Pat) of '_' -> IxKey = element(Pos, Pat), @@ -154,7 +198,7 @@ realkeys(Tab, Pos, IxKey) -> Index = get_index_table(Tab, Pos), db_get(Index, IxKey). % a list on the form [{IxKey, RealKey1} , .... -dirty_select(Tab, Spec, Pos) -> +dirty_select(Tab, Spec, Pos) when is_integer(Pos) -> %% Assume that we are on the node where the replica is %% Returns the records without applying the match spec %% The actual filtering is handled by the caller @@ -164,59 +208,80 @@ dirty_select(Tab, Spec, Pos) -> lists:append([mnesia_lib:db_get(StorageType, Tab, Key) || {_,Key} <- RealKeys]). dirty_read(Tab, IxKey, Pos) -> - ResList = mnesia:dirty_rpc(Tab, ?MODULE, dirty_read2, - [Tab, IxKey, Pos]), - case val({Tab, setorbag}) of - bag -> - %% Remove all tuples which don't include Ixkey - mnesia_lib:key_search_all(IxKey, Pos, ResList); - _ -> - ResList - end. + mnesia:dirty_rpc(Tab, ?MODULE, dirty_read2, + [Tab, IxKey, Pos]). dirty_read2(Tab, IxKey, Pos) -> - Ix = get_index_table(Tab, Pos), - Keys = db_match(Ix, {IxKey, '$1'}), - r_keys(Keys, Tab, []). - -r_keys([[H]|T],Tab,Ack) -> - V = mnesia_lib:db_get(Tab, H), - r_keys(T, Tab, V ++ Ack); -r_keys([], _, Ack) -> - Ack. + #index{pos_list = PosL} = val({Tab, index_info}), + Storage = val({Tab, storage_type}), + {Type, Ixt} = pick_index(PosL, Tab, Pos), + Pat = case Type of + ordered -> [{{{IxKey, '$1'}}, [], ['$1']}]; + bag -> [{{IxKey, '$1'}, [], ['$1']}] + end, + Keys = db_select(Ixt, Pat), + ValsF = index_vals_f(Storage, Tab, Pos), + lists:reverse( + lists:foldl( + fun(K, Acc) -> + lists:foldl( + fun(Obj, Acc1) -> + case lists:member(IxKey, ValsF(Obj)) of + true -> [Obj|Acc1]; + false -> Acc1 + end + end, Acc, mnesia_lib:db_get(Storage, Tab, K)) + end, [], Keys)). + +pick_index([{{{Pfx,_},IxType}, Ixt}|_], _Tab, {_} = Pfx) -> + {IxType, Ixt}; +pick_index([{{Pos,IxType}, Ixt}|_], _Tab, Pos) -> + {IxType, Ixt}; +pick_index([_|T], Tab, Pos) -> + pick_index(T, Tab, Pos); +pick_index([], Tab, Pos) -> + mnesia:abort({no_exist, Tab, {index, Pos}}). + %%%%%%% Creation, Init and deletion routines for index tables %% We can have several indexes on the same table %% this can be a fairly costly operation if table is *very* large -tab2filename(Tab, Pos) -> +tab2filename(Tab, {A}) when is_atom(A) -> + mnesia_lib:dir(Tab) ++ "_-" ++ atom_to_list(A) ++ "-.DAT"; +tab2filename(Tab, T) when is_tuple(T) -> + tab2filename(Tab, element(1, T)); +tab2filename(Tab, Pos) when is_integer(Pos) -> mnesia_lib:dir(Tab) ++ "_" ++ integer_to_list(Pos) ++ ".DAT". -tab2tmp_filename(Tab, Pos) -> - mnesia_lib:dir(Tab) ++ "_" ++ integer_to_list(Pos) ++ ".TMP". - init_index(Tab, Storage) -> - PosList = val({Tab, index}), + Cs = val({Tab, cstruct}), + PosList = Cs#cstruct.index, init_indecies(Tab, Storage, PosList). init_indecies(Tab, Storage, PosList) -> case Storage of unknown -> ignore; + {ext, Alias, Mod} -> + init_ext_index(Tab, Storage, Alias, Mod, PosList); disc_only_copies -> - init_disc_index(Tab, PosList); + init_disc_index(Tab, Storage, PosList); ram_copies -> - make_ram_index(Tab, PosList); + make_ram_index(Tab, Storage, PosList); disc_copies -> - make_ram_index(Tab, PosList) + make_ram_index(Tab, Storage, PosList) end. %% works for both ram and disc indexes del_index_table(_, unknown, _) -> ignore; -del_index_table(Tab, Storage, Pos) -> +del_index_table(Tab, Storage, {_} = Pos) -> + delete_transient_index(Tab, Pos, Storage), + mnesia_lib:del({Tab, index}, Pos); +del_index_table(Tab, Storage, Pos) when is_integer(Pos) -> delete_transient_index(Tab, Pos, Storage), mnesia_lib:del({Tab, index}, Pos). @@ -229,13 +294,24 @@ del_transient(Tab, [Pos | Tail], Storage) -> delete_transient_index(Tab, Pos, Storage), del_transient(Tab, Tail, Storage). +delete_transient_index(Tab, Pos, {ext, Alias, Mod}) -> + PosInfo = case Pos of + _ when is_integer(Pos) -> + Cs = val({Tab, cstruct}), + lists:keyfind(Pos, 1, Cs#cstruct.index); + {P, T} -> {P, T} + end, + Tag = {Tab, index, PosInfo}, + Mod:close_table(Alias, Tag), + Mod:delete_table(Alias, Tag), + del_index_info(Tab, Pos), + mnesia_lib:unset({Tab, {index, Pos}}); delete_transient_index(Tab, Pos, disc_only_copies) -> Tag = {Tab, index, Pos}, mnesia_monitor:unsafe_close_dets(Tag), _ = file:delete(tab2filename(Tab, Pos)), del_index_info(Tab, Pos), %% Uses val(..) mnesia_lib:unset({Tab, {index, Pos}}); - delete_transient_index(Tab, Pos, _Storage) -> Ixt = val({Tab, {index, Pos}}), ?ets_delete_table(Ixt), @@ -245,11 +321,12 @@ delete_transient_index(Tab, Pos, _Storage) -> %%%%% misc functions for the index create/init/delete functions above %% assuming that the file exists. -init_disc_index(_Tab, []) -> +init_disc_index(_Tab, _Storage, []) -> done; -init_disc_index(Tab, [Pos | Tail]) when is_integer(Pos) -> +init_disc_index(Tab, disc_only_copies, [{Pos,_Pref} | Tail]) -> + PosInfo = {Pos, bag}, Fn = tab2filename(Tab, Pos), - IxTag = {Tab, index, Pos}, + IxTag = {Tab, index, PosInfo}, _ = file:delete(Fn), Args = [{file, Fn}, {keypos, 1}, {type, bag}], mnesia_monitor:open_dets(IxTag, Args), @@ -262,16 +339,59 @@ init_disc_index(Tab, [Pos | Tail]) when is_integer(Pos) -> mnesia_lib:db_fixtable(Storage, Tab, true), ok = dets:init_table(IxTag, create_fun(Init, Tab, Pos)), mnesia_lib:db_fixtable(Storage, Tab, false), - mnesia_lib:set({Tab, {index, Pos}}, IxTag), - add_index_info(Tab, val({Tab, setorbag}), {Pos, {dets, IxTag}}), - init_disc_index(Tab, Tail). + mnesia_lib:set({Tab, {index, PosInfo}}, IxTag), + add_index_info(Tab, val({Tab, setorbag}), {PosInfo, {dets, IxTag}}), + init_disc_index(Tab, Storage, Tail). + +init_ext_index(_, _, _, _, []) -> + done; +init_ext_index(Tab, Storage, Alias, Mod, [{Pos,Type} | Tail]) -> + PosInfo = {Pos, Type}, + IxTag = {Tab, index, PosInfo}, + CS = val({Tab, cstruct}), + CsList = mnesia_schema:cs2list(CS), + _Res = mnesia_monitor:unsafe_create_external(IxTag, Alias, Mod, CsList), + Mod:load_table(Alias, IxTag, init_index, CsList), + case Mod:is_index_consistent(Alias, IxTag) of + false -> + Mod:index_is_consistent(Alias, IxTag, false), + Mod:match_delete(Alias, IxTag, '_'), + IxValsF = index_vals_f(Storage, Tab, Pos), + IxObjF = case Type of + bag -> fun(IxVal, Key) -> {IxVal, Key} end; + ordered -> fun(IxVal, Key) -> {{IxVal, Key}} end + end, + mnesia_lib:db_fixtable(Storage, Tab, true), + mnesia_lib:db_foldl( + Storage, + fun(Rec, Acc) -> + Key = element(2, Rec), + lists:foreach( + fun(V) -> + IxObj = IxObjF(V, Key), + Mod:insert(Alias, IxTag, IxObj) + end, IxValsF(Rec)), + Acc + end, ok, Tab, + [{'_', [], ['$_']}], 100), + Mod:index_is_consistent(Alias, IxTag, true); + true -> + ignore + end, + + mnesia_lib:set({Tab, {index, PosInfo}}, IxTag), + + add_index_info(Tab, val({Tab, setorbag}), {PosInfo, {Storage, IxTag}}), + init_ext_index(Tab, Storage, Alias, Mod, Tail). create_fun(Cont, Tab, Pos) -> + IxF = index_vals_f(disc_only_copies, Tab, Pos), fun(read) -> Data = case Cont of {start, KeysPerChunk} -> - mnesia_lib:db_init_chunk(disc_only_copies, Tab, KeysPerChunk); + mnesia_lib:db_init_chunk( + disc_only_copies, Tab, KeysPerChunk); '$end_of_table' -> '$end_of_table'; _Else -> @@ -281,56 +401,82 @@ create_fun(Cont, Tab, Pos) -> '$end_of_table' -> end_of_input; {Recs, Next} -> - IdxElems = [{element(Pos, Obj), element(2, Obj)} || Obj <- Recs], + IdxElems = lists:flatmap( + fun(Obj) -> + PrimK = element(2, Obj), + [{V, PrimK} || V <- IxF(Obj)] + end, Recs), {IdxElems, create_fun(Next, Tab, Pos)} end; (close) -> ok end. -make_ram_index(_, []) -> +make_ram_index(_, _, []) -> done; -make_ram_index(Tab, [Pos | Tail]) -> - add_ram_index(Tab, Pos), - make_ram_index(Tab, Tail). +make_ram_index(Tab, Storage, [Pos | Tail]) -> + add_ram_index(Tab, Storage, Pos), + make_ram_index(Tab, Storage, Tail). -add_ram_index(Tab, Pos) when is_integer(Pos) -> - verbose("Creating index for ~w ~n", [Tab]), +add_ram_index(Tab, Storage, {Pos, _Pref}) -> + Type = ordered, + verbose("Creating index for ~tw ~p ~p~n", [Tab, Pos, Type]), SetOrBag = val({Tab, setorbag}), - IndexType = case SetOrBag of - set -> duplicate_bag; - ordered_set -> duplicate_bag; - bag -> bag - end, - Index = mnesia_monitor:mktab(mnesia_index, [IndexType, public]), + IxValsF = index_vals_f(Storage, Tab, Pos), + IxFun = fun(Val, Key) -> {{Val, Key}} end, + Index = mnesia_monitor:mktab(mnesia_index, [ordered_set, public]), Insert = fun(Rec, _Acc) -> - true = ?ets_insert(Index, {element(Pos, Rec), element(2, Rec)}) + PrimK = element(2, Rec), + true = ?ets_insert( + Index, [IxFun(V, PrimK) + || V <- IxValsF(Rec)]) end, mnesia_lib:db_fixtable(ram_copies, Tab, true), - true = ets:foldl(Insert, true, Tab), + true = mnesia_lib:db_foldl(Storage, Insert, true, Tab), mnesia_lib:db_fixtable(ram_copies, Tab, false), mnesia_lib:set({Tab, {index, Pos}}, Index), - add_index_info(Tab, SetOrBag, {Pos, {ram, Index}}); -add_ram_index(_Tab, snmp) -> + add_index_info(Tab, SetOrBag, {{Pos, Type}, {ram, Index}}); +add_ram_index(_Tab, _, snmp) -> ok. -add_index_info(Tab, Type, IxElem) -> +index_info(SetOrBag, PosList) -> + IxPlugins = mnesia_schema:index_plugins(), + ExpPosList = lists:map( + fun({{P,Type},Ixt} = PI) -> + case P of + {_} = IxN -> + {_, M, F} = + lists:keyfind(IxN, 1, IxPlugins), + {{{IxN,M,F}, Type}, Ixt}; + _ -> + PI + end + end, PosList), + #index{setorbag = SetOrBag, pos_list = ExpPosList}. + +add_index_info(Tab, SetOrBag, IxElem) -> Commit = val({Tab, commit_work}), case lists:keysearch(index, 1, Commit) of false -> - Index = #index{setorbag = Type, - pos_list = [IxElem]}, - %% Check later if mnesia_tm is sensative about the order + IndexInfo = index_info(SetOrBag, [IxElem]), + %% Check later if mnesia_tm is sensitive about the order + mnesia_lib:set({Tab, index_info}, IndexInfo), + mnesia_lib:set({Tab, index}, index_positions(IndexInfo)), mnesia_lib:set({Tab, commit_work}, - mnesia_lib:sort_commit([Index | Commit])); + mnesia_lib:sort_commit([IndexInfo | Commit])); {value, Old} -> %% We could check for consistency here Index = Old#index{pos_list = [IxElem | Old#index.pos_list]}, + mnesia_lib:set({Tab, index_info}, Index), + mnesia_lib:set({Tab, index}, index_positions(Index)), NewC = lists:keyreplace(index, 1, Commit, Index), mnesia_lib:set({Tab, commit_work}, mnesia_lib:sort_commit(NewC)) end. +index_positions(#index{pos_list = PL}) -> + [P || {{P,_},_} <- PL]. + del_index_info(Tab, Pos) -> Commit = val({Tab, commit_work}), case lists:keysearch(index, 1, Commit) of @@ -338,13 +484,21 @@ del_index_info(Tab, Pos) -> %% Something is wrong ignore skip; {value, Old} -> - case lists:keydelete(Pos, 1, Old#index.pos_list) of + case lists:filter(fun({P,_}) -> + element(1,P)=/=Pos + end, + Old#index.pos_list) of [] -> + IndexInfo = index_info(Old#index.setorbag,[]), + mnesia_lib:set({Tab, index_info}, IndexInfo), + mnesia_lib:set({Tab, index}, index_positions(IndexInfo)), NewC = lists:keydelete(index, 1, Commit), mnesia_lib:set({Tab, commit_work}, mnesia_lib:sort_commit(NewC)); New -> Index = Old#index{pos_list = New}, + mnesia_lib:set({Tab, index_info}, Index), + mnesia_lib:set({Tab, index}, index_positions(Index)), NewC = lists:keyreplace(index, 1, Commit, Index), mnesia_lib:set({Tab, commit_work}, mnesia_lib:sort_commit(NewC)) @@ -353,33 +507,69 @@ del_index_info(Tab, Pos) -> db_put({ram, Ixt}, V) -> true = ?ets_insert(Ixt, V); +db_put({{ext, _, _} = Ext, Ixt}, V) -> + mnesia_lib:db_put(Ext, Ixt, V); db_put({dets, Ixt}, V) -> ok = dets:insert(Ixt, V). -db_get({ram, Ixt}, K) -> - ?ets_lookup(Ixt, K); +db_get({ram, _}=Ixt, IxKey) -> + Pat = [{{{IxKey, '$1'}}, [], [{element, 1, '$_'}]}], + db_select(Ixt, Pat); +db_get({{ext,_,_} = _Storage, {_,_,{_,Type}}} = Ixt, IxKey) -> + Pat = case Type of + ordered -> [{{{IxKey, '$1'}}, [], [{element, 1, '$_'}]}]; + bag -> [{{IxKey, '_'}, [], ['$_']}] + end, + db_select(Ixt, Pat); db_get({dets, Ixt}, K) -> dets:lookup(Ixt, K). +db_erase({ram, Ixt}, K) -> + ?ets_delete(Ixt, K); +db_erase({{ext,_,_} = Ext, Ixt}, K) -> + mnesia_lib:db_erase(Ext, Ixt, K); +db_erase({dets, Ixt}, K) -> + dets:delete(Ixt, K). + db_match_erase({ram, Ixt}, Pat) -> true = ?ets_match_delete(Ixt, Pat); +db_match_erase({{ext,_,_} = Ext, Ixt}, Pat) -> + mnesia_lib:db_match_erase(Ext, Ixt, Pat); db_match_erase({dets, Ixt}, Pat) -> ok = dets:match_delete(Ixt, Pat). -db_match({ram, Ixt}, Pat) -> - ?ets_match(Ixt, Pat); -db_match({dets, Ixt}, Pat) -> - dets:match(Ixt, Pat). +db_select({ram, Ixt}, Pat) -> + ets:select(Ixt, Pat); +db_select({{ext,_,_} = Ext, Ixt}, Pat) -> + mnesia_lib:db_select(Ext, Ixt, Pat); +db_select({dets, Ixt}, Pat) -> + dets:select(Ixt, Pat). + get_index_table(Tab, Pos) -> get_index_table(Tab, val({Tab, storage_type}), Pos). -get_index_table(Tab, ram_copies, Pos) -> - {ram, val({Tab, {index, Pos}})}; -get_index_table(Tab, disc_copies, Pos) -> - {ram, val({Tab, {index, Pos}})}; -get_index_table(Tab, disc_only_copies, Pos) -> - {dets, val({Tab, {index, Pos}})}; -get_index_table(_Tab, unknown, _Pos) -> - unknown. - +get_index_table(Tab, _Storage, Pos) -> + #index{pos_list = PosL} = val({Tab, index_info}), + {_IxType, Ixt} = pick_index(PosL, Tab, Pos), + Ixt. + +index_vals_f(Storage, Tab, {_} = Pos) -> + index_vals_f(Storage, Tab, + lists:keyfind(Pos, 1, mnesia_schema:index_plugins())); +index_vals_f(_Storage, Tab, {Pos,M,F}) -> + fun(Obj) -> + M:F(Tab, Pos, Obj) + end; +index_vals_f(Storage, Tab, Pos) when is_integer(Pos) -> + case mnesia_lib:semantics(Storage, index_fun) of + undefined -> + fun(Obj) -> + [element(Pos, Obj)] + end; + F when is_function(F, 4) -> + {ext, Alias, _Mod} = Storage, + fun(Obj) -> + F(Alias, Tab, Pos, Obj) + end + end. diff --git a/lib/mnesia/src/mnesia_kernel_sup.erl b/lib/mnesia/src/mnesia_kernel_sup.erl index 1583e5e1f7..a761d5eed0 100644 --- a/lib/mnesia/src/mnesia_kernel_sup.erl +++ b/lib/mnesia/src/mnesia_kernel_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2009. All Rights Reserved. +%% Copyright Ericsson AB 1997-2017. 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. @@ -43,7 +43,6 @@ init([]) -> worker_spec(mnesia_recover, timer:minutes(3), [gen_server]), worker_spec(mnesia_tm, timer:seconds(30), ProcLib), supervisor_spec(mnesia_checkpoint_sup), - supervisor_spec(mnesia_snmp_sup), worker_spec(mnesia_controller, timer:seconds(3), [gen_server]), worker_spec(mnesia_late_loader, timer:seconds(3), ProcLib) ], diff --git a/lib/mnesia/src/mnesia_late_loader.erl b/lib/mnesia/src/mnesia_late_loader.erl index bd21bdc347..e4f8dcf2b9 100644 --- a/lib/mnesia/src/mnesia_late_loader.erl +++ b/lib/mnesia/src/mnesia_late_loader.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2009. All Rights Reserved. +%% Copyright Ericsson AB 1998-2016. 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. @@ -87,13 +87,13 @@ loop(State) -> loop(State); {system, From, Msg} -> - mnesia_lib:dbg_out("~p got {system, ~p, ~p}~n", + mnesia_lib:dbg_out("~p got {system, ~p, ~tp}~n", [?SERVER_NAME, From, Msg]), Parent = State#state.supervisor, sys:handle_system_msg(Msg, From, Parent, ?MODULE, [], State); Msg -> - mnesia_lib:error("~p got unexpected message: ~p~n", + mnesia_lib:error("~p got unexpected message: ~tp~n", [?SERVER_NAME, Msg]), loop(State) end. diff --git a/lib/mnesia/src/mnesia_lib.erl b/lib/mnesia/src/mnesia_lib.erl index 77c7a7638d..53fdd76de8 100644 --- a/lib/mnesia/src/mnesia_lib.erl +++ b/lib/mnesia/src/mnesia_lib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2014. All Rights Reserved. +%% Copyright Ericsson AB 1996-2016. 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. @@ -54,6 +54,7 @@ db_erase_tab/2, db_first/1, db_first/2, + db_foldl/3, db_foldl/4, db_foldl/6, db_last/1, db_last/2, db_fixtable/3, @@ -135,6 +136,7 @@ set_local_content_whereabouts/1, set_remote_where_to_read/1, set_remote_where_to_read/2, + semantics/2, show/1, show/2, sort_commit/1, @@ -144,6 +146,7 @@ tab2tmp/1, tab2dcd/1, tab2dcl/1, + tab2logtmp/1, to_list/1, union/2, uniq/1, @@ -151,6 +154,8 @@ unset/1, %% update_counter/2, val/1, + validate_key/2, + validate_record/2, vcore/0, vcore/1, verbose/2, @@ -319,15 +324,41 @@ tab2dcd(Tab) -> %% Disc copies data tab2dcl(Tab) -> %% Disc copies log dir(lists:concat([Tab, ".DCL"])). +tab2logtmp(Tab) -> %% Disc copies log + dir(lists:concat([Tab, ".LOGTMP"])). + storage_type_at_node(Node, Tab) -> search_key(Node, [{disc_copies, val({Tab, disc_copies})}, {ram_copies, val({Tab, ram_copies})}, - {disc_only_copies, val({Tab, disc_only_copies})}]). + {disc_only_copies, val({Tab, disc_only_copies})}| + wrap_external(val({Tab, external_copies}))]). cs_to_storage_type(Node, Cs) -> search_key(Node, [{disc_copies, Cs#cstruct.disc_copies}, {ram_copies, Cs#cstruct.ram_copies}, - {disc_only_copies, Cs#cstruct.disc_only_copies}]). + {disc_only_copies, Cs#cstruct.disc_only_copies} | + wrap_external(Cs#cstruct.external_copies)]). + +-define(native(T), T==ram_copies; T==disc_copies; T==disc_only_copies). + +semantics({ext,Alias,Mod}, Item) -> + Mod:semantics(Alias, Item); +semantics({Alias,Mod}, Item) -> + Mod:semantics(Alias, Item); +semantics(Type, storage) when ?native(Type) -> + Type; +semantics(Type, types) when ?native(Type) -> + [set, ordered_set, bag]; +semantics(disc_only_copies, index_types) -> + [bag]; +semantics(Type, index_types) when ?native(Type) -> + [bag, ordered]; +semantics(_, _) -> + undefined. + + +wrap_external(L) -> + [{{ext,Alias,Mod},Ns} || {{Alias,Mod},Ns} <- L]. schema_cs_to_storage_type(Node, Cs) -> case cs_to_storage_type(Node, Cs) of @@ -335,7 +366,6 @@ schema_cs_to_storage_type(Node, Cs) -> Other -> Other end. - search_key(Key, [{Val, List} | Tail]) -> case lists:member(Key, List) of true -> Val; @@ -344,6 +374,33 @@ search_key(Key, [{Val, List} | Tail]) -> search_key(_Key, []) -> unknown. +validate_key(Tab, Key) -> + case ?catch_val({Tab, record_validation}) of + {RecName, Arity, Type} -> + {RecName, Arity, Type}; + {RecName, Arity, Type, Alias, Mod} -> + %% external type + Mod:validate_key(Alias, Tab, RecName, Arity, Type, Key); + {'EXIT', _} -> + mnesia:abort({no_exists, Tab}) + end. + + +validate_record(Tab, Obj) -> + case ?catch_val({Tab, record_validation}) of + {RecName, Arity, Type} + when tuple_size(Obj) == Arity, RecName == element(1, Obj) -> + {RecName, Arity, Type}; + {RecName, Arity, Type, Alias, Mod} + when tuple_size(Obj) == Arity, RecName == element(1, Obj) -> + %% external type + Mod:validate_record(Alias, Tab, RecName, Arity, Type, Obj); + {'EXIT', _} -> + mnesia:abort({no_exists, Tab}); + _ -> + mnesia:abort({bad_type, Obj}) + end. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% ops, we've got some global variables here :-) @@ -403,15 +460,16 @@ other_val_1(Var) -> _ -> error end. +-spec pr_other(_) -> no_return(). pr_other(Var) -> Why = case is_running() of no -> {node_not_running, node()}; _ -> {no_exists, Var} end, - verbose("~p (~p) val(mnesia_gvar, ~w) -> ~p ~n", + verbose("~p (~tp) val(mnesia_gvar, ~tw) -> ~p ~tp ~n", [self(), process_info(self(), registered_name), - Var, Why]), + Var, Why, erlang:get_stacktrace()]), mnesia:abort(Why). %% Some functions for list valued variables @@ -548,10 +606,16 @@ read_counter(Name) -> ?ets_lookup_element(mnesia_stats, Name, 2). cs_to_nodes(Cs) -> + ext_nodes(Cs#cstruct.external_copies) ++ Cs#cstruct.disc_only_copies ++ Cs#cstruct.disc_copies ++ Cs#cstruct.ram_copies. +ext_nodes(Ext) -> + lists:flatmap(fun({_, Ns}) -> + Ns + end, Ext). + overload_types() -> [mnesia_tm, mnesia_dump_log]. @@ -590,7 +654,7 @@ coredump() -> coredump(CrashInfo) -> Core = mkcore(CrashInfo), Out = core_file(), - important("Writing Mnesia core to file: ~p...~p~n", [Out, CrashInfo]), + important("Writing Mnesia core to file: ~tp...~tp~n", [Out, CrashInfo]), _ = file:write_file(Out, Core), Out. @@ -747,7 +811,7 @@ view(File) -> true -> view(File, dat); false -> - case suffix([".LOG", ".BUP", ".ETS"], File) of + case suffix([".LOG", ".BUP", ".ETS", ".LOGTMP"], File) of true -> view(File, log); false -> @@ -780,7 +844,7 @@ vcore() -> case file:list_dir(Cwd) of {ok, Files}-> CoreFiles = lists:sort(lists:zf(Filter, Files)), - show("Mnesia core files: ~p~n", [CoreFiles]), + show("Mnesia core files: ~tp~n", [CoreFiles]), vcore(lists:last(CoreFiles)); Error -> Error @@ -789,17 +853,17 @@ vcore() -> vcore(Bin) when is_binary(Bin) -> Core = binary_to_term(Bin), Fun = fun({Item, Info}) -> - show("***** ~p *****~n", [Item]), + show("***** ~tp *****~n", [Item]), case catch vcore_elem({Item, Info}) of {'EXIT', Reason} -> - show("{'EXIT', ~p}~n", [Reason]); + show("{'EXIT', ~tp}~n", [Reason]); _ -> ok end end, lists:foreach(Fun, Core); vcore(File) -> - show("~n***** Mnesia core: ~p *****~n", [File]), + show("~n***** Mnesia core: ~tp *****~n", [File]), case file:read_file(File) of {ok, Bin} -> vcore(Bin); @@ -815,7 +879,7 @@ vcore_elem({schema_file, {ok, B}}) -> vcore_elem({logfile, {ok, BinList}}) -> Fun = fun({F, Info}) -> - show("----- logfile: ~p -----~n", [F]), + show("----- logfile: ~tp -----~n", [F]), case Info of {ok, B} -> Fname = "/tmp/mnesia_vcore_elem.TMP", @@ -823,7 +887,7 @@ vcore_elem({logfile, {ok, BinList}}) -> mnesia_log:view(Fname), file:delete(Fname); _ -> - show("~p~n", [Info]) + show("~tp~n", [Info]) end end, lists:foreach(Fun, BinList); @@ -831,12 +895,12 @@ vcore_elem({logfile, {ok, BinList}}) -> vcore_elem({crashinfo, {Format, Args}}) -> show(Format, Args); vcore_elem({gvar, L}) -> - show("~p~n", [lists:sort(L)]); + show("~tp~n", [lists:sort(L)]); vcore_elem({transactions, Info}) -> mnesia_tm:display_info(user, Info); vcore_elem({_Item, Info}) -> - show("~p~n", [Info]). + show("~tp~n", [Info]). fix_error(X) -> set(last_error, X), %% for debugabililty @@ -921,20 +985,7 @@ random_time(Retries, _Counter0) -> UpperLimit = 500, Dup = Retries * Retries, MaxIntv = trunc(UpperLimit * (1-(50/((Dup)+50)))), - - case get(random_seed) of - undefined -> - _ = random:seed(erlang:unique_integer(), - erlang:monotonic_time(), - erlang:unique_integer()), - Time = Dup + random:uniform(MaxIntv), - %% dbg_out("---random_test rs ~w max ~w val ~w---~n", [Retries, MaxIntv, Time]), - Time; - _ -> - Time = Dup + random:uniform(MaxIntv), - %% dbg_out("---random_test rs ~w max ~w val ~w---~n", [Retries, MaxIntv, Time]), - Time - end. + Dup + rand:uniform(MaxIntv). report_system_event(Event0) -> Event = {mnesia_system_event, Event0}, @@ -967,7 +1018,7 @@ report_system_event({'EXIT', Reason}, Event) -> end; Error -> - Msg = "Mnesia(~p): Cannot report event ~p: ~p (~p)~n", + Msg = "Mnesia(~tp): Cannot report event ~tp: ~tp (~tp)~n", error_logger:format(Msg, [node(), Event, Reason, Error]) end, ok; @@ -1056,18 +1107,24 @@ db_get(Tab, Key) -> db_get(val({Tab, storage_type}), Tab, Key). db_get(ram_copies, Tab, Key) -> ?ets_lookup(Tab, Key); db_get(disc_copies, Tab, Key) -> ?ets_lookup(Tab, Key); -db_get(disc_only_copies, Tab, Key) -> dets:lookup(Tab, Key). +db_get(disc_only_copies, Tab, Key) -> dets:lookup(Tab, Key); +db_get({ext, Alias, Mod}, Tab, Key) -> + Mod:lookup(Alias, Tab, Key). db_init_chunk(Tab) -> db_init_chunk(val({Tab, storage_type}), Tab, 1000). db_init_chunk(Tab, N) -> db_init_chunk(val({Tab, storage_type}), Tab, N). +db_init_chunk({ext, Alias, Mod}, Tab, N) -> + Mod:select(Alias, Tab, [{'_', [], ['$_']}], N); db_init_chunk(disc_only_copies, Tab, N) -> dets:select(Tab, [{'_', [], ['$_']}], N); db_init_chunk(_, Tab, N) -> ets:select(Tab, [{'_', [], ['$_']}], N). +db_chunk({ext, _Alias, Mod}, State) -> + Mod:select(State); db_chunk(disc_only_copies, State) -> dets:select(State); db_chunk(_, State) -> @@ -1078,7 +1135,9 @@ db_put(Tab, Val) -> db_put(ram_copies, Tab, Val) -> ?ets_insert(Tab, Val), ok; db_put(disc_copies, Tab, Val) -> ?ets_insert(Tab, Val), ok; -db_put(disc_only_copies, Tab, Val) -> dets:insert(Tab, Val). +db_put(disc_only_copies, Tab, Val) -> dets:insert(Tab, Val); +db_put({ext, Alias, Mod}, Tab, Val) -> + Mod:insert(Alias, Tab, Val). db_match_object(Tab, Pat) -> db_match_object(val({Tab, storage_type}), Tab, Pat). @@ -1087,12 +1146,36 @@ db_match_object(Storage, Tab, Pat) -> try case Storage of disc_only_copies -> dets:match_object(Tab, Pat); + {ext, Alias, Mod} -> Mod:select(Alias, Tab, [{Pat, [], ['$_']}]); _ -> ets:match_object(Tab, Pat) end after db_fixtable(Storage, Tab, false) end. +db_foldl(Fun, Acc, Tab) -> + db_foldl(val({Tab, storage_type}), Fun, Acc, Tab). + +db_foldl(Storage, Fun, Acc, Tab) -> + Limit = mnesia_monitor:get_env(fold_chunk_size), + db_foldl(Storage, Fun, Acc, Tab, [{'_', [], ['$_']}], Limit). + +db_foldl(ram_copies, Fun, Acc, Tab, Pat, Limit) -> + mnesia_lib:db_fixtable(ram_copies, Tab, true), + try select_foldl(db_select_init(ram_copies, Tab, Pat, Limit), + Fun, Acc, ram_copies) + after + mnesia_lib:db_fixtable(ram_copies, Tab, false) + end; +db_foldl(Storage, Fun, Acc, Tab, Pat, Limit) -> + select_foldl(mnesia_lib:db_select_init(Storage, Tab, Pat, Limit), Fun, Acc, Storage). + +select_foldl({Objs, Cont}, Fun, Acc, Storage) -> + select_foldl(mnesia_lib:db_select_cont(Storage, Cont, []), + Fun, lists:foldl(Fun, Acc, Objs), Storage); +select_foldl('$end_of_table', _, Acc, _) -> + Acc. + db_select(Tab, Pat) -> db_select(val({Tab, storage_type}), Tab, Pat). @@ -1101,17 +1184,23 @@ db_select(Storage, Tab, Pat) -> try case Storage of disc_only_copies -> dets:select(Tab, Pat); + {ext, Alias, Mod} -> Mod:select(Alias, Tab, Pat); _ -> ets:select(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) -> dets:select(Tab, Pat, Limit); db_select_init(_, Tab, Pat, Limit) -> ets:select(Tab, Pat, Limit). +db_select_cont({ext, _Alias, Mod}, Cont0, Ms) -> + Cont = Mod:repair_continuation(Cont0, Ms), + Mod:select(Cont); db_select_cont(disc_only_copies, Cont0, Ms) -> Cont = dets:repair_continuation(Cont0, Ms), dets:select(Cont); @@ -1128,13 +1217,18 @@ db_fixtable(disc_copies, Tab, Bool) -> db_fixtable(dets, Tab, Bool) -> dets:safe_fixtable(Tab, Bool); db_fixtable(disc_only_copies, Tab, Bool) -> - dets:safe_fixtable(Tab, Bool). + dets:safe_fixtable(Tab, Bool); +db_fixtable({ext, Alias, Mod}, Tab, Bool) -> + Mod:fixtable(Alias, Tab, Bool). db_erase(Tab, Key) -> db_erase(val({Tab, storage_type}), Tab, Key). db_erase(ram_copies, Tab, Key) -> ?ets_delete(Tab, Key), ok; db_erase(disc_copies, Tab, Key) -> ?ets_delete(Tab, Key), ok; -db_erase(disc_only_copies, Tab, Key) -> dets:delete(Tab, Key). +db_erase(disc_only_copies, Tab, Key) -> dets:delete(Tab, Key); +db_erase({ext, Alias, Mod}, Tab, Key) -> + Mod:delete(Alias, Tab, Key), + ok. db_match_erase(Tab, '_') -> db_delete_all(val({Tab, storage_type}),Tab); @@ -1142,7 +1236,10 @@ db_match_erase(Tab, Pat) -> db_match_erase(val({Tab, storage_type}), Tab, Pat). db_match_erase(ram_copies, Tab, Pat) -> ?ets_match_delete(Tab, Pat), ok; db_match_erase(disc_copies, Tab, Pat) -> ?ets_match_delete(Tab, Pat), ok; -db_match_erase(disc_only_copies, Tab, Pat) -> dets:match_delete(Tab, Pat). +db_match_erase(disc_only_copies, Tab, Pat) -> dets:match_delete(Tab, Pat); +db_match_erase({ext, Alias, Mod}, Tab, Pat) -> + Mod:match_delete(Alias, Tab, Pat), + ok. db_delete_all(ram_copies, Tab) -> ets:delete_all_objects(Tab); db_delete_all(disc_copies, Tab) -> ets:delete_all_objects(Tab); @@ -1152,31 +1249,41 @@ db_first(Tab) -> db_first(val({Tab, storage_type}), Tab). db_first(ram_copies, Tab) -> ?ets_first(Tab); db_first(disc_copies, Tab) -> ?ets_first(Tab); -db_first(disc_only_copies, Tab) -> dets:first(Tab). +db_first(disc_only_copies, Tab) -> dets:first(Tab); +db_first({ext, Alias, Mod}, Tab) -> + Mod:first(Alias, Tab). db_next_key(Tab, Key) -> db_next_key(val({Tab, storage_type}), Tab, Key). db_next_key(ram_copies, Tab, Key) -> ?ets_next(Tab, Key); db_next_key(disc_copies, Tab, Key) -> ?ets_next(Tab, Key); -db_next_key(disc_only_copies, Tab, Key) -> dets:next(Tab, Key). +db_next_key(disc_only_copies, Tab, Key) -> dets:next(Tab, Key); +db_next_key({ext, Alias, Mod}, Tab, Key) -> + Mod:next(Alias, Tab, Key). db_last(Tab) -> db_last(val({Tab, storage_type}), Tab). db_last(ram_copies, Tab) -> ?ets_last(Tab); db_last(disc_copies, Tab) -> ?ets_last(Tab); -db_last(disc_only_copies, Tab) -> dets:first(Tab). %% Dets don't have order +db_last(disc_only_copies, Tab) -> dets:first(Tab); %% Dets don't have order +db_last({ext, Alias, Mod}, Tab) -> + Mod:last(Alias, Tab). db_prev_key(Tab, Key) -> db_prev_key(val({Tab, storage_type}), Tab, Key). db_prev_key(ram_copies, Tab, Key) -> ?ets_prev(Tab, Key); db_prev_key(disc_copies, Tab, Key) -> ?ets_prev(Tab, Key); -db_prev_key(disc_only_copies, Tab, Key) -> dets:next(Tab, Key). %% Dets don't have order +db_prev_key(disc_only_copies, Tab, Key) -> dets:next(Tab, Key); %% Dets don't have order +db_prev_key({ext, Alias, Mod}, Tab, Key) -> + Mod:prev(Alias, Tab, Key). db_slot(Tab, Pos) -> db_slot(val({Tab, storage_type}), Tab, Pos). db_slot(ram_copies, Tab, Pos) -> ?ets_slot(Tab, Pos); db_slot(disc_copies, Tab, Pos) -> ?ets_slot(Tab, Pos); -db_slot(disc_only_copies, Tab, Pos) -> dets:slot(Tab, Pos). +db_slot(disc_only_copies, Tab, Pos) -> dets:slot(Tab, Pos); +db_slot({ext, Alias, Mod}, Tab, Pos) -> + Mod:slot(Alias, Tab, Pos). db_update_counter(Tab, C, Val) -> db_update_counter(val({Tab, storage_type}), Tab, C, Val). @@ -1185,13 +1292,16 @@ db_update_counter(ram_copies, Tab, C, Val) -> db_update_counter(disc_copies, Tab, C, Val) -> ?ets_update_counter(Tab, C, Val); db_update_counter(disc_only_copies, Tab, C, Val) -> - dets:update_counter(Tab, C, Val). + dets:update_counter(Tab, C, Val); +db_update_counter({ext, Alias, Mod}, Tab, C, Val) -> + Mod:update_counter(Alias, Tab, C, Val). db_erase_tab(Tab) -> db_erase_tab(val({Tab, storage_type}), Tab). db_erase_tab(ram_copies, Tab) -> ?ets_delete_table(Tab); db_erase_tab(disc_copies, Tab) -> ?ets_delete_table(Tab); -db_erase_tab(disc_only_copies, _Tab) -> ignore. +db_erase_tab(disc_only_copies, _Tab) -> ignore; +db_erase_tab({ext, _Alias, _Mod}, _Tab) -> ignore. %% assuming that Tab is a valid ets-table dets_to_ets(Tabname, Tab, File, Type, Rep, Lock) -> diff --git a/lib/mnesia/src/mnesia_loader.erl b/lib/mnesia/src/mnesia_loader.erl index da8549be50..4c6336cb73 100644 --- a/lib/mnesia/src/mnesia_loader.erl +++ b/lib/mnesia/src/mnesia_loader.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2014. All Rights Reserved. +%% Copyright Ericsson AB 1998-2016. 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. @@ -46,7 +46,7 @@ val(Var) -> disc_load_table(Tab, Reason) -> Storage = val({Tab, storage_type}), Type = val({Tab, setorbag}), - dbg_out("Getting table ~p (~p) from disc: ~p~n", + dbg_out("Getting table ~tp (~p) from disc: ~tp~n", [Tab, Storage, Reason]), ?eval_debug_fun({?MODULE, do_get_disc_copy}, [{tab, Tab}, @@ -56,7 +56,7 @@ disc_load_table(Tab, Reason) -> do_get_disc_copy2(Tab, Reason, Storage, Type). do_get_disc_copy2(Tab, _Reason, Storage, _Type) when Storage == unknown -> - verbose("Local table copy of ~p has recently been deleted, ignored.~n", + verbose("Local table copy of ~tp has recently been deleted, ignored.~n", [Tab]), {not_loaded, storage_unknown}; do_get_disc_copy2(Tab, Reason, Storage, Type) when Storage == disc_copies -> @@ -147,6 +147,19 @@ do_get_disc_copy2(Tab, Reason, Storage, Type) when Storage == disc_only_copies - {error, Error} -> {not_loaded, {"Failed to create dets table", Error}} end + end; + +do_get_disc_copy2(Tab, Reason, Storage = {ext, Alias, Mod}, _Type) -> + Cs = val({Tab, cstruct}), + case mnesia_monitor:unsafe_create_external(Tab, Alias, Mod, Cs) of + ok -> + ok = ext_load_table(Mod, Alias, Tab, Reason), + mnesia_index:init_index(Tab, Storage), + set({Tab, load_node}, node()), + set({Tab, load_reason}, Reason), + {loaded, ok}; + Other -> + {not_loaded, Other} end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -180,27 +193,26 @@ do_get_disc_copy2(Tab, Reason, Storage, Type) when Storage == disc_only_copies - -define(MAX_RAM_TRANSFERS, (?MAX_RAM_FILE_SIZE div ?MAX_TRANSFER_SIZE) + 1). -define(MAX_NOPACKETS, 20). -net_load_table(Tab, Reason, Ns, Cs) - when Reason == {dumper,add_table_copy} -> +net_load_table(Tab, {dumper,{add_table_copy, _}}=Reason, Ns, Cs) -> try_net_load_table(Tab, Reason, Ns, Cs); net_load_table(Tab, Reason, Ns, _Cs) -> try_net_load_table(Tab, Reason, Ns, val({Tab, cstruct})). try_net_load_table(Tab, _Reason, [], _Cs) -> - verbose("Copy failed. No active replicas of ~p are available.~n", [Tab]), + verbose("Copy failed. No active replicas of ~tp are available.~n", [Tab]), {not_loaded, none_active}; try_net_load_table(Tab, Reason, Ns, Cs) -> Storage = mnesia_lib:cs_to_storage_type(node(), Cs), do_get_network_copy(Tab, Reason, Ns, Storage, Cs). do_get_network_copy(Tab, _Reason, _Ns, unknown, _Cs) -> - verbose("Local table copy of ~p has recently been deleted, ignored.~n", [Tab]), + verbose("Local table copy of ~tp has recently been deleted, ignored.~n", [Tab]), {not_loaded, storage_unknown}; do_get_network_copy(Tab, Reason, Ns, Storage, Cs) -> [Node | Tail] = Ns, case lists:member(Node,val({current, db_nodes})) of true -> - dbg_out("Getting table ~p (~p) from node ~p: ~p~n", + dbg_out("Getting table ~tp (~p) from node ~p: ~tp~n", [Tab, Storage, Node, Reason]), ?eval_debug_fun({?MODULE, do_get_network_copy}, [{tab, Tab}, {reason, Reason}, @@ -210,7 +222,7 @@ do_get_network_copy(Tab, Reason, Ns, Storage, Cs) -> set({Tab, load_node}, Node), set({Tab, load_reason}, Reason), mnesia_controller:i_have_tab(Tab), - dbg_out("Table ~p copied from ~p to ~p~n", [Tab, Node, node()]), + dbg_out("Table ~tp copied from ~p to ~p~n", [Tab, Node, node()]), {loaded, ok}; Err = {error, _} when element(1, Reason) == dumper -> {not_loaded,Err}; @@ -233,7 +245,8 @@ do_snmpify(Tab, Us, Storage) -> set({Tab, {index, snmp}}, Snmp). %% Start the recieiver -init_receiver(Node, Tab, Storage, Cs, Reas={dumper,add_table_copy}) -> +init_receiver(Node, Tab, Storage, Cs, Reas={dumper,{add_table_copy, Tid}}) -> + rpc:call(Node, mnesia_lib, set, [{?MODULE, active_trans}, Tid]), case start_remote_sender(Node, Tab, Storage) of {SenderPid, TabSize, DetsData} -> start_receiver(Tab,Storage,Cs,SenderPid,TabSize,DetsData,Reas); @@ -259,7 +272,7 @@ init_receiver(Node, Tab,Storage,Cs,Reason) -> true = lists:member(Node, Active), {SenderPid, TabSize, DetsData} = start_remote_sender(Node,Tab,Storage), - Init = table_init_fun(SenderPid), + Init = table_init_fun(SenderPid, Storage), Args = [self(),Tab,Storage,Cs,SenderPid, TabSize,DetsData,Init], Pid = spawn_link(?MODULE, spawned_receiver, Args), @@ -273,12 +286,12 @@ init_receiver(Node, Tab,Storage,Cs,Reason) -> element(1,Reason) == dumper -> {error,Result}; {atomic, {error,Result}} -> - fatal("Cannot create table ~p: ~p~n", + fatal("Cannot create table ~tp: ~tp~n", [[Tab, Storage], Result]); {atomic, Result} -> Result; {aborted, nomore} -> restart; {aborted, _Reas} -> - verbose("Receiver failed on ~p from ~p:~nReason: ~p~n", + verbose("Receiver failed on ~tp from ~p:~nReason: ~tp~n", [Tab,Node,_Reas]), down %% either this node or sender is dying end, @@ -289,26 +302,33 @@ start_remote_sender(Node,Tab,Storage) -> mnesia_controller:start_remote_sender(Node, Tab, self(), Storage), put(mnesia_table_sender_node, {Tab, Node}), receive - {SenderPid, {first, TabSize}} -> + {SenderPid, {first, _} = Msg} + when is_pid(SenderPid), element(1, Storage) == ext -> + {ext, Alias, Mod} = Storage, + {Sz, Data} = Mod:receiver_first_message(SenderPid, Msg, Alias, Tab), + {SenderPid, Sz, Data}; + {SenderPid, {first, TabSize}} =_M1 -> {SenderPid, TabSize, false}; {SenderPid, {first, TabSize, DetsData}} -> {SenderPid, TabSize, DetsData}; %% Protocol conversion hack {copier_done, Node} -> - verbose("Sender of table ~p crashed on node ~p ~n", [Tab, Node]), + verbose("Sender of table ~tp crashed on node ~p ~n", [Tab, Node]), down(Tab, Storage) end. -table_init_fun(SenderPid) -> +table_init_fun(SenderPid, Storage) -> fun(read) -> Receiver = self(), SenderPid ! {Receiver, more}, - get_data(SenderPid, Receiver) + get_data(SenderPid, Receiver, Storage); + (close) -> + ok end. %% Add_table_copy get's it's own locks. -start_receiver(Tab,Storage,Cs,SenderPid,TabSize,DetsData,{dumper,add_table_copy}) -> - Init = table_init_fun(SenderPid), +start_receiver(Tab,Storage,Cs,SenderPid,TabSize,DetsData,{dumper,{add_table_copy,_}}) -> + Init = table_init_fun(SenderPid, Storage), case do_init_table(Tab,Storage,Cs,SenderPid,TabSize,DetsData,self(), Init) of Err = {error, _} -> SenderPid ! {copier_done, node()}, @@ -322,9 +342,12 @@ spawned_receiver(ReplyTo,Tab,Storage,Cs, SenderPid,TabSize,DetsData, Init) -> Done = do_init_table(Tab,Storage,Cs, SenderPid,TabSize,DetsData, ReplyTo, Init), - ReplyTo ! {self(),Done}, - unlink(ReplyTo), - unlink(whereis(mnesia_controller)), + try + ReplyTo ! {self(),Done}, + unlink(ReplyTo), + unlink(whereis(mnesia_controller)) + catch _:_ -> ok %% avoid error reports when stopping down mnesia + end, exit(normal). wait_on_load_complete(Pid) -> @@ -351,7 +374,7 @@ do_init_table(Tab,Storage,Cs,SenderPid, tab_receiver(Node,Tab,Storage,Cs,OrigTabRec); Reason -> Msg = "[d]ets:init table failed", - verbose("~s: ~p: ~p~n", [Msg, Tab, Reason]), + verbose("~ts: ~tp: ~tp~n", [Msg, Tab, Reason]), down(Tab, Storage) end; Error -> @@ -391,7 +414,15 @@ create_table(Tab, TabSize, Storage, Cs) -> {Storage, Tab}; Else -> Else - end + end; + element(1, Storage) == ext -> + {_, Alias, Mod} = Storage, + case mnesia_monitor:unsafe_create_external(Tab, Alias, Mod, Cs) of + ok -> + {Storage, Tab}; + Else -> + Else + end end. tab_receiver(Node, Tab, Storage, Cs, OrigTabRec) -> @@ -401,7 +432,7 @@ tab_receiver(Node, Tab, Storage, Cs, OrigTabRec) -> %% Protocol conversion hack {copier_done, Node} -> - verbose("Sender of table ~p crashed on node ~p ~n", [Tab, Node]), + verbose("Sender of table ~tp crashed on node ~p ~n", [Tab, Node]), down(Tab, Storage); {'EXIT', Pid, Reason} -> @@ -409,21 +440,25 @@ tab_receiver(Node, Tab, Storage, Cs, OrigTabRec) -> tab_receiver(Node, Tab, Storage, Cs, OrigTabRec) end. -make_table_fun(Pid, TabRec) -> +make_table_fun(Pid, TabRec, Storage) -> fun(close) -> ok; + ({read, Msg}) -> + Pid ! {TabRec, Msg}, + get_data(Pid, TabRec, Storage); (read) -> - get_data(Pid, TabRec) + get_data(Pid, TabRec, Storage) end. -get_data(Pid, TabRec) -> +get_data(Pid, TabRec, Storage) -> receive {Pid, {more_z, CompressedRecs}} when is_binary(CompressedRecs) -> - Pid ! {TabRec, more}, - {zlib_uncompress(CompressedRecs), make_table_fun(Pid,TabRec)}; + maybe_reply(Pid, {TabRec, more}, Storage), + {zlib_uncompress(CompressedRecs), + make_table_fun(Pid, TabRec, Storage)}; {Pid, {more, Recs}} -> - Pid ! {TabRec, more}, - {Recs, make_table_fun(Pid,TabRec)}; + maybe_reply(Pid, {TabRec, more}, Storage), + {Recs, make_table_fun(Pid, TabRec, Storage)}; {Pid, no_more} -> end_of_input; {copier_done, Node} -> @@ -431,13 +466,48 @@ get_data(Pid, TabRec) -> Node -> {copier_done, Node}; _ -> - get_data(Pid, TabRec) + get_data(Pid, TabRec, Storage) end; {'EXIT', Pid, Reason} -> handle_exit(Pid, Reason), - get_data(Pid, TabRec) + get_data(Pid, TabRec, Storage) + end. + +maybe_reply(_, _, {ext, _, _}) -> + ignore; +maybe_reply(Pid, Msg, _) -> + Pid ! Msg. + +ext_init_table(Alias, Mod, Tab, Fun, State, Sender) -> + ok = ext_load_table(Mod, Alias, Tab, {net_load, node(Sender)}), + ext_init_table(read, Alias, Mod, Tab, Fun, State, Sender). + +ext_load_table(Mod, Alias, Tab, Reason) -> + CS = val({Tab, cstruct}), + Mod:load_table(Alias, Tab, Reason, mnesia_schema:cs2list(CS)). + + +ext_init_table(Action, Alias, Mod, Tab, Fun, State, Sender) -> + case Fun(Action) of + {copier_done, Node} -> + verbose("Receiver of table ~tp crashed on ~p (more)~n", [Tab, Node]), + down(Tab, {ext,Alias,Mod}); + {Data, NewFun} -> + case Mod:receive_data(Data, Alias, Tab, Sender, State) of + {more, NewState} -> + ext_init_table({read, more}, Alias, Mod, + Tab, NewFun, NewState, Sender); + {{more,Msg}, NewState} -> + ext_init_table({read, Msg}, Alias, Mod, + Tab, NewFun, NewState, Sender) + end; + end_of_input -> + Mod:receive_done(Alias, Tab, Sender, State), + ok = Fun(close) end. +init_table(Tab, {ext,Alias,Mod}, Fun, State, Sender) -> + ext_init_table(Alias, Mod, Tab, Fun, State, Sender); init_table(Tab, disc_only_copies, Fun, DetsInfo,Sender) -> ErtsVer = erlang:system_info(version), case DetsInfo of @@ -483,7 +553,7 @@ finish_copy(Storage,Tab,Cs,SenderPid,DatBin,OrigTabRec) -> ok; {error, Reason} -> Msg = "Failed to handle last", - verbose("~s: ~p: ~p~n", [Msg, Tab, Reason]), + verbose("~ts: ~tp: ~tp~n", [Msg, Tab, Reason]), down(Tab, Storage) end. @@ -567,7 +637,10 @@ handle_last({ram_copies, Tab}, _Type, DatBin) -> ok; false -> ok - end. + end; + +handle_last(_Storage, _Type, nobin) -> + ok. down(Tab, Storage) -> case Storage of @@ -578,7 +651,10 @@ down(Tab, Storage) -> disc_only_copies -> TmpFile = mnesia_lib:tab2tmp(Tab), mnesia_lib:dets_sync_close(Tab), - file:delete(TmpFile) + file:delete(TmpFile); + {ext, Alias, Mod} -> + catch Mod:close_table(Alias, Tab), + catch Mod:delete_table(Alias, Tab) end, mnesia_checkpoint:tm_del_copy(Tab, node()), mnesia_controller:sync_del_table_copy_whereabouts(Tab, node()), @@ -603,21 +679,35 @@ db_erase({ram_copies, Tab}, Key) -> db_erase({disc_copies, Tab}, Key) -> true = ?ets_delete(Tab, Key); db_erase({disc_only_copies, Tab}, Key) -> - ok = dets:delete(Tab, Key). + ok = dets:delete(Tab, Key); +db_erase({{ext, Alias, Mod}, Tab}, Key) -> + ok = Mod:delete(Alias, Tab, Key). db_match_erase({ram_copies, Tab} , Pat) -> true = ?ets_match_delete(Tab, Pat); db_match_erase({disc_copies, Tab} , Pat) -> true = ?ets_match_delete(Tab, Pat); db_match_erase({disc_only_copies, Tab}, Pat) -> - ok = dets:match_delete(Tab, Pat). + ok = dets:match_delete(Tab, Pat); +db_match_erase({{ext, Alias, Mod}, Tab}, Pat) -> + % "ets style" is to return true + % "dets style" is to return N | { error, Reason } + % or sometimes ok (?) + % be nice and accept both + case Mod:match_delete(Alias, Tab, Pat) of + N when is_integer (N) -> ok; + true -> ok; + ok -> ok + end. db_put({ram_copies, Tab}, Val) -> true = ?ets_insert(Tab, Val); db_put({disc_copies, Tab}, Val) -> true = ?ets_insert(Tab, Val); db_put({disc_only_copies, Tab}, Val) -> - ok = dets:insert(Tab, Val). + ok = dets:insert(Tab, Val); +db_put({{ext, Alias, Mod}, Tab}, Val) -> + ok = Mod:insert(Alias, Tab, Val). %% This code executes at the remote site where the data is %% executes in a special copier process. @@ -636,52 +726,68 @@ send_table(Pid, Tab, RemoteS) -> unknown -> {error, {no_exists, Tab}}; Storage -> - %% Send first - TabSize = mnesia:table_info(Tab, size), - KeysPerTransfer = calc_nokeys(Storage, Tab), - ChunkData = dets:info(Tab, bchunk_format), - - UseDetsChunk = - Storage == RemoteS andalso - Storage == disc_only_copies andalso - ChunkData /= undefined, - if - UseDetsChunk == true -> - DetsInfo = erlang:system_info(version), - Pid ! {self(), {first, TabSize, {DetsInfo, ChunkData}}}; - true -> - Pid ! {self(), {first, TabSize}} - end, + do_send_table(Pid, Tab, Storage, RemoteS) + end. - %% Debug info - put(mnesia_table_sender, {Tab, node(Pid), Pid}), - {Init, Chunk} = reader_funcs(UseDetsChunk, Tab, Storage, KeysPerTransfer), - - SendIt = fun() -> - {atomic, ok} = prepare_copy(Pid, Tab, Storage), - send_more(Pid, 1, Chunk, Init(), Tab), - finish_copy(Pid, Tab, Storage, RemoteS) - end, - - try SendIt() of - {_, receiver_died} -> ok; - {atomic, no_more} -> ok - catch - throw:receiver_died -> - cleanup_tab_copier(Pid, Storage, Tab), - ok; - error:Reason -> %% Prepare failed - cleanup_tab_copier(Pid, Storage, Tab), - {error, {tab_copier, Tab, {Reason, erlang:get_stacktrace()}}} - after - unlink(whereis(mnesia_tm)) - end +do_send_table(Pid, Tab, Storage, RemoteS) -> + {Init, Chunk} = + case Storage of + {ext, Alias, Mod} -> + case Mod:sender_init(Alias, Tab, RemoteS, Pid) of + {standard, I, C} -> + Pid ! {self(), {first, Mod:info(Alias, Tab, size)}}, + {I, C}; + {_, _} = Res -> + Res + end; + Storage -> + %% Send first + TabSize = mnesia:table_info(Tab, size), + KeysPerTransfer = calc_nokeys(Storage, Tab), + ChunkData = dets:info(Tab, bchunk_format), + + UseDetsChunk = + Storage == RemoteS andalso + Storage == disc_only_copies andalso + ChunkData /= undefined, + if + UseDetsChunk == true -> + DetsInfo = erlang:system_info(version), + Pid ! {self(), {first, TabSize, {DetsInfo, ChunkData}}}; + true -> + Pid ! {self(), {first, TabSize}} + end, + {_I, _C} = + reader_funcs(UseDetsChunk, Tab, Storage, KeysPerTransfer) + end, + %% Debug info + put(mnesia_table_sender, {Tab, node(Pid), Pid}), + + SendIt = fun() -> + NeedLock = need_lock(Tab), + {atomic, ok} = prepare_copy(Pid, Tab, Storage, NeedLock), + send_more(Pid, 1, Chunk, Init(), Tab, Storage), + finish_copy(Pid, Tab, Storage, RemoteS, NeedLock) + end, + + try SendIt() of + {_, receiver_died} -> ok; + {atomic, no_more} -> ok + catch + throw:receiver_died -> + cleanup_tab_copier(Pid, Storage, Tab), + ok; + error:Reason -> %% Prepare failed + cleanup_tab_copier(Pid, Storage, Tab), + {error, {tab_copier, Tab, {Reason, erlang:get_stacktrace()}}} + after + unlink(whereis(mnesia_tm)) end. -prepare_copy(Pid, Tab, Storage) -> +prepare_copy(Pid, Tab, Storage, NeedLock) -> Trans = fun() -> - mnesia:lock_table(Tab, load), + NeedLock andalso mnesia:lock_table(Tab, load), mnesia_subscr:subscribe(Pid, {table, Tab}), update_where_to_write(Tab, node(Pid)), mnesia_lib:db_fixtable(Storage, Tab, true), @@ -689,6 +795,21 @@ prepare_copy(Pid, Tab, Storage) -> end, mnesia:transaction(Trans). + +need_lock(Tab) -> + case ?catch_val({?MODULE, active_trans}) of + #tid{} = Tid -> + %% move_table_copy grabs it's own table-lock + %% do not deadlock with it + mnesia_lib:unset({?MODULE, active_trans}), + case mnesia_locker:get_held_locks(Tab) of + [{write, Tid}|_] -> false; + _Locks -> true + end; + _ -> + true + end. + update_where_to_write(Tab, Node) -> case val({Tab, access_mode}) of read_only -> @@ -710,23 +831,35 @@ update_where_to_write([H|T], Tab, AddNode) -> [{update_where_to_write, [add, Tab, AddNode], self()}]), update_where_to_write(T, Tab, AddNode). -send_more(Pid, N, Chunk, DataState, Tab) -> +send_more(Pid, N, Chunk, DataState, Tab, Storage) -> receive {NewPid, more} -> case send_packet(N - 1, NewPid, Chunk, DataState) of New when is_integer(New) -> New - 1; NewData -> - send_more(NewPid, ?MAX_NOPACKETS, Chunk, NewData, Tab) + send_more(NewPid, ?MAX_NOPACKETS, Chunk, NewData, + Tab, Storage) + end; + {NewPid, {more, Msg}} when element(1, Storage) == ext -> + {ext, Alias, Mod} = Storage, + {NewChunk, NewState} = + Mod:sender_handle_info(Msg, Alias, Tab, NewPid, DataState), + case send_packet(N - 1, NewPid, NewChunk, NewState) of + New when is_integer(New) -> + New -1; + NewData -> + send_more(NewPid, N, NewChunk, NewData, Tab, + Storage) end; {_NewPid, {old_protocol, Tab}} -> Storage = val({Tab, storage_type}), {Init, NewChunk} = reader_funcs(false, Tab, Storage, calc_nokeys(Storage, Tab)), - send_more(Pid, 1, NewChunk, Init(), Tab); + send_more(Pid, 1, NewChunk, Init(), Tab, Storage); {copier_done, Node} when Node == node(Pid)-> - verbose("Receiver of table ~p crashed on ~p (more)~n", [Tab, Node]), + verbose("Receiver of table ~tp crashed on ~p (more)~n", [Tab, Node]), throw(receiver_died) end. @@ -783,12 +916,18 @@ send_packet(N, Pid, Chunk, {Recs, Cont}) when N < ?MAX_NOPACKETS -> send_packet(_N, _Pid, _Chunk, DataState) -> DataState. -finish_copy(Pid, Tab, Storage, RemoteS) -> +finish_copy(Pid, Tab, Storage, RemoteS, NeedLock) -> RecNode = node(Pid), DatBin = dat2bin(Tab, Storage, RemoteS), + Node = node(Pid), Trans = fun() -> - mnesia:read_lock_table(Tab), + NeedLock andalso mnesia:read_lock_table(Tab), + %% Check that receiver is still alive + receive {copier_done, Node} -> + throw(receiver_died) + after 0 -> ok + end, A = val({Tab, access_mode}), mnesia_controller:sync_and_block_table_whereabouts(Tab, RecNode, RemoteS, A), cleanup_tab_copier(Pid, Storage, Tab), @@ -797,8 +936,8 @@ finish_copy(Pid, Tab, Storage, RemoteS) -> receive {Pid, no_more} -> % Dont bother about the spurious 'more' message no_more; - {copier_done, Node} when Node == node(Pid)-> - verbose("Tab receiver ~p crashed (more): ~p~n", [Tab, Node]), + {copier_done, Node} -> + verbose("Tab receiver ~tp crashed (more): ~p~n", [Tab, Node]), receiver_died end end, diff --git a/lib/mnesia/src/mnesia_locker.erl b/lib/mnesia/src/mnesia_locker.erl index 89feeba2c3..073b48abc0 100644 --- a/lib/mnesia/src/mnesia_locker.erl +++ b/lib/mnesia/src/mnesia_locker.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2014. All Rights Reserved. +%% Copyright Ericsson AB 1996-2016. 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. @@ -22,7 +22,7 @@ -module(mnesia_locker). -export([ - get_held_locks/0, + get_held_locks/0, get_held_locks/1, get_lock_queue/0, global_lock/5, ixrlock/5, @@ -236,11 +236,16 @@ loop(State) -> From ! {Ref, ok}, loop(State); + {From, {is_locked, Oid}} -> + Held = ?ets_lookup(mnesia_held_locks, Oid), + reply(From, Held), + loop(State); + {'EXIT', Pid, _} when Pid == State#state.supervisor -> do_stop(); {system, From, Msg} -> - verbose("~p got {system, ~p, ~p}~n", [?MODULE, From, Msg]), + verbose("~p got {system, ~p, ~tp}~n", [?MODULE, From, Msg]), Parent = State#state.supervisor, sys:handle_system_msg(Msg, From, Parent, ?MODULE, [], State); @@ -249,7 +254,7 @@ loop(State) -> loop(State); Msg -> - error("~p got unexpected message: ~p~n", [?MODULE, Msg]), + error("~p got unexpected message: ~tp~n", [?MODULE, Msg]), loop(State) end. @@ -1151,6 +1156,19 @@ get_held_locks() -> Locks = receive {mnesia_held_locks, Ls} -> Ls after 5000 -> [] end, rewrite_locks(Locks, []). +%% Mnesia internal usage only +get_held_locks(Tab) when is_atom(Tab) -> + Oid = {Tab, ?ALL}, + ?MODULE ! {self(), {is_locked, Oid}}, + receive + {?MODULE, _Node, Locks} -> + case Locks of + [] -> []; + [{Oid, _Prev, What}] -> What + end + end. + + rewrite_locks([{Oid, _, Ls}|Locks], Acc0) -> Acc = rewrite_locks(Ls, Oid, Acc0), rewrite_locks(Locks, Acc); diff --git a/lib/mnesia/src/mnesia_log.erl b/lib/mnesia/src/mnesia_log.erl index 8b19e13ff6..a2de23a2a3 100644 --- a/lib/mnesia/src/mnesia_log.erl +++ b/lib/mnesia/src/mnesia_log.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2014. All Rights Reserved. +%% Copyright Ericsson AB 1996-2016. 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. @@ -224,17 +224,12 @@ sappend(Log, Term) -> ok = disk_log:log(Log, Term). %% Write commit records to the latest_log -log(C) when C#commit.disc_copies == [], - C#commit.disc_only_copies == [], - C#commit.schema_ops == [] -> - ignore; log(C) -> - case mnesia_monitor:use_dir() of + case need_log(C) andalso mnesia_monitor:use_dir() of true -> if is_record(C, commit) -> - C2 = C#commit{ram_copies = [], snmp = []}, - append(latest_log, C2); + append(latest_log, strip_snmp(C)); true -> %% Either a commit record as binary %% or some decision related info @@ -247,17 +242,12 @@ log(C) -> %% Synced -slog(C) when C#commit.disc_copies == [], - C#commit.disc_only_copies == [], - C#commit.schema_ops == [] -> - ignore; slog(C) -> - case mnesia_monitor:use_dir() of + case need_log(C) andalso mnesia_monitor:use_dir() of true -> if is_record(C, commit) -> - C2 = C#commit{ram_copies = [], snmp = []}, - sappend(latest_log, C2); + sappend(latest_log, strip_snmp(C)); true -> %% Either a commit record as binary %% or some decision related info @@ -268,6 +258,13 @@ slog(C) -> ignore end. +need_log(#commit{disc_copies=[], disc_only_copies=[], schema_ops=[], ext=Ext}) -> + lists:keymember(ext_copies, 1, Ext); +need_log(_) -> true. + +strip_snmp(#commit{ext=[]}=CR) -> CR; +strip_snmp(#commit{ext=Ext}=CR) -> + CR#commit{ext=lists:keydelete(snmp, 1, Ext)}. %% Stuff related to the file LOG @@ -313,7 +310,7 @@ verify_no_exists(Fname) -> false -> ok; true -> - fatal("Log file exists: ~p~n", [Fname]) + fatal("Log file exists: ~tp~n", [Fname]) end. open_log(Name, Header, Fname) -> @@ -334,7 +331,7 @@ open_log(Name, Header, Fname, Exists, Repair) -> open_log(Name, Header, Fname, Exists, Repair, Mode) -> Args = [{file, Fname}, {name, Name}, {repair, Repair}, {mode, Mode}], -%% io:format("~p:open_log: ~p ~p~n", [?MODULE, Name, Fname]), +%% io:format("~p:open_log: ~tp ~tp~n", [?MODULE, Name, Fname]), case mnesia_monitor:open_log(Args) of {ok, Log} when Exists == true -> Log; @@ -347,19 +344,19 @@ open_log(Name, Header, Fname, Exists, Repair, Mode) -> write_header(Log, Header), Log; {repaired, Log, _Recover, BadBytes} -> - mnesia_lib:important("Data may be missing, log ~p repaired: Lost ~p bytes~n", + mnesia_lib:important("Data may be missing, log ~tp repaired: Lost ~p bytes~n", [Fname, BadBytes]), Log; {error, Reason = {file_error, _Fname, emfile}} -> - fatal("Cannot open log file ~p: ~p~n", [Fname, Reason]); + fatal("Cannot open log file ~tp: ~tp~n", [Fname, Reason]); {error, Reason} when Repair == true -> file:delete(Fname), - mnesia_lib:important("Data may be missing, Corrupt logfile deleted: ~p, ~p ~n", + mnesia_lib:important("Data may be missing, Corrupt logfile deleted: ~tp, ~tp ~n", [Fname, Reason]), %% Create a new open_log(Name, Header, Fname, false, false, read_write); {error, Reason} -> - fatal("Cannot open log file ~p: ~p~n", [Fname, Reason]) + fatal("Cannot open log file ~tp: ~tp~n", [Fname, Reason]) end. write_header(Log, Header) -> @@ -384,7 +381,7 @@ close_log(Log) -> {error, {read_only_mode, Log}} -> ok; {error, Reason} -> - mnesia_lib:important("Failed syncing ~p to_disk reason ~p ~n", + mnesia_lib:important("Failed syncing ~tp to_disk reason ~tp ~n", [Log, Reason]) end, mnesia_monitor:close_log(Log). @@ -467,13 +464,13 @@ chunk_log(_Log, eof) -> chunk_log(Log, Cont) -> case disk_log:chunk(Log, Cont) of {error, Reason} -> - fatal("Possibly truncated ~p file: ~p~n", + fatal("Possibly truncated ~tp file: ~tp~n", [Log, Reason]); {C2, Chunk, _BadBytes} -> %% Read_only case, should we warn about the bad log file? %% BUGBUG Should we crash if Repair == false ?? %% We got to check this !! - mnesia_lib:important("~p repaired, lost ~p bad bytes~n", [Log, _BadBytes]), + mnesia_lib:important("~tp repaired, lost ~p bad bytes~n", [Log, _BadBytes]), {C2, Chunk}; Other -> Other @@ -508,7 +505,7 @@ prepare_decision_log_dump(false, Prev) -> ok -> prepare_decision_log_dump(true, Prev); {error, Reason} -> - fatal("Cannot rename decision log file ~p -> ~p: ~p~n", + fatal("Cannot rename decision log file ~tp -> ~tp: ~tp~n", [decision_log_file(), Prev, Reason]) end; prepare_decision_log_dump(true, Prev) -> @@ -525,7 +522,7 @@ confirm_decision_log_dump() -> ok -> file:delete(previous_decision_log_file()); {error, Reason} -> - fatal("Cannot confirm decision log dump: ~p~n", + fatal("Cannot confirm decision log dump: ~tp~n", [Reason]) end. @@ -564,7 +561,7 @@ view() -> lists:foreach(fun(F) -> view(F) end, log_files()). view(File) -> - mnesia_lib:show("***** ~p ***** ~n", [File]), + mnesia_lib:show("***** ~tp ***** ~n", [File]), case exists(File) of false -> nolog; @@ -577,25 +574,25 @@ view(File) -> {repaired, _, _, _} -> view_file(start, N); {error, Reason} -> - error("Cannot open log ~p: ~p~n", [File, Reason]) + error("Cannot open log ~tp: ~tp~n", [File, Reason]) end end. view_file(C, Log) -> case disk_log:chunk(Log, C) of {error, Reason} -> - error("** Possibly truncated FILE ~p~n", [Reason]), + error("** Possibly truncated FILE ~tp~n", [Reason]), error; eof -> disk_log:close(Log), eof; {C2, Terms, _BadBytes} -> - dbg_out("Lost ~p bytes in ~p ~n", [_BadBytes, Log]), - lists:foreach(fun(X) -> mnesia_lib:show("~p~n", [X]) end, + dbg_out("Lost ~p bytes in ~tp ~n", [_BadBytes, Log]), + lists:foreach(fun(X) -> mnesia_lib:show("~tp~n", [X]) end, Terms), view_file(C2, Log); {C2, Terms} -> - lists:foreach(fun(X) -> mnesia_lib:show("~p~n", [X]) end, + lists:foreach(fun(X) -> mnesia_lib:show("~tp~n", [X]) end, Terms), view_file(C2, Log) end. @@ -734,7 +731,7 @@ backup_schema(B, Tabs) -> safe_apply(B, write, [_, Items]) when Items == [] -> B; safe_apply(B, What, Args) -> - Abort = fun(R) -> abort_write(B, What, Args, R) end, + Abort = abort_write_fun(B, What, Args), receive {'EXIT', Pid, R} -> Abort({'EXIT', Pid, R}) after 0 -> @@ -746,15 +743,19 @@ safe_apply(B, What, Args) -> end end. +-spec abort_write_fun(_, _, _) -> fun((_) -> no_return()). +abort_write_fun(B, What, Args) -> + fun(R) -> abort_write(B, What, Args, R) end. + abort_write(B, What, Args, Reason) -> Mod = B#backup_args.module, Opaque = B#backup_args.opaque, - dbg_out("Failed to perform backup. M=~p:F=~p:A=~p -> ~p~n", + dbg_out("Failed to perform backup. M=~p:F=~tp:A=~tp -> ~tp~n", [Mod, What, Args, Reason]), - try apply(Mod, abort_write, [Opaque]) of - {ok, _Res} -> throw({error, Reason}) + try {ok, _Res} = apply(Mod, abort_write, [Opaque]) of + _ -> throw({error, Reason}) catch _:Other -> - error("Failed to abort backup. ~p:~p~p -> ~p~n", + error("Failed to abort backup. ~p:~tp~tp -> ~tp~n", [Mod, abort_write, [Opaque], Other]), throw({error, Reason}) end. @@ -801,7 +802,7 @@ select_source(Tab, Name, PrevName) -> {PrevName, retainer}; _ -> %% Do a full backup anyway - dbg_out("Incremental backup escalated to full backup: ~p~n", [Tab]), + dbg_out("Incremental backup escalated to full backup: ~tp~n", [Tab]), {Name, table} end end. diff --git a/lib/mnesia/src/mnesia_monitor.erl b/lib/mnesia/src/mnesia_monitor.erl index 8313c3bda5..4cfe16dec0 100644 --- a/lib/mnesia/src/mnesia_monitor.erl +++ b/lib/mnesia/src/mnesia_monitor.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2014. All Rights Reserved. +%% Copyright Ericsson AB 1996-2017. 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. @@ -32,6 +32,7 @@ init/0, mktab/2, unsafe_mktab/2, + unsafe_create_external/4, mnesia_down/2, needs_protocol_conversion/1, negotiate_protocol/1, @@ -82,9 +83,9 @@ going_down = [], tm_started = false, early_connects = [], connecting, mq = [], remote_node_status = []}). --define(current_protocol_version, {8,1}). +-define(current_protocol_version, {8,3}). --define(previous_protocol_version, {8,0}). +-define(previous_protocol_version, {8,2}). start() -> gen_server:start_link({local, ?MODULE}, ?MODULE, @@ -129,6 +130,8 @@ close_log(Name) -> unsafe_close_log(Name) -> unsafe_call({unsafe_close_log, Name}). +unsafe_create_external(Tab, Alias, Mod, Cs) -> + unsafe_call({unsafe_create_external, Tab, Alias, Mod, Cs}). disconnect(Node) -> cast({disconnect, Node}). @@ -166,7 +169,7 @@ check_protocol([{Node, {accept, Mon, Version, Protocol}} | Tail], Protocols) -> verbose("Failed to connect with ~p. ~p protocols rejected. " "expected version = ~p, expected protocol = ~p~n", [Node, Protocols, Version, Protocol]), - unlink(Mon), % Get rid of unneccessary link + unlink(Mon), % Get rid of unnecessary link check_protocol(Tail, Protocols) end; check_protocol([{Node, {reject, _Mon, Version, Protocol}} | Tail], Protocols) -> @@ -175,10 +178,10 @@ check_protocol([{Node, {reject, _Mon, Version, Protocol}} | Tail], Protocols) -> [Node, Protocols, Version, Protocol]), check_protocol(Tail, Protocols); check_protocol([{error, _Reason} | Tail], Protocols) -> - dbg_out("~p connect failed error: ~p~n", [?MODULE, _Reason]), + dbg_out("~p connect failed error: ~tp~n", [?MODULE, _Reason]), check_protocol(Tail, Protocols); check_protocol([{badrpc, _Reason} | Tail], Protocols) -> - dbg_out("~p connect failed badrpc: ~p~n", [?MODULE, _Reason]), + dbg_out("~p connect failed badrpc: ~tp~n", [?MODULE, _Reason]), check_protocol(Tail, Protocols); check_protocol([], [Protocol | _Protocols]) -> set(protocol_version, Protocol), @@ -193,7 +196,7 @@ protocol_version() -> %% A sorted list of acceptable protocols the %% preferred protocols are first in the list acceptable_protocol_versions() -> - [protocol_version(), ?previous_protocol_version, {7,6}]. + [protocol_version(), ?previous_protocol_version, {8,1}]. needs_protocol_conversion(Node) -> case {?catch_val({protocol, Node}), protocol_version()} of @@ -243,10 +246,10 @@ start_proc(Who, Mod, Fun, Args) -> proc_lib:start_link(mnesia_sp, init_proc, Args2, infinity). terminate_proc(Who, R, State) when R /= shutdown, R /= killed -> - fatal("~p crashed: ~p state: ~p~n", [Who, R, State]); + fatal("~p crashed: ~p state: ~tp~n", [Who, R, State]); terminate_proc(Who, Reason, _State) -> - mnesia_lib:verbose("~p terminated: ~p~n", [Who, Reason]), + mnesia_lib:verbose("~p terminated: ~tp~n", [Who, Reason]), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -291,7 +294,7 @@ init([Parent]) -> {ok, #state{supervisor = Parent}} catch _:Reason -> - mnesia_lib:report_fatal("Bad configuration: ~p~n", [Reason]), + mnesia_lib:report_fatal("Bad configuration: ~tp~n", [Reason]), {stop, {bad_config, Reason}} end. @@ -330,7 +333,7 @@ handle_call({mktab, Tab, Args}, _From, State) -> catch error:ExitReason -> Msg = "Cannot create ets table", Reason = {system_limit, Msg, Tab, Args, ExitReason}, - fatal("~p~n", [Reason]), + fatal("~tp~n", [Reason]), {noreply, State} end; @@ -350,7 +353,7 @@ handle_call({open_dets, Tab, Args}, _From, State) -> {error, Reason} -> Msg = "Cannot open dets table", Error = {error, {Msg, Tab, Args, Reason}}, - fatal("~p~n", [Error]), + fatal("~tp~n", [Error]), {noreply, State} end; @@ -382,7 +385,7 @@ handle_call({reopen_log, Name, Fname, Head}, _From, State) -> {error, Reason} -> Msg = "Cannot rename disk_log file", Error = {error, {Msg, Name, Fname, Head, Reason}}, - fatal("~p~n", [Error]), + fatal("~tp~n", [Error]), {noreply, State} end; @@ -397,7 +400,7 @@ handle_call({close_log, Name}, _From, State) -> {error, Reason} -> Msg = "Cannot close disk_log file", Error = {error, {Msg, Name, Reason}}, - fatal("~p~n", [Error]), + fatal("~tp~n", [Error]), {noreply, State} end; @@ -405,6 +408,14 @@ handle_call({unsafe_close_log, Name}, _From, State) -> _ = disk_log:close(Name), {reply, ok, State}; +handle_call({unsafe_create_external, Tab, Alias, Mod, Cs}, _From, State) -> + case catch Mod:create_table(Alias, Tab, mnesia_schema:cs2list(Cs)) of + {'EXIT', ExitReason} -> + {reply, {error, ExitReason}, State}; + Reply -> + {reply, Reply, State} + end; + handle_call({negotiate_protocol, Mon, _Version, _Protocols}, _From, State) when State#state.tm_started == false -> State2 = State#state{early_connects = [node(Mon) | State#state.early_connects]}, @@ -424,8 +435,6 @@ handle_call({negotiate_protocol, Mon, Version, Protocols}, From, State) case hd(Protocols) of ?previous_protocol_version -> accept_protocol(Mon, MyVersion, ?previous_protocol_version, From, State); - {7,6} -> - accept_protocol(Mon, MyVersion, {7,6}, From, State); _ -> verbose("Connection with ~p rejected. " "version = ~p, protocols = ~p, " @@ -452,7 +461,7 @@ handle_call(init, _From, State) -> {reply, EarlyNodes, State2}; handle_call(Msg, _From, State) -> - error("~p got unexpected call: ~p~n", [?MODULE, Msg]), + error("~p got unexpected call: ~tp~n", [?MODULE, Msg]), {noreply, State}. accept_protocol(Mon, Version, Protocol, From, State) -> @@ -526,7 +535,7 @@ handle_cast({inconsistent_database, Context, Node}, State) -> {noreply, State}; handle_cast(Msg, State) -> - error("~p got unexpected cast: ~p~n", [?MODULE, Msg]), + error("~p got unexpected cast: ~tp~n", [?MODULE, Msg]), {noreply, State}. %%---------------------------------------------------------------------- @@ -563,7 +572,7 @@ handle_info(Msg = {'EXIT',Pid,_}, State) -> %% We have probably got an exit signal from %% disk_log or dets Hint = "Hint: check that the disk still is writable", - fatal("~p got unexpected info: ~p; ~p~n", + fatal("~p got unexpected info: ~tp; ~p~n", [?MODULE, Msg, Hint]) end; @@ -590,13 +599,13 @@ handle_info({disk_log, _Node, Log, Info}, State) -> {truncated, _No} -> ok; _ -> - mnesia_lib:important("Warning Log file ~p error reason ~s~n", + mnesia_lib:important("Warning Log file ~tp error reason ~ts~n", [Log, disk_log:format_error(Info)]) end, {noreply, State}; handle_info(Msg, State) -> - error("~p got unexpected info (~p): ~p~n", [?MODULE, State, Msg]). + error("~p got unexpected info (~tp): ~tp~n", [?MODULE, State, Msg]). process_q(State = #state{mq=[]}) -> {noreply,State}; process_q(State = #state{mq=[{info,Msg}|R]}) -> @@ -660,6 +669,7 @@ get_env(E) -> env() -> [ access_module, + allow_index_on_key, auto_repair, backup_module, debug, @@ -673,19 +683,23 @@ env() -> extra_db_nodes, ignore_fallback_at_startup, fallback_error_function, + fold_chunk_size, max_wait_for_decision, schema_location, core_dir, pid_sort_order, no_table_loaders, dc_dump_limit, - send_compressed + send_compressed, + schema ]. default_env(access_module) -> mnesia; default_env(auto_repair) -> true; +default_env(allow_index_on_key) -> + false; default_env(backup_module) -> mnesia_backup; default_env(debug) -> @@ -711,6 +725,8 @@ default_env(ignore_fallback_at_startup) -> false; default_env(fallback_error_function) -> {mnesia, lkill}; +default_env(fold_chunk_size) -> + 100; default_env(max_wait_for_decision) -> infinity; default_env(schema_location) -> @@ -724,7 +740,9 @@ default_env(no_table_loaders) -> default_env(dc_dump_limit) -> 4; default_env(send_compressed) -> - 0. + 0; +default_env(schema) -> + []. check_type(Env, Val) -> try do_check_type(Env, Val) @@ -732,6 +750,7 @@ check_type(Env, Val) -> end. do_check_type(access_module, A) when is_atom(A) -> A; +do_check_type(allow_index_on_key, B) -> bool(B); do_check_type(auto_repair, B) -> bool(B); do_check_type(backup_module, B) when is_atom(B) -> B; do_check_type(debug, debug) -> debug; @@ -755,6 +774,8 @@ do_check_type(extra_db_nodes, L) when is_list(L) -> (A) when is_atom(A) -> true end, lists:filter(Fun, L); +do_check_type(fold_chunk_size, I) when is_integer(I), I > 0; + I =:= infinity -> I; do_check_type(max_wait_for_decision, infinity) -> infinity; do_check_type(max_wait_for_decision, I) when is_integer(I), I > 0 -> I; do_check_type(schema_location, M) -> media(M); @@ -768,7 +789,8 @@ do_check_type(pid_sort_order, "standard") -> standard; do_check_type(pid_sort_order, _) -> false; do_check_type(no_table_loaders, N) when is_integer(N), N > 0 -> N; do_check_type(dc_dump_limit,N) when is_number(N), N > 0 -> N; -do_check_type(send_compressed, L) when is_integer(L), L >= 0, L =< 9 -> L. +do_check_type(send_compressed, L) when is_integer(L), L >= 0, L =< 9 -> L; +do_check_type(schema, L) when is_list(L) -> L. bool(true) -> true; bool(false) -> false. diff --git a/lib/mnesia/src/mnesia_recover.erl b/lib/mnesia/src/mnesia_recover.erl index 9420e57afa..d792070332 100644 --- a/lib/mnesia/src/mnesia_recover.erl +++ b/lib/mnesia/src/mnesia_recover.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2014. All Rights Reserved. +%% Copyright Ericsson AB 1997-2016. 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. @@ -762,7 +762,7 @@ handle_call(sync, _From, State) -> {reply, ok, State}; handle_call(Msg, _From, State) -> - error("~p got unexpected call: ~p~n", [?MODULE, Msg]), + error("~p got unexpected call: ~tp~n", [?MODULE, Msg]), {noreply, State}. do_log_mnesia_up(Node) -> @@ -881,7 +881,7 @@ handle_cast({log_dump_overload, Flag}, State) when is_boolean(Flag) -> {noreply, State#state{log_dump_overload = Flag}}; handle_cast(Msg, State) -> - error("~p got unexpected cast: ~p~n", [?MODULE, Msg]), + error("~p got unexpected cast: ~tp~n", [?MODULE, Msg]), {noreply, State}. %%---------------------------------------------------------------------- @@ -927,11 +927,11 @@ handle_info({force_decision, Tid}, State) -> end; handle_info({'EXIT', Pid, R}, State) when Pid == State#state.supervisor -> - mnesia_lib:dbg_out("~p was ~p~n",[?MODULE, R]), + mnesia_lib:dbg_out("~p was ~tp~n",[?MODULE, R]), {stop, shutdown, State}; handle_info(Msg, State) -> - error("~p got unexpected info: ~p~n", [?MODULE, Msg]), + error("~p got unexpected info: ~tp~n", [?MODULE, Msg]), {noreply, State}. %%---------------------------------------------------------------------- diff --git a/lib/mnesia/src/mnesia_registry.erl b/lib/mnesia/src/mnesia_registry.erl index abf0b745a1..55ddc3d2fd 100644 --- a/lib/mnesia/src/mnesia_registry.erl +++ b/lib/mnesia/src/mnesia_registry.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2010. All Rights Reserved. +%% Copyright Ericsson AB 1998-2016. 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. diff --git a/lib/mnesia/src/mnesia_schema.erl b/lib/mnesia/src/mnesia_schema.erl index 593360415d..71952af31c 100644 --- a/lib/mnesia/src/mnesia_schema.erl +++ b/lib/mnesia/src/mnesia_schema.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2011. All Rights Reserved. +%% Copyright Ericsson AB 1996-2017. 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. @@ -29,6 +29,16 @@ -module(mnesia_schema). -export([ + add_backend_type/2, + do_add_backend_type/2, + delete_backend_type/1, + do_delete_backend_type/1, + backend_types/0, + add_index_plugin/3, + do_add_index_plugin/3, + delete_index_plugin/1, + do_delete_index_plugin/1, + index_plugins/0, add_snmp/2, add_table_copy/3, add_table_index/2, @@ -55,14 +65,16 @@ dump_tables/1, ensure_no_schema/1, get_create_list/1, - get_initial_schema/2, + get_initial_schema/3, get_table_properties/1, info/0, info/1, init/1, + init_backends/0, insert_cstruct/3, is_remote_member/1, list2cs/1, + list2cs/2, lock_schema/0, merge_schema/0, merge_schema/1, @@ -110,7 +122,8 @@ do_delete_table/1, do_read_table_property/2, do_delete_table_property/2, - do_write_table_property/2]). + do_write_table_property/2, + do_change_table_copy_type/3]). -include("mnesia.hrl"). -include_lib("kernel/include/file.hrl"). @@ -138,12 +151,30 @@ init(IgnoreFallback) -> verbose("Schema initiated from: ~p~n", [Source]), set({schema, tables}, []), set({schema, local_tables}, []), + do_set_schema(schema), Tabs = set_schema(?ets_first(schema)), lists:foreach(fun(Tab) -> clear_whereabouts(Tab) end, Tabs), set({schema, where_to_read}, node()), set({schema, load_node}, node()), set({schema, load_reason}, initial), - mnesia_controller:add_active_replica(schema, node()). + mnesia_controller:add_active_replica(schema, node()), + init_backends(). + + +init_backends() -> + Backends = lists:foldl(fun({Alias, Mod}, Acc) -> + orddict:append(Mod, Alias, Acc) + end, orddict:new(), get_ext_types()), + [init_backend(Mod, Aliases) || {Mod, Aliases} <- Backends], + ok. + +init_backend(Mod, [_|_] = Aliases) -> + case Mod:init_backend() of + ok -> + Mod:add_aliases(Aliases); + Error -> + mnesia:abort({backend_init_error, Error}) + end. exit_on_error({error, Reason}) -> exit(Reason); @@ -180,6 +211,7 @@ do_set_schema(Tab, Cs) -> set({Tab, ram_copies}, Cs#cstruct.ram_copies), set({Tab, disc_copies}, Cs#cstruct.disc_copies), set({Tab, disc_only_copies}, Cs#cstruct.disc_only_copies), + set({Tab, external_copies}, Cs#cstruct.external_copies), set({Tab, load_order}, Cs#cstruct.load_order), set({Tab, access_mode}, Cs#cstruct.access_mode), set({Tab, majority}, Cs#cstruct.majority), @@ -195,15 +227,21 @@ do_set_schema(Tab, Cs) -> set({Tab, arity}, Arity), RecName = Cs#cstruct.record_name, set({Tab, record_name}, RecName), - set({Tab, record_validation}, {RecName, Arity, Type}), set({Tab, wild_pattern}, wild(RecName, Arity)), - set({Tab, index}, Cs#cstruct.index), + set({Tab, index}, [P || {P,_} <- Cs#cstruct.index]), + case Cs#cstruct.index of + [] -> + set({Tab, index_info}, mnesia_index:index_info(Type, [])); + _ -> + ignore + end, %% create actual index tabs later set({Tab, cookie}, Cs#cstruct.cookie), set({Tab, version}, Cs#cstruct.version), set({Tab, cstruct}, Cs), Storage = mnesia_lib:schema_cs_to_storage_type(node(), Cs), set({Tab, storage_type}, Storage), + set_record_validation(Tab, Storage, RecName, Arity, Type), mnesia_lib:add({schema, tables}, Tab), Ns = mnesia_lib:cs_to_nodes(Cs), case lists:member(node(), Ns) of @@ -213,7 +251,24 @@ do_set_schema(Tab, Cs) -> mnesia_lib:add({schema, local_tables}, Tab); false -> ignore - end. + end, + set_ext_types(Tab, get_ext_types(), Cs#cstruct.external_copies). + +set_record_validation(Tab, {ext,Alias,Mod}, RecName, Arity, Type) -> + set({Tab, record_validation}, {RecName, Arity, Type, Alias, Mod}); +set_record_validation(Tab, _, RecName, Arity, Type) -> + set({Tab, record_validation}, {RecName, Arity, Type}). + +set_ext_types(Tab, ExtTypes, ExtCopies) -> + lists:foreach( + fun({Type, _} = Key) -> + Nodes = case lists:keyfind(Key, 1, ExtCopies) of + {_, Ns} -> Ns; + false -> [] + end, + set({Tab, Type}, Nodes) + end, ExtTypes). + wild(RecName, Arity) -> Wp0 = list_to_tuple(lists:duplicate(Arity, '_')), @@ -331,7 +386,7 @@ delete_schema(Ns) when is_list(Ns), Ns /= [] -> [] -> ok; BadReplies -> - verbose("~s: ~p~n", [Reason, BadReplies]), + verbose("~s: ~tp~n", [Reason, BadReplies]), {error, {"All nodes not running", BadReplies}} end; {_Replies, BadNs} -> @@ -412,10 +467,10 @@ opt_create_dir(UseDir, Dir) when UseDir == true-> false -> case file:make_dir(Dir) of ok -> - verbose("Create Directory ~p~n", [Dir]), + verbose("Create Directory ~tp~n", [Dir]), ok; {error, Reason} -> - verbose("Cannot create mnesia dir ~p~n", [Reason]), + verbose("Cannot create mnesia dir ~tp~n", [Reason]), {error, {"Cannot create Mnesia dir", Dir, Reason}} end end; @@ -525,9 +580,14 @@ do_read_disc_schema(Fname, Keep) -> Res. get_initial_schema(SchemaStorage, Nodes) -> + get_initial_schema(SchemaStorage, Nodes, []). + +get_initial_schema(SchemaStorage, Nodes, Properties) -> % + UserProps = initial_schema_properties(Properties), Cs = #cstruct{name = schema, record_name = schema, - attributes = [table, cstruct]}, + attributes = [table, cstruct], + user_properties = UserProps}, Cs2 = case SchemaStorage of ram_copies -> Cs#cstruct{ram_copies = Nodes}; @@ -535,6 +595,35 @@ get_initial_schema(SchemaStorage, Nodes) -> end, cs2list(Cs2). +initial_schema_properties(Props0) -> + DefaultProps = remove_duplicates(mnesia_monitor:get_env(schema)), + Props = lists:foldl( + fun({K,V}, Acc) -> + lists:keystore(K, 1, Acc, {K,V}) + end, DefaultProps, remove_duplicates(Props0)), + initial_schema_properties_(Props). + +initial_schema_properties_([{backend_types, Types}|Props]) -> + lists:foreach(fun({Name, Module}) -> + verify_backend_type(Name, Module) + end, Types), + [{mnesia_backend_types, Types}|initial_schema_properties_(Props)]; +initial_schema_properties_([{index_plugins, Plugins}|Props]) -> + lists:foreach(fun({Name, Module, Function}) -> + verify_index_plugin(Name, Module, Function) + end, Plugins), + [{mnesia_index_plugins, Plugins}|initial_schema_properties_(Props)]; +initial_schema_properties_([P|_Props]) -> + mnesia:abort({bad_schema_property, P}); +initial_schema_properties_([]) -> + []. + +remove_duplicates([{K,_} = H|T]) -> + [H | remove_duplicates([X || {K1,_} = X <- T, + K1 =/= K])]; +remove_duplicates([]) -> + []. + read_cstructs_from_disc() -> %% Assumptions: %% - local schema lock in global @@ -551,8 +640,9 @@ read_cstructs_from_disc() -> {type, set}], case dets:open_file(make_ref(), Args) of {ok, Tab} -> + ExtTypes = get_ext_types_disc(), Fun = fun({_, _, List}) -> - {continue, list2cs(List)} + {continue, list2cs(List, ExtTypes)} end, Cstructs = dets:traverse(Tab, Fun), dets:close(Tab), @@ -632,7 +722,7 @@ do_insert_schema_ops(_Store, []) -> api_list2cs(List) when is_list(List) -> Name = pick(unknown, name, List, must), - Keys = check_keys(Name, List, record_info(fields, cstruct)), + Keys = check_keys(Name, List), check_duplicates(Name, Keys), list2cs(List); api_list2cs(Other) -> @@ -646,55 +736,57 @@ cs2list(Cs) when is_record(Cs, cstruct) -> rec2list(Tags, Tags, 2, Cs); cs2list(CreateList) when is_list(CreateList) -> CreateList; -%% 4.6 -cs2list(Cs) when element(1, Cs) == cstruct, tuple_size(Cs) == 19 -> - Tags = [name,type,ram_copies,disc_copies,disc_only_copies, + +cs2list(Cs) when element(1, Cs) == cstruct, tuple_size(Cs) == 20 -> + Tags = [name,type, + ram_copies,disc_copies,disc_only_copies,external_copies, load_order,access_mode,majority,index,snmp,local_content, record_name,attributes, user_properties,frag_properties,storage_properties, cookie,version], rec2list(Tags, Tags, 2, Cs); -%% 4.4.19 -cs2list(Cs) when element(1, Cs) == cstruct, tuple_size(Cs) == 18 -> +%% since vsn-4.6 (protocol 8.2 or older) +cs2list(Cs) when element(1, Cs) == cstruct, tuple_size(Cs) == 19 -> Tags = [name,type,ram_copies,disc_copies,disc_only_copies, load_order,access_mode,majority,index,snmp,local_content, - record_name,attributes,user_properties,frag_properties, - cookie,version], - rec2list(Tags, Tags, 2, Cs); -%% 4.4.18 and earlier -cs2list(Cs) when element(1, Cs) == cstruct, tuple_size(Cs) == 17 -> - Tags = [name,type,ram_copies,disc_copies,disc_only_copies, - load_order,access_mode,index,snmp,local_content, - record_name,attributes,user_properties,frag_properties, + record_name,attributes, + user_properties,frag_properties,storage_properties, cookie,version], rec2list(Tags, Tags, 2, Cs). cs2list(false, Cs) -> cs2list(Cs); -cs2list(ver4_4_18, Cs) -> %% Or earlier - Orig = record_info(fields, cstruct), - Tags = [name,type,ram_copies,disc_copies,disc_only_copies, - load_order,access_mode,index,snmp,local_content, - record_name,attributes,user_properties,frag_properties, - cookie,version], - rec2list(Tags, Orig, 2, Cs); -cs2list(ver4_4_19, Cs) -> - Orig = record_info(fields, cstruct), - Tags = [name,type,ram_copies,disc_copies,disc_only_copies, - load_order,access_mode,majority,index,snmp,local_content, - record_name,attributes,user_properties,frag_properties, - cookie,version], - rec2list(Tags, Orig, 2, Cs); -cs2list(ver4_6, Cs) -> +cs2list({8,3}, Cs) -> + cs2list(Cs); +cs2list({8,Minor}, Cs) when Minor =:= 2; Minor =:= 1 -> Orig = record_info(fields, cstruct), Tags = [name,type,ram_copies,disc_copies,disc_only_copies, load_order,access_mode,majority,index,snmp,local_content, record_name,attributes, user_properties,frag_properties,storage_properties, cookie,version], - rec2list(Tags, Orig, 2, Cs). - + CsList = rec2list(Tags, Orig, 2, Cs), + case proplists:get_value(index, CsList, []) of + [] -> CsList; + NewFormat -> + OldFormat = [Pos || {Pos, _Pref} <- NewFormat], + lists:keyreplace(index, 1, CsList, {index, OldFormat}) + end. +rec2list([index | Tags], [index|Orig], Pos, Rec) -> + Val = element(Pos, Rec), + [{index, lists:map( + fun({_, _Type}=P) -> P; + (P) when is_integer(P); is_atom(P) -> {P, ordered} + end, Val)} | rec2list(Tags, Orig, Pos + 1, Rec)]; +rec2list([external_copies | Tags], Orig0, Pos, Rec) -> + Orig = case Orig0 of + [external_copies|Rest] -> Rest; + _ -> Orig0 + end, + Val = element(Pos, Rec), + [{Alias, Ns} || {{Alias,_}, Ns} <- Val] + ++ rec2list(Tags, Orig, Pos+1, Rec); rec2list([Tag | Tags], [Tag | Orig], Pos, Rec) -> Val = element(Pos, Rec), [{Tag, Val} | rec2list(Tags, Orig, Pos + 1, Rec)]; @@ -717,14 +809,19 @@ convert_cs(Version, Cs) -> Fields = [Value || {_, Value} <- cs2list(Version, Cs)], list_to_tuple([cstruct|Fields]). -list2cs(List) when is_list(List) -> +list2cs(List) -> + list2cs(List, get_ext_types()). + +list2cs(List, ExtTypes) when is_list(List) -> Name = pick(unknown, name, List, must), Type = pick(Name, type, List, set), Rc0 = pick(Name, ram_copies, List, []), Dc = pick(Name, disc_copies, List, []), Doc = pick(Name, disc_only_copies, List, []), - Rc = case {Rc0, Dc, Doc} of - {[], [], []} -> [node()]; + + Ext = pick_external_copies(List, ExtTypes), + Rc = case {Rc0, Dc, Doc, Ext} of + {[], [], [], []} -> [node()]; _ -> Rc0 end, LC = pick(Name, local_content, List, false), @@ -742,8 +839,6 @@ list2cs(List) when is_list(List) -> Ix = pick(Name, index, List, []), verify({alt, [nil, list]}, mnesia_lib:etype(Ix), {bad_type, Name, {index, [Ix]}}), - Ix2 = [attr_to_pos(I, Attrs) || I <- Ix], - Frag = pick(Name, frag_properties, List, []), verify({alt, [nil, list]}, mnesia_lib:etype(Frag), {badarg, Name, {frag_properties, Frag}}), @@ -770,24 +865,48 @@ list2cs(List) when is_list(List) -> DetsOpts = proplists:get_value(dets, BEProps, []), is_list(DetsOpts) orelse mnesia:abort({badarg, Name, {dets, DetsOpts}}), [CheckProp(Prop, BadDetsOpts) || Prop <- DetsOpts], - #cstruct{name = Name, - ram_copies = Rc, - disc_copies = Dc, - disc_only_copies = Doc, - type = Type, - index = Ix2, - snmp = Snmp, - load_order = LoadOrder, - access_mode = AccessMode, - majority = Majority, - local_content = LC, - record_name = RecName, - attributes = Attrs, - user_properties = lists:sort(UserProps), - frag_properties = lists:sort(Frag), - storage_properties = lists:sort(BEProps), - cookie = Cookie, - version = Version}. + + case whereis(mnesia_controller) of + undefined -> + %% check_keys/2 cannot be executed when mnesia is not + %% running, due to it not being possible to read what ext + %% backends are loaded. + %% this doesn't work - disabled for now: + %%Keys = check_keys(Name, List, record_info(fields, cstruct)), + %%check_duplicates(Name, Keys) + ignore; + Pid when is_pid(Pid) -> + Keys = check_keys(Name, List), + check_duplicates(Name, Keys) + end, + + Cs0 = #cstruct{name = Name, + ram_copies = Rc, + disc_copies = Dc, + disc_only_copies = Doc, + external_copies = Ext, + type = Type, + index = Ix, + snmp = Snmp, + load_order = LoadOrder, + access_mode = AccessMode, + majority = Majority, + local_content = LC, + record_name = RecName, + attributes = Attrs, + user_properties = lists:sort(UserProps), + frag_properties = lists:sort(Frag), + storage_properties = lists:sort(BEProps), + cookie = Cookie, + version = Version}, + case Ix of + [] -> Cs0; + [_|_] -> + Ix2 = expand_index_attrs(Cs0), + Cs0#cstruct{index = Ix2} + end; +list2cs(Other, _ExtTypes) -> + mnesia:abort({badarg, Other}). pick(Tab, Key, List, Default) -> case lists:keysearch(Key, 1, List) of @@ -801,6 +920,69 @@ pick(Tab, Key, List, Default) -> mnesia:abort({bad_type, Tab, BadArg}) end. +pick_external_copies(_List, []) -> + []; +pick_external_copies(List, ExtTypes) -> + lists:foldr( + fun({K, Val}, Acc) -> + case lists:keyfind(K, 1, ExtTypes) of + false -> + Acc; + {_, Mod} -> + [{{K,Mod}, Val}|Acc] + end + end, [], List). + +expand_storage_type(S) when S==ram_copies; + S==disc_copies; + S==disc_only_copies -> + S; +expand_storage_type(S) -> + case lists:keyfind(S, 1, get_ext_types()) of + false -> + mnesia:abort({bad_type, {storage_type, S}}); + {Alias, Mod} -> + {ext, Alias, Mod} + end. + +get_ext_types() -> + get_schema_user_property(mnesia_backend_types). + +get_index_plugins() -> + get_schema_user_property(mnesia_index_plugins). + +get_schema_user_property(Key) -> + case dirty_read_table_property(schema, Key) of + undefined -> []; + {_, Types} -> Types + end. + +get_ext_types_disc() -> + try get_ext_types_disc_() + catch + error:_ ->[] + end. + +get_ext_types_disc_() -> + case mnesia_schema:remote_read_schema() of + {ok, _, Prop} -> + K1 = user_properties, + case lists:keyfind(K1, 1, Prop) of + {K1, UserProp} -> + K2 = mnesia_backend_types, + case lists:keyfind(K2, 1, UserProp) of + {K2, Types} -> + Types; + _ -> + [] + end; + _ -> + [] + end; + _ -> + [] + end. + %% Convert attribute name to integer if neccessary attr_tab_to_pos(_Tab, Pos) when is_integer(Pos) -> Pos; @@ -808,6 +990,7 @@ attr_tab_to_pos(Tab, Attr) -> attr_to_pos(Attr, val({Tab, attributes})). %% Convert attribute name to integer if neccessary +attr_to_pos({_} = P, _) -> P; attr_to_pos(Pos, _Attrs) when is_integer(Pos) -> Pos; attr_to_pos(Attr, Attrs) when is_atom(Attr) -> @@ -822,8 +1005,18 @@ attr_to_pos(Attr, [_ | Attrs], Pos) -> attr_to_pos(Attr, _, _) -> mnesia:abort({bad_type, Attr}). +check_keys(Tab, Attrs) -> + Types = [T || {T,_} <- get_ext_types()], + check_keys(Tab, Attrs, Types ++ record_info(fields, cstruct)). + check_keys(Tab, [{Key, _Val} | Tail], Items) -> - case lists:member(Key, Items) of + Key1 = if + is_tuple(Key) -> + element(1, Key); + true -> + Key + end, + case lists:member(Key1, Items) of true -> [Key | check_keys(Tab, Tail, Items)]; false -> mnesia:abort({badarg, Tab, Key}) end; @@ -847,7 +1040,92 @@ has_duplicates([]) -> false. %% This is the only place where we check the validity of data -verify_cstruct(Cs) when is_record(Cs, cstruct) -> + +verify_cstruct(#cstruct{} = Cs) -> + assert_correct_cstruct(Cs), + Cs1 = verify_external_copies( + Cs#cstruct{index = expand_index_attrs(Cs)}), + assert_correct_cstruct(Cs1), + Cs1. + +expand_index_attrs(#cstruct{index = Ix, attributes = Attrs, + name = Tab} = Cs) -> + Prefered = prefered_index_types(Cs), + expand_index_attrs(Ix, Tab, Attrs, Prefered). + +expand_index_attrs(Ix, Tab, Attrs, Prefered) -> + lists:map(fun(P) when is_integer(P); is_atom(P) -> + {attr_to_pos(P, Attrs), Prefered}; + ({A} = P) when is_atom(A) -> + {P, Prefered}; + ({P, Type}) -> + {attr_to_pos(P, Attrs), Type}; + (_Other) -> + mnesia:abort({bad_type, Tab, {index, Ix}}) + end, Ix). + +prefered_index_types(#cstruct{external_copies = Ext}) -> + ExtTypes = [mnesia_lib:semantics(S, index_types) || + {S,Ns} <- Ext, Ns =/= []], + case intersect_types(ExtTypes) of + [] -> ordered; + [Pref|_] -> Pref + end. + +intersect_types([]) -> + []; +intersect_types([S1, S2|Rest]) -> + intersect_types([S1 -- (S1 -- S2)|Rest]); +intersect_types([S]) -> + S. + +verify_external_copies(#cstruct{external_copies = []} = Cs) -> + Cs; +verify_external_copies(#cstruct{name = Tab, external_copies = EC} = Cs) -> + Bad = {bad_type, Tab, {external_copies, EC}}, + AllECNodes = lists:concat([Ns || {_, Ns} <- EC, + is_list(Ns)]), + verify(true, length(lists:usort(AllECNodes)) == length(AllECNodes), Bad), + CsL = cs2list(Cs), + CsL1 = lists:foldl( + fun({{Alias, Mod}, Ns} = _X, CsLx) -> + BadTab = fun(Why) -> + {Why, Tab, {{ext, Alias, Mod},Ns}} + end, + verify(atom, mnesia_lib:etype(Mod), BadTab), + verify(true, fun() -> + lists:all(fun is_atom/1, Ns) + end, BadTab), + check_semantics(Mod, Alias, BadTab, Cs), + try Mod:check_definition(Alias, Tab, Ns, CsLx) of + ok -> + CsLx; + {ok, CsLx1} -> + CsLx1; + {error, Reason} -> + mnesia:abort(BadTab(Reason)) + catch + error:E -> + mnesia:abort(BadTab(E)) + end; + (_, CsLx) -> + CsLx + end, CsL, EC), + list2cs(CsL1). + +check_semantics(Mod, Alias, BadTab, #cstruct{type = Type}) -> + Ext = {ext, Alias, Mod}, + case lists:member(mnesia_lib:semantics(Ext, storage), [ram_copies, disc_copies, + disc_only_copies]) of + false -> mnesia:abort(BadTab(invalid_storage)); + true -> ok + end, + case lists:member(Type, mnesia_lib:semantics(Ext, types)) of + false -> mnesia:abort(BadTab(bad_type)); + true -> ok + end. + +assert_correct_cstruct(Cs) when is_record(Cs, cstruct) -> verify_nodes(Cs), Tab = Cs#cstruct.name, @@ -888,22 +1166,30 @@ verify_cstruct(Cs) when is_record(Cs, cstruct) -> Attrs), Index = Cs#cstruct.index, + verify({alt, [nil, list]}, mnesia_lib:etype(Index), {bad_type, Tab, {index, Index}}), + IxPlugins = get_index_plugins(), + AllowIndexOnKey = check_if_allow_index_on_key(), IxFun = - fun(Pos) -> - verify(true, fun() -> - if - is_integer(Pos), - Pos > 2, - Pos =< Arity -> - true; - true -> false - end - end, - {bad_type, Tab, {index, [Pos]}}) - end, + fun(Pos) -> + verify( + true, fun() -> + I = index_pos(Pos), + case Pos of + {_, T} -> + (T==bag orelse T==ordered) + andalso good_ix_pos( + I, AllowIndexOnKey, + Arity, IxPlugins); + _ -> + good_ix_pos(Pos, AllowIndexOnKey, + Arity, IxPlugins) + end + end, + {bad_type, Tab, {index, [Pos]}}) + end, lists:foreach(IxFun, Index), LC = Cs#cstruct.local_content, @@ -927,7 +1213,9 @@ verify_cstruct(Cs) when is_record(Cs, cstruct) -> {badarg, Tab, {snmp, Snmp}}), CheckProp = fun(Prop) when is_tuple(Prop), size(Prop) >= 1 -> ok; - (Prop) -> mnesia:abort({bad_type, Tab, {user_properties, [Prop]}}) + (Prop) -> + mnesia:abort({bad_type, Tab, + {user_properties, [Prop]}}) end, lists:foreach(CheckProp, Cs#cstruct.user_properties), @@ -947,17 +1235,45 @@ verify_cstruct(Cs) when is_record(Cs, cstruct) -> mnesia:abort({bad_type, Tab, {version, Version}}) end. +good_ix_pos({_} = P, _, _, Plugins) -> + lists:keymember(P, 1, Plugins); +good_ix_pos(I, true, Arity, _) when is_integer(I) -> + I >= 0 andalso I =< Arity; +good_ix_pos(I, false, Arity, _) when is_integer(I) -> + I > 2 andalso I =< Arity; +good_ix_pos(_, _, _, _) -> + false. + + +check_if_allow_index_on_key() -> + case mnesia_monitor:get_env(allow_index_on_key) of + true -> + true; + _ -> + false + end. + verify_nodes(Cs) -> Tab = Cs#cstruct.name, Ram = Cs#cstruct.ram_copies, Disc = Cs#cstruct.disc_copies, DiscOnly = Cs#cstruct.disc_only_copies, + Ext = lists:append([Ns || {_,Ns} <- Cs#cstruct.external_copies]), LoadOrder = Cs#cstruct.load_order, verify({alt, [nil, list]}, mnesia_lib:etype(Ram), {bad_type, Tab, {ram_copies, Ram}}), verify({alt, [nil, list]}, mnesia_lib:etype(Disc), {bad_type, Tab, {disc_copies, Disc}}), + lists:foreach( + fun({BE, Ns}) -> + verify({alt, [nil, list]}, mnesia_lib:etype(Ns), + {bad_type, Tab, {BE, Ns}}), + lists:foreach(fun(N) -> + verify(atom, mnesia_lib:etype(N), + {bad_type, Tab, {BE, Ns}}) + end, Ns) + end, Cs#cstruct.external_copies), case Tab of schema -> verify([], DiscOnly, {bad_type, Tab, {disc_only_copies, DiscOnly}}); @@ -969,12 +1285,15 @@ verify_nodes(Cs) -> verify(integer, mnesia_lib:etype(LoadOrder), {bad_type, Tab, {load_order, LoadOrder}}), - Nodes = Ram ++ Disc ++ DiscOnly, + Nodes = Ram ++ Disc ++ DiscOnly ++ Ext, verify(list, mnesia_lib:etype(Nodes), {combine_error, Tab, - [{ram_copies, []}, {disc_copies, []}, {disc_only_copies, []}]}), + [{ram_copies, []}, {disc_copies, []}, + {disc_only_copies, []}, {external_copies, []}]}), verify(false, has_duplicates(Nodes), {combine_error, Tab, Nodes}), - AtomCheck = fun(N) -> verify(atom, mnesia_lib:etype(N), {bad_type, Tab, N}) end, + AtomCheck = fun(N) -> + verify(atom, mnesia_lib:etype(N), {bad_type, Tab, N}) + end, lists:foreach(AtomCheck, Nodes). verify(Expected, Fun, Error) when is_function(Fun) -> @@ -1057,28 +1376,194 @@ check_active([{badrpc, Reason} | _Replies], Expl, Tab) -> check_active([], _Expl, _Tab) -> ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Function for definining an external backend type + +add_backend_type(Name, Module) -> + case schema_transaction(fun() -> do_add_backend_type(Name, Module) end) of + {atomic, NeedsInit} -> + case NeedsInit of + true -> + Module:init_backend(); + false -> + ignore + end, + Module:add_aliases([Name]), + {atomic, ok}; + Other -> + Other + end. + +do_add_backend_type(Name, Module) -> + verify_backend_type(Name, Module), + Types = case do_read_table_property(schema, mnesia_backend_types) of + undefined -> + []; + {_, Ts} -> + case lists:keymember(Name, 1, Ts) of + true -> + mnesia:abort({backend_type_already_exists, Name}); + false -> + Ts + end + end, + ModuleRegistered = lists:keymember(Module, 2, Types), + do_write_table_property(schema, {mnesia_backend_types, + [{Name, Module}|Types]}), + ModuleRegistered. + +delete_backend_type(Name) -> + schema_transaction(fun() -> do_delete_backend_type(Name) end). + +do_delete_backend_type(Name) -> + case do_read_table_property(schema, mnesia_backend_types) of + undefined -> + []; + {_, Ts} -> + case lists:keyfind(Name, 1, Ts) of + {_, Mod} -> + case using_backend_type(Name, Mod) of + [_|_] = Tabs -> + mnesia:abort({backend_in_use, {Name, Tabs}}); + [] -> + do_write_table_property( + schema, {mnesia_backend_types, + lists:keydelete(Name, 1, Ts)}) + end; + false -> + mnesia:abort({no_such_backend, Name}) + end + end. + +using_backend_type(Name, Mod) -> + Ext = ets:select(mnesia_gvar, + [{ {{'$1',external_copies},'$2'}, [], [{{'$1','$2'}}] }]), + Entry = {Name, Mod}, + [T || {T,C} <- Ext, + lists:keymember(Entry, 1, C)]. + +verify_backend_type(Name, Module) -> + case legal_backend_name(Name) of + false -> + mnesia:abort({bad_type, {backend_type,Name,Module}}); + true -> + ok + end, + ExpectedExports = mnesia_backend_type:behaviour_info(callbacks), + Exports = try Module:module_info(exports) + catch + error:_ -> + mnesia:abort({undef_backend, Module}) + end, + case ExpectedExports -- Exports of + [] -> + ok; + _Other -> + io:fwrite(user, "Missing backend_type exports: ~tp~n", [_Other]), + mnesia:abort({bad_type, {backend_type,Name,Module}}) + end. + +legal_backend_name(Name) -> + is_atom(Name) andalso + (not lists:member(Name, record_info(fields, cstruct))). + +%% Used e.g. by mnesia:system_info(backend_types). +backend_types() -> + [ram_copies, disc_copies, disc_only_copies | + [T || {T,_} <- get_ext_types()]]. + +add_index_plugin(Name, Module, Function) -> + schema_transaction( + fun() -> do_add_index_plugin(Name, Module, Function) end). + +do_add_index_plugin(Name, Module, Function) -> + verify_index_plugin(Name, Module, Function), + Plugins = case do_read_table_property(schema, mnesia_index_plugins) of + undefined -> + []; + {_, Ps} -> + case lists:keymember(Name, 1, Ps) of + true -> + mnesia:abort({index_plugin_already_exists, Name}); + false -> + Ps + end + end, + do_write_table_property(schema, {mnesia_index_plugins, + [{Name, Module, Function}|Plugins]}). + +delete_index_plugin(P) -> + schema_transaction( + fun() -> do_delete_index_plugin(P) end). + +do_delete_index_plugin({A} = P) when is_atom(A) -> + Plugins = get_index_plugins(), + case lists:keyfind(P, 1, Plugins) of + false -> + mnesia:abort({no_exists, {index_plugin, P}}); + _Found -> + case ets:select(mnesia_gvar, + [{ {{'$1',{index,{P,'_'}}},'_'},[],['$1']}, + { {{'$1',{index,P}},'_'},[],['$1']}], 1) of + {[_], _} -> + mnesia:abort({plugin_in_use, P}); + '$end_of_table' -> + do_write_table_property( + schema, {mnesia_index_plugins, + lists:keydelete(P, 1, Plugins)}) + end + end. + +verify_index_plugin({A} = Name, Module, Function) + when is_atom(A), is_atom(Module), is_atom(Function) -> + case code:ensure_loaded(Module) of + {error, nofile} -> + mnesia:abort({bad_type, {index_plugin,Name,Module,Function}}); + {module,_} -> + %% Index plugins are called as Module:Function(Tab, Pos, Obj) + case erlang:function_exported(Module, Function, 3) of + true -> + ok; + false -> + mnesia:abort( + {bad_type, {index_plugin,Name,Module,Function}}) + end + end; +verify_index_plugin(Name, Module, Function) -> + mnesia:abort({bad_type, {index_plugin,Name,Module,Function}}). + + +%% Used e.g. by mnesia:system_info(backend_types). +index_plugins() -> + get_index_plugins(). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Here's the real interface function to create a table -create_table(TabDef) -> - schema_transaction(fun() -> do_multi_create_table(TabDef) end). +create_table([_|_] = TabDef) -> + schema_transaction(fun() -> do_multi_create_table(TabDef) end); +create_table(Arg) -> {aborted, {badarg, Arg}}. %% And the corresponding do routines .... do_multi_create_table(TabDef) -> get_tid_ts_and_lock(schema, write), ensure_writable(schema), + do_create_table(TabDef), + ok. + +do_create_table(TabDef) when is_list(TabDef) -> Cs = api_list2cs(TabDef), case Cs#cstruct.frag_properties of [] -> - do_create_table(Cs); + do_create_table_1(Cs); _Props -> CsList = mnesia_frag:expand_cstruct(Cs), - lists:foreach(fun do_create_table/1, CsList) - end, - ok. + lists:foreach(fun do_create_table_1/1, CsList) + end. -do_create_table(Cs) -> +do_create_table_1(Cs) -> {_Mod, _Tid, Ts} = get_tid_ts_and_lock(schema, none), Store = Ts#tidstore.store, do_insert_schema_ops(Store, make_create_table(Cs)). @@ -1088,14 +1573,9 @@ make_create_table(Cs) -> verify(false, check_if_exists(Tab), {already_exists, Tab}), unsafe_make_create_table(Cs). -% unsafe_do_create_table(Cs) -> -% {_Mod, Tid, Ts} = get_tid_ts_and_lock(schema, none), -% Store = Ts#tidstore.store, -% do_insert_schema_ops(Store, unsafe_make_create_table(Cs)). - -unsafe_make_create_table(Cs) -> +unsafe_make_create_table(Cs0) -> {_Mod, Tid, Ts} = get_tid_ts_and_lock(schema, none), - verify_cstruct(Cs), + Cs = verify_cstruct(Cs0), Tab = Cs#cstruct.name, %% Check that we have all disc replica nodes running @@ -1142,6 +1622,7 @@ do_delete_table(Tab) -> ensure_writable(schema), insert_schema_ops(TidTs, make_delete_table(Tab, whole_table)). +-dialyzer({no_improper_lists, make_delete_table/2}). make_delete_table(Tab, Mode) -> case existed_before(Tab) of false -> @@ -1242,8 +1723,7 @@ make_add_table_copy(Tab, Node, Storage) -> Cs = incr_version(val({Tab, cstruct})), Ns = mnesia_lib:cs_to_nodes(Cs), verify(false, lists:member(Node, Ns), {already_exists, Tab, Node}), - Cs2 = new_cs(Cs, Node, Storage, add), - verify_cstruct(Cs2), + Cs2 = verify_cstruct(new_cs(Cs, Node, Storage, add)), %% Check storage and if node is running IsRunning = lists:member(Node, val({current, db_nodes})), @@ -1286,18 +1766,19 @@ make_del_table_copy(Tab, Node) -> mnesia:abort({combine_error, Tab, "Last replica"}); [] -> ensure_active(Cs), - dbg_out("Last replica deleted in table ~p~n", [Tab]), + dbg_out("Last replica deleted in table ~tp~n", [Tab]), make_delete_table(Tab, whole_table); _ when Tab == schema -> %% ensure_active(Cs2), ensure_not_active(Tab, Node), - verify_cstruct(Cs2), + Cs3 = verify_cstruct(Cs2), Ops = remove_node_from_tabs(val({schema, tables}), Node), - [{op, del_table_copy, ram_copies, Node, vsn_cs2list(Cs2)} | Ops]; + [{op, del_table_copy, ram_copies, Node, vsn_cs2list(Cs3)} | Ops]; _ -> ensure_active(Cs), - verify_cstruct(Cs2), - [{op, del_table_copy, Storage, Node, vsn_cs2list(Cs2)}] + Cs3 = verify_cstruct(Cs2), + get_tid_ts_and_lock(Tab, write), + [{op, del_table_copy, Storage, Node, vsn_cs2list(Cs3)}] end. remove_node_from_tabs([], _Node) -> @@ -1323,8 +1804,9 @@ remove_node_from_tabs([Tab|Rest], Node) -> [{op, delete_table, vsn_cs2list(Cs)} | remove_node_from_tabs(Rest, Node)]; _Ns -> - verify_cstruct(Cs2), - [{op, del_table_copy, ram_copies, Node, vsn_cs2list(Cs2)}| + Cs3 = verify_cstruct(Cs2), + get_tid_ts_and_lock(Tab, write), + [{op, del_table_copy, ram_copies, Node, vsn_cs2list(Cs3)}| remove_node_from_tabs(Rest, Node)] end end. @@ -1342,8 +1824,34 @@ new_cs(Cs, Node, disc_copies, del) -> new_cs(Cs, Node, disc_only_copies, del) -> Cs#cstruct{disc_only_copies = lists:delete(Node , Cs#cstruct.disc_only_copies)}; -new_cs(Cs, _Node, Storage, _Op) -> - mnesia:abort({badarg, Cs#cstruct.name, Storage}). +new_cs(#cstruct{external_copies = ExtCps} = Cs, Node, Storage0, Op) -> + Storage = case Storage0 of + {ext, Alias, _} -> Alias; + Alias -> Alias + end, + ExtTypes = get_ext_types(), + case lists:keyfind(Storage, 1, ExtTypes) of + false -> + mnesia:abort({badarg, Cs#cstruct.name, Storage}); + {_, Mod} -> + Key = {Storage, Mod}, + case {lists:keymember(Key, 1, ExtCps), Op} of + {false, del} -> + Cs; + {false, add} -> + Cs#cstruct{external_copies = [{Key, [Node]}|ExtCps]}; + {true, _} -> + F = fun({K, Ns}) when K == Key -> + case Op of + del -> {K, lists:delete(Node, Ns)}; + add -> {K, opt_add(Node, Ns)} + end; + (X) -> + X + end, + Cs#cstruct{external_copies = lists:map(F, ExtCps)} + end + end. opt_add(N, L) -> [N | lists:delete(N, L)]. @@ -1355,6 +1863,11 @@ do_move_table(schema, _FromNode, _ToNode) -> mnesia:abort({bad_type, schema}); do_move_table(Tab, FromNode, ToNode) when is_atom(FromNode), is_atom(ToNode) -> TidTs = get_tid_ts_and_lock(schema, write), + AnyOld = lists:any(fun(Node) -> mnesia_monitor:needs_protocol_conversion(Node) end, + [ToNode|val({Tab, where_to_write})]), + if AnyOld -> ignore; %% Leads to deadlock on old nodes + true -> get_tid_ts_and_lock(Tab, write) + end, insert_schema_ops(TidTs, make_move_table(Tab, FromNode, ToNode)); do_move_table(Tab, FromNode, ToNode) -> mnesia:abort({badarg, Tab, FromNode, ToNode}). @@ -1374,8 +1887,7 @@ make_move_table(Tab, FromNode, ToNode) -> verify(true, lists:member(ToNode, Running), {not_active, schema, ToNode}), Cs2 = new_cs(Cs, ToNode, Storage, add), - Cs3 = new_cs(Cs2, FromNode, Storage, del), - verify_cstruct(Cs3), + Cs3 = verify_cstruct(new_cs(Cs2, FromNode, Storage, del)), [{op, add_table_copy, Storage, ToNode, vsn_cs2list(Cs2)}, {op, sync_trans}, {op, del_table_copy, Storage, FromNode, vsn_cs2list(Cs3)}]. @@ -1402,9 +1914,11 @@ make_change_table_copy_type(Tab, Node, ToS) -> Cs = incr_version(val({Tab, cstruct})), FromS = mnesia_lib:storage_type_at_node(Node, Tab), - case compare_storage_type(false, FromS, ToS) of + ToSExp = expand_storage_type(ToS), + + case compare_storage_type(false, FromS, ToSExp) of {same, _} -> - mnesia:abort({already_exists, Tab, Node, ToS}); + mnesia:abort({already_exists, Tab, Node, ToSExp}); {diff, _} -> ignore; incompatible -> @@ -1412,14 +1926,12 @@ make_change_table_copy_type(Tab, Node, ToS) -> end, Cs2 = new_cs(Cs, Node, FromS, del), - Cs3 = new_cs(Cs2, Node, ToS, add), - verify_cstruct(Cs3), - - [{op, change_table_copy_type, Node, FromS, ToS, vsn_cs2list(Cs3)}]. + Cs3 = verify_cstruct(new_cs(Cs2, Node, ToS, add)), + [{op, change_table_copy_type, Node, FromS, ToSExp, vsn_cs2list(Cs3)}]. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% change index functions .... -%% Pos is allready added by 1 in both of these functions +%% Pos is already added by 1 in both of these functions add_table_index(Tab, Pos) -> schema_transaction(fun() -> do_add_table_index(Tab, Pos) end). @@ -1437,11 +1949,12 @@ make_add_table_index(Tab, Pos) -> Cs = incr_version(val({Tab, cstruct})), ensure_active(Cs), Ix = Cs#cstruct.index, - verify(false, lists:member(Pos, Ix), {already_exists, Tab, Pos}), + verify(false, lists:keymember(index_pos(Pos), 1, Ix), + {already_exists, Tab, Pos}), Ix2 = lists:sort([Pos | Ix]), - Cs2 = Cs#cstruct{index = Ix2}, - verify_cstruct(Cs2), - [{op, add_index, Pos, vsn_cs2list(Cs2)}]. + Cs2 = verify_cstruct(Cs#cstruct{index = Ix2}), + NewPosInfo = lists:keyfind(Pos, 1, Cs2#cstruct.index), + [{op, add_index, NewPosInfo, vsn_cs2list(Cs2)}]. del_table_index(Tab, Pos) -> schema_transaction(fun() -> do_del_table_index(Tab, Pos) end). @@ -1459,9 +1972,8 @@ make_del_table_index(Tab, Pos) -> Cs = incr_version(val({Tab, cstruct})), ensure_active(Cs), Ix = Cs#cstruct.index, - verify(true, lists:member(Pos, Ix), {no_exists, Tab, Pos}), - Cs2 = Cs#cstruct{index = lists:delete(Pos, Ix)}, - verify_cstruct(Cs2), + verify(true, lists:keymember(Pos, 1, Ix), {no_exists, Tab, Pos}), + Cs2 = verify_cstruct(Cs#cstruct{index = lists:keydelete(Pos, 1, Ix)}), [{op, del_index, Pos, vsn_cs2list(Cs2)}]. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1483,8 +1995,7 @@ make_add_snmp(Tab, Ustruct) -> verify([], Cs#cstruct.snmp, {already_exists, Tab, snmp}), Error = {badarg, Tab, snmp, Ustruct}, verify(true, mnesia_snmp_hook:check_ustruct(Ustruct), Error), - Cs2 = Cs#cstruct{snmp = Ustruct}, - verify_cstruct(Cs2), + Cs2 = verify_cstruct(Cs#cstruct{snmp = Ustruct}), [{op, add_snmp, Ustruct, vsn_cs2list(Cs2)}]. del_snmp(Tab) -> @@ -1501,8 +2012,7 @@ make_del_snmp(Tab) -> ensure_writable(schema), Cs = incr_version(val({Tab, cstruct})), ensure_active(Cs), - Cs2 = Cs#cstruct{snmp = []}, - verify_cstruct(Cs2), + Cs2 = verify_cstruct(Cs#cstruct{snmp = []}), [{op, del_snmp, vsn_cs2list(Cs2)}]. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1531,19 +2041,21 @@ make_transform(Tab, Fun, NewAttrs, NewRecName) -> Cs = incr_version(val({Tab, cstruct})), ensure_active(Cs), ensure_writable(Tab), - case mnesia_lib:val({Tab, index}) of + case Cs#cstruct.index of [] -> - Cs2 = Cs#cstruct{attributes = NewAttrs, record_name = NewRecName}, - verify_cstruct(Cs2), + Cs2 = verify_cstruct( + Cs#cstruct{attributes = NewAttrs, + record_name = NewRecName}), [{op, transform, Fun, vsn_cs2list(Cs2)}]; PosList -> - DelIdx = fun(Pos, Ncs) -> + DelIdx = fun({Pos,_}, Ncs) -> Ix = Ncs#cstruct.index, - Ncs1 = Ncs#cstruct{index = lists:delete(Pos, Ix)}, + Ix2 = lists:keydelete(Pos, 1, Ix), + Ncs1 = Ncs#cstruct{index = Ix2}, Op = {op, del_index, Pos, vsn_cs2list(Ncs1)}, {Op, Ncs1} end, - AddIdx = fun(Pos, Ncs) -> + AddIdx = fun({_,_} = Pos, Ncs) -> Ix = Ncs#cstruct.index, Ix2 = lists:sort([Pos | Ix]), Ncs1 = Ncs#cstruct{index = Ix2}, @@ -1553,10 +2065,16 @@ make_transform(Tab, Fun, NewAttrs, NewRecName) -> {DelOps, Cs1} = lists:mapfoldl(DelIdx, Cs, PosList), Cs2 = Cs1#cstruct{attributes = NewAttrs, record_name = NewRecName}, {AddOps, Cs3} = lists:mapfoldl(AddIdx, Cs2, PosList), - verify_cstruct(Cs3), - lists:flatten([DelOps, {op, transform, Fun, vsn_cs2list(Cs2)}, AddOps]) + _ = verify_cstruct(Cs3), % just a sanity check + lists:flatten([DelOps, {op, transform, Fun, vsn_cs2list(Cs2)}, + AddOps]) end. +index_pos({Pos,_}) -> Pos; +index_pos(Pos) when is_integer(Pos) -> Pos; +index_pos({P} = Pos) when is_atom(P) -> Pos. + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% @@ -1576,8 +2094,7 @@ make_change_table_access_mode(Tab, Mode) -> ensure_active(Cs), OldMode = Cs#cstruct.access_mode, verify(false, OldMode == Mode, {already_exists, Tab, Mode}), - Cs2 = Cs#cstruct{access_mode = Mode}, - verify_cstruct(Cs2), + Cs2 = verify_cstruct(Cs#cstruct{access_mode = Mode}), [{op, change_table_access_mode, vsn_cs2list(Cs2), OldMode, Mode}]. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1597,8 +2114,7 @@ make_change_table_load_order(Tab, LoadOrder) -> Cs = incr_version(val({Tab, cstruct})), ensure_active(Cs), OldLoadOrder = Cs#cstruct.load_order, - Cs2 = Cs#cstruct{load_order = LoadOrder}, - verify_cstruct(Cs2), + Cs2 = verify_cstruct(Cs#cstruct{load_order = LoadOrder}), [{op, change_table_load_order, vsn_cs2list(Cs2), OldLoadOrder, LoadOrder}]. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1644,6 +2160,7 @@ write_table_property(Tab, Prop) when is_tuple(Prop), size(Prop) >= 1 -> schema_transaction(fun() -> do_write_table_property(Tab, Prop) end); write_table_property(Tab, Prop) -> {aborted, {bad_type, Tab, Prop}}. + do_write_table_property(Tab, Prop) -> TidTs = get_tid_ts_and_lock(schema, write), {_, _, Ts} = TidTs, @@ -1651,13 +2168,13 @@ do_write_table_property(Tab, Prop) -> case change_prop_in_existing_op(Tab, Prop, write_property, Store) of true -> dbg_out("change_prop_in_existing_op" - "(~p,~p,write_property,Store) -> true~n", + "(~tp,~p,write_property,Store) -> true~n", [Tab,Prop]), %% we have merged the table prop into the create_table op ok; false -> dbg_out("change_prop_in_existing_op" - "(~p,~p,write_property,Store) -> false~n", + "(~tp,~p,write_property,Store) -> false~n", [Tab,Prop]), %% this must be an existing table get_tid_ts_and_lock(Tab, none), @@ -1675,8 +2192,7 @@ make_write_table_properties(Tab, [Prop | Props], Cs) -> PropKey = element(1, Prop), DelProps = lists:keydelete(PropKey, 1, OldProps), MergedProps = lists:merge(DelProps, [Prop]), - Cs2 = Cs#cstruct{user_properties = MergedProps}, - verify_cstruct(Cs2), + Cs2 = verify_cstruct(Cs#cstruct{user_properties = MergedProps}), [{op, write_property, vsn_cs2list(Cs2), Prop} | make_write_table_properties(Tab, Props, Cs2)]; make_write_table_properties(_Tab, [], _Cs) -> @@ -1721,22 +2237,38 @@ do_read_table_property(Tab, Key) -> {_, _, Ts} = TidTs, Store = Ts#tidstore.store, Props = ets:foldl( - fun({op, create_table, [{name, T}|Opts]}, _Acc) - when T==Tab -> + fun({op, announce_im_running,_,Opts,_,_}, _Acc) when Tab==schema -> + find_props(Opts); + ({op, create_table, [{name, T}|Opts]}, _Acc) + when T==Tab -> find_props(Opts); ({op, Op, [{name,T}|Opts], _Prop}, _Acc) - when T==Tab, Op==write_property; Op==delete_property -> + when T==Tab, Op==write_property; + T==Tab, Op==delete_property -> find_props(Opts); ({op, delete_table, [{name,T}|_]}, _Acc) when T==Tab -> []; (_Other, Acc) -> Acc - end, [], Store), - case lists:keysearch(Key, 1, Props) of - {value, Property} -> - Property; - false -> + end, undefined, Store), + case Props of + undefined -> + get_tid_ts_and_lock(Tab, read), + dirty_read_table_property(Tab, Key); + _ when is_list(Props) -> + case lists:keyfind(Key, 1, Props) of + false -> + undefined; + Other -> + Other + end + end. + +dirty_read_table_property(Tab, Key) -> + try ets:lookup_element(mnesia_gvar, {Tab,user_property,Key}, 2) + catch + error:_ -> undefined end. @@ -1773,13 +2305,13 @@ do_delete_table_property(Tab, PropKey) -> case change_prop_in_existing_op(Tab, PropKey, delete_property, Store) of true -> dbg_out("change_prop_in_existing_op" - "(~p,~p,delete_property,Store) -> true~n", + "(~tp,~p,delete_property,Store) -> true~n", [Tab,PropKey]), %% we have merged the table prop into the create_table op ok; false -> dbg_out("change_prop_in_existing_op" - "(~p,~p,delete_property,Store) -> false~n", + "(~tp,~p,delete_property,Store) -> false~n", [Tab,PropKey]), %% this must be an existing table get_tid_ts_and_lock(Tab, none), @@ -1796,8 +2328,7 @@ make_delete_table_properties(Tab, PropKeys) -> make_delete_table_properties(Tab, [PropKey | PropKeys], Cs) -> OldProps = Cs#cstruct.user_properties, Props = lists:keydelete(PropKey, 1, OldProps), - Cs2 = Cs#cstruct{user_properties = Props}, - verify_cstruct(Cs2), + Cs2 = verify_cstruct(Cs#cstruct{user_properties = Props}), [{op, delete_property, vsn_cs2list(Cs2), PropKey} | make_delete_table_properties(Tab, PropKeys, Cs2)]; make_delete_table_properties(_Tab, [], _Cs) -> @@ -1894,17 +2425,17 @@ prepare_op(_Tid, {op, sync_trans}, {part, CoordPid}) -> {sync_trans, CoordPid} -> {false, optional}; {mnesia_down, _Node} = Else -> - mnesia_lib:verbose("sync_op terminated due to ~p~n", [Else]), + mnesia_lib:verbose("sync_op terminated due to ~tp~n", [Else]), mnesia:abort(Else); {'EXIT', _, _} = Else -> - mnesia_lib:verbose("sync_op terminated due to ~p~n", [Else]), + mnesia_lib:verbose("sync_op terminated due to ~tp~n", [Else]), mnesia:abort(Else) end; prepare_op(_Tid, {op, sync_trans}, {coord, Nodes}) -> case receive_sync(Nodes, []) of {abort, Reason} -> - mnesia_lib:verbose("sync_op terminated due to ~p~n", [Reason]), + mnesia_lib:verbose("sync_op terminated due to ~tp~n", [Reason]), mnesia:abort(Reason); Pids -> [Pid ! {sync_trans, self()} || Pid <- Pids], @@ -1938,6 +2469,11 @@ prepare_op(Tid, {op, create_table, TabDef}, _WaitFor) -> create_disc_only_table(Tab,Cs), insert_cstruct(Tid, Cs, false), {true, optional}; + {ext, Alias, Mod} -> + mnesia_lib:set({Tab, create_table},true), + create_external_table(Alias, Tab, Mod, Cs), + insert_cstruct(Tid, Cs, false), + {true, optional}; unknown -> %% No replica on this node mnesia_lib:set({Tab, create_table},true), insert_cstruct(Tid, Cs, false), @@ -1962,7 +2498,7 @@ prepare_op(Tid, {op, add_table_copy, Storage, Node, TabDef}, _WaitFor) -> end, %% Tables are created by mnesia_loader get_network code insert_cstruct(Tid, Cs, true), - case mnesia_controller:get_network_copy(Tab, Cs) of + case mnesia_controller:get_network_copy(Tid, Tab, Cs) of {loaded, ok} -> {true, optional}; {not_loaded, ErrReason} -> @@ -1993,28 +2529,11 @@ prepare_op(Tid, {op, add_table_copy, Storage, Node, TabDef}, _WaitFor) -> {true, optional} end; -prepare_op(Tid, {op, del_table_copy, _Storage, Node, TabDef}, _WaitFor) -> +prepare_op(_Tid, {op, del_table_copy, _Storage, Node, TabDef}, _WaitFor) -> Cs = list2cs(TabDef), Tab = Cs#cstruct.name, - - if - %% Schema table lock is always required to run a schema op. - %% No need to look it. - node(Tid#tid.pid) == node(), Tab /= schema -> - Self = self(), - Pid = spawn_link(fun() -> lock_del_table(Tab, Node, Cs, Self) end), - put(mnesia_lock, Pid), - receive - {Pid, updated} -> - {true, optional}; - {Pid, FailReason} -> - mnesia:abort(FailReason); - {'EXIT', Pid, Reason} -> - mnesia:abort(Reason) - end; - true -> - {true, optional} - end; + set_where_to_read(Tab, Node, Cs), + {true, optional}; prepare_op(_Tid, {op, change_table_copy_type, N, FromS, ToS, TabDef}, _WaitFor) when N == node() -> @@ -2023,6 +2542,12 @@ prepare_op(_Tid, {op, change_table_copy_type, N, FromS, ToS, TabDef}, _WaitFor) NotActive = mnesia_lib:not_active_here(Tab), + if Tab =/= schema -> + check_if_disc_required(FromS, ToS); + true -> + ok + end, + if NotActive == true -> mnesia:abort({not_active, Tab, node()}); @@ -2062,6 +2587,15 @@ prepare_op(_Tid, {op, change_table_copy_type, N, FromS, ToS, TabDef}, _WaitFor) mnesia:abort({combine_error, Tab, ToS}) end; + element(1,FromS) == ext; element(1,ToS) == ext -> + if ToS == ram_copies -> + create_ram_table(Tab, Cs); + true -> + ok + end, + mnesia_dumper:dump_to_logfile(FromS, Tab), + mnesia_checkpoint:tm_change_table_copy_type(Tab, FromS, ToS); + FromS == ram_copies -> case mnesia_monitor:use_dir() of true -> @@ -2077,7 +2611,9 @@ prepare_op(_Tid, {op, change_table_copy_type, N, FromS, ToS, TabDef}, _WaitFor) disc_only_copies -> mnesia_dumper:raw_named_dump_table(Tab, dmp) end, - mnesia_checkpoint:tm_change_table_copy_type(Tab, FromS, ToS) + mnesia_checkpoint:tm_change_table_copy_type(Tab, + FromS, + ToS) end; false -> mnesia:abort({has_no_disc, node()}) @@ -2085,6 +2621,7 @@ prepare_op(_Tid, {op, change_table_copy_type, N, FromS, ToS, TabDef}, _WaitFor) FromS == disc_copies, ToS == disc_only_copies -> mnesia_dumper:raw_named_dump_table(Tab, dmp); + FromS == disc_only_copies -> Type = Cs#cstruct.type, create_ram_table(Tab, Cs), @@ -2096,6 +2633,7 @@ prepare_op(_Tid, {op, change_table_copy_type, N, FromS, ToS, TabDef}, _WaitFor) Err = "Failed to copy disc data to ram", mnesia:abort({system_limit, Tab, {Err,Reason}}) end; + true -> ignore end, @@ -2159,6 +2697,8 @@ prepare_op(_Tid, {op, transform, Fun, TabDef}, _WaitFor) -> {true, Objs, mandatory} catch _:Reason -> mnesia_lib:db_fixtable(Storage, Tab, false), + mnesia_lib:important("Transform function failed: '~tp' in '~tp'", + [Reason, erlang:get_stacktrace()]), exit({"Bad transform function", Tab, Fun, node(), Reason}) end end; @@ -2169,12 +2709,28 @@ prepare_op(_Tid, {op, merge_schema, TabDef}, _WaitFor) -> ok -> {true, optional}; Error -> - verbose("Merge_Schema ~p failed on ~p: ~p~n", [_Tid,node(),Error]), + verbose("Merge_Schema ~p failed on ~p: ~tp~n", [_Tid,node(),Error]), mnesia:abort({bad_commit, Error}) end; prepare_op(_Tid, _Op, _WaitFor) -> {true, optional}. +check_if_disc_required(FromS, ToS) -> + FromSem = mnesia_lib:semantics(FromS, storage), + ToSem = mnesia_lib:semantics(ToS, storage), + case {FromSem, ToSem} of + {ram_copies, _} when ToSem == disc_copies; + ToSem == disc_only_copies -> + case mnesia_monitor:use_dir() of + true -> + ok; + false -> + mnesia:abort({has_no_disc, node()}) + end; + _ -> + ok + end. + create_ram_table(Tab, #cstruct{type=Type, storage_properties=Props}) -> EtsOpts = proplists:get_value(ets, Props, []), Args = [{keypos, 2}, public, named_table, Type | EtsOpts], @@ -2216,6 +2772,14 @@ create_disc_only_table(Tab, #cstruct{type=Type, storage_properties=Props}) -> mnesia:abort({system_limit, Tab, {Err,Reason}}) end. +create_external_table(Alias, Tab, Mod, Cs) -> + case mnesia_monitor:unsafe_create_external(Tab, Alias, Mod, Cs) of + ok -> + ok; + {error,Reason} -> + Err = "Failed to create external table", + mnesia:abort({system_limit, Tab, {Err,Reason}}) + end. receive_sync([], Pids) -> Pids; @@ -2228,46 +2792,6 @@ receive_sync(Nodes, Pids) -> {abort, Else} end. -lock_del_table(Tab, NewNode, Cs0, Father) -> - Ns = val({schema, active_replicas}), - process_flag(trap_exit,true), - Lock = fun() -> - mnesia:write_lock_table(Tab), - %% Sigh using cs record - Set = fun(Node) -> - [Cs] = normalize_cs([Cs0], Node), - rpc:call(Node, ?MODULE, set_where_to_read, [Tab, NewNode, Cs]) - end, - Res = [Set(Node) || Node <- Ns], - Filter = fun(ok) -> - false; - ({badrpc, {'EXIT', {undef, _}}}) -> - %% This will be the case we talks with elder nodes - %% than 3.8.2, they will set where_to_read without - %% getting a lock. - false; - (_) -> - true - end, - case lists:filter(Filter, Res) of - [] -> - Father ! {self(), updated}, - %% When transaction is commited the process dies - %% and the lock is released. - receive _ -> ok end; - Err -> - Father ! {self(), {bad_commit, Err}} - end, - ok - end, - case mnesia:transaction(Lock) of - {atomic, ok} -> ok; - {aborted, R} -> Father ! {self(), R} - end, - unlink(Father), - unlink(whereis(mnesia_tm)), - exit(normal). - set_where_to_read(Tab, Node, Cs) -> case mnesia_lib:val({Tab, where_to_read}) of Node -> @@ -2390,7 +2914,10 @@ undo_prepare_op(Tid, {op, create_table, TabDef}) -> mnesia_monitor:unsafe_close_dets(Tab), Dat = mnesia_lib:tab2dat(Tab), %% disc_delete_table(Tab, Storage), - file:delete(Dat) + file:delete(Dat); + {ext, Alias, Mod} -> + Mod:close_table(Alias, Tab), + Mod:delete_table(Alias, Tab) end; undo_prepare_op(Tid, {op, add_table_copy, Storage, Node, TabDef}) -> @@ -2418,13 +2945,19 @@ undo_prepare_op(Tid, {op, add_table_copy, Storage, Node, TabDef}) -> insert_cstruct(Tid, Cs2, true) % Don't care about the version end; -undo_prepare_op(_Tid, {op, del_table_copy, _, Node, TabDef}) - when Node == node() -> - WriteLocker = get(mnesia_lock), - WriteLocker =/= undefined andalso (WriteLocker ! die), +undo_prepare_op(_Tid, {op, del_table_copy, _, Node, TabDef}) -> Cs = list2cs(TabDef), Tab = Cs#cstruct.name, - mnesia_lib:set({Tab, where_to_read}, Node); + if node() =:= Node -> + mnesia_lib:set({Tab, where_to_read}, Node); + true -> + case mnesia_lib:val({Tab, where_to_read}) of + nowhere -> + mnesia_lib:set_remote_where_to_read(Tab); + _ -> + ignore + end + end; undo_prepare_op(_Tid, {op, change_table_copy_type, N, FromS, ToS, TabDef}) when N == node() -> @@ -2484,6 +3017,8 @@ ram_delete_table(Tab, Storage) -> case Storage of unknown -> ignore; + {ext, _, _} -> + ignore; disc_only_copies -> ignore; _Else -> @@ -2545,13 +3080,27 @@ purge_known_files([File | Tail], KeepFiles, Dir, Suffixes) -> ignore; true -> AbsFile = filename:join([Dir, File]), - file:delete(AbsFile) - end + delete_recursive(AbsFile) + end end, purge_known_files(Tail, KeepFiles, Dir, Suffixes); purge_known_files([], _KeepFiles, _Dir, _Suffixes) -> ok. +%% Removes a directory or file recursively +delete_recursive(Path) -> + case filelib:is_dir(Path) of + true -> + {ok, Names} = file:list_dir(Path), + lists:foreach(fun(Name) -> + delete_recursive(filename:join(Path, Name)) + end, + Names), + file:del_dir(Path); + false -> + file:delete(Path) + end. + has_known_suffix(_File, _Suffixes, true) -> true; has_known_suffix(File, [Suffix | Tail], false) -> @@ -2559,11 +3108,33 @@ has_known_suffix(File, [Suffix | Tail], false) -> has_known_suffix(_File, [], Bool) -> Bool. -known_suffixes() -> real_suffixes() ++ tmp_suffixes(). +known_suffixes() -> known_suffixes(get_ext_types_disc()). + +known_suffixes(Ext) -> real_suffixes(Ext) ++ tmp_suffixes(Ext). + +real_suffixes(Ext) -> [".DAT", ".LOG", ".BUP", ".DCL", ".DCD"] ++ ext_real_suffixes(Ext). -real_suffixes() -> [".DAT", ".LOG", ".BUP", ".DCL", ".DCD"]. +tmp_suffixes() -> tmp_suffixes(get_ext_types_disc()). + +tmp_suffixes(Ext) -> [".TMP", ".BUPTMP", ".RET", ".DMP", "."] ++ ext_tmp_suffixes(Ext). + +ext_real_suffixes(Ext) -> + try lists:foldl(fun(Mod, Acc) -> Acc++Mod:real_suffixes() end, [], + [M || {_,M} <- Ext]) + catch + error:E -> + verbose("Cant find real ext suffixes (~tp)~n", [E]), + [] + end. -tmp_suffixes() -> [".TMP", ".BUPTMP", ".RET", ".DMP"]. +ext_tmp_suffixes(Ext) -> + try lists:foldl(fun(Mod, Acc) -> Acc++Mod:tmp_suffixes() end, [], + [M || {_,M} <- Ext]) + catch + error:E -> + verbose("Cant find tmp ext suffixes (~tp)~n", [E]), + [] + end. info() -> Tabs = lists:sort(val({schema, tables})), @@ -2572,14 +3143,14 @@ info() -> info(Tab) -> Props = get_table_properties(Tab), - io:format("-- Properties for ~w table --- ~n",[Tab]), + io:format("-- Properties for ~tw table --- ~n",[Tab]), info2(Tab, Props). info2(Tab, [{cstruct, _V} | Tail]) -> % Ignore cstruct info2(Tab, Tail); info2(Tab, [{frag_hash, _V} | Tail]) -> % Ignore frag_hash info2(Tab, Tail); info2(Tab, [{P, V} | Tail]) -> - io:format("~-20w -> ~p~n",[P,V]), + io:format("~-20tw -> ~tp~n",[P,V]), info2(Tab, Tail); info2(_, []) -> io:format("~n", []). @@ -2681,12 +3252,12 @@ do_restore(R, BupSchema) -> arrange_restore(R, Fun, Recs) -> R2 = R#r{insert_op = Fun, recs = Recs}, - case mnesia_bup:iterate(R#r.module, fun restore_items/4, R#r.opaque, R2) of + case mnesia_bup:iterate(R#r.module, fun restore_items/5, R#r.opaque, R2) of {ok, R3} -> R3#r.recs; {error, Reason} -> mnesia:abort(Reason) end. -restore_items([Rec | Recs], Header, Schema, R) -> +restore_items([Rec | Recs], Header, Schema, Ext, R) -> Tab = element(1, Rec), case lists:keysearch(Tab, 1, R#r.tables) of {value, {Tab, Where0, Snmp, RecName}} -> @@ -2699,13 +3270,13 @@ restore_items([Rec | Recs], Header, Schema, R) -> {Rest, NRecs} = restore_tab_items([Rec | Recs], Tab, RecName, Where, Snmp, R#r.recs, R#r.insert_op), - restore_items(Rest, Header, Schema, R#r{recs = NRecs}); + restore_items(Rest, Header, Schema, Ext, R#r{recs = NRecs}); false -> Rest = skip_tab_items(Recs, Tab), - restore_items(Rest, Header, Schema, R) + restore_items(Rest, Header, Schema, Ext, R) end; -restore_items([], _Header, _Schema, R) -> +restore_items([], _Header, _Schema, _Ext, R) -> R. restore_func(Tab, R) -> @@ -2719,8 +3290,14 @@ restore_func(Tab, R) -> where_to_commit(Tab, CsList) -> Ram = [{N, ram_copies} || N <- pick(Tab, ram_copies, CsList, [])], Disc = [{N, disc_copies} || N <- pick(Tab, disc_copies, CsList, [])], - DiscO = [{N, disc_only_copies} || N <- pick(Tab, disc_only_copies, CsList, [])], - Ram ++ Disc ++ DiscO. + DiscO = [{N, disc_only_copies} || + N <- pick(Tab, disc_only_copies, CsList, [])], + ExtNodes = [{Alias, Mod, pick(Tab, Alias, CsList, [])} || + {Alias, Mod} <- get_ext_types()], + Ext = lists:foldl(fun({Alias, Mod, Ns}, Acc) -> + [{N, {ext, Alias, Mod}} || N <- Ns] ++ Acc + end, [], ExtNodes), + Ram ++ Disc ++ DiscO ++ Ext. %% Changes of the Meta info of schema itself is not allowed restore_schema([{schema, schema, _List} | Schema], R) -> @@ -2799,7 +3376,13 @@ make_dump_tables([schema | _Tabs]) -> make_dump_tables([Tab | Tabs]) -> get_tid_ts_and_lock(Tab, read), TabDef = get_create_list(Tab), - DiscResident = val({Tab, disc_copies}) ++ val({Tab, disc_only_copies}), + DiscResident = + val({Tab, disc_copies}) ++ + val({Tab, disc_only_copies}) ++ + lists:concat([Ns || {{A,M},Ns} <- val({Tab, external_copies}), + lists:member( + mnesia_lib:semantics({ext,A,M},storage), + [disc_copies, disc_only_copies])]), verify([], DiscResident, {"Only allowed on ram_copies", Tab, DiscResident}), [{op, dump_table, unknown, TabDef} | make_dump_tables(Tabs)]; @@ -2811,7 +3394,9 @@ merge_schema() -> schema_transaction(fun() -> do_merge_schema([]) end). merge_schema(UserFun) -> - schema_transaction(fun() -> UserFun(fun(Arg) -> do_merge_schema(Arg) end) end). + schema_transaction(fun() -> + UserFun(fun(Arg) -> do_merge_schema(Arg) end) + end). do_merge_schema(LockTabs0) -> {_Mod, Tid, Ts} = get_tid_ts_and_lock(schema, write), @@ -2885,39 +3470,22 @@ do_merge_schema(LockTabs0) -> end. fetch_cstructs(Node) -> - case need_old_cstructs([Node]) of - false -> - rpc:call(Node, mnesia_controller, get_remote_cstructs, []); - _Ver -> - case rpc:call(Node, mnesia_controller, get_cstructs, []) of - {cstructs, Cs0, RR} -> - {cstructs, [list2cs(cs2list(Cs)) || Cs <- Cs0], RR}; - Err -> Err - end + Convert = mnesia_monitor:needs_protocol_conversion(Node), + case rpc:call(Node, mnesia_controller, get_remote_cstructs, []) of + {cstructs, Cs0, RemoteRunning1} when Convert -> + {cstructs, [list2cs(cs2list(Cs)) || Cs <- Cs0], RemoteRunning1}; + Result -> + Result end. need_old_cstructs() -> need_old_cstructs(val({schema, where_to_write})). need_old_cstructs(Nodes) -> - Filter = fun(Node) -> not mnesia_monitor:needs_protocol_conversion(Node) end, - case lists:dropwhile(Filter, Nodes) of + Filter = fun(Node) -> mnesia_monitor:needs_protocol_conversion(Node) end, + case lists:filter(Filter, Nodes) of [] -> false; - [Node|_] -> - case rpc:call(Node, mnesia_lib, val, [{schema,cstruct}]) of - #cstruct{} -> - %% mnesia_lib:warning("Mnesia on ~p do not need to convert cstruct (~p)~n", - %% [node(), Node]), - false; - {badrpc, _} -> - need_old_cstructs(lists:delete(Node,Nodes)); - Cs when element(1, Cs) == cstruct, tuple_size(Cs) == 17 -> - ver4_4_18; % Without majority - Cs when element(1, Cs) == cstruct, tuple_size(Cs) == 18 -> - ver4_4_19; % With majority - Cs when element(1, Cs) == cstruct, tuple_size(Cs) == 19 -> - ver4_6 % With storage_properties - end + Ns -> lists:min([element(1, ?catch_val({protocol, Node})) || Node <- Ns]) end. tab_to_nodes(Tab) when is_atom(Tab) -> @@ -3071,8 +3639,8 @@ do_make_merge_schema(Node, NeedsConv, RemoteCs = #cstruct{}) -> %% invariants must be enforced in order to allow merge of cstructs. %% %% Returns a new cstruct or issues a fatal error -merge_cstructs(Cs, RemoteCs, Force) -> - verify_cstruct(Cs), +merge_cstructs(Cs0, RemoteCs, Force) -> + Cs = verify_cstruct(Cs0), try do_merge_cstructs(Cs, RemoteCs, Force) of MergedCs when is_record(MergedCs, cstruct) -> MergedCs @@ -3082,15 +3650,15 @@ merge_cstructs(Cs, RemoteCs, Force) -> error:Reason -> exit(Reason) end. -do_merge_cstructs(Cs, RemoteCs, Force) -> - verify_cstruct(RemoteCs), +do_merge_cstructs(Cs, RemoteCs0, Force) -> + RemoteCs = verify_cstruct(RemoteCs0), Ns = mnesia_lib:uniq(mnesia_lib:cs_to_nodes(Cs) ++ mnesia_lib:cs_to_nodes(RemoteCs)), {AnythingNew, MergedCs} = merge_storage_type(Ns, false, Cs, RemoteCs, Force), - MergedCs2 = merge_versions(AnythingNew, MergedCs, RemoteCs, Force), - verify_cstruct(MergedCs2), - MergedCs2. + verify_cstruct( + merge_versions(AnythingNew, MergedCs, RemoteCs, Force)). + merge_storage_type([N | Ns], AnythingNew, Cs, RemoteCs, Force) -> Local = mnesia_lib:cs_to_storage_type(N, Cs), @@ -3148,7 +3716,7 @@ merge_versions(AnythingNew, Cs, RemoteCs, Force) -> ok; true -> Str = io_lib:format("Bad cookies. Cannot merge definitions of " - "table ~w. Local = ~w, Remote = ~w~n", + "table ~tw. Local = ~w, Remote = ~w~n", [Cs#cstruct.name, Cs, RemoteCs]), throw(Str) end, @@ -3168,7 +3736,7 @@ merge_versions(AnythingNew, Cs, RemoteCs, Force) -> do_merge_versions(AnythingNew, Cs, RemoteCs); true -> Str1 = io_lib:format("Cannot merge definitions of " - "table ~w. Local = ~w, Remote = ~w~n", + "table ~tw. Local = ~w, Remote = ~w~n", [Cs#cstruct.name, Cs, RemoteCs]), throw(Str1) end. @@ -3234,4 +3802,3 @@ unannounce_im_running([N | Ns]) -> unannounce_im_running(Ns); unannounce_im_running([]) -> ok. - diff --git a/lib/mnesia/src/mnesia_snmp_hook.erl b/lib/mnesia/src/mnesia_snmp_hook.erl index 1599ff1e65..58a20c6051 100644 --- a/lib/mnesia/src/mnesia_snmp_hook.erl +++ b/lib/mnesia/src/mnesia_snmp_hook.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2014. All Rights Reserved. +%% Copyright Ericsson AB 1996-2016. 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. diff --git a/lib/mnesia/src/mnesia_snmp_sup.erl b/lib/mnesia/src/mnesia_snmp_sup.erl deleted file mode 100644 index 862eaaec77..0000000000 --- a/lib/mnesia/src/mnesia_snmp_sup.erl +++ /dev/null @@ -1,43 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1997-2009. 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_snmp_sup). - --behaviour(supervisor). - --export([start/0, init/1]). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% top supervisor callback functions - -start() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, []). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% sub supervisor callback functions - -init([]) -> - Flags = {simple_one_for_one, 0, timer:hours(24)}, % Trust the top supervisor - MFA = {mnesia_snmp_hook, start, []}, - Modules = [?MODULE, mnesia_snmp_hook, supervisor], - KillAfter = mnesia_kernel_sup:supervisor_timeout(timer:seconds(3)), - Workers = [{?MODULE, MFA, transient, KillAfter, worker, Modules}], - {ok, {Flags, Workers}}. diff --git a/lib/mnesia/src/mnesia_sp.erl b/lib/mnesia/src/mnesia_sp.erl index 71c1e7dd0c..d65c659690 100644 --- a/lib/mnesia/src/mnesia_sp.erl +++ b/lib/mnesia/src/mnesia_sp.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1999-2009. All Rights Reserved. +%% Copyright Ericsson AB 1999-2016. 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. diff --git a/lib/mnesia/src/mnesia_subscr.erl b/lib/mnesia/src/mnesia_subscr.erl index 246d0e8697..dfaa20d2d3 100644 --- a/lib/mnesia/src/mnesia_subscr.erl +++ b/lib/mnesia/src/mnesia_subscr.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2013. All Rights Reserved. +%% Copyright Ericsson AB 1997-2016. 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. @@ -264,7 +264,7 @@ handle_call({change, How}, _From, State) -> {reply, Reply, State}; handle_call(Msg, _From, State) -> - error("~p got unexpected call: ~p~n", [?MODULE, Msg]), + error("~p got unexpected call: ~tp~n", [?MODULE, Msg]), {noreply, State}. %%---------------------------------------------------------------------- @@ -274,7 +274,7 @@ handle_call(Msg, _From, State) -> %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- handle_cast(Msg, State) -> - error("~p got unexpected cast: ~p~n", [?MODULE, Msg]), + error("~p got unexpected cast: ~tp~n", [?MODULE, Msg]), {noreply, State}. %%---------------------------------------------------------------------- @@ -292,7 +292,7 @@ handle_info({'EXIT', Pid, _Reason}, State) -> {noreply, State}; handle_info(Msg, State) -> - error("~p got unexpected info: ~p~n", [?MODULE, Msg]), + error("~p got unexpected info: ~tp~n", [?MODULE, Msg]), {noreply, State}. %%---------------------------------------------------------------------- diff --git a/lib/mnesia/src/mnesia_sup.erl b/lib/mnesia/src/mnesia_sup.erl index 3a9360aa6d..3e5792900b 100644 --- a/lib/mnesia/src/mnesia_sup.erl +++ b/lib/mnesia/src/mnesia_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2013. All Rights Reserved. +%% Copyright Ericsson AB 1996-2016. 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. @@ -23,46 +23,29 @@ -module(mnesia_sup). --behaviour(application). -behaviour(supervisor). --export([start/0, start/2, init/1, stop/1, start_event/0, kill/0]). +-export([start_link/1, init/1, start_event/0, kill/0]). -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% application and suprvisor callback functions - -start(normal, Args) -> - SupName = {local,?MODULE}, - case supervisor:start_link(SupName, ?MODULE, [Args]) of - {ok, Pid} -> - {ok, Pid, {normal, Args}}; - Error -> - Error - end; -start(_, _) -> - {error, badarg}. +start_link(Args) -> + supervisor:start_link({local,?MODULE}, ?MODULE, [Args]). -start() -> - SupName = {local,?MODULE}, - supervisor:start_link(SupName, ?MODULE, []). - -stop(_StartArgs) -> - ok. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% supervisor callback functions -init([]) -> % Supervisor - init(); -init([[]]) -> % Application +init([[]]) -> init(); init(BadArg) -> {error, {badarg, BadArg}}. - + init() -> Flags = {one_for_all, 0, 3600}, % Should be rest_for_one policy Event = event_procs(), + Ext = ext_procs(), Kernel = kernel_procs(), - {ok, {Flags, Event ++ Kernel}}. + {ok, {Flags, Event ++ Ext ++ Kernel}}. event_procs() -> KillAfter = timer:seconds(30), @@ -75,6 +58,11 @@ kernel_procs() -> KA = infinity, [{K, {K, start, []}, permanent, KA, supervisor, [K, supervisor]}]. +ext_procs() -> + K = mnesia_ext_sup, + KA = infinity, + [{K, {K, start, []}, permanent, KA, supervisor, [K, supervisor]}]. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% event handler @@ -118,4 +106,3 @@ ensure_dead(Name) -> timer:sleep(10), ensure_dead(Name) end. - diff --git a/lib/mnesia/src/mnesia_text.erl b/lib/mnesia/src/mnesia_text.erl index 34655b0318..7d24d09472 100644 --- a/lib/mnesia/src/mnesia_text.erl +++ b/lib/mnesia/src/mnesia_text.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2011. All Rights Reserved. +%% Copyright Ericsson AB 1996-2016. 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. @@ -87,18 +87,18 @@ validate_tab(_) -> error(badtab). make_tabs([{Tab, Def} | Tail]) -> try mnesia:table_info(Tab, where_to_read) of Node -> - io:format("** Table ~w already exists on ~p, just entering data~n", + io:format("** Table ~tw already exists on ~p, just entering data~n", [Tab, Node]), make_tabs(Tail) catch exit:_ -> %% non-existing table case mnesia:create_table(Tab, Def) of {aborted, Reason} -> - io:format("** Failed to create table ~w ~n" - "** Reason = ~w, Args = ~p~n", + io:format("** Failed to create table ~tw ~n" + "** Reason = ~tw, Args = ~tp~n", [Tab, Reason, Def]), [Tab | make_tabs(Tail)]; _ -> - io:format("New table ~w~n", [Tab]), + io:format("New table ~tw~n", [Tab]), make_tabs(Tail) end end; @@ -139,12 +139,12 @@ collect_data(Tabs, [{Line, Term} | Tail]) when is_tuple(Term) -> {value, _} -> [Term | collect_data(Tabs, Tail)]; _Other -> - io:format("Object:~p at line ~w unknown\n", [Term,Line]), + io:format("Object:~tp at line ~w unknown\n", [Term,Line]), error(undefined_object) end; collect_data(_Tabs, []) -> []; collect_data(_Tabs, [H|_T]) -> - io:format("Object:~p unknown\n", [H]), + io:format("Object:~tp unknown\n", [H]), error(undefined_object). error(What) -> throw({error, What}). @@ -178,7 +178,7 @@ read_term_from_stream(Stream, File, Line) -> {ok, {Line, Term}, EndLine}; {error, {NewLine,Mod,What}} -> Str = Mod:format_error(What), - io:format("Error in line:~p of:~p ~s\n", + io:format("Error in line:~p of:~tp ~ts\n", [NewLine, File, Str]), error end; diff --git a/lib/mnesia/src/mnesia_tm.erl b/lib/mnesia/src/mnesia_tm.erl index e7ee938312..ebf580d09e 100644 --- a/lib/mnesia/src/mnesia_tm.erl +++ b/lib/mnesia/src/mnesia_tm.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2014. All Rights Reserved. +%% Copyright Ericsson AB 1996-2016. 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. @@ -42,7 +42,8 @@ put_activity_id/2, block_tab/1, unblock_tab/1, - fixtable/3 + fixtable/3, + new_cr_format/1 ]). %% sys callback functions @@ -79,6 +80,7 @@ start() -> init(Parent) -> register(?MODULE, self()), process_flag(trap_exit, true), + process_flag(message_queue_data, off_heap), %% Initialize the schema IgnoreFallback = mnesia_monitor:get_env(ignore_fallback_at_startup), @@ -206,10 +208,10 @@ doit_loop(#state{coordinators=Coordinators,participants=Participants,supervisor= {_From, {async_dirty, Tid, Commit, Tab}} -> case lists:member(Tab, State#state.blocked_tabs) of false -> - do_async_dirty(Tid, Commit, Tab), + do_async_dirty(Tid, new_cr_format(Commit), Tab), doit_loop(State); true -> - Item = {async_dirty, Tid, Commit, Tab}, + Item = {async_dirty, Tid, new_cr_format(Commit), Tab}, State2 = State#state{dirty_queue = [Item | State#state.dirty_queue]}, doit_loop(State2) end; @@ -217,10 +219,10 @@ doit_loop(#state{coordinators=Coordinators,participants=Participants,supervisor= {From, {sync_dirty, Tid, Commit, Tab}} -> case lists:member(Tab, State#state.blocked_tabs) of false -> - do_sync_dirty(From, Tid, Commit, Tab), + do_sync_dirty(From, Tid, new_cr_format(Commit), Tab), doit_loop(State); true -> - Item = {sync_dirty, From, Tid, Commit, Tab}, + Item = {sync_dirty, From, Tid, new_cr_format(Commit), Tab}, State2 = State#state{dirty_queue = [Item | State#state.dirty_queue]}, doit_loop(State2) end; @@ -241,10 +243,11 @@ doit_loop(#state{coordinators=Coordinators,participants=Participants,supervisor= reply(From, {error, {system_limit, Msg, Reason}}, State) end; - {From, {ask_commit, Protocol, Tid, Commit, DiscNs, RamNs}} -> + {From, {ask_commit, Protocol, Tid, Commit0, DiscNs, RamNs}} -> ?eval_debug_fun({?MODULE, doit_ask_commit}, [{tid, Tid}, {prot, Protocol}]), mnesia_checkpoint:tm_enter_pending(Tid, DiscNs, RamNs), + Commit = new_cr_format(Commit0), Pid = case Protocol of asym_trans when node(Tid#tid.pid) /= node() -> @@ -311,7 +314,7 @@ doit_loop(#state{coordinators=Coordinators,participants=Participants,supervisor= ?eval_debug_fun({?MODULE, do_abort, pre}, [{tid, Tid}]), case gb_trees:lookup(Tid, Participants) of none -> - verbose("Tried to abort a non participant transaction ~p: ~p~n", + verbose("Tried to abort a non participant transaction ~p: ~tp~n", [Tid, Reason]), mnesia_locker:release_tid(Tid), doit_loop(State); @@ -414,7 +417,7 @@ doit_loop(#state{coordinators=Coordinators,participants=Participants,supervisor= {From, {unblock_me, Tab}} -> case lists:member(Tab, State#state.blocked_tabs) of false -> - verbose("Wrong dirty Op blocked on ~p ~p ~p", + verbose("Wrong dirty Op blocked on ~p ~tp ~p", [node(), Tab, From]), reply(From, unblocked), doit_loop(State); @@ -463,11 +466,11 @@ doit_loop(#state{coordinators=Coordinators,participants=Participants,supervisor= end; {system, From, Msg} -> - dbg_out("~p got {system, ~p, ~p}~n", [?MODULE, From, Msg]), + dbg_out("~p got {system, ~p, ~tp}~n", [?MODULE, From, Msg]), sys:handle_system_msg(Msg, From, Sup, ?MODULE, [], State); Msg -> - verbose("** ERROR ** ~p got unexpected message: ~p~n", [?MODULE, Msg]), + verbose("** ERROR ** ~p got unexpected message: ~tp~n", [?MODULE, Msg]), doit_loop(State) end. @@ -553,7 +556,7 @@ handle_exit(Pid, Reason, State) -> %% We got exit from a local fool doit_loop(State); {P = #participant{}, _RestP} -> - fatal("Participant ~p in transaction ~p died ~p~n", + fatal("Participant ~p in transaction ~p died ~tp~n", [P#participant.pid, P#participant.tid, Reason]), NewPs = gb_trees:delete(P#participant.tid,State#state.participants), doit_loop(State#state{participants = NewPs}) @@ -595,7 +598,7 @@ recover_coordinator(Tid, Etabs) -> ok %% to the new nested trans store. end catch _:Reason -> - dbg_out("Recovery of coordinator ~p failed:~n", + dbg_out("Recovery of coordinator ~p failed: ~tp~n", [Tid, {Reason, erlang:get_stacktrace()}]), Protocol = asym_trans, tell_outcome(Tid, Protocol, node(), CheckNodes, TellNodes) @@ -938,7 +941,7 @@ decr(_X) -> 0. return_abort(Fun, Args, Reason) -> {_Mod, Tid, Ts} = get(mnesia_activity_state), - dbg_out("Transaction ~p calling ~p with ~p failed: ~n ~p~n", + dbg_out("Transaction ~p calling ~tp with ~tp failed: ~n ~tp~n", [Tid, Fun, Args, Reason]), OldStore = Ts#tidstore.store, Nodes = get_elements(nodes, OldStore), @@ -948,7 +951,7 @@ return_abort(Fun, Args, Reason) -> if Level == 1 -> mnesia_locker:async_release_tid(Nodes, Tid), - ?MODULE ! {delete_transaction, Tid}, + ?SAFE(?MODULE ! {delete_transaction, Tid}), erase(mnesia_activity_state), flush_downs(), ?SAFE(unlink(whereis(?MODULE))), @@ -1137,14 +1140,14 @@ arrange(Tid, Store, Type) -> reverse([]) -> []; reverse([H=#commit{ram_copies=Ram, disc_copies=DC, - disc_only_copies=DOC,snmp = Snmp} + disc_only_copies=DOC, ext=Ext} |R]) -> [ H#commit{ - ram_copies = lists:reverse(Ram), - disc_copies = lists:reverse(DC), - disc_only_copies = lists:reverse(DOC), - snmp = lists:reverse(Snmp) + ram_copies = lists:reverse(Ram), + disc_copies = lists:reverse(DC), + disc_only_copies = lists:reverse(DOC), + ext = [{Type, lists:reverse(E)} || {Type,E} <- Ext] } | reverse(R)]. @@ -1311,8 +1314,13 @@ pick_node({dirty,_}, Node, [], Done) -> pick_node(_Tid, Node, [], _Done) -> mnesia:abort({bad_commit, {missing_lock, Node}}). -prepare_node(Node, Storage, [Item | Items], Rec, Kind) when Kind == snmp -> - Rec2 = Rec#commit{snmp = [Item | Rec#commit.snmp]}, +prepare_node(Node, Storage, [Item | Items], #commit{ext=Ext0}=Rec, Kind) when Kind == snmp -> + Rec2 = case lists:keytake(snmp, 1, Ext0) of + false -> + Rec#commit{ext = [{snmp,[Item]}|Ext0]}; + {_, {snmp,Snmp},Ext} -> + Rec#commit{ext = [{snmp,[Item|Snmp]}|Ext]} + end, prepare_node(Node, Storage, Items, Rec2, Kind); prepare_node(Node, Storage, [Item | Items], Rec, Kind) when Kind /= schema -> Rec2 = @@ -1323,7 +1331,15 @@ prepare_node(Node, Storage, [Item | Items], Rec, Kind) when Kind /= schema -> Rec#commit{disc_copies = [Item | Rec#commit.disc_copies]}; disc_only_copies -> Rec#commit{disc_only_copies = - [Item | Rec#commit.disc_only_copies]} + [Item | Rec#commit.disc_only_copies]}; + {ext, Alias, Mod} -> + Ext0 = Rec#commit.ext, + case lists:keytake(ext_copies, 1, Ext0) of + false -> + Rec#commit{ext = [{ext_copies, [{{ext,Alias,Mod}, Item}]}|Ext0]}; + {_,{_,EC},Ext} -> + Rec#commit{ext = [{ext_copies, [{{ext,Alias,Mod}, Item}|EC]}|Ext]} + end end, prepare_node(Node, Storage, Items, Rec2, Kind); prepare_node(_Node, _Storage, Items, Rec, Kind) @@ -1692,16 +1708,13 @@ commit_participant(Coord, Tid, Bin, C0, DiscNs, _RamNs) -> ?eval_debug_fun({?MODULE, commit_participant, undo_prepare}, [{tid, Tid}]); - {'EXIT', _, _} -> + {'EXIT', _MnesiaTM, Reason} -> + reply(Coord, {do_abort, Tid, self(), {bad_commit,Reason}}), mnesia_recover:log_decision(D#decision{outcome = aborted}), - ?eval_debug_fun({?MODULE, commit_participant, exit_log_abort}, - [{tid, Tid}]), - mnesia_schema:undo_prepare_commit(Tid, C0), - ?eval_debug_fun({?MODULE, commit_participant, exit_undo_prepare}, - [{tid, Tid}]); + mnesia_schema:undo_prepare_commit(Tid, C0); Msg -> - verbose("** ERROR ** commit_participant ~p, got unexpected msg: ~p~n", + verbose("** ERROR ** commit_participant ~p, got unexpected msg: ~tp~n", [Tid, Msg]) end; {Tid, {do_abort, Reason}} -> @@ -1717,7 +1730,7 @@ commit_participant(Coord, Tid, Bin, C0, DiscNs, _RamNs) -> Msg -> reply(Coord, {do_abort, Tid, self(), {bad_commit,internal}}), - verbose("** ERROR ** commit_participant ~p, got unexpected msg: ~p~n", + verbose("** ERROR ** commit_participant ~p, got unexpected msg: ~tp~n", [Tid, Msg]) end catch _:Reason -> @@ -1753,16 +1766,30 @@ do_commit(Tid, Bin) when is_binary(Bin) -> do_commit(Tid, binary_to_term(Bin)); do_commit(Tid, C) -> do_commit(Tid, C, optional). + do_commit(Tid, Bin, DumperMode) when is_binary(Bin) -> do_commit(Tid, binary_to_term(Bin), DumperMode); do_commit(Tid, C, DumperMode) -> mnesia_dumper:update(Tid, C#commit.schema_ops, DumperMode), - R = do_snmp(Tid, C#commit.snmp), + R = do_snmp(Tid, proplists:get_value(snmp, C#commit.ext, [])), R2 = do_update(Tid, ram_copies, C#commit.ram_copies, R), R3 = do_update(Tid, disc_copies, C#commit.disc_copies, R2), R4 = do_update(Tid, disc_only_copies, C#commit.disc_only_copies, R3), + R5 = do_update_ext(Tid, C#commit.ext, R4), mnesia_subscr:report_activity(Tid), - R4. + R5. + +%% This could/should be optimized +do_update_ext(_Tid, [], OldRes) -> OldRes; +do_update_ext(Tid, Ext, OldRes) -> + case lists:keyfind(ext_copies, 1, Ext) of + false -> OldRes; + {_, Ops} -> + Do = fun({{ext, _,_} = Storage, Op}, R) -> + do_update(Tid, Storage, [Op], R) + end, + lists:foldl(Do, OldRes, Ops) + end. %% Update the items do_update(Tid, Storage, [Op | Ops], OldRes) -> @@ -1777,7 +1804,7 @@ do_update(Tid, Storage, [Op | Ops], OldRes) -> %% Determine actual storage type and try again. %% BUGBUG: Updates may be lost if table is transformed. ST = erlang:get_stacktrace(), - verbose("do_update in ~w failed: ~p -> {'EXIT', ~p}~n", + verbose("do_update in ~w failed: ~tp -> {'EXIT', ~tp}~n", [Tid, Op, {Reason, ST}]), do_update(Tid, Storage, Ops, OldRes) end; @@ -1785,12 +1812,12 @@ do_update(_Tid, _Storage, [], Res) -> Res. do_update_op(Tid, Storage, {{Tab, K}, Obj, write}) -> - commit_write(?catch_val({Tab, commit_work}), Tid, + commit_write(?catch_val({Tab, commit_work}), Tid, Storage, Tab, K, Obj, undefined), mnesia_lib:db_put(Storage, Tab, Obj); do_update_op(Tid, Storage, {{Tab, K}, Val, delete}) -> - commit_delete(?catch_val({Tab, commit_work}), Tid, Tab, K, Val, undefined), + commit_delete(?catch_val({Tab, commit_work}), Tid, Storage, Tab, K, Val, undefined), mnesia_lib:db_erase(Storage, Tab, K); do_update_op(Tid, Storage, {{Tab, K}, {RecName, Incr}, update_counter}) -> @@ -1808,122 +1835,123 @@ do_update_op(Tid, Storage, {{Tab, K}, {RecName, Incr}, update_counter}) -> mnesia_lib:db_put(Storage, Tab, Zero), {Zero, []} end, - commit_update(?catch_val({Tab, commit_work}), Tid, Tab, + commit_update(?catch_val({Tab, commit_work}), Tid, Storage, Tab, K, NewObj, OldObjs), element(3, NewObj); do_update_op(Tid, Storage, {{Tab, Key}, Obj, delete_object}) -> commit_del_object(?catch_val({Tab, commit_work}), - Tid, Tab, Key, Obj, undefined), + Tid, Storage, Tab, Key, Obj), mnesia_lib:db_match_erase(Storage, Tab, Obj); do_update_op(Tid, Storage, {{Tab, Key}, Obj, clear_table}) -> - commit_clear(?catch_val({Tab, commit_work}), Tid, Tab, Key, Obj), + commit_clear(?catch_val({Tab, commit_work}), Tid, Storage, Tab, Key, Obj), mnesia_lib:db_match_erase(Storage, Tab, Obj). -commit_write([], _, _, _, _, _) -> ok; -commit_write([{checkpoints, CpList}|R], Tid, Tab, K, Obj, Old) -> +commit_write([], _, _, _, _, _, _) -> ok; +commit_write([{checkpoints, CpList}|R], Tid, Storage, Tab, K, Obj, Old) -> mnesia_checkpoint:tm_retain(Tid, Tab, K, write, CpList), - commit_write(R, Tid, Tab, K, Obj, Old); -commit_write([H|R], Tid, Tab, K, Obj, Old) + commit_write(R, Tid, Storage, Tab, K, Obj, Old); +commit_write([H|R], Tid, Storage, Tab, K, Obj, Old) when element(1, H) == subscribers -> mnesia_subscr:report_table_event(H, Tab, Tid, Obj, write, Old), - commit_write(R, Tid, Tab, K, Obj, Old); -commit_write([H|R], Tid, Tab, K, Obj, Old) + commit_write(R, Tid, Storage, Tab, K, Obj, Old); +commit_write([H|R], Tid, Storage, Tab, K, Obj, Old) when element(1, H) == index -> - mnesia_index:add_index(H, Tab, K, Obj, Old), - commit_write(R, Tid, Tab, K, Obj, Old). + mnesia_index:add_index(H, Storage, Tab, K, Obj, Old), + commit_write(R, Tid, Storage, Tab, K, Obj, Old). -commit_update([], _, _, _, _, _) -> ok; -commit_update([{checkpoints, CpList}|R], Tid, Tab, K, Obj, _) -> +commit_update([], _, _, _, _, _, _) -> ok; +commit_update([{checkpoints, CpList}|R], Tid, Storage, Tab, K, Obj, _) -> Old = mnesia_checkpoint:tm_retain(Tid, Tab, K, write, CpList), - commit_update(R, Tid, Tab, K, Obj, Old); -commit_update([H|R], Tid, Tab, K, Obj, Old) + commit_update(R, Tid, Storage, Tab, K, Obj, Old); +commit_update([H|R], Tid, Storage, Tab, K, Obj, Old) when element(1, H) == subscribers -> mnesia_subscr:report_table_event(H, Tab, Tid, Obj, write, Old), - commit_update(R, Tid, Tab, K, Obj, Old); -commit_update([H|R], Tid, Tab, K, Obj, Old) + commit_update(R, Tid, Storage, Tab, K, Obj, Old); +commit_update([H|R], Tid,Storage, Tab, K, Obj, Old) when element(1, H) == index -> - mnesia_index:add_index(H, Tab, K, Obj, Old), - commit_update(R, Tid, Tab, K, Obj, Old). + mnesia_index:add_index(H, Storage, Tab, K, Obj, Old), + commit_update(R, Tid, Storage, Tab, K, Obj, Old). -commit_delete([], _, _, _, _, _) -> ok; -commit_delete([{checkpoints, CpList}|R], Tid, Tab, K, Obj, _) -> +commit_delete([], _, _, _, _, _, _) -> ok; +commit_delete([{checkpoints, CpList}|R], Tid, Storage, Tab, K, Obj, _) -> Old = mnesia_checkpoint:tm_retain(Tid, Tab, K, delete, CpList), - commit_delete(R, Tid, Tab, K, Obj, Old); -commit_delete([H|R], Tid, Tab, K, Obj, Old) + commit_delete(R, Tid, Storage, Tab, K, Obj, Old); +commit_delete([H|R], Tid, Storage, Tab, K, Obj, Old) when element(1, H) == subscribers -> mnesia_subscr:report_table_event(H, Tab, Tid, Obj, delete, Old), - commit_delete(R, Tid, Tab, K, Obj, Old); -commit_delete([H|R], Tid, Tab, K, Obj, Old) + commit_delete(R, Tid, Storage, Tab, K, Obj, Old); +commit_delete([H|R], Tid, Storage, Tab, K, Obj, Old) when element(1, H) == index -> - mnesia_index:delete_index(H, Tab, K), - commit_delete(R, Tid, Tab, K, Obj, Old). + mnesia_index:delete_index(H, Storage, Tab, K), + commit_delete(R, Tid, Storage, Tab, K, Obj, Old). commit_del_object([], _, _, _, _, _) -> ok; -commit_del_object([{checkpoints, CpList}|R], Tid, Tab, K, Obj, _) -> - Old = mnesia_checkpoint:tm_retain(Tid, Tab, K, delete_object, CpList), - commit_del_object(R, Tid, Tab, K, Obj, Old); -commit_del_object([H|R], Tid, Tab, K, Obj, Old) - when element(1, H) == subscribers -> - mnesia_subscr:report_table_event(H, Tab, Tid, Obj, delete_object, Old), - commit_del_object(R, Tid, Tab, K, Obj, Old); -commit_del_object([H|R], Tid, Tab, K, Obj, Old) - when element(1, H) == index -> - mnesia_index:del_object_index(H, Tab, K, Obj, Old), - commit_del_object(R, Tid, Tab, K, Obj, Old). - -commit_clear([], _, _, _, _) -> ok; -commit_clear([{checkpoints, CpList}|R], Tid, Tab, K, Obj) -> +commit_del_object([{checkpoints, CpList}|R], Tid, Storage, Tab, K, Obj) -> + mnesia_checkpoint:tm_retain(Tid, Tab, K, delete_object, CpList), + commit_del_object(R, Tid, Storage, Tab, K, Obj); +commit_del_object([H|R], Tid, Storage, Tab, K, Obj) when element(1, H) == subscribers -> + mnesia_subscr:report_table_event(H, Tab, Tid, Obj, delete_object), + commit_del_object(R, Tid, Storage, Tab, K, Obj); +commit_del_object([H|R], Tid, Storage, Tab, K, Obj) when element(1, H) == index -> + mnesia_index:del_object_index(H, Storage, Tab, K, Obj), + commit_del_object(R, Tid, Storage, Tab, K, Obj). + +commit_clear([], _, _, _, _, _) -> ok; +commit_clear([{checkpoints, CpList}|R], Tid, Storage, Tab, K, Obj) -> mnesia_checkpoint:tm_retain(Tid, Tab, K, clear_table, CpList), - commit_clear(R, Tid, Tab, K, Obj); -commit_clear([H|R], Tid, Tab, K, Obj) + commit_clear(R, Tid, Storage, Tab, K, Obj); +commit_clear([H|R], Tid, Storage, Tab, K, Obj) when element(1, H) == subscribers -> mnesia_subscr:report_table_event(H, Tab, Tid, Obj, clear_table, undefined), - commit_clear(R, Tid, Tab, K, Obj); -commit_clear([H|R], Tid, Tab, K, Obj) + commit_clear(R, Tid, Storage, Tab, K, Obj); +commit_clear([H|R], Tid, Storage, Tab, K, Obj) when element(1, H) == index -> mnesia_index:clear_index(H, Tab, K, Obj), - commit_clear(R, Tid, Tab, K, Obj). + commit_clear(R, Tid, Storage, Tab, K, Obj). do_snmp(_, []) -> ok; -do_snmp(Tid, [Head | Tail]) -> +do_snmp(Tid, [Head|Tail]) -> try mnesia_snmp_hook:update(Head) catch _:Reason -> %% This should only happen when we recently have %% deleted our local replica or recently deattached %% the snmp table ST = erlang:get_stacktrace(), - verbose("do_snmp in ~w failed: ~p -> {'EXIT', ~p}~n", + verbose("do_snmp in ~w failed: ~tp -> {'EXIT', ~tp}~n", [Tid, Head, {Reason, ST}]) end, do_snmp(Tid, Tail). -commit_nodes([C | Tail], AccD, AccR) - when C#commit.disc_copies == [], - C#commit.disc_only_copies == [], - C#commit.schema_ops == [] -> - commit_nodes(Tail, AccD, [C#commit.node | AccR]); commit_nodes([C | Tail], AccD, AccR) -> - commit_nodes(Tail, [C#commit.node | AccD], AccR); + case C of + #commit{disc_copies=[], disc_only_copies=[], schema_ops=[], ext=Ext} -> + case lists:keyfind(ext_copies, 1, Ext) of + false -> commit_nodes(Tail, AccD, [C#commit.node | AccR]); + _ -> commit_nodes(Tail, [C#commit.node | AccD], AccR) + end; + _ -> + commit_nodes(Tail, [C#commit.node | AccD], AccR) + end; commit_nodes([], AccD, AccR) -> {AccD, AccR}. commit_decision(D, [C | Tail], AccD, AccR) -> N = C#commit.node, {D2, Tail2} = - case C#commit.schema_ops of - [] when C#commit.disc_copies == [], - C#commit.disc_only_copies == [] -> - commit_decision(D, Tail, AccD, [N | AccR]); - [] -> + case C of + #commit{disc_copies=[], disc_only_copies=[], schema_ops=[], ext=Ext} -> + case lists:keyfind(ext_copies, 1, Ext) of + false -> commit_decision(D, Tail, AccD, [N | AccR]); + _ -> commit_decision(D, Tail, [N | AccD], AccR) + end; + #commit{schema_ops=[]} -> commit_decision(D, Tail, [N | AccD], AccR); - Ops -> + #commit{schema_ops=Ops} -> case ram_only_ops(N, Ops) of - true -> - commit_decision(D, Tail, AccD, [N | AccR]); - false -> - commit_decision(D, Tail, [N | AccD], AccR) + true -> commit_decision(D, Tail, AccD, [N | AccR]); + false -> commit_decision(D, Tail, [N | AccD], AccR) end end, {D2, [C#commit{decision = D2} | Tail2]}; @@ -1951,7 +1979,7 @@ sync_send_dirty(Tid, [Head | Tail], Tab, WaitFor) -> Res = do_dirty(Tid, Head), {WF, Res}; true -> - {?MODULE, Node} ! {self(), {sync_dirty, Tid, Head, Tab}}, + {?MODULE, Node} ! {self(), {sync_dirty, Tid, ext_format(Head), Tab}}, sync_send_dirty(Tid, Tail, Tab, [Node | WaitFor]) end; sync_send_dirty(_Tid, [], _Tab, WaitFor) -> @@ -1970,11 +1998,11 @@ async_send_dirty(Tid, [Head | Tail], Tab, ReadNode, WaitFor, Res) -> NewRes = do_dirty(Tid, Head), async_send_dirty(Tid, Tail, Tab, ReadNode, WaitFor, NewRes); ReadNode == Node -> - {?MODULE, Node} ! {self(), {sync_dirty, Tid, Head, Tab}}, + {?MODULE, Node} ! {self(), {sync_dirty, Tid, ext_format(Head), Tab}}, NewRes = {'EXIT', {aborted, {node_not_running, Node}}}, async_send_dirty(Tid, Tail, Tab, ReadNode, [Node | WaitFor], NewRes); true -> - {?MODULE, Node} ! {self(), {async_dirty, Tid, Head, Tab}}, + {?MODULE, Node} ! {self(), {async_dirty, Tid, ext_format(Head), Tab}}, async_send_dirty(Tid, Tail, Tab, ReadNode, WaitFor, Res) end; async_send_dirty(_Tid, [], _Tab, _ReadNode, WaitFor, Res) -> @@ -2031,23 +2059,29 @@ ask_commit(Protocol, Tid, [Head | Tail], DiscNs, RamNs, WaitFor, Local) -> Node == node() -> ask_commit(Protocol, Tid, Tail, DiscNs, RamNs, WaitFor, Head); true -> - Bin = opt_term_to_binary(Protocol, Head, DiscNs++RamNs), - Msg = {ask_commit, Protocol, Tid, Bin, DiscNs, RamNs}, + CR = ext_format(Head), + Msg = {ask_commit, Protocol, Tid, CR, DiscNs, RamNs}, {?MODULE, Node} ! {self(), Msg}, ask_commit(Protocol, Tid, Tail, DiscNs, RamNs, [Node | WaitFor], Local) end; ask_commit(_Protocol, _Tid, [], _DiscNs, _RamNs, WaitFor, Local) -> {WaitFor, Local}. -%% This used to test protocol conversion between mnesia-nodes -%% but it is really dependent on the emulator version on the -%% two nodes (if funs are sent which they are in transform table op). -%% to be safe we let erts do the translation (many times maybe and thus -%% slower but it works. -% opt_term_to_binary(asym_trans, Head, Nodes) -> -% opt_term_to_binary(Nodes, Head); -opt_term_to_binary(_Protocol, Head, _Nodes) -> - Head. +ext_format(#commit{ext=[]}=CR) -> CR; +ext_format(#commit{node=Node, ext=Ext}=CR) -> + case mnesia_monitor:needs_protocol_conversion(Node) of + true -> + case lists:keyfind(snmp, 1, Ext) of + false -> CR#commit{ext=[]}; + {snmp, List} -> CR#commit{ext=List} + end; + false -> CR + end. + +new_cr_format(#commit{ext=[]}=Cr) -> Cr; +new_cr_format(#commit{ext=[{_,_}|_]}=Cr) -> Cr; +new_cr_format(#commit{ext=Snmp}=Cr) -> + Cr#commit{ext=[{snmp,Snmp}]}. rec_all([Node | Tail], Tid, Res, Pids) -> receive @@ -2117,7 +2151,7 @@ pr_participant(Stream, P) -> true -> Commit0 end, pr_tid(Stream, P#participant.tid), - io:format(Stream, "with participant objects ~p~n", [Commit]). + io:format(Stream, "with participant objects ~tp~n", [Commit]). pr_tid(Stream, Tid) -> @@ -2159,7 +2193,7 @@ search_pr_participant(S, [ P | Tail]) -> true -> Commit0 end, - io:format("~p~n", [Commit]), + io:format("~tp~n", [Commit]), search_pr_participant(S,Tail); %% !!!!! true -> search_pr_participant(S, Tail) @@ -2180,12 +2214,12 @@ display_pid_info(Pid) -> Reds = fetch(reductions, Info), LM = length(fetch(messages, Info)), pformat(io_lib:format("~p", [Pid]), - io_lib:format("~p", [Call]), - io_lib:format("~p", [Curr]), Reds, LM) + io_lib:format("~tp", [Call]), + io_lib:format("~tp", [Curr]), Reds, LM) end. pformat(A1, A2, A3, A4, A5) -> - io:format( "~-12s ~-21s ~-21s ~9w ~4w~n", [A1,A2,A3,A4,A5]). + io:format( "~-12s ~-21ts ~-21ts ~9w ~4w~n", [A1,A2,A3,A4,A5]). fetch(Key, Info) -> case lists:keysearch(Key, 1, Info) of @@ -2210,8 +2244,6 @@ reconfigure_coordinators(N, [{Tid, [Store | _]} | Coordinators]) -> true -> send_mnesia_down(Tid, Store, N) end; - aborted -> - ignore; % avoid spurious mnesia_down messages _ -> %% Tell the coordinator about the mnesia_down send_mnesia_down(Tid, Store, N) |