From 08bfbfe6c38a61570a2951d4cb7bff99e96f0e30 Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Thu, 19 Nov 2015 13:31:23 +0100 Subject: mnesia_ext: Dumper and schema changes --- lib/mnesia/src/mnesia_dumper.erl | 416 ++++++++++++++++++++++++++---------- lib/mnesia/src/mnesia_schema.erl | 443 +++++++++++++++++++++++++++++++++------ 2 files changed, 689 insertions(+), 170 deletions(-) (limited to 'lib') diff --git a/lib/mnesia/src/mnesia_dumper.erl b/lib/mnesia/src/mnesia_dumper.erl index 0e954d869d..845ad0ac39 100644 --- a/lib/mnesia/src/mnesia_dumper.erl +++ b/lib/mnesia/src/mnesia_dumper.erl @@ -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, @@ -271,15 +273,25 @@ 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{external_copies = C}) -> + lists:foldl(fun({Ext, Op}, D) -> + orddict:append(Ext, Op, D) + end, orddict:new(), C). update(_Tid, [], _DumperMode) -> dumped; @@ -330,14 +342,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 +362,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 +447,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 +478,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 +510,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 +543,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 +557,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 = ToMod:create_table(ToAlias, Tab, []), + 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 = ToMod:create_table(ToAlias, Tab, []), + 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 +693,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 +714,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 +729,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 +738,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 +751,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 +772,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 +798,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 +921,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 +979,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 +1003,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 +1044,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 +1116,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 +1130,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) -> @@ -1076,12 +1217,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 +1235,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 +1295,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 +1304,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 +1362,57 @@ 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); + {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 %% diff --git a/lib/mnesia/src/mnesia_schema.erl b/lib/mnesia/src/mnesia_schema.erl index d482f1be43..19b578f551 100644 --- a/lib/mnesia/src/mnesia_schema.erl +++ b/lib/mnesia/src/mnesia_schema.erl @@ -69,6 +69,7 @@ insert_cstruct/3, is_remote_member/1, list2cs/1, + list2cs/2, lock_schema/0, merge_schema/0, merge_schema/1, @@ -116,7 +117,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"). @@ -144,6 +146,7 @@ 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()), @@ -203,6 +206,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), @@ -218,7 +222,6 @@ 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), %% create actual index tabs later @@ -227,6 +230,7 @@ do_set_schema(Tab, Cs) -> 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 @@ -236,7 +240,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, '_')), @@ -603,8 +624,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), @@ -684,7 +706,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) -> @@ -729,6 +751,14 @@ cs2list({8,Minor}, Cs) when Minor =:= 2; Minor =:= 1 -> cookie,version], rec2list(Tags, Orig, 2, Cs). +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)]; @@ -751,14 +781,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), @@ -804,24 +839,47 @@ 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 lists:keymember(mnesia, 1, application:which_applications()) of + true -> + Keys = check_keys(Name, List), + check_duplicates(Name, Keys); + false -> + %% 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 + 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; + [_|_] -> + Cs0#cstruct{index = Ix2} + end; +list2cs(Other, _ExtTypes) -> + mnesia:abort({badarg, Other}). pick(Tab, Key, List, Default) -> case lists:keysearch(Key, 1, List) of @@ -835,6 +893,31 @@ 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). @@ -854,6 +937,32 @@ get_schema_user_property(Key) -> 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; @@ -875,8 +984,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; @@ -902,7 +1021,55 @@ has_duplicates([]) -> %% This is the only place where we check the validity of data verify_cstruct(#cstruct{} = Cs) -> assert_correct_cstruct(Cs), - Cs. + Cs1 = verify_external_copies(Cs), + assert_correct_cstruct(Cs1), + Cs1. + +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), @@ -1011,12 +1178,22 @@ verify_nodes(Cs) -> 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}}); @@ -1028,12 +1205,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) -> @@ -1216,25 +1396,29 @@ backend_types() -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% 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)). @@ -1495,8 +1679,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)]. @@ -1559,9 +1769,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 -> @@ -1570,7 +1782,7 @@ make_change_table_copy_type(Tab, Node, ToS) -> Cs2 = new_cs(Cs, Node, FromS, del), Cs3 = verify_cstruct(new_cs(Cs2, Node, ToS, add)), - [{op, change_table_copy_type, Node, FromS, ToS, vsn_cs2list(Cs3)}]. + [{op, change_table_copy_type, Node, FromS, ToSExp, vsn_cs2list(Cs3)}]. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% change index functions .... @@ -1614,9 +1826,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)}]. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1684,20 +1895,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 = 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}, @@ -1797,6 +2009,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, @@ -2105,6 +2318,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), @@ -2173,6 +2391,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()}); @@ -2212,6 +2436,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 -> @@ -2237,6 +2470,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), @@ -2248,6 +2482,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, @@ -2311,6 +2546,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: '~p' in '~p'", + [Reason, erlang:get_stacktrace()]), exit({"Bad transform function", Tab, Fun, node(), Reason}) end end; @@ -2327,6 +2564,22 @@ prepare_op(_Tid, {op, merge_schema, TabDef}, _WaitFor) -> 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], @@ -2368,6 +2621,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; @@ -2502,7 +2763,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}) -> @@ -2602,6 +2866,8 @@ ram_delete_table(Tab, Storage) -> case Storage of unknown -> ignore; + {ext, _, _} -> + ignore; disc_only_copies -> ignore; _Else -> @@ -2663,13 +2929,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) -> @@ -2677,11 +2957,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() -> [".DAT", ".LOG", ".BUP", ".DCL", ".DCD"]. +real_suffixes(Ext) -> [".DAT", ".LOG", ".BUP", ".DCL", ".DCD"] ++ ext_real_suffixes(Ext). -tmp_suffixes() -> [".TMP", ".BUPTMP", ".RET", ".DMP"]. +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 (~p)~n", [E]), + [] + end. + +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 (~p)~n", [E]), + [] + end. info() -> Tabs = lists:sort(val({schema, tables})), @@ -2799,12 +3101,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}} -> @@ -2817,13 +3119,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) -> @@ -2837,8 +3139,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) -> @@ -2917,7 +3225,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)]; @@ -3337,4 +3651,3 @@ unannounce_im_running([N | Ns]) -> unannounce_im_running(Ns); unannounce_im_running([]) -> ok. - -- cgit v1.2.3