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

processone / ejabberd / 1230

02 Dec 2025 01:08PM UTC coverage: 33.61% (+0.01%) from 33.6%
1230

push

github

prefiks
Fix error handling in last commit

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

5 existing lines in 1 file now uncovered.

15531 of 46210 relevant lines covered (33.61%)

1072.1 hits per line

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

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

25
-module(mod_muc_sql).
26

27

28
-behaviour(mod_muc).
29
-behaviour(mod_muc_room).
30
-behaviour(ejabberd_db_serialize).
31

32
%% API
33
-export([init/2, store_room/5, store_changes/4,
34
         restore_room/3, forget_room/3,
35
         can_use_nick/4, get_rooms/2, get_nick/3, get_nicks/2, set_nick/4,
36
         import/3, export/1]).
37
-export([register_online_room/4, unregister_online_room/4, find_online_room/3,
38
         get_online_rooms/3, count_online_rooms/2, rsm_supported/0,
39
         register_online_user/4, unregister_online_user/4,
40
         count_online_rooms_by_user/3, get_online_rooms_by_user/3,
41
         get_subscribed_rooms/3, get_rooms_without_subscribers/2,
42
         get_hibernated_rooms_older_than/3,
43
         find_online_room_by_pid/2, remove_user/2]).
44
-export([set_affiliation/6, set_affiliations/4, get_affiliation/5,
45
         get_affiliations/3, search_affiliation/4]).
46
-export([sql_schemas/0]).
47
-export([serialize/3, deserialize_start/1, deserialize/2]).
48

49
-include_lib("xmpp/include/jid.hrl").
50
-include("mod_muc.hrl").
51
-include("logger.hrl").
52
-include("ejabberd_sql_pt.hrl").
53
-include("ejabberd_db_serialize.hrl").
54

55
%%%===================================================================
56
%%% API
57
%%%===================================================================
58
init(Host, Opts) ->
59
    ejabberd_sql_schema:update_schema(Host, ?MODULE, sql_schemas()),
12✔
60
    case gen_mod:ram_db_mod(Opts, mod_muc) of
12✔
61
        ?MODULE ->
62
            clean_tables(Host);
12✔
63
        _ ->
64
            ok
×
65
    end.
66

67
sql_schemas() ->
68
    [#sql_schema{
12✔
69
        version = 1,
70
        tables =
71
            [#sql_table{
72
                name = <<"muc_room">>,
73
                columns =
74
                    [#sql_column{name = <<"name">>, type = text},
75
                     #sql_column{name = <<"host">>, type = text},
76
                     #sql_column{name = <<"server_host">>, type = text},
77
                     #sql_column{name = <<"opts">>, type = {text, big}},
78
                     #sql_column{name = <<"created_at">>, type = timestamp,
79
                                 default = true}],
80
                indices = [#sql_index{
81
                              columns = [<<"name">>, <<"host">>],
82
                              unique = true},
83
                           #sql_index{
84
                              columns = [<<"host">>, <<"created_at">>]}]},
85
             #sql_table{
86
                name = <<"muc_registered">>,
87
                columns =
88
                    [#sql_column{name = <<"jid">>, type = text},
89
                     #sql_column{name = <<"host">>, type = text},
90
                     #sql_column{name = <<"server_host">>, type = text},
91
                     #sql_column{name = <<"nick">>, type = text},
92
                     #sql_column{name = <<"created_at">>, type = timestamp,
93
                                 default = true}],
94
                indices = [#sql_index{
95
                              columns = [<<"jid">>, <<"host">>],
96
                              unique = true},
97
                           #sql_index{
98
                              columns = [<<"nick">>]}]},
99
             #sql_table{
100
                name = <<"muc_online_room">>,
101
                columns =
102
                    [#sql_column{name = <<"name">>, type = text},
103
                     #sql_column{name = <<"host">>, type = text},
104
                     #sql_column{name = <<"server_host">>, type = text},
105
                     #sql_column{name = <<"node">>, type = text},
106
                     #sql_column{name = <<"pid">>, type = text}],
107
                indices = [#sql_index{
108
                              columns = [<<"name">>, <<"host">>],
109
                              unique = true}]},
110
             #sql_table{
111
                name = <<"muc_online_users">>,
112
                columns =
113
                    [#sql_column{name = <<"username">>, type = text},
114
                     #sql_column{name = <<"server">>, type = {text, 75}},
115
                     #sql_column{name = <<"resource">>, type = text},
116
                     #sql_column{name = <<"name">>, type = text},
117
                     #sql_column{name = <<"host">>, type = {text, 75}},
118
                     #sql_column{name = <<"server_host">>, type = text},
119
                     #sql_column{name = <<"node">>, type = text}],
120
                indices = [#sql_index{
121
                              columns = [<<"username">>, <<"server">>,
122
                                         <<"resource">>, <<"name">>,
123
                                         <<"host">>],
124
                              unique = true}]},
125
             #sql_table{
126
                name = <<"muc_room_subscribers">>,
127
                columns =
128
                    [#sql_column{name = <<"room">>, type = text},
129
                     #sql_column{name = <<"host">>, type = text},
130
                     #sql_column{name = <<"jid">>, type = text},
131
                     #sql_column{name = <<"nick">>, type = text},
132
                     #sql_column{name = <<"nodes">>, type = text},
133
                     #sql_column{name = <<"created_at">>, type = timestamp,
134
                                 default = true}],
135
                indices = [#sql_index{
136
                              columns = [<<"host">>, <<"room">>, <<"jid">>],
137
                              unique = true},
138
                           #sql_index{
139
                              columns = [<<"host">>, <<"jid">>]},
140
                           #sql_index{
141
                              columns = [<<"jid">>]}]}]}].
142

143
store_room(LServer, Host, Name, Opts, ChangesHints) ->
144
    {Subs, Opts2} = case lists:keytake(subscribers, 1, Opts) of
588✔
145
                        {value, {subscribers, S}, OptN} -> {S, OptN};
588✔
146
                        _ -> {[], Opts}
×
147
                    end,
148
    SOpts = misc:term_to_expr(Opts2),
588✔
149
    Timestamp = case lists:keyfind(hibernation_time, 1, Opts) of
588✔
150
                    false -> <<"1970-01-02 00:00:00">>;
×
151
                    {_, undefined} -> <<"1970-01-02 00:00:00">>;
588✔
152
                    {_, Time} -> usec_to_sql_timestamp(Time)
×
153
                end,
154
    F = fun () ->
588✔
155
                ?SQL_UPSERT_T(
588✔
156
                   "muc_room",
157
                   ["!name=%(Name)s",
158
                    "!host=%(Host)s",
159
                    "server_host=%(LServer)s",
160
                    "opts=%(SOpts)s",
161
                    "created_at=%(Timestamp)t"]),
162
                case ChangesHints of
588✔
163
                    Changes when is_list(Changes) ->
164
                        [change_room(Host, Name, Change) || Change <- Changes];
588✔
165
                    _ ->
166
                        ejabberd_sql:sql_query_t(
×
167
                          ?SQL("delete from muc_room_subscribers where "
×
168
                               "room=%(Name)s and host=%(Host)s")),
169
                        [change_room(Host, Name, {add_subscription, JID, Nick, Nodes})
×
170
                         || {JID, Nick, Nodes} <- Subs]
×
171
                end
172
        end,
173
    ejabberd_sql:sql_transaction(LServer, F).
588✔
174

175
store_changes(LServer, Host, Name, Changes) ->
176
    F = fun () ->
108✔
177
                [change_room(Host, Name, Change) || Change <- Changes]
108✔
178
        end,
179
    ejabberd_sql:sql_transaction(LServer, F).
108✔
180

181
change_room(Host, Room, {add_subscription, JID, Nick, Nodes}) ->
182
    SJID = jid:encode(JID),
54✔
183
    SNodes = misc:term_to_expr(Nodes),
54✔
184
    ?SQL_UPSERT_T(
54✔
185
       "muc_room_subscribers",
186
       ["!jid=%(SJID)s",
187
        "!host=%(Host)s",
188
        "!room=%(Room)s",
189
        "nick=%(Nick)s",
190
        "nodes=%(SNodes)s"]);
191
change_room(Host, Room, {del_subscription, JID}) ->
192
    SJID = jid:encode(JID),
54✔
193
    ejabberd_sql:sql_query_t(?SQL("delete from muc_room_subscribers where "
54✔
194
                                  "room=%(Room)s and host=%(Host)s and jid=%(SJID)s"));
195
change_room(Host, Room, Change) ->
196
    ?ERROR_MSG("Unsupported change on room ~ts@~ts: ~p", [Room, Host, Change]).
×
197

198
restore_room(LServer, Host, Name) ->
199
    case catch ejabberd_sql:sql_query(
282✔
200
                 LServer,
201
                 ?SQL("select @(opts)s from muc_room where name=%(Name)s"
380✔
202
                      " and host=%(Host)s")) of
203
        {selected, [{Opts}]} ->
204
            OptsD = ejabberd_sql:decode_term(Opts),
×
205
            case catch ejabberd_sql:sql_query(
×
206
                LServer,
207
                ?SQL("select @(jid)s, @(nick)s, @(nodes)s from muc_room_subscribers where room=%(Name)s"
×
208
                     " and host=%(Host)s")) of
209
                {selected, []} ->
210
                    OptsR = mod_muc:opts_to_binary(OptsD),
×
211
                    case lists:keymember(subscribers, 1, OptsD) of
×
212
                        true ->
213
                            store_room(LServer, Host, Name, OptsR, undefined);
×
214
                        _ ->
215
                            ok
×
216
                    end,
217
                    OptsR;
×
218
                {selected, Subs} ->
219
                    SubData = lists:map(
×
220
                             fun({Jid, Nick, Nodes}) ->
221
                                 {jid:decode(Jid), Nick, ejabberd_sql:decode_term(Nodes)}
×
222
                             end, Subs),
223
                    Opts2 = lists:keystore(subscribers, 1, OptsD, {subscribers, SubData}),
×
224
                    mod_muc:opts_to_binary(Opts2);
×
225
                _ ->
226
                    {error, db_failure}
×
227
            end;
228
        {selected, _} ->
229
            error;
282✔
230
        _ ->
231
            {error, db_failure}
×
232
    end.
233

234
forget_room(LServer, Host, Name) ->
235
    F = fun () ->
540✔
236
                ejabberd_sql:sql_query_t(
540✔
237
                  ?SQL("delete from muc_room where name=%(Name)s"
724✔
238
                       " and host=%(Host)s")),
239
                ejabberd_sql:sql_query_t(
540✔
240
                    ?SQL("delete from muc_room_subscribers where room=%(Name)s"
724✔
241
                       " and host=%(Host)s"))
242
        end,
243
    ejabberd_sql:sql_transaction(LServer, F).
540✔
244

245
can_use_nick(LServer, ServiceOrRoom, JID, Nick) ->
246
    SJID = jid:encode(jid:tolower(jid:remove_resource(JID))),
546✔
247
    SqlQuery = case (jid:decode(ServiceOrRoom))#jid.lserver of
546✔
248
                   ServiceOrRoom ->
249
                       ?SQL("select @(jid)s from muc_registered "
×
250
                            "where nick=%(Nick)s"
251
                            " and host=%(ServiceOrRoom)s");
252
                   Service ->
253
                       ?SQL("select @(jid)s from muc_registered "
546✔
254
                            "where nick=%(Nick)s"
255
                            " and (host=%(ServiceOrRoom)s or host=%(Service)s)")
256
               end,
257
    case catch ejabberd_sql:sql_query(LServer, SqlQuery) of
546✔
258
        {selected, [{SJID1}]} -> SJID == SJID1;
×
259
        _ -> true
546✔
260
    end.
261

262
get_rooms_without_subscribers(LServer, Host) ->
263
    case catch ejabberd_sql:sql_query(
×
264
        LServer,
265
        ?SQL("select @(name)s, @(opts)s from muc_room"
×
266
             " where host=%(Host)s")) of
267
        {selected, RoomOpts} ->
268
            lists:map(
×
269
                fun({Room, Opts}) ->
270
                    OptsD = ejabberd_sql:decode_term(Opts),
×
271
                    #muc_room{name_host = {Room, Host},
×
272
                              opts = mod_muc:opts_to_binary(OptsD)}
273
                end, RoomOpts);
274
        _Err ->
275
            []
×
276
    end.
277

278
get_hibernated_rooms_older_than(LServer, Host, Timestamp) ->
279
    TimestampS = usec_to_sql_timestamp(Timestamp),
×
280
    case catch ejabberd_sql:sql_query(
×
281
        LServer,
282
        ?SQL("select @(name)s, @(opts)s from muc_room"
×
283
             " where host=%(Host)s and created_at < %(TimestampS)t and created_at > '1970-01-02 00:00:00'")) of
284
        {selected, RoomOpts} ->
285
            lists:map(
×
286
                fun({Room, Opts}) ->
287
                    OptsD = ejabberd_sql:decode_term(Opts),
×
288
                    #muc_room{name_host = {Room, Host},
×
289
                              opts = mod_muc:opts_to_binary(OptsD)}
290
                end, RoomOpts);
291
        _Err ->
292
            []
×
293
    end.
294

295
get_rooms(LServer, Host) ->
296
    case catch ejabberd_sql:sql_query(
6✔
297
                 LServer,
298
                 ?SQL("select @(name)s, @(opts)s from muc_room"
12✔
299
                      " where host=%(Host)s")) of
300
        {selected, RoomOpts} ->
301
            case catch ejabberd_sql:sql_query(
6✔
302
                LServer,
303
                ?SQL("select @(room)s, @(jid)s, @(nick)s, @(nodes)s from muc_room_subscribers"
12✔
304
                     " where host=%(Host)s")) of
305
                {selected, Subs} ->
306
                    SubsD = lists:foldl(
6✔
307
                        fun({Room, Jid, Nick, Nodes}, Dict) ->
308
                                Sub = {jid:decode(Jid),
×
309
                                       Nick, ejabberd_sql:decode_term(Nodes)},
310
                                maps:update_with(
×
311
                                  Room,
312
                                  fun(SubAcc) ->
313
                                          [Sub | SubAcc]
×
314
                                  end,
315
                                  [Sub],
316
                                  Dict)
317
                        end, maps:new(), Subs),
318
            lists:map(
6✔
319
              fun({Room, Opts}) ->
320
                            OptsD = ejabberd_sql:decode_term(Opts),
×
321
                            OptsD2 = case {maps:find(Room, SubsD), lists:keymember(subscribers, 1, OptsD)} of
×
322
                                         {_, true} ->
323
                                             store_room(LServer, Host, Room, mod_muc:opts_to_binary(OptsD), undefined),
×
324
                                             OptsD;
×
325
                                         {{ok, SubsI}, false} ->
326
                                             lists:keystore(subscribers, 1, OptsD, {subscribers, SubsI});
×
327
                                         _ ->
328
                                             OptsD
×
329
                            end,
330
                      #muc_room{name_host = {Room, Host},
×
331
                                      opts = mod_muc:opts_to_binary(OptsD2)}
332
              end, RoomOpts);
333
        _Err ->
334
                    []
×
335
            end;
336
        _Err ->
337
            []
×
338
    end.
339

340
get_nick(LServer, Host, From) ->
341
    SJID = jid:encode(jid:tolower(jid:remove_resource(From))),
84✔
342
    case catch ejabberd_sql:sql_query(
84✔
343
                 LServer,
344
                 ?SQL("select @(nick)s from muc_registered where"
116✔
345
                      " jid=%(SJID)s and host=%(Host)s")) of
346
        {selected, [{Nick}]} -> Nick;
48✔
347
        _ -> error
36✔
348
    end.
349

350
get_nicks(LServer, Host) ->
351
    case catch ejabberd_sql:sql_query(LServer,
×
352
                                      ?SQL("select @(jid)s, @(nick)s from muc_registered where"
×
353
                                           " host=%(Host)s"))
354
    of
355
        {selected, Results} ->
356
            lists:map(fun({JidBin, Nick}) ->
×
357
                         {User, Server, _Resource} =
×
358
                             jid:tolower(
359
                                 jid:decode(JidBin)),
360
                         {User, Server, Nick}
×
361
                      end,
362
                      Results);
363
        _ ->
364
            error
×
365
    end.
366

367
set_nick(LServer, ServiceOrRoom, From, Nick) ->
368
    JID = jid:encode(jid:tolower(jid:remove_resource(From))),
48✔
369
    F = fun () ->
48✔
370
                case Nick of
48✔
371
                    <<"">> ->
372
                        ejabberd_sql:sql_query_t(
24✔
373
                          ?SQL("delete from muc_registered where"
36✔
374
                               " jid=%(JID)s and host=%(ServiceOrRoom)s")),
375
                        ok;
24✔
376
                    _ ->
377
                        Service = (jid:decode(ServiceOrRoom))#jid.lserver,
24✔
378
                        SqlQuery = case (ServiceOrRoom == Service) of
24✔
379
                                       true ->
380
                                           ?SQL("select @(jid)s, @(host)s from muc_registered "
24✔
381
                                                "where nick=%(Nick)s"
382
                                                " and host=%(ServiceOrRoom)s");
383
                                       false ->
384
                                           ?SQL("select @(jid)s, @(host)s from muc_registered "
×
385
                                                "where nick=%(Nick)s"
386
                                                " and (host=%(ServiceOrRoom)s or host=%(Service)s)")
387
                                   end,
388
                        Allow = case ejabberd_sql:sql_query_t(SqlQuery) of
24✔
389
                                    {selected, []}
390
                                      when (ServiceOrRoom == Service) ->
391
                                        %% Registering in the service...
392
                                        %% check if nick is registered for some room in this service
393
                                        {selected, NickRegistrations} =
18✔
394
                                            ejabberd_sql:sql_query_t(
395
                                              ?SQL("select @(jid)s, @(host)s from muc_registered "
28✔
396
                                                   "where nick=%(Nick)s")),
397
                                        not lists:any(fun({_NRJid, NRServiceOrRoom}) ->
18✔
398
                                                              Service == (jid:decode(NRServiceOrRoom))#jid.lserver end,
×
399
                                                      NickRegistrations);
400
                                    {selected, []} ->
401
                                        %% Nick not registered in any service or room
402
                                        true;
×
403
                                    {selected, [{_J, Host}]}
404
                                      when (Host == Service) and (ServiceOrRoom /= Service) ->
405
                                        %% Registering in a room, but the nick is already registered in the service
406
                                        false;
×
407
                                    {selected, [{J, _Host}]} ->
408
                                        %% Registering in room (or service) a nick that is
409
                                        %% already registered in this room (or service)
410
                                        %% Only the owner of this registration can use the nick
411
                                        J == JID
6✔
412
                                end,
413
                        if Allow ->
24✔
414
                                ?SQL_UPSERT_T(
18✔
415
                                  "muc_registered",
416
                                  ["!jid=%(JID)s",
417
                                   "!host=%(ServiceOrRoom)s",
418
                                   "server_host=%(LServer)s",
419
                                   "nick=%(Nick)s"]),
420
                                ok;
18✔
421
                           true ->
422
                                false
6✔
423
                        end
424
                end
425
        end,
426
    ejabberd_sql:sql_transaction(LServer, F).
48✔
427

428
set_affiliation(_ServerHost, _Room, _Host, _JID, _Affiliation, _Reason) ->
429
    {error, not_implemented}.
54✔
430

431
set_affiliations(_ServerHost, _Room, _Host, _Affiliations) ->
432
    {error, not_implemented}.
264✔
433

434
get_affiliation(_ServerHost, _Room, _Host, _LUser, _LServer) ->
435
    {error, not_implemented}.
2,478✔
436

437
get_affiliations(_ServerHost, _Room, _Host) ->
438
    {error, not_implemented}.
258✔
439

440
search_affiliation(_ServerHost, _Room, _Host, _Affiliation) ->
441
    {error, not_implemented}.
66✔
442

443
register_online_room(ServerHost, Room, Host, Pid) ->
444
    PidS = misc:encode_pid(Pid),
282✔
445
    NodeS = erlang:atom_to_binary(node(Pid), latin1),
282✔
446
    case ?SQL_UPSERT(ServerHost,
282✔
447
                     "muc_online_room",
448
                     ["!name=%(Room)s",
449
                      "!host=%(Host)s",
450
                      "server_host=%(ServerHost)s",
451
                      "node=%(NodeS)s",
452
                      "pid=%(PidS)s"]) of
453
        ok ->
454
            ok;
282✔
455
        Err ->
456
            Err
×
457
    end.
458

459
unregister_online_room(ServerHost, Room, Host, Pid) ->
460
    %% TODO: report errors
461
    PidS = misc:encode_pid(Pid),
282✔
462
    NodeS = erlang:atom_to_binary(node(Pid), latin1),
282✔
463
    ejabberd_sql:sql_query(
282✔
464
      ServerHost,
465
      ?SQL("delete from muc_online_room where name=%(Room)s and "
380✔
466
           "host=%(Host)s and node=%(NodeS)s and pid=%(PidS)s")).
467

468
find_online_room(ServerHost, Room, Host) ->
469
    case ejabberd_sql:sql_query(
3,192✔
470
           ServerHost,
471
           ?SQL("select @(pid)s, @(node)s from muc_online_room where "
4,260✔
472
                "name=%(Room)s and host=%(Host)s")) of
473
        {selected, [{PidS, NodeS}]} ->
474
            try {ok, misc:decode_pid(PidS, NodeS)}
2,616✔
475
            catch _:{bad_node, _} -> error
×
476
            end;
477
        {selected, []} ->
478
            error;
576✔
479
        _Err ->
480
            error
×
481
    end.
482

483
find_online_room_by_pid(ServerHost, Pid) ->
484
    PidS = misc:encode_pid(Pid),
282✔
485
    NodeS = erlang:atom_to_binary(node(Pid), latin1),
282✔
486
    case ejabberd_sql:sql_query(
282✔
487
        ServerHost,
488
        ?SQL("select @(name)s, @(host)s from muc_online_room where "
380✔
489
             "node=%(NodeS)s and pid=%(PidS)s")) of
490
        {selected, [{Room, Host}]} ->
491
            {ok, Room, Host};
282✔
492
        {selected, []} ->
493
            error;
×
494
        _Err ->
495
            error
×
496
    end.
497

498
count_online_rooms(ServerHost, Host) ->
499
    case ejabberd_sql:sql_query(
24✔
500
           ServerHost,
501
           ?SQL("select @(count(*))d from muc_online_room "
36✔
502
                "where host=%(Host)s")) of
503
        {selected, [{Num}]} ->
504
            Num;
24✔
505
        _Err ->
506
            0
×
507
    end.
508

509
get_online_rooms(ServerHost, Host, _RSM) ->
510
    case ejabberd_sql:sql_query(
42✔
511
           ServerHost,
512
           ?SQL("select @(name)s, @(pid)s, @(node)s from muc_online_room "
60✔
513
                "where host=%(Host)s")) of
514
        {selected, Rows} ->
515
            lists:flatmap(
42✔
516
              fun({Room, PidS, NodeS}) ->
517
                      try [{Room, Host, misc:decode_pid(PidS, NodeS)}]
48✔
518
                      catch _:{bad_node, _} -> []
×
519
                      end
520
              end, Rows);
521
        _Err ->
522
            []
×
523
    end.
524

525
rsm_supported() ->
526
    false.
36✔
527

528
register_online_user(ServerHost, {U, S, R}, Room, Host) ->
529
    NodeS = erlang:atom_to_binary(node(), latin1),
456✔
530
    case ?SQL_UPSERT(ServerHost, "muc_online_users",
456✔
531
                     ["!username=%(U)s",
532
                      "!server=%(S)s",
533
                      "!resource=%(R)s",
534
                      "!name=%(Room)s",
535
                      "!host=%(Host)s",
536
                      "server_host=%(ServerHost)s",
537
                      "node=%(NodeS)s"]) of
538
        ok ->
539
            ok;
456✔
540
        Err ->
541
            Err
×
542
    end.
543

544
unregister_online_user(ServerHost, {U, S, R}, Room, Host) ->
545
    %% TODO: report errors
546
    ejabberd_sql:sql_query(
456✔
547
      ServerHost,
548
      ?SQL("delete from muc_online_users where username=%(U)s and "
612✔
549
           "server=%(S)s and resource=%(R)s and name=%(Room)s and "
550
           "host=%(Host)s")).
551

552
count_online_rooms_by_user(ServerHost, U, S) ->
553
    case ejabberd_sql:sql_query(
534✔
554
           ServerHost,
555
           ?SQL("select @(count(*))d from muc_online_users where "
716✔
556
                "username=%(U)s and server=%(S)s")) of
557
        {selected, [{Num}]} ->
558
            Num;
534✔
559
        _Err ->
560
            0
×
561
    end.
562

563
get_online_rooms_by_user(ServerHost, U, S) ->
564
    case ejabberd_sql:sql_query(
×
565
           ServerHost,
566
           ?SQL("select @(name)s, @(host)s from muc_online_users where "
×
567
                "username=%(U)s and server=%(S)s")) of
568
        {selected, Rows} ->
569
            Rows;
×
570
        _Err ->
571
            []
×
572
    end.
573

574
export(_Server) ->
575
    [{muc_room,
×
576
      fun(Host, #muc_room{name_host = {Name, RoomHost}, opts = Opts}) ->
577
              case str:suffix(Host, RoomHost) of
×
578
                  true ->
579
                      SOpts = misc:term_to_expr(Opts),
×
580
                      [?SQL("delete from muc_room where name=%(Name)s"
×
581
                            " and host=%(RoomHost)s;"),
582
                       ?SQL_INSERT(
×
583
                          "muc_room",
584
                          ["name=%(Name)s",
585
                           "host=%(RoomHost)s",
586
                           "server_host=%(Host)s",
587
                           "opts=%(SOpts)s"])];
588
                  false ->
589
                      []
×
590
              end
591
      end},
592
     {muc_registered,
593
      fun(Host, #muc_registered{us_host = {{U, S}, RoomHost},
594
                                nick = Nick}) ->
595
              case str:suffix(Host, RoomHost) of
×
596
                  true ->
597
                      SJID = jid:encode(jid:make(U, S)),
×
598
                      [?SQL("delete from muc_registered where"
×
599
                            " jid=%(SJID)s and host=%(RoomHost)s;"),
600
                       ?SQL_INSERT(
×
601
                          "muc_registered",
602
                          ["jid=%(SJID)s",
603
                           "host=%(RoomHost)s",
604
                           "server_host=%(Host)s",
605
                           "nick=%(Nick)s"])];
606
                  false ->
607
                      []
×
608
              end
609
      end}].
610

611
import(_, _, _) ->
612
    ok.
×
613

614
serialize(LServer, BatchSize, undefined) ->
615
    serialize(LServer, BatchSize, {rooms, 0});
×
616
serialize(LServer, BatchSize, {rooms, Offset}) ->
617
    Records =
×
618
    case ejabberd_sql:sql_query(
619
        LServer,
620
        ?SQL("select @(name)s, @(host)s, @(opts)s from muc_room "
×
621
             "where %(LServer)H "
622
             "order by host, name "
623
             "limit %(BatchSize)d offset %(Offset)d")) of
624
        {selected, Data} ->
625
            lists:foldl(
×
626
                fun(_, {error, _} = Err) -> Err;
×
627
                   ({Name, Host, Opts}, Acc) ->
628
                       case ejabberd_sql:sql_query(
×
629
                           LServer,
630
                           ?SQL("select @(jid)s, @(nick)s, @(nodes)s from muc_room_subscribers where room=%(Name)s"
×
631
                                " and host=%(Host)s and %(LServer)H")) of
632
                           {selected, Subs} ->
633
                               SubData = lists:map(
×
634
                                   fun({Jid, Nick, Nodes}) ->
635
                                       {jid:decode(Jid), Nick, ejabberd_sql:decode_term(Nodes)}
×
636
                                   end, Subs),
637
                               OptsD = ejabberd_sql:decode_term(Opts),
×
638
                               Opts2 = lists:keystore(subscribers, 1, OptsD, {subscribers, SubData}),
×
639
                               [#serialize_muc_room_v1{serverhost = LServer, host = Host, name = Name,
×
640
                                                       options = mod_muc:opts_to_binary(Opts2)} | Acc];
641
                           _ ->
642
                               {error, io_lib:format("Error when retrieving list of muc subscribers for room ~s@~s",
×
643
                                                     [Name, Host])}
644
                       end
645
                end, [], Data);
646
        _ ->
647
            {error, io_lib:format("Error when retrieving list of muc rooms",
×
648
                                  [])}
649
    end,
NEW
650
    case Records of
×
NEW
651
        {error, _} = Err -> Err;
×
652
        _ ->
NEW
653
            case length(Records) of
×
654
                Val when Val < BatchSize ->
NEW
655
                    case serialize(LServer, BatchSize - Val, {registrations, 0}) of
×
656
                        {ok, Records2, Next} ->
NEW
657
                            {ok, Records ++ Records2, Next};
×
NEW
658
                        Err -> Err
×
659
                    end;
660
                Val ->
NEW
661
                    {ok, Records, {rooms, Offset + Val}}
×
662
            end
663
    end;
664
serialize(LServer, BatchSize, {registrations, Offset}) ->
665
    Records =
×
666
        case ejabberd_sql:sql_query(
667
            LServer,
668
            ?SQL("select @(jid)s, @(host)s, @(nick)s from muc_registered "
×
669
                 "where %(LServer)H "
670
                 "order by host, jid "
671
                 "limit %(BatchSize)d offset %(Offset)d")) of
672
            {selected, Data} ->
673
                lists:map(
×
674
                    fun({Jid, Host, Nick}) ->
675
                        #serialize_muc_registrations_v1{serverhost = LServer, host = Host, jid = Jid, nick = Nick}
×
676
                    end, Data);
677
            _ ->
678
                {error, io_lib:format("Error when retrieving list of muc registered nicks",
×
679
                                      [])}
680
        end,
681
    {ok, Records, {registrations, Offset + length(Records)}}.
×
682

683
deserialize_start(LServer) ->
684
    ejabberd_sql:sql_query(
×
685
        LServer,
686
        ?SQL("delete from muc_room where %(LServer)H")),
×
687
    ejabberd_sql:sql_query(
×
688
        LServer,
689
        ?SQL("delete from muc_registered where %(LServer)H")).
×
690

691
deserialize(LServer, Batch) ->
692
    lists:foldl(
×
693
        fun(_, {error, _} = Err) ->
694
            Err;
×
695
           (#serialize_muc_room_v1{name = Name, host = Host, options = Opts}, _) ->
696
               case store_room(LServer, Host, Name, Opts, undefined) of
×
697
                   {atomic, _} ->
698
                       ok;
×
699
                   _ -> {error, io_lib:format("Error when writing muc room data", [])}
×
700
               end;
701
           (#serialize_muc_registrations_v1{host = Host, jid = Jid, nick = Nick}, _) ->
702
               case ejabberd_sql:sql_query(LServer,
×
703
                                           ?SQL_INSERT(
×
704
                                               "muc_registered",
705
                                               ["jid=%(Jid)s",
706
                                                "host=%(Host)s",
707
                                                "server_host=%(LServer)s",
708
                                                "nick=%(Nick)s"])) of
709
                   {updated, _} -> ok;
×
710
                   _ -> {error, io_lib:format("Error when writing muc registration data", [])}
×
711
               end
712
        end, ok, Batch).
713

714
get_subscribed_rooms(LServer, Host, Jid) ->
715
    JidS = jid:encode(Jid),
6✔
716
    case ejabberd_sql:sql_query(
6✔
717
           LServer,
718
           ?SQL("select @(room)s, @(nick)s, @(nodes)s from muc_room_subscribers "
12✔
719
                "where jid=%(JidS)s and host=%(Host)s")) of
720
        {selected, Subs} ->
721
            {ok, [{jid:make(Room, Host), Nick, ejabberd_sql:decode_term(Nodes)}
6✔
722
                  || {Room, Nick, Nodes} <- Subs]};
6✔
723
        _Error ->
724
            {error, db_failure}
×
725
    end.
726

727
remove_user(LUser, LServer) ->
728
    SJID = jid:encode(jid:make(LUser, LServer)),
12✔
729
    ejabberd_sql:sql_query(
12✔
730
      LServer,
731
      ?SQL("delete from muc_room_subscribers where jid=%(SJID)s")),
20✔
732
    ok.
12✔
733

734
%%%===================================================================
735
%%% Internal functions
736
%%%===================================================================
737
clean_tables(ServerHost) ->
738
    NodeS = erlang:atom_to_binary(node(), latin1),
12✔
739
    ?DEBUG("Cleaning SQL muc_online_room table...", []),
12✔
740
    case ejabberd_sql:sql_query(
12✔
741
           ServerHost,
742
           ?SQL("delete from muc_online_room where node=%(NodeS)s")) of
20✔
743
        {updated, _} ->
744
            ok;
12✔
745
        Err1 ->
746
            ?ERROR_MSG("Failed to clean 'muc_online_room' table: ~p", [Err1]),
×
747
            Err1
×
748
    end,
749
    ?DEBUG("Cleaning SQL muc_online_users table...", []),
12✔
750
    case ejabberd_sql:sql_query(
12✔
751
           ServerHost,
752
           ?SQL("delete from muc_online_users where node=%(NodeS)s")) of
20✔
753
        {updated, _} ->
754
            ok;
12✔
755
        Err2 ->
756
            ?ERROR_MSG("Failed to clean 'muc_online_users' table: ~p", [Err2]),
×
757
            Err2
×
758
    end.
759

760
usec_to_sql_timestamp(Timestamp) ->
761
    TS = misc:usec_to_now(Timestamp),
×
762
    case calendar:now_to_universal_time(TS) of
×
763
        {{Year, Month, Day}, {Hour, Minute, Second}} ->
764
            list_to_binary(io_lib:format("~4..0B-~2..0B-~2..0B "
×
765
                                         "~2..0B:~2..0B:~2..0B",
766
                                         [Year, Month, Day, Hour, Minute, Second]))
767
    end.
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