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 |
172 |
prepare_queries(HostType), |
36 |
172 |
ok. |
37 |
|
|
38 |
|
-spec transaction(mongooseim:host_type(), fun(() -> any())) -> |
39 |
|
{aborted, any()} | {atomic, any()} | {error, any()}. |
40 |
|
transaction(HostType, F) -> |
41 |
6436 |
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 |
5626 |
{selected, Rows} = execute_roster_get(HostType, LUser, LServer), |
59 |
5626 |
{selected, GroupRows} = execute_roster_group_get(HostType, LUser, LServer), |
60 |
5626 |
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 |
1133 |
BinJID = jid:to_binary(LJID), |
67 |
1133 |
case execute_roster_get_by_jid(HostType, LUser, LServer, BinJID) of |
68 |
|
{selected, []} -> |
69 |
266 |
does_not_exist; |
70 |
|
{selected, [Row]} -> |
71 |
867 |
Groups = get_groups_by_jid(HostType, LUser, LServer, BinJID), |
72 |
867 |
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 |
5086 |
HostType = mongoose_acc:host_type(Acc), |
86 |
5086 |
{selected, Rows} = execute_roster_get(HostType, LUser, LServer), |
87 |
5086 |
[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 |
840 |
RosterRow = record_to_row(Item), |
92 |
840 |
roster_upsert(HostType, RosterRow), |
93 |
840 |
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 |
5371 |
mongoose_rdbms:execute_successfully(HostType, roster_delete, [LServer, LUser]), |
118 |
5371 |
mongoose_rdbms:execute_successfully(HostType, roster_group_delete, [LServer, LUser]), |
119 |
5371 |
ok. |
120 |
|
|
121 |
|
-spec remove_domain_t(mongooseim:host_type(), jid:lserver()) -> ok. |
122 |
|
remove_domain_t(HostType, Domain) -> |
123 |
20 |
mongoose_rdbms:execute_successfully(HostType, rosterusers_remove_domain, [Domain]), |
124 |
20 |
mongoose_rdbms:execute_successfully(HostType, rostergroups_remove_domain, [Domain]), |
125 |
20 |
mongoose_rdbms:execute_successfully(HostType, roster_version_remove_domain, [Domain]), |
126 |
20 |
ok. |
127 |
|
|
128 |
|
%% Query preparation |
129 |
|
|
130 |
|
prepare_queries(HostType) -> |
131 |
172 |
mongoose_rdbms:prepare(roster_group_insert, rostergroups, [server, username, jid, grp], |
132 |
|
<<"INSERT INTO rostergroups(server, username, jid, grp) " |
133 |
|
"VALUES (?, ?, ?, ?)">>), |
134 |
172 |
mongoose_rdbms:prepare(roster_version_get, roster_version, [server, username], |
135 |
|
<<"SELECT version FROM roster_version " |
136 |
|
"WHERE server = ? AND username = ?">>), |
137 |
172 |
mongoose_rdbms:prepare(roster_get, rosterusers, [server, username], |
138 |
|
<<"SELECT ", (roster_fields())/binary, |
139 |
|
" FROM rosterusers WHERE server = ? AND username = ?">>), |
140 |
172 |
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 |
172 |
mongoose_rdbms:prepare(roster_group_get, rostergroups, [server, username], |
144 |
|
<<"SELECT jid, grp FROM rostergroups WHERE server = ? AND username = ?">>), |
145 |
172 |
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 |
172 |
mongoose_rdbms:prepare(roster_delete, rosterusers, [server, username], |
149 |
|
<<"DELETE FROM rosterusers WHERE server = ? AND username = ?">>), |
150 |
172 |
mongoose_rdbms:prepare(roster_group_delete, rostergroups, [server, username], |
151 |
|
<<"DELETE FROM rostergroups WHERE server = ? AND username = ?">>), |
152 |
172 |
mongoose_rdbms:prepare(roster_delete_by_jid, rosterusers, [server, username, jid], |
153 |
|
<<"DELETE FROM rosterusers" |
154 |
|
" WHERE server = ? AND username = ? AND jid = ?">>), |
155 |
172 |
mongoose_rdbms:prepare(roster_group_delete_by_jid, rostergroups, [server, username, jid], |
156 |
|
<<"DELETE FROM rostergroups" |
157 |
|
" WHERE server = ? AND username = ? AND jid = ?">>), |
158 |
172 |
mongoose_rdbms:prepare(rosterusers_remove_domain, rosterusers, [server], |
159 |
|
<<"DELETE FROM rosterusers WHERE server = ?">>), |
160 |
172 |
mongoose_rdbms:prepare(rostergroups_remove_domain, rostergroups, [server], |
161 |
|
<<"DELETE FROM rostergroups WHERE server = ?">>), |
162 |
172 |
mongoose_rdbms:prepare(roster_version_remove_domain, roster_version, [server], |
163 |
|
<<"DELETE FROM roster_version WHERE server = ?">>), |
164 |
172 |
prepare_roster_upsert(HostType), |
165 |
172 |
prepare_version_upsert(HostType), |
166 |
172 |
ok. |
167 |
|
|
168 |
|
prepare_roster_upsert(HostType) -> |
169 |
172 |
Fields = [<<"nick">>, <<"subscription">>, <<"ask">>, <<"askmessage">>], |
170 |
172 |
Filter = [<<"server">>, <<"username">>, <<"jid">>], |
171 |
172 |
rdbms_queries:prepare_upsert(HostType, roster_upsert, rosterusers, |
172 |
|
Filter ++ Fields, Fields, Filter). |
173 |
|
|
174 |
|
prepare_version_upsert(HostType) -> |
175 |
172 |
Fields = [<<"version">>], |
176 |
172 |
Filter = [<<"server">>, <<"username">>], |
177 |
172 |
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 |
10712 |
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 |
5626 |
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 |
1317 |
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 |
867 |
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 |
989 |
InsertParams = RosterRow, |
205 |
989 |
UpdateParams = Rest, |
206 |
989 |
UniqueKeyValues = [LServer, LUser, BinJID], |
207 |
989 |
{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 |
867 |
{selected, Rows} = execute_roster_get_groups_by_jid(HostType, LUser, LServer, BinJID), |
223 |
867 |
[Group || {Group} <- Rows]. |
224 |
|
|
225 |
|
%%============================================================================== |
226 |
|
%% Helper functions |
227 |
|
%%============================================================================== |
228 |
|
|
229 |
296 |
decode_subscription($B) -> both; |
230 |
196 |
decode_subscription($T) -> to; |
231 |
125 |
decode_subscription($F) -> from; |
232 |
603 |
decode_subscription($N) -> none. |
233 |
|
|
234 |
150 |
encode_subscription(both) -> <<"B">>; |
235 |
185 |
encode_subscription(to) -> <<"T">>; |
236 |
111 |
encode_subscription(from) -> <<"F">>; |
237 |
543 |
encode_subscription(none) -> <<"N">>. |
238 |
|
|
239 |
:-( |
decode_ask($S) -> subscribe; |
240 |
:-( |
decode_ask($U) -> unsubscribe; |
241 |
104 |
decode_ask($B) -> both; |
242 |
181 |
decode_ask($O) -> out; |
243 |
192 |
decode_ask($I) -> in; |
244 |
743 |
decode_ask($N) -> none. |
245 |
|
|
246 |
:-( |
encode_ask(subscribe) -> <<"S">>; |
247 |
:-( |
encode_ask(unsubscribe) -> <<"U">>; |
248 |
104 |
encode_ask(both) -> <<"B">>; |
249 |
171 |
encode_ask(out) -> <<"O">>; |
250 |
179 |
encode_ask(in) -> <<"I">>; |
251 |
535 |
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 |
989 |
BinJID = jid:to_binary(jid:to_lower(JID)), |
257 |
989 |
ExtSubscription = encode_subscription(Subscription), |
258 |
989 |
ExtAsk = encode_ask(Ask), |
259 |
989 |
[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 |
344 |
<<"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 |
1220 |
JID = jid:from_binary_noprep(BinJID), %% We trust the DB has correct jids |
275 |
1220 |
LJID = jid:to_lower(JID), %% Convert to tuple {U,S,R} |
276 |
1220 |
Subscription = decode_subscription(mongoose_rdbms:character_to_integer(ExtSubscription)), |
277 |
1220 |
Ask = decode_ask(mongoose_rdbms:character_to_integer(ExtAsk)), |
278 |
1220 |
US = {LUser, LServer}, |
279 |
1220 |
USJ = {US, LJID}, |
280 |
1220 |
Groups = maps:get(BinJID, GroupsPerJID, []), |
281 |
1220 |
#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 |
5626 |
GroupsPerJID = group_per_jid(JIDGroups), |
286 |
5626 |
[row_to_record(LServer, LUser, Row, GroupsPerJID) || Row <- Rows]. |
287 |
|
|
288 |
|
group_per_jid(Pairs) -> |
289 |
5626 |
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 |
5626 |
lists:foldl(F, #{}, Pairs). |