./ct_report/coverage/mod_offline_rdbms.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : mod_offline_rdbms.erl
3 %%% Author : Alexey Shchepin <alexey@process-one.net>
4 %%% Purpose : Store and manage offline messages in relational database.
5 %%% Created : 5 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
6 %%%
7 %%%
8 %%% ejabberd, Copyright (C) 2002-2011 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
21 %%% along with this program; if not, write to the Free Software
22 %%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 %%%
24 %%%----------------------------------------------------------------------
25
26 -module(mod_offline_rdbms).
27 -behaviour(mod_offline_backend).
28 -export([init/2,
29 pop_messages/2,
30 fetch_messages/2,
31 write_messages/4,
32 count_offline_messages/4,
33 remove_expired_messages/2,
34 remove_old_messages/3,
35 remove_user/3,
36 remove_domain/2]).
37
38 -import(mongoose_rdbms, [prepare/4, execute_successfully/3]).
39
40 -include("jlib.hrl").
41 -include("mod_offline.hrl").
42
43 -spec init(mongooseim:host_type(), gen_mod:module_opts()) -> ok.
44 init(_HostType, _Opts) ->
45 9 prepare_queries(),
46 9 ok.
47
48 prepare_queries() ->
49 9 prepare(offline_insert, offline_message,
50 [username, server, timestamp, expire,
51 from_jid, packet, permanent_fields],
52 <<"INSERT INTO offline_message "
53 "(username, server, timestamp, expire,"
54 " from_jid, packet, permanent_fields) "
55 "VALUES (?, ?, ?, ?, ?, ?, ?)">>),
56 9 {LimitSQL, LimitMSSQL} = rdbms_queries:get_db_specific_limits_binaries(),
57 9 prepare(offline_count_limit, offline_message,
58 rdbms_queries:add_limit_arg(limit, [server, username]),
59 <<"SELECT ", LimitMSSQL/binary,
60 " count(*) FROM offline_message "
61 "WHERE server = ? AND username = ? ", LimitSQL/binary>>),
62 9 prepare(offline_select, offline_message,
63 [server, username, expire],
64 <<"SELECT timestamp, from_jid, packet, permanent_fields "
65 "FROM offline_message "
66 "WHERE server = ? AND username = ? AND "
67 "(expire IS null OR expire > ?) "
68 "ORDER BY timestamp">>),
69 9 prepare(offline_delete, offline_message,
70 [server, username],
71 <<"DELETE FROM offline_message "
72 "WHERE server = ? AND username = ?">>),
73 9 prepare(offline_remove_domain, offline_message,
74 [server],
75 <<"DELETE FROM offline_message WHERE server = ?">>),
76 9 prepare(offline_delete_old, offline_message,
77 [server, timestamp],
78 <<"DELETE FROM offline_message WHERE server = ? AND timestamp < ?">>),
79 9 prepare(offline_delete_expired, offline_message,
80 [server, expire],
81 <<"DELETE FROM offline_message "
82 "WHERE server = ? AND expire IS NOT null AND expire < ?">>).
83
84 -spec execute_count_offline_messages(mongooseim:host_type(), jid:luser(), jid:lserver(),
85 pos_integer()) ->
86 mongoose_rdbms:query_result().
87 execute_count_offline_messages(HostType, LUser, LServer, Limit) ->
88 65 Args = rdbms_queries:add_limit_arg(Limit, [LServer, LUser]),
89 65 execute_successfully(HostType, offline_count_limit, Args).
90
91 -spec execute_fetch_offline_messages(mongooseim:host_type(), jid:luser(), jid:lserver(),
92 integer()) ->
93 mongoose_rdbms:query_result().
94 execute_fetch_offline_messages(HostType, LUser, LServer, ExtTimeStamp) ->
95 82 execute_successfully(HostType, offline_select, [LServer, LUser, ExtTimeStamp]).
96
97 -spec execute_remove_expired_offline_messages(mongooseim:host_type(), jid:lserver(), integer()) ->
98 mongoose_rdbms:query_result().
99 execute_remove_expired_offline_messages(HostType, LServer, ExtTimeStamp) ->
100 6 execute_successfully(HostType, offline_delete_expired, [LServer, ExtTimeStamp]).
101
102 -spec execute_remove_old_offline_messages(mongooseim:host_type(), jid:lserver(), integer()) ->
103 mongoose_rdbms:query_result().
104 execute_remove_old_offline_messages(HostType, LServer, ExtTimeStamp) ->
105 6 execute_successfully(HostType, offline_delete_old, [LServer, ExtTimeStamp]).
106
107 -spec execute_offline_delete(mongooseim:host_type(), jid:luser(), jid:lserver()) ->
108 mongoose_rdbms:query_result().
109 execute_offline_delete(HostType, LUser, LServer) ->
110 109 execute_successfully(HostType, offline_delete, [LServer, LUser]).
111
112 %% Transactions
113
114 -spec pop_offline_messages(mongooseim:host_type(), jid:luser(), jid:server(), integer()) ->
115 mongoose_rdbms:transaction_result().
116 pop_offline_messages(HostType, LUser, LServer, ExtTimeStamp) ->
117 62 F = fun() ->
118 62 Res = execute_fetch_offline_messages(HostType, LUser, LServer, ExtTimeStamp),
119 62 execute_offline_delete(HostType, LUser, LServer),
120 62 Res
121 end,
122 62 mongoose_rdbms:sql_transaction(HostType, F).
123
124 -spec push_offline_messages(mongooseim:host_type(), [list()]) ->
125 mongoose_rdbms:transaction_result().
126 push_offline_messages(HostType, Rows) ->
127 71 F = fun() ->
128 71 [execute_successfully(HostType, offline_insert, Row)
129 71 || Row <- Rows], ok
130 end,
131 71 mongoose_rdbms:sql_transaction(HostType, F).
132
133 %% API functions
134
135 -spec pop_messages(mongooseim:host_type(), jid:jid()) -> {ok, [mod_offline:msg()]} | {error, any()}.
136 pop_messages(HostType, #jid{} = To) ->
137 62 US = {LUser, LServer} = jid:to_lus(To),
138 62 ExtTimeStamp = os:system_time(microsecond),
139 62 case pop_offline_messages(HostType, LUser, LServer, ExtTimeStamp) of
140 {atomic, {selected, Rows}} ->
141 62 {ok, rows_to_records(US, To, Rows)};
142 {aborted, Reason} ->
143
:-(
{error, Reason};
144 {error, Reason} ->
145
:-(
{error, Reason}
146 end.
147
148 %% Fetch messages for GDPR
149 -spec fetch_messages(mongooseim:host_type(), jid:jid()) -> {ok, [mod_offline:msg()]}.
150 fetch_messages(HostType, #jid{} = To) ->
151 20 US = {LUser, LServer} = jid:to_lus(To),
152 20 ExtTimeStamp = os:system_time(microsecond),
153 20 {selected, Rows} = execute_fetch_offline_messages(HostType, LUser, LServer, ExtTimeStamp),
154 20 {ok, rows_to_records(US, To, Rows)}.
155
156 -spec write_messages(mongooseim:host_type(), jid:luser(), jid:lserver(), [mod_offline:msg()]) ->
157 ok | {error, any()}.
158 write_messages(HostType, LUser, LServer, Msgs) ->
159 71 Rows = [record_to_row(LUser, LServer, Msg) || Msg <- Msgs],
160 71 case push_offline_messages(HostType, Rows) of
161 {atomic, ok} ->
162 71 ok;
163 Other ->
164
:-(
{error, Other}
165 end.
166
167 -spec count_offline_messages(mongooseim:host_type(), jid:luser(), jid:lserver(),
168 mod_offline:msg_count()) ->
169 mod_offline:msg_count().
170 count_offline_messages(HostType, LUser, LServer, Limit) ->
171 65 Result = execute_count_offline_messages(HostType, LUser, LServer, Limit),
172 65 mongoose_rdbms:selected_to_integer(Result).
173
174 -spec remove_expired_messages(mongooseim:host_type(), jid:lserver()) -> {ok, mod_offline:msg_count()}.
175 remove_expired_messages(HostType, LServer) ->
176 6 TimeStamp = os:system_time(microsecond),
177 6 Result = execute_remove_expired_offline_messages(HostType, LServer, TimeStamp),
178 6 updated_ok(Result).
179
180 -spec remove_old_messages(mongooseim:host_type(), jid:lserver(), mod_offline:timestamp()) ->
181 {ok, mod_offline:msg_count()}.
182 remove_old_messages(HostType, LServer, TimeStamp) ->
183 6 Result = execute_remove_old_offline_messages(HostType, LServer, TimeStamp),
184 6 updated_ok(Result).
185
186 -spec remove_user(mongooseim:host_type(), jid:luser(), jid:lserver()) -> ok.
187 remove_user(HostType, LUser, LServer) ->
188 47 execute_offline_delete(HostType, LUser, LServer),
189 47 ok.
190
191 -spec remove_domain(mongooseim:host_type(), jid:lserver()) -> ok.
192 remove_domain(HostType, Domain) ->
193
:-(
mongoose_rdbms:execute_successfully(HostType, offline_remove_domain, [Domain]),
194
:-(
ok.
195
196 %% Pure helper functions
197 record_to_row(LUser, LServer,
198 #offline_msg{timestamp = TimeStamp, expire = Expire, from = From,
199 packet = Packet, permanent_fields = PermanentFields}) ->
200 133 ExtExpire = maybe_encode_timestamp(Expire),
201 133 ExtFrom = jid:to_binary(From),
202 133 ExtPacket = exml:to_binary(Packet),
203 133 ExtFields = encode_permanent_fields(PermanentFields),
204 133 prepare_offline_message(LUser, LServer, TimeStamp, ExtExpire,
205 ExtFrom, ExtPacket, ExtFields).
206
207 prepare_offline_message(LUser, LServer, ExtTimeStamp, ExtExpire, ExtFrom, ExtPacket, ExtFields) ->
208 133 [LUser, LServer, ExtTimeStamp, ExtExpire, ExtFrom, ExtPacket, ExtFields].
209
210 encode_permanent_fields(Fields) ->
211 133 term_to_binary(Fields).
212
213 111 maybe_encode_timestamp(never) -> null;
214 22 maybe_encode_timestamp(TimeStamp) -> TimeStamp.
215
216 rows_to_records(US, To, Rows) ->
217 82 [row_to_record(US, To, Row) || Row <- Rows].
218
219 row_to_record(US, To, {ExtTimeStamp, ExtFrom, ExtPacket, ExtPermanentFields}) ->
220 109 {ok, Packet} = exml:parse(ExtPacket),
221 109 TimeStamp = mongoose_rdbms:result_to_integer(ExtTimeStamp),
222 109 From = jid:from_binary(ExtFrom),
223 109 PermanentFields = extract_permanent_fields(ExtPermanentFields),
224 109 #offline_msg{us = US, timestamp = TimeStamp, expire = never,
225 from = From, to = To, packet = Packet,
226 permanent_fields = PermanentFields}.
227
228 extract_permanent_fields(null) ->
229
:-(
[]; %% This is needed in transition period when upgrading to MongooseIM above 3.5.0
230 extract_permanent_fields(Escaped) ->
231 109 Bin = mongoose_rdbms:unescape_binary(global, Escaped),
232 109 binary_to_term(Bin).
233
234 12 updated_ok({updated, Count}) -> {ok, Count}.
Line Hits Source