• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

processone / ejabberd / 747

27 Jun 2024 01:43PM UTC coverage: 32.123% (+0.8%) from 31.276%
747

push

github

badlop
Set version to 24.06

14119 of 43953 relevant lines covered (32.12%)

614.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/ejd2sql.erl
1
%%%----------------------------------------------------------------------
2
%%% File    : ejd2sql.erl
3
%%% Author  : Alexey Shchepin <alexey@process-one.net>
4
%%% Purpose : Export some mnesia tables to SQL DB
5
%%% Created : 22 Aug 2005 by Alexey Shchepin <alexey@process-one.net>
6
%%%
7
%%%
8
%%% ejabberd, Copyright (C) 2002-2024   ProcessOne
9
%%%
10
%%% This program is free software; you can redistribute it and/or
11
%%% modify it under the terms of the GNU General Public License as
12
%%% published by the Free Software Foundation; either version 2 of the
13
%%% License, or (at your option) any later version.
14
%%%
15
%%% This program is distributed in the hope that it will be useful,
16
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
%%% General Public License for more details.
19
%%%
20
%%% You should have received a copy of the GNU General Public License along
21
%%% with this program; if not, write to the Free Software Foundation, Inc.,
22
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23
%%%
24
%%%----------------------------------------------------------------------
25

26
-module(ejd2sql).
27

28
-author('alexey@process-one.net').
29

30
-include("logger.hrl").
31
-include("ejabberd_sql_pt.hrl").
32

33
-export([export/2, export/3, import/3, import/4, delete/1, import_info/1]).
34

35

36
-define(MAX_RECORDS_PER_TRANSACTION, 100).
37

38
-record(sql_dump, {fd, type}).
39

40
%%%----------------------------------------------------------------------
41
%%% API
42
%%%----------------------------------------------------------------------
43
%%% How to use:
44
%%% A table can be converted from Mnesia to an ODBC database by calling
45
%%% one of the API function with the following parameters:
46
%%% - Server is the server domain you want to convert
47
%%% - Output can be either sql to export to the configured relational
48
%%%   database or "Filename" to export to text file.
49

50
modules() ->
51
    [ejabberd_auth,
×
52
     mod_announce,
53
     mod_caps,
54
     mod_last,
55
     mod_mam,
56
     mod_muc,
57
     mod_offline,
58
     mod_privacy,
59
     mod_private,
60
     mod_pubsub,
61
     mod_push,
62
     mod_roster,
63
     mod_shared_roster,
64
     mod_vcard].
65

66
export(Server, Output) ->
67
    LServer = jid:nameprep(iolist_to_binary(Server)),
×
68
    Modules = modules(),
×
69
    IO = prepare_output(Output),
×
70
    lists:foreach(
×
71
      fun(Module) ->
72
              export(LServer, IO, Module)
×
73
      end, Modules),
74
    close_output(Output, IO).
×
75

76
export(Server, Output, mod_mam = M1) ->
77
    MucServices = case gen_mod:is_loaded(Server, mod_muc) of
×
78
        true -> gen_mod:get_module_opt_hosts(Server, mod_muc);
×
79
        false -> []
×
80
    end,
81
    [export2(MucService, Output, M1, M1) || MucService <- MucServices],
×
82
    export2(Server, Output, M1, M1);
×
83
export(Server, Output, mod_pubsub = M1) ->
84
    export2(Server, Output, M1, pubsub_db);
×
85
export(Server, Output, M1) ->
86
    export2(Server, Output, M1, M1).
×
87

88
export2(Server, Output, Module1, Module) ->
89
    SQLMod = gen_mod:db_mod(sql, Module),
×
90
    LServer = jid:nameprep(iolist_to_binary(Server)),
×
91
    IO = prepare_output(Output),
×
92
    lists:foreach(
×
93
      fun({Table, ConvertFun}) ->
94
              case export(LServer, Table, IO, ConvertFun) of
×
95
                  {atomic, ok} -> ok;
×
96
                  {aborted, {no_exists, _}} ->
97
                      ?WARNING_MSG("Ignoring export for module ~ts: "
×
98
                                   "Mnesia table ~ts doesn't exist (most likely "
99
                                   "because the module is unused)",
100
                                   [Module1, Table]);
×
101
                  {aborted, Reason} ->
102
                      ?ERROR_MSG("Failed export for module ~p and table ~p: ~p",
×
103
                                 [Module, Table, Reason])
×
104
              end
105
      end, SQLMod:export(Server)),
106
    close_output(Output, IO).
×
107

108
delete(Server) ->
109
    Modules = modules(),
×
110
    lists:foreach(
×
111
      fun(Module) ->
112
              delete(Server, Module)
×
113
      end, Modules).
114

115
delete(Server, Module1) ->
116
    LServer = jid:nameprep(iolist_to_binary(Server)),
×
117
    Module = case Module1 of
×
118
                 mod_pubsub -> pubsub_db;
×
119
                 _ -> Module1
×
120
             end,
121
    SQLMod = gen_mod:db_mod(sql, Module),
×
122
    lists:foreach(
×
123
      fun({Table, ConvertFun}) ->
124
              delete(LServer, Table, ConvertFun)
×
125
      end, SQLMod:export(Server)).
126

127
import(Server, Dir, ToType) ->
128
    lists:foreach(
×
129
      fun(Mod) ->
130
              ?INFO_MSG("Importing ~p...", [Mod]),
×
131
              import(Mod, Server, Dir, ToType)
×
132
      end, modules()).
133

134
import(Mod, Server, Dir, ToType) ->
135
    LServer = jid:nameprep(iolist_to_binary(Server)),
×
136
    try Mod:import_start(LServer, ToType)
×
137
    catch error:undef -> ok end,
×
138
    lists:foreach(
×
139
      fun({File, Tab, _Mod, FieldsNumber}) ->
140
              FileName = filename:join([Dir, File]),
×
141
              case open_sql_dump(FileName) of
×
142
                  {ok, #sql_dump{type = FromType} = Dump} ->
143
                      import_rows(LServer, {sql, FromType}, ToType,
×
144
                                  Tab, Mod, Dump, FieldsNumber),
145
                      close_sql_dump(Dump);
×
146
                  {error, enoent} ->
147
                      ok;
×
148
                  eof ->
149
                      ?INFO_MSG("It seems like SQL dump ~ts is empty", [FileName]);
×
150
                  Err ->
151
                      ?ERROR_MSG("Failed to open SQL dump ~ts: ~ts",
×
152
                                 [FileName, format_error(Err)])
×
153
              end
154
      end, import_info(Mod)),
155
    try Mod:import_stop(LServer, ToType)
×
156
    catch error:undef -> ok end.
×
157

158
import_info(Mod) ->
159
    Info = Mod:import_info(),
×
160
    lists:map(
×
161
      fun({Tab, FieldsNum}) ->
162
              FileName = <<Tab/binary, ".txt">>,
×
163
              {FileName, Tab, Mod, FieldsNum}
×
164
      end, Info).
165

166
%%%----------------------------------------------------------------------
167
%%% Internal functions
168
%%%----------------------------------------------------------------------
169
export(LServer, Table, IO, ConvertFun) ->
170
    DbType = ejabberd_option:sql_type(LServer),
×
171
    LServerConvert = case Table of
×
172
                         archive_msg ->
173
                             [LServer | mod_muc_admin:find_hosts(LServer)];
×
174
                         _ ->
175
                             LServer
×
176
                     end,
177
    F = fun () ->
×
178
                mnesia:read_lock_table(Table),
×
179
                {_N, SQLs} =
×
180
                    mnesia:foldl(
181
                      fun(R, {N, SQLs} = Acc) ->
182
                              case ConvertFun(LServerConvert, R) of
×
183
                                  [] ->
184
                                      Acc;
×
185
                                  SQL1 ->
186
                                      SQL = format_queries(DbType, SQL1),
×
187
                                      if N < (?MAX_RECORDS_PER_TRANSACTION) - 1 ->
×
188
                                              {N + 1, [SQL | SQLs]};
×
189
                                         true ->
190
                                              output(LServer,
×
191
                                                     Table, IO,
192
                                                     flatten([SQL | SQLs])),
193
                                              {0, []}
×
194
                                      end
195
                              end
196
                      end,
197
                      {0, []}, Table),
198
                output(LServer, Table, IO, flatten(SQLs))
×
199
        end,
200
    mnesia:transaction(F).
×
201

202
output(_LServer, _Table, _IO, []) ->
203
    ok;
×
204
output(LServer, _Table, sql, SQLs) ->
205
    {atomic, ok} = ejabberd_sql:sql_transaction(LServer, SQLs),
×
206
    ok;
×
207
output(_LServer, Table, Fd, SQLs) ->
208
    file:write(Fd, ["-- \n-- Mnesia table: ", atom_to_list(Table),
×
209
                    "\n--\n", SQLs]).
210

211
delete(LServer, Table, ConvertFun) ->
212
    F = fun () ->
×
213
                mnesia:write_lock_table(Table),
×
214
                {_N, _SQLs} =
×
215
                    mnesia:foldl(
216
                      fun(R, Acc) ->
217
                              case ConvertFun(LServer, R) of
×
218
                                  [] ->
219
                                      Acc;
×
220
                                  _SQL ->
221
                                      mnesia:delete_object(R),
×
222
                                      Acc
×
223
                              end
224
                      end,
225
                      {0, []}, Table)
226
        end,
227
    mnesia:transaction(F).
×
228

229
prepare_output(FileName) ->
230
    prepare_output(FileName, normal).
×
231

232
prepare_output(FileName, Type) when is_binary(FileName) ->
233
    prepare_output(binary_to_list(FileName), Type);
×
234
prepare_output(FileName, normal) when is_list(FileName) ->
235
    case file:open(FileName, [write, raw]) of
×
236
        {ok, Fd} ->
237
            Fd;
×
238
        {error, eacces} ->
239
            exit({"Not enough permission to the file or path", FileName});
×
240
        {error, enoent} ->
241
            exit({"Path does not exist", FileName});
×
242
        Err ->
243
            exit(Err)
×
244
    end;
245
prepare_output(Output, _Type) ->
246
    Output.
×
247

248
close_output(FileName, Fd) when FileName /= Fd ->
249
    file:close(Fd),
×
250
    ok;
×
251
close_output(_, _) ->
252
    ok.
×
253

254
flatten(SQLs) ->
255
    flatten(SQLs, []).
×
256

257
flatten([L|Ls], Acc) ->
258
    flatten(Ls, flatten1(lists:reverse(L), Acc));
×
259
flatten([], Acc) ->
260
    Acc.
×
261

262
flatten1([H|T], Acc) ->
263
    flatten1(T, [[H, $\n]|Acc]);
×
264
flatten1([], Acc) ->
265
    Acc.
×
266

267
import_rows(LServer, FromType, ToType, Tab, Mod, Dump, FieldsNumber) ->
268
    case read_row_from_sql_dump(Dump, FieldsNumber) of
×
269
        {ok, Fields} ->
270
            case catch Mod:import(LServer, FromType, ToType, Tab, Fields) of
×
271
                ok ->
272
                    ok;
×
273
                Err ->
274
                    ?ERROR_MSG("Failed to import fields ~p for tab ~p: ~p",
×
275
                               [Fields, Tab, Err])
×
276
            end,
277
            import_rows(LServer, FromType, ToType,
×
278
                        Tab, Mod, Dump, FieldsNumber);
279
        eof ->
280
            ok;
×
281
        Err ->
282
            ?ERROR_MSG("Failed to read row from SQL dump: ~ts",
×
283
                       [format_error(Err)])
×
284
    end.
285

286
open_sql_dump(FileName) ->
287
    case file:open(FileName, [raw, read, binary, read_ahead]) of
×
288
        {ok, Fd} ->
289
            case file:read(Fd, 11) of
×
290
                {ok, <<"PGCOPY\n", 16#ff, "\r\n", 0>>} ->
291
                    case skip_pgcopy_header(Fd) of
×
292
                        ok ->
293
                            {ok, #sql_dump{fd = Fd, type = pgsql}};
×
294
                        Err ->
295
                            Err
×
296
                    end;
297
                {ok, _} ->
298
                    file:position(Fd, 0),
×
299
                    {ok, #sql_dump{fd = Fd, type = mysql}};
×
300
                Err ->
301
                    Err
×
302
            end;
303
        Err ->
304
            Err
×
305
    end.
306

307
close_sql_dump(#sql_dump{fd = Fd}) ->
308
    file:close(Fd).
×
309

310
read_row_from_sql_dump(#sql_dump{fd = Fd, type = pgsql}, _) ->
311
    case file:read(Fd, 2) of
×
312
        {ok, <<(-1):16/signed>>} ->
313
            eof;
×
314
        {ok, <<FieldsNum:16>>} ->
315
            read_fields(Fd, FieldsNum, []);
×
316
        {ok, _} ->
317
            {error, eof};
×
318
        eof ->
319
            {error, eof};
×
320
        {error, _} = Err ->
321
            Err
×
322
    end;
323
read_row_from_sql_dump(#sql_dump{fd = Fd, type = mysql}, FieldsNum) ->
324
    read_lines(Fd, FieldsNum, <<"">>, []).
×
325

326
skip_pgcopy_header(Fd) ->
327
    try
×
328
        {ok, <<_:4/binary, ExtSize:32>>} = file:read(Fd, 8),
×
329
        {ok, <<_:ExtSize/binary>>} = file:read(Fd, ExtSize),
×
330
        ok
×
331
    catch error:{badmatch, {error, _} = Err} ->
332
            Err;
×
333
          error:{badmatch, _} ->
334
            {error, eof}
×
335
    end.
336

337
read_fields(_Fd, 0, Acc) ->
338
    {ok, lists:reverse(Acc)};
×
339
read_fields(Fd, N, Acc) ->
340
    case file:read(Fd, 4) of
×
341
        {ok, <<(-1):32/signed>>} ->
342
            read_fields(Fd, N-1, [null|Acc]);
×
343
        {ok, <<ValSize:32>>} ->
344
            case file:read(Fd, ValSize) of
×
345
                {ok, <<Val:ValSize/binary>>} ->
346
                    read_fields(Fd, N-1, [Val|Acc]);
×
347
                {ok, _} ->
348
                    {error, eof};
×
349
                Err ->
350
                    Err
×
351
            end;
352
        {ok, _} ->
353
            {error, eof};
×
354
        eof ->
355
            {error, eof};
×
356
        {error, _} = Err ->
357
            Err
×
358
    end.
359

360
read_lines(_Fd, 0, <<"">>, Acc) ->
361
    {ok, lists:reverse(Acc)};
×
362
read_lines(Fd, N, Buf, Acc) ->
363
    case file:read_line(Fd) of
×
364
        {ok, Data} when size(Data) >= 2 ->
365
            Size = size(Data) - 2,
×
366
            case Data of
×
367
                <<Val:Size/binary, 0, $\n>> ->
368
                    NewBuf = <<Buf/binary, Val/binary>>,
×
369
                    read_lines(Fd, N-1, <<"">>, [NewBuf|Acc]);
×
370
                _ ->
371
                    NewBuf = <<Buf/binary, Data/binary>>,
×
372
                    read_lines(Fd, N, NewBuf, Acc)
×
373
            end;
374
        {ok, Data} ->
375
            NewBuf = <<Buf/binary, Data/binary>>,
×
376
            read_lines(Fd, N, NewBuf, Acc);
×
377
        eof when Buf == <<"">>, Acc == [] ->
378
            eof;
×
379
        eof ->
380
            {error, eof};
×
381
        {error, _} = Err ->
382
            Err
×
383
    end.
384

385
format_error({error, eof}) ->
386
    "unexpected end of file";
×
387
format_error({error, Posix}) ->
388
    file:format_error(Posix).
×
389

390
format_queries(DbType, SQLs) ->
391
    lists:map(
×
392
      fun(#sql_query{} = SQL) ->
393
              ejabberd_sql:sql_query_to_iolist(DbType, SQL);
×
394
         (SQL) ->
395
              SQL
×
396
      end, SQLs).
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc