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

processone / ejabberd / 1212

18 Nov 2025 12:37PM UTC coverage: 33.784% (+0.003%) from 33.781%
1212

push

github

badlop
mod_conversejs: Improve link to conversejs in WebAdmin (#4495)

Until now, the WebAdmin menu included a link to the first request handler
with mod_conversejs that the admin configured in ejabberd.yml
That link included the authentication credentials hashed as URI arguments
if using HTTPS. Then process/2 extracted those arguments and passed them
as autologin options to Converse.

From now, mod_conversejs automatically adds a request_handler nested in
webadmin subpath. The webadmin menu links to that converse URI; this allows
to access the HTTP auth credentials, no need to explicitly pass them.
process/2 extracts this HTTP auth and passes autologin options to Converse.
Now scram password storage is supported too.

This minimum configuration allows WebAdmin to access Converse:

listen:
  -
    port: 5443
    module: ejabberd_http
    tls: true
    request_handlers:
      /admin: ejabberd_web_admin
      /ws: ejabberd_http_ws
modules:
  mod_conversejs:
    conversejs_resources: "/home/conversejs/12.0.0/dist"

0 of 12 new or added lines in 1 file covered. (0.0%)

11290 existing lines in 174 files now uncovered.

15515 of 45924 relevant lines covered (33.78%)

1277.8 hits per line

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

43.53
/src/ejabberd_sql_schema.erl
1
%%%----------------------------------------------------------------------
2
%%% File    : ejabberd_sql.erl
3
%%% Author  : Alexey Shchepin <alexey@process-one.net>
4
%%% Purpose : SQL schema versioning
5
%%% Created : 15 Aug 2023 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(ejabberd_sql_schema).
27

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

30
-export([start/1, update_schema/3,
31
         get_table_schema/2, get_table_indices/2, print_schema/3,
32
         test/0]).
33

34
-include("logger.hrl").
35
-include("ejabberd_sql_pt.hrl").
36
-include("ejabberd_ctl.hrl").
37

38
start(Host) ->
UNCOV
39
    case should_update_schema(Host) of
7✔
40
        true ->
UNCOV
41
            case table_exists(Host, <<"schema_version">>) of
7✔
42
                true ->
43
                    ok;
×
44
                false ->
UNCOV
45
                    SchemaInfo =
7✔
46
                        ejabberd_sql:sql_query(
47
                          Host,
48
                          fun(DBType, DBVersion) ->
UNCOV
49
                                  #sql_schema_info{
7✔
50
                                     db_type = DBType,
51
                                     db_version = DBVersion,
52
                                     multihost_schema = ejabberd_sql:use_multihost_schema()}
53
                          end),
UNCOV
54
                    Table = filter_table_sh(SchemaInfo, schema_table()),
7✔
UNCOV
55
                    Res = create_table(Host, SchemaInfo, Table),
7✔
UNCOV
56
                    case Res of
7✔
57
                        {error, Error} ->
58
                            ?ERROR_MSG("Failed to create table ~s: ~p",
×
59
                                       [Table#sql_table.name, Error]),
×
60
                            {error, Error};
×
61
                        _ ->
UNCOV
62
                            ok
7✔
63
                    end
64
            end;
65
        false ->
66
            ok
×
67
    end.
68

69
schema_table() ->
UNCOV
70
    #sql_table{
7✔
71
       name = <<"schema_version">>,
72
       columns = [#sql_column{name = <<"module">>, type = text},
73
                  #sql_column{name = <<"version">>, type = bigint}],
74
       indices = [#sql_index{
75
                     columns = [<<"module">>],
76
                     unique = true}]}.
77

78
get_table_schema(Host, Table) ->
UNCOV
79
    ejabberd_sql:sql_query(
96✔
80
      Host,
81
      fun(pgsql, _) ->
82
              case
×
83
                  ejabberd_sql:sql_query_t(
84
                    ?SQL("select "
×
85
                         "  @(a.attname)s, "
86
                         "  @(pg_catalog.format_type(a.atttypid, a.atttypmod))s "
87
                         "  from "
88
                         "    pg_class t, "
89
                         "    pg_attribute a "
90
                         "  where "
91
                         "    a.attrelid = t.oid and "
92
                         "    a.attnum > 0 and "
93
                         "    a.atttypid > 0 and "
94
                         "    t.relkind = 'r' and "
95
                         "    t.relname=%(Table)s"))
96
              of
97
                  {selected, Cols} ->
98
                      [{Col, string_to_type(SType)} || {Col, SType} <- Cols]
×
99
              end;
100
         (sqlite, _) ->
UNCOV
101
              case
96✔
102
                  ejabberd_sql:sql_query_t(
UNCOV
103
                    ?SQL("select @(i.name)s, @(i.type)s"
96✔
104
                         "  from pragma_table_info(%(Table)s) as i"))
105
              of
106
                  {selected, Cols} ->
UNCOV
107
                      [{Col, string_to_type(SType)} || {Col, SType} <- Cols]
96✔
108
              end;
109
         (mysql, _) ->
110
              case
×
111
                  ejabberd_sql:sql_query_t(
112
                    ?SQL("select @(column_name)s, @(column_type)s"
×
113
                         "  from information_schema.columns"
114
                         "  where table_name=%(Table)s and"
115
                         "        table_schema=schema()"
116
                         "  order by ordinal_position"))
117
              of
118
                  {selected, Cols} ->
119
                      [{Col, string_to_type(SType)} || {Col, SType} <- Cols]
×
120
              end
121
      end).
122

123
get_table_indices(Host, Table) ->
124
    ejabberd_sql:sql_query(
×
125
      Host,
126
      fun(pgsql, _) ->
127
              case
×
128
                  ejabberd_sql:sql_query_t(
129
                    ?SQL("select "
×
130
                         "  @(i.relname)s, "
131
                         "  @(a.attname)s "
132
                         "  from "
133
                         "    pg_class t, "
134
                         "    pg_class i, "
135
                         "    pg_index ix, "
136
                         "    pg_attribute a "
137
                         "  where "
138
                         "    t.oid = ix.indrelid and "
139
                         "    i.oid = ix.indexrelid and "
140
                         "    a.attrelid = t.oid and "
141
                         "    a.attnum = ANY(ix.indkey) and "
142
                         "    t.relkind = 'r' and "
143
                         "    t.relname=%(Table)s "
144
                         "  order by "
145
                         "    i.relname, "
146
                         "    array_position(ix.indkey, a.attnum)"))
147
              of
148
                  {selected, Cols} ->
149
                      Indices =
×
150
                          lists:foldr(
151
                            fun({IdxName, ColName}, Acc) ->
152
                                    maps:update_with(
×
153
                                      IdxName,
154
                                      fun(Cs) -> [ColName | Cs] end,
×
155
                                      [ColName],
156
                                      Acc)
157
                            end, #{}, Cols),
158
                      maps:to_list(Indices)
×
159
              end;
160
         (sqlite, _) ->
161
              case
×
162
                  ejabberd_sql:sql_query_t(
163
                    ?SQL("select @(i.name)s, @(c.name)s "
×
164
                         "  from pragma_index_list(%(Table)s) as i,"
165
                         "       pragma_index_xinfo(i.name) as c"
166
                         "  where c.cid >= 0"
167
                         "  order by i.name, c.seqno"))
168
              of
169
                  {selected, Cols} ->
170
                      Indices =
×
171
                          lists:foldr(
172
                            fun({IdxName, ColName}, Acc) ->
173
                                    maps:update_with(
×
174
                                      IdxName,
175
                                      fun(Cs) -> [ColName | Cs] end,
×
176
                                      [ColName],
177
                                      Acc)
178
                            end, #{}, Cols),
179
                      maps:to_list(Indices)
×
180
              end;
181
         (mysql, _) ->
182
              case
×
183
                  ejabberd_sql:sql_query_t(
184
                    ?SQL("select @(index_name)s, @(column_name)s"
×
185
                         "  from information_schema.statistics"
186
                         "  where table_name=%(Table)s and"
187
                         "        table_schema=schema()"
188
                         "  order by index_name, seq_in_index"))
189
              of
190
                  {selected, Cols} ->
191
                      Indices =
×
192
                          lists:foldr(
193
                            fun({IdxName, ColName}, Acc) ->
194
                                    maps:update_with(
×
195
                                      IdxName,
196
                                      fun(Cs) -> [ColName | Cs] end,
×
197
                                      [ColName],
198
                                      Acc)
199
                            end, #{}, Cols),
200
                      maps:to_list(Indices)
×
201
              end
202
      end).
203

204
find_index_name(Host, Table, Columns) ->
205
    Indices = get_table_indices(Host, Table),
×
206
    case lists:keyfind(Columns, 2, Indices) of
×
207
        false ->
208
            false;
×
209
        {Name, _} ->
210
            {ok, Name}
×
211
    end.
212

213
get_version(Host, Module) ->
UNCOV
214
    SModule = misc:atom_to_binary(Module),
121✔
UNCOV
215
    ejabberd_sql:sql_query(
121✔
216
      Host,
UNCOV
217
      ?SQL("select @(version)d"
163✔
218
           " from schema_version"
219
           " where module=%(SModule)s")).
220

221
store_version(Host, Module, Version) ->
UNCOV
222
    SModule = misc:atom_to_binary(Module),
114✔
UNCOV
223
    ?SQL_UPSERT(
114✔
UNCOV
224
       Host,
114✔
225
       "schema_version",
226
       ["!module=%(SModule)s",
227
        "version=%(Version)d"]).
228

229
store_version_t(Module, Version) ->
230
    SModule = misc:atom_to_binary(Module),
×
231
    ?SQL_UPSERT_T(
×
232
        "schema_version",
233
        ["!module=%(SModule)s",
234
         "version=%(Version)d"]).
235

236
table_exists(Host, Table) ->
UNCOV
237
    ejabberd_sql:sql_query(
193✔
238
      Host,
239
      fun(pgsql, _) ->
UNCOV
240
              case
74✔
241
                  ejabberd_sql:sql_query_t(
UNCOV
242
                    ?SQL("select @()b exists (select * from pg_tables "
76✔
243
                         "  where tablename=%(Table)s)"))
244
              of
245
                  {selected, [{Res}]} ->
UNCOV
246
                      Res
74✔
247
              end;
248
         (sqlite, _) ->
UNCOV
249
              case
45✔
250
                  ejabberd_sql:sql_query_t(
UNCOV
251
                    ?SQL("select @()b exists"
45✔
252
                         " (select 0 from pragma_table_info(%(Table)s))"))
253
              of
254
                  {selected, [{Res}]} ->
UNCOV
255
                      Res
45✔
256
              end;
257
         (mysql, _) ->
UNCOV
258
              case
74✔
259
                  ejabberd_sql:sql_query_t(
UNCOV
260
                    ?SQL("select @()b exists"
150✔
261
                         " (select 0 from information_schema.tables"
262
                         "  where table_name=%(Table)s and"
263
                         "        table_schema=schema())"))
264
              of
265
                  {selected, [{Res}]} ->
UNCOV
266
                      Res
74✔
267
              end
268
      end).
269

270
filter_table_sh(SchemaInfo, Table) ->
UNCOV
271
    case {SchemaInfo#sql_schema_info.multihost_schema, Table#sql_table.name} of
294✔
272
        {true, _} ->
UNCOV
273
            Table;
128✔
274
        {_, <<"route">>} ->
275
            Table;
×
276
        {false, _} ->
UNCOV
277
            Table#sql_table{
166✔
278
              columns =
279
                  lists:keydelete(
280
                    <<"server_host">>, #sql_column.name, Table#sql_table.columns),
281
              indices =
282
                  lists:map(
283
                    fun(Idx) ->
UNCOV
284
                            Idx#sql_index{
320✔
285
                              columns =
286
                                  lists:delete(
287
                                    <<"server_host">>, Idx#sql_index.columns)
288
                             }
289
                    end, Table#sql_table.indices)
290
             }
291
    end.
292

293
string_to_type(SType) ->
UNCOV
294
    case string:lowercase(SType) of
569✔
UNCOV
295
        <<"text">> -> text;
419✔
296
        <<"mediumtext">> -> text;
×
UNCOV
297
        <<"bigint">> -> bigint;
21✔
UNCOV
298
        <<"bigint ", _/binary>> -> bigint;
6✔
299
        <<"bigint(", _/binary>> -> bigint;
×
UNCOV
300
        <<"integer">> -> integer;
24✔
301
        <<"int">> -> integer;
×
302
        <<"int(", _/binary>> -> integer;
×
303
        <<"int ", _/binary>> -> integer;
×
UNCOV
304
        <<"smallint">> -> smallint;
6✔
305
        <<"smallint(", _/binary>> -> smallint;
×
UNCOV
306
        <<"numeric">> -> numeric;
3✔
307
        <<"decimal", _/binary>> -> numeric;
×
308
        <<"bigserial">> -> bigserial;
×
UNCOV
309
        <<"boolean">> -> boolean;
15✔
310
        <<"tinyint(1)">> -> boolean;
×
311
        <<"tinyint", _/binary>> -> smallint;
×
312
        <<"bytea">> -> blob;
×
313
        <<"blob">> -> blob;
×
UNCOV
314
        <<"timestamp", _/binary>> -> timestamp;
48✔
315
        <<"character(", R/binary>> ->
UNCOV
316
            {ok, [N], []} = io_lib:fread("~d)", binary_to_list(R)),
18✔
UNCOV
317
            {char, N};
18✔
318
        <<"char(", R/binary>> ->
319
            {ok, [N], []} = io_lib:fread("~d)", binary_to_list(R)),
×
320
            {char, N};
×
UNCOV
321
        <<"varchar(", _/binary>> -> text;
9✔
322
        <<"character varying(", _/binary>> -> text;
×
323
        T ->
324
            ?ERROR_MSG("Unknown SQL type '~s'", [T]),
×
325
            {undefined, T}
×
326
    end.
327

328
check_columns_compatibility(RequiredColumns, Columns) ->
UNCOV
329
    lists:all(
96✔
330
      fun(#sql_column{name = Name, type = Type}) ->
331
              %io:format("col ~p~n", [{Name, Type}]),
UNCOV
332
              case lists:keyfind(Name, 1, Columns) of
563✔
333
                  false ->
334
                      false;
×
335
                  {_, Type2} ->
336
                      %io:format("tt ~p~n", [{Type, Type2}]),
UNCOV
337
                      case {Type, Type2} of
563✔
UNCOV
338
                          {T, T} -> true;
488✔
339
                          {text, blob} -> true;
×
340
                          {{text, _}, blob} -> true;
×
UNCOV
341
                          {{text, _}, text} -> true;
57✔
342
                          {{text, _}, {varchar, _}} -> true;
×
343
                          {text, {varchar, _}} -> true;
×
344
                          {{char, _}, text} -> true;
×
345
                          {{varchar, _}, text} -> true;
×
346
                          {smallint, integer} -> true;
×
347
                          {smallint, bigint} -> true;
×
348
                          {smallint, numeric} -> true;
×
349
                          {integer, bigint} -> true;
×
350
                          {integer, numeric} -> true;
×
351
                          {bigint, numeric} -> true;
×
352
                          %% a workaround for MySQL definition of mqtt_pub
353
                          {bigint, integer} -> true;
×
UNCOV
354
                          {bigserial, integer} -> true;
18✔
355
                          {bigserial, bigint} -> true;
×
356
                          {bigserial, numeric} -> true;
×
357
                          _ -> false
×
358
                      end
359
              end
360
      end, RequiredColumns).
361

362
guess_version(Host, Schemas) ->
UNCOV
363
    LastSchema = lists:max(Schemas),
114✔
UNCOV
364
    SomeTablesExist =
114✔
365
        lists:any(
366
          fun(Table) ->
UNCOV
367
                  table_exists(Host, Table#sql_table.name)
186✔
368
          end, LastSchema#sql_schema.tables),
UNCOV
369
    if
114✔
370
        SomeTablesExist ->
UNCOV
371
            CompatibleSchemas =
42✔
372
                lists:filter(
373
                  fun(Schema) ->
UNCOV
374
                          lists:all(
48✔
375
                            fun(Table) ->
UNCOV
376
                                    CurrentColumns =
96✔
377
                                        get_table_schema(
378
                                          Host, Table#sql_table.name),
UNCOV
379
                                    check_columns_compatibility(
96✔
380
                                      Table#sql_table.columns,
381
                                      CurrentColumns)
382
                            end, Schema#sql_schema.tables)
383
                  end, Schemas),
UNCOV
384
            case CompatibleSchemas of
42✔
385
                [] -> -1;
×
386
                _ ->
UNCOV
387
                    (lists:max(CompatibleSchemas))#sql_schema.version
42✔
388
            end;
389
        true ->
UNCOV
390
            0
72✔
391
    end.
392

393
get_current_version(Host, Module, Schemas) ->
UNCOV
394
    case get_version(Host, Module) of
121✔
395
        {selected, [{Version}]} ->
UNCOV
396
            Version;
7✔
397
        {selected, []} ->
UNCOV
398
            Version = guess_version(Host, Schemas),
114✔
UNCOV
399
            if
114✔
400
                Version > 0 ->
UNCOV
401
                    store_version(Host, Module, Version);
42✔
402
                true ->
UNCOV
403
                    ok
72✔
404
            end,
UNCOV
405
            Version
114✔
406
    end.
407

408
sqlite_table_copy_t(SchemaInfo, Table) ->
409
    TableName = Table#sql_table.name,
×
410
    NewTableName = <<"multihost_", TableName/binary>>,
×
411
    NewTable = Table#sql_table{name = NewTableName},
×
412
    create_table_t(SchemaInfo, NewTable),
×
413
    Columns = lists:join(<<",">>,
×
414
                         lists:map(fun(C) -> escape_name(SchemaInfo, C#sql_column.name) end,
×
415
                                   Table#sql_table.columns)),
416
    SQL2 = [<<"INSERT INTO ">>, NewTableName,
×
417
            <<" SELECT ">>, Columns, <<" FROM ">>, TableName],
418
    ?INFO_MSG("Copying table ~s to ~s:~n~s~n",
×
419
              [TableName, NewTableName, SQL2]),
×
420
    ejabberd_sql:sql_query_t(SQL2),
×
421
    SQL3 = <<"DROP TABLE ", TableName/binary>>,
×
422
    ?INFO_MSG("Droping old table ~s:~n~s~n",
×
423
              [TableName, SQL3]),
×
424
    ejabberd_sql:sql_query_t(SQL3),
×
425
    SQL4 = <<"ALTER TABLE ", NewTableName/binary,
×
426
             " RENAME TO ", TableName/binary>>,
427
    ?INFO_MSG("Renaming table ~s to ~s:~n~s~n",
×
428
              [NewTableName, TableName, SQL4]),
×
429
    ejabberd_sql:sql_query_t(SQL4).
×
430

431
format_type(#sql_schema_info{db_type = pgsql}, Column) ->
UNCOV
432
    case Column#sql_column.type of
434✔
UNCOV
433
        text -> <<"text">>;
296✔
UNCOV
434
        {text, _} -> <<"text">>;
32✔
UNCOV
435
        bigint -> <<"bigint">>;
20✔
UNCOV
436
        integer -> <<"integer">>;
2✔
UNCOV
437
        smallint -> <<"smallint">>;
6✔
UNCOV
438
        numeric -> <<"numeric">>;
2✔
UNCOV
439
        boolean -> <<"boolean">>;
12✔
UNCOV
440
        blob -> <<"bytea">>;
6✔
UNCOV
441
        timestamp -> <<"timestamp">>;
36✔
UNCOV
442
        {char, N} -> [<<"character(">>, integer_to_binary(N), <<")">>];
12✔
UNCOV
443
        bigserial -> <<"bigserial">>
10✔
444
    end;
445
format_type(#sql_schema_info{db_type = sqlite}, Column) ->
UNCOV
446
    case Column#sql_column.type of
6✔
UNCOV
447
        text -> <<"text">>;
3✔
448
        {text, _} -> <<"text">>;
×
UNCOV
449
        bigint -> <<"bigint">>;
3✔
450
        integer -> <<"integer">>;
×
451
        smallint -> <<"smallint">>;
×
452
        numeric -> <<"numeric">>;
×
453
        boolean -> <<"boolean">>;
×
454
        blob -> <<"blob">>;
×
455
        timestamp -> <<"timestamp">>;
×
456
        {char, N} -> [<<"character(">>, integer_to_binary(N), <<")">>];
×
457
        bigserial -> <<"integer primary key autoincrement">>
×
458
    end;
459
format_type(#sql_schema_info{db_type = mysql}, Column) ->
UNCOV
460
    case Column#sql_column.type of
434✔
UNCOV
461
        text -> <<"text">>;
296✔
UNCOV
462
        {text, big} -> <<"mediumtext">>;
12✔
463
        {text, N} when is_integer(N), N < 191 ->
UNCOV
464
            [<<"varchar(">>, integer_to_binary(N), <<")">>];
20✔
465
        {text, _} -> <<"text">>;
×
UNCOV
466
        bigint -> <<"bigint">>;
20✔
UNCOV
467
        integer -> <<"integer">>;
2✔
UNCOV
468
        smallint -> <<"smallint">>;
6✔
UNCOV
469
        numeric -> <<"numeric">>;
2✔
UNCOV
470
        boolean -> <<"boolean">>;
12✔
UNCOV
471
        blob -> <<"blob">>;
6✔
UNCOV
472
        timestamp -> <<"timestamp">>;
36✔
UNCOV
473
        {char, N} -> [<<"character(">>, integer_to_binary(N), <<")">>];
12✔
UNCOV
474
        bigserial -> <<"bigint auto_increment primary key">>
10✔
475
    end.
476

477
format_default(#sql_schema_info{db_type = pgsql}, Column) ->
UNCOV
478
    case Column#sql_column.type of
48✔
UNCOV
479
        text -> <<"''">>;
4✔
UNCOV
480
        {text, _} -> <<"''">>;
6✔
481
        bigint -> <<"0">>;
×
UNCOV
482
        integer -> <<"0">>;
2✔
483
        smallint -> <<"0">>;
×
484
        numeric -> <<"0">>;
×
485
        boolean -> <<"false">>;
×
486
        blob -> <<"''">>;
×
UNCOV
487
        timestamp -> <<"now()">>
36✔
488
        %{char, N} -> <<"''">>;
489
        %bigserial -> <<"0">>
490
    end;
491
format_default(#sql_schema_info{db_type = sqlite}, Column) ->
492
    case Column#sql_column.type of
×
493
        text -> <<"''">>;
×
494
        {text, _} -> <<"''">>;
×
495
        bigint -> <<"0">>;
×
496
        integer -> <<"0">>;
×
497
        smallint -> <<"0">>;
×
498
        numeric -> <<"0">>;
×
499
        boolean -> <<"false">>;
×
500
        blob -> <<"''">>;
×
501
        timestamp -> <<"CURRENT_TIMESTAMP">>
×
502
        %{char, N} -> <<"''">>;
503
        %bigserial -> <<"0">>
504
    end;
505
format_default(#sql_schema_info{db_type = mysql}, Column) ->
UNCOV
506
    case Column#sql_column.type of
48✔
UNCOV
507
        text -> <<"('')">>;
4✔
UNCOV
508
        {text, _} -> <<"('')">>;
6✔
509
        bigint -> <<"0">>;
×
UNCOV
510
        integer -> <<"0">>;
2✔
511
        smallint -> <<"0">>;
×
512
        numeric -> <<"0">>;
×
513
        boolean -> <<"false">>;
×
514
        blob -> <<"('')">>;
×
UNCOV
515
        timestamp -> <<"CURRENT_TIMESTAMP">>
36✔
516
        %{char, N} -> <<"''">>;
517
        %bigserial -> <<"0">>
518
    end.
519

520
escape_name(#sql_schema_info{db_type = pgsql}, <<"type">>) ->
UNCOV
521
    <<"\"type\"">>;
4✔
522
escape_name(_SchemaInfo, ColumnName) ->
UNCOV
523
    ColumnName.
870✔
524

525
format_column_def(SchemaInfo, Column) ->
UNCOV
526
    [<<"    ">>,
874✔
527
     escape_name(SchemaInfo, Column#sql_column.name), <<" ">>,
528
     format_type(SchemaInfo, Column),
529
     <<" NOT NULL">>,
530
     case Column#sql_column.default of
UNCOV
531
         false -> [];
778✔
532
         true ->
UNCOV
533
             [<<" DEFAULT ">>, format_default(SchemaInfo, Column)]
96✔
534
     end,
535
     case lists:keyfind(sql_references, 1, Column#sql_column.opts) of
UNCOV
536
         false -> [];
854✔
537
         #sql_references{table = T, column = C} ->
UNCOV
538
             [<<" REFERENCES ">>, T, <<"(">>, C, <<") ON DELETE CASCADE">>]
20✔
539
     end].
540

541
format_mysql_index_column(Table, ColumnName) ->
UNCOV
542
    {value, Column} =
256✔
543
        lists:keysearch(
544
          ColumnName, #sql_column.name, Table#sql_table.columns),
UNCOV
545
    NeedsSizeLimit =
256✔
546
        case Column#sql_column.type of
UNCOV
547
            {text, N} when is_integer(N), N < 191 -> false;
12✔
548
            {text, _} -> true;
×
UNCOV
549
            text -> true;
220✔
UNCOV
550
            _ -> false
24✔
551
        end,
UNCOV
552
    if
256✔
553
        NeedsSizeLimit ->
UNCOV
554
            [ColumnName, <<"(191)">>];
220✔
555
        true ->
UNCOV
556
            ColumnName
36✔
557
    end.
558

559
format_create_index(#sql_schema_info{db_type = pgsql}, Table, Index) ->
UNCOV
560
    TableName = Table#sql_table.name,
82✔
UNCOV
561
    Unique =
82✔
562
        case Index#sql_index.unique of
UNCOV
563
            true -> <<"UNIQUE ">>;
10✔
UNCOV
564
            false -> <<"">>
72✔
565
        end,
UNCOV
566
    Name = [<<"i_">>, TableName, <<"_">>,
82✔
567
            lists:join(
568
              <<"_">>,
569
              Index#sql_index.columns)],
UNCOV
570
    [<<"CREATE ">>, Unique, <<"INDEX ">>, Name, <<" ON ">>, TableName,
82✔
571
     <<" USING btree (">>,
572
     lists:join(
573
       <<", ">>,
574
       Index#sql_index.columns),
575
     <<");">>];
576
format_create_index(#sql_schema_info{db_type = sqlite}, Table, Index) ->
UNCOV
577
    TableName = Table#sql_table.name,
3✔
UNCOV
578
    Unique =
3✔
579
        case Index#sql_index.unique of
UNCOV
580
            true -> <<"UNIQUE ">>;
3✔
581
            false -> <<"">>
×
582
        end,
UNCOV
583
    Name = [<<"i_">>, TableName, <<"_">>,
3✔
584
            lists:join(
585
              <<"_">>,
586
              Index#sql_index.columns)],
UNCOV
587
    [<<"CREATE ">>, Unique, <<"INDEX ">>, Name, <<" ON ">>, TableName,
3✔
588
     <<"(">>,
589
     lists:join(
590
       <<", ">>,
591
       Index#sql_index.columns),
592
     <<");">>];
593
format_create_index(#sql_schema_info{db_type = mysql}, Table, Index) ->
UNCOV
594
    TableName = Table#sql_table.name,
82✔
UNCOV
595
    Unique =
82✔
596
        case Index#sql_index.unique of
UNCOV
597
            true -> <<"UNIQUE ">>;
10✔
UNCOV
598
            false -> <<"">>
72✔
599
        end,
UNCOV
600
    Name = [<<"i_">>, TableName, <<"_">>,
82✔
601
            lists:join(
602
              <<"_">>,
603
              Index#sql_index.columns)],
UNCOV
604
    [<<"CREATE ">>, Unique, <<"INDEX ">>, Name,
82✔
605
     <<" USING BTREE ON ">>, TableName,
606
     <<"(">>,
607
     lists:join(
608
       <<", ">>,
609
       lists:map(
610
         fun(Col) ->
UNCOV
611
                 format_mysql_index_column(Table, Col)
135✔
612
         end, Index#sql_index.columns)),
613
     <<");">>].
614

615
format_primary_key(#sql_schema_info{db_type = mysql}, Table) ->
UNCOV
616
    case lists:filter(
74✔
UNCOV
617
           fun(#sql_index{meta = #{primary_key := true}}) -> true;
52✔
UNCOV
618
              (_) -> false
88✔
619
           end, Table#sql_table.indices) of
UNCOV
620
        [] -> [];
22✔
621
        [I] ->
UNCOV
622
            [[<<"    ">>,
52✔
623
              <<"PRIMARY KEY (">>,
624
              lists:join(
625
                <<", ">>,
626
                lists:map(
627
                  fun(Col) ->
UNCOV
628
                          format_mysql_index_column(Table, Col)
121✔
629
                  end, I#sql_index.columns)),
630
              <<")">>]]
631
    end;
632
format_primary_key(_SchemaInfo, Table) ->
UNCOV
633
    case lists:filter(
77✔
UNCOV
634
           fun(#sql_index{meta = #{primary_key := true}}) -> true;
58✔
UNCOV
635
              (_) -> false
85✔
636
           end, Table#sql_table.indices) of
UNCOV
637
        [] -> [];
19✔
638
        [I] ->
UNCOV
639
            [[<<"    ">>,
58✔
640
              <<"PRIMARY KEY (">>,
641
              lists:join(<<", ">>, I#sql_index.columns),
642
              <<")">>]]
643
    end.
644

645
format_add_primary_key(#sql_schema_info{db_type = sqlite} = SchemaInfo,
646
                       Table, Index) ->
647
    format_create_index(SchemaInfo, Table, Index);
×
648
format_add_primary_key(#sql_schema_info{db_type = pgsql}, Table, Index) ->
649
    TableName = Table#sql_table.name,
×
650
    [<<"ALTER TABLE ">>, TableName, <<" ADD PRIMARY KEY (">>,
×
651
     lists:join(
652
       <<", ">>,
653
       Index#sql_index.columns),
654
     <<");">>];
655
format_add_primary_key(#sql_schema_info{db_type = mysql}, Table, Index) ->
656
    TableName = Table#sql_table.name,
×
657
    [<<"ALTER TABLE ">>, TableName, <<" ADD PRIMARY KEY (">>,
×
658
     lists:join(
659
       <<", ">>,
660
       lists:map(
661
         fun(Col) ->
662
                 format_mysql_index_column(Table, Col)
×
663
         end, Index#sql_index.columns)),
664
     <<");">>].
665

666
format_create_table(#sql_schema_info{db_type = pgsql} = SchemaInfo, Table) ->
UNCOV
667
    TableName = Table#sql_table.name,
74✔
668
    [iolist_to_binary(
669
       [<<"CREATE TABLE ">>, TableName, <<" (\n">>,
670
        lists:join(
671
          <<",\n">>,
672
          lists:map(
UNCOV
673
            fun(C) -> format_column_def(SchemaInfo, C) end,
434✔
674
            Table#sql_table.columns) ++
675
              format_primary_key(SchemaInfo, Table)),
UNCOV
676
        <<"\n);\n">>])] ++
74✔
677
        lists:flatmap(
678
          fun(#sql_index{meta = #{primary_key := true}}) ->
UNCOV
679
                  [];
58✔
680
             (#sql_index{meta = #{ignore := true}}) ->
681
                  [];
×
682
             (I) ->
UNCOV
683
                  [iolist_to_binary(
82✔
684
                     [format_create_index(SchemaInfo, Table, I),
685
                      <<"\n">>])]
686
          end,
687
          Table#sql_table.indices);
688
format_create_table(#sql_schema_info{db_type = sqlite} = SchemaInfo, Table) ->
UNCOV
689
    TableName = Table#sql_table.name,
3✔
690
    [iolist_to_binary(
691
       [<<"CREATE TABLE ">>, TableName, <<" (\n">>,
692
        lists:join(
693
          <<",\n">>,
694
          lists:map(
UNCOV
695
            fun(C) -> format_column_def(SchemaInfo, C) end,
6✔
696
            Table#sql_table.columns) ++
697
              format_primary_key(SchemaInfo, Table)),
UNCOV
698
        <<"\n);\n">>])] ++
3✔
699
        lists:flatmap(
700
          fun(#sql_index{meta = #{primary_key := true}}) ->
701
                  [];
×
702
             (#sql_index{meta = #{ignore := true}}) ->
703
                  [];
×
704
             (I) ->
UNCOV
705
                  [iolist_to_binary(
3✔
706
                     [format_create_index(SchemaInfo, Table, I),
707
                      <<"\n">>])]
708
          end,
709
          Table#sql_table.indices);
710
format_create_table(#sql_schema_info{db_type = mysql} = SchemaInfo, Table) ->
UNCOV
711
    TableName = Table#sql_table.name,
74✔
712
    [iolist_to_binary(
713
      [<<"CREATE TABLE ">>, TableName, <<" (\n">>,
714
       lists:join(
715
         <<",\n">>,
716
         lists:map(
UNCOV
717
           fun(C) -> format_column_def(SchemaInfo, C) end,
434✔
718
           Table#sql_table.columns) ++
719
             format_primary_key(SchemaInfo, Table)),
UNCOV
720
       <<"\n) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;\n">>])] ++
74✔
721
        lists:flatmap(
722
          fun(#sql_index{meta = #{primary_key := true}}) ->
UNCOV
723
                  [];
52✔
724
             (#sql_index{meta = #{ignore := true}}) ->
UNCOV
725
                  [];
6✔
726
             (I) ->
UNCOV
727
                  [iolist_to_binary(
82✔
728
                     [format_create_index(SchemaInfo, Table, I),
729
                      <<"\n">>])]
730
          end,
731
          Table#sql_table.indices).
732

733
create_table(Host, SchemaInfo, Table) ->
UNCOV
734
    ejabberd_sql:sql_query(Host,
151✔
735
        fun() ->
UNCOV
736
            create_table_t(SchemaInfo, Table)
151✔
737
        end).
738

739
create_table_t(SchemaInfo, Table) ->
UNCOV
740
    SQLs = format_create_table(SchemaInfo, Table),
151✔
UNCOV
741
    ?INFO_MSG("Creating table ~s:~n~s~n",
151✔
UNCOV
742
        [Table#sql_table.name, SQLs]),
151✔
UNCOV
743
    lists:foreach(
151✔
UNCOV
744
        fun(SQL) -> ejabberd_sql:sql_query_t(SQL) end, SQLs),
318✔
UNCOV
745
    case Table#sql_table.post_create of
151✔
746
        undefined ->
UNCOV
747
            ok;
147✔
748
        F when is_function(F, 1) ->
UNCOV
749
            PostSQLs = F(SchemaInfo),
4✔
UNCOV
750
            lists:foreach(
4✔
UNCOV
751
                fun(SQL) -> ejabberd_sql:sql_query_t(SQL) end,
2✔
752
                PostSQLs)
753
    end.
754

755
create_tables(Host, Module, SchemaInfo, Schema) ->
UNCOV
756
    lists:foreach(
72✔
757
      fun(Table) ->
UNCOV
758
              Res = create_table(Host, SchemaInfo, Table),
144✔
UNCOV
759
              case Res of
144✔
760
                  {error, Error} ->
761
                      ?ERROR_MSG("Failed to create table ~s: ~p",
×
762
                                 [Table#sql_table.name, Error]),
×
763
                      error(Error);
×
764
                  _ ->
UNCOV
765
                      ok
144✔
766
              end
767
      end, Schema#sql_schema.tables),
UNCOV
768
    store_version(Host, Module, Schema#sql_schema.version).
72✔
769

770
should_update_schema(Host) ->
UNCOV
771
    SupportedDB =
128✔
772
        case ejabberd_option:sql_type(Host) of
UNCOV
773
            pgsql -> true;
40✔
UNCOV
774
            sqlite -> true;
48✔
UNCOV
775
            mysql -> true;
40✔
776
            _ -> false
×
777
        end,
UNCOV
778
    case ejabberd_option:update_sql_schema() andalso SupportedDB of
128✔
779
        true ->
UNCOV
780
            case ejabberd_sql:use_multihost_schema() of
128✔
781
                true ->
UNCOV
782
                    lists:member(sql, ejabberd_option:auth_method(Host));
56✔
783
                false ->
UNCOV
784
                    true
72✔
785
            end;
786
        false ->
787
            false
×
788
    end.
789

790
preprocess_table(SchemaInfo, Table) ->
UNCOV
791
    Table1 = filter_table_sh(SchemaInfo, Table),
287✔
UNCOV
792
    ImplicitPK =
287✔
793
        case SchemaInfo#sql_schema_info.db_type of
UNCOV
794
            pgsql -> false;
88✔
795
            sqlite ->
UNCOV
796
                case lists:keyfind(bigserial, #sql_column.type,
111✔
797
                                   Table1#sql_table.columns) of
UNCOV
798
                    false -> false;
93✔
UNCOV
799
                    #sql_column{name = Name} -> {ok, Name}
18✔
800
                end;
801
            mysql ->
UNCOV
802
                case lists:keyfind(bigserial, #sql_column.type,
88✔
803
                                   Table1#sql_table.columns) of
UNCOV
804
                    false -> false;
76✔
UNCOV
805
                    #sql_column{name = Name} -> {ok, Name}
12✔
806
                end
807
        end,
UNCOV
808
    Indices =
287✔
809
        case ImplicitPK of
810
            false ->
UNCOV
811
                {Inds, _} =
257✔
812
                    lists:mapfoldl(
813
                      fun(#sql_index{unique = true} = I, false) ->
UNCOV
814
                              {I#sql_index{
216✔
815
                                 meta = (I#sql_index.meta)#{primary_key => true}},
816
                               true};
817
                         (I, Acc) ->
UNCOV
818
                              {I, Acc}
247✔
819
                      end, false, Table1#sql_table.indices),
UNCOV
820
                Inds;
257✔
821
            {ok, CN} ->
UNCOV
822
                lists:map(
30✔
823
                  fun(#sql_index{columns = [CN1]} = I) when CN == CN1 ->
UNCOV
824
                          I#sql_index{
15✔
825
                            meta = (I#sql_index.meta)#{ignore => true}};
UNCOV
826
                     (I) -> I
80✔
827
                  end,
828
                  Table1#sql_table.indices)
829
        end,
UNCOV
830
    Table1#sql_table{indices = Indices}.
287✔
831

832
preprocess_schemas(SchemaInfo, Schemas) ->
UNCOV
833
    lists:map(
121✔
834
      fun(Schema) ->
UNCOV
835
              Schema#sql_schema{
135✔
836
                tables = lists:map(
837
                           fun(T) ->
UNCOV
838
                                   preprocess_table(SchemaInfo, T)
287✔
839
                           end,
840
                           Schema#sql_schema.tables)}
841
      end, Schemas).
842

843
update_schema(Host, Module, RawSchemas) ->
UNCOV
844
    case should_update_schema(Host) of
121✔
845
        true ->
UNCOV
846
            SchemaInfo =
121✔
847
                ejabberd_sql:sql_query(
848
                  Host,
849
                  fun(DBType, DBVersion) ->
UNCOV
850
                          #sql_schema_info{
121✔
851
                             db_type = DBType,
852
                             db_version = DBVersion,
853
                             multihost_schema = ejabberd_sql:use_multihost_schema()}
854
                  end),
UNCOV
855
            Schemas = preprocess_schemas(SchemaInfo, RawSchemas),
121✔
UNCOV
856
            Version = get_current_version(Host, Module, Schemas),
121✔
UNCOV
857
            LastSchema = lists:max(Schemas),
121✔
UNCOV
858
            LastVersion = LastSchema#sql_schema.version,
121✔
UNCOV
859
            case Version of
121✔
860
                _ when Version < 0 ->
861
                    ?ERROR_MSG("Can't update SQL schema for module ~p, please do it manually", [Module]);
×
862
                0 ->
UNCOV
863
                    create_tables(Host, Module, SchemaInfo, LastSchema);
72✔
864
                LastVersion ->
UNCOV
865
                    ok;
49✔
866
                _ when LastVersion < Version ->
867
                    ?ERROR_MSG("The current SQL schema for module ~p is ~p, but the latest known schema in the module is ~p", [Module, Version, LastVersion]);
×
868
                _ ->
869
                    lists:foreach(
×
870
                      fun(Schema) ->
871
                              if
×
872
                                  Schema#sql_schema.version > Version ->
873
                                      do_update_schema(Host, Module,
×
874
                                                       SchemaInfo, Schema);
875
                                  true ->
876
                                      ok
×
877
                              end
878
                      end, lists:sort(Schemas))
879
            end;
880
        false ->
881
            ok
×
882
    end.
883

884
do_update_schema(Host, Module, SchemaInfo, Schema) ->
885
    F = fun() ->
×
886
        lists:foreach(
×
887
            fun({add_column, TableName, ColumnName}) ->
888
                {value, Table} =
×
889
                    lists:keysearch(
890
                        TableName, #sql_table.name, Schema#sql_schema.tables),
891
                {value, Column} =
×
892
                    lists:keysearch(
893
                        ColumnName, #sql_column.name, Table#sql_table.columns),
894
                Res =
×
895
                    ejabberd_sql:sql_query_t(
896
                        fun(DBType, _DBVersion) ->
897
                            Def = format_column_def(SchemaInfo, Column),
×
898
                            Default = format_default(SchemaInfo, Column),
×
899
                            SQLs =
×
900
                                [[<<"ALTER TABLE ">>,
901
                                  TableName,
902
                                  <<" ADD COLUMN\n">>,
903
                                  Def,
904
                                  <<" DEFAULT ">>,
905
                                  Default, <<";\n">>]] ++
906
                                case Column#sql_column.default of
907
                                    false when DBType /= sqlite ->
908
                                        [[<<"ALTER TABLE ">>,
×
909
                                          TableName,
910
                                          <<" ALTER COLUMN ">>,
911
                                          ColumnName,
912
                                          <<" DROP DEFAULT;">>]];
913
                                    _ ->
914
                                        []
×
915
                                end,
916
                            ?INFO_MSG("Add column ~s/~s:~n~s~n",
×
917
                                      [TableName,
918
                                       ColumnName,
919
                                       SQLs]),
×
920
                            lists:foreach(
×
921
                                fun(SQL) -> ejabberd_sql:sql_query_t(SQL) end,
×
922
                                SQLs)
923
                        end),
924
                case Res of
×
925
                    {error, Error} ->
926
                        ?ERROR_MSG("Failed to update table ~s: ~p",
×
927
                                   [TableName, Error]),
×
928
                        error(Error);
×
929
                    _ ->
930
                        ok
×
931
                end;
932
               ({drop_column, TableName, ColumnName}) ->
933
                   Res =
×
934
                       ejabberd_sql:sql_query_t(
935
                           fun(_DBType, _DBVersion) ->
936
                               SQL = [<<"ALTER TABLE ">>,
×
937
                                      TableName,
938
                                      <<" DROP COLUMN ">>,
939
                                      ColumnName,
940
                                      <<";">>],
941
                               ?INFO_MSG("Drop column ~s/~s:~n~s~n",
×
942
                                         [TableName,
943
                                          ColumnName,
944
                                          SQL]),
×
945
                               ejabberd_sql:sql_query_t(SQL)
×
946
                           end),
947
                   case Res of
×
948
                       {error, Error} ->
949
                           ?ERROR_MSG("Failed to update table ~s: ~p",
×
950
                                      [TableName, Error]),
×
951
                           error(Error);
×
952
                       _ ->
953
                           ok
×
954
                   end;
955
               ({create_index, TableName, Columns1}) ->
956
                   Columns =
×
957
                       case ejabberd_sql:use_multihost_schema() of
958
                           true ->
959
                               Columns1;
×
960
                           false ->
961
                               lists:delete(
×
962
                                   <<"server_host">>, Columns1)
963
                       end,
964
                   {value, Table} =
×
965
                       lists:keysearch(
966
                           TableName, #sql_table.name, Schema#sql_schema.tables),
967
                   {value, Index} =
×
968
                       lists:keysearch(
969
                           Columns, #sql_index.columns, Table#sql_table.indices),
970
                   case Index#sql_index.meta of
×
971
                       #{ignore := true} -> ok;
×
972
                       _ ->
973
                           Res =
×
974
                               ejabberd_sql:sql_query_t(
975
                                   fun() ->
976
                                       case Index#sql_index.meta of
×
977
                                           #{primary_key := true} ->
978
                                               SQL1 = format_add_primary_key(
×
979
                                                   SchemaInfo, Table, Index),
980
                                               SQL = iolist_to_binary(SQL1),
×
981
                                               ?INFO_MSG("Add primary key ~s/~p:~n~s~n",
×
982
                                                         [Table#sql_table.name,
983
                                                          Index#sql_index.columns,
984
                                                          SQL]),
×
985
                                               ejabberd_sql:sql_query_t(SQL);
×
986
                                           _ ->
987
                                               SQL1 = format_create_index(
×
988
                                                   SchemaInfo, Table, Index),
989
                                               SQL = iolist_to_binary(SQL1),
×
990
                                               ?INFO_MSG("Create index ~s/~p:~n~s~n",
×
991
                                                         [Table#sql_table.name,
992
                                                          Index#sql_index.columns,
993
                                                          SQL]),
×
994
                                               ejabberd_sql:sql_query_t(SQL)
×
995
                                       end
996
                                   end),
997
                           case Res of
×
998
                               {error, Error} ->
999
                                   ?ERROR_MSG("Failed to update table ~s: ~p",
×
1000
                                              [TableName, Error]),
×
1001
                                   error(Error);
×
1002
                               _ ->
1003
                                   ok
×
1004
                           end
1005
                   end;
1006
               ({update_primary_key, TableName, Columns1}) ->
1007
                   Columns =
×
1008
                       case ejabberd_sql:use_multihost_schema() of
1009
                           true ->
1010
                               Columns1;
×
1011
                           false ->
1012
                               lists:delete(
×
1013
                                   <<"server_host">>, Columns1)
1014
                       end,
1015
                   {value, Table} =
×
1016
                       lists:keysearch(
1017
                           TableName, #sql_table.name, Schema#sql_schema.tables),
1018
                   {value, Index} =
×
1019
                       lists:keysearch(
1020
                           Columns, #sql_index.columns, Table#sql_table.indices),
1021
                   Res =
×
1022
                       case SchemaInfo#sql_schema_info.db_type of
1023
                           sqlite ->
1024
                               sqlite_table_copy_t(SchemaInfo, Table);
×
1025
                           pgsql ->
1026
                               TableName = Table#sql_table.name,
×
1027
                               SQL1 = [<<"ALTER TABLE ">>, TableName, <<" DROP CONSTRAINT ",
×
1028
                                                                        TableName/binary, "_pkey, ",
1029
                                                                        "ADD PRIMARY KEY (">>,
1030
                                       lists:join(
1031
                                           <<", ">>,
1032
                                           Index#sql_index.columns),
1033
                                       <<");">>],
1034
                               SQL = iolist_to_binary(SQL1),
×
1035
                               ?INFO_MSG("Update primary key ~s/~p:~n~s~n",
×
1036
                                         [Table#sql_table.name,
1037
                                          Index#sql_index.columns,
1038
                                          SQL]),
×
1039
                               ejabberd_sql:sql_query_t(
×
1040
                                   fun(_DBType, _DBVersion) ->
1041
                                       ejabberd_sql:sql_query_t(SQL)
×
1042
                                   end);
1043
                           mysql ->
1044
                               TableName = Table#sql_table.name,
×
1045
                               SQL1 = [<<"ALTER TABLE ">>, TableName, <<" DROP PRIMARY KEY, "
×
1046
                                                                        "ADD PRIMARY KEY (">>,
1047
                                       lists:join(
1048
                                           <<", ">>,
1049
                                           lists:map(
1050
                                               fun(Col) ->
1051
                                                   format_mysql_index_column(Table, Col)
×
1052
                                               end, Index#sql_index.columns)),
1053
                                       <<");">>],
1054
                               SQL = iolist_to_binary(SQL1),
×
1055
                               ?INFO_MSG("Update primary key ~s/~p:~n~s~n",
×
1056
                                         [Table#sql_table.name,
1057
                                          Index#sql_index.columns,
1058
                                          SQL]),
×
1059
                               ejabberd_sql:sql_query_t(
×
1060
                                   fun(_DBType, _DBVersion) ->
1061
                                       ejabberd_sql:sql_query_t(SQL)
×
1062
                                   end)
1063
                       end,
1064
                   case Res of
×
1065
                       {error, Error} ->
1066
                           ?ERROR_MSG("Failed to update table ~s: ~p",
×
1067
                                      [TableName, Error]),
×
1068
                           error(Error);
×
1069
                       _ ->
1070
                           ok
×
1071
                   end;
1072
               ({drop_index, TableName, Columns1}) ->
1073
                   Columns =
×
1074
                       case ejabberd_sql:use_multihost_schema() of
1075
                           true ->
1076
                               Columns1;
×
1077
                           false ->
1078
                               lists:delete(
×
1079
                                   <<"server_host">>, Columns1)
1080
                       end,
1081
                   case find_index_name(Host, TableName, Columns) of
×
1082
                       false ->
1083
                           ?ERROR_MSG("Can't find an index to drop for ~s/~p",
×
1084
                                      [TableName, Columns]);
×
1085
                       {ok, IndexName} ->
1086
                           Res =
×
1087
                               ejabberd_sql:sql_query_t(
1088
                                   fun(DBType, _DBVersion) ->
1089
                                       SQL =
×
1090
                                           case DBType of
1091
                                               mysql ->
1092
                                                   [<<"DROP INDEX ">>,
×
1093
                                                    IndexName,
1094
                                                    <<" ON ">>,
1095
                                                    TableName,
1096
                                                    <<";">>];
1097
                                               _ ->
1098
                                                   [<<"DROP INDEX ">>,
×
1099
                                                    IndexName, <<";">>]
1100
                                           end,
1101
                                       ?INFO_MSG("Drop index ~s/~p:~n~s~n",
×
1102
                                                 [TableName,
1103
                                                  Columns,
1104
                                                  SQL]),
×
1105
                                       ejabberd_sql:sql_query_t(SQL)
×
1106
                                   end),
1107
                           case Res of
×
1108
                               {error, Error} ->
1109
                                   ?ERROR_MSG("Failed to update table ~s: ~p",
×
1110
                                              [TableName, Error]),
×
1111
                                   error(Error);
×
1112
                               _ ->
1113
                                   ok
×
1114
                           end
1115
                   end
1116
            end, Schema#sql_schema.update),
1117
        store_version_t(Module, Schema#sql_schema.version)
×
1118
        end,
1119
    ejabberd_sql:sql_transaction(Host, F, ejabberd_option:update_sql_schema_timeout(), 1).
×
1120

1121
print_schema(SDBType, SDBVersion, SNewSchema) ->
1122
    {DBType, DBVersion} =
×
1123
        case SDBType of
1124
            "pgsql" ->
1125
                case string:split(SDBVersion, ".") of
×
1126
                    [SMajor, SMinor] ->
1127
                        try {list_to_integer(SMajor), list_to_integer(SMinor)} of
×
1128
                            {Major, Minor} ->
1129
                                {pgsql, Major * 10000 + Minor}
×
1130
                        catch _:_ ->
1131
                                io:format("pgsql version be in the form of "
×
1132
                                          "Major.Minor, e.g. 16.1~n"),
1133
                                {error, error}
×
1134
                        end;
1135
                    _ ->
1136
                        io:format("pgsql version be in the form of "
×
1137
                                  "Major.Minor, e.g. 16.1~n"),
1138
                        {error, error}
×
1139
                end;
1140
            "mysql" ->
1141
                case ejabberd_sql:parse_mysql_version(SDBVersion, 0) of
×
1142
                    {ok, V} ->
1143
                        {mysql, V};
×
1144
                    error ->
1145
                        io:format("mysql version be in the same form as "
×
1146
                                  "SELECT VERSION() returns, e.g. 8.2.0~n"),
1147
                        {error, error}
×
1148
                end;
1149
            "sqlite" ->
1150
                {sqlite, undefined};
×
1151
            _ ->
1152
                io:format("db_type must be one of the following: "
×
1153
                          "'pgsql', 'mysql', 'sqlite'~n"),
1154
                {error, error}
×
1155
        end,
1156
    NewSchema =
×
1157
        case SNewSchema of
1158
            "0" -> false;
×
1159
            "1" -> true;
×
1160
            "false" -> false;
×
1161
            "true" -> true;
×
1162
            _ ->
1163
                io:format("multihost_schema must be one of the following: "
×
1164
                          "'0', '1', 'false', 'true'~n"),
1165
                error
×
1166
        end,
1167
    case {DBType, NewSchema} of
×
1168
        {error, _} -> ?STATUS_ERROR;
×
1169
        {_, error} -> ?STATUS_ERROR;
×
1170
        _ ->
1171
            SchemaInfo =
×
1172
                #sql_schema_info{
1173
                   db_type = DBType,
1174
                   db_version = DBVersion,
1175
                   multihost_schema = NewSchema},
1176
            Mods = ejabberd_config:beams(all),
×
1177
            lists:foreach(
×
1178
              fun(Mod) ->
1179
                      case erlang:function_exported(Mod, sql_schemas, 0) of
×
1180
                          true ->
1181
                              Schemas = Mod:sql_schemas(),
×
1182
                              Schemas2 = preprocess_schemas(SchemaInfo, Schemas),
×
1183
                              Schema = lists:max(Schemas2),
×
1184
                              SQLs =
×
1185
                                  lists:flatmap(
1186
                                    fun(Table) ->
1187
                                            SQLs = format_create_table(SchemaInfo, Table),
×
1188
                                            PostSQLs =
×
1189
                                                case Table#sql_table.post_create of
1190
                                                    undefined ->
1191
                                                        [];
×
1192
                                                    F when is_function(F, 1) ->
1193
                                                        PSQLs = F(SchemaInfo),
×
1194
                                                        lists:map(
×
1195
                                                          fun(S) ->
1196
                                                                  [S, <<"\n">>]
×
1197
                                                          end, PSQLs)
1198
                                                end,
1199
                                            SQLs ++ PostSQLs
×
1200
                                    end, Schema#sql_schema.tables),
1201
                              io:format("~s~n", [SQLs]);
×
1202
                          false ->
1203
                              ok
×
1204
                      end
1205
              end, Mods),
1206
            ?STATUS_SUCCESS
×
1207
    end.
1208

1209

1210
test() ->
1211
    Schemas =
×
1212
        [#sql_schema{
1213
            version = 2,
1214
            tables =
1215
                [#sql_table{
1216
                    name = <<"archive2">>,
1217
                    columns = [#sql_column{name = <<"username">>, type = text},
1218
                               #sql_column{name = <<"server_host">>, type = text},
1219
                               #sql_column{name = <<"timestamp">>, type = bigint},
1220
                               #sql_column{name = <<"peer">>, type = text},
1221
                               #sql_column{name = <<"bare_peer">>, type = text},
1222
                               #sql_column{name = <<"xml">>, type = {text, big}},
1223
                               #sql_column{name = <<"txt">>, type = {text, big}},
1224
                               #sql_column{name = <<"id">>, type = bigserial},
1225
                               #sql_column{name = <<"kind">>, type = text},
1226
                               #sql_column{name = <<"nick">>, type = text},
1227
                               #sql_column{name = <<"origin_id">>, type = text},
1228
                               #sql_column{name = <<"type">>, type = text},
1229
                               #sql_column{name = <<"created_at">>, type = timestamp,
1230
                                           default = true}],
1231
                    indices = [#sql_index{columns = [<<"id">>],
1232
                                          unique = true},
1233
                               #sql_index{
1234
                                  columns = [<<"server_host">>, <<"username">>, <<"timestamp">>]},
1235
                               #sql_index{
1236
                                  columns = [<<"server_host">>, <<"username">>, <<"peer">>]},
1237
                               #sql_index{
1238
                                  columns = [<<"server_host">>, <<"username">>, <<"bare_peer">>]},
1239
                               #sql_index{
1240
                                  columns = [<<"server_host">>, <<"origin_id">>]},
1241
                               #sql_index{
1242
                                  columns = [<<"server_host">>, <<"timestamp">>]}
1243
                              ]}],
1244
           update =
1245
                [{add_column, <<"archive2">>, <<"origin_id">>},
1246
                 {create_index, <<"archive2">>,
1247
                  [<<"server_host">>, <<"origin_id">>]},
1248
                 {drop_index, <<"archive2">>,
1249
                  [<<"server_host">>, <<"origin_id">>]},
1250
                 {drop_column, <<"archive2">>, <<"origin_id">>},
1251
                 {create_index, <<"archive2">>, [<<"id">>]}
1252
                ]},
1253
         #sql_schema{
1254
            version = 1,
1255
            tables =
1256
                [#sql_table{
1257
                    name = <<"archive2">>,
1258
                    columns = [#sql_column{name = <<"username">>, type = text},
1259
                               #sql_column{name = <<"server_host">>, type = text},
1260
                               #sql_column{name = <<"timestamp">>, type = bigint},
1261
                               #sql_column{name = <<"peer">>, type = text},
1262
                               #sql_column{name = <<"bare_peer">>, type = text},
1263
                               #sql_column{name = <<"xml">>, type = {text, big}},
1264
                               #sql_column{name = <<"txt">>, type = {text, big}},
1265
                               #sql_column{name = <<"id">>, type = bigserial},
1266
                               #sql_column{name = <<"kind">>, type = {text, 10}},
1267
                               #sql_column{name = <<"nick">>, type = text},
1268
                               #sql_column{name = <<"created_at">>, type = timestamp,
1269
                                           default = true}],
1270
                    indices = [#sql_index{
1271
                                  columns = [<<"server_host">>, <<"username">>, <<"timestamp">>]},
1272
                               #sql_index{
1273
                                  columns = [<<"server_host">>, <<"username">>, <<"peer">>]},
1274
                               #sql_index{
1275
                                  columns = [<<"server_host">>, <<"username">>, <<"bare_peer">>]},
1276
                               #sql_index{
1277
                                  columns = [<<"server_host">>, <<"timestamp">>]}
1278
                              ]}]}],
1279
    update_schema(<<"localhost">>, mod_foo, Schemas).
×
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

© 2026 Coveralls, Inc