summaryrefslogtreecommitdiff
path: root/src/ejd2sql.erl
diff options
context:
space:
mode:
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>2016-04-20 12:27:32 +0300
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>2016-04-20 13:25:42 +0300
commit1aae8a9fda184b8a8fa139d9fc4bf2aa2c6ba8a0 (patch)
tree678fb932d5bda2fdc2961c054dbaea64d81f977c /src/ejd2sql.erl
parentRename ejabberd_sm_odbc -> ejabberd_sm_sql (diff)
Rename odbc to sql everywhere
Diffstat (limited to 'src/ejd2sql.erl')
-rw-r--r--src/ejd2sql.erl315
1 files changed, 315 insertions, 0 deletions
diff --git a/src/ejd2sql.erl b/src/ejd2sql.erl
new file mode 100644
index 00000000..aa74286e
--- /dev/null
+++ b/src/ejd2sql.erl
@@ -0,0 +1,315 @@
+%%%----------------------------------------------------------------------
+%%% File : ejd2sql.erl
+%%% Author : Alexey Shchepin <alexey@process-one.net>
+%%% Purpose : Export some mnesia tables to SQL DB
+%%% Created : 22 Aug 2005 by Alexey Shchepin <alexey@process-one.net>
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
+%%%
+%%% This program is free software; you can redistribute it and/or
+%%% modify it under the terms of the GNU General Public License as
+%%% published by the Free Software Foundation; either version 2 of the
+%%% License, or (at your option) any later version.
+%%%
+%%% This program is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%%% General Public License for more details.
+%%%
+%%% You should have received a copy of the GNU General Public License along
+%%% with this program; if not, write to the Free Software Foundation, Inc.,
+%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+%%%
+%%%----------------------------------------------------------------------
+
+-module(ejd2sql).
+
+-author('alexey@process-one.net').
+
+-include("logger.hrl").
+
+-export([export/2, export/3, import_file/2, import/2,
+ import/3, delete/1]).
+
+-define(MAX_RECORDS_PER_TRANSACTION, 100).
+
+-record(dump, {fd, cont = start}).
+
+%%%----------------------------------------------------------------------
+%%% API
+%%%----------------------------------------------------------------------
+%%% How to use:
+%%% A table can be converted from Mnesia to an ODBC database by calling
+%%% one of the API function with the following parameters:
+%%% - Server is the server domain you want to convert
+%%% - Output can be either sql to export to the configured relational
+%%% database or "Filename" to export to text file.
+
+modules() ->
+ [ejabberd_auth,
+ mod_announce,
+ mod_irc,
+ mod_last,
+ mod_muc,
+ mod_offline,
+ mod_privacy,
+ mod_private,
+ %% mod_pubsub,
+ mod_roster,
+ mod_shared_roster,
+ mod_vcard,
+ mod_vcard_xupdate].
+
+export(Server, Output) ->
+ LServer = jid:nameprep(iolist_to_binary(Server)),
+ Modules = modules(),
+ IO = prepare_output(Output),
+ lists:foreach(
+ fun(Module) ->
+ export(LServer, IO, Module)
+ end, Modules),
+ close_output(Output, IO).
+
+export(Server, Output, Module) ->
+ LServer = jid:nameprep(iolist_to_binary(Server)),
+ IO = prepare_output(Output),
+ lists:foreach(
+ fun({Table, ConvertFun}) ->
+ export(LServer, Table, IO, ConvertFun)
+ end, Module:export(Server)),
+ close_output(Output, IO).
+
+delete(Server) ->
+ Modules = modules(),
+ lists:foreach(
+ fun(Module) ->
+ delete(Server, Module)
+ end, Modules).
+
+delete(Server, Module) ->
+ LServer = jid:nameprep(iolist_to_binary(Server)),
+ lists:foreach(
+ fun({Table, ConvertFun}) ->
+ delete(LServer, Table, ConvertFun)
+ end, Module:export(Server)).
+
+import_file(Server, FileName) when is_binary(FileName) ->
+ import(Server, binary_to_list(FileName));
+import_file(Server, FileName) ->
+ case disk_log:open([{name, make_ref()},
+ {file, FileName},
+ {mode, read_only}]) of
+ {ok, Fd} ->
+ LServer = jid:nameprep(Server),
+ Mods = [{Mod, gen_mod:db_type(LServer, Mod)}
+ || Mod <- modules(), gen_mod:is_loaded(LServer, Mod)],
+ AuthMods = case lists:member(ejabberd_auth_internal,
+ ejabberd_auth:auth_modules(LServer)) of
+ true ->
+ [{ejabberd_auth, mnesia}];
+ false ->
+ []
+ end,
+ import_dump(LServer, AuthMods ++ Mods, #dump{fd = Fd});
+ Err ->
+ exit(Err)
+ end.
+
+import(Server, Output) ->
+ import(Server, Output, [{fast, true}]).
+
+import(Server, Output, Opts) ->
+ LServer = jid:nameprep(iolist_to_binary(Server)),
+ Modules = modules(),
+ IO = prepare_output(Output, disk_log),
+ lists:foreach(
+ fun(Module) ->
+ import(LServer, IO, Opts, Module)
+ end, Modules),
+ close_output(Output, IO).
+
+import(Server, Output, Opts, Module) ->
+ LServer = jid:nameprep(iolist_to_binary(Server)),
+ IO = prepare_output(Output, disk_log),
+ lists:foreach(
+ fun({SelectQuery, ConvertFun}) ->
+ import(LServer, SelectQuery, IO, ConvertFun, Opts)
+ end, Module:import(Server)),
+ close_output(Output, IO).
+
+%%%----------------------------------------------------------------------
+%%% Internal functions
+%%%----------------------------------------------------------------------
+export(LServer, Table, IO, ConvertFun) ->
+ F = fun () ->
+ mnesia:read_lock_table(Table),
+ {_N, SQLs} =
+ mnesia:foldl(
+ fun(R, {N, SQLs} = Acc) ->
+ case ConvertFun(LServer, R) of
+ [] ->
+ Acc;
+ SQL ->
+ if N < (?MAX_RECORDS_PER_TRANSACTION) - 1 ->
+ {N + 1, [SQL | SQLs]};
+ true ->
+ output(LServer,
+ Table, IO,
+ flatten([SQL | SQLs])),
+ {0, []}
+ end
+ end
+ end,
+ {0, []}, Table),
+ output(LServer, Table, IO, flatten(SQLs))
+ end,
+ mnesia:transaction(F).
+
+output(_LServer, _Table, _IO, []) ->
+ ok;
+output(LServer, _Table, sql, SQLs) ->
+ ejabberd_sql:sql_transaction(LServer, SQLs);
+output(_LServer, Table, Fd, SQLs) ->
+ file:write(Fd, ["-- \n-- Mnesia table: ", atom_to_list(Table),
+ "\n--\n", SQLs]).
+
+delete(LServer, Table, ConvertFun) ->
+ F = fun () ->
+ mnesia:write_lock_table(Table),
+ {_N, SQLs} =
+ mnesia:foldl(
+ fun(R, {N, SQLs} = Acc) ->
+ case ConvertFun(LServer, R) of
+ [] ->
+ Acc;
+ _SQL ->
+ mnesia:delete_object(R),
+ Acc
+ end
+ end,
+ {0, []}, Table),
+ delete(LServer, Table, SQLs)
+ end,
+ mnesia:transaction(F).
+
+import(LServer, SelectQuery, IO, ConvertFun, Opts) ->
+ F = case proplists:get_bool(fast, Opts) of
+ true ->
+ fun() ->
+ case ejabberd_sql:sql_query_t(SelectQuery) of
+ {selected, _, Rows} ->
+ lists:foldl(fun process_sql_row/2,
+ {IO, ConvertFun, undefined}, Rows);
+ Err ->
+ erlang:error(Err)
+ end
+ end;
+ false ->
+ fun() ->
+ ejabberd_sql:sql_query_t(
+ [iolist_to_binary(
+ [<<"declare c cursor for ">>, SelectQuery])]),
+ fetch(IO, ConvertFun, undefined)
+ end
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+fetch(IO, ConvertFun, PrevRow) ->
+ case ejabberd_sql:sql_query_t([<<"fetch c;">>]) of
+ {selected, _, [Row]} ->
+ process_sql_row(Row, {IO, ConvertFun, PrevRow}),
+ fetch(IO, ConvertFun, Row);
+ {selected, _, []} ->
+ ok;
+ Err ->
+ erlang:error(Err)
+ end.
+
+process_sql_row(Row, {IO, ConvertFun, PrevRow}) when Row == PrevRow ->
+ %% Avoid calling ConvertFun with the same input
+ {IO, ConvertFun, Row};
+process_sql_row(Row, {IO, ConvertFun, _PrevRow}) ->
+ case catch ConvertFun(Row) of
+ {'EXIT', _} = Err ->
+ ?ERROR_MSG("failed to convert ~p: ~p", [Row, Err]);
+ Term ->
+ ok = disk_log:log(IO#dump.fd, Term)
+ end,
+ {IO, ConvertFun, Row}.
+
+import_dump(LServer, Mods, #dump{fd = Fd, cont = Cont}) ->
+ case disk_log:chunk(Fd, Cont) of
+ {NewCont, Terms} ->
+ import_terms(LServer, Mods, Terms),
+ import_dump(LServer, Mods, #dump{fd = Fd, cont = NewCont});
+ eof ->
+ ok;
+ Err ->
+ exit(Err)
+ end.
+
+import_terms(LServer, Mods, [Term|Terms]) ->
+ import_term(LServer, Mods, Term),
+ import_terms(LServer, Mods, Terms);
+import_terms(_LServer, _Mods, []) ->
+ ok.
+
+import_term(LServer, [{Mod, DBType}|Mods], Term) ->
+ case catch Mod:import(LServer, DBType, Term) of
+ pass -> import_term(LServer, Mods, Term);
+ ok -> ok;
+ Err ->
+ ?ERROR_MSG("failed to import ~p for module ~p: ~p",
+ [Term, Mod, Err])
+ end;
+import_term(_LServer, [], _Term) ->
+ ok.
+
+prepare_output(FileName) ->
+ prepare_output(FileName, normal).
+
+prepare_output(FileName, Type) when is_binary(FileName) ->
+ prepare_output(binary_to_list(FileName), Type);
+prepare_output(FileName, normal) when is_list(FileName) ->
+ case file:open(FileName, [write, raw]) of
+ {ok, Fd} ->
+ Fd;
+ Err ->
+ exit(Err)
+ end;
+prepare_output(FileName, disk_log) when is_list(FileName) ->
+ case disk_log:open([{name, make_ref()},
+ {repair, truncate},
+ {file, FileName}]) of
+ {ok, Fd} ->
+ #dump{fd = Fd};
+ Err ->
+ exit(Err)
+ end;
+prepare_output(Output, _Type) ->
+ Output.
+
+close_output(FileName, Fd) when FileName /= Fd ->
+ case Fd of
+ #dump{} ->
+ disk_log:close(Fd#dump.fd);
+ _ ->
+ file:close(Fd)
+ end,
+ ok;
+close_output(_, _) ->
+ ok.
+
+flatten(SQLs) ->
+ flatten(SQLs, []).
+
+flatten([L|Ls], Acc) ->
+ flatten(Ls, flatten1(lists:reverse(L), Acc));
+flatten([], Acc) ->
+ Acc.
+
+flatten1([H|T], Acc) ->
+ flatten1(T, [[H, $\n]|Acc]);
+flatten1([], Acc) ->
+ Acc.