./ct_report/coverage/mod_muc_rdbms.COVER.html

1 -module(mod_muc_rdbms).
2 -include("mod_muc.hrl").
3 -include("mongoose_logger.hrl").
4
5 -export([init/2,
6 store_room/4,
7 restore_room/3,
8 forget_room/3,
9 get_rooms/2,
10 can_use_nick/4,
11 get_nick/3,
12 set_nick/4,
13 unset_nick/3,
14 remove_domain/3
15 ]).
16
17 -ignore_xref([can_use_nick/4, forget_room/3, get_nick/3, get_rooms/2, remove_domain/3, init/2,
18 restore_room/3, set_nick/4, store_room/4, unset_nick/3]).
19
20 -import(mongoose_rdbms, [prepare/4, execute_successfully/3]).
21
22 %% Host of MUC service
23 -type muc_host() :: jid:server().
24
25 %% User's JID. Can be on another domain accessible over FED.
26 %% Only bare part (user@host) is important.
27 -type client_jid() :: jid:jid().
28 -type room_id() :: pos_integer().
29 -type room_opts() :: [{OptionName :: atom(), OptionValue :: term()}].
30 -type aff() :: atom().
31
32
33 -spec init(mongooseim:host_type(), ModuleOpts :: list()) -> ok.
34 init(HostType, _Opts) ->
35 38 prepare_queries(HostType),
36 38 ok.
37
38 prepare_queries(HostType) ->
39 %% Queries to muc_rooms table
40 38 prepare(muc_insert_room, muc_rooms,
41 [muc_host, room_name, options],
42 <<"INSERT INTO muc_rooms (muc_host, room_name, options)"
43 " VALUES (?, ?, ?)">>),
44 38 prepare(muc_select_room_id, muc_rooms,
45 [muc_host, room_name],
46 <<"SELECT id FROM muc_rooms "
47 "WHERE muc_host = ? AND room_name = ?">>),
48 38 prepare(muc_select_room, muc_rooms,
49 [muc_host, room_name],
50 <<"SELECT id, options FROM muc_rooms "
51 "WHERE muc_host = ? AND room_name = ?">>),
52 38 prepare(muc_delete_room, muc_rooms,
53 [muc_host, room_name],
54 <<"DELETE FROM muc_rooms WHERE muc_host = ? AND room_name = ?">>),
55 38 prepare(muc_rooms_remove_domain, muc_rooms,
56 [muc_host],
57 <<"DELETE FROM muc_rooms WHERE muc_host = ?">>),
58 38 prepare(muc_select_rooms, muc_rooms, [muc_host],
59 <<"SELECT id, room_name, options FROM muc_rooms WHERE muc_host = ?">>),
60 %% Queries to muc_room_aff table
61 38 prepare(muc_insert_aff, muc_room_aff,
62 [room_id, luser, lserver, resource, aff],
63 <<"INSERT INTO muc_room_aff"
64 " (room_id, luser, lserver, resource, aff)"
65 " VALUES(?, ?, ?, ?, ?)">>),
66 38 prepare(muc_select_aff, muc_room_aff,
67 [room_id],
68 <<"SELECT luser, lserver, resource, aff "
69 "FROM muc_room_aff WHERE room_id = ?">>),
70 38 prepare(muc_delete_aff, muc_room_aff, [room_id],
71 <<"DELETE FROM muc_room_aff WHERE room_id = ?">>),
72 38 prepare(muc_room_aff_remove_room_domain, muc_room_aff,
73 ['muc_rooms.muc_host'],
74 <<"DELETE FROM muc_room_aff WHERE room_id IN "
75 "(SELECT id FROM muc_rooms WHERE muc_host = ?)">>),
76 38 prepare(muc_room_aff_remove_user_domain, muc_room_aff,
77 [lserver],
78 <<"DELETE FROM muc_room_aff WHERE lserver = ?">>),
79 %% Queries to muc_registered table
80 38 prepare(muc_select_nick_user, muc_registered,
81 [muc_host, lserver, nick],
82 <<"SELECT luser FROM muc_registered WHERE muc_host = ?"
83 " AND lserver = ? AND nick = ?">>),
84 38 prepare(muc_select_nick, muc_registered,
85 [muc_host, lserver, luser],
86 <<"SELECT nick FROM muc_registered WHERE muc_host = ?"
87 " AND lserver = ? AND luser = ?">>),
88 38 prepare(muc_delete_nick, muc_registered,
89 [muc_host, lserver, luser],
90 <<"DELETE FROM muc_registered WHERE muc_host = ?"
91 " AND lserver = ? AND luser = ?">>),
92 38 prepare(muc_registered_remove_room_domain, muc_registered,
93 [muc_host],
94 <<"DELETE FROM muc_registered WHERE muc_host = ?">>),
95 38 prepare(muc_registered_remove_user_domain, muc_registered,
96 [lserver],
97 <<"DELETE FROM muc_room_aff WHERE lserver = ?">>),
98 38 rdbms_queries:prepare_upsert(HostType, muc_nick_upsert, muc_registered,
99 [<<"muc_host">>, <<"luser">>, <<"lserver">>, <<"nick">>],
100 [<<"nick">>],
101 [<<"muc_host">>, <<"luser">>, <<"lserver">>]),
102 38 ok.
103
104 %% Room API functions
105
106 -spec remove_domain(mongooseim:host_type(), muc_host(), jid:lserver()) -> ok.
107 remove_domain(HostType, MucHost, Domain) ->
108 1 F = fun() ->
109 1 mongoose_rdbms:execute_successfully(
110 HostType, muc_registered_remove_room_domain, [MucHost]),
111 1 mongoose_rdbms:execute_successfully(
112 HostType, muc_registered_remove_user_domain, [Domain]),
113 1 mongoose_rdbms:execute_successfully(
114 HostType, muc_room_aff_remove_room_domain, [MucHost]),
115 1 mongoose_rdbms:execute_successfully(
116 HostType, muc_room_aff_remove_user_domain, [Domain]),
117 1 mongoose_rdbms:execute_successfully(
118 HostType, muc_rooms_remove_domain, [MucHost]),
119 1 ok
120 end,
121 1 {atomic, ok} = mongoose_rdbms:sql_transaction(HostType, F),
122 1 ok.
123
124 -spec store_room(mongooseim:host_type(), muc_host(), mod_muc:room(), room_opts()) ->
125 ok | {error, term()}.
126 store_room(HostType, MucHost, RoomName, Opts) ->
127 302 Affs = proplists:get_value(affiliations, Opts),
128 302 NewOpts = proplists:delete(affiliations, Opts),
129 302 ExtOpts = jiffy:encode({NewOpts}),
130 302 F = fun() ->
131 302 forget_room_transaction(HostType, MucHost, RoomName),
132 302 store_room_transaction(HostType, MucHost, RoomName, ExtOpts, Affs)
133 end,
134 302 {atomic, Res} = mongoose_rdbms:sql_transaction(HostType, F),
135 302 Res.
136
137 -spec restore_room(mongooseim:host_type(), muc_host(), mod_muc:room()) ->
138 {ok, room_opts()} | {error, room_not_found} | {error, term()}.
139 restore_room(HostType, MucHost, RoomName) ->
140 56 case execute_select_room(HostType, MucHost, RoomName) of
141 {selected, [{ExtRoomID, ExtOpts}]} ->
142 7 RoomID = mongoose_rdbms:result_to_integer(ExtRoomID),
143 7 FullOpts = get_full_options(HostType, ExtOpts, RoomID),
144 7 {ok, FullOpts};
145 {selected, []} ->
146 49 {error, room_not_found}
147 end.
148
149 -spec forget_room(mongooseim:host_type(), muc_host(), mod_muc:room()) ->
150 ok | {error, term()}.
151 forget_room(HostType, MucHost, RoomName) ->
152 247 F = fun() -> forget_room_transaction(HostType, MucHost, RoomName) end,
153 247 {atomic, _Res} = mongoose_rdbms:sql_transaction(HostType, F),
154 247 ok.
155
156 %% Room helper functions
157
158 -spec get_rooms(mongooseim:host_type(), muc_host()) -> {ok, [#muc_room{}]}.
159 get_rooms(HostType, MucHost) ->
160 64 {selected, RoomRows} = execute_select_rooms(HostType, MucHost),
161 64 RoomRecs = [handle_room_row(HostType, MucHost, Row) || Row <- RoomRows],
162 64 {ok, RoomRecs}.
163
164 handle_room_row(HostType, MucHost, {ExtRoomID, RoomName, ExtOpts}) ->
165 37 RoomID = mongoose_rdbms:result_to_integer(ExtRoomID),
166 37 FullOpts = get_full_options(HostType, ExtOpts, RoomID),
167 37 #muc_room{name_host = {RoomName, MucHost}, opts = FullOpts}.
168
169 get_full_options(HostType, ExtOpts, RoomID) ->
170 44 {selected, Affs} = execute_select_aff(HostType, RoomID),
171 44 decode_opts(ExtOpts, Affs).
172
173 %% Nick API functions
174
175 -spec can_use_nick(mongooseim:host_type(), muc_host(), client_jid(), mod_muc:nick()) -> boolean().
176 can_use_nick(HostType, MucHost, Jid, Nick) ->
177 662 {UserU, UserS} = jid:to_lus(Jid),
178 662 case execute_select_nick_user(HostType, MucHost, UserS, Nick) of
179 660 {selected, []} -> true;
180 2 {selected, [{U}]} -> U == UserU
181 end.
182
183 %% Get nick associated with jid client_jid() across muc_host() domain
184 -spec get_nick(mongooseim:host_type(), muc_host(), client_jid()) ->
185 {ok, mod_muc:nick()} | {error, not_registered}.
186 get_nick(HostType, MucHost, Jid) ->
187 24 {UserU, UserS} = jid:to_lus(Jid),
188 24 case execute_select_nick(HostType, MucHost, UserU, UserS) of
189 9 {selected, []} -> {error, not_registered};
190 15 {selected, [{Nick}]} -> {ok, Nick}
191 end.
192
193 %% Register nick
194 -spec set_nick(mongooseim:host_type(), muc_host(), client_jid(), mod_muc:nick()) -> ok | {error, term()}.
195 set_nick(HostType, MucHost, Jid, Nick) when is_binary(Nick), Nick =/= <<>> ->
196 15 CanUseNick = can_use_nick(HostType, MucHost, Jid, Nick),
197 15 store_nick_transaction(HostType, MucHost, Jid, Nick, CanUseNick).
198
199 %% Unregister nick
200 %% Unregistered nicks can be used by someone else
201 -spec unset_nick(mongooseim:host_type(), muc_host(), client_jid()) -> ok.
202 unset_nick(HostType, MucHost, Jid) ->
203 6 {UserU, UserS} = jid:to_lus(Jid),
204 6 execute_delete_nick(HostType, MucHost, UserU, UserS),
205 6 ok.
206
207 %% Transaction body functions
208
209 store_nick_transaction(_HostType, _MucHost, _Jid, _Nick, false) ->
210
:-(
{error, conflict};
211 store_nick_transaction(HostType, MucHost, Jid, Nick, true) ->
212 15 {LU, LS} = jid:to_lus(Jid),
213 15 InsertParams = [MucHost, LU, LS, Nick],
214 15 UpdateParams = [Nick],
215 15 UniqueKeyValues = [MucHost, LU, LS],
216 15 case rdbms_queries:execute_upsert(HostType, muc_nick_upsert,
217 InsertParams, UpdateParams, UniqueKeyValues) of
218 15 {updated, _} -> ok;
219
:-(
Error -> Error
220 end.
221
222 -spec store_room_transaction(mongooseim:host_type(), muc_host(), jid:luser(), binary(), term()) -> ok.
223 store_room_transaction(HostType, MucHost, RoomName, ExtOpts, Affs) ->
224 302 execute_insert_room(HostType, MucHost, RoomName, ExtOpts),
225 302 Result = execute_select_room_id(HostType, MucHost, RoomName),
226 302 RoomID = mongoose_rdbms:selected_to_integer(Result),
227 302 store_aff(HostType, RoomID, Affs),
228 302 ok.
229
230 store_aff(_HostType, _, undefined) ->
231 3 ok;
232 store_aff(HostType, RoomID, Affs) ->
233 299 F = fun({{UserU, UserS, Resource}, Aff}) ->
234 327 ExtAff = aff_atom2db(Aff),
235 327 execute_insert_aff(HostType, RoomID, UserU, UserS, Resource, ExtAff)
236 end,
237 299 lists:foreach(F, Affs).
238
239 forget_room_transaction(HostType, MucHost, RoomName) ->
240 549 case execute_select_room_id(HostType, MucHost, RoomName) of
241 {selected, [{ExtRoomID}]} ->
242 301 RoomID = mongoose_rdbms:result_to_integer(ExtRoomID),
243 301 execute_delete_affs(HostType, RoomID),
244 301 execute_delete_room(HostType, MucHost, RoomName),
245 301 ok;
246 {selected, []} ->
247 248 {error, not_exists}
248 end.
249
250 %% Execute call functions
251
252 -spec execute_insert_room(mongooseim:host_type(), muc_host(), jid:luser(), binary()) -> ok.
253 execute_insert_room(HostType, MucHost, RoomName, ExtOpts) ->
254 302 Args = [MucHost, RoomName, ExtOpts],
255 302 execute_successfully(HostType, muc_insert_room, Args),
256 302 ok.
257
258 -spec execute_insert_aff(mongooseim:host_type(), RoomID :: room_id(),
259 UserU :: jid:luser(), UserS :: jid:lserver(),
260 Res :: binary(), ExtAff :: pos_integer()) -> ok.
261 execute_insert_aff(HostType, RoomID, UserU, UserS, Res, ExtAff) ->
262 327 Args = [RoomID, UserU, UserS, Res, ExtAff],
263 327 execute_successfully(HostType, muc_insert_aff, Args),
264 327 ok.
265
266 -spec execute_select_aff(mongooseim:host_type(), room_id()) -> term().
267 execute_select_aff(HostType, RoomID) ->
268 44 execute_successfully(HostType, muc_select_aff, [RoomID]).
269
270 -spec execute_select_room_id(mongooseim:host_type(), muc_host(), jid:luser()) -> term().
271 execute_select_room_id(HostType, MucHost, RoomName) ->
272 851 execute_successfully(HostType, muc_select_room_id, [MucHost, RoomName]).
273
274 -spec execute_select_room(mongooseim:host_type(), muc_host(), jid:luser()) -> term().
275 execute_select_room(HostType, MucHost, RoomName) ->
276 56 execute_successfully(HostType, muc_select_room, [MucHost, RoomName]).
277
278 -spec execute_delete_affs(mongooseim:host_type(), room_id()) -> term().
279 execute_delete_affs(HostType, RoomID) ->
280 301 execute_successfully(HostType, muc_delete_aff, [RoomID]).
281
282 -spec execute_delete_room(mongooseim:host_type(), muc_host(), jid:luser()) -> term().
283 execute_delete_room(HostType, MucHost, RoomName) ->
284 301 execute_successfully(HostType, muc_delete_room, [MucHost, RoomName]).
285
286 -spec execute_select_rooms(mongooseim:host_type(), muc_host()) -> term().
287 execute_select_rooms(HostType, MucHost) ->
288 64 execute_successfully(HostType, muc_select_rooms, [MucHost]).
289
290 -spec execute_select_nick_user(mongooseim:host_type(), muc_host(), jid:luser(), mod_muc:nick()) -> term().
291 execute_select_nick_user(HostType, MucHost, UserS, Nick) ->
292 662 execute_successfully(HostType, muc_select_nick_user, [MucHost, UserS, Nick]).
293
294 -spec execute_select_nick(mongooseim:host_type(), muc_host(), jid:luser(), jid:lserver()) -> term().
295 execute_select_nick(HostType, MucHost, UserU, UserS) ->
296 24 execute_successfully(HostType, muc_select_nick, [MucHost, UserS, UserU]).
297
298 -spec execute_delete_nick(mongooseim:host_type(), muc_host(), jid:luser(), jid:lserver()) -> term().
299 execute_delete_nick(HostType, MucHost, UserU, UserS) ->
300 6 execute_successfully(HostType, muc_delete_nick, [MucHost, UserS, UserU]).
301
302 %% Conversion functions
303
304 -spec aff_atom2db(aff()) -> pos_integer().
305 297 aff_atom2db(owner) -> 1;
306 6 aff_atom2db({owner, _}) -> 1;
307 1 aff_atom2db(member) -> 2;
308 12 aff_atom2db({member, _}) -> 2;
309
:-(
aff_atom2db(admin) -> 3;
310 6 aff_atom2db({admin, _}) -> 3;
311
:-(
aff_atom2db(outcast) -> 4;
312 5 aff_atom2db({outcast, _}) -> 4;
313
:-(
aff_atom2db(_Other) -> 5.
314
315 -spec aff_db2atom(pos_integer()) -> aff().
316 35 aff_db2atom(1) -> owner;
317 1 aff_db2atom(2) -> member;
318
:-(
aff_db2atom(3) -> admin;
319
:-(
aff_db2atom(4) -> outcast;
320
:-(
aff_db2atom(5) -> none.
321
322 decode_opts(ExtOpts, Affs) ->
323 44 {Opts} = jiffy:decode(ExtOpts),
324 44 [{affiliations, decode_affs(Affs)} | keys_as_atoms(Opts)].
325
326 decode_affs(Affs) ->
327 44 [{{UserU, UserS, Res}, aff_db2atom(mongoose_rdbms:result_to_integer(Aff))}
328 44 || {UserU, UserS, Res, Aff} <- Affs].
329
330 keys_as_atoms(KVs) ->
331 44 [{binary_to_existing_atom(Key, utf8), Value}
332 44 || {Key, Value} <- KVs].
Line Hits Source