./ct_report/coverage/mod_roster_rdbms.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : mod_roster_rdbms.erl
3 %%% Author : MichaƂ Piotrowski <michal.piotrowski@erlang-solutions.com>
4 %%% Purpose : mod_roster_rdbms rdbms backend
5 %%%
6 %%%
7 %%% ejabberd, Copyright (C) 2002-2014 ProcessOne
8 %%% MongooseIM, Copyright (C) 2015 Erlang Solutions Ltd.
9 %%%
10 %%%----------------------------------------------------------------------
11
12 -module(mod_roster_rdbms).
13 -include("mod_roster.hrl").
14 -include("jlib.hrl").
15 -include("mongoose.hrl").
16 -include("mongoose_logger.hrl").
17
18 -behaviour(mod_roster_backend).
19
20 %% API
21 -export([init/2,
22 transaction/2,
23 read_roster_version/3,
24 write_roster_version/5,
25 get_roster/3,
26 get_roster_entry/6,
27 get_subscription_lists/3,
28 roster_subscribe_t/2,
29 update_roster_t/2,
30 del_roster_t/4,
31 remove_user_t/3,
32 remove_domain_t/2]).
33
34 %% mod_roster backend API
35
36 -spec init(mongooseim:host_type(), gen_mod:module_opts()) -> ok.
37 init(HostType, _Opts) ->
38 172 prepare_queries(HostType),
39 172 ok.
40
41 -spec transaction(mongooseim:host_type(), fun(() -> any())) ->
42 {aborted, any()} | {atomic, any()} | {error, any()}.
43 transaction(HostType, F) ->
44 6081 mongoose_rdbms:sql_transaction(HostType, F).
45
46 -spec read_roster_version(mongooseim:host_type(), jid:luser(), jid:lserver()) -> binary() | error.
47 read_roster_version(HostType, LUser, LServer) ->
48 5 case mongoose_rdbms:execute_successfully(HostType, roster_version_get, [LServer, LUser]) of
49 4 {selected, [{Version}]} -> Version;
50 1 {selected, []} -> error
51 end.
52
53 -spec write_roster_version(mongooseim:host_type(), jid:luser(), jid:lserver(),
54 mod_roster:transaction_state(), mod_roster:version()) -> ok.
55 write_roster_version(HostType, LUser, LServer, _TransactionState, Ver) ->
56 3 version_upsert(HostType, LUser, LServer, Ver),
57 3 ok.
58
59 -spec get_roster(mongooseim:host_type(), jid:luser(), jid:lserver()) -> [mod_roster:roster()].
60 get_roster(HostType, LUser, LServer) ->
61 5222 {selected, Rows} = execute_roster_get(HostType, LUser, LServer),
62 5222 {selected, GroupRows} =
63 mongoose_rdbms:execute_successfully(HostType, roster_group_get, [LServer, LUser]),
64 5222 decode_roster_rows(LServer, Rows, GroupRows).
65
66 -spec get_roster_entry(mongooseim:host_type(), jid:luser(), jid:lserver(), mod_roster:contact(),
67 mod_roster:transaction_state(), mod_roster:entry_format()) ->
68 mod_roster:roster() | does_not_exist.
69 get_roster_entry(HostType, LUser, LServer, LJID, _TransactionState, full) ->
70 1140 case get_roster_entry(HostType, LUser, LServer, LJID) of
71 does_not_exist ->
72 268 does_not_exist;
73 Rec ->
74 872 Groups = get_groups_by_jid(HostType, LUser, LServer, LJID),
75 872 record_with_groups(Rec, Groups)
76 end;
77 get_roster_entry(HostType, LUser, LServer, LJID, _TransactionState, short) ->
78 186 get_roster_entry(HostType, LUser, LServer, LJID).
79
80 -spec get_subscription_lists(mongoose_acc:t(), jid:luser(), jid:lserver()) -> [mod_roster:roster()].
81 get_subscription_lists(Acc, LUser, LServer) ->
82 4813 HostType = mongoose_acc:host_type(Acc),
83 4813 {selected, Rows} = execute_roster_get(HostType, LUser, LServer),
84 4813 [row_to_record(LServer, Row) || Row <- Rows].
85
86 -spec roster_subscribe_t(mongooseim:host_type(), mod_roster:roster()) -> ok.
87 roster_subscribe_t(HostType, Item) ->
88 845 RosterRow = record_to_row(Item),
89 845 roster_upsert(HostType, RosterRow),
90 845 ok.
91
92 -spec update_roster_t(mongooseim:host_type(), mod_roster:roster()) -> ok.
93 update_roster_t(HostType, Item) ->
94 151 RosterRow = [LServer, LUser, BinJID | _] = record_to_row(Item),
95 151 GroupRows = groups_to_rows(Item),
96 151 roster_upsert(HostType, RosterRow),
97 151 mongoose_rdbms:execute_successfully(HostType, roster_group_delete_by_jid,
98 [LServer, LUser, BinJID]),
99 151 [mongoose_rdbms:execute_successfully(HostType, roster_group_insert, GroupRow)
100 151 || GroupRow <- GroupRows],
101 151 ok.
102
103 -spec del_roster_t(mongooseim:host_type(), jid:luser(), jid:lserver(), mod_roster:contact()) -> ok.
104 del_roster_t(HostType, LUser, LServer, LJID) ->
105 30 BinJID = jid:to_binary(LJID),
106 30 mongoose_rdbms:execute_successfully(HostType, roster_delete_by_jid,
107 [LServer, LUser, BinJID]),
108 30 mongoose_rdbms:execute_successfully(HostType, roster_group_delete_by_jid,
109 [LServer, LUser, BinJID]),
110 30 ok.
111
112 -spec remove_user_t(mongooseim:host_type(), jid:luser(), jid:lserver()) -> ok.
113 remove_user_t(HostType, LUser, LServer) ->
114 5009 mongoose_rdbms:execute_successfully(HostType, roster_delete, [LServer, LUser]),
115 5009 mongoose_rdbms:execute_successfully(HostType, roster_group_delete, [LServer, LUser]),
116 5009 ok.
117
118 -spec remove_domain_t(mongooseim:host_type(), jid:lserver()) -> ok.
119 remove_domain_t(HostType, Domain) ->
120 20 mongoose_rdbms:execute_successfully(HostType, rosterusers_remove_domain, [Domain]),
121 20 mongoose_rdbms:execute_successfully(HostType, rostergroups_remove_domain, [Domain]),
122 20 mongoose_rdbms:execute_successfully(HostType, roster_version_remove_domain, [Domain]),
123 20 ok.
124
125 %% Query preparation
126
127 prepare_queries(HostType) ->
128 172 mongoose_rdbms:prepare(roster_group_insert, rostergroups, [server, username, jid, grp],
129 <<"INSERT INTO rostergroups(server, username, jid, grp) "
130 "VALUES (?, ?, ?, ?)">>),
131 172 mongoose_rdbms:prepare(roster_version_get, roster_version, [server, username],
132 <<"SELECT version FROM roster_version "
133 "WHERE server = ? AND username = ?">>),
134 172 mongoose_rdbms:prepare(roster_get, rosterusers, [server, username],
135 <<"SELECT ", (roster_fields())/binary,
136 " FROM rosterusers WHERE server = ? AND username = ?">>),
137 172 mongoose_rdbms:prepare(roster_get_by_jid, rostergroups, [server, username, jid],
138 <<"SELECT ", (roster_fields())/binary,
139 " FROM rosterusers WHERE server = ? AND username = ? AND jid = ?">>),
140 172 mongoose_rdbms:prepare(roster_group_get, rostergroups, [server, username],
141 <<"SELECT jid, grp FROM rostergroups WHERE server = ? AND username = ?">>),
142 172 mongoose_rdbms:prepare(roster_group_get_by_jid, rostergroups, [server, username, jid],
143 <<"SELECT grp FROM rostergroups "
144 "WHERE server = ? AND username = ? AND jid = ?">>),
145 172 mongoose_rdbms:prepare(roster_delete, rosterusers, [server, username],
146 <<"DELETE FROM rosterusers WHERE server = ? AND username = ?">>),
147 172 mongoose_rdbms:prepare(roster_group_delete, rostergroups, [server, username],
148 <<"DELETE FROM rostergroups WHERE server = ? AND username = ?">>),
149 172 mongoose_rdbms:prepare(roster_delete_by_jid, rosterusers, [server, username, jid],
150 <<"DELETE FROM rosterusers"
151 " WHERE server = ? AND username = ? AND jid = ?">>),
152 172 mongoose_rdbms:prepare(roster_group_delete_by_jid, rostergroups, [server, username, jid],
153 <<"DELETE FROM rostergroups"
154 " WHERE server = ? AND username = ? AND jid = ?">>),
155 172 mongoose_rdbms:prepare(rosterusers_remove_domain, rosterusers, [server],
156 <<"DELETE FROM rosterusers WHERE server = ?">>),
157 172 mongoose_rdbms:prepare(rostergroups_remove_domain, rostergroups, [server],
158 <<"DELETE FROM rostergroups WHERE server = ?">>),
159 172 mongoose_rdbms:prepare(roster_version_remove_domain, roster_version, [server],
160 <<"DELETE FROM roster_version WHERE server = ?">>),
161 172 prepare_roster_upsert(HostType),
162 172 prepare_version_upsert(HostType),
163 172 ok.
164
165 %% We don't care about `server, subscribe, type' fields
166 roster_fields() ->
167 344 <<"username, jid, nick, subscription, ask, askmessage">>.
168
169 prepare_roster_upsert(HostType) ->
170 172 Fields = [<<"nick">>, <<"subscription">>, <<"ask">>, <<"askmessage">>],
171 172 Filter = [<<"server">>, <<"username">>, <<"jid">>],
172 172 rdbms_queries:prepare_upsert(HostType, roster_upsert, rosterusers,
173 Filter ++ Fields, Fields, Filter).
174
175 prepare_version_upsert(HostType) ->
176 172 Fields = [<<"version">>],
177 172 Filter = [<<"server">>, <<"username">>],
178 172 rdbms_queries:prepare_upsert(HostType, roster_version_upsert, roster_version,
179 Filter ++ Fields, Fields, Filter).
180
181 %% Query Helpers
182
183 -spec execute_roster_get(mongooseim:host_type(), jid:luser(), jid:lserver()) ->
184 mongoose_rdbms:query_result().
185 execute_roster_get(HostType, LUser, LServer) ->
186 10035 mongoose_rdbms:execute_successfully(HostType, roster_get, [LServer, LUser]).
187
188 -spec roster_upsert(mongooseim:host_type(), list()) -> mongoose_rdbms:query_result().
189 roster_upsert(HostType, [LServer, LUser, BinJID | Rest] = RosterRow) ->
190 996 InsertParams = RosterRow,
191 996 UpdateParams = Rest,
192 996 UniqueKeyValues = [LServer, LUser, BinJID],
193 996 {updated, _} = rdbms_queries:execute_upsert(HostType, roster_upsert,
194 InsertParams, UpdateParams, UniqueKeyValues).
195
196 -spec version_upsert(mongooseim:host_type(), jid:luser(), jid:lserver(), mod_roster:version()) ->
197 mongoose_rdbms:query_result().
198 version_upsert(HostType, LUser, LServer, Version) ->
199 3 InsertParams = [LServer, LUser, Version],
200 3 UpdateParams = [Version],
201 3 UniqueKeyValues = [LServer, LUser],
202 3 {updated, _} = rdbms_queries:execute_upsert(HostType, roster_version_upsert,
203 InsertParams, UpdateParams, UniqueKeyValues).
204
205 -spec get_roster_entry(mongooseim:host_type(), jid:luser(), jid:lserver(), jid:simple_jid()) ->
206 mod_roster:roster() | does_not_exist.
207 get_roster_entry(HostType, LUser, LServer, LJID) ->
208 1326 BinJID = jid:to_binary(LJID),
209 1326 {selected, Rows} = mongoose_rdbms:execute_successfully(HostType, roster_get_by_jid,
210 [LServer, LUser, BinJID]),
211 1326 case Rows of
212 420 [] -> does_not_exist;
213 906 [Row] -> row_to_record(LServer, Row)
214 end.
215
216 -spec get_groups_by_jid(mongooseim:host_type(), jid:luser(), jid:lserver(), jid:simple_jid()) ->
217 [binary()].
218 get_groups_by_jid(HostType, LUser, LServer, LJID) ->
219 872 BinJID = jid:to_binary(LJID),
220 872 {selected, Rows} = mongoose_rdbms:execute_successfully(
221 HostType, roster_group_get_by_jid, [LServer, LUser, BinJID]),
222 872 [Group || {Group} <- Rows].
223
224 %%==============================================================================
225 %% Helper functions
226 %%==============================================================================
227
228 296 decode_subscription($B) -> both;
229 198 decode_subscription($T) -> to;
230 128 decode_subscription($F) -> from;
231 608 decode_subscription($N) -> none.
232
233 150 encode_subscription(both) -> <<"B">>;
234 186 encode_subscription(to) -> <<"T">>;
235 112 encode_subscription(from) -> <<"F">>;
236 548 encode_subscription(none) -> <<"N">>.
237
238
:-(
decode_ask($S) -> subscribe;
239
:-(
decode_ask($U) -> unsubscribe;
240 104 decode_ask($B) -> both;
241 182 decode_ask($O) -> out;
242 194 decode_ask($I) -> in;
243 750 decode_ask($N) -> none.
244
245
:-(
encode_ask(subscribe) -> <<"S">>;
246
:-(
encode_ask(unsubscribe) -> <<"U">>;
247 104 encode_ask(both) -> <<"B">>;
248 172 encode_ask(out) -> <<"O">>;
249 181 encode_ask(in) -> <<"I">>;
250 539 encode_ask(none) -> <<"N">>.
251
252 record_to_row(#roster{us = {LUser, LServer},
253 jid = JID, name = Nick, subscription = Subscription,
254 ask = Ask, askmessage = AskMessage}) ->
255 996 BinJID = jid:to_binary(jid:to_lower(JID)),
256 996 ExtSubscription = encode_subscription(Subscription),
257 996 ExtAsk = encode_ask(Ask),
258 996 [LServer, LUser, BinJID, Nick, ExtSubscription, ExtAsk, AskMessage].
259
260 groups_to_rows(#roster{us = {LUser, LServer}, jid = JID, groups = Groups}) ->
261 151 BinJID = jid:to_binary(jid:to_lower(JID)),
262 151 lists:foldl(fun (<<>>, Acc) -> Acc;
263 96 (Group, Acc) -> [[LServer, LUser, BinJID, Group] | Acc]
264 end, [], Groups).
265
266 %% Decode fields from `roster_fields()' into a record
267 row_to_record(LServer,
268 {User, BinJID, Nick, ExtSubscription, ExtAsk, AskMessage}) ->
269 1230 JID = parse_jid(BinJID),
270 1230 LJID = jid:to_lower(JID), %% Convert to tuple {U,S,R}
271 1230 Subscription = decode_subscription(mongoose_rdbms:character_to_integer(ExtSubscription)),
272 1230 Ask = decode_ask(mongoose_rdbms:character_to_integer(ExtAsk)),
273 1230 USJ = {User, LServer, LJID},
274 1230 US = {User, LServer},
275 1230 #roster{usj = USJ, us = US, jid = LJID, name = Nick,
276 subscription = Subscription, ask = Ask, askmessage = AskMessage}.
277
278 313 row_to_binary_jid(Row) -> element(2, Row).
279
280 record_with_groups(Rec, Groups) ->
281 1185 Rec#roster{groups = Groups}.
282
283 %% We require all DB jids to be parsable.
284 %% They should be lowered too.
285 parse_jid(BinJID) ->
286 1230 case jid:from_binary(BinJID) of
287 error ->
288
:-(
error(#{what => parse_jid_failed, jid => BinJID});
289 JID ->
290 1230 JID
291 end.
292
293 decode_roster_rows(LServer, Rows, JIDGroups) ->
294 5222 GroupsDict = pairs_to_dict(JIDGroups),
295 5222 [raw_to_record_with_group(LServer, Row, GroupsDict) || Row <- Rows].
296
297 pairs_to_dict(Pairs) ->
298 5222 F = fun ({K, V}, Acc) -> dict:append(K, V, Acc) end,
299 5222 lists:foldl(F, dict:new(), Pairs).
300
301 raw_to_record_with_group(LServer, Row, GroupsDict) ->
302 313 Rec = row_to_record(LServer, Row),
303 313 BinJID = row_to_binary_jid(Row),
304 313 Groups = dict_get(BinJID, GroupsDict, []),
305 313 record_with_groups(Rec, Groups).
306
307 dict_get(K, Dict, Default) ->
308 313 case dict:find(K, Dict) of
309 141 {ok, Values} -> Values;
310 172 error -> Default
311 end.
Line Hits Source