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