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 |
446 |
prepare_queries(HostType), |
39 |
446 |
ok. |
40 |
|
|
41 |
|
-spec transaction(mongooseim:host_type(), fun(() -> any())) -> |
42 |
|
{aborted, any()} | {atomic, any()} | {error, any()}. |
43 |
|
transaction(HostType, F) -> |
44 |
7085 |
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 |
6211 |
{selected, Rows} = execute_roster_get(HostType, LUser, LServer), |
62 |
6211 |
{selected, GroupRows} = |
63 |
|
mongoose_rdbms:execute_successfully(HostType, roster_group_get, [LServer, LUser]), |
64 |
6211 |
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 |
1232 |
case get_roster_entry(HostType, LUser, LServer, LJID) of |
71 |
|
does_not_exist -> |
72 |
292 |
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 |
5691 |
HostType = mongoose_acc:host_type(Acc), |
83 |
5691 |
{selected, Rows} = execute_roster_get(HostType, LUser, LServer), |
84 |
5691 |
[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 |
933 |
RosterRow = record_to_row(Item), |
89 |
933 |
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 |
5933 |
mongoose_rdbms:execute_successfully(HostType, roster_delete, [LServer, LUser]), |
115 |
5933 |
mongoose_rdbms:execute_successfully(HostType, roster_group_delete, [LServer, LUser]), |
116 |
5933 |
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 |
446 |
mongoose_rdbms:prepare(roster_group_insert, rostergroups, [server, username, jid, grp], |
129 |
|
<<"INSERT INTO rostergroups(server, username, jid, grp) " |
130 |
|
"VALUES (?, ?, ?, ?)">>), |
131 |
446 |
mongoose_rdbms:prepare(roster_version_get, roster_version, [server, username], |
132 |
|
<<"SELECT version FROM roster_version " |
133 |
|
"WHERE server = ? AND username = ?">>), |
134 |
446 |
mongoose_rdbms:prepare(roster_get, rosterusers, [server, username], |
135 |
|
<<"SELECT ", (roster_fields())/binary, |
136 |
|
" FROM rosterusers WHERE server = ? AND username = ?">>), |
137 |
446 |
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 |
446 |
mongoose_rdbms:prepare(roster_group_get, rostergroups, [server, username], |
141 |
|
<<"SELECT jid, grp FROM rostergroups WHERE server = ? AND username = ?">>), |
142 |
446 |
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 |
446 |
mongoose_rdbms:prepare(roster_delete, rosterusers, [server, username], |
146 |
|
<<"DELETE FROM rosterusers WHERE server = ? AND username = ?">>), |
147 |
446 |
mongoose_rdbms:prepare(roster_group_delete, rostergroups, [server, username], |
148 |
|
<<"DELETE FROM rostergroups WHERE server = ? AND username = ?">>), |
149 |
446 |
mongoose_rdbms:prepare(roster_delete_by_jid, rosterusers, [server, username, jid], |
150 |
|
<<"DELETE FROM rosterusers" |
151 |
|
" WHERE server = ? AND username = ? AND jid = ?">>), |
152 |
446 |
mongoose_rdbms:prepare(roster_group_delete_by_jid, rostergroups, [server, username, jid], |
153 |
|
<<"DELETE FROM rostergroups" |
154 |
|
" WHERE server = ? AND username = ? AND jid = ?">>), |
155 |
446 |
mongoose_rdbms:prepare(rosterusers_remove_domain, rosterusers, [server], |
156 |
|
<<"DELETE FROM rosterusers WHERE server = ?">>), |
157 |
446 |
mongoose_rdbms:prepare(rostergroups_remove_domain, rostergroups, [server], |
158 |
|
<<"DELETE FROM rostergroups WHERE server = ?">>), |
159 |
446 |
mongoose_rdbms:prepare(roster_version_remove_domain, roster_version, [server], |
160 |
|
<<"DELETE FROM roster_version WHERE server = ?">>), |
161 |
446 |
prepare_roster_upsert(HostType), |
162 |
446 |
prepare_version_upsert(HostType), |
163 |
446 |
ok. |
164 |
|
|
165 |
|
%% We don't care about `server, subscribe, type' fields |
166 |
|
roster_fields() -> |
167 |
892 |
<<"username, jid, nick, subscription, ask, askmessage">>. |
168 |
|
|
169 |
|
prepare_roster_upsert(HostType) -> |
170 |
446 |
Fields = [<<"nick">>, <<"subscription">>, <<"ask">>, <<"askmessage">>], |
171 |
446 |
Filter = [<<"server">>, <<"username">>, <<"jid">>], |
172 |
446 |
rdbms_queries:prepare_upsert(HostType, roster_upsert, rosterusers, |
173 |
|
Filter ++ Fields, Fields, Filter). |
174 |
|
|
175 |
|
prepare_version_upsert(HostType) -> |
176 |
446 |
Fields = [<<"version">>], |
177 |
446 |
Filter = [<<"server">>, <<"username">>], |
178 |
446 |
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 |
11902 |
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 |
1082 |
InsertParams = RosterRow, |
191 |
1082 |
UpdateParams = Rest, |
192 |
1082 |
UniqueKeyValues = [LServer, LUser, BinJID], |
193 |
1082 |
{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 |
1416 |
BinJID = jid:to_binary(LJID), |
209 |
1416 |
{selected, Rows} = mongoose_rdbms:execute_successfully(HostType, roster_get_by_jid, |
210 |
|
[LServer, LUser, BinJID]), |
211 |
1416 |
case Rows of |
212 |
443 |
[] -> 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 |
591 |
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 |
198 |
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 |
1082 |
BinJID = jid:to_binary(jid:to_lower(JID)), |
256 |
1082 |
ExtSubscription = encode_subscription(Subscription), |
257 |
1082 |
ExtAsk = encode_ask(Ask), |
258 |
1082 |
[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 |
6211 |
GroupsDict = pairs_to_dict(JIDGroups), |
295 |
6211 |
[raw_to_record_with_group(LServer, Row, GroupsDict) || Row <- Rows]. |
296 |
|
|
297 |
|
pairs_to_dict(Pairs) -> |
298 |
6211 |
F = fun ({K, V}, Acc) -> dict:append(K, V, Acc) end, |
299 |
6211 |
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. |