./ct_report/coverage/mod_smart_markers_rdbms.COVER.html

1 %%%----------------------------------------------------------------------------
2 %%% @doc
3 %%% RDBMS backend for mod_smart_markers
4 %%% @end
5 %%% @copyright (C) 2020-2022, Erlang Solutions Ltd.
6 %%%----------------------------------------------------------------------------
7 -module(mod_smart_markers_rdbms).
8 -author("denysgonchar").
9 -behavior(mod_smart_markers_backend).
10
11 -include("jlib.hrl").
12 -include("mongoose_logger.hrl").
13
14 -export([init/2, update_chat_marker/2, get_chat_markers/4]).
15 -export([get_conv_chat_marker/6]).
16 -export([remove_domain/2, remove_user/2, remove_to/2, remove_to_for_user/3]).
17 -export([encode_jid/1, encode_thread/1, encode_type/1, verify/2]).
18
19 %%--------------------------------------------------------------------
20 %% API
21 %%--------------------------------------------------------------------
22 -spec init(mongooseim:host_type(), gen_mod:module_opts()) -> ok.
23 init(HostType, _) ->
24 8 KeyFields = [<<"lserver">>, <<"luser">>, <<"to_jid">>, <<"thread">>, <<"type">>],
25 8 UpdateFields = [<<"msg_id">>, <<"timestamp">>],
26 8 InsertFields = KeyFields ++ UpdateFields,
27 8 rdbms_queries:prepare_upsert(HostType, smart_markers_upsert, smart_markers,
28 InsertFields, UpdateFields, KeyFields, <<"timestamp">>),
29 8 mongoose_rdbms:prepare(smart_markers_select_conv, smart_markers,
30 [lserver, luser, to_jid, thread, timestamp],
31 <<"SELECT lserver, luser, to_jid, thread, type, msg_id, timestamp FROM smart_markers "
32 "WHERE lserver = ? AND luser = ? AND to_jid = ? AND thread = ? AND timestamp >= ?">>),
33 8 mongoose_rdbms:prepare(smart_markers_select, smart_markers,
34 [to_jid, thread, timestamp],
35 <<"SELECT lserver, luser, type, msg_id, timestamp FROM smart_markers "
36 "WHERE to_jid = ? AND thread = ? AND timestamp >= ?">>),
37 8 mongoose_rdbms:prepare(markers_remove_domain, smart_markers,
38 [lserver], <<"DELETE FROM smart_markers WHERE lserver=?">>),
39 8 mongoose_rdbms:prepare(markers_remove_user, smart_markers,
40 [lserver, luser], <<"DELETE FROM smart_markers WHERE lserver=? AND luser=?">>),
41 8 mongoose_rdbms:prepare(markers_remove_to, smart_markers,
42 [to_jid], <<"DELETE FROM smart_markers WHERE to_jid=?">>),
43 8 mongoose_rdbms:prepare(markers_remove_to_for_user, smart_markers,
44 [lserver, luser, to_jid],
45 <<"DELETE FROM smart_markers WHERE lserver=? AND luser=? AND to_jid=?">>),
46 8 ok.
47
48 %%% @doc
49 %%% 'from', 'to', 'thread' and 'type' keys of the ChatMarker map serve
50 %%% as a composite database key. If key is not available in the database,
51 %%% then chat marker must be added. Otherwise this function must update
52 %%% chat marker record for that composite key.
53 %%% @end
54 -spec update_chat_marker(mongooseim:host_type(),
55 mod_smart_markers:chat_marker()) -> ok.
56 update_chat_marker(HostType, #{from := #jid{luser = LU, lserver = LS},
57 to := To, thread := Thread,
58 type := Type, timestamp := TS, id := Id} = Marker) ->
59 30 ToEncoded = encode_jid(To),
60 30 ThreadEncoded = encode_thread(Thread),
61 30 TypeEncoded = encode_type(Type),
62 30 KeyValues = [LS, LU, ToEncoded, ThreadEncoded, TypeEncoded],
63 30 UpdateValues = [Id, TS],
64 30 InsertValues = KeyValues ++ UpdateValues,
65 30 Res = rdbms_queries:execute_upsert(HostType, smart_markers_upsert,
66 InsertValues, UpdateValues, KeyValues),
67 30 verify(Res, Marker).
68
69 -spec get_conv_chat_marker(HostType :: mongooseim:host_type(),
70 From :: jid:jid(),
71 To :: jid:jid(),
72 Thread :: mod_smart_markers:maybe_thread(),
73 Timestamp :: integer(),
74 Private :: boolean()) -> [mod_smart_markers:chat_marker()].
75 get_conv_chat_marker(HostType, From, To = #jid{lserver = ToLServer}, Thread, TS, Private) ->
76 % If To is a room, we'll want to check just the room
77 24 case mongoose_domain_api:get_subdomain_host_type(ToLServer) of
78 {error, not_found} ->
79 17 one2one_get_conv_chat_marker(HostType, From, To, Thread, TS, Private);
80 {ok, _} ->
81 7 groupchat_get_conv_chat_marker(HostType, From, To, Thread, TS, Private)
82 end.
83
84 one2one_get_conv_chat_marker(HostType,
85 From = #jid{luser = FromLUser, lserver = FromLServer},
86 To = #jid{luser = ToLUser, lserver = ToLServer},
87 Thread, TS, Private) ->
88 17 {selected, ChatMarkersFrom} = mongoose_rdbms:execute_successfully(
89 HostType, smart_markers_select_conv,
90 [FromLServer, FromLUser, encode_jid(To), encode_thread(Thread), TS]),
91 17 ChatMarkers = case Private of
92 5 true -> ChatMarkersFrom;
93 false ->
94 12 {selected, ChatMarkersTo} = mongoose_rdbms:execute_successfully(
95 HostType, smart_markers_select_conv,
96 [ToLServer, ToLUser, encode_jid(From), encode_thread(Thread), TS]),
97 12 ChatMarkersFrom ++ ChatMarkersTo
98 end,
99 17 [ decode_chat_marker(Tuple) || Tuple <- ChatMarkers].
100
101 groupchat_get_conv_chat_marker(HostType, _From, To, Thread, TS, false) ->
102 2 get_chat_markers(HostType, To, Thread, TS);
103 groupchat_get_conv_chat_marker(HostType, #jid{luser = FromLUser, lserver = FromLServer}, To, Thread, TS, true) ->
104 5 {selected, ChatMarkers} = mongoose_rdbms:execute_successfully(
105 HostType, smart_markers_select_conv,
106 [FromLServer, FromLUser, encode_jid(To), encode_thread(Thread), TS]),
107 5 [ decode_chat_marker(Tuple) || Tuple <- ChatMarkers].
108
109 %%% @doc
110 %%% This function must return the latest chat markers sent to the
111 %%% user/room (with or w/o thread) later than provided timestamp.
112 %%% @end
113 -spec get_chat_markers(HostType :: mongooseim:host_type(),
114 To :: jid:jid(),
115 Thread :: mod_smart_markers:maybe_thread(),
116 Timestamp :: integer()) -> [mod_smart_markers:chat_marker()].
117 get_chat_markers(HostType, To, Thread, TS) ->
118 29 {selected, ChatMarkers} = mongoose_rdbms:execute_successfully(
119 HostType, smart_markers_select,
120 [encode_jid(To), encode_thread(Thread), TS]),
121 29 [ #{from => jid:make_noprep(CLUser, CLServer, <<>>),
122 to => To,
123 thread => Thread,
124 type => decode_type(CType),
125 timestamp => decode_timestamp(CTS),
126 id => CMsgId}
127 29 || {CLServer, CLUser, CType, CMsgId, CTS} <- ChatMarkers].
128
129
130 -spec remove_domain(mongooseim:host_type(), jid:lserver()) -> mongoose_rdbms:query_result().
131 remove_domain(HostType, Domain) ->
132 1 mongoose_rdbms:execute_successfully(HostType, markers_remove_domain, [Domain]).
133
134 -spec remove_user(mongooseim:host_type(), jid:jid()) -> mongoose_rdbms:query_result().
135 remove_user(HostType, #jid{luser = LU, lserver = LS}) ->
136 66 mongoose_rdbms:execute_successfully(HostType, markers_remove_user, [LS, LU]).
137
138 -spec remove_to(mongooseim:host_type(), jid:jid()) -> mongoose_rdbms:query_result().
139 remove_to(HostType, To) ->
140 4 mongoose_rdbms:execute_successfully(HostType, markers_remove_to, [encode_jid(To)]).
141
142 -spec remove_to_for_user(mongooseim:host_type(), From :: jid:jid(), To :: jid:jid()) ->
143 mongoose_rdbms:query_result().
144 remove_to_for_user(HostType, #jid{luser = LU, lserver = LS}, To) ->
145 2 mongoose_rdbms:execute_successfully(HostType, markers_remove_to_for_user, [LS, LU, encode_jid(To)]).
146
147 -spec verify(term(), mod_smart_markers:chat_marker()) -> ok.
148 verify(Answer, Marker) ->
149 49 case check_upsert_result(Answer) of
150 {error, Reason} ->
151
:-(
?LOG_WARNING(#{what => smart_marker_insert_failed, reason => Reason,
152
:-(
marker => Marker});
153 49 _ -> ok
154 end.
155
156 %%--------------------------------------------------------------------
157 %% local functions
158 %%--------------------------------------------------------------------
159 118 encode_jid(JID) -> jid:to_bare_binary(JID).
160
161 90 encode_thread(undefined) -> <<>>;
162 22 encode_thread(Thread) -> Thread.
163
164 8 encode_type(received) -> <<"R">>;
165 41 encode_type(displayed) -> <<"D">>;
166
:-(
encode_type(acknowledged) -> <<"A">>.
167
168 %% MySQL returns 1 when an upsert is an insert
169 %% and 2, when an upsert acts as update
170
:-(
check_upsert_result({updated, 0}) -> ok;
171 49 check_upsert_result({updated, 1}) -> ok;
172
:-(
check_upsert_result({updated, 2}) -> ok;
173 check_upsert_result(Result) ->
174
:-(
{error, {bad_result, Result}}.
175
176 decode_chat_marker({LS, LU, ToJid, MsgThread, Type, MsgId, MsgTS}) ->
177 19 #{from => jid:make_noprep(LU, LS, <<>>),
178 to => decode_jid(ToJid),
179 thread => decode_thread(MsgThread),
180 type => decode_type(Type),
181 timestamp => decode_timestamp(MsgTS),
182 id => MsgId}.
183
184 19 decode_jid(EncodedJID) -> jid:from_binary(EncodedJID).
185
186 15 decode_thread(<<>>) -> undefined;
187 4 decode_thread(Thread) -> Thread.
188
189 4 decode_type(<<"R">>) -> received;
190 34 decode_type(<<"D">>) -> displayed;
191
:-(
decode_type(<<"A">>) -> acknowledged.
192
193 decode_timestamp(EncodedTS) ->
194 38 mongoose_rdbms:result_to_integer(EncodedTS).
Line Hits Source