./ct_report/coverage/rdbms_queries.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : rdbms_queries.erl
3 %%% Author : Mickael Remond <mremond@process-one.net>
4 %%% Purpose : RDBMS queries dependind on back-end
5 %%% Created : by Mickael Remond <mremond@process-one.net>
6 %%%
7 %%%
8 %%% ejabberd, Copyright (C) 2002-2011 ProcessOne
9 %%%
10 %%% This program is free software; you can redistribute it and/or
11 %%% modify it under the terms of the GNU General Public License as
12 %%% published by the Free Software Foundation; either version 2 of the
13 %%% License, or (at your option) any later version.
14 %%%
15 %%% This program is distributed in the hope that it will be useful,
16 %%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17 %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 %%% General Public License for more details.
19 %%%
20 %%% You should have received a copy of the GNU General Public License
21 %%% along with this program; if not, write to the Free Software
22 %%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 %%%
24 %%%----------------------------------------------------------------------
25
26 -module(rdbms_queries).
27 -author("mremond@process-one.net").
28
29 -export([get_db_type/0,
30 begin_trans/0,
31 get_db_specific_limits/0,
32 get_db_specific_limits/1,
33 get_db_specific_limits_binaries/0,
34 get_db_specific_limits_binaries/1,
35 get_db_specific_offset/2,
36 add_limit_arg/2,
37 limit_offset_sql/0,
38 limit_offset_args/2,
39 sql_transaction/2,
40 count_records_where/3,
41 create_bulk_insert_query/3]).
42
43 -export([join/2,
44 prepare_upsert/6,
45 prepare_upsert/7,
46 execute_upsert/5, execute_upsert/6,
47 request_upsert/5]).
48
49 -ignore_xref([
50 execute_upsert/6, count_records_where/3,
51 get_db_specific_limits/1, get_db_specific_offset/2, get_db_type/0
52 ]).
53
54 %% We have only two compile time options for db queries:
55 %%-define(generic, true).
56 %%-define(mssql, true).
57 -ifndef(mssql).
58 -undef(generic).
59 -endif.
60
61 -define(RDBMS_TYPE, (mongoose_rdbms:db_type())).
62
63 -include("mongoose.hrl").
64
65 -ifdef(gen_server_request_id).
66 -type request_id() :: gen_server:request_id().
67 -else.
68 -type request_id() :: term().
69 -endif.
70
71 %
72 %% -----------------
73 %% Common functions
74
75 join([], _Sep) ->
76
:-(
[];
77 join([H|T], Sep) ->
78 13134 [H, [[Sep, X] || X <- T]].
79
80 %% -----------------
81 %% Generic queries
82
83 get_db_type() ->
84
:-(
?RDBMS_TYPE.
85
86 -spec execute_upsert(HostType :: mongooseim:host_type_or_global(),
87 Name :: atom(),
88 InsertParams :: [any()],
89 UpdateParams :: [any()],
90 UniqueKeyValues :: [any()]) -> mongoose_rdbms:query_result().
91 execute_upsert(HostType, Name, InsertParams, UpdateParams, UniqueKeyValues) ->
92 2304 execute_upsert(HostType, default, Name, InsertParams, UpdateParams, UniqueKeyValues).
93
94 -spec execute_upsert(HostType :: mongooseim:host_type_or_global(),
95 PoolTag :: mongoose_wpool:tag(),
96 Name :: atom(),
97 InsertParams :: [any()],
98 UpdateParams :: [any()],
99 UniqueKeyValues :: [any()]) -> mongoose_rdbms:query_result().
100 execute_upsert(HostType, PoolTag, Name, InsertParams, UpdateParams, UniqueKeyValues) ->
101 2304 case {mongoose_rdbms:db_engine(HostType), mongoose_rdbms:db_type()} of
102 {mysql, _} ->
103
:-(
mongoose_rdbms:execute(HostType, PoolTag, Name, InsertParams ++ UpdateParams);
104 {pgsql, _} ->
105
:-(
mongoose_rdbms:execute(HostType, PoolTag, Name, InsertParams ++ UpdateParams);
106 {odbc, mssql} ->
107 2304 mongoose_rdbms:execute(HostType, PoolTag, Name, UniqueKeyValues ++ InsertParams ++ UpdateParams);
108
:-(
NotSupported -> erlang:error({rdbms_not_supported, NotSupported})
109 end.
110
111 -spec request_upsert(HostType :: mongooseim:host_type_or_global(),
112 Name :: atom(),
113 InsertParams :: [any()],
114 UpdateParams :: [any()],
115 UniqueKeyValues :: [any()]) -> request_id().
116 request_upsert(HostType, Name, InsertParams, UpdateParams, UniqueKeyValues) ->
117 476 case {mongoose_rdbms:db_engine(HostType), mongoose_rdbms:db_type()} of
118 {mysql, _} ->
119
:-(
mongoose_rdbms:execute_request(HostType, Name, InsertParams ++ UpdateParams);
120 {pgsql, _} ->
121
:-(
mongoose_rdbms:execute_request(HostType, Name, InsertParams ++ UpdateParams);
122 {odbc, mssql} ->
123 476 mongoose_rdbms:execute_request(HostType, Name, UniqueKeyValues ++ InsertParams ++ UpdateParams);
124
:-(
NotSupported -> erlang:error({rdbms_not_supported, NotSupported})
125 end.
126
127 %% @doc
128 %% This functions prepares query for inserting a row or updating it if already exists
129 %% Updates can be either fields or literal expressions like "column = tab.column + 1"
130 -spec prepare_upsert(HostType :: mongooseim:host_type_or_global(),
131 QueryName :: mongoose_rdbms:query_name(),
132 TableName :: atom(),
133 InsertFields :: [binary()],
134 Updates :: [binary() | {assignment | expression, binary(), binary()}],
135 UniqueKeyFields :: [binary()]) ->
136 {ok, QueryName :: mongoose_rdbms:query_name()} | {error, already_exists}.
137 prepare_upsert(HostType, Name, Table, InsertFields, Updates, UniqueKeyFields) ->
138 1916 prepare_upsert(HostType, Name, Table, InsertFields, Updates, UniqueKeyFields, none).
139
140 -spec prepare_upsert(HostType :: mongooseim:host_type_or_global(),
141 QueryName :: mongoose_rdbms:query_name(),
142 TableName :: atom(),
143 InsertFields :: [ColumnName :: binary()],
144 Updates :: [binary() | {assignment | expression, binary(), binary()}],
145 UniqueKeyFields :: [binary()],
146 IncrementalField :: none | binary()) ->
147 {ok, QueryName :: mongoose_rdbms:query_name()} | {error, already_exists}.
148 prepare_upsert(HostType, Name, Table, InsertFields, Updates, UniqueKeyFields, IncrementalField) ->
149 2033 SQL = upsert_query(HostType, Table, InsertFields, Updates, UniqueKeyFields, IncrementalField),
150 2033 Query = iolist_to_binary(SQL),
151 2033 ?LOG_DEBUG(#{what => rdbms_upsert_query, name => Name, query => Query}),
152 2033 Fields = prepared_upsert_fields(InsertFields, Updates, UniqueKeyFields),
153 2033 mongoose_rdbms:prepare(Name, Table, Fields, Query).
154
155 prepared_upsert_fields(InsertFields, Updates, UniqueKeyFields) ->
156 2033 UpdateFields = lists:filtermap(fun get_field_name/1, Updates),
157 2033 case mongoose_rdbms:db_type() of
158 mssql ->
159 2033 UniqueKeyFields ++ InsertFields ++ UpdateFields;
160
:-(
_ -> InsertFields ++ UpdateFields
161 end.
162
163 upsert_query(HostType, Table, InsertFields, Updates, UniqueKeyFields, IncrementalField) ->
164 2033 case {mongoose_rdbms:db_engine(HostType), mongoose_rdbms:db_type()} of
165 {mysql, _} ->
166
:-(
upsert_mysql_query(Table, InsertFields, Updates, UniqueKeyFields, IncrementalField);
167 {pgsql, _} ->
168
:-(
upsert_pgsql_query(Table, InsertFields, Updates, UniqueKeyFields, IncrementalField);
169 {odbc, mssql} ->
170 2033 upsert_mssql_query(Table, InsertFields, Updates, UniqueKeyFields);
171
:-(
NotSupported -> erlang:error({rdbms_not_supported, NotSupported})
172 end.
173
174 mysql_and_pgsql_insert(Table, Columns) ->
175
:-(
JoinedFields = join(Columns, <<", ">>),
176
:-(
Placeholders = lists:duplicate(length(Columns), $?),
177
:-(
["INSERT INTO ", atom_to_binary(Table, utf8), " (",
178 JoinedFields,
179 ") VALUES (",
180 join(Placeholders, ", "),
181 ")"].
182
183 upsert_mysql_query(Table, InsertFields, Updates, [Key | _], IncrementalField) ->
184
:-(
Insert = mysql_and_pgsql_insert(Table, InsertFields),
185
:-(
OnConflict = mysql_on_conflict(Table, Updates, Key, IncrementalField),
186
:-(
[Insert, OnConflict].
187
188 upsert_pgsql_query(Table, InsertFields, Updates, UniqueKeyFields, IncrementalField) ->
189
:-(
Insert = mysql_and_pgsql_insert(Table, InsertFields),
190
:-(
OnConflict = pgsql_on_conflict(Updates, UniqueKeyFields),
191
:-(
WhereIncrements = pgsql_ensure_increments(Table, IncrementalField),
192
:-(
[Insert, OnConflict, WhereIncrements].
193
194 mysql_on_conflict(_Table, [], Key, _) ->
195 %% Update field to itself (no-op), there is no 'DO NOTHING' in MySQL
196
:-(
[" ON DUPLICATE KEY UPDATE ", Key, " = ", Key];
197 mysql_on_conflict(_Table, UpdateFields, _, none) ->
198
:-(
[" ON DUPLICATE KEY UPDATE ",
199 update_fields_on_conflict(UpdateFields)];
200 mysql_on_conflict(Table, UpdateFields, _, IncrementalField) ->
201
:-(
TableName = atom_to_list(Table),
202
:-(
FieldsWithPlaceHolders = [mysql_fields_with_placeholders(TableName, Update, IncrementalField)
203
:-(
|| Update <- UpdateFields],
204
:-(
IncrUpdates = join(FieldsWithPlaceHolders, ", "),
205
:-(
[" AS alias ON DUPLICATE KEY UPDATE ", IncrUpdates].
206
207 mysql_fields_with_placeholders(TableName, UpdateField, IncrementalField) ->
208
:-(
Alternatives = case UpdateField of
209 {Op, Column, Expression} when Op =:= assignment; Op =:= expression ->
210
:-(
[Expression, ", ", TableName, ".", Column, ")"];
211 Column ->
212
:-(
["? , ", TableName, ".", Column, ")"]
213 end,
214
:-(
[ Column, " = IF(", TableName, ".", IncrementalField, " < alias.", IncrementalField, ", "
215 | Alternatives].
216
217 pgsql_on_conflict([], UniqueKeyFields) ->
218
:-(
JoinedKeys = join(UniqueKeyFields, ", "),
219
:-(
[" ON CONFLICT (", JoinedKeys, ") DO NOTHING"];
220 pgsql_on_conflict(UpdateFields, UniqueKeyFields) ->
221
:-(
JoinedKeys = join(UniqueKeyFields, ", "),
222
:-(
[" ON CONFLICT (", JoinedKeys, ")"
223 " DO UPDATE SET ",
224 update_fields_on_conflict(UpdateFields)].
225
226 update_fields_on_conflict(Updates) ->
227 2026 FieldsWithPlaceHolders = [get_field_expression(Update) || Update <- Updates],
228 2026 join(FieldsWithPlaceHolders, ", ").
229
230 pgsql_ensure_increments(_Table, none) ->
231
:-(
[];
232 pgsql_ensure_increments(Table, IncrementalField) ->
233
:-(
TableName = atom_to_list(Table),
234
:-(
[" WHERE ", TableName, ".", IncrementalField, " < EXCLUDED.", IncrementalField].
235
236 upsert_mssql_query(Table, InsertFields, Updates, UniqueKeyFields) ->
237 2033 UniqueKeysInSelect = [[" ? AS ", Key] || Key <- UniqueKeyFields],
238 2033 BinTab = atom_to_binary(Table, utf8),
239 2033 UniqueConstraint = [[BinTab, ".", Key, " = source.", Key] || Key <- UniqueKeyFields],
240 2033 JoinedInsertFields = join(InsertFields, ", "),
241 2033 Placeholders = lists:duplicate(length(InsertFields), $?),
242 2033 ["MERGE INTO ", BinTab, " WITH (SERIALIZABLE)"
243 " USING (SELECT ", join(UniqueKeysInSelect, ", "), ")"
244 " AS source (", join(UniqueKeyFields, ", "), ")"
245 " ON (", join(UniqueConstraint, " AND "), ")"
246 " WHEN NOT MATCHED THEN INSERT"
247 " (", JoinedInsertFields, ")"
248 " VALUES (", join(Placeholders, ", "), ")" | mssql_on_conflict(Updates)].
249
250 7 mssql_on_conflict([]) -> ";";
251 mssql_on_conflict(Updates) ->
252 2026 [" WHEN MATCHED THEN UPDATE SET ", update_fields_on_conflict(Updates), ";"].
253
254 get_field_expression({Op, ColumnName, Expr}) when Op =:= assignment; Op =:= expression ->
255 162 [ColumnName, " = ", Expr];
256 get_field_expression(Field) when is_binary(Field) ->
257 13312 [Field, " = ?"].
258
259 get_field_name({assignment, Field, _}) when is_binary(Field) ->
260
:-(
false;
261 get_field_name({expression, Field, _}) when is_binary(Field) ->
262 162 {true, Field};
263 get_field_name(Field) when is_binary(Field) ->
264 13312 true.
265
266 %% F can be either a fun or a list of queries
267 %% TODO: We should probably move the list of queries transaction
268 %% wrapper from the mongoose_rdbms module to this one (rdbms_queries)
269 sql_transaction(LServer, F) ->
270 513 mongoose_rdbms:sql_transaction(LServer, F).
271
272 begin_trans() ->
273 19398 begin_trans(?RDBMS_TYPE).
274
275 begin_trans(mssql) ->
276 19398 rdbms_queries_mssql:begin_trans();
277 begin_trans(_) ->
278
:-(
[<<"BEGIN;">>].
279
280 %% Count number of records in a table given a where clause
281 count_records_where(LServer, Table, WhereClause) ->
282
:-(
mongoose_rdbms:sql_query(
283 LServer,
284 [<<"select count(*) from ">>, Table, " ", WhereClause, ";"]).
285
286 -spec create_bulk_insert_query(Table :: iodata() | atom(), Fields :: [iodata() | atom()],
287 RowsNum :: pos_integer()) ->
288 {iodata(), [binary()]}.
289 create_bulk_insert_query(Table, Fields, RowsNum) when is_atom(Table) ->
290 294 create_bulk_insert_query(atom_to_binary(Table, utf8), Fields, RowsNum);
291 create_bulk_insert_query(Table, [Field | _] = Fields, RowsNum) when is_atom(Field) ->
292 294 create_bulk_insert_query(Table, [atom_to_binary(F, utf8) || F <- Fields], RowsNum);
293 create_bulk_insert_query(Table, Fields, RowsNum) when RowsNum > 0 ->
294 294 JoinedFields = join(Fields, <<", ">>),
295 294 Placeholders = lists:duplicate(length(Fields), <<"?">>),
296 294 PlaceholderSet = [<<"(">>, join(Placeholders, <<", ">>), <<")">>],
297 294 PlaceholderSets = lists:duplicate(RowsNum, PlaceholderSet),
298 294 JoinedPlaceholderSets = join(PlaceholderSets, <<", ">>),
299 294 Sql = [<<"INSERT INTO ">>, Table, <<" (">>, JoinedFields, <<") "
300 "VALUES ">>, JoinedPlaceholderSets, <<";">>],
301 294 Fields2 = lists:append(lists:duplicate(RowsNum, Fields)),
302 294 {Sql, Fields2}.
303
304 get_db_specific_limits() ->
305 717 do_get_db_specific_limits(?RDBMS_TYPE, "?", true).
306
307 get_db_specific_limits_binaries() ->
308 675 {LimitSQL, LimitMSSQL} = get_db_specific_limits(),
309 675 {list_to_binary(LimitSQL), list_to_binary(LimitMSSQL)}.
310
311 -spec get_db_specific_limits(integer())
312 -> {SQL :: nonempty_string(), []} | {[], MSSQL::nonempty_string()}.
313 get_db_specific_limits(Limit) ->
314 148 LimitStr = integer_to_list(Limit),
315 148 do_get_db_specific_limits(?RDBMS_TYPE, LimitStr, false).
316
317 -spec get_db_specific_offset(integer(), integer()) -> iolist().
318 get_db_specific_offset(Offset, Limit) ->
319
:-(
do_get_db_specific_offset(?RDBMS_TYPE, integer_to_list(Offset), integer_to_list(Limit)).
320
321
322 %% Arguments:
323 %% - Type (atom) - database type
324 %% - LimitStr (string) - a field value
325 %% - Wrap (boolean) - add parentheses around a field for MSSQL
326 do_get_db_specific_limits(mssql, LimitStr, _Wrap = false) ->
327 148 {"", "TOP " ++ LimitStr};
328 do_get_db_specific_limits(mssql, LimitStr, _Wrap = true) ->
329 717 {"", "TOP (" ++ LimitStr ++ ")"};
330 do_get_db_specific_limits(_, LimitStr, _Wrap) ->
331
:-(
{"LIMIT " ++ LimitStr, ""}.
332
333 do_get_db_specific_offset(mssql, Offset, Limit) ->
334
:-(
[" OFFSET ", Offset, " ROWS"
335 " FETCH NEXT ", Limit, " ROWS ONLY"];
336 do_get_db_specific_offset(_, Offset, _Limit) ->
337
:-(
[" OFFSET ", Offset].
338
339 add_limit_arg(Limit, Args) ->
340 2462 add_limit_arg(?RDBMS_TYPE, Limit, Args).
341
342 add_limit_arg(mssql, Limit, Args) ->
343 2462 [Limit|Args];
344 add_limit_arg(_, Limit, Args) ->
345
:-(
Args ++ [Limit].
346
347 get_db_specific_limits_binaries(Limit) ->
348 148 {LimitSQL, LimitMSSQL} = get_db_specific_limits(Limit),
349 148 {list_to_binary(LimitSQL), list_to_binary(LimitMSSQL)}.
350
351 limit_offset_sql() ->
352 268 limit_offset_sql(?RDBMS_TYPE).
353
354 limit_offset_sql(mssql) ->
355 268 <<" OFFSET (?) ROWS FETCH NEXT (?) ROWS ONLY">>;
356 limit_offset_sql(_) ->
357
:-(
<<" LIMIT ? OFFSET ?">>.
358
359 limit_offset_args(Limit, Offset) ->
360 27 limit_offset_args(?RDBMS_TYPE, Limit, Offset).
361
362 27 limit_offset_args(mssql, Limit, Offset) -> [Offset, Limit];
363
:-(
limit_offset_args(_, Limit, Offset) -> [Limit, Offset].
Line Hits Source