./ct_report/coverage/mod_mam_muc_rdbms_arch.COVER.html

1 %%%-------------------------------------------------------------------
2 %%% @author Uvarov Michael <arcusfelis@gmail.com>
3 %%% @copyright (C) 2013, Uvarov Michael
4 %%% @doc RDBMS backend for MUC Message Archive Management.
5 %%% @end
6 %%%-------------------------------------------------------------------
7 -module(mod_mam_muc_rdbms_arch).
8
9 %% ----------------------------------------------------------------------
10 %% Exports
11
12 %% gen_mod handlers
13 -export([start/2, stop/1, supported_features/0]).
14
15 %% MAM hook handlers
16 -behaviour(ejabberd_gen_mam_archive).
17 -behaviour(gen_mod).
18 -behaviour(mongoose_module_metrics).
19
20 -callback encode(term()) -> binary().
21 -callback decode(binary()) -> term().
22
23 -export([archive_size/4,
24 archive_message/3,
25 lookup_messages/3,
26 remove_archive/4,
27 remove_domain/3]).
28
29 -export([get_mam_muc_gdpr_data/3]).
30
31 %% Called from mod_mam_muc_rdbms_async_pool_writer
32 -export([prepare_message/2, retract_message/2, prepare_insert/2]).
33 -export([extend_params_with_sender_id/2]).
34
35 -ignore_xref([behaviour_info/1, remove_archive/4, remove_domain/3]).
36
37 %% ----------------------------------------------------------------------
38 %% Imports
39
40 -include("mongoose.hrl").
41 -include("jlib.hrl").
42 -include_lib("exml/include/exml.hrl").
43 -include("mongoose_rsm.hrl").
44 -include("mongoose_mam.hrl").
45
46 %% ----------------------------------------------------------------------
47 %% Types
48
49 -type env_vars() :: mod_mam_rdbms_arch:env_vars().
50 -type host_type() :: mongooseim:host_type().
51
52 %% ----------------------------------------------------------------------
53 %% gen_mod callbacks
54 %% Starting and stopping functions for users' archives
55
56 -spec start(host_type(), _) -> ok.
57 start(HostType, _Opts) ->
58
:-(
start_hooks(HostType),
59
:-(
register_prepared_queries(),
60
:-(
ok.
61
62 -spec stop(host_type()) -> ok.
63 stop(HostType) ->
64
:-(
stop_hooks(HostType).
65
66 -spec supported_features() -> [atom()].
67 supported_features() ->
68
:-(
[dynamic_domains].
69
70 -spec get_mam_muc_gdpr_data(ejabberd_gen_mam_archive:mam_pm_gdpr_data(),
71 host_type(), jid:jid()) ->
72 ejabberd_gen_mam_archive:mam_muc_gdpr_data().
73 get_mam_muc_gdpr_data(Acc, HostType, #jid{luser = LUser, lserver = LServer} = _UserJID) ->
74
:-(
case mod_mam:archive_id(LServer, LUser) of
75 undefined ->
76
:-(
Acc;
77 SenderID ->
78 %% We don't know the real room JID here, use FakeEnv
79
:-(
FakeEnv = env_vars(HostType, jid:make(<<>>, <<>>, <<>>)),
80
:-(
{selected, Rows} = extract_gdpr_messages(HostType, SenderID),
81
:-(
[mam_decoder:decode_muc_gdpr_row(Row, FakeEnv) || Row <- Rows] ++ Acc
82 end.
83
84 %% ----------------------------------------------------------------------
85 %% Add hooks for mod_mam
86
87 -spec start_hooks(host_type()) -> ok.
88 start_hooks(HostType) ->
89
:-(
ejabberd_hooks:add(hooks(HostType)).
90
91 -spec stop_hooks(host_type()) -> ok.
92 stop_hooks(HostType) ->
93
:-(
ejabberd_hooks:delete(hooks(HostType)).
94
95 hooks(HostType) ->
96
:-(
NoWriter = gen_mod:get_module_opt(HostType, ?MODULE, no_writer, false),
97 case NoWriter of
98 true ->
99
:-(
[];
100 false ->
101
:-(
[{mam_muc_archive_message, HostType, ?MODULE, archive_message, 50}]
102
:-(
end ++
103 [{mam_muc_archive_size, HostType, ?MODULE, archive_size, 50},
104 {mam_muc_lookup_messages, HostType, ?MODULE, lookup_messages, 50},
105 {mam_muc_remove_archive, HostType, ?MODULE, remove_archive, 50},
106 {remove_domain, HostType, ?MODULE, remove_domain, 50},
107 {get_mam_muc_gdpr_data, HostType, ?MODULE, get_mam_muc_gdpr_data, 50}].
108
109 %% ----------------------------------------------------------------------
110 %% SQL queries
111
112 register_prepared_queries() ->
113
:-(
prepare_insert(insert_mam_muc_message, 1),
114
:-(
mongoose_rdbms:prepare(mam_muc_archive_remove, mam_muc_message, [room_id],
115 <<"DELETE FROM mam_muc_message "
116 "WHERE room_id = ?">>),
117
:-(
mongoose_rdbms:prepare(mam_muc_remove_domain, mam_muc_message, ['mam_server_user.server'],
118 <<"DELETE FROM mam_muc_message "
119 "WHERE room_id IN (SELECT id FROM mam_server_user where server = ?)">>),
120
:-(
mongoose_rdbms:prepare(mam_muc_remove_domain_users, mam_server_user, [server],
121 <<"DELETE FROM mam_server_user WHERE server = ?">>),
122
:-(
mongoose_rdbms:prepare(mam_muc_make_tombstone, mam_muc_message, [message, room_id, id],
123 <<"UPDATE mam_muc_message SET message = ?, search_body = '' "
124 "WHERE room_id = ? AND id = ?">>),
125
:-(
{LimitSQL, LimitMSSQL} = rdbms_queries:get_db_specific_limits_binaries(1),
126
:-(
mongoose_rdbms:prepare(mam_muc_select_messages_to_retract_on_origin_id, mam_muc_message,
127 [room_id, sender_id, origin_id],
128 <<"SELECT ", LimitMSSQL/binary,
129 " id, message FROM mam_muc_message"
130 " WHERE room_id = ? AND sender_id = ? "
131 " AND origin_id = ?"
132 " ORDER BY id DESC ", LimitSQL/binary>>),
133
:-(
mongoose_rdbms:prepare(mam_muc_select_messages_to_retract_on_stanza_id, mam_muc_message,
134 [room_id, sender_id, id],
135 <<"SELECT ", LimitMSSQL/binary,
136 " origin_id, message FROM mam_muc_message"
137 " WHERE room_id = ? AND sender_id = ? "
138 " AND id = ?"
139 " ORDER BY id DESC ", LimitSQL/binary>>),
140
:-(
mongoose_rdbms:prepare(mam_muc_extract_gdpr_messages, mam_muc_message, [sender_id],
141 <<"SELECT id, message FROM mam_muc_message "
142 " WHERE sender_id = ? ORDER BY id">>).
143
144 %% ----------------------------------------------------------------------
145 %% Declarative logic
146
147 db_mappings() ->
148
:-(
[#db_mapping{column = id, param = message_id, format = int},
149 #db_mapping{column = room_id, param = archive_id, format = int},
150 #db_mapping{column = sender_id, param = sender_id, format = int},
151 #db_mapping{column = nick_name, param = source_jid, format = jid_resource},
152 #db_mapping{column = origin_id, param = origin_id, format = maybe_string},
153 #db_mapping{column = message, param = packet, format = xml},
154 #db_mapping{column = search_body, param = packet, format = search}].
155
156 lookup_fields() ->
157
:-(
[#lookup_field{op = equal, column = room_id, param = archive_id, required = true},
158 #lookup_field{op = ge, column = id, param = start_id},
159 #lookup_field{op = le, column = id, param = end_id},
160 #lookup_field{op = equal, column = nick_name, param = remote_resource},
161 #lookup_field{op = like, column = search_body, param = norm_search_text, value_maker = search_words}].
162
163 -spec env_vars(host_type(), jid:jid()) -> env_vars().
164 env_vars(HostType, ArcJID) ->
165 %% Please, minimize the usage of the host field.
166 %% It's only for passing into RDBMS.
167
:-(
#{host_type => HostType,
168 archive_jid => ArcJID,
169 table => mam_muc_message,
170 index_hint_fn => fun index_hint_sql/1,
171 columns_sql_fn => fun columns_sql/1,
172 column_to_id_fn => fun column_to_id/1,
173 lookup_fn => fun lookup_query/5,
174 decode_row_fn => fun row_to_uniform_format/2,
175 has_message_retraction => mod_mam_utils:has_message_retraction(mod_mam_muc, HostType),
176 has_full_text_search => mod_mam_utils:has_full_text_search(mod_mam_muc, HostType),
177 db_jid_codec => db_jid_codec(HostType, ?MODULE),
178 db_message_codec => db_message_codec(HostType, ?MODULE)}.
179
180 row_to_uniform_format(Row, Env) ->
181
:-(
mam_decoder:decode_muc_row(Row, Env).
182
183 -spec index_hint_sql(env_vars()) -> string().
184
:-(
index_hint_sql(_) -> "".
185
186
:-(
columns_sql(lookup) -> "id, nick_name, message";
187
:-(
columns_sql(count) -> "COUNT(*)".
188
189
:-(
column_to_id(id) -> "i";
190
:-(
column_to_id(room_id) -> "u";
191
:-(
column_to_id(nick_name) -> "n";
192
:-(
column_to_id(search_body) -> "s".
193
194 column_names(Mappings) ->
195
:-(
[Column || #db_mapping{column = Column} <- Mappings].
196
197 %% ----------------------------------------------------------------------
198 %% Options
199
200 -spec db_jid_codec(host_type(), module()) -> module().
201 db_jid_codec(HostType, Module) ->
202
:-(
gen_mod:get_module_opt(HostType, Module, db_jid_format, mam_jid_rfc).
203
204 -spec db_message_codec(host_type(), module()) -> module().
205 db_message_codec(HostType, Module) ->
206
:-(
gen_mod:get_module_opt(HostType, Module, db_message_format, mam_message_compressed_eterm).
207
208 -spec get_retract_id(exml:element(), env_vars()) -> none | mod_mam_utils:retraction_id().
209 get_retract_id(Packet, #{has_message_retraction := Enabled}) ->
210
:-(
mod_mam_utils:get_retract_id(Enabled, Packet).
211
212 %% ----------------------------------------------------------------------
213 %% Internal functions and callbacks
214
215 -spec archive_size(Size :: integer(), HostType :: mongooseim:host_type(),
216 ArcId :: mod_mam:archive_id(), ArcJID :: jid:jid()) -> integer().
217 archive_size(Size, HostType, ArcID, ArcJID) when is_integer(Size) ->
218
:-(
Filter = [{equal, room_id, ArcID}],
219
:-(
Env = env_vars(HostType, ArcJID),
220
:-(
Result = lookup_query(count, Env, Filter, unordered, all),
221
:-(
mongoose_rdbms:selected_to_integer(Result).
222
223 extend_params_with_sender_id(HostType, Params = #{remote_jid := SenderJID}) ->
224
:-(
BareSenderJID = jid:to_bare(SenderJID),
225
:-(
SenderID = mod_mam:archive_id_int(HostType, BareSenderJID),
226
:-(
Params#{sender_id => SenderID}.
227
228 -spec archive_message(_Result, HostType :: mongooseim:host_type(),
229 mod_mam:archive_message_params()) -> ok.
230 archive_message(_Result, HostType, Params0 = #{local_jid := ArcJID}) ->
231
:-(
try
232
:-(
Params = extend_params_with_sender_id(HostType, Params0),
233
:-(
Env = env_vars(HostType, ArcJID),
234
:-(
do_archive_message(HostType, Params, Env),
235
:-(
retract_message(HostType, Params, Env),
236
:-(
ok
237 catch error:Reason:StackTrace ->
238
:-(
?LOG_ERROR(#{what => archive_message_failed,
239 host_type => HostType, mam_params => Params0,
240
:-(
reason => Reason, stacktrace => StackTrace}),
241
:-(
erlang:raise(error, Reason, StackTrace)
242 end.
243
244 do_archive_message(HostType, Params, Env) ->
245
:-(
Row = mam_encoder:encode_message(Params, Env, db_mappings()),
246
:-(
{updated, 1} = mongoose_rdbms:execute_successfully(HostType, insert_mam_muc_message, Row).
247
248 %% Retraction logic
249 %% Called after inserting a new message
250 -spec retract_message(mongooseim:host_type(), mod_mam:archive_message_params()) -> ok.
251 retract_message(HostType, #{local_jid := ArcJID} = Params) ->
252
:-(
Env = env_vars(HostType, ArcJID),
253
:-(
retract_message(HostType, Params, Env).
254
255 -spec retract_message(mongooseim:host_type(), mod_mam:archive_message_params(), env_vars()) -> ok.
256 retract_message(HostType, #{archive_id := ArcID, sender_id := SenderID,
257 packet := Packet} = Params, Env) ->
258
:-(
case get_retract_id(Packet, Env) of
259
:-(
none -> ok;
260 RetractionId ->
261
:-(
Info = get_retraction_info(HostType, ArcID, SenderID, RetractionId, Env),
262
:-(
make_tombstone(HostType, ArcID, RetractionId, Info, Params, Env)
263 end.
264
265 get_retraction_info(HostType, ArcID, SenderID, RetractionId, Env) ->
266
:-(
{selected, Rows} =
267 execute_select_messages_to_retract(HostType, ArcID, SenderID, RetractionId),
268
:-(
mam_decoder:decode_retraction_info(Env, Rows, RetractionId).
269
270 make_tombstone(_HostType, ArcID, RetractionId, skip, _Params, _Env) ->
271
:-(
?LOG_INFO(#{what => make_tombstone_failed,
272 text => <<"Message to retract was not found">>,
273
:-(
user_id => ArcID, retraction_context => RetractionId});
274 make_tombstone(HostType, ArcID, _RetractionId,
275 RetractionInfo = #{message_id := MessID}, Params,
276 #{archive_jid := ArcJID} = Env) ->
277
:-(
RetractionInfo1 = mongoose_hooks:mam_muc_retraction(HostType, RetractionInfo, Params),
278
:-(
Tombstone = mod_mam_utils:tombstone(RetractionInfo1, ArcJID),
279
:-(
TombstoneData = mam_encoder:encode_packet(Tombstone, Env),
280
:-(
execute_make_tombstone(HostType, TombstoneData, ArcID, MessID).
281
282 execute_select_messages_to_retract(HostType, ArcID, SenderID, {origin_id, OriginID}) ->
283
:-(
mongoose_rdbms:execute_successfully(HostType, mam_muc_select_messages_to_retract_on_origin_id,
284 [ArcID, SenderID, OriginID]);
285 execute_select_messages_to_retract(HostType, ArcID, SenderID, {stanza_id, BinStanzaId}) ->
286
:-(
StanzaId = mod_mam_utils:external_binary_to_mess_id(BinStanzaId),
287
:-(
mongoose_rdbms:execute_successfully(HostType, mam_muc_select_messages_to_retract_on_stanza_id,
288 [ArcID, SenderID, StanzaId]).
289
290 execute_make_tombstone(HostType, TombstoneData, ArcID, MessID) ->
291
:-(
mongoose_rdbms:execute_successfully(HostType, mam_muc_make_tombstone,
292 [TombstoneData, ArcID, MessID]).
293
294 %% Insert logic
295 -spec prepare_message(mongooseim:host_type(), mod_mam:archive_message_params()) -> list().
296 prepare_message(HostType, Params = #{local_jid := ArcJID}) ->
297
:-(
Env = env_vars(HostType, ArcJID),
298
:-(
mam_encoder:encode_message(Params, Env, db_mappings()).
299
300 -spec prepare_insert(Name :: atom(), NumRows :: pos_integer()) -> ok.
301 prepare_insert(Name, NumRows) ->
302
:-(
Table = mam_muc_message,
303
:-(
Fields = column_names(db_mappings()),
304
:-(
{Query, Fields2} = rdbms_queries:create_bulk_insert_query(Table, Fields, NumRows),
305
:-(
mongoose_rdbms:prepare(Name, Table, Fields2, Query),
306
:-(
ok.
307
308 %% Removal logic
309 -spec remove_archive(Acc :: mongoose_acc:t(), HostType :: mongooseim:host_type(),
310 ArcID :: mod_mam:archive_id(),
311 ArcJID :: jid:jid()) -> mongoose_acc:t().
312 remove_archive(Acc, HostType, ArcID, _ArcJID) ->
313
:-(
mongoose_rdbms:execute_successfully(HostType, mam_muc_archive_remove, [ArcID]),
314
:-(
Acc.
315
316 -spec remove_domain(mongoose_hooks:simple_acc(),
317 mongooseim:host_type(), jid:lserver()) ->
318 mongoose_hooks:simple_acc().
319 remove_domain(Acc, HostType, Domain) ->
320
:-(
SubHosts = get_subhosts(HostType, Domain),
321
:-(
{atomic, _} = mongoose_rdbms:sql_transaction(HostType, fun() ->
322
:-(
[remove_domain_trans(HostType, SubHost) || SubHost <- SubHosts]
323 end),
324
:-(
Acc.
325
326 remove_domain_trans(HostType, MucHost) ->
327
:-(
mongoose_rdbms:execute_successfully(HostType, mam_muc_remove_domain, [MucHost]),
328
:-(
mongoose_rdbms:execute_successfully(HostType, mam_muc_remove_domain_users, [MucHost]).
329
330 get_subhosts(HostType, Domain) ->
331
:-(
MucHostPattern = gen_mod:get_module_opt(HostType, mod_muc, host,
332 mod_muc:default_host()),
333
:-(
LightHostPattern = gen_mod:get_module_opt(HostType, mod_muc_light, host,
334 mod_muc_light:default_host()),
335
:-(
MucHost = mongoose_subdomain_utils:get_fqdn(MucHostPattern, Domain),
336
:-(
LightHost = mongoose_subdomain_utils:get_fqdn(LightHostPattern, Domain),
337
:-(
lists:usort([MucHost, LightHost]).
338
339 %% GDPR logic
340 extract_gdpr_messages(HostType, SenderID) ->
341
:-(
mongoose_rdbms:execute_successfully(HostType, mam_muc_extract_gdpr_messages, [SenderID]).
342
343 %% Lookup logic
344 -spec lookup_messages(Result :: any(), HostType :: mongooseim:host_type(), Params :: map()) ->
345 {ok, mod_mam:lookup_result()}.
346 lookup_messages({error, _Reason} = Result, _HostType, _Params) ->
347
:-(
Result;
348 lookup_messages(_Result, HostType, Params = #{owner_jid := ArcJID}) ->
349
:-(
Env = env_vars(HostType, ArcJID),
350
:-(
ExdParams = mam_encoder:extend_lookup_params(Params, Env),
351
:-(
Filter = mam_filter:produce_filter(ExdParams, lookup_fields()),
352
:-(
mam_lookup:lookup(Env, Filter, ExdParams).
353
354 lookup_query(QueryType, Env, Filters, Order, OffsetLimit) ->
355
:-(
mam_lookup_sql:lookup_query(QueryType, Env, Filters, Order, OffsetLimit).
Line Hits Source