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

processone / ejabberd / 747

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

push

github

badlop
Set version to 24.06

14119 of 43953 relevant lines covered (32.12%)

614.73 hits per line

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

42.57
/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-2024   ProcessOne
9
%%%
10
%%% This program is free software; you can redistribute it and/or
11
%%% modify it under the terms of the GNU General Public License as
12
%%% published by the Free Software Foundation; either version 2 of the
13
%%% License, or (at your option) any later version.
14
%%%
15
%%% This program is distributed in the hope that it will be useful,
16
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
%%% General Public License for more details.
19
%%%
20
%%% You should have received a copy of the GNU General Public License along
21
%%% with this program; if not, write to the Free Software Foundation, Inc.,
22
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23
%%%
24
%%%----------------------------------------------------------------------
25

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

69
schema_table() ->
70
    #sql_table{
3✔
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) ->
79
    ejabberd_sql:sql_query(
31✔
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, _) ->
101
              case
31✔
102
                  ejabberd_sql:sql_query_t(
103
                    ?SQL("select @(i.name)s, @(i.type)s"
31✔
104
                         "  from pragma_table_info(%(Table)s) as i"))
105
              of
106
                  {selected, Cols} ->
107
                      [{Col, string_to_type(SType)} || {Col, SType} <- Cols]
31✔
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) ->
214
    SModule = misc:atom_to_binary(Module),
45✔
215
    ejabberd_sql:sql_query(
45✔
216
      Host,
217
      ?SQL("select @(version)d"
62✔
218
           " from schema_version"
219
           " where module=%(SModule)s")).
220

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

229
table_exists(Host, Table) ->
230
    ejabberd_sql:sql_query(
75✔
231
      Host,
232
      fun(pgsql, _) ->
233
              case
30✔
234
                  ejabberd_sql:sql_query_t(
235
                    ?SQL("select @()b exists (select * from pg_tables "
31✔
236
                         "  where tablename=%(Table)s)"))
237
              of
238
                  {selected, [{Res}]} ->
239
                      Res
30✔
240
              end;
241
         (sqlite, _) ->
242
              case
15✔
243
                  ejabberd_sql:sql_query_t(
244
                    ?SQL("select @()b exists"
15✔
245
                         " (select 0 from pragma_table_info(%(Table)s))"))
246
              of
247
                  {selected, [{Res}]} ->
248
                      Res
15✔
249
              end;
250
         (mysql, _) ->
251
              case
30✔
252
                  ejabberd_sql:sql_query_t(
253
                    ?SQL("select @()b exists"
61✔
254
                         " (select 0 from information_schema.tables"
255
                         "  where table_name=%(Table)s and"
256
                         "        table_schema=schema())"))
257
              of
258
                  {selected, [{Res}]} ->
259
                      Res
30✔
260
              end
261
      end).
262

263
filter_table_sh(SchemaInfo, Table) ->
264
    case {SchemaInfo#sql_schema_info.new_schema, Table#sql_table.name} of
111✔
265
        {true, _} ->
266
            Table;
×
267
        {_, <<"route">>} ->
268
            Table;
×
269
        {false, _} ->
270
            Table#sql_table{
111✔
271
              columns =
272
                  lists:keydelete(
273
                    <<"server_host">>, #sql_column.name, Table#sql_table.columns),
274
              indices =
275
                  lists:map(
276
                    fun(Idx) ->
277
                            Idx#sql_index{
222✔
278
                              columns =
279
                                  lists:delete(
280
                                    <<"server_host">>, Idx#sql_index.columns)
281
                             }
282
                    end, Table#sql_table.indices)
283
             }
284
    end.
285

286
string_to_type(SType) ->
287
    case string:lowercase(SType) of
174✔
288
        <<"text">> -> text;
128✔
289
        <<"mediumtext">> -> text;
×
290
        <<"bigint">> -> bigint;
7✔
291
        <<"bigint ", _/binary>> -> bigint;
2✔
292
        <<"bigint(", _/binary>> -> bigint;
×
293
        <<"integer">> -> integer;
7✔
294
        <<"int">> -> integer;
×
295
        <<"int(", _/binary>> -> integer;
×
296
        <<"int ", _/binary>> -> integer;
×
297
        <<"smallint">> -> smallint;
×
298
        <<"smallint(", _/binary>> -> smallint;
×
299
        <<"numeric">> -> numeric;
1✔
300
        <<"decimal", _/binary>> -> numeric;
×
301
        <<"bigserial">> -> bigserial;
×
302
        <<"boolean">> -> boolean;
5✔
303
        <<"tinyint(1)">> -> boolean;
×
304
        <<"tinyint", _/binary>> -> smallint;
×
305
        <<"bytea">> -> blob;
×
306
        <<"blob">> -> blob;
×
307
        <<"timestamp", _/binary>> -> timestamp;
15✔
308
        <<"character(", R/binary>> ->
309
            {ok, [N], []} = io_lib:fread("~d)", binary_to_list(R)),
6✔
310
            {char, N};
6✔
311
        <<"char(", R/binary>> ->
312
            {ok, [N], []} = io_lib:fread("~d)", binary_to_list(R)),
×
313
            {char, N};
×
314
        <<"varchar(", _/binary>> -> text;
3✔
315
        <<"character varying(", _/binary>> -> text;
×
316
        T ->
317
            ?ERROR_MSG("Unknown SQL type '~s'", [T]),
×
318
            {undefined, T}
×
319
    end.
320

321
check_columns_compatibility(RequiredColumns, Columns) ->
322
    lists:all(
31✔
323
      fun(#sql_column{name = Name, type = Type}) ->
324
              %io:format("col ~p~n", [{Name, Type}]),
325
              case lists:keyfind(Name, 1, Columns) of
173✔
326
                  false ->
327
                      false;
×
328
                  {_, Type2} ->
329
                      %io:format("tt ~p~n", [{Type, Type2}]),
330
                      case {Type, Type2} of
173✔
331
                          {T, T} -> true;
150✔
332
                          {text, blob} -> true;
×
333
                          {{text, _}, blob} -> true;
×
334
                          {{text, _}, text} -> true;
17✔
335
                          {{text, _}, {varchar, _}} -> true;
×
336
                          {text, {varchar, _}} -> true;
×
337
                          {{char, _}, text} -> true;
×
338
                          {{varchar, _}, text} -> true;
×
339
                          {smallint, integer} -> true;
×
340
                          {smallint, bigint} -> true;
×
341
                          {smallint, numeric} -> true;
×
342
                          {integer, bigint} -> true;
×
343
                          {integer, numeric} -> true;
×
344
                          {bigint, numeric} -> true;
×
345
                          %% a workaround for MySQL definition of mqtt_pub
346
                          {bigint, integer} -> true;
×
347
                          {bigserial, integer} -> true;
6✔
348
                          {bigserial, bigint} -> true;
×
349
                          {bigserial, numeric} -> true;
×
350
                          _ -> false
×
351
                      end
352
              end
353
      end, RequiredColumns).
354

355
guess_version(Host, Schemas) ->
356
    LastSchema = lists:max(Schemas),
42✔
357
    SomeTablesExist =
42✔
358
        lists:any(
359
          fun(Table) ->
360
                  table_exists(Host, Table#sql_table.name)
72✔
361
          end, LastSchema#sql_schema.tables),
362
    if
42✔
363
        SomeTablesExist ->
364
            CompatibleSchemas =
14✔
365
                lists:filter(
366
                  fun(Schema) ->
367
                          lists:all(
15✔
368
                            fun(Table) ->
369
                                    CurrentColumns =
31✔
370
                                        get_table_schema(
371
                                          Host, Table#sql_table.name),
372
                                    check_columns_compatibility(
31✔
373
                                      Table#sql_table.columns,
374
                                      CurrentColumns)
375
                            end, Schema#sql_schema.tables)
376
                  end, Schemas),
377
            case CompatibleSchemas of
14✔
378
                [] -> -1;
×
379
                _ ->
380
                    (lists:max(CompatibleSchemas))#sql_schema.version
14✔
381
            end;
382
        true ->
383
            0
28✔
384
    end.
385

386
get_current_version(Host, Module, Schemas) ->
387
    case get_version(Host, Module) of
45✔
388
        {selected, [{Version}]} ->
389
            Version;
3✔
390
        {selected, []} ->
391
            Version = guess_version(Host, Schemas),
42✔
392
            if
42✔
393
                Version > 0 ->
394
                    store_version(Host, Module, Version);
14✔
395
                true ->
396
                    ok
28✔
397
            end,
398
            Version
42✔
399
    end.
400

401
sqlite_table_copy(Host, SchemaInfo, Table) ->
402
    ejabberd_sql:sql_transaction(Host,
×
403
        fun() ->
404
            TableName = Table#sql_table.name,
×
405
            NewTableName = <<"new_", TableName/binary>>,
×
406
            NewTable = Table#sql_table{name = NewTableName},
×
407
            create_table_t(SchemaInfo, NewTable),
×
408
            SQL2 = <<"INSERT INTO ", NewTableName/binary,
×
409
                " SELECT * FROM ", TableName/binary>>,
410
            ?INFO_MSG("Copying table ~s to ~s:~n~s~n",
×
411
                [TableName, NewTableName, SQL2]),
×
412
            ejabberd_sql:sql_query_t(SQL2),
×
413
            SQL3 = <<"DROP TABLE ", TableName/binary>>,
×
414
            ?INFO_MSG("Droping old table ~s:~n~s~n",
×
415
                [TableName, SQL2]),
×
416
            ejabberd_sql:sql_query_t(SQL3),
×
417
            SQL4 = <<"ALTER TABLE ", NewTableName/binary,
×
418
                " RENAME TO ", TableName/binary>>,
419
            ?INFO_MSG("Renameing table ~s to ~s:~n~s~n",
×
420
                [NewTableName, TableName, SQL4]),
×
421
            ejabberd_sql:sql_query_t(SQL4)
×
422
        end).
423

424
format_type(#sql_schema_info{db_type = pgsql}, Column) ->
425
    case Column#sql_column.type of
160✔
426
        text -> <<"text">>;
106✔
427
        {text, _} -> <<"text">>;
14✔
428
        bigint -> <<"bigint">>;
9✔
429
        integer -> <<"integer">>;
1✔
430
        smallint -> <<"smallint">>;
×
431
        numeric -> <<"numeric">>;
1✔
432
        boolean -> <<"boolean">>;
5✔
433
        blob -> <<"bytea">>;
×
434
        timestamp -> <<"timestamp">>;
13✔
435
        {char, N} -> [<<"character(">>, integer_to_binary(N), <<")">>];
6✔
436
        bigserial -> <<"bigserial">>
5✔
437
    end;
438
format_type(#sql_schema_info{db_type = sqlite}, Column) ->
439
    case Column#sql_column.type of
2✔
440
        text -> <<"text">>;
1✔
441
        {text, _} -> <<"text">>;
×
442
        bigint -> <<"bigint">>;
1✔
443
        integer -> <<"integer">>;
×
444
        smallint -> <<"smallint">>;
×
445
        numeric -> <<"numeric">>;
×
446
        boolean -> <<"boolean">>;
×
447
        blob -> <<"blob">>;
×
448
        timestamp -> <<"timestamp">>;
×
449
        {char, N} -> [<<"character(">>, integer_to_binary(N), <<")">>];
×
450
        bigserial -> <<"integer primary key autoincrement">>
×
451
    end;
452
format_type(#sql_schema_info{db_type = mysql}, Column) ->
453
    case Column#sql_column.type of
160✔
454
        text -> <<"text">>;
106✔
455
        {text, big} -> <<"mediumtext">>;
6✔
456
        {text, N} when is_integer(N), N < 191 ->
457
            [<<"varchar(">>, integer_to_binary(N), <<")">>];
8✔
458
        {text, _} -> <<"text">>;
×
459
        bigint -> <<"bigint">>;
9✔
460
        integer -> <<"integer">>;
1✔
461
        smallint -> <<"smallint">>;
×
462
        numeric -> <<"numeric">>;
1✔
463
        boolean -> <<"boolean">>;
5✔
464
        blob -> <<"blob">>;
×
465
        timestamp -> <<"timestamp">>;
13✔
466
        {char, N} -> [<<"character(">>, integer_to_binary(N), <<")">>];
6✔
467
        bigserial -> <<"bigint auto_increment primary key">>
5✔
468
    end.
469

470
format_default(#sql_schema_info{db_type = pgsql}, Column) ->
471
    case Column#sql_column.type of
19✔
472
        text -> <<"''">>;
2✔
473
        {text, _} -> <<"''">>;
3✔
474
        bigint -> <<"0">>;
×
475
        integer -> <<"0">>;
1✔
476
        smallint -> <<"0">>;
×
477
        numeric -> <<"0">>;
×
478
        boolean -> <<"false">>;
×
479
        blob -> <<"''">>;
×
480
        timestamp -> <<"now()">>
13✔
481
        %{char, N} -> <<"''">>;
482
        %bigserial -> <<"0">>
483
    end;
484
format_default(#sql_schema_info{db_type = sqlite}, Column) ->
485
    case Column#sql_column.type of
×
486
        text -> <<"''">>;
×
487
        {text, _} -> <<"''">>;
×
488
        bigint -> <<"0">>;
×
489
        integer -> <<"0">>;
×
490
        smallint -> <<"0">>;
×
491
        numeric -> <<"0">>;
×
492
        boolean -> <<"false">>;
×
493
        blob -> <<"''">>;
×
494
        timestamp -> <<"CURRENT_TIMESTAMP">>
×
495
        %{char, N} -> <<"''">>;
496
        %bigserial -> <<"0">>
497
    end;
498
format_default(#sql_schema_info{db_type = mysql}, Column) ->
499
    case Column#sql_column.type of
19✔
500
        text -> <<"('')">>;
2✔
501
        {text, _} -> <<"('')">>;
3✔
502
        bigint -> <<"0">>;
×
503
        integer -> <<"0">>;
1✔
504
        smallint -> <<"0">>;
×
505
        numeric -> <<"0">>;
×
506
        boolean -> <<"false">>;
×
507
        blob -> <<"('')">>;
×
508
        timestamp -> <<"CURRENT_TIMESTAMP">>
13✔
509
        %{char, N} -> <<"''">>;
510
        %bigserial -> <<"0">>
511
    end.
512

513
escape_name(#sql_schema_info{db_type = pgsql}, <<"type">>) ->
514
    <<"\"type\"">>;
1✔
515
escape_name(_SchemaInfo, ColumnName) ->
516
    ColumnName.
321✔
517

518
format_column_def(SchemaInfo, Column) ->
519
    [<<"    ">>,
322✔
520
     escape_name(SchemaInfo, Column#sql_column.name), <<" ">>,
521
     format_type(SchemaInfo, Column),
522
     <<" NOT NULL">>,
523
     case Column#sql_column.default of
524
         false -> [];
284✔
525
         true ->
526
             [<<" DEFAULT ">>, format_default(SchemaInfo, Column)]
38✔
527
     end,
528
     case lists:keyfind(sql_references, 1, Column#sql_column.opts) of
529
         false -> [];
312✔
530
         #sql_references{table = T, column = C} ->
531
             [<<" REFERENCES ">>, T, <<"(">>, C, <<") ON DELETE CASCADE">>]
10✔
532
     end].
533

534
format_mysql_index_column(Table, ColumnName) ->
535
    {value, Column} =
85✔
536
        lists:keysearch(
537
          ColumnName, #sql_column.name, Table#sql_table.columns),
538
    NeedsSizeLimit =
85✔
539
        case Column#sql_column.type of
540
            {text, N} when is_integer(N), N < 191 -> false;
3✔
541
            {text, _} -> true;
×
542
            text -> true;
71✔
543
            _ -> false
11✔
544
        end,
545
    if
85✔
546
        NeedsSizeLimit ->
547
            [ColumnName, <<"(191)">>];
71✔
548
        true ->
549
            ColumnName
14✔
550
    end.
551

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

608
format_primary_key(#sql_schema_info{db_type = mysql}, Table) ->
609
    case lists:filter(
30✔
610
           fun(#sql_index{meta = #{primary_key := true}}) -> true;
19✔
611
              (_) -> false
41✔
612
           end, Table#sql_table.indices) of
613
        [] -> [];
11✔
614
        [I] ->
615
            [[<<"    ">>,
19✔
616
              <<"PRIMARY KEY (">>,
617
              lists:join(
618
                <<", ">>,
619
                lists:map(
620
                  fun(Col) ->
621
                          format_mysql_index_column(Table, Col)
34✔
622
                  end, I#sql_index.columns)),
623
              <<")">>]]
624
    end;
625
format_primary_key(_SchemaInfo, Table) ->
626
    case lists:filter(
31✔
627
           fun(#sql_index{meta = #{primary_key := true}}) -> true;
22✔
628
              (_) -> false
39✔
629
           end, Table#sql_table.indices) of
630
        [] -> [];
9✔
631
        [I] ->
632
            [[<<"    ">>,
22✔
633
              <<"PRIMARY KEY (">>,
634
              lists:join(<<", ">>, I#sql_index.columns),
635
              <<")">>]]
636
    end.
637

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

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

726
create_table(Host, SchemaInfo, Table) ->
727
    ejabberd_sql:sql_query(Host,
61✔
728
        fun() ->
729
            create_table_t(SchemaInfo, Table)
61✔
730
        end).
731

732
create_table_t(SchemaInfo, Table) ->
733
    SQLs = format_create_table(SchemaInfo, Table),
61✔
734
    ?INFO_MSG("Creating table ~s:~n~s~n",
61✔
735
        [Table#sql_table.name, SQLs]),
61✔
736
    lists:foreach(
61✔
737
        fun(SQL) -> ejabberd_sql:sql_query_t(SQL) end, SQLs),
138✔
738
    case Table#sql_table.post_create of
61✔
739
        undefined ->
740
            ok;
59✔
741
        F when is_function(F, 1) ->
742
            PostSQLs = F(SchemaInfo),
2✔
743
            lists:foreach(
2✔
744
                fun(SQL) -> ejabberd_sql:sql_query_t(SQL) end,
1✔
745
                PostSQLs)
746
    end.
747

748
create_tables(Host, Module, SchemaInfo, Schema) ->
749
    lists:foreach(
28✔
750
      fun(Table) ->
751
              Res = create_table(Host, SchemaInfo, Table),
58✔
752
              case Res of
58✔
753
                  {error, Error} ->
754
                      ?ERROR_MSG("Failed to create table ~s: ~p",
×
755
                                 [Table#sql_table.name, Error]),
×
756
                      error(Error);
×
757
                  _ ->
758
                      ok
58✔
759
              end
760
      end, Schema#sql_schema.tables),
761
    store_version(Host, Module, Schema#sql_schema.version).
28✔
762

763
should_update_schema(Host) ->
764
    SupportedDB =
48✔
765
        case ejabberd_option:sql_type(Host) of
766
            pgsql -> true;
16✔
767
            sqlite -> true;
16✔
768
            mysql -> true;
16✔
769
            _ -> false
×
770
        end,
771
    case ejabberd_option:update_sql_schema() andalso SupportedDB of
48✔
772
        true ->
773
            case ejabberd_sql:use_new_schema() of
48✔
774
                true ->
775
                    lists:member(sql, ejabberd_option:auth_method(Host));
×
776
                false ->
777
                    true
48✔
778
            end;
779
        false ->
780
            false
×
781
    end.
782

783
preprocess_table(SchemaInfo, Table) ->
784
    Table1 = filter_table_sh(SchemaInfo, Table),
108✔
785
    ImplicitPK =
108✔
786
        case SchemaInfo#sql_schema_info.db_type of
787
            pgsql -> false;
36✔
788
            sqlite ->
789
                case lists:keyfind(bigserial, #sql_column.type,
36✔
790
                                   Table1#sql_table.columns) of
791
                    false -> false;
30✔
792
                    #sql_column{name = Name} -> {ok, Name}
6✔
793
                end;
794
            mysql ->
795
                case lists:keyfind(bigserial, #sql_column.type,
36✔
796
                                   Table1#sql_table.columns) of
797
                    false -> false;
30✔
798
                    #sql_column{name = Name} -> {ok, Name}
6✔
799
                end
800
        end,
801
    Indices =
108✔
802
        case ImplicitPK of
803
            false ->
804
                {Inds, _} =
96✔
805
                    lists:mapfoldl(
806
                      fun(#sql_index{unique = true} = I, false) ->
807
                              {I#sql_index{
78✔
808
                                 meta = (I#sql_index.meta)#{primary_key => true}},
809
                               true};
810
                         (I, Acc) ->
811
                              {I, Acc}
103✔
812
                      end, false, Table1#sql_table.indices),
813
                Inds;
96✔
814
            {ok, CN} ->
815
                lists:map(
12✔
816
                  fun(#sql_index{columns = [CN1]} = I) when CN == CN1 ->
817
                          I#sql_index{
6✔
818
                            meta = (I#sql_index.meta)#{ignore => true}};
819
                     (I) -> I
32✔
820
                  end,
821
                  Table1#sql_table.indices)
822
        end,
823
    Table1#sql_table{indices = Indices}.
108✔
824

825
preprocess_schemas(SchemaInfo, Schemas) ->
826
    lists:map(
45✔
827
      fun(Schema) ->
828
              Schema#sql_schema{
48✔
829
                tables = lists:map(
830
                           fun(T) ->
831
                                   preprocess_table(SchemaInfo, T)
108✔
832
                           end,
833
                           Schema#sql_schema.tables)}
834
      end, Schemas).
835

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

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

1117

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

1206

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