2010-07-22 18:43:38 +02:00
|
|
|
-module(gen_storage_migration).
|
|
|
|
|
|
|
|
-export([migrate_mnesia/3, migrate_odbc/3]).
|
|
|
|
|
|
|
|
-include("ejabberd.hrl").
|
|
|
|
|
2010-08-13 17:01:08 +02:00
|
|
|
%% @spec (Host::storage_host(), Table::atom(), Migrations) -> any()
|
2010-07-23 13:36:35 +02:00
|
|
|
%% Migrations = [{OldTable, OldAttributes, MigrateFun}]
|
2010-07-22 18:43:38 +02:00
|
|
|
migrate_mnesia(Host, Table, Migrations) ->
|
|
|
|
SameTableName = [Migration
|
|
|
|
|| {OldTable, _, _} = Migration <- Migrations,
|
|
|
|
OldTable =:= Table],
|
|
|
|
lists:foreach(fun(Migration) ->
|
|
|
|
case (catch migrate_mnesia1(Host, Table, Migration)) of
|
|
|
|
ok -> ok;
|
|
|
|
ignored -> ok;
|
|
|
|
R ->
|
|
|
|
?ERROR_MSG("Error performing migration ~p:~n~p",
|
|
|
|
[Migration, R])
|
|
|
|
end
|
|
|
|
end, SameTableName),
|
|
|
|
DifferentTableName = [Migration
|
|
|
|
|| {OldTable, _, _} = Migration <- Migrations,
|
|
|
|
OldTable =/= Table],
|
|
|
|
lists:foreach(fun(Migration) ->
|
|
|
|
case (catch migrate_mnesia1(Host, Table, Migration)) of
|
|
|
|
ok -> ok;
|
|
|
|
ignored -> ok;
|
|
|
|
R ->
|
|
|
|
?ERROR_MSG("Error performing migration ~p:~n~p",
|
|
|
|
[Migration, R])
|
|
|
|
end
|
|
|
|
end, DifferentTableName).
|
|
|
|
|
2011-07-11 19:46:26 +02:00
|
|
|
migrate_mnesia1(Host, Table, {OldTable, OldAttributes, MigrateFun}) when is_list(Host) ->
|
|
|
|
migrate_mnesia1(list_to_binary(Host), Table, {OldTable, OldAttributes, MigrateFun});
|
|
|
|
migrate_mnesia1(HostB, Table, {OldTable, OldAttributes, MigrateFun}) ->
|
2010-07-22 18:43:38 +02:00
|
|
|
case (catch mnesia:table_info(OldTable, attributes)) of
|
|
|
|
OldAttributes ->
|
|
|
|
if
|
|
|
|
Table =:= OldTable ->
|
|
|
|
%% TODO: move into transaction
|
|
|
|
TmpTable = list_to_atom(atom_to_list(Table) ++ "_tmp"),
|
2010-08-13 17:01:08 +02:00
|
|
|
NewRecordName = gen_storage:table_info(HostB, Table, record_name),
|
|
|
|
NewAttributes = gen_storage:table_info(HostB, Table, attributes),
|
2010-07-22 18:43:38 +02:00
|
|
|
?INFO_MSG("Migrating mnesia table ~p via ~p~nfrom ~p~nto ~p",
|
|
|
|
[Table, TmpTable, OldAttributes, NewAttributes]),
|
|
|
|
|
|
|
|
{atomic, ok} = mnesia:create_table(
|
|
|
|
TmpTable,
|
|
|
|
[{disc_only_copies, [node()]},
|
|
|
|
{type, bag},
|
|
|
|
{local_content, true},
|
|
|
|
{record_name, NewRecordName},
|
|
|
|
{attributes, NewAttributes}]),
|
|
|
|
F1 = fun() ->
|
|
|
|
mnesia:write_lock_table(TmpTable),
|
|
|
|
mnesia:foldl(
|
|
|
|
fun(OldRecord, _) ->
|
|
|
|
NewRecord = MigrateFun(OldRecord),
|
|
|
|
?DEBUG("~p-~p: ~p -> ~p~n",[OldTable, Table, OldRecord, NewRecord]),
|
|
|
|
if
|
|
|
|
is_tuple(NewRecord) ->
|
|
|
|
mnesia:write(TmpTable, NewRecord, write);
|
|
|
|
true ->
|
|
|
|
ignored
|
|
|
|
end
|
|
|
|
end, ok, OldTable)
|
|
|
|
end,
|
|
|
|
{atomic, ok} = mnesia:transaction(F1),
|
|
|
|
mnesia:delete_table(OldTable),
|
2010-08-13 17:01:08 +02:00
|
|
|
TableInfo = gen_storage:table_info(HostB, Table, all),
|
2010-07-22 18:43:38 +02:00
|
|
|
{value, {_, Backend}} = lists:keysearch(backend, 1, TableInfo),
|
2010-08-13 17:01:08 +02:00
|
|
|
gen_storage:create_table(Backend, HostB, Table, TableInfo),
|
2010-07-22 18:43:38 +02:00
|
|
|
F2 = fun() ->
|
|
|
|
mnesia:write_lock_table(Table),
|
|
|
|
mnesia:foldl(
|
|
|
|
fun(NewRecord, _) ->
|
|
|
|
?DEBUG("~p-~p: ~p~n",[OldTable, Table, NewRecord]),
|
2010-08-13 17:01:08 +02:00
|
|
|
gen_storage:write(HostB, Table, NewRecord, write)
|
2010-07-22 18:43:38 +02:00
|
|
|
end, ok, TmpTable)
|
|
|
|
end,
|
|
|
|
{atomic, ok} = mnesia:transaction(F2),
|
|
|
|
mnesia:delete_table(TmpTable),
|
|
|
|
?INFO_MSG("Migration of mnesia table ~p successfully finished", [Table]);
|
|
|
|
|
|
|
|
Table =/= OldTable ->
|
|
|
|
?INFO_MSG("Migrating mnesia table ~p to ~p~nfrom ~p",
|
|
|
|
[OldTable, Table, OldAttributes]),
|
|
|
|
F1 = fun() ->
|
|
|
|
mnesia:write_lock_table(Table),
|
|
|
|
mnesia:foldl(
|
|
|
|
fun(OldRecord, _) ->
|
|
|
|
NewRecord = MigrateFun(OldRecord),
|
|
|
|
?DEBUG("~p-~p: ~p -> ~p~n",[OldTable, Table, OldRecord, NewRecord]),
|
|
|
|
if
|
|
|
|
is_tuple(NewRecord) ->
|
2010-08-13 17:01:08 +02:00
|
|
|
gen_storage:write(HostB, Table, NewRecord, write);
|
2010-07-22 18:43:38 +02:00
|
|
|
true ->
|
|
|
|
ignored
|
|
|
|
end
|
|
|
|
end, ok, OldTable)
|
|
|
|
end,
|
2010-07-22 18:47:36 +02:00
|
|
|
{atomic, _} = mnesia:transaction(F1),
|
2010-07-22 18:43:38 +02:00
|
|
|
mnesia:delete_table(OldTable),
|
|
|
|
?INFO_MSG("Migration of mnesia table ~p successfully finished", [Table]),
|
|
|
|
ok
|
|
|
|
end;
|
|
|
|
_ ->
|
|
|
|
ignored
|
|
|
|
end.
|
|
|
|
|
|
|
|
|
2011-08-13 18:11:26 +02:00
|
|
|
migrate_odbc(HostStr, Tables, Migrations) when is_list(HostStr) ->
|
|
|
|
migrate_odbc(list_to_binary(HostStr), Tables, Migrations);
|
2010-07-22 18:43:38 +02:00
|
|
|
migrate_odbc(Host, Tables, Migrations) ->
|
2011-08-13 18:11:26 +02:00
|
|
|
HostStr = binary_to_list(Host),
|
2010-07-22 18:43:38 +02:00
|
|
|
try ejabberd_odbc:sql_transaction(
|
2011-08-13 18:11:26 +02:00
|
|
|
HostStr,
|
2010-07-22 18:43:38 +02:00
|
|
|
fun() ->
|
|
|
|
lists:foreach(
|
|
|
|
fun(Migration) ->
|
|
|
|
case (catch migrate_odbc1(Host, Tables, Migration)) of
|
|
|
|
ok -> ok;
|
|
|
|
ignored -> ok;
|
|
|
|
R ->
|
|
|
|
?ERROR_MSG("Error performing migration ~p:~n~p",
|
|
|
|
[Migration, R])
|
|
|
|
end
|
|
|
|
end, Migrations)
|
|
|
|
end)
|
|
|
|
catch exit:{noproc, _Where} ->
|
|
|
|
?INFO_MSG("Not migrating ODBC on host ~p because no ODBC was configured.", [Host]),
|
|
|
|
ok
|
|
|
|
end.
|
|
|
|
|
|
|
|
migrate_odbc1(Host, Tables, {OldTable, OldColumns, MigrateFun}) ->
|
|
|
|
migrate_odbc1(Host, Tables, {[{OldTable, OldColumns}], MigrateFun});
|
|
|
|
|
|
|
|
migrate_odbc1(Host, Tables, {OldTablesColumns, MigrateFun}) ->
|
|
|
|
{[OldTable | _] = OldTables,
|
|
|
|
[OldColumns | _] = OldColumnsAll} = lists:unzip(OldTablesColumns),
|
|
|
|
OldTablesA = [list_to_atom(Table) || Table <- OldTables],
|
2011-03-10 19:47:14 +01:00
|
|
|
case is_table_exists(OldTable, odbc) of
|
|
|
|
true ->
|
|
|
|
ColumnsT = [odbc_table_columns_t(OldTable1) || OldTable1 <- OldTables],
|
|
|
|
migrate_odbc2(Host, Tables, OldTable, OldTables, OldColumns, OldColumnsAll, OldTablesA, ColumnsT, MigrateFun);
|
|
|
|
false ->
|
|
|
|
ignored
|
|
|
|
end.
|
2010-07-22 18:47:36 +02:00
|
|
|
|
2011-08-13 18:11:26 +02:00
|
|
|
migrate_odbc2(HostB, Tables, OldTable, OldTables, OldColumns, OldColumnsAll, OldTablesA, ColumnsT, MigrateFun)
|
2010-07-22 18:47:36 +02:00
|
|
|
when ColumnsT == OldColumnsAll ->
|
|
|
|
?INFO_MSG("Migrating ODBC table ~p to gen_storage tables ~p", [OldTable, Tables]),
|
|
|
|
|
|
|
|
%% rename old tables to *_old
|
|
|
|
lists:foreach(fun(OldTable1) ->
|
|
|
|
{updated, _} =
|
|
|
|
ejabberd_odbc:sql_query_t("alter table " ++ OldTable1 ++
|
|
|
|
" rename to " ++ OldTable1 ++ "_old")
|
|
|
|
end, OldTables),
|
|
|
|
%% recreate new tables
|
|
|
|
lists:foreach(fun(NewTable) ->
|
|
|
|
case lists:member(NewTable, OldTablesA) of
|
|
|
|
true ->
|
|
|
|
TableInfo =
|
2010-08-13 17:01:08 +02:00
|
|
|
gen_storage:table_info(HostB, NewTable, all),
|
2010-07-22 18:47:36 +02:00
|
|
|
{value, {_, Backend}} =
|
|
|
|
lists:keysearch(backend, 1, TableInfo),
|
|
|
|
gen_storage:create_table(Backend, HostB,
|
|
|
|
NewTable, TableInfo);
|
|
|
|
false -> ignored
|
|
|
|
end
|
|
|
|
end, Tables),
|
|
|
|
|
|
|
|
SELECT =
|
|
|
|
fun(Columns, Table, Keys) ->
|
|
|
|
Table1 = case lists:member(Table, OldTables) of
|
|
|
|
true -> Table ++ "_old";
|
|
|
|
false -> Table
|
|
|
|
end,
|
|
|
|
WherePart = case Keys of
|
|
|
|
[] -> "";
|
|
|
|
_ -> " WHERE " ++
|
|
|
|
string:join([K ++ "=" ++
|
|
|
|
if
|
|
|
|
is_list(V) ->
|
|
|
|
"\"" ++ ejabberd_odbc:escape(V) ++ "\"";
|
|
|
|
is_integer(V) ->
|
|
|
|
integer_to_list(V)
|
|
|
|
end
|
|
|
|
|| {K, V} <- Keys],
|
|
|
|
" AND ")
|
|
|
|
end,
|
|
|
|
{selected, _, Rows} =
|
|
|
|
ejabberd_odbc:sql_query_t("SELECT " ++ string:join(Columns, ", ") ++
|
|
|
|
" FROM " ++ Table1 ++
|
|
|
|
WherePart),
|
|
|
|
[tuple_to_list(Row) || Row <- Rows]
|
|
|
|
end,
|
|
|
|
|
|
|
|
%% TODO: this will need lots of RAM, make it batched
|
|
|
|
OldRows = SELECT(OldColumns, OldTable, []),
|
|
|
|
NRows =
|
|
|
|
lists:foldl(fun(OldRow, NRow) ->
|
|
|
|
NewRecords = apply(MigrateFun, [SELECT | OldRow]),
|
|
|
|
if
|
|
|
|
is_list(NewRecords) ->
|
|
|
|
lists:foreach(
|
|
|
|
fun(NewRecord) ->
|
|
|
|
%% TODO: gen_storage transaction?
|
|
|
|
gen_storage:dirty_write(HostB, NewRecord)
|
|
|
|
end, NewRecords);
|
|
|
|
is_tuple(NewRecords) ->
|
|
|
|
gen_storage:dirty_write(HostB, NewRecords)
|
|
|
|
end,
|
|
|
|
NRow + 1
|
|
|
|
end, 0, OldRows),
|
|
|
|
|
|
|
|
lists:foreach(fun(OldTable1) ->
|
|
|
|
{updated, _} = ejabberd_odbc:sql_query_t("drop table " ++ OldTable1 ++ "_old")
|
|
|
|
end, OldTables),
|
|
|
|
|
|
|
|
?INFO_MSG("Migrated ODBC table ~p to gen_storage tables ~p (~p rows)", [OldTable, Tables, NRows]),
|
|
|
|
ok;
|
|
|
|
|
|
|
|
migrate_odbc2(_Host, _Tables, _OldTable, _OldTables, _OldColumns, _OldColumnsAll, _OldTablesA, [[]], _MigrateFun) ->
|
|
|
|
ignored;
|
|
|
|
|
|
|
|
migrate_odbc2(Host, Tables, OldTable, OldTables, OldColumns, OldColumnsAll, OldTablesA, [ColumnsTAndCreatedat | MoreCTAC], MigrateFun)
|
|
|
|
when ColumnsTAndCreatedat /= OldColumnsAll ->
|
|
|
|
case lists:last(ColumnsTAndCreatedat) of
|
|
|
|
"created_at" ->
|
|
|
|
ColumnsT = ColumnsTAndCreatedat -- ["created_at"],
|
|
|
|
migrate_odbc2(Host, Tables, OldTable, OldTables, OldColumns, OldColumnsAll, OldTablesA, [ColumnsT | MoreCTAC], MigrateFun);
|
|
|
|
_ ->
|
2010-07-22 18:43:38 +02:00
|
|
|
ignored
|
|
|
|
end.
|
|
|
|
|
|
|
|
odbc_table_columns_t(Table) ->
|
|
|
|
case ejabberd_odbc:sql_query_t("select column_name from information_schema.columns where table_name='" ++ Table ++ "'") of
|
|
|
|
{selected, _, Columns1} ->
|
|
|
|
Columns2 = lists:map(fun({C}) -> C end, Columns1),
|
|
|
|
Columns2
|
|
|
|
end.
|
|
|
|
|
2011-03-10 19:47:14 +01:00
|
|
|
is_table_exists(Table, odbc) ->
|
|
|
|
case catch ejabberd_odbc:sql_query_t("SELECT COUNT(*) FROM " ++ Table) of
|
|
|
|
{selected, _, _} ->
|
|
|
|
true;
|
|
|
|
_ ->
|
|
|
|
false
|
|
|
|
end.
|