./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 -module(mod_roster_rdbms).
12
13 -include("mod_roster.hrl").
14
15 -behaviour(mod_roster_backend).
16
17 %% API
18 -export([init/2,
19 transaction/2,
20 read_roster_version/3,
21 write_roster_version/5,
22 get_roster/3,
23 get_roster_entry/6,
24 get_subscription_lists/3,
25 roster_subscribe_t/2,
26 update_roster_t/2,
27 del_roster_t/4,
28 remove_user_t/3,
29 remove_domain_t/2]).
30
31 %% mod_roster backend API
32
33 -spec init(mongooseim:host_type(), gen_mod:module_opts()) -> ok.
34 init(HostType, _Opts) ->
35 245 prepare_queries(HostType),
36 245 ok.
37
38 -spec transaction(mongooseim:host_type(), fun(() -> any())) ->
39 {aborted, any()} | {atomic, any()} | {error, any()}.
40 transaction(HostType, F) ->
41 7258 mongoose_rdbms:sql_transaction(HostType, F).
42
43 -spec read_roster_version(mongooseim:host_type(), jid:luser(), jid:lserver()) -> binary() | error.
44 read_roster_version(HostType, LUser, LServer) ->
45 5 case mongoose_rdbms:execute_successfully(HostType, roster_version_get, [LServer, LUser]) of
46 4 {selected, [{Version}]} -> Version;
47 1 {selected, []} -> error
48 end.
49
50 -spec write_roster_version(mongooseim:host_type(), jid:luser(), jid:lserver(),
51 mod_roster:transaction_state(), mod_roster:version()) -> ok.
52 write_roster_version(HostType, LUser, LServer, _TransactionState, Ver) ->
53 3 version_upsert(HostType, LUser, LServer, Ver),
54 3 ok.
55
56 -spec get_roster(mongooseim:host_type(), jid:luser(), jid:lserver()) -> [mod_roster:roster()].
57 get_roster(HostType, LUser, LServer) ->
58 6381 {selected, Rows} = execute_roster_get(HostType, LUser, LServer),
59 6381 {selected, GroupRows} = execute_roster_group_get(HostType, LUser, LServer),
60 6381 decode_roster_rows(LServer, LUser, Rows, GroupRows).
61
62 -spec get_roster_entry(mongooseim:host_type(), jid:luser(), jid:lserver(), mod_roster:contact(),
63 mod_roster:transaction_state(), mod_roster:entry_format()) ->
64 mod_roster:roster() | does_not_exist.
65 get_roster_entry(HostType, LUser, LServer, LJID, _TransactionState, full) ->
66 1231 BinJID = jid:to_binary(LJID),
67 1231 case execute_roster_get_by_jid(HostType, LUser, LServer, BinJID) of
68 {selected, []} ->
69 291 does_not_exist;
70 {selected, [Row]} ->
71 940 Groups = get_groups_by_jid(HostType, LUser, LServer, BinJID),
72 940 row_to_record(LServer, LUser, Row, #{BinJID => Groups})
73 end;
74 get_roster_entry(HostType, LUser, LServer, LJID, _TransactionState, short) ->
75 184 BinJID = jid:to_binary(LJID),
76 184 case execute_roster_get_by_jid(HostType, LUser, LServer, BinJID) of
77 {selected, []} ->
78 151 does_not_exist;
79 {selected, [Row]} ->
80 33 row_to_record(LServer, LUser, Row, #{})
81 end.
82
83 -spec get_subscription_lists(mongoose_acc:t(), jid:luser(), jid:lserver()) -> [mod_roster:roster()].
84 get_subscription_lists(Acc, LUser, LServer) ->
85 5807 HostType = mongoose_acc:host_type(Acc),
86 5807 {selected, Rows} = execute_roster_get(HostType, LUser, LServer),
87 5807 [row_to_record(LServer, LUser, Row, #{}) || Row <- Rows].
88
89 -spec roster_subscribe_t(mongooseim:host_type(), mod_roster:roster()) -> ok.
90 roster_subscribe_t(HostType, Item) ->
91 932 RosterRow = record_to_row(Item),
92 932 roster_upsert(HostType, RosterRow),
93 932 ok.
94
95 -spec update_roster_t(mongooseim:host_type(), mod_roster:roster()) -> ok.
96 update_roster_t(HostType, Item) ->
97 149 RosterRow = [LServer, LUser, BinJID | _] = record_to_row(Item),
98 149 GroupRows = groups_to_rows(Item),
99 149 roster_upsert(HostType, RosterRow),
100 149 mongoose_rdbms:execute_successfully(HostType, roster_group_delete_by_jid,
101 [LServer, LUser, BinJID]),
102 149 [mongoose_rdbms:execute_successfully(HostType, roster_group_insert, GroupRow)
103 149 || GroupRow <- GroupRows],
104 149 ok.
105
106 -spec del_roster_t(mongooseim:host_type(), jid:luser(), jid:lserver(), mod_roster:contact()) -> ok.
107 del_roster_t(HostType, LUser, LServer, LJID) ->
108 30 BinJID = jid:to_binary(LJID),
109 30 mongoose_rdbms:execute_successfully(
110 HostType, roster_delete_by_jid, [LServer, LUser, BinJID]),
111 30 mongoose_rdbms:execute_successfully(
112 HostType, roster_group_delete_by_jid, [LServer, LUser, BinJID]),
113 30 ok.
114
115 -spec remove_user_t(mongooseim:host_type(), jid:luser(), jid:lserver()) -> ok.
116 remove_user_t(HostType, LUser, LServer) ->
117 6103 mongoose_rdbms:execute_successfully(HostType, roster_delete, [LServer, LUser]),
118 6103 mongoose_rdbms:execute_successfully(HostType, roster_group_delete, [LServer, LUser]),
119 6103 ok.
120
121 -spec remove_domain_t(mongooseim:host_type(), jid:lserver()) -> ok.
122 remove_domain_t(HostType, Domain) ->
123 18 mongoose_rdbms:execute_successfully(HostType, rosterusers_remove_domain, [Domain]),
124 18 mongoose_rdbms:execute_successfully(HostType, rostergroups_remove_domain, [Domain]),
125 18 mongoose_rdbms:execute_successfully(HostType, roster_version_remove_domain, [Domain]),
126 18 ok.
127
128 %% Query preparation
129
130 prepare_queries(HostType) ->
131 245 mongoose_rdbms:prepare(roster_group_insert, rostergroups, [server, username, jid, grp],
132 <<"INSERT INTO rostergroups(server, username, jid, grp) "
133 "VALUES (?, ?, ?, ?)">>),
134 245 mongoose_rdbms:prepare(roster_version_get, roster_version, [server, username],
135 <<"SELECT version FROM roster_version "
136 "WHERE server = ? AND username = ?">>),
137 245 mongoose_rdbms:prepare(roster_get, rosterusers, [server, username],
138 <<"SELECT ", (roster_fields())/binary,
139 " FROM rosterusers WHERE server = ? AND username = ?">>),
140 245 mongoose_rdbms:prepare(roster_get_by_jid, rosterusers, [server, username, jid],
141 <<"SELECT ", (roster_fields())/binary,
142 " FROM rosterusers WHERE server = ? AND username = ? AND jid = ?">>),
143 245 mongoose_rdbms:prepare(roster_group_get, rostergroups, [server, username],
144 <<"SELECT jid, grp FROM rostergroups WHERE server = ? AND username = ?">>),
145 245 mongoose_rdbms:prepare(roster_group_get_by_jid, rostergroups, [server, username, jid],
146 <<"SELECT grp FROM rostergroups "
147 "WHERE server = ? AND username = ? AND jid = ?">>),
148 245 mongoose_rdbms:prepare(roster_delete, rosterusers, [server, username],
149 <<"DELETE FROM rosterusers WHERE server = ? AND username = ?">>),
150 245 mongoose_rdbms:prepare(roster_group_delete, rostergroups, [server, username],
151 <<"DELETE FROM rostergroups WHERE server = ? AND username = ?">>),
152 245 mongoose_rdbms:prepare(roster_delete_by_jid, rosterusers, [server, username, jid],
153 <<"DELETE FROM rosterusers"
154 " WHERE server = ? AND username = ? AND jid = ?">>),
155 245 mongoose_rdbms:prepare(roster_group_delete_by_jid, rostergroups, [server, username, jid],
156 <<"DELETE FROM rostergroups"
157 " WHERE server = ? AND username = ? AND jid = ?">>),
158 245 mongoose_rdbms:prepare(rosterusers_remove_domain, rosterusers, [server],
159 <<"DELETE FROM rosterusers WHERE server = ?">>),
160 245 mongoose_rdbms:prepare(rostergroups_remove_domain, rostergroups, [server],
161 <<"DELETE FROM rostergroups WHERE server = ?">>),
162 245 mongoose_rdbms:prepare(roster_version_remove_domain, roster_version, [server],
163 <<"DELETE FROM roster_version WHERE server = ?">>),
164 245 prepare_roster_upsert(HostType),
165 245 prepare_version_upsert(HostType),
166 245 ok.
167
168 prepare_roster_upsert(HostType) ->
169 245 Fields = [<<"nick">>, <<"subscription">>, <<"ask">>, <<"askmessage">>],
170 245 Filter = [<<"server">>, <<"username">>, <<"jid">>],
171 245 rdbms_queries:prepare_upsert(HostType, roster_upsert, rosterusers,
172 Filter ++ Fields, Fields, Filter).
173
174 prepare_version_upsert(HostType) ->
175 245 Fields = [<<"version">>],
176 245 Filter = [<<"server">>, <<"username">>],
177 245 rdbms_queries:prepare_upsert(HostType, roster_version_upsert, roster_version,
178 Filter ++ Fields, Fields, Filter).
179
180 %% Query Helpers
181
182 -spec execute_roster_get(mongooseim:host_type(), jid:luser(), jid:lserver()) ->
183 mongoose_rdbms:query_result().
184 execute_roster_get(HostType, LUser, LServer) ->
185 12188 mongoose_rdbms:execute_successfully(HostType, roster_get, [LServer, LUser]).
186
187 -spec execute_roster_group_get(mongooseim:host_type(), jid:luser(), jid:lserver()) ->
188 mongoose_rdbms:query_result().
189 execute_roster_group_get(HostType, LUser, LServer) ->
190 6381 mongoose_rdbms:execute_successfully(HostType, roster_group_get, [LServer, LUser]).
191
192 -spec execute_roster_get_by_jid(mongooseim:host_type(), jid:luser(), jid:lserver(), jid:literal_jid()) ->
193 mongoose_rdbms:query_result().
194 execute_roster_get_by_jid(HostType, LUser, LServer, BinJID) ->
195 1415 mongoose_rdbms:execute_successfully(HostType, roster_get_by_jid, [LServer, LUser, BinJID]).
196
197 -spec execute_roster_get_groups_by_jid(mongooseim:host_type(), jid:luser(), jid:lserver(), jid:literal_jid()) ->
198 mongoose_rdbms:query_result().
199 execute_roster_get_groups_by_jid(HostType, LUser, LServer, BinJID) ->
200 940 mongoose_rdbms:execute_successfully(HostType, roster_group_get_by_jid, [LServer, LUser, BinJID]).
201
202 -spec roster_upsert(mongooseim:host_type(), list()) -> mongoose_rdbms:query_result().
203 roster_upsert(HostType, [LServer, LUser, BinJID | Rest] = RosterRow) ->
204 1081 InsertParams = RosterRow,
205 1081 UpdateParams = Rest,
206 1081 UniqueKeyValues = [LServer, LUser, BinJID],
207 1081 {updated, _} = rdbms_queries:execute_upsert(HostType, roster_upsert,
208 InsertParams, UpdateParams, UniqueKeyValues).
209
210 -spec version_upsert(mongooseim:host_type(), jid:luser(), jid:lserver(), mod_roster:version()) ->
211 mongoose_rdbms:query_result().
212 version_upsert(HostType, LUser, LServer, Version) ->
213 3 InsertParams = [LServer, LUser, Version],
214 3 UpdateParams = [Version],
215 3 UniqueKeyValues = [LServer, LUser],
216 3 {updated, _} = rdbms_queries:execute_upsert(HostType, roster_version_upsert,
217 InsertParams, UpdateParams, UniqueKeyValues).
218
219 -spec get_groups_by_jid(mongooseim:host_type(), jid:luser(), jid:lserver(), jid:literal_jid()) ->
220 [binary()].
221 get_groups_by_jid(HostType, LUser, LServer, BinJID) ->
222 940 {selected, Rows} = execute_roster_get_groups_by_jid(HostType, LUser, LServer, BinJID),
223 940 [Group || {Group} <- Rows].
224
225 %%==============================================================================
226 %% Helper functions
227 %%==============================================================================
228
229 314 decode_subscription($B) -> both;
230 215 decode_subscription($T) -> to;
231 138 decode_subscription($F) -> from;
232 650 decode_subscription($N) -> none.
233
234 164 encode_subscription(both) -> <<"B">>;
235 203 encode_subscription(to) -> <<"T">>;
236 124 encode_subscription(from) -> <<"F">>;
237 590 encode_subscription(none) -> <<"N">>.
238
239
:-(
decode_ask($S) -> subscribe;
240
:-(
decode_ask($U) -> unsubscribe;
241 118 decode_ask($B) -> both;
242 199 decode_ask($O) -> out;
243 210 decode_ask($I) -> in;
244 790 decode_ask($N) -> none.
245
246
:-(
encode_ask(subscribe) -> <<"S">>;
247
:-(
encode_ask(unsubscribe) -> <<"U">>;
248 118 encode_ask(both) -> <<"B">>;
249 189 encode_ask(out) -> <<"O">>;
250 197 encode_ask(in) -> <<"I">>;
251 577 encode_ask(none) -> <<"N">>.
252
253 record_to_row(#roster{us = {LUser, LServer},
254 jid = JID, name = Nick, subscription = Subscription,
255 ask = Ask, askmessage = AskMessage}) ->
256 1081 BinJID = jid:to_binary(jid:to_lower(JID)),
257 1081 ExtSubscription = encode_subscription(Subscription),
258 1081 ExtAsk = encode_ask(Ask),
259 1081 [LServer, LUser, BinJID, Nick, ExtSubscription, ExtAsk, AskMessage].
260
261 groups_to_rows(#roster{us = {LUser, LServer}, jid = JID, groups = Groups}) ->
262 149 BinJID = jid:to_binary(jid:to_lower(JID)),
263 149 lists:foldl(fun (<<>>, Acc) -> Acc;
264 94 (Group, Acc) -> [[LServer, LUser, BinJID, Group] | Acc]
265 end, [], Groups).
266
267 %% We don't care about `server, subscribe, type' fields
268 roster_fields() ->
269 490 <<"jid, nick, subscription, ask, askmessage">>.
270
271 %% Decode fields from `roster_fields()' into a record
272 row_to_record(LServer, LUser,
273 {BinJID, Nick, ExtSubscription, ExtAsk, AskMessage}, GroupsPerJID) ->
274 1317 JID = jid:from_binary_noprep(BinJID), %% We trust the DB has correct jids
275 1317 LJID = jid:to_lower(JID), %% Convert to tuple {U,S,R}
276 1317 Subscription = decode_subscription(mongoose_rdbms:character_to_integer(ExtSubscription)),
277 1317 Ask = decode_ask(mongoose_rdbms:character_to_integer(ExtAsk)),
278 1317 US = {LUser, LServer},
279 1317 USJ = {US, LJID},
280 1317 Groups = maps:get(BinJID, GroupsPerJID, []),
281 1317 #roster{usj = USJ, us = US, jid = LJID, name = Nick,
282 subscription = Subscription, ask = Ask, groups = Groups, askmessage = AskMessage}.
283
284 decode_roster_rows(LServer, LUser, Rows, JIDGroups) ->
285 6381 GroupsPerJID = group_per_jid(JIDGroups),
286 6381 [row_to_record(LServer, LUser, Row, GroupsPerJID) || Row <- Rows].
287
288 group_per_jid(Pairs) ->
289 6381 F = fun ({Jid, Group}, Acc) ->
290 139 case Acc of
291 #{Jid := Groups} ->
292
:-(
Acc#{Jid := [Group | Groups]};
293 _ ->
294 139 Acc#{Jid => [Group]}
295 end
296 end,
297 6381 lists:foldl(F, #{}, Pairs).
Line Hits Source