./ct_report/coverage/mongoose_domain_sql.COVER.html

1 -module(mongoose_domain_sql).
2
3 -export([start/1]).
4
5 -export([insert_domain/2,
6 delete_domain/2,
7 disable_domain/1,
8 enable_domain/1]).
9
10 -export([select_domain/1,
11 get_minmax_event_id/0,
12 count_events_between_ids/2,
13 get_event_ids_between/2,
14 select_from/2,
15 select_updates_between/2,
16 get_enabled_dynamic/0,
17 delete_events_older_than/1,
18 insert_dummy_event/1]).
19
20 %% interfaces only for integration tests
21 -export([prepare_test_queries/0,
22 erase_database/0,
23 insert_full_event/2,
24 insert_domain_settings_without_event/2]).
25
26 -ignore_xref([erase_database/0, prepare_test_queries/0, get_enabled_dynamic/0,
27 insert_full_event/2, insert_domain_settings_without_event/2]).
28
29 -include("mongoose_logger.hrl").
30
31 -import(mongoose_rdbms, [prepare/4, execute_successfully/3]).
32
33 -type event_id() :: non_neg_integer().
34 -type domain() :: binary().
35 -type row() :: {event_id(), domain(), mongooseim:host_type() | null}.
36 -export_type([row/0]).
37
38 start(_Opts) ->
39
:-(
{LimitSQL, LimitMSSQL} = rdbms_queries:get_db_specific_limits_binaries(),
40
:-(
Pool = get_db_pool(),
41
:-(
True = sql_true(Pool),
42 %% Settings
43
:-(
prepare(domain_insert_settings, domain_settings, [domain, host_type],
44 <<"INSERT INTO domain_settings (domain, host_type) "
45 "VALUES (?, ?)">>),
46
:-(
prepare(domain_update_settings_enabled, domain_settings,
47 [enabled, domain],
48 <<"UPDATE domain_settings "
49 "SET enabled = ? "
50 "WHERE domain = ?">>),
51
:-(
prepare(domain_delete_settings, domain_settings, [domain],
52 <<"DELETE FROM domain_settings WHERE domain = ?">>),
53
:-(
prepare(domain_select, domain_settings, [domain],
54 <<"SELECT host_type, enabled "
55 "FROM domain_settings WHERE domain = ?">>),
56
:-(
prepare(domain_select_from, domain_settings,
57 rdbms_queries:add_limit_arg(limit, [id]),
58 <<"SELECT ", LimitMSSQL/binary,
59 " id, domain, host_type "
60 " FROM domain_settings "
61 " WHERE id > ? AND enabled = ", True/binary, " "
62 " ORDER BY id ",
63 LimitSQL/binary>>),
64 %% Events
65
:-(
prepare(domain_insert_event, domain_events, [domain],
66 <<"INSERT INTO domain_events (domain) VALUES (?)">>),
67
:-(
prepare(domain_insert_full_event, domain_events, [id, domain],
68 <<"INSERT INTO domain_events (id, domain) VALUES (?, ?)">>),
69
:-(
prepare(domain_events_minmax, domain_events, [],
70 <<"SELECT MIN(id), MAX(id) FROM domain_events">>),
71
:-(
prepare(domain_count_event_between, domain_events, [id, id],
72 <<"SELECT COUNT(*) FROM domain_events WHERE id >= ? AND id <= ?">>),
73
:-(
prepare(domain_event_ids_between, domain_events, [id, id],
74 <<"SELECT id FROM domain_events WHERE id >= ? AND id <= ? ORDER BY id">>),
75
:-(
prepare(domain_events_delete_older_than, domain_events, [id],
76 <<"DELETE FROM domain_events WHERE id < ?">>),
77
:-(
prepare(domain_select_events_between, domain_events, [id, id],
78 <<"SELECT "
79 " domain_events.id, domain_events.domain, domain_settings.host_type "
80 " FROM domain_events "
81 " LEFT JOIN domain_settings ON "
82 "(domain_settings.domain = domain_events.domain AND "
83 "domain_settings.enabled = ", True/binary, ") "
84 " WHERE domain_events.id >= ? AND domain_events.id <= ? "
85 " ORDER BY domain_events.id ">>),
86
:-(
ok.
87
88 prepare_test_queries() ->
89
:-(
Pool = get_db_pool(),
90
:-(
True = sql_true(Pool),
91
:-(
prepare(domain_erase_settings, domain_settings, [],
92 <<"DELETE FROM domain_settings">>),
93
:-(
prepare(domain_erase_events, domain_events, [],
94 <<"DELETE FROM domain_events">>),
95
:-(
prepare(domain_get_enabled_dynamic, domain_settings, [],
96 <<"SELECT "
97 " domain, host_type "
98 " FROM domain_settings "
99 " WHERE enabled = ", True/binary, " "
100 " ORDER BY id">>),
101
:-(
prepare(domain_events_get_all, domain_events, [],
102 <<"SELECT id, domain FROM domain_events ORDER BY id">>).
103
104 sql_true(Pool) ->
105
:-(
case mongoose_rdbms:db_engine(Pool) of
106
:-(
pgsql -> <<"true">>;
107
:-(
_ -> <<"1">>
108 end.
109
110 %% ----------------------------------------------------------------------------
111 %% API
112 insert_domain(Domain, HostType) ->
113
:-(
transaction(fun(Pool) ->
114
:-(
case select_domain(Domain) of
115 {ok, #{host_type := HT}} when HT =:= HostType ->
116
:-(
ok; %% ignore second call
117 {error, not_found} ->
118
:-(
insert_domain_settings(Pool, Domain, HostType),
119
:-(
insert_domain_event(Pool, Domain),
120
:-(
ok;
121 {ok, _} ->
122
:-(
{error, duplicate}
123 end
124 end).
125
126 select_domain(Domain) ->
127
:-(
Pool = get_db_pool(),
128
:-(
case execute_successfully(Pool, domain_select, [Domain]) of
129 {selected, []} ->
130
:-(
{error, not_found};
131 {selected, [Row]} ->
132
:-(
{ok, row_to_map(Row)}
133 end.
134
135 delete_domain(Domain, HostType) ->
136
:-(
transaction(fun(Pool) ->
137
:-(
case select_domain(Domain) of
138 {ok, #{host_type := HT}} when HT =:= HostType ->
139
:-(
{updated, 1} = delete_domain_settings(Pool, Domain),
140
:-(
insert_domain_event(Pool, Domain),
141
:-(
ok;
142 {ok, _} ->
143
:-(
{error, wrong_host_type};
144 {error, not_found} ->
145
:-(
ok
146 end
147 end).
148
149 disable_domain(Domain) ->
150
:-(
set_enabled(Domain, false).
151
152 enable_domain(Domain) ->
153
:-(
set_enabled(Domain, true).
154
155 %% Returns smallest id first
156 select_from(FromId, Limit) ->
157
:-(
Pool = get_db_pool(),
158
:-(
Args = rdbms_queries:add_limit_arg(Limit, [FromId]),
159
:-(
{selected, Rows} = execute_successfully(Pool, domain_select_from, Args),
160
:-(
Rows.
161
162 get_enabled_dynamic() ->
163
:-(
prepare_test_queries(),
164
:-(
Pool = get_db_pool(),
165
:-(
{selected, Rows} = execute_successfully(Pool, domain_get_enabled_dynamic, []),
166
:-(
Rows.
167
168 %% FromId, ToId are included into the result
169 -spec select_updates_between(event_id(), event_id()) -> [row()].
170 select_updates_between(FromId, ToId) ->
171
:-(
Pool = get_db_pool(),
172
:-(
Args = [FromId, ToId],
173
:-(
{selected, Rows} = execute_successfully(Pool, domain_select_events_between, Args),
174
:-(
Rows.
175
176 get_minmax_event_id() ->
177
:-(
Pool = get_db_pool(),
178
:-(
{selected, [{Min, Max}]} = execute_successfully(Pool, domain_events_minmax, []),
179
:-(
case Min of
180 null ->
181
:-(
{null, null};
182 _ ->
183
:-(
{mongoose_rdbms:result_to_integer(Min),
184 mongoose_rdbms:result_to_integer(Max)}
185 end.
186
187 count_events_between_ids(Min, Max) ->
188
:-(
Pool = get_db_pool(),
189
:-(
Selected = execute_successfully(Pool, domain_count_event_between, [Min, Max]),
190
:-(
mongoose_rdbms:selected_to_integer(Selected).
191
192 %% Min and Max are included in the result set
193 get_event_ids_between(Min, Max) ->
194
:-(
Pool = get_db_pool(),
195
:-(
{selected, Rows} = execute_successfully(Pool, domain_event_ids_between, [Min, Max]),
196
:-(
[mongoose_rdbms:result_to_integer(Id) || {Id} <- Rows].
197
198 delete_events_older_than(Id) ->
199
:-(
transaction(fun(Pool) ->
200
:-(
execute_successfully(Pool, domain_events_delete_older_than, [Id])
201 end).
202
203 insert_dummy_event(EventId) ->
204
:-(
insert_full_event(EventId, <<>>).
205
206 insert_full_event(EventId, Domain) ->
207
:-(
case mongoose_rdbms:db_type() of
208 mssql ->
209
:-(
insert_full_event_mssql(EventId, Domain);
210 _ ->
211
:-(
Pool = get_db_pool(),
212
:-(
execute_successfully(Pool, domain_insert_full_event, [EventId, Domain])
213 end.
214
215 insert_full_event_mssql(EventId, Domain) ->
216 %% MSSQL does not allow to specify ids,
217 %% that are supposed to be autoincremented, easily
218 %% https://docs.microsoft.com/pl-pl/sql/t-sql/statements/set-identity-insert-transact-sql
219
:-(
transaction(fun(Pool) ->
220 %% This query could not be a prepared query
221 %% You will get an error:
222 %% "No SQL-driver information available."
223 %% when trying to execute
224
:-(
mongoose_rdbms:sql_query(Pool, <<"SET IDENTITY_INSERT domain_events ON">>),
225
:-(
try
226
:-(
execute_successfully(Pool, domain_insert_full_event, [EventId, Domain])
227 after
228
:-(
mongoose_rdbms:sql_query(Pool, <<"SET IDENTITY_INSERT domain_events OFF">>)
229 end
230 end).
231
232 %% ----------------------------------------------------------------------------
233 %% For testing
234
235 erase_database() ->
236
:-(
Pool = get_db_pool(),
237
:-(
execute_successfully(Pool, domain_erase_events, []),
238
:-(
execute_successfully(Pool, domain_erase_settings, []).
239
240 insert_domain_settings_without_event(Domain, HostType) ->
241
:-(
Pool = get_db_pool(),
242
:-(
execute_successfully(Pool, domain_insert_settings, [Domain, HostType]).
243
244 %% ----------------------------------------------------------------------------
245
246 %% Inserts a new event with an autoincremented EventId.
247 %% Rows would not appear in the EventId order, RDBMS likes to rearrange them.
248 %% Example of rearranging:
249 %% Events with ids [1, 2, 3] could appear as:
250 %% 1. [1]
251 %% 2. [1, 3] - at this step record with EventId=2 is not visible yet.
252 %% 3. [1, 2, 3] - and finally everything is fine.
253 insert_domain_event(Pool, Domain) ->
254
:-(
execute_successfully(Pool, domain_insert_event, [Domain]).
255
256 insert_domain_settings(Pool, Domain, HostType) ->
257
:-(
execute_successfully(Pool, domain_insert_settings, [Domain, HostType]).
258
259 delete_domain_settings(Pool, Domain) ->
260
:-(
execute_successfully(Pool, domain_delete_settings, [Domain]).
261
262 set_enabled(Domain, Enabled) when is_boolean(Enabled) ->
263
:-(
transaction(fun(Pool) ->
264
:-(
case select_domain(Domain) of
265 {error, Reason} ->
266
:-(
{error, Reason};
267 {ok, #{enabled := En, host_type := HostType}} ->
268
:-(
case mongoose_domain_core:is_host_type_allowed(HostType) of
269 false ->
270
:-(
{error, unknown_host_type};
271 true when Enabled =:= En ->
272
:-(
ok;
273 true ->
274
:-(
update_domain_enabled(Pool, Domain, Enabled),
275
:-(
insert_domain_event(Pool, Domain),
276
:-(
ok
277 end
278 end
279 end).
280
281 update_domain_enabled(Pool, Domain, Enabled) ->
282
:-(
ExtEnabled = bool_to_ext(Pool, Enabled),
283
:-(
execute_successfully(Pool, domain_update_settings_enabled, [ExtEnabled, Domain]).
284
285 %% MySQL needs booleans as integers
286 bool_to_ext(Pool, Bool) when is_boolean(Bool) ->
287
:-(
case mongoose_rdbms:db_engine(Pool) of
288 pgsql ->
289
:-(
Bool;
290 _ ->
291
:-(
bool_to_int(Bool)
292 end.
293
294
:-(
bool_to_int(true) -> 1;
295
:-(
bool_to_int(false) -> 0.
296
297 row_to_map({HostType, Enabled}) ->
298
:-(
#{host_type => HostType, enabled => mongoose_rdbms:to_bool(Enabled)}.
299
300 get_db_pool() ->
301
:-(
proplists:get_value(db_pool, get_service_opts(), global).
302
303 get_service_opts() ->
304
:-(
mongoose_service:get_service_opts(service_domain_db).
305
306 transaction(F) ->
307
:-(
transaction(F, 3, []).
308
309 %% MSSQL especially likes to kill a connection deadlocked by tablock connections.
310 %% But that's fine, we could just restart
311 %% (there is no logic, that would suffer by a restart of a transaction).
312 transaction(_F, 0, Errors) ->
313
:-(
{error, {db_error, Errors}};
314 transaction(F, Retries, Errors) when Retries > 0 ->
315
:-(
Pool = get_db_pool(),
316
:-(
Result = rdbms_queries:sql_transaction(Pool, fun() -> F(Pool) end),
317
:-(
case Result of
318 {aborted, _} -> %% Restart any rolled back transaction
319
:-(
timer:sleep(100), %% Small break before retry
320
:-(
transaction(F, Retries - 1, [Result|Errors]);
321 _ ->
322
:-(
simple_result(Result)
323 end.
324
325
:-(
simple_result({atomic, Result}) -> Result;
326
:-(
simple_result(Other) -> {error, {db_error, Other}}.
Line Hits Source