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

processone / ejabberd / 603

17 Oct 2023 01:57PM UTC coverage: 32.654% (-0.4%) from 33.021%
603

push

github

badlop
Fixing minor typos in CHANGELOG

13497 of 41333 relevant lines covered (32.65%)

646.75 hits per line

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

52.79
/src/mod_mam_sql.erl
1
%%%-------------------------------------------------------------------
2
%%% File    : mod_mam_sql.erl
3
%%% Author  : Evgeny Khramtsov <ekhramtsov@process-one.net>
4
%%% Created : 15 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
5
%%%
6
%%%
7
%%% ejabberd, Copyright (C) 2002-2023   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_mam_sql).
26

27

28
-behaviour(mod_mam).
29

30
%% API
31
-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
32
         extended_fields/0, store/8, write_prefs/4, get_prefs/2, select/7, export/1, remove_from_archive/3,
33
         is_empty_for_user/2, is_empty_for_room/3, select_with_mucsub/6,
34
         delete_old_messages_batch/4, count_messages_to_delete/3]).
35

36
-include_lib("stdlib/include/ms_transform.hrl").
37
-include_lib("xmpp/include/xmpp.hrl").
38
-include("mod_mam.hrl").
39
-include("logger.hrl").
40
-include("ejabberd_sql_pt.hrl").
41
-include("mod_muc_room.hrl").
42

43
%%%===================================================================
44
%%% API
45
%%%===================================================================
46
init(Host, _Opts) ->
47
    ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
3✔
48
    ok.
3✔
49

50
schemas() ->
51
    [#sql_schema{
3✔
52
        version = 1,
53
        tables =
54
            [#sql_table{
55
                name = <<"archive">>,
56
                columns =
57
                    [#sql_column{name = <<"username">>, type = text},
58
                     #sql_column{name = <<"server_host">>, type = text},
59
                     #sql_column{name = <<"timestamp">>, type = bigint},
60
                     #sql_column{name = <<"peer">>, type = text},
61
                     #sql_column{name = <<"bare_peer">>, type = text},
62
                     #sql_column{name = <<"xml">>, type = {text, big}},
63
                     #sql_column{name = <<"txt">>, type = {text, big}},
64
                     #sql_column{name = <<"id">>, type = bigserial},
65
                     #sql_column{name = <<"kind">>, type = {text, 10}},
66
                     #sql_column{name = <<"nick">>, type = text},
67
                     #sql_column{name = <<"created_at">>, type = timestamp,
68
                                 default = true}],
69
                indices = [#sql_index{
70
                              columns = [<<"server_host">>, <<"username">>, <<"timestamp">>]},
71
                           #sql_index{
72
                              columns = [<<"server_host">>, <<"username">>, <<"peer">>]},
73
                           #sql_index{
74
                              columns = [<<"server_host">>, <<"username">>, <<"bare_peer">>]},
75
                           #sql_index{
76
                              columns = [<<"server_host">>, <<"timestamp">>]}
77
                          ],
78
                post_create =
79
                    fun(mysql, _) ->
80
                            ejabberd_sql:sql_query_t(
×
81
                              <<"CREATE FULLTEXT INDEX i_archive_txt ON archive(txt);">>);
82
                       (_, _) ->
83
                            ok
×
84
                    end},
85
             #sql_table{
86
                name = <<"archive_prefs">>,
87
                columns =
88
                    [#sql_column{name = <<"username">>, type = text},
89
                     #sql_column{name = <<"server_host">>, type = text},
90
                     #sql_column{name = <<"def">>, type = text},
91
                     #sql_column{name = <<"always">>, type = text},
92
                     #sql_column{name = <<"never">>, type = text},
93
                     #sql_column{name = <<"created_at">>, type = timestamp,
94
                                 default = true}],
95
                indices = [#sql_index{
96
                              columns = [<<"server_host">>, <<"username">>],
97
                              unique = true}]}]}].
98

99
remove_user(LUser, LServer) ->
100
    ejabberd_sql:sql_query(
354✔
101
      LServer,
102
      ?SQL("delete from archive where username=%(LUser)s and %(LServer)H")),
354✔
103
    ejabberd_sql:sql_query(
354✔
104
      LServer,
105
      ?SQL("delete from archive_prefs where username=%(LUser)s and %(LServer)H")).
354✔
106

107
remove_room(LServer, LName, LHost) ->
108
    LUser = jid:encode({LName, LHost, <<>>}),
264✔
109
    remove_user(LUser, LServer).
264✔
110

111
remove_from_archive(LUser, LServer, none) ->
112
    case ejabberd_sql:sql_query(LServer,
×
113
                                ?SQL("delete from archive where username=%(LUser)s and %(LServer)H")) of
×
114
        {error, Reason} -> {error, Reason};
×
115
        _ -> ok
×
116
    end;
117
remove_from_archive(LUser, LServer, #jid{} = WithJid) ->
118
    Peer = jid:encode(jid:remove_resource(WithJid)),
×
119
    case ejabberd_sql:sql_query(LServer,
×
120
                                ?SQL("delete from archive where username=%(LUser)s and %(LServer)H and bare_peer=%(Peer)s")) of
×
121
        {error, Reason} -> {error, Reason};
×
122
        _ -> ok
×
123
    end;
124
remove_from_archive(LUser, LServer, StanzaId) ->
125
    case ejabberd_sql:sql_query(LServer,
×
126
                                ?SQL("delete from archive where username=%(LUser)s and %(LServer)H and timestamp=%(StanzaId)d")) of
×
127
        {error, Reason} -> {error, Reason};
×
128
        _ -> ok
×
129
    end.
130

131
count_messages_to_delete(ServerHost, TimeStamp, Type) ->
132
    TS = misc:now_to_usec(TimeStamp),
×
133
    Res =
×
134
    case Type of
135
        all ->
136
            ejabberd_sql:sql_query(
×
137
                ServerHost,
138
                ?SQL("select count(*) from archive"
×
139
                     " where timestamp < %(TS)d and %(ServerHost)H"));
140
        _ ->
141
            SType = misc:atom_to_binary(Type),
×
142
            ejabberd_sql:sql_query(
×
143
                ServerHost,
144
                ?SQL("select @(count(*))d from archive"
×
145
                     " where timestamp < %(TS)d"
146
                     " and kind=%(SType)s"
147
                     " and %(ServerHost)H"))
148
    end,
149
    case Res of
×
150
        {selected, [Count]} ->
151
            {ok, Count};
×
152
        _ ->
153
            error
×
154
    end.
155

156
delete_old_messages_batch(ServerHost, TimeStamp, Type, Batch) ->
157
    TS = misc:now_to_usec(TimeStamp),
×
158
    Res =
×
159
    case Type of
160
        all ->
161
            ejabberd_sql:sql_query(
×
162
                ServerHost,
163
                ?SQL("delete from archive"
×
164
                     " where timestamp < %(TS)d and %(ServerHost)H limit %(Batch)d"));
165
        _ ->
166
            SType = misc:atom_to_binary(Type),
×
167
            ejabberd_sql:sql_query(
×
168
                ServerHost,
169
                ?SQL("delete from archive"
×
170
                     " where timestamp < %(TS)d"
171
                     " and kind=%(SType)s"
172
                     " and %(ServerHost)H limit %(Batch)d"))
173
    end,
174
    case Res of
×
175
        {updated, Count} ->
176
            {ok, Count};
×
177
        {error, _} = Error ->
178
            Error
×
179
    end.
180

181
delete_old_messages(ServerHost, TimeStamp, Type) ->
182
    TS = misc:now_to_usec(TimeStamp),
×
183
    case Type of
×
184
        all ->
185
            ejabberd_sql:sql_query(
×
186
              ServerHost,
187
              ?SQL("delete from archive"
×
188
                   " where timestamp < %(TS)d and %(ServerHost)H"));
189
        _ ->
190
            SType = misc:atom_to_binary(Type),
×
191
            ejabberd_sql:sql_query(
×
192
              ServerHost,
193
              ?SQL("delete from archive"
×
194
                   " where timestamp < %(TS)d"
195
                   " and kind=%(SType)s"
196
                   " and %(ServerHost)H"))
197
    end,
198
    ok.
×
199

200
extended_fields() ->
201
    [{withtext, <<"">>}].
18✔
202

203
store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir, TS) ->
204
    SUser = case Type of
789✔
205
                chat -> LUser;
723✔
206
                groupchat -> jid:encode({LUser, LHost, <<>>})
66✔
207
            end,
208
    BarePeer = jid:encode(
789✔
209
                 jid:tolower(
210
                   jid:remove_resource(Peer))),
211
    LPeer = jid:encode(
789✔
212
              jid:tolower(Peer)),
213
    Body = fxml:get_subtag_cdata(Pkt, <<"body">>),
789✔
214
    SType = misc:atom_to_binary(Type),
789✔
215
    SqlType = ejabberd_option:sql_type(LServer),
789✔
216
    XML = case mod_mam_opt:compress_xml(LServer) of
789✔
217
              true ->
218
                  J1 = case Type of
×
219
                              chat -> jid:encode({LUser, LHost, <<>>});
×
220
                              groupchat -> SUser
×
221
                          end,
222
                  xml_compress:encode(Pkt, J1, LPeer);
×
223
              _ ->
224
                  fxml:element_to_binary(Pkt)
789✔
225
          end,
226
        case SqlType of
789✔
227
          mssql -> case ejabberd_sql:sql_query(
×
228
                   LServer,
229
                   ?SQL_INSERT(
×
230
                      "archive",
231
                      ["username=%(SUser)s",
232
                       "server_host=%(LServer)s",
233
                       "timestamp=%(TS)d",
234
                       "peer=%(LPeer)s",
235
                       "bare_peer=%(BarePeer)s",
236
                       "xml=N%(XML)s",
237
                       "txt=N%(Body)s",
238
                       "kind=%(SType)s",
239
                       "nick=%(Nick)s"])) of
240
                {updated, _} ->
241
                    ok;
×
242
                Err ->
243
                    Err
×
244
            end;
245
            _ -> case ejabberd_sql:sql_query(
789✔
246
                   LServer,
247
                   ?SQL_INSERT(
789✔
248
                      "archive",
249
                      ["username=%(SUser)s",
250
                       "server_host=%(LServer)s",
251
                       "timestamp=%(TS)d",
252
                       "peer=%(LPeer)s",
253
                       "bare_peer=%(BarePeer)s",
254
                       "xml=%(XML)s",
255
                       "txt=%(Body)s",
256
                       "kind=%(SType)s",
257
                       "nick=%(Nick)s"])) of
258
                {updated, _} ->
259
                    ok;
789✔
260
                Err ->
261
                    Err
×
262
            end
263
        end.
264

265
write_prefs(LUser, _LServer, #archive_prefs{default = Default,
266
                                           never = Never,
267
                                           always = Always},
268
            ServerHost) ->
269
    SDefault = erlang:atom_to_binary(Default, utf8),
483✔
270
    SAlways = misc:term_to_expr(Always),
483✔
271
    SNever = misc:term_to_expr(Never),
483✔
272
    case ?SQL_UPSERT(
483✔
273
            ServerHost,
483✔
274
            "archive_prefs",
275
            ["!username=%(LUser)s",
276
             "!server_host=%(ServerHost)s",
277
             "def=%(SDefault)s",
278
             "always=%(SAlways)s",
279
             "never=%(SNever)s"]) of
280
        ok ->
281
            ok;
483✔
282
        Err ->
283
            Err
×
284
    end.
285

286
get_prefs(LUser, LServer) ->
287
    case ejabberd_sql:sql_query(
360✔
288
           LServer,
289
           ?SQL("select @(def)s, @(always)s, @(never)s from archive_prefs"
360✔
290
                " where username=%(LUser)s and %(LServer)H")) of
291
        {selected, [{SDefault, SAlways, SNever}]} ->
292
            Default = erlang:binary_to_existing_atom(SDefault, utf8),
339✔
293
            Always = ejabberd_sql:decode_term(SAlways),
339✔
294
            Never = ejabberd_sql:decode_term(SNever),
339✔
295
            {ok, #archive_prefs{us = {LUser, LServer},
339✔
296
                    default = Default,
297
                    always = Always,
298
                    never = Never}};
299
        _ ->
300
            error
21✔
301
    end.
302

303
select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
304
       MAMQuery, RSM, MsgType, Flags) ->
305
    User = case MsgType of
570✔
306
               chat -> LUser;
558✔
307
               _ -> jid:encode(JidArchive)
12✔
308
           end,
309
    {Query, CountQuery} = make_sql_query(User, LServer, MAMQuery, RSM, none),
570✔
310
    do_select_query(LServer, JidRequestor, JidArchive, RSM, MsgType, Query, CountQuery, Flags).
570✔
311

312
-spec select_with_mucsub(binary(), jid(), jid(), mam_query:result(),
313
                             #rsm_set{} | undefined, all | only_count | only_messages) ->
314
                                {[{binary(), non_neg_integer(), xmlel()}], boolean(), non_neg_integer()} |
315
                                {error, db_failure}.
316
select_with_mucsub(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
317
                   MAMQuery, RSM, Flags) ->
318
    Extra = case gen_mod:db_mod(LServer, mod_muc) of
9✔
319
                mod_muc_sql ->
320
                    subscribers_table;
9✔
321
                _ ->
322
                    SubRooms = case mod_muc_admin:find_hosts(LServer) of
×
323
                                   [First|_] ->
324
                                       case mod_muc:get_subscribed_rooms(First, JidRequestor) of
×
325
                                           {ok, L} -> L;
×
326
                                           {error, _} -> []
×
327
                                       end;
328
                                   _ ->
329
                                       []
×
330
                               end,
331
                    [jid:encode(Jid) || {Jid, _, _} <- SubRooms]
×
332
            end,
333
    {Query, CountQuery} = make_sql_query(LUser, LServer, MAMQuery, RSM, Extra),
9✔
334
    do_select_query(LServer, JidRequestor, JidArchive, RSM, chat, Query, CountQuery, Flags).
9✔
335

336
do_select_query(LServer, JidRequestor, #jid{luser = LUser} = JidArchive, RSM,
337
                MsgType, Query, CountQuery, Flags) ->
338
    % TODO from XEP-0313 v0.2: "To conserve resources, a server MAY place a
339
    % reasonable limit on how many stanzas may be pushed to a client in one
340
    % request. If a query returns a number of stanzas greater than this limit
341
    % and the client did not specify a limit using RSM then the server should
342
    % return a policy-violation error to the client." We currently don't do this
343
    % for v0.2 requests, but we do limit #rsm_in.max for v0.3 and newer.
344
    QRes = case Flags of
579✔
345
                   all ->
346
                       {ejabberd_sql:sql_query(LServer, Query), ejabberd_sql:sql_query(LServer, CountQuery)};
573✔
347
                   only_messages ->
348
                       {ejabberd_sql:sql_query(LServer, Query), {selected, ok, [[<<"0">>]]}};
6✔
349
                   only_count ->
350
                       {{selected, ok, []}, ejabberd_sql:sql_query(LServer, CountQuery)}
×
351
               end,
352
    case QRes of
579✔
353
        {{selected, _, Res}, {selected, _, [[Count]]}} ->
354
            {Max, Direction, _} = get_max_direction_id(RSM),
579✔
355
            {Res1, IsComplete} =
579✔
356
            if Max >= 0 andalso Max /= undefined andalso length(Res) > Max ->
357
                if Direction == before ->
144✔
358
                    {lists:nthtail(1, Res), false};
24✔
359
                    true ->
360
                        {lists:sublist(Res, Max), false}
120✔
361
                end;
362
                true ->
363
                    {Res, true}
435✔
364
            end,
365
            MucState = #state{config = #config{anonymous = true}},
579✔
366
            JidArchiveS = jid:encode(jid:remove_resource(JidArchive)),
579✔
367
            {lists:flatmap(
579✔
368
                fun([TS, XML, PeerBin, Kind, Nick]) ->
369
                    case make_archive_el(JidArchiveS, TS, XML, PeerBin, Kind, Nick,
2,196✔
370
                                         MsgType, JidRequestor, JidArchive) of
371
                        {ok, El} ->
372
                            [{TS, binary_to_integer(TS), El}];
2,196✔
373
                        {error, _} ->
374
                            []
×
375
                    end;
376
                   ([User, TS, XML, PeerBin, Kind, Nick]) when User == LUser ->
377
                       case make_archive_el(JidArchiveS, TS, XML, PeerBin, Kind, Nick,
×
378
                                            MsgType, JidRequestor, JidArchive) of
379
                           {ok, El} ->
380
                               [{TS, binary_to_integer(TS), El}];
×
381
                           {error, _} ->
382
                               []
×
383
                       end;
384
                   ([User, TS, XML, PeerBin, Kind, Nick]) ->
385
                       case make_archive_el(User, TS, XML, PeerBin, Kind, Nick,
33✔
386
                                            {groupchat, member, MucState}, JidRequestor,
387
                                            jid:decode(User)) of
388
                           {ok, El} ->
389
                               mod_mam:wrap_as_mucsub([{TS, binary_to_integer(TS), El}],
33✔
390
                                                      JidRequestor);
391
                           {error, _} ->
392
                               []
×
393
                       end
394
                end, Res1), IsComplete, binary_to_integer(Count)};
395
        _ ->
396
            {[], false, 0}
×
397
    end.
398

399
export(_Server) ->
400
    [{archive_prefs,
×
401
      fun(Host, #archive_prefs{us =
402
                {LUser, LServer},
403
                default = Default,
404
                always = Always,
405
                never = Never})
406
          when LServer == Host ->
407
                SDefault = erlang:atom_to_binary(Default, utf8),
×
408
                SAlways = misc:term_to_expr(Always),
×
409
                SNever = misc:term_to_expr(Never),
×
410
                [?SQL_INSERT(
×
411
                    "archive_prefs",
412
                    ["username=%(LUser)s",
413
                     "server_host=%(LServer)s",
414
                     "def=%(SDefault)s",
415
                     "always=%(SAlways)s",
416
                     "never=%(SNever)s"])];
417
          (_Host, _R) ->
418
              []
×
419
      end},
420
     {archive_msg,
421
      fun([Host | HostTail], #archive_msg{us ={LUser, LServer},
422
                id = _ID, timestamp = TS, peer = Peer,
423
                type = Type, nick = Nick, packet = Pkt})
424
          when (LServer == Host) or ([LServer] == HostTail)  ->
425
                TStmp = misc:now_to_usec(TS),
×
426
                SUser = case Type of
×
427
                      chat -> LUser;
×
428
                      groupchat -> jid:encode({LUser, LServer, <<>>})
×
429
                    end,
430
                BarePeer = jid:encode(jid:tolower(jid:remove_resource(Peer))),
×
431
                LPeer = jid:encode(jid:tolower(Peer)),
×
432
                XML = fxml:element_to_binary(Pkt),
×
433
                Body = fxml:get_subtag_cdata(Pkt, <<"body">>),
×
434
                SType = misc:atom_to_binary(Type),
×
435
                SqlType = ejabberd_option:sql_type(Host),
×
436
                case SqlType of
×
437
                        mssql -> [?SQL_INSERT(
×
438
                            "archive",
439
                            ["username=%(SUser)s",
440
                             "server_host=%(LServer)s",
441
                             "timestamp=%(TStmp)d",
442
                             "peer=%(LPeer)s",
443
                             "bare_peer=%(BarePeer)s",
444
                             "xml=N%(XML)s",
445
                             "txt=N%(Body)s",
446
                             "kind=%(SType)s",
447
                             "nick=%(Nick)s"])];
448
                        _ -> [?SQL_INSERT(
×
449
                            "archive",
450
                            ["username=%(SUser)s",
451
                             "server_host=%(LServer)s",
452
                             "timestamp=%(TStmp)d",
453
                             "peer=%(LPeer)s",
454
                             "bare_peer=%(BarePeer)s",
455
                             "xml=%(XML)s",
456
                             "txt=%(Body)s",
457
                             "kind=%(SType)s",
458
                             "nick=%(Nick)s"])]
459
                            end;
460
         (_Host, _R) ->
461
              []
×
462
      end}].
463

464
is_empty_for_user(LUser, LServer) ->
465
    case ejabberd_sql:sql_query(
×
466
           LServer,
467
           ?SQL("select @(1)d from archive"
×
468
                " where username=%(LUser)s and %(LServer)H limit 1")) of
469
        {selected, [{1}]} ->
470
            false;
×
471
        _ ->
472
            true
×
473
    end.
474

475
is_empty_for_room(LServer, LName, LHost) ->
476
    LUser = jid:encode({LName, LHost, <<>>}),
×
477
    is_empty_for_user(LUser, LServer).
×
478

479
%%%===================================================================
480
%%% Internal functions
481
%%%===================================================================
482
make_sql_query(User, LServer, MAMQuery, RSM, ExtraUsernames) ->
483
    Start = proplists:get_value(start, MAMQuery),
579✔
484
    End = proplists:get_value('end', MAMQuery),
579✔
485
    With = proplists:get_value(with, MAMQuery),
579✔
486
    WithText = proplists:get_value(withtext, MAMQuery),
579✔
487
    {Max, Direction, ID} = get_max_direction_id(RSM),
579✔
488
    ODBCType = ejabberd_option:sql_type(LServer),
579✔
489
    ToString = fun(S) -> ejabberd_sql:to_string_literal(ODBCType, S) end,
579✔
490
    LimitClause = if is_integer(Max), Max >= 0, ODBCType /= mssql ->
579✔
491
                          [<<" limit ">>, integer_to_binary(Max+1)];
483✔
492
                     true ->
493
                          []
96✔
494
                  end,
495
    TopClause = if is_integer(Max), Max >= 0, ODBCType == mssql ->
579✔
496
                          [<<" TOP ">>, integer_to_binary(Max+1)];
×
497
                     true ->
498
                          []
579✔
499
                  end,
500
    SubOrderClause = if LimitClause /= []; TopClause /= [] ->
579✔
501
                          <<" ORDER BY timestamp DESC ">>;
483✔
502
                     true ->
503
                          []
96✔
504
                  end,
505
    WithTextClause = if is_binary(WithText), WithText /= <<>> ->
579✔
506
                             [<<" and match (txt) against (">>,
×
507
                              ToString(WithText), <<")">>];
508
                        true ->
509
                             []
579✔
510
                     end,
511
    WithClause = case catch jid:tolower(With) of
579✔
512
                     {_, _, <<>>} ->
513
                         [<<" and bare_peer=">>,
24✔
514
                          ToString(jid:encode(With))];
515
                     {_, _, _} ->
516
                         [<<" and peer=">>,
24✔
517
                          ToString(jid:encode(With))];
518
                     _ ->
519
                         []
531✔
520
                 end,
521
    PageClause = case catch binary_to_integer(ID) of
579✔
522
                     I when is_integer(I), I >= 0 ->
523
                         case Direction of
246✔
524
                             before ->
525
                                 [<<" AND timestamp < ">>, ID];
126✔
526
                             'after' ->
527
                                 [<<" AND timestamp > ">>, ID];
120✔
528
                             _ ->
529
                                 []
×
530
                         end;
531
                     _ ->
532
                         []
333✔
533
                 end,
534
    StartClause = case Start of
579✔
535
                      {_, _, _} ->
536
                          [<<" and timestamp >= ">>,
6✔
537
                           integer_to_binary(misc:now_to_usec(Start))];
538
                      _ ->
539
                          []
573✔
540
                  end,
541
    EndClause = case End of
579✔
542
                    {_, _, _} ->
543
                        [<<" and timestamp <= ">>,
×
544
                         integer_to_binary(misc:now_to_usec(End))];
545
                    _ ->
546
                        []
579✔
547
                end,
548
    SUser = ToString(User),
579✔
549
    SServer = ToString(LServer),
579✔
550

551
    HostMatch = case ejabberd_sql:use_new_schema() of
579✔
552
                    true ->
553
                        [<<" and server_host=", SServer/binary>>];
×
554
                    _ ->
555
                        <<"">>
579✔
556
                end,
557

558
    {UserSel, UserWhere} = case ExtraUsernames of
579✔
559
                               Users when is_list(Users) ->
560
                                   EscUsers = [ToString(U) || U <- [User | Users]],
×
561
                                   {<<" username,">>,
×
562
                                    [<<" username in (">>, str:join(EscUsers, <<",">>), <<")">>]};
563
                               subscribers_table ->
564
                                   SJid = ToString(jid:encode({User, LServer, <<>>})),
9✔
565
                                   RoomName = case ODBCType of
9✔
566
                                                  sqlite ->
567
                                                      <<"room || '@' || host">>;
3✔
568
                                                  _ ->
569
                                                      <<"concat(room, '@', host)">>
6✔
570
                                              end,
571
                                   {<<" username,">>,
9✔
572
                                    [<<" (username = ">>, SUser,
573
                                        <<" or username in (select ">>, RoomName,
574
                                          <<" from muc_room_subscribers where jid=">>, SJid, HostMatch, <<"))">>]};
575
                               _ ->
576
                                   {<<>>, [<<" username=">>, SUser]}
570✔
577
                           end,
578

579
    Query = [<<"SELECT ">>, TopClause, UserSel,
579✔
580
             <<" timestamp, xml, peer, kind, nick"
581
               " FROM archive WHERE">>, UserWhere, HostMatch,
582
             WithClause, WithTextClause,
583
             StartClause, EndClause, PageClause],
584

585
    QueryPage =
579✔
586
        case Direction of
587
            before ->
588
                % ID can be empty because of
589
                % XEP-0059: Result Set Management
590
                % 2.5 Requesting the Last Page in a Result Set
591
                [<<"SELECT">>, UserSel, <<" timestamp, xml, peer, kind, nick FROM (">>,
174✔
592
                 Query, SubOrderClause,
593
                 LimitClause, <<") AS t ORDER BY timestamp ASC;">>];
594
            _ ->
595
                [Query, <<" ORDER BY timestamp ASC ">>,
405✔
596
                 LimitClause, <<";">>]
597
        end,
598
    {QueryPage,
579✔
599
     [<<"SELECT COUNT(*) FROM archive WHERE ">>, UserWhere,
600
      HostMatch, WithClause, WithTextClause,
601
      StartClause, EndClause, <<";">>]}.
602

603
-spec get_max_direction_id(rsm_set() | undefined) ->
604
                                  {integer() | undefined,
605
                                   before | 'after' | undefined,
606
                                   binary()}.
607
get_max_direction_id(RSM) ->
608
    case RSM of
1,158✔
609
        #rsm_set{max = Max, before = Before} when is_binary(Before) ->
610
            {Max, before, Before};
348✔
611
        #rsm_set{max = Max, 'after' = After} when is_binary(After) ->
612
            {Max, 'after', After};
240✔
613
        #rsm_set{max = Max} ->
614
            {Max, undefined, <<>>};
534✔
615
        _ ->
616
            {undefined, undefined, <<>>}
36✔
617
    end.
618

619
-spec make_archive_el(binary(), binary(), binary(), binary(), binary(),
620
                      binary(), _, jid(), jid()) ->
621
                             {ok, xmpp_element()} | {error, invalid_jid |
622
                                                     invalid_timestamp |
623
                                                     invalid_xml}.
624
make_archive_el(User, TS, XML, Peer, Kind, Nick, MsgType, JidRequestor, JidArchive) ->
625
    case xml_compress:decode(XML, User, Peer) of
2,229✔
626
        #xmlel{} = El ->
627
            try binary_to_integer(TS) of
2,229✔
628
                TSInt ->
629
                    try jid:decode(Peer) of
2,229✔
630
                        PeerJID ->
631
                            Now = misc:usec_to_now(TSInt),
2,229✔
632
                            PeerLJID = jid:tolower(PeerJID),
2,229✔
633
                            T = case Kind of
2,229✔
634
                                    <<"">> -> chat;
×
635
                                    null -> chat;
×
636
                                    _ -> misc:binary_to_atom(Kind)
2,229✔
637
                                end,
638
                            mod_mam:msg_to_el(
2,229✔
639
                              #archive_msg{timestamp = Now,
640
                                           id = TS,
641
                                           packet = El,
642
                                           type = T,
643
                                           nick = Nick,
644
                                           peer = PeerLJID},
645
                              MsgType, JidRequestor, JidArchive)
646
                    catch _:{bad_jid, _} ->
647
                            ?ERROR_MSG("Malformed 'peer' field with value "
×
648
                                       "'~ts' detected for user ~ts in table "
649
                                       "'archive': invalid JID",
650
                                       [Peer, jid:encode(JidArchive)]),
×
651
                            {error, invalid_jid}
×
652
                    end
653
            catch _:_ ->
654
                    ?ERROR_MSG("Malformed 'timestamp' field with value '~ts' "
×
655
                               "detected for user ~ts in table 'archive': "
656
                               "not an integer",
657
                               [TS, jid:encode(JidArchive)]),
×
658
                    {error, invalid_timestamp}
×
659
            end;
660
        {error, {_, Reason}} ->
661
            ?ERROR_MSG("Malformed 'xml' field with value '~ts' detected "
×
662
                       "for user ~ts in table 'archive': ~ts",
663
                       [XML, jid:encode(JidArchive), Reason]),
×
664
            {error, invalid_xml}
×
665
    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