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