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

processone / ejabberd / 1077

11 Jul 2025 11:15AM UTC coverage: 34.008% (+0.4%) from 33.58%
1077

push

github

badlop
Set version to 25.07

15528 of 45660 relevant lines covered (34.01%)

644.27 hits per line

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

1.6
/src/mod_admin_update_sql.erl
1
%%%-------------------------------------------------------------------
2
%%% File    : mod_admin_update_sql.erl
3
%%% Author  : Alexey Shchepin <alexey@process-one.net>
4
%%% Purpose : Convert SQL DB to the new format
5
%%% Created :  9 Aug 2017 by Alexey Shchepin <alexey@process-one.net>
6
%%%
7
%%%
8
%%% ejabberd, Copyright (C) 2002-2025   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(mod_admin_update_sql).
27
-author('alexey@process-one.net').
28

29
-behaviour(gen_mod).
30

31
-export([start/2, stop/1, reload/3, mod_options/1,
32
         get_commands_spec/0, depends/2, mod_doc/0]).
33

34
% Commands API
35
-export([update_sql/0]).
36

37
% For testing
38
-export([update_sql/1]).
39

40
-include("logger.hrl").
41
-include("ejabberd_commands.hrl").
42
-include_lib("xmpp/include/xmpp.hrl").
43
-include("ejabberd_sql_pt.hrl").
44
-include("translate.hrl").
45

46
%%%
47
%%% gen_mod
48
%%%
49

50
start(_Host, _Opts) ->
51
    {ok, [{commands, get_commands_spec()}]}.
3✔
52

53
stop(_Host) ->
54
    ok.
3✔
55

56
reload(_Host, _NewOpts, _OldOpts) ->
57
    ok.
×
58

59
depends(_Host, _Opts) ->
60
    [].
9✔
61

62
%%%
63
%%% Register commands
64
%%%
65

66
get_commands_spec() ->
67
    [#ejabberd_commands{name = update_sql, tags = [sql],
3✔
68
                        desc = "Convert MS SQL, MySQL or PostgreSQL DB to the new format",
69
                        note = "improved in 23.04",
70
                        module = ?MODULE, function = update_sql,
71
                        args = [],
72
                        args_example = [],
73
                        args_desc = [],
74
                        result = {res, rescode},
75
                        result_example = ok}
76
    ].
77

78
update_sql() ->
79
    lists:foreach(
×
80
      fun(Host) ->
81
              case ejabberd_sql_sup:is_started(Host) of
×
82
                  false ->
83
                      ok;
×
84
                  true ->
85
                      update_sql(Host)
×
86
              end
87
      end, ejabberd_option:hosts()).
88

89
-record(state, {host :: binary(),
90
                dbtype :: mysql | pgsql | sqlite | mssql | odbc,
91
                escape}).
92

93
update_sql(Host) ->
94
    LHost = jid:nameprep(Host),
×
95
    DBType = ejabberd_option:sql_type(LHost),
×
96
    IsSupported =
×
97
        case DBType of
98
            mssql -> true;
×
99
            mysql -> true;
×
100
            pgsql -> true;
×
101
            _ -> false
×
102
        end,
103
    if
×
104
        not IsSupported ->
105
            io:format("Converting ~p DB is not supported~n", [DBType]),
×
106
            error;
×
107
        true ->
108
            Escape =
×
109
                case DBType of
110
                    mssql -> fun ejabberd_sql:standard_escape/1;
×
111
                    sqlite -> fun ejabberd_sql:standard_escape/1;
×
112
                    _ -> fun ejabberd_sql:escape/1
×
113
                end,
114
            State = #state{host = LHost,
×
115
                           dbtype = DBType,
116
                           escape = Escape},
117
            update_tables(State),
×
118
            check_config()
×
119
    end.
120

121
check_config() ->
122
    case ejabberd_sql:use_new_schema() of
×
123
        true -> ok;
×
124
        false ->
125
            ejabberd_config:set_option(new_sql_schema, true),
×
126
            io:format('~nNOTE: you must add "new_sql_schema: true" to ejabberd.yml before next restart~n~n', [])
×
127
    end.
128

129
update_tables(State) ->
130
    case add_sh_column(State, "users") of
×
131
        true ->
132
            drop_pkey(State, "users"),
×
133
            add_pkey(State, "users", ["server_host", "username", "type"]),
×
134
            drop_sh_default(State, "users");
×
135
        false ->
136
            ok
×
137
    end,
138

139
    case add_sh_column(State, "last") of
×
140
        true ->
141
            drop_pkey(State, "last"),
×
142
            add_pkey(State, "last", ["server_host", "username"]),
×
143
            drop_sh_default(State, "last");
×
144
        false ->
145
            ok
×
146
    end,
147

148
    case add_sh_column(State, "rosterusers") of
×
149
        true ->
150
            drop_index(State, "rosterusers", "i_rosteru_user_jid"),
×
151
            drop_index(State, "rosterusers", "i_rosteru_username"),
×
152
            drop_index(State, "rosterusers", "i_rosteru_jid"),
×
153
            create_unique_index(State, "rosterusers", "i_rosteru_sh_user_jid", ["server_host", "username", "jid"]),
×
154
            create_index(State, "rosterusers", "i_rosteru_sh_jid", ["server_host", "jid"]),
×
155
            drop_sh_default(State, "rosterusers");
×
156
        false ->
157
            ok
×
158
    end,
159

160
    case add_sh_column(State, "rostergroups") of
×
161
        true ->
162
            drop_index(State, "rostergroups", "pk_rosterg_user_jid"),
×
163
            create_index(State, "rostergroups", "i_rosterg_sh_user_jid", ["server_host", "username", "jid"]),
×
164
            drop_sh_default(State, "rostergroups");
×
165
        false ->
166
            ok
×
167
    end,
168

169
    case add_sh_column(State, "sr_group") of
×
170
        true ->
171
            drop_index(State, "sr_group", "i_sr_group_name"),
×
172
            create_unique_index(State, "sr_group", "i_sr_group_sh_name", ["server_host", "name"]),
×
173
            drop_sh_default(State, "sr_group");
×
174
        false ->
175
            ok
×
176
    end,
177

178
    case add_sh_column(State, "sr_user") of
×
179
        true ->
180
            drop_index(State, "sr_user", "i_sr_user_jid_grp"),
×
181
            drop_index(State, "sr_user", "i_sr_user_jid"),
×
182
            drop_index(State, "sr_user", "i_sr_user_grp"),
×
183
            create_unique_index(State, "sr_user", "i_sr_user_sh_jid_grp", ["server_host", "jid", "grp"]),
×
184
            create_index(State, "sr_user", "i_sr_user_sh_grp", ["server_host", "grp"]),
×
185
            drop_sh_default(State, "sr_user");
×
186
        false ->
187
            ok
×
188
    end,
189

190
    case add_sh_column(State, "spool") of
×
191
        true ->
192
            drop_index(State, "spool", "i_despool"),
×
193
            create_index(State, "spool", "i_spool_sh_username", ["server_host", "username"]),
×
194
            drop_sh_default(State, "spool");
×
195
        false ->
196
            ok
×
197
    end,
198

199
    case add_sh_column(State, "archive") of
×
200
        true ->
201
            drop_index(State, "archive", "i_username"),
×
202
            drop_index(State, "archive", "i_username_timestamp"),
×
203
            drop_index(State, "archive", "i_timestamp"),
×
204
            drop_index(State, "archive", "i_peer"),
×
205
            drop_index(State, "archive", "i_bare_peer"),
×
206
            drop_index(State, "archive", "i_username_peer"),
×
207
            drop_index(State, "archive", "i_username_bare_peer"),
×
208
            create_index(State, "archive", "i_archive_sh_username_timestamp", ["server_host", "username", "timestamp"]),
×
209
            create_index(State, "archive", "i_archive_sh_timestamp", ["server_host", "timestamp"]),
×
210
            create_index(State, "archive", "i_archive_sh_username_peer", ["server_host", "username", "peer"]),
×
211
            create_index(State, "archive", "i_archive_sh_username_bare_peer", ["server_host", "username", "bare_peer"]),
×
212
            drop_sh_default(State, "archive");
×
213
        false ->
214
            ok
×
215
    end,
216

217
    case add_sh_column(State, "archive_prefs") of
×
218
        true ->
219
            drop_pkey(State, "archive_prefs"),
×
220
            add_pkey(State, "archive_prefs", ["server_host", "username"]),
×
221
            drop_sh_default(State, "archive_prefs");
×
222
        false ->
223
            ok
×
224
    end,
225

226
    case add_sh_column(State, "vcard") of
×
227
        true ->
228
            drop_pkey(State, "vcard"),
×
229
            add_pkey(State, "vcard", ["server_host", "username"]),
×
230
            drop_sh_default(State, "vcard");
×
231
        false ->
232
            ok
×
233
    end,
234

235
    case add_sh_column(State, "vcard_search") of
×
236
        true ->
237
            drop_pkey(State, "vcard_search"),
×
238
            drop_index(State, "vcard_search", "i_vcard_search_lfn"),
×
239
            drop_index(State, "vcard_search", "i_vcard_search_lfamily"),
×
240
            drop_index(State, "vcard_search", "i_vcard_search_lgiven"),
×
241
            drop_index(State, "vcard_search", "i_vcard_search_lmiddle"),
×
242
            drop_index(State, "vcard_search", "i_vcard_search_lnickname"),
×
243
            drop_index(State, "vcard_search", "i_vcard_search_lbday"),
×
244
            drop_index(State, "vcard_search", "i_vcard_search_lctry"),
×
245
            drop_index(State, "vcard_search", "i_vcard_search_llocality"),
×
246
            drop_index(State, "vcard_search", "i_vcard_search_lemail"),
×
247
            drop_index(State, "vcard_search", "i_vcard_search_lorgname"),
×
248
            drop_index(State, "vcard_search", "i_vcard_search_lorgunit"),
×
249
            add_pkey(State, "vcard_search", ["server_host", "lusername"]),
×
250
            create_index(State, "vcard_search", "i_vcard_search_sh_lfn",       ["server_host", "lfn"]),
×
251
            create_index(State, "vcard_search", "i_vcard_search_sh_lfamily",   ["server_host", "lfamily"]),
×
252
            create_index(State, "vcard_search", "i_vcard_search_sh_lgiven",    ["server_host", "lgiven"]),
×
253
            create_index(State, "vcard_search", "i_vcard_search_sh_lmiddle",   ["server_host", "lmiddle"]),
×
254
            create_index(State, "vcard_search", "i_vcard_search_sh_lnickname", ["server_host", "lnickname"]),
×
255
            create_index(State, "vcard_search", "i_vcard_search_sh_lbday",     ["server_host", "lbday"]),
×
256
            create_index(State, "vcard_search", "i_vcard_search_sh_lctry",     ["server_host", "lctry"]),
×
257
            create_index(State, "vcard_search", "i_vcard_search_sh_llocality", ["server_host", "llocality"]),
×
258
            create_index(State, "vcard_search", "i_vcard_search_sh_lemail",    ["server_host", "lemail"]),
×
259
            create_index(State, "vcard_search", "i_vcard_search_sh_lorgname",  ["server_host", "lorgname"]),
×
260
            create_index(State, "vcard_search", "i_vcard_search_sh_lorgunit",  ["server_host", "lorgunit"]),
×
261
            drop_sh_default(State, "vcard_search");
×
262
        false ->
263
            ok
×
264
    end,
265

266
    case add_sh_column(State, "privacy_default_list") of
×
267
        true ->
268
            drop_pkey(State, "privacy_default_list"),
×
269
            add_pkey(State, "privacy_default_list", ["server_host", "username"]),
×
270
            drop_sh_default(State, "privacy_default_list");
×
271
        false ->
272
            ok
×
273
    end,
274

275
    case add_sh_column(State, "privacy_list") of
×
276
        true ->
277
            drop_index(State, "privacy_list", "i_privacy_list_username"),
×
278
            drop_index(State, "privacy_list", "i_privacy_list_username_name"),
×
279
            create_unique_index(State, "privacy_list", "i_privacy_list_sh_username_name", ["server_host", "username", "name"]),
×
280
            drop_sh_default(State, "privacy_list");
×
281
        false ->
282
            ok
×
283
    end,
284

285
    case add_sh_column(State, "private_storage") of
×
286
        true ->
287
            drop_index(State, "private_storage", "i_private_storage_username"),
×
288
            drop_index(State, "private_storage", "i_private_storage_username_namespace"),
×
289
            drop_pkey(State, "private_storage"),
×
290
            add_pkey(State, "private_storage", ["server_host", "username", "namespace"]),
×
291
            drop_sh_default(State, "private_storage");
×
292
        false ->
293
            ok
×
294
    end,
295

296
    case add_sh_column(State, "roster_version") of
×
297
        true ->
298
            drop_pkey(State, "roster_version"),
×
299
            add_pkey(State, "roster_version", ["server_host", "username"]),
×
300
            drop_sh_default(State, "roster_version");
×
301
        false ->
302
            ok
×
303
    end,
304

305
    case add_sh_column(State, "muc_room") of
×
306
        true ->
307
            drop_sh_default(State, "muc_room");
×
308
        false ->
309
            ok
×
310
    end,
311

312
    case add_sh_column(State, "muc_registered") of
×
313
        true ->
314
            drop_sh_default(State, "muc_registered");
×
315
        false ->
316
            ok
×
317
    end,
318

319
    case add_sh_column(State, "muc_online_room") of
×
320
        true ->
321
            drop_sh_default(State, "muc_online_room");
×
322
        false ->
323
            ok
×
324
    end,
325

326
    case add_sh_column(State, "muc_online_users") of
×
327
        true ->
328
            drop_sh_default(State, "muc_online_users");
×
329
        false ->
330
            ok
×
331
    end,
332

333
    case add_sh_column(State, "motd") of
×
334
        true ->
335
            drop_pkey(State, "motd"),
×
336
            add_pkey(State, "motd", ["server_host", "username"]),
×
337
            drop_sh_default(State, "motd");
×
338
        false ->
339
            ok
×
340
    end,
341

342
    case add_sh_column(State, "sm") of
×
343
        true ->
344
            drop_index(State, "sm", "i_sm_sid"),
×
345
            drop_index(State, "sm", "i_sm_username"),
×
346
            drop_pkey(State, "sm"),
×
347
            add_pkey(State, "sm", ["usec", "pid"]),
×
348
            create_index(State, "sm", "i_sm_sh_username", ["server_host", "username"]),
×
349
            drop_sh_default(State, "sm");
×
350
        false ->
351
            ok
×
352
    end,
353

354
    case add_sh_column(State, "push_session") of
×
355
        true ->
356
            drop_index(State, "push_session", "i_push_usn"),
×
357
            drop_index(State, "push_session", "i_push_ut"),
×
358
            create_unique_index(State, "push_session", "i_push_session_susn", ["server_host", "username", "service", "node"]),
×
359
            create_index(State, "push_session", "i_push_session_sh_username_timestamp", ["server_host", "username", "timestamp"]),
×
360
            drop_sh_default(State, "push_session");
×
361
        false ->
362
            ok
×
363
    end,
364

365
    case add_sh_column(State, "mix_pam") of
×
366
        true ->
367
            drop_index(State, "mix_pam", "i_mix_pam"),
×
368
            drop_index(State, "mix_pam", "i_mix_pam_u"),
×
369
            drop_index(State, "mix_pam", "i_mix_pam_us"),
×
370
            create_unique_index(State, "mix_pam", "i_mix_pam", ["username", "server_host", "channel", "service"]),
×
371
            drop_sh_default(State, "mix_pam");
×
372
        false ->
373
            ok
×
374
    end,
375

376
    case add_sh_column(State, "mqtt_pub") of
×
377
        true ->
378
            drop_index(State, "mqtt_pub", "i_mqtt_topic"),
×
379
            create_unique_index(State, "mqtt_pub", "i_mqtt_topic_server", ["topic", "server_host"]),
×
380
            drop_sh_default(State, "mqtt_pub");
×
381
        false ->
382
            ok
×
383
    end,
384

385
    ok.
×
386

387
check_sh_column(#state{dbtype = mysql} = State, Table) ->
388
    DB = ejabberd_option:sql_database(State#state.host),
×
389
    sql_query(
×
390
      State#state.host,
391
      ["SELECT 1 FROM information_schema.columns ",
392
       "WHERE table_name = '", Table, "' AND column_name = 'server_host' ",
393
       "AND table_schema = '", (State#state.escape)(DB), "' ",
394
       "GROUP BY table_name, column_name;"], false);
395
check_sh_column(State, Table) ->
396
    DB = ejabberd_option:sql_database(State#state.host),
×
397
    sql_query(
×
398
      State#state.host,
399
      ["SELECT 1 FROM information_schema.columns ",
400
       "WHERE table_name = '", Table, "' AND column_name = 'server_host' ",
401
       "AND table_catalog = '", (State#state.escape)(DB), "' ",
402
       "GROUP BY table_name, column_name;"], false).
403

404
add_sh_column(State, Table) ->
405
    case check_sh_column(State, Table) of
×
406
        true -> false;
×
407
        false ->
408
            do_add_sh_column(State, Table),
×
409
            true
×
410
    end.
411

412
do_add_sh_column(#state{dbtype = pgsql} = State, Table) ->
413
    sql_query(
×
414
      State#state.host,
415
      ["ALTER TABLE ", Table, " ADD COLUMN server_host text NOT NULL DEFAULT '",
416
       (State#state.escape)(State#state.host),
417
       "';"]);
418
do_add_sh_column(#state{dbtype = mssql} = State, Table) ->
419
    sql_query(
×
420
      State#state.host,
421
      ["ALTER TABLE [", Table, "] ADD [server_host] varchar (250) NOT NULL ",
422
       "CONSTRAINT [server_host_default] DEFAULT '",
423
       (State#state.escape)(State#state.host),
424
       "';"]);
425
do_add_sh_column(#state{dbtype = mysql} = State, Table) ->
426
    sql_query(
×
427
      State#state.host,
428
      ["ALTER TABLE ", Table, " ADD COLUMN server_host varchar(191) NOT NULL DEFAULT '",
429
       (State#state.escape)(State#state.host),
430
       "';"]).
431

432
drop_pkey(#state{dbtype = pgsql} = State, Table) ->
433
    sql_query(
×
434
      State#state.host,
435
      ["ALTER TABLE ", Table, " DROP CONSTRAINT ", Table, "_pkey;"]);
436
drop_pkey(#state{dbtype = mssql} = State, Table) ->
437
    sql_query(
×
438
      State#state.host,
439
      ["ALTER TABLE [", Table, "] DROP CONSTRAINT [", Table, "_PRIMARY];"]);
440
drop_pkey(#state{dbtype = mysql} = State, Table) ->
441
    sql_query(
×
442
      State#state.host,
443
      ["ALTER TABLE ", Table, " DROP PRIMARY KEY;"]).
444

445
add_pkey(#state{dbtype = pgsql} = State, Table, Cols) ->
446
    Cols2 = lists:map(fun("type") -> "\"type\""; (V) -> V end, Cols),
×
447
    SCols = string:join(Cols2, ", "),
×
448
    sql_query(
×
449
      State#state.host,
450
      ["ALTER TABLE ", Table, " ADD PRIMARY KEY (", SCols, ");"]);
451
add_pkey(#state{dbtype = mssql} = State, Table, Cols) ->
452
    SCols = string:join(Cols, "], ["),
×
453
    sql_query(
×
454
      State#state.host,
455
      ["ALTER TABLE [", Table, "] ADD CONSTRAINT [", Table, "_PRIMARY] PRIMARY KEY CLUSTERED ([", SCols, "]) ",
456
       "WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ",
457
       "ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY];"]);
458
add_pkey(#state{dbtype = mysql} = State, Table, Cols) ->
459
    Cols2 = [C ++ mysql_keylen(Table, C) || C <- Cols],
×
460
    SCols = string:join(Cols2, ", "),
×
461
    sql_query(
×
462
      State#state.host,
463
      ["ALTER TABLE ", Table, " ADD PRIMARY KEY (", SCols, ");"]).
464

465
drop_sh_default(#state{dbtype = pgsql} = State, Table) ->
466
    sql_query(
×
467
      State#state.host,
468
      ["ALTER TABLE ", Table, " ALTER COLUMN server_host DROP DEFAULT;"]);
469
drop_sh_default(#state{dbtype = mssql} = State, Table) ->
470
    sql_query(
×
471
      State#state.host,
472
      ["ALTER TABLE [", Table, "] DROP CONSTRAINT [server_host_default];"]);
473
drop_sh_default(#state{dbtype = mysql} = State, Table) ->
474
    sql_query(
×
475
      State#state.host,
476
      ["ALTER TABLE ", Table, " ALTER COLUMN server_host DROP DEFAULT;"]).
477

478
check_index(#state{dbtype = pgsql} = State, Table, Index) ->
479
    sql_query(
×
480
      State#state.host,
481
      ["SELECT 1 FROM pg_indexes WHERE tablename = '", Table,
482
       "' AND indexname = '", Index, "';"], false);
483
check_index(#state{dbtype = mssql} = State, Table, Index) ->
484
    sql_query(
×
485
      State#state.host,
486
      ["SELECT 1 FROM sys.tables t ",
487
       "INNER JOIN sys.indexes i ON i.object_id = t.object_id ",
488
       "WHERE i.index_id > 0 ",
489
       "AND i.name = '", Index, "' ",
490
       "AND t.name = '", Table, "';"], false);
491
check_index(#state{dbtype = mysql} = State, Table, Index) ->
492
    DB = ejabberd_option:sql_database(State#state.host),
×
493
    sql_query(
×
494
      State#state.host,
495
      ["SELECT 1 FROM information_schema.statistics ",
496
       "WHERE table_name = '", Table, "' AND index_name = '", Index, "' ",
497
       "AND table_schema = '", (State#state.escape)(DB), "' ",
498
       "GROUP BY table_name, index_name;"], false).
499

500
drop_index(State, Table, Index) ->
501
    OldIndex = old_index_name(State#state.dbtype, Index),
×
502
    case check_index(State, Table, OldIndex) of
×
503
        true -> do_drop_index(State, Table, OldIndex);
×
504
        false -> ok
×
505
    end.
506

507
do_drop_index(#state{dbtype = pgsql} = State, _Table, Index) ->
508
    sql_query(
×
509
      State#state.host,
510
      ["DROP INDEX ", Index, ";"]);
511
do_drop_index(#state{dbtype = mssql} = State, Table, Index) ->
512
    sql_query(
×
513
      State#state.host,
514
      ["DROP INDEX [", Index, "] ON [", Table, "];"]);
515
do_drop_index(#state{dbtype = mysql} = State, Table, Index) ->
516
    sql_query(
×
517
      State#state.host,
518
      ["ALTER TABLE ", Table, " DROP INDEX ", Index, ";"]).
519

520
create_unique_index(#state{dbtype = pgsql} = State, Table, Index, Cols) ->
521
    SCols = string:join(Cols, ", "),
×
522
    sql_query(
×
523
      State#state.host,
524
      ["CREATE UNIQUE INDEX ", Index, " ON ", Table, " USING btree (",
525
       SCols, ");"]);
526
create_unique_index(#state{dbtype = mssql} = State, Table, "i_privacy_list_sh_username_name" = Index, Cols) ->
527
    create_index(State, Table, Index, Cols);
×
528
create_unique_index(#state{dbtype = mssql} = State, Table, Index, Cols) ->
529
    SCols = string:join(Cols, ", "),
×
530
    sql_query(
×
531
      State#state.host,
532
      ["CREATE UNIQUE ", mssql_clustered(Index), "INDEX [", new_index_name(State#state.dbtype, Index), "] ",
533
       "ON [", Table, "] (", SCols, ") ",
534
       "WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);"]);
535
create_unique_index(#state{dbtype = mysql} = State, Table, Index, Cols) ->
536
    Cols2 = [C ++ mysql_keylen(Index, C) || C <- Cols],
×
537
    SCols = string:join(Cols2, ", "),
×
538
    sql_query(
×
539
      State#state.host,
540
      ["CREATE UNIQUE INDEX ", Index, " ON ", Table, "(",
541
       SCols, ");"]).
542

543
create_index(#state{dbtype = pgsql} = State, Table, Index, Cols) ->
544
    NewIndex = new_index_name(State#state.dbtype, Index),
×
545
    SCols = string:join(Cols, ", "),
×
546
    sql_query(
×
547
      State#state.host,
548
      ["CREATE INDEX ", NewIndex, " ON ", Table, " USING btree (",
549
       SCols, ");"]);
550
create_index(#state{dbtype = mssql} = State, Table, Index, Cols) ->
551
    NewIndex = new_index_name(State#state.dbtype, Index),
×
552
    SCols = string:join(Cols, ", "),
×
553
    sql_query(
×
554
      State#state.host,
555
      ["CREATE INDEX [", NewIndex, "] ON [", Table, "] (", SCols, ") ",
556
       "WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);"]);
557
create_index(#state{dbtype = mysql} = State, Table, Index, Cols) ->
558
    NewIndex = new_index_name(State#state.dbtype, Index),
×
559
    Cols2 = [C ++ mysql_keylen(NewIndex, C) || C <- Cols],
×
560
    SCols = string:join(Cols2, ", "),
×
561
    sql_query(
×
562
      State#state.host,
563
      ["CREATE INDEX ", NewIndex, " ON ", Table, "(",
564
       SCols, ");"]).
565

566
old_index_name(mssql, "i_bare_peer") -> "archive_bare_peer";
×
567
old_index_name(mssql, "i_peer") -> "archive_peer";
×
568
old_index_name(mssql, "i_timestamp") -> "archive_timestamp";
×
569
old_index_name(mssql, "i_username") -> "archive_username";
×
570
old_index_name(mssql, "i_username_bare_peer") -> "archive_username_bare_peer";
×
571
old_index_name(mssql, "i_username_peer") -> "archive_username_peer";
×
572
old_index_name(mssql, "i_username_timestamp") -> "archive_username_timestamp";
×
573
old_index_name(mssql, "i_push_usn") -> "i_push_usn";
×
574
old_index_name(mssql, "i_push_ut") -> "i_push_ut";
×
575
old_index_name(mssql, "pk_rosterg_user_jid") -> "rostergroups_username_jid";
×
576
old_index_name(mssql, "i_rosteru_jid") -> "rosterusers_jid";
×
577
old_index_name(mssql, "i_rosteru_username") -> "rosterusers_username";
×
578
old_index_name(mssql, "i_rosteru_user_jid") -> "rosterusers_username_jid";
×
579
old_index_name(mssql, "i_despool") -> "spool_username";
×
580
old_index_name(mssql, "i_sr_user_jid_grp") -> "sr_user_jid_group";
×
581
old_index_name(mssql, Index) -> string:substr(Index, 3);
×
582
old_index_name(_Type, Index) -> Index.
×
583

584
new_index_name(mssql, "i_rosterg_sh_user_jid") -> "rostergroups_sh_username_jid";
×
585
new_index_name(mssql, "i_rosteru_sh_jid") -> "rosterusers_sh_jid";
×
586
new_index_name(mssql, "i_rosteru_sh_user_jid") -> "rosterusers_sh_username_jid";
×
587
new_index_name(mssql, "i_sr_user_sh_jid_grp") -> "sr_user_sh_jid_group";
×
588
new_index_name(mssql, Index) -> string:substr(Index, 3);
×
589
new_index_name(_Type, Index) -> Index.
×
590

591
mssql_clustered("i_mix_pam") -> "";
×
592
mssql_clustered("i_push_session_susn") -> "";
×
593
mssql_clustered(_) -> "CLUSTERED ".
×
594

595
mysql_keylen(_, "bare_peer") -> "(191)";
×
596
mysql_keylen(_, "channel") -> "(191)";
×
597
mysql_keylen(_, "domain") -> "(75)";
×
598
mysql_keylen(_, "grp") -> "(191)"; %% in mysql*.sql this is text, not varchar(191)
×
599
mysql_keylen(_, "jid") -> "(75)";
×
600
mysql_keylen(_, "lbday") -> "(191)";
×
601
mysql_keylen(_, "lctry") -> "(191)";
×
602
mysql_keylen(_, "lemail") -> "(191)";
×
603
mysql_keylen(_, "lfamily") -> "(191)";
×
604
mysql_keylen(_, "lfn") -> "(191)";
×
605
mysql_keylen(_, "lgiven") -> "(191)";
×
606
mysql_keylen(_, "llocality") -> "(191)";
×
607
mysql_keylen(_, "lmiddle") -> "(191)";
×
608
mysql_keylen(_, "lnickname") -> "(191)";
×
609
mysql_keylen(_, "lorgname") -> "(191)";
×
610
mysql_keylen(_, "lorgunit") -> "(191)";
×
611
mysql_keylen(_, "lusername") -> "(191)";
×
612
mysql_keylen(_, "name") -> "(75)";
×
613
mysql_keylen(_, "namespace") -> "(191)";
×
614
mysql_keylen(_, "node") -> "(75)";
×
615
mysql_keylen(_, "peer") -> "(191)";
×
616
mysql_keylen(_, "pid") -> "(75)";
×
617
mysql_keylen(_, "server_host") -> "(191)";
×
618
mysql_keylen(_, "service") -> "(191)";
×
619
mysql_keylen(_, "topic") -> "(191)";
×
620
mysql_keylen("i_privacy_list_sh_username_name", "username") -> "(75)";
×
621
mysql_keylen("i_rosterg_sh_user_jid", "username") -> "(75)";
×
622
mysql_keylen("i_rosteru_sh_user_jid", "username") -> "(75)";
×
623
mysql_keylen(_, "username") -> "(191)";
×
624
mysql_keylen(_, _) -> "".
×
625

626
sql_query(Host, Query) ->
627
    sql_query(Host, Query, true).
×
628

629
sql_query(Host, Query, Log) ->
630
    case Log of
×
631
        true -> io:format("executing \"~ts\" on ~ts~n", [Query, Host]);
×
632
        false -> ok
×
633
    end,
634
    case ejabberd_sql:sql_query(Host, Query) of
×
635
        {selected, _Cols, []} ->
636
            false;
×
637
        {selected, _Cols, [_Rows]} ->
638
            true;
×
639
        {error, Error} ->
640
            io:format("error: ~p~n", [Error]),
×
641
            false;
×
642
        _ ->
643
            ok
×
644
    end.
645

646
mod_options(_) -> [].
9✔
647

648
mod_doc() ->
649
    #{desc =>
×
650
          ?T("This module can be used to update existing SQL database "
651
             "from the default to the new schema. Check the section "
652
             "_`database.md#default-and-new-schemas|Default and New Schemas`_ for details. "
653
             "Please note that only MS SQL, MySQL, and PostgreSQL are supported. "
654
             "When the module is loaded use _`update_sql`_ API.")}.
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