./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 450 prepare_queries(HostType),
39 450 ok.
40
41 -spec transaction(mongooseim:host_type(), fun(() -> any())) ->
42 {aborted, any()} | {atomic, any()} | {error, any()}.
43 transaction(HostType, F) ->
44 7182 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 6308 {selected, Rows} = execute_roster_get(HostType, LUser, LServer),
62 6308 {selected, GroupRows} =
63 mongoose_rdbms:execute_successfully(HostType, roster_group_get, [LServer, LUser]),
64 6308 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 1231 case get_roster_entry(HostType, LUser, LServer, LJID) of
71 does_not_exist ->
72 291 does_not_exist;
73 Rec ->
74 940 Groups = get_groups_by_jid(HostType, LUser, LServer, LJID),
75 940 record_with_groups(Rec, Groups)
76 end;
77 get_roster_entry(HostType, LUser, LServer, LJID, _TransactionState, short) ->
78 184 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 5716 HostType = mongoose_acc:host_type(Acc),
83 5716 {selected, Rows} = execute_roster_get(HostType, LUser, LServer),
84 5716 [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 932 RosterRow = record_to_row(Item),
89 932 roster_upsert(HostType, RosterRow),
90 932 ok.
91
92 -spec update_roster_t(mongooseim:host_type(), mod_roster:roster()) -> ok.
93 update_roster_t(HostType, Item) ->
94 149 RosterRow = [LServer, LUser, BinJID | _] = record_to_row(Item),
95 149 GroupRows = groups_to_rows(Item),
96 149 roster_upsert(HostType, RosterRow),
97 149 mongoose_rdbms:execute_successfully(HostType, roster_group_delete_by_jid,
98 [LServer, LUser, BinJID]),
99 149 [mongoose_rdbms:execute_successfully(HostType, roster_group_insert, GroupRow)
100 149 || GroupRow <- GroupRows],
101 149 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 6030 mongoose_rdbms:execute_successfully(HostType, roster_delete, [LServer, LUser]),
115 6030 mongoose_rdbms:execute_successfully(HostType, roster_group_delete, [LServer, LUser]),
116 6030 ok.
117
118 -spec remove_domain_t(mongooseim:host_type(), jid:lserver()) -> ok.
119 remove_domain_t(HostType, Domain) ->
120 15 mongoose_rdbms:execute_successfully(HostType, rosterusers_remove_domain, [Domain]),
121 15 mongoose_rdbms:execute_successfully(HostType, rostergroups_remove_domain, [Domain]),
122 15 mongoose_rdbms:execute_successfully(HostType, roster_version_remove_domain, [Domain]),
123 15 ok.
124
125 %% Query preparation
126
127 prepare_queries(HostType) ->
128 450 mongoose_rdbms:prepare(roster_group_insert, rostergroups, [server, username, jid, grp],
129 <<"INSERT INTO rostergroups(server, username, jid, grp) "
130 "VALUES (?, ?, ?, ?)">>),
131 450 mongoose_rdbms:prepare(roster_version_get, roster_version, [server, username],
132 <<"SELECT version FROM roster_version "
133 "WHERE server = ? AND username = ?">>),
134 450 mongoose_rdbms:prepare(roster_get, rosterusers, [server, username],
135 <<"SELECT ", (roster_fields())/binary,
136 " FROM rosterusers WHERE server = ? AND username = ?">>),
137 450 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 450 mongoose_rdbms:prepare(roster_group_get, rostergroups, [server, username],
141 <<"SELECT jid, grp FROM rostergroups WHERE server = ? AND username = ?">>),
142 450 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 450 mongoose_rdbms:prepare(roster_delete, rosterusers, [server, username],
146 <<"DELETE FROM rosterusers WHERE server = ? AND username = ?">>),
147 450 mongoose_rdbms:prepare(roster_group_delete, rostergroups, [server, username],
148 <<"DELETE FROM rostergroups WHERE server = ? AND username = ?">>),
149 450 mongoose_rdbms:prepare(roster_delete_by_jid, rosterusers, [server, username, jid],
150 <<"DELETE FROM rosterusers"
151 " WHERE server = ? AND username = ? AND jid = ?">>),
152 450 mongoose_rdbms:prepare(roster_group_delete_by_jid, rostergroups, [server, username, jid],
153 <<"DELETE FROM rostergroups"
154 " WHERE server = ? AND username = ? AND jid = ?">>),
155 450 mongoose_rdbms:prepare(rosterusers_remove_domain, rosterusers, [server],
156 <<"DELETE FROM rosterusers WHERE server = ?">>),
157 450 mongoose_rdbms:prepare(rostergroups_remove_domain, rostergroups, [server],
158 <<"DELETE FROM rostergroups WHERE server = ?">>),
159 450 mongoose_rdbms:prepare(roster_version_remove_domain, roster_version, [server],
160 <<"DELETE FROM roster_version WHERE server = ?">>),
161 450 prepare_roster_upsert(HostType),
162 450 prepare_version_upsert(HostType),
163 450 ok.
164
165 %% We don't care about `server, subscribe, type' fields
166 roster_fields() ->
167 900 <<"username, jid, nick, subscription, ask, askmessage">>.
168
169 prepare_roster_upsert(HostType) ->
170 450 Fields = [<<"nick">>, <<"subscription">>, <<"ask">>, <<"askmessage">>],
171 450 Filter = [<<"server">>, <<"username">>, <<"jid">>],
172 450 rdbms_queries:prepare_upsert(HostType, roster_upsert, rosterusers,
173 Filter ++ Fields, Fields, Filter).
174
175 prepare_version_upsert(HostType) ->
176 450 Fields = [<<"version">>],
177 450 Filter = [<<"server">>, <<"username">>],
178 450 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 12024 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 1081 InsertParams = RosterRow,
191 1081 UpdateParams = Rest,
192 1081 UniqueKeyValues = [LServer, LUser, BinJID],
193 1081 {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 1415 BinJID = jid:to_binary(LJID),
209 1415 {selected, Rows} = mongoose_rdbms:execute_successfully(HostType, roster_get_by_jid,
210 [LServer, LUser, BinJID]),
211 1415 case Rows of
212 442 [] -> does_not_exist;
213 973 [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 940 BinJID = jid:to_binary(LJID),
220 940 {selected, Rows} = mongoose_rdbms:execute_successfully(
221 HostType, roster_group_get_by_jid, [LServer, LUser, BinJID]),
222 940 [Group || {Group} <- Rows].
223
224 %%==============================================================================
225 %% Helper functions
226 %%==============================================================================
227
228 314 decode_subscription($B) -> both;
229 215 decode_subscription($T) -> to;
230 138 decode_subscription($F) -> from;
231 650 decode_subscription($N) -> none.
232
233 164 encode_subscription(both) -> <<"B">>;
234 203 encode_subscription(to) -> <<"T">>;
235 124 encode_subscription(from) -> <<"F">>;
236 590 encode_subscription(none) -> <<"N">>.
237
238
:-(
decode_ask($S) -> subscribe;
239
:-(
decode_ask($U) -> unsubscribe;
240 118 decode_ask($B) -> both;
241 199 decode_ask($O) -> out;
242 210 decode_ask($I) -> in;
243 790 decode_ask($N) -> none.
244
245
:-(
encode_ask(subscribe) -> <<"S">>;
246
:-(
encode_ask(unsubscribe) -> <<"U">>;
247 118 encode_ask(both) -> <<"B">>;
248 189 encode_ask(out) -> <<"O">>;
249 197 encode_ask(in) -> <<"I">>;
250 577 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 1081 BinJID = jid:to_binary(jid:to_lower(JID)),
256 1081 ExtSubscription = encode_subscription(Subscription),
257 1081 ExtAsk = encode_ask(Ask),
258 1081 [LServer, LUser, BinJID, Nick, ExtSubscription, ExtAsk, AskMessage].
259
260 groups_to_rows(#roster{us = {LUser, LServer}, jid = JID, groups = Groups}) ->
261 149 BinJID = jid:to_binary(jid:to_lower(JID)),
262 149 lists:foldl(fun (<<>>, Acc) -> Acc;
263 94 (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 1317 JID = parse_jid(BinJID),
270 1317 LJID = jid:to_lower(JID), %% Convert to tuple {U,S,R}
271 1317 Subscription = decode_subscription(mongoose_rdbms:character_to_integer(ExtSubscription)),
272 1317 Ask = decode_ask(mongoose_rdbms:character_to_integer(ExtAsk)),
273 1317 USJ = {User, LServer, LJID},
274 1317 US = {User, LServer},
275 1317 #roster{usj = USJ, us = US, jid = LJID, name = Nick,
276 subscription = Subscription, ask = Ask, askmessage = AskMessage}.
277
278 333 row_to_binary_jid(Row) -> element(2, Row).
279
280 record_with_groups(Rec, Groups) ->
281 1273 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 1317 case jid:from_binary(BinJID) of
287 error ->
288
:-(
error(#{what => parse_jid_failed, jid => BinJID});
289 JID ->
290 1317 JID
291 end.
292
293 decode_roster_rows(LServer, Rows, JIDGroups) ->
294 6308 GroupsDict = pairs_to_dict(JIDGroups),
295 6308 [raw_to_record_with_group(LServer, Row, GroupsDict) || Row <- Rows].
296
297 pairs_to_dict(Pairs) ->
298 6308 F = fun ({K, V}, Acc) -> dict:append(K, V, Acc) end,
299 6308 lists:foldl(F, dict:new(), Pairs).
300
301 raw_to_record_with_group(LServer, Row, GroupsDict) ->
302 333 Rec = row_to_record(LServer, Row),
303 333 BinJID = row_to_binary_jid(Row),
304 333 Groups = dict_get(BinJID, GroupsDict, []),
305 333 record_with_groups(Rec, Groups).
306
307 dict_get(K, Dict, Default) ->
308 333 case dict:find(K, Dict) of
309 139 {ok, Values} -> Values;
310 194 error -> Default
311 end.
Line Hits Source