1 |
|
%%%------------------------------------------------------------------- |
2 |
|
%%% @copyright (C) 2018, Erlang Solutions Ltd. |
3 |
|
%%% @doc |
4 |
|
%%% |
5 |
|
%%% @end |
6 |
|
%%% Created : 30. Jan 2018 16:59 |
7 |
|
%%%------------------------------------------------------------------- |
8 |
|
-module(mod_inbox_rdbms). |
9 |
|
|
10 |
|
-include("mod_inbox.hrl"). |
11 |
|
-include("mongoose_logger.hrl"). |
12 |
|
|
13 |
|
-behaviour(mod_inbox_backend). |
14 |
|
|
15 |
|
%% API |
16 |
|
-export([get_inbox/4, |
17 |
|
init/2, |
18 |
|
set_inbox/6, |
19 |
|
set_inbox_incr_unread/5, |
20 |
|
reset_unread/4, |
21 |
|
empty_user_bin/4, |
22 |
|
empty_domain_bin/3, |
23 |
|
empty_global_bin/2, |
24 |
|
remove_inbox_row/2, |
25 |
|
remove_domain/2, |
26 |
|
clear_inbox/3, |
27 |
|
get_inbox_unread/2, |
28 |
|
get_full_entry/2, |
29 |
|
get_entry_properties/2, |
30 |
|
set_entry_properties/3]). |
31 |
|
-export([check_result/1]). |
32 |
|
|
33 |
|
%% ---------------------------------------------------------------------- |
34 |
|
%% API |
35 |
|
%% ---------------------------------------------------------------------- |
36 |
|
|
37 |
|
%% TODO pools aren't multitenancy-ready yet |
38 |
|
init(HostType, Opts) -> |
39 |
48 |
RowCond = <<"WHERE luser = ? AND lserver = ? AND remote_bare_jid = ?">>, |
40 |
48 |
mongoose_rdbms:prepare(inbox_select_entry, inbox, |
41 |
|
[luser, lserver, remote_bare_jid], |
42 |
|
<<"SELECT", (query_row())/binary, "FROM inbox ", RowCond/binary>>), |
43 |
48 |
mongoose_rdbms:prepare(inbox_select_unread_count, inbox, |
44 |
|
[luser, lserver, remote_bare_jid], |
45 |
|
<<"SELECT unread_count FROM inbox ", RowCond/binary>>), |
46 |
48 |
mongoose_rdbms:prepare(inbox_select_properties, inbox, |
47 |
|
[luser, lserver, remote_bare_jid], |
48 |
|
<<"SELECT box, muted_until, unread_count FROM inbox ", RowCond/binary>>), |
49 |
48 |
mongoose_rdbms:prepare(inbox_reset_unread, inbox, |
50 |
|
[luser, lserver, remote_bare_jid, timestamp], |
51 |
|
<<"UPDATE inbox SET unread_count = 0 ", |
52 |
|
RowCond/binary, " AND timestamp <= ?">>), |
53 |
48 |
mongoose_rdbms:prepare(inbox_reset_unread_msg, inbox, |
54 |
|
[luser, lserver, remote_bare_jid, msg_id, timestamp], |
55 |
|
<<"UPDATE inbox SET unread_count = 0 ", RowCond/binary, |
56 |
|
" AND msg_id = ? AND timestamp <= ?">>), |
57 |
|
% removals |
58 |
48 |
mongoose_rdbms:prepare(inbox_clean_global_bin, inbox, [timestamp], |
59 |
|
<<"DELETE FROM inbox WHERE box='bin' AND timestamp < ?">>), |
60 |
48 |
mongoose_rdbms:prepare(inbox_clean_domain_bin, inbox, [lserver, timestamp], |
61 |
|
<<"DELETE FROM inbox WHERE", |
62 |
|
" lserver = ? AND box='bin' AND timestamp < ?">>), |
63 |
48 |
mongoose_rdbms:prepare(inbox_clean_user_bin, inbox, [lserver, luser, timestamp], |
64 |
|
<<"DELETE FROM inbox WHERE", |
65 |
|
" lserver = ? AND luser = ? AND box='bin' AND timestamp < ?">>), |
66 |
48 |
mongoose_rdbms:prepare(inbox_delete_row, inbox, |
67 |
|
[luser, lserver, remote_bare_jid], |
68 |
|
<<"DELETE FROM inbox ", RowCond/binary>>), |
69 |
48 |
mongoose_rdbms:prepare(inbox_delete, inbox, |
70 |
|
[luser, lserver], |
71 |
|
<<"DELETE FROM inbox WHERE luser = ? AND lserver = ?">>), |
72 |
48 |
prepare_remove_domain(Opts), |
73 |
|
% upserts |
74 |
48 |
BoxQuery = <<"CASE WHEN ?='bin' THEN 'bin'", |
75 |
|
" WHEN inbox.box='archive' THEN 'inbox'", |
76 |
|
" ELSE inbox.box END">>, |
77 |
48 |
UniqueKeyFields = [<<"luser">>, <<"lserver">>, <<"remote_bare_jid">>], |
78 |
48 |
InsertFields = UniqueKeyFields ++ [<<"msg_id">>, <<"box">>, <<"content">>, <<"unread_count">>, <<"timestamp">>], |
79 |
48 |
rdbms_queries:prepare_upsert(HostType, inbox_upsert, inbox, |
80 |
|
InsertFields, |
81 |
|
[<<"msg_id">>, |
82 |
|
{expression, <<"box">>, BoxQuery}, |
83 |
|
<<"content">>, |
84 |
|
<<"unread_count">>, |
85 |
|
<<"timestamp">>], |
86 |
|
UniqueKeyFields, <<"timestamp">>), |
87 |
48 |
rdbms_queries:prepare_upsert(HostType, inbox_upsert_incr_unread, inbox, |
88 |
|
InsertFields, |
89 |
|
[<<"msg_id">>, |
90 |
|
{expression, <<"box">>, BoxQuery}, |
91 |
|
<<"content">>, |
92 |
|
{expression, <<"unread_count">>, <<"inbox.unread_count + ?">>}, |
93 |
|
<<"timestamp">>], |
94 |
|
UniqueKeyFields, <<"timestamp">>), |
95 |
48 |
ok. |
96 |
|
|
97 |
|
prepare_remove_domain(#{delete_domain_limit := infinity}) -> |
98 |
47 |
mongoose_rdbms:prepare( |
99 |
|
inbox_delete_domain, inbox, [lserver], <<"DELETE FROM inbox WHERE lserver = ?">>); |
100 |
|
prepare_remove_domain(#{delete_domain_limit := Limit}) -> |
101 |
1 |
LimitSQL = case mongoose_rdbms:db_type() of |
102 |
:-( |
mssql -> throw(delete_domain_limit_not_supported_for_mssql); |
103 |
1 |
_ -> {MaybeLimitSQL, _} = rdbms_queries:get_db_specific_limits_binaries(Limit), |
104 |
1 |
MaybeLimitSQL |
105 |
|
end, |
106 |
1 |
ServerTable = <<"(SELECT * FROM ", |
107 |
|
"(SELECT lserver, luser, remote_bare_jid FROM inbox", |
108 |
|
" WHERE lserver = ? ", LimitSQL/binary, ") AS T)">>, |
109 |
1 |
mongoose_rdbms:prepare( |
110 |
|
inbox_incr_delete_domain, inbox, [lserver], |
111 |
|
<<"DELETE FROM inbox WHERE (lserver, luser, remote_bare_jid) IN ", ServerTable/binary>>). |
112 |
|
|
113 |
|
-spec get_inbox(HostType :: mongooseim:host_type(), |
114 |
|
LUser :: jid:luser(), |
115 |
|
LServer :: jid:lserver(), |
116 |
|
Params :: mod_inbox:get_inbox_params()) -> [mod_inbox:inbox_res()]. |
117 |
|
get_inbox(HostType, LUser, LServer, Params) -> |
118 |
1109 |
case get_inbox_rdbms(HostType, LUser, LServer, Params) of |
119 |
|
{selected, []} -> |
120 |
223 |
[]; |
121 |
|
{selected, Res} -> |
122 |
886 |
[decode_row(HostType, R) || R <- Res] |
123 |
|
end. |
124 |
|
|
125 |
|
-spec get_inbox_unread(mongooseim:host_type(), mod_inbox:entry_key()) -> |
126 |
|
{ok, integer()}. |
127 |
|
get_inbox_unread(HostType, {LUser, LServer, RemBareJID}) -> |
128 |
:-( |
Res = execute_select_unread_count(HostType, LUser, LServer, RemBareJID), |
129 |
:-( |
{ok, Val} = check_result(Res), |
130 |
|
%% We read unread_count value when the message is sent and is not yet in receiver inbox |
131 |
|
%% so we have to add +1 |
132 |
:-( |
{ok, Val + 1}. |
133 |
|
|
134 |
|
-spec set_inbox(HostType, InboxEntryKey, Packet, Count, MsgId, Timestamp) -> |
135 |
|
mod_inbox:write_res() when |
136 |
|
HostType :: mongooseim:host_type(), |
137 |
|
InboxEntryKey :: mod_inbox:entry_key(), |
138 |
|
Packet :: exml:element(), |
139 |
|
Count :: integer(), |
140 |
|
MsgId :: binary(), |
141 |
|
Timestamp :: integer(). |
142 |
|
set_inbox(HostType, {LUser, LServer, LToBareJid}, Packet, Count, MsgId, Timestamp) -> |
143 |
153 |
Content = exml:to_binary(Packet), |
144 |
153 |
Unique = [LUser, LServer, LToBareJid], |
145 |
153 |
Update = [MsgId, <<"inbox">>, Content, Count, Timestamp], |
146 |
153 |
Insert = [LUser, LServer, LToBareJid, MsgId, <<"inbox">>, Content, Count, Timestamp], |
147 |
153 |
Res = rdbms_queries:execute_upsert(HostType, inbox_upsert, Insert, Update, Unique), |
148 |
|
%% MySQL returns 1 when an upsert is an insert |
149 |
|
%% and 2, when an upsert acts as update |
150 |
153 |
check_result_is_expected(Res, [1, 2]). |
151 |
|
|
152 |
|
-spec empty_user_bin(HostType :: mongooseim:host_type(), |
153 |
|
LServer :: jid:lserver(), |
154 |
|
LUser :: jid:luser(), |
155 |
|
TS :: integer()) -> non_neg_integer(). |
156 |
|
empty_user_bin(HostType, LServer, LUser, TS) -> |
157 |
8 |
{updated, BinN} = mongoose_rdbms:execute_successfully( |
158 |
|
HostType, inbox_clean_user_bin, [LServer, LUser, TS]), |
159 |
8 |
mongoose_rdbms:result_to_integer(BinN). |
160 |
|
|
161 |
|
-spec empty_domain_bin(HostType :: mongooseim:host_type(), |
162 |
|
LServer :: jid:lserver(), |
163 |
|
TS :: integer()) -> non_neg_integer(). |
164 |
|
empty_domain_bin(HostType, LServer, TS) -> |
165 |
7 |
{updated, BinN} = mongoose_rdbms:execute_successfully( |
166 |
|
HostType, inbox_clean_domain_bin, [LServer, TS]), |
167 |
7 |
mongoose_rdbms:result_to_integer(BinN). |
168 |
|
|
169 |
|
-spec empty_global_bin(HostType :: mongooseim:host_type(), |
170 |
|
TS :: integer()) -> non_neg_integer(). |
171 |
|
empty_global_bin(HostType, TS) -> |
172 |
22 |
case mongoose_rdbms:execute(HostType, inbox_clean_global_bin, [TS]) of |
173 |
|
{updated, BinN} -> |
174 |
22 |
mongoose_rdbms:result_to_integer(BinN); |
175 |
|
{Error, Reason} -> |
176 |
:-( |
?LOG_WARNING(#{what => inbox_clean_global_bin_failed, |
177 |
:-( |
error => Error, reason => Reason}), |
178 |
:-( |
0 |
179 |
|
end. |
180 |
|
|
181 |
|
-spec remove_inbox_row(HostType :: mongooseim:host_type(), |
182 |
|
InboxEntryKey :: mod_inbox:entry_key()) -> mod_inbox:write_res(). |
183 |
|
remove_inbox_row(HostType, {LUser, LServer, LToBareJid}) -> |
184 |
5 |
Res = execute_delete(HostType, LUser, LServer, LToBareJid), |
185 |
5 |
check_result(Res). |
186 |
|
|
187 |
|
-spec remove_domain(HostType :: mongooseim:host_type(), |
188 |
|
LServer :: jid:lserver()) -> term(). |
189 |
|
remove_domain(HostType, LServer) -> |
190 |
2 |
DeleteDomainLimit = gen_mod:get_module_opt(HostType, mod_inbox, delete_domain_limit), |
191 |
2 |
execute_delete_domain(HostType, LServer, DeleteDomainLimit). |
192 |
|
|
193 |
|
-spec set_inbox_incr_unread( |
194 |
|
mongooseim:host_type(), mod_inbox:entry_key(), exml:element(), binary(), integer()) -> |
195 |
|
mod_inbox:count_res(). |
196 |
|
set_inbox_incr_unread(HostType, Entry, Packet, MsgId, Timestamp) -> |
197 |
226 |
set_inbox_incr_unread(HostType, Entry, Packet, MsgId, Timestamp, 1). |
198 |
|
|
199 |
|
-spec set_inbox_incr_unread(HostType :: mongooseim:host_type(), |
200 |
|
InboxEntryKey :: mod_inbox:entry_key(), |
201 |
|
Packet :: exml:element(), |
202 |
|
MsgId :: binary(), |
203 |
|
Timestamp :: integer(), |
204 |
|
Incrs :: pos_integer()) -> mod_inbox:count_res(). |
205 |
|
set_inbox_incr_unread(HostType, {LUser, LServer, LToBareJid}, Packet, MsgId, Timestamp, Incrs) -> |
206 |
226 |
Content = exml:to_binary(Packet), |
207 |
226 |
Unique = [LUser, LServer, LToBareJid], |
208 |
226 |
Update = [MsgId, <<"inbox">>, Content, Incrs, Timestamp], |
209 |
226 |
Insert = [LUser, LServer, LToBareJid, MsgId, <<"inbox">>, Content, Incrs, Timestamp], |
210 |
226 |
Res = rdbms_queries:execute_upsert(HostType, inbox_upsert_incr_unread, Insert, Update, Unique), |
211 |
226 |
check_result(Res). |
212 |
|
|
213 |
|
-spec reset_unread(HostType :: mongooseim:host_type(), |
214 |
|
InboxEntryKey :: mod_inbox:entry_key(), |
215 |
|
MsgId :: binary() | undefined, |
216 |
|
TS :: integer()) -> mod_inbox:write_res(). |
217 |
|
reset_unread(HostType, {LUser, LServer, LToBareJid}, MsgId, TS) -> |
218 |
57 |
Res = execute_reset_unread(HostType, LUser, LServer, LToBareJid, MsgId, TS), |
219 |
57 |
check_result(Res). |
220 |
|
|
221 |
|
-spec clear_inbox(HostType :: mongooseim:host_type(), |
222 |
|
LUser :: jid:luser(), |
223 |
|
LServer :: jid:lserver()) -> mod_inbox:write_res(). |
224 |
|
clear_inbox(HostType, LUser, LServer) -> |
225 |
718 |
Res = execute_delete(HostType, LUser, LServer), |
226 |
718 |
check_result(Res). |
227 |
|
|
228 |
|
-spec get_full_entry(HostType :: mongooseim:host_type(), |
229 |
|
InboxEntryKey :: mod_inbox:entry_key()) -> |
230 |
|
inbox_res() | nil(). |
231 |
|
get_full_entry(HostType, {LUser, LServer, RemBareJID}) -> |
232 |
2 |
case execute_select_full_entry(HostType, LUser, LServer, RemBareJID) of |
233 |
|
{selected, []} -> |
234 |
:-( |
[]; |
235 |
|
{selected, [Selected]} -> |
236 |
2 |
decode_row(HostType, Selected) |
237 |
|
end. |
238 |
|
|
239 |
|
-spec get_entry_properties(HostType :: mongooseim:host_type(), |
240 |
|
InboxEntryKey :: mod_inbox:entry_key()) -> |
241 |
|
entry_properties() | nil(). |
242 |
|
get_entry_properties(HostType, {LUser, LServer, RemBareJID}) -> |
243 |
2 |
case execute_select_properties(HostType, LUser, LServer, RemBareJID) of |
244 |
|
{selected, []} -> |
245 |
:-( |
[]; |
246 |
|
{selected, [Selected]} -> |
247 |
2 |
decode_properties(Selected) |
248 |
|
end. |
249 |
|
|
250 |
|
-spec set_entry_properties(HostType :: mongooseim:host_type(), |
251 |
|
InboxEntryKey :: mod_inbox:entry_key(), |
252 |
|
entry_properties()) -> |
253 |
|
entry_properties() | {error, binary()}. |
254 |
|
set_entry_properties(HostType, {LUser, LServer, RemBareJID}, Properties) -> |
255 |
102 |
case set_entry_properties_rdbms(HostType, LUser, LServer, RemBareJID, Properties) of |
256 |
|
{error, Msg} when is_list(Msg) -> |
257 |
:-( |
{error, list_to_binary(Msg)}; |
258 |
|
{error, Msg} -> |
259 |
:-( |
{error, Msg}; |
260 |
|
{updated, 0} -> |
261 |
8 |
{error, <<"item-not-found">>}; |
262 |
|
{selected, [Result]} -> |
263 |
94 |
decode_properties(Result) |
264 |
|
end. |
265 |
|
|
266 |
|
%% ---------------------------------------------------------------------- |
267 |
|
%% Internal functions |
268 |
|
%% ---------------------------------------------------------------------- |
269 |
|
|
270 |
|
-spec get_inbox_rdbms(HostType :: mongooseim:host_type(), |
271 |
|
LUser :: jid:luser(), |
272 |
|
LServer :: jid:lserver(), |
273 |
|
Params :: mod_inbox:get_inbox_params()) -> |
274 |
|
mongoose_rdbms:query_result(). |
275 |
|
get_inbox_rdbms(HostType, LUser, LServer, Params) -> |
276 |
1109 |
QueryName = lookup_query_name(Params), |
277 |
1109 |
case mongoose_rdbms:prepared(QueryName) of |
278 |
|
false -> |
279 |
19 |
SQL = lookup_query(Params), |
280 |
19 |
Columns = lookup_query_columns(Params), |
281 |
19 |
mongoose_rdbms:prepare(QueryName, inbox, Columns, SQL); |
282 |
|
true -> |
283 |
1090 |
ok |
284 |
|
end, |
285 |
1109 |
Args = lookup_query_args(LServer, LUser, Params), |
286 |
1109 |
mongoose_rdbms:execute_successfully(HostType, QueryName, Args). |
287 |
|
|
288 |
|
set_entry_properties_rdbms(HostType, LUser, LServer, RemBareJID, Properties) -> |
289 |
102 |
QueryName = update_query_name(Properties), |
290 |
102 |
case mongoose_rdbms:prepared(QueryName) of |
291 |
|
false -> |
292 |
6 |
SQL = update_properties_query(Properties), |
293 |
6 |
Columns = update_query_columns(Properties), |
294 |
6 |
mongoose_rdbms:prepare(QueryName, inbox, Columns, SQL); |
295 |
|
true -> |
296 |
96 |
ok |
297 |
|
end, |
298 |
102 |
{atomic, TransactionResult} = |
299 |
|
mongoose_rdbms:sql_transaction( |
300 |
|
LServer, |
301 |
102 |
fun() -> set_entry_properties_t(HostType, QueryName, LUser, LServer, RemBareJID, Properties) end), |
302 |
102 |
TransactionResult. |
303 |
|
|
304 |
|
-spec set_entry_properties_t(mongooseim:host_type(), atom(), jid:luser(), jid:lserver(), jid:literal_jid(), |
305 |
|
entry_properties()) -> |
306 |
|
mongoose_rdbms:query_result(). |
307 |
|
set_entry_properties_t(HostType, QueryName, LUser, LServer, RemBareJID, Properties) -> |
308 |
102 |
Args = update_query_args(LUser, LServer, RemBareJID, Properties), |
309 |
102 |
case mongoose_rdbms:execute_successfully(HostType, QueryName, Args) of |
310 |
|
{updated, 1} -> |
311 |
94 |
execute_select_properties(HostType, LUser, LServer, RemBareJID); |
312 |
|
Other -> |
313 |
8 |
Other |
314 |
|
end. |
315 |
|
|
316 |
|
%% Inbox lookup |
317 |
|
|
318 |
|
-spec lookup_query(mod_inbox:get_inbox_params()) -> iolist(). |
319 |
|
lookup_query(#{order := Order} = Params) -> |
320 |
19 |
OrderSQL = order_to_sql(Order), |
321 |
19 |
{LimitSQL, MSLimitSQL} = sql_and_where_limit(maps:get(limit, Params, undefined)), |
322 |
19 |
Conditions = [lookup_sql_condition(Key, maps:get(Key, Params, undefined)) |
323 |
19 |
|| Key <- [start, 'end', hidden_read, box]], |
324 |
19 |
["SELECT ", MSLimitSQL, query_row(), |
325 |
|
" FROM inbox WHERE luser = ? AND lserver = ?", Conditions, |
326 |
|
" ORDER BY timestamp ", OrderSQL, " ", LimitSQL]. |
327 |
|
|
328 |
|
-spec lookup_query_args(jid:lserver(), jid:luser(), mod_inbox:get_inbox_params()) -> list(). |
329 |
|
lookup_query_args(LServer, LUser, Params) -> |
330 |
1109 |
Args = [LUser, LServer | [maps:get(Key, Params) || Key <- lookup_arg_keys(Params)]], |
331 |
1109 |
case maps:get(limit, Params, undefined) of |
332 |
1092 |
undefined -> Args; |
333 |
17 |
Limit -> rdbms_queries:add_limit_arg(Limit, Args) |
334 |
|
end. |
335 |
|
|
336 |
|
-spec lookup_query_columns(mod_inbox:get_inbox_params()) -> [atom()]. |
337 |
|
lookup_query_columns(Params) -> |
338 |
19 |
Columns = [luser, lserver | lists:map(fun param_to_column/1, lookup_arg_keys(Params))], |
339 |
19 |
case maps:get(limit, Params, undefined) of |
340 |
15 |
undefined -> Columns; |
341 |
4 |
_ -> rdbms_queries:add_limit_arg(limit, Columns) |
342 |
|
end. |
343 |
|
|
344 |
|
-spec lookup_arg_keys(mod_inbox:get_inbox_params()) -> [atom()]. |
345 |
|
lookup_arg_keys(Params) -> |
346 |
1128 |
lists:filter( |
347 |
1128 |
fun(box) -> maps:is_key(box, Params) andalso maps:get(box, Params, undefined) =/= <<"all">>; |
348 |
2256 |
(Key) -> maps:is_key(Key, Params) |
349 |
|
end, [start, 'end', box]). |
350 |
|
|
351 |
|
-spec lookup_query_name(mod_inbox:get_inbox_params()) -> atom(). |
352 |
|
lookup_query_name(Params) -> |
353 |
1109 |
IDString = lists:flatmap(fun(Param) -> |
354 |
6654 |
param_id(Param, maps:get(Param, Params, undefined)) |
355 |
|
end, lookup_param_keys()), |
356 |
1109 |
list_to_atom("inbox_lookup" ++ IDString). |
357 |
|
|
358 |
|
-spec lookup_param_keys() -> [atom()]. |
359 |
|
lookup_param_keys() -> |
360 |
1109 |
[order, limit, start, 'end', hidden_read, box]. |
361 |
|
|
362 |
|
-spec param_to_column(atom()) -> atom(). |
363 |
7 |
param_to_column(start) -> timestamp; |
364 |
5 |
param_to_column('end') -> timestamp; |
365 |
8 |
param_to_column(box) -> box. |
366 |
|
|
367 |
|
-spec param_id(Key :: atom(), Value :: any()) -> string(). |
368 |
894 |
param_id(box, undefined) -> "_no_bin"; |
369 |
5 |
param_id(box, <<"all">>) -> ""; |
370 |
210 |
param_id(box, _) -> "_box"; |
371 |
3229 |
param_id(_, undefined) -> ""; |
372 |
1072 |
param_id(order, desc) -> "_desc"; |
373 |
37 |
param_id(order, asc) -> "_asc"; |
374 |
17 |
param_id(limit, _) -> "_lim"; |
375 |
41 |
param_id(start, _) -> "_start"; |
376 |
40 |
param_id('end', _) -> "_end"; |
377 |
13 |
param_id(hidden_read, true) -> "_hr"; |
378 |
1096 |
param_id(hidden_read, false) -> "". |
379 |
|
|
380 |
|
-spec order_to_sql(Order :: asc | desc) -> binary(). |
381 |
5 |
order_to_sql(asc) -> <<"ASC">>; |
382 |
14 |
order_to_sql(desc) -> <<"DESC">>. |
383 |
|
|
384 |
|
-spec sql_and_where_limit(non_neg_integer() | undefined) -> {iolist(), iolist()}. |
385 |
|
sql_and_where_limit(undefined) -> |
386 |
15 |
{"", ""}; |
387 |
|
sql_and_where_limit(_) -> |
388 |
4 |
rdbms_queries:get_db_specific_limits(). |
389 |
|
|
390 |
|
-spec lookup_sql_condition(Key :: atom(), Value :: any()) -> string(). |
391 |
|
lookup_sql_condition(start, Timestamp) when is_integer(Timestamp) -> |
392 |
7 |
" AND timestamp >= ?"; |
393 |
|
lookup_sql_condition('end', Timestamp) when is_integer(Timestamp) -> |
394 |
5 |
" AND timestamp <= ?"; |
395 |
|
lookup_sql_condition(hidden_read, true) -> |
396 |
1 |
" AND unread_count > 0"; |
397 |
|
lookup_sql_condition(box, undefined) -> |
398 |
10 |
" AND box <> 'bin'"; |
399 |
|
lookup_sql_condition(box, <<"all">>) -> |
400 |
1 |
""; |
401 |
|
lookup_sql_condition(box, Val) when is_binary(Val) -> |
402 |
8 |
" AND box = ?"; |
403 |
|
lookup_sql_condition(_, _) -> |
404 |
44 |
"". |
405 |
|
|
406 |
|
%% Property update |
407 |
|
|
408 |
|
update_properties_query(Properties) -> |
409 |
6 |
KVs = [{Key, maps:get(Key, Properties, undefined)} || Key <- property_keys()], |
410 |
6 |
Parts = [update_sql_part(Key, Value) || {Key, Value} <- KVs, Value =/= undefined], |
411 |
6 |
["UPDATE inbox SET ", string:join(Parts, ", "), |
412 |
|
" WHERE luser = ? AND lserver = ? AND remote_bare_jid = ?"]. |
413 |
|
|
414 |
|
update_query_args(LUser, LServer, RemBareJID, Properties) -> |
415 |
102 |
[maps:get(Key, Properties) || Key <- update_arg_keys(Properties)] ++ |
416 |
|
[LUser, LServer, RemBareJID]. |
417 |
|
|
418 |
|
update_query_columns(Properties) -> |
419 |
6 |
update_arg_keys(Properties) ++ [luser, lserver, remote_bare_jid]. |
420 |
|
|
421 |
|
update_arg_keys(Properties) -> |
422 |
108 |
lists:filter(fun(Key) -> maps:is_key(Key, Properties) end, [box, muted_until]). |
423 |
|
|
424 |
|
update_query_name(Properties) -> |
425 |
102 |
IDString = lists:flatmap(fun(Prop) -> |
426 |
306 |
property_id(Prop, maps:get(Prop, Properties, undefined)) |
427 |
|
end, property_keys()), |
428 |
102 |
list_to_atom("inbox_update_properties" ++ IDString). |
429 |
|
|
430 |
|
property_keys() -> |
431 |
108 |
[unread_count, box, muted_until]. |
432 |
|
|
433 |
|
-spec property_id(Key :: atom(), Value :: any()) -> string(). |
434 |
194 |
property_id(_, undefined) -> ""; |
435 |
16 |
property_id(unread_count, 0) -> "_read"; |
436 |
8 |
property_id(unread_count, 1) -> "_unread"; |
437 |
68 |
property_id(box, _) -> "_box"; |
438 |
20 |
property_id(muted_until, _) -> "_muted". |
439 |
|
|
440 |
|
-spec update_sql_part(Key :: atom(), Value :: any()) -> string(). |
441 |
|
update_sql_part(unread_count, 0) -> |
442 |
3 |
"unread_count = 0"; |
443 |
|
update_sql_part(unread_count, 1) -> |
444 |
1 |
"unread_count = CASE unread_count WHEN 0 THEN 1 ELSE unread_count END"; |
445 |
|
update_sql_part(box, Val) when is_binary(Val) -> |
446 |
2 |
"box = ?"; |
447 |
|
update_sql_part(muted_until, Val) when is_integer(Val) -> |
448 |
3 |
"muted_until = ?". |
449 |
|
|
450 |
|
%% Query execution |
451 |
|
|
452 |
|
-spec execute_select_unread_count(mongooseim:host_type(), jid:luser(), jid:lserver(), jid:literal_jid()) -> |
453 |
|
mongoose_rdbms:query_result(). |
454 |
|
execute_select_unread_count(HostType, LUser, LServer, RemBareJID) -> |
455 |
:-( |
mongoose_rdbms:execute_successfully(HostType, inbox_select_unread_count, |
456 |
|
[LUser, LServer, RemBareJID]). |
457 |
|
|
458 |
|
-spec execute_select_full_entry(mongooseim:host_type(), jid:luser(), jid:lserver(), jid:literal_jid()) -> |
459 |
|
mongoose_rdbms:query_result(). |
460 |
|
execute_select_full_entry(HostType, LUser, LServer, RemBareJID) -> |
461 |
2 |
mongoose_rdbms:execute_successfully(HostType, inbox_select_entry, |
462 |
|
[LUser, LServer, RemBareJID]). |
463 |
|
|
464 |
|
-spec execute_select_properties(mongooseim:host_type(), jid:luser(), jid:lserver(), jid:literal_jid()) -> |
465 |
|
mongoose_rdbms:query_result(). |
466 |
|
execute_select_properties(HostType, LUser, LServer, RemBareJID) -> |
467 |
96 |
mongoose_rdbms:execute_successfully(HostType, inbox_select_properties, |
468 |
|
[LUser, LServer, RemBareJID]). |
469 |
|
|
470 |
|
-spec execute_reset_unread(mongooseim:host_type(), jid:luser(), jid:lserver(), jid:literal_jid(), binary() | undefined, integer()) -> |
471 |
|
mongoose_rdbms:query_result(). |
472 |
|
execute_reset_unread(HostType, LUser, LServer, RemBareJID, undefined, TS) -> |
473 |
3 |
mongoose_rdbms:execute_successfully(HostType, inbox_reset_unread, |
474 |
|
[LUser, LServer, RemBareJID, TS]); |
475 |
|
execute_reset_unread(HostType, LUser, LServer, RemBareJID, MsgId, TS) -> |
476 |
54 |
mongoose_rdbms:execute_successfully(HostType, inbox_reset_unread_msg, |
477 |
|
[LUser, LServer, RemBareJID, MsgId, TS]). |
478 |
|
|
479 |
|
-spec execute_delete(mongooseim:host_type(), |
480 |
|
jid:luser(), jid:lserver(), jid:literal_jid()) -> |
481 |
|
mongoose_rdbms:query_result(). |
482 |
|
execute_delete(HostType, LUser, LServer, RemBareJID) -> |
483 |
5 |
mongoose_rdbms:execute_successfully(HostType, inbox_delete_row, [LUser, LServer, RemBareJID]). |
484 |
|
|
485 |
|
-spec execute_delete(mongooseim:host_type(), jid:lserver(), jid:luser()) -> mongoose_rdbms:query_result(). |
486 |
|
execute_delete(HostType, LUser, LServer) -> |
487 |
718 |
mongoose_rdbms:execute_successfully(HostType, inbox_delete, [LUser, LServer]). |
488 |
|
|
489 |
|
-spec execute_delete_domain(mongooseim:host_type(), jid:lserver(), infinity | non_neg_integer()) -> |
490 |
|
mongoose_rdbms:query_result(). |
491 |
|
execute_delete_domain(HostType, LServer, infinity) -> |
492 |
1 |
mongoose_rdbms:execute_successfully(HostType, inbox_delete_domain, [LServer]); |
493 |
|
execute_delete_domain(HostType, LServer, Limit) -> |
494 |
1 |
mod_mam_utils:incremental_delete_domain(HostType, LServer, Limit, [inbox_incr_delete_domain], 0). |
495 |
|
|
496 |
|
%% DB result processing |
497 |
|
-type db_return() :: {RemBareJID :: jid:luser(), |
498 |
|
MsgId :: id(), |
499 |
|
MsgContents :: binary(), |
500 |
|
Timestamp :: non_neg_integer() | binary(), |
501 |
|
Count :: non_neg_integer() | binary(), |
502 |
|
MutedUntil :: binary(), |
503 |
|
Box :: binary()}. |
504 |
|
|
505 |
|
query_row() -> |
506 |
67 |
<<" remote_bare_jid, msg_id, box, content, timestamp, muted_until, unread_count ">>. |
507 |
|
|
508 |
|
-spec decode_row(mongooseim:host_type(), db_return()) -> inbox_res(). |
509 |
|
decode_row(HostType, {RemBareJID, MsgId, Box, Content, Timestamp, MutedUntil, Count}) -> |
510 |
973 |
{ok, Parsed} = exml:parse(mongoose_rdbms:unescape_binary(HostType, Content)), |
511 |
973 |
BCount = mongoose_rdbms:result_to_integer(Count), |
512 |
973 |
NumericTimestamp = mongoose_rdbms:result_to_integer(Timestamp), |
513 |
973 |
NumericMutedUntil = mongoose_rdbms:result_to_integer(MutedUntil), |
514 |
973 |
#{remote_jid => RemBareJID, |
515 |
|
msg_id => MsgId, |
516 |
|
box => Box, |
517 |
|
msg => Parsed, |
518 |
|
timestamp => NumericTimestamp, |
519 |
|
muted_until => NumericMutedUntil, |
520 |
|
unread_count => BCount, |
521 |
|
extra => []}. |
522 |
|
|
523 |
|
-spec decode_properties({_, _, _}) -> entry_properties(). |
524 |
|
decode_properties({Box, BMutedUntil, BCount}) -> |
525 |
96 |
Count = mongoose_rdbms:result_to_integer(BCount), |
526 |
96 |
MutedUntil = mongoose_rdbms:result_to_integer(BMutedUntil), |
527 |
96 |
#{box => Box, |
528 |
|
unread_count => Count, |
529 |
|
muted_until => MutedUntil, |
530 |
|
extra => []}. |
531 |
|
|
532 |
|
-spec check_result_is_expected(_, list()) -> mod_inbox:write_res(). |
533 |
|
check_result_is_expected({updated, Val}, ValList) when is_list(ValList) -> |
534 |
153 |
case lists:member(Val, ValList) of |
535 |
153 |
true -> ok; |
536 |
:-( |
_ -> {error, {expected_does_not_match, Val, ValList}} |
537 |
|
end; |
538 |
|
check_result_is_expected(Result, _) -> |
539 |
:-( |
{error, {bad_result, Result}}. |
540 |
|
|
541 |
|
-spec check_result(_) -> mod_inbox:count_res(). |
542 |
|
check_result({selected, []}) -> |
543 |
:-( |
{ok, 0}; |
544 |
|
check_result({selected, [{Val}]}) -> |
545 |
:-( |
parse_result(Val); |
546 |
|
check_result({updated, _, [{Val}]}) -> |
547 |
:-( |
parse_result(Val); |
548 |
|
check_result({updated, _}) -> |
549 |
1634 |
ok; |
550 |
|
check_result(Result) -> |
551 |
:-( |
{error, {bad_result, Result}}. |
552 |
|
|
553 |
|
parse_result(Value) when is_integer(Value) -> |
554 |
:-( |
{ok, Value}; |
555 |
|
parse_result(Value) when is_binary(Value) -> |
556 |
:-( |
{ok, binary_to_integer(Value)}; |
557 |
|
parse_result(null) -> |
558 |
:-( |
{ok, 0}; |
559 |
|
parse_result(Value) -> |
560 |
:-( |
{error, {unknown_result_value_type, Value}}. |