1 |
|
%% MySQL/OTP – MySQL client library for Erlang/OTP |
2 |
|
%% Copyright (C) 2014-2015, 2018 Viktor Söderqvist, |
3 |
|
%% 2016 Johan Lövdahl |
4 |
|
%% 2017 Piotr Nosek, Michal Slaski |
5 |
|
%% |
6 |
|
%% This file is part of MySQL/OTP. |
7 |
|
%% |
8 |
|
%% MySQL/OTP is free software: you can redistribute it and/or modify it under |
9 |
|
%% the terms of the GNU Lesser General Public License as published by the Free |
10 |
|
%% Software Foundation, either version 3 of the License, or (at your option) |
11 |
|
%% any later version. |
12 |
|
%% |
13 |
|
%% This program is distributed in the hope that it will be useful, but WITHOUT |
14 |
|
%% ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
15 |
|
%% FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
16 |
|
%% more details. |
17 |
|
%% |
18 |
|
%% You should have received a copy of the GNU Lesser General Public License |
19 |
|
%% along with this program. If not, see <https://www.gnu.org/licenses/>. |
20 |
|
|
21 |
|
%% @doc MySQL client. |
22 |
|
%% |
23 |
|
%% The `connection()' type is a gen_server reference as described in the |
24 |
|
%% documentation for `gen_server:call/2,3', e.g. the pid or the name if the |
25 |
|
%% gen_server is locally registered. |
26 |
|
-module(mysql). |
27 |
|
|
28 |
|
-export([start_link/1, stop/1, stop/2, |
29 |
|
is_connected/1, |
30 |
|
query/2, query/3, query/4, query/5, |
31 |
|
execute/3, execute/4, execute/5, |
32 |
|
prepare/2, prepare/3, unprepare/2, |
33 |
|
warning_count/1, affected_rows/1, autocommit/1, insert_id/1, |
34 |
|
encode/2, in_transaction/1, |
35 |
|
transaction/2, transaction/3, transaction/4, |
36 |
|
change_user/3, change_user/4, reset_connection/1]). |
37 |
|
|
38 |
|
-export_type([option/0, connection/0, query/0, statement_name/0, |
39 |
|
statement_ref/0, query_param/0, query_filtermap_fun/0, |
40 |
|
query_result/0, transaction_result/1, server_reason/0]). |
41 |
|
|
42 |
|
%% A connection is a ServerRef as in gen_server:call/2,3. |
43 |
|
-type connection() :: Name :: atom() | |
44 |
|
{Name :: atom(), Node :: atom()} | |
45 |
|
{global, GlobalName :: term()} | |
46 |
|
{via, Module :: atom(), ViaName :: term()} | |
47 |
|
pid(). |
48 |
|
|
49 |
|
%% MySQL error with the codes and message returned from the server. |
50 |
|
-type server_reason() :: {Code :: integer(), SQLState :: binary() | undefined, |
51 |
|
Message :: binary()}. |
52 |
|
|
53 |
|
-type column_name() :: binary(). |
54 |
|
-type query() :: iodata(). |
55 |
|
-type row() :: [term()]. |
56 |
|
|
57 |
|
-type query_param() :: term(). |
58 |
|
|
59 |
|
-type query_filtermap_fun() :: fun((row()) -> query_filtermap_res()) |
60 |
|
| fun(([column_name()], row()) -> query_filtermap_res()). |
61 |
|
-type query_filtermap_res() :: boolean() |
62 |
|
| {true, term()}. |
63 |
|
|
64 |
|
-type statement_id() :: integer(). |
65 |
|
-type statement_name() :: atom(). |
66 |
|
-type statement_ref() :: statement_id() | statement_name(). |
67 |
|
|
68 |
|
-type query_result() :: ok |
69 |
|
| {ok, [column_name()], [row()]} |
70 |
|
| {ok, [{[column_name()], [row()]}, ...]} |
71 |
|
| {error, server_reason()}. |
72 |
|
|
73 |
|
-type transaction_result(Result) :: {atomic, Result} | {aborted, Reason :: term()}. |
74 |
|
|
75 |
|
-type server_name() :: {local, Name :: atom()} |
76 |
|
| {global, GlobalName :: term()} |
77 |
|
| {via, Via :: module(), ViaName :: term()}. |
78 |
|
|
79 |
|
-type option() :: {name, ServerName :: server_name()} |
80 |
|
| {host, inet:socket_address() | inet:hostname()} | {port, integer()} |
81 |
|
| {user, iodata()} | {password, iodata()} |
82 |
|
| {database, iodata()} |
83 |
|
| {connect_mode, synchronous | asynchronous | lazy} |
84 |
|
| {connect_timeout, timeout()} |
85 |
|
| {allowed_local_paths, [binary()]} |
86 |
|
| {log_warnings, boolean()} |
87 |
|
| {log_slow_queries, boolean()} |
88 |
|
| {keep_alive, boolean() | timeout()} |
89 |
|
| {prepare, [{StatementName :: statement_name(), Statement :: query()}]} |
90 |
|
| {queries, [query()]} |
91 |
|
| {query_timeout, timeout()} |
92 |
|
| {found_rows, boolean()} |
93 |
|
| {query_cache_time, non_neg_integer()} |
94 |
|
| {tcp_options, [gen_tcp:connect_option()]} |
95 |
|
| {ssl, term()}. |
96 |
|
|
97 |
|
-include("exception.hrl"). |
98 |
|
|
99 |
|
%% @doc Starts a connection gen_server process and connects to a database. To |
100 |
|
%% disconnect use `mysql:stop/1,2'. |
101 |
|
%% |
102 |
|
%% Options: |
103 |
|
%% |
104 |
|
%% <dl> |
105 |
|
%% <dt>`{name, ServerName}'</dt> |
106 |
|
%% <dd>If a name is provided, the gen_server will be registered with this |
107 |
|
%% name. For details see the documentation for the first argument of |
108 |
|
%% gen_server:start_link/4.</dd> |
109 |
|
%% <dt>`{host, Host}'</dt> |
110 |
|
%% <dd>Hostname of the MySQL database. Since OTP version 19, it is also |
111 |
|
%% possible to specify a local (Unix) Socket by specifying |
112 |
|
%% `{local, SocketFile}'. Default `"localhost"'.</dd> |
113 |
|
%% <dt>`{port, Port}'</dt> |
114 |
|
%% <dd>Port; default 3306 for non-local or 0 for local (Unix) sockets.</dd> |
115 |
|
%% <dt>`{user, User}'</dt> |
116 |
|
%% <dd>Username.</dd> |
117 |
|
%% <dt>`{password, Password}'</dt> |
118 |
|
%% <dd>Password.</dd> |
119 |
|
%% <dt>`{database, Database}'</dt> |
120 |
|
%% <dd>The name of the database AKA schema to use. This can be changed later |
121 |
|
%% using the query `USE <database>'.</dd> |
122 |
|
%% <dt>`{connect_mode, synchronous | asynchronous | lazy}'</dt> |
123 |
|
%% <dd>Specifies how and when the connection process should establish a connection |
124 |
|
%% to the MySQL server. |
125 |
|
%% <dl> |
126 |
|
%% <dt>`synchronous' (default)</dt> |
127 |
|
%% <dd>The connection will be established as part of the connection process' |
128 |
|
%% start routine, ie the returned connection process will already be |
129 |
|
%% connected and ready to use, and any on-connect prepares and queries |
130 |
|
%% will have been executed.</dd> |
131 |
|
%% <dt>`asynchronous'</dt> |
132 |
|
%% <dd>The connection process will be started and returned to the caller |
133 |
|
%% before really establishing a connection to the server and executing |
134 |
|
%% the on-connect prepares and executes. This will instead be done |
135 |
|
%% immediately afterwards as the first action of the connection |
136 |
|
%% process.</dd> |
137 |
|
%% <dt>`lazy'</dt> |
138 |
|
%% <dd>Similar to `asynchronous' mode, but an actual connection will be |
139 |
|
%% established and the on-connect prepares and queries executed only |
140 |
|
%% when a connection is needed for the first time, eg. to execute a |
141 |
|
%% query.</dd> |
142 |
|
%% </dl> |
143 |
|
%% </dd> |
144 |
|
%% <dt>`{connect_timeout, Timeout}'</dt> |
145 |
|
%% <dd>The maximum time to spend for start_link/1.</dd> |
146 |
|
%% <dt>`{allowed_local_paths, [binary()]}'</dt> |
147 |
|
%% <dd>This option allows you to specify a list of directories or individual |
148 |
|
%% files on the client machine which the server may request, for example |
149 |
|
%% when executing a `LOAD DATA LOCAL INFILE' query. Only absolute paths |
150 |
|
%% without relative components such as `..' and `.' are allowed. |
151 |
|
%% The default is an empty list, meaning the client will not send any |
152 |
|
%% local files to the server.</dd> |
153 |
|
%% <dt>`{log_warnings, boolean()}'</dt> |
154 |
|
%% <dd>Whether to fetch warnings and log them using error_logger; default |
155 |
|
%% true.</dd> |
156 |
|
%% <dt>`{log_slow_queries, boolean()}'</dt> |
157 |
|
%% <dd>Whether to log slow queries using error_logger; default false. Queries |
158 |
|
%% are flagged as slow by the server if their execution time exceeds the |
159 |
|
%% value in the `long_query_time' variable.</dd> |
160 |
|
%% <dt>`{keep_alive, boolean() | timeout()}'</dt> |
161 |
|
%% <dd>Send ping when unused for a certain time. Possible values are `true', |
162 |
|
%% `false' and `integer() > 0' for an explicit interval in milliseconds. |
163 |
|
%% The default is `false'. For `true' a default ping timeout is used. |
164 |
|
%% </dd> |
165 |
|
%% <dt>`{prepare, NamedStatements}'</dt> |
166 |
|
%% <dd>Named prepared statements to be created as soon as the connection is |
167 |
|
%% ready.</dd> |
168 |
|
%% <dt>`{queries, Queries}'</dt> |
169 |
|
%% <dd>Queries to be executed as soon as the connection is ready. Any results |
170 |
|
%% are discarded. Typically, this is used for setting time zone and other |
171 |
|
%% session variables.</dd> |
172 |
|
%% <dt>`{query_timeout, Timeout}'</dt> |
173 |
|
%% <dd>The default time to wait for a response when executing a query or a |
174 |
|
%% prepared statement. This can be given per query using `query/3,4' and |
175 |
|
%% `execute/4'. The default is `infinity'.</dd> |
176 |
|
%% <dt>`{found_rows, boolean()}'</dt> |
177 |
|
%% <dd>If set to true, the connection will be established with |
178 |
|
%% CLIENT_FOUND_ROWS capability. affected_rows/1 will now return the |
179 |
|
%% number of found rows, not the number of rows changed by the |
180 |
|
%% query.</dd> |
181 |
|
%% <dt>`{query_cache_time, Timeout}'</dt> |
182 |
|
%% <dd>The minimum number of milliseconds to cache prepared statements used |
183 |
|
%% for parametrized queries with query/3.</dd> |
184 |
|
%% <dt>`{tcp_options, Options}'</dt> |
185 |
|
%% <dd>Additional options for `gen_tcp:connect/3'. You may want to set |
186 |
|
%% `{recbuf, Size}' and `{sndbuf, Size}' if you send or receive more than |
187 |
|
%% the default (typically 8K) per query.</dd> |
188 |
|
%% <dt>`{ssl, Options}'</dt> |
189 |
|
%% <dd>Additional options for `ssl:connect/3'.<br /> |
190 |
|
%% The `verify' option, if not given explicitly, defaults to |
191 |
|
%% `verify_peer'.<br /> |
192 |
|
%% The `server_name_indication' option, if omitted, defaults to the value |
193 |
|
%% of the `host' option if it is a hostname string, otherwise no default |
194 |
|
%% value is set.</dd> |
195 |
|
%% </dl> |
196 |
|
-spec start_link(Options :: [option()]) -> {ok, pid()} | ignore | {error, term()}. |
197 |
|
start_link(Options) -> |
198 |
40 |
case proplists:get_value(name, Options) of |
199 |
|
undefined -> |
200 |
38 |
gen_server:start_link(mysql_conn, Options, []); |
201 |
|
ServerName -> |
202 |
2 |
gen_server:start_link(ServerName, mysql_conn, Options, []) |
203 |
|
end. |
204 |
|
|
205 |
|
%% @see stop/2. |
206 |
|
-spec stop(Conn) -> ok |
207 |
|
when Conn :: connection(). |
208 |
|
stop(Conn) -> |
209 |
30 |
stop(Conn, infinity). |
210 |
|
|
211 |
|
%% @doc Stops a connection process and closes the connection. The |
212 |
|
%% process calling `stop' will be blocked until the connection |
213 |
|
%% process stops or the given timeout expires. |
214 |
|
%% |
215 |
|
%% If the connection is not stopped within the given timeout, |
216 |
|
%% an exit exception is raised with reason `timeout'. |
217 |
|
%% |
218 |
|
%% If the connection process exits with any other reason than `normal', |
219 |
|
%% an exit exception is raised with that reason. |
220 |
|
-spec stop(Conn, Timeout) -> ok |
221 |
|
when Conn :: connection(), |
222 |
|
Timeout :: timeout(). |
223 |
|
stop(Conn, Timeout) -> |
224 |
31 |
case erlang:function_exported(gen_server, stop, 3) of |
225 |
31 |
true -> gen_server:stop(Conn, normal, Timeout); %% OTP >= 18 |
226 |
:-( |
false -> backported_gen_server_stop(Conn, normal, Timeout) %% OTP < 18 |
227 |
|
end. |
228 |
|
|
229 |
|
-spec backported_gen_server_stop(Conn, Reason, Timeout) -> ok |
230 |
|
when Conn :: connection(), |
231 |
|
Reason :: term(), |
232 |
|
Timeout :: timeout(). |
233 |
|
backported_gen_server_stop(Conn, Reason, Timeout) -> |
234 |
:-( |
Monitor=monitor(process, Conn), |
235 |
:-( |
exit(Conn, Reason), |
236 |
:-( |
receive |
237 |
|
{'DOWN', Monitor, process, Conn, Reason} -> |
238 |
:-( |
ok; |
239 |
|
{'DOWN', Monitor, process, Conn, UnexpectedReason} -> |
240 |
:-( |
exit(UnexpectedReason) |
241 |
|
after Timeout -> |
242 |
:-( |
exit(Conn, kill), |
243 |
:-( |
receive |
244 |
|
{'DOWN', Monitor, process, Conn, killed} -> |
245 |
:-( |
exit(timeout) |
246 |
|
end |
247 |
|
end. |
248 |
|
|
249 |
|
%% @private |
250 |
|
-spec is_connected(Conn) -> boolean() |
251 |
|
when Conn :: connection(). |
252 |
|
is_connected(Conn) -> |
253 |
4 |
gen_server:call(Conn, is_connected). |
254 |
|
|
255 |
|
%% @doc Executes a plain query. |
256 |
|
%% @see query/5. |
257 |
|
-spec query(Conn, Query) -> Result |
258 |
|
when Conn :: connection(), |
259 |
|
Query :: iodata(), |
260 |
|
Result :: query_result(). |
261 |
|
query(Conn, Query) -> |
262 |
446 |
query_helper(Conn, Query, no_params, no_filtermap_fun, default_timeout). |
263 |
|
|
264 |
|
%% @doc Executes a query. |
265 |
|
%% @see query/5. |
266 |
|
-spec query(Conn, Query, Params | FilterMap | Timeout) -> Result |
267 |
|
when Conn :: connection(), |
268 |
|
Query :: query(), |
269 |
|
Timeout :: timeout(), |
270 |
|
Params :: [query_param()], |
271 |
|
FilterMap :: query_filtermap_fun(), |
272 |
|
Result :: query_result(). |
273 |
|
query(Conn, Query, Params) when Params == no_params; |
274 |
|
is_list(Params) -> |
275 |
11 |
query_helper(Conn, Query, Params, no_filtermap_fun, default_timeout); |
276 |
|
query(Conn, Query, FilterMap) when FilterMap == no_filtermap_fun; |
277 |
|
is_function(FilterMap, 1); |
278 |
|
is_function(FilterMap, 2) -> |
279 |
2 |
query_helper(Conn, Query, no_params, FilterMap, default_timeout); |
280 |
|
query(Conn, Query, Timeout) when Timeout == default_timeout; |
281 |
|
is_integer(Timeout); |
282 |
|
Timeout == infinity -> |
283 |
2 |
query_helper(Conn, Query, no_params, no_filtermap_fun, Timeout). |
284 |
|
|
285 |
|
%% @doc Executes a query. |
286 |
|
%% @see query/5. |
287 |
|
-spec query(Conn, Query, Params, Timeout) -> Result |
288 |
|
when Conn :: connection(), |
289 |
|
Query :: query(), |
290 |
|
Timeout :: timeout(), |
291 |
|
Params :: [query_param()], |
292 |
|
Result :: query_result(); |
293 |
|
(Conn, Query, FilterMap, Timeout) -> Result |
294 |
|
when Conn :: connection(), |
295 |
|
Query :: query(), |
296 |
|
Timeout :: timeout(), |
297 |
|
FilterMap :: query_filtermap_fun(), |
298 |
|
Result :: query_result(); |
299 |
|
(Conn, Query, Params, FilterMap) -> Result |
300 |
|
when Conn :: connection(), |
301 |
|
Query :: query(), |
302 |
|
Params :: [query_param()], |
303 |
|
FilterMap :: query_filtermap_fun(), |
304 |
|
Result :: query_result(). |
305 |
|
query(Conn, Query, Params, Timeout) when (Params == no_params orelse |
306 |
|
is_list(Params)) andalso |
307 |
|
(Timeout == default_timeout orelse |
308 |
|
is_integer(Timeout) orelse |
309 |
|
Timeout == infinity) -> |
310 |
1 |
query_helper(Conn, Query, Params, no_filtermap_fun, Timeout); |
311 |
|
query(Conn, Query, FilterMap, Timeout) when (FilterMap == no_filtermap_fun orelse |
312 |
|
is_function(FilterMap, 1) orelse |
313 |
|
is_function(FilterMap, 2)) andalso |
314 |
|
(Timeout == default_timeout orelse |
315 |
|
is_integer(Timeout) orelse |
316 |
|
Timeout=:=infinity) -> |
317 |
:-( |
query_helper(Conn, Query, no_params, FilterMap, Timeout); |
318 |
|
query(Conn, Query, Params, FilterMap) when (Params == no_params orelse |
319 |
|
is_list(Params)) andalso |
320 |
|
(FilterMap == no_filtermap_fun orelse |
321 |
|
is_function(FilterMap, 1) orelse |
322 |
|
is_function(FilterMap, 2)) -> |
323 |
2 |
query_helper(Conn, Query, Params, FilterMap, default_timeout). |
324 |
|
|
325 |
|
%% @doc Executes a query. |
326 |
|
%% |
327 |
|
%% === Parameters === |
328 |
|
%% |
329 |
|
%% `Conn' is identifying a connection process started using |
330 |
|
%% `mysql:start_link/1'. |
331 |
|
%% |
332 |
|
%% `Query' is the query to execute, as a binary or a list. |
333 |
|
%% |
334 |
|
%% `Params', `FilterMap' and `Timeout' are optional. |
335 |
|
%% |
336 |
|
%% If `Params' (a list) is specified, the query is performed as a prepared |
337 |
|
%% statement. A prepared statement is created, executed and then cached for a |
338 |
|
%% certain time (specified using the option `{query_cache_time, Milliseconds}' |
339 |
|
%% to `start_link/1'). If the same query is executed again during this time, |
340 |
|
%% it does not need to be prepared again. If `Params' is omitted, the query |
341 |
|
%% is executed as a plain query. To force a query without parameters to be |
342 |
|
%% executed as a prepared statement, an empty list can be used for `Params'. |
343 |
|
%% |
344 |
|
%% If `FilterMap' (a fun) is specified, the function is applied to each row to |
345 |
|
%% filter or perform other actions on the rows, in a way similar to how |
346 |
|
%% `lists:filtermap/2' works, before the result is returned to the caller. See |
347 |
|
%% below for details. |
348 |
|
%% |
349 |
|
%% `Timeout' specifies the time to wait for a response from the database. If |
350 |
|
%% omitted, the timeout given in `start_link/1' is used. |
351 |
|
%% |
352 |
|
%% === Return value === |
353 |
|
%% |
354 |
|
%% Results are returned in the form `{ok, ColumnNames, Rows}' if there is one |
355 |
|
%% result set. If there are more than one result sets, they are returned in the |
356 |
|
%% form `{ok, [{ColumnNames, Rows}, ...]}'. This is typically the case if |
357 |
|
%% multiple queries are specified at the same time, separated by semicolons. |
358 |
|
%% |
359 |
|
%% For queries that don't return any rows (INSERT, UPDATE, etc.) only the atom |
360 |
|
%% `ok' is returned. |
361 |
|
%% |
362 |
|
%% === FilterMap details === |
363 |
|
%% |
364 |
|
%% If the `FilterMap' argument is used, it must be a function of arity 1 or 2 |
365 |
|
%% that returns either `true', `false', or `{true, Value}'. |
366 |
|
%% |
367 |
|
%% Each result row is handed to the given function as soon as it is received |
368 |
|
%% from the server, and only when the function has returned, the next row is |
369 |
|
%% fetched. This provides the ability to prevent memory exhaustion. On the |
370 |
|
%% other hand, it can cause the server to time out on sending if your function |
371 |
|
%% is doing something slow (see the MySQL documentation on `NET_WRITE_TIMEOUT'). |
372 |
|
%% |
373 |
|
%% If the function is of arity 1, only the row is passed to it as the single |
374 |
|
%% argument, while if the function is of arity 2, the column names are passed |
375 |
|
%% in as the first argument and the row as the second. |
376 |
|
%% |
377 |
|
%% The value returned is then used to decide if the row is to be included in |
378 |
|
%% the result(s) returned from the `query' call (filtering), or if something |
379 |
|
%% else is to be included in the result instead (mapping). You may also use |
380 |
|
%% this function for side effects, like writing rows to disk or sending them |
381 |
|
%% to another process etc. |
382 |
|
%% |
383 |
|
%% === Examples === |
384 |
|
%% |
385 |
|
%% Here is an example showing some of the things that are possible: |
386 |
|
%% ``` |
387 |
|
%% Query = "SELECT a, b, c FROM foo", |
388 |
|
%% FilterMap = fun |
389 |
|
%% %% Include all rows where the first column is < 10. |
390 |
|
%% ([A|_]) when A < 10 -> |
391 |
|
%% true; |
392 |
|
%% %% Exclude all rows where the first column is >= 10 and < 20. |
393 |
|
%% ([A|_]) when A < 20 -> |
394 |
|
%% false; |
395 |
|
%% %% For rows where the first column is >= 20 and < 30, include |
396 |
|
%% %% the atom 'foo' in place of the row instead. |
397 |
|
%% ([A|_]) when A < 30 -> |
398 |
|
%% {true, foo}}; |
399 |
|
%% %% For rows where the first row is >= 30 and < 40, send the |
400 |
|
%% %% row to a gen_server via call (ie, wait for a response), |
401 |
|
%% %% and do not include the row in the result. |
402 |
|
%% (R=[A|_]) when A < 40 -> |
403 |
|
%% gen_server:call(Pid, R), |
404 |
|
%% false; |
405 |
|
%% %% For rows where the first column is >= 40 and < 50, send the |
406 |
|
%% %% row to a gen_server via cast (ie, do not wait for a reply), |
407 |
|
%% %% and include the row in the result, also. |
408 |
|
%% (R=[A|_]) when A < 50 -> |
409 |
|
%% gen_server:cast(Pid, R), |
410 |
|
%% true; |
411 |
|
%% %% Exclude all other rows from the result. |
412 |
|
%% (_) -> |
413 |
|
%% false |
414 |
|
%% end, |
415 |
|
%% query(Conn, Query, FilterMap). |
416 |
|
%% ''' |
417 |
|
-spec query(Conn, Query, Params, FilterMap, Timeout) -> Result |
418 |
|
when Conn :: connection(), |
419 |
|
Query :: query(), |
420 |
|
Timeout :: timeout(), |
421 |
|
Params :: [query_param()], |
422 |
|
FilterMap :: query_filtermap_fun(), |
423 |
|
Result :: query_result(). |
424 |
|
query(Conn, Query, Params, FilterMap, Timeout) -> |
425 |
:-( |
query_helper(Conn, Query, Params, FilterMap, Timeout). |
426 |
|
|
427 |
|
-spec query_helper(Conn, Query, Params, FilterMap, Timeout) -> Result |
428 |
|
when Conn :: connection(), |
429 |
|
Query :: query(), |
430 |
|
Timeout :: default_timeout | timeout(), |
431 |
|
Params :: no_params | [query_param()], |
432 |
|
FilterMap :: no_filtermap_fun | query_filtermap_fun(), |
433 |
|
Result :: query_result(). |
434 |
|
query_helper(Conn, Query, no_params, FilterMap, Timeout) -> |
435 |
450 |
query_call(Conn, {query, Query, FilterMap, Timeout}); |
436 |
|
query_helper(Conn, Query, Params, FilterMap, Timeout) -> |
437 |
14 |
case mysql_protocol:valid_params(Params) of |
438 |
|
true -> |
439 |
13 |
query_call(Conn, |
440 |
|
{param_query, Query, Params, FilterMap, Timeout}); |
441 |
|
false -> |
442 |
1 |
error(badarg) |
443 |
|
end. |
444 |
|
|
445 |
|
%% @doc Executes a prepared statement with the default query timeout as given |
446 |
|
%% to start_link/1. |
447 |
|
%% @see prepare/2 |
448 |
|
%% @see prepare/3 |
449 |
|
%% @see prepare/4 |
450 |
|
%% @see execute/5 |
451 |
|
-spec execute(Conn, StatementRef, Params) -> Result | {error, not_prepared} |
452 |
|
when Conn :: connection(), |
453 |
|
StatementRef :: statement_ref(), |
454 |
|
Params :: [query_param()], |
455 |
|
Result :: query_result(). |
456 |
|
execute(Conn, StatementRef, Params) -> |
457 |
185 |
execute_helper(Conn, StatementRef, Params, no_filtermap_fun, default_timeout). |
458 |
|
|
459 |
|
%% @doc Executes a prepared statement. |
460 |
|
%% @see prepare/2 |
461 |
|
%% @see prepare/3 |
462 |
|
%% @see prepare/4 |
463 |
|
%% @see execute/5 |
464 |
|
-spec execute(Conn, StatementRef, Params, FilterMap | Timeout) -> |
465 |
|
Result | {error, not_prepared} |
466 |
|
when Conn :: connection(), |
467 |
|
StatementRef :: statement_ref(), |
468 |
|
Params :: [query_param()], |
469 |
|
FilterMap :: query_filtermap_fun(), |
470 |
|
Timeout :: timeout(), |
471 |
|
Result :: query_result(). |
472 |
|
execute(Conn, StatementRef, Params, Timeout) when Timeout == default_timeout; |
473 |
|
is_integer(Timeout); |
474 |
|
Timeout=:=infinity -> |
475 |
1 |
execute_helper(Conn, StatementRef, Params, no_filtermap_fun, Timeout); |
476 |
|
execute(Conn, StatementRef, Params, FilterMap) when FilterMap == no_filtermap_fun; |
477 |
|
is_function(FilterMap, 1); |
478 |
|
is_function(FilterMap, 2) -> |
479 |
2 |
execute_helper(Conn, StatementRef, Params, FilterMap, default_timeout). |
480 |
|
|
481 |
|
%% @doc Executes a prepared statement. |
482 |
|
%% |
483 |
|
%% The `FilterMap' and `Timeout' arguments are optional. |
484 |
|
%% <ul> |
485 |
|
%% <li>If the `FilterMap' argument is the atom `no_filtermap_fun' or is |
486 |
|
%% omitted, no row filtering/mapping will be applied and all result rows |
487 |
|
%% will be returned unchanged.</li> |
488 |
|
%% <li>If the `Timeout' argument is the atom `default_timeout' or is omitted, |
489 |
|
%% the timeout given in `start_link/1' is used.</li> |
490 |
|
%% </ul> |
491 |
|
%% |
492 |
|
%% See `query/5' for an explanation of the `FilterMap' argument. |
493 |
|
%% |
494 |
|
%% @see prepare/2 |
495 |
|
%% @see prepare/3 |
496 |
|
%% @see prepare/4 |
497 |
|
%% @see query/5 |
498 |
|
-spec execute(Conn, StatementRef, Params, FilterMap, Timeout) -> |
499 |
|
Result | {error, not_prepared} |
500 |
|
when Conn :: connection(), |
501 |
|
StatementRef :: statement_ref(), |
502 |
|
Params :: [query_param()], |
503 |
|
FilterMap :: query_filtermap_fun(), |
504 |
|
Timeout :: timeout(), |
505 |
|
Result :: query_result(). |
506 |
|
execute(Conn, StatementRef, Params, FilterMap, Timeout) -> |
507 |
:-( |
execute_helper(Conn, StatementRef, Params, FilterMap, Timeout). |
508 |
|
|
509 |
|
-spec execute_helper(Conn, StatementRef, Params, FilterMap, Timeout) -> |
510 |
|
Result | {error, not_prepared} |
511 |
|
when Conn :: connection(), |
512 |
|
StatementRef :: statement_ref(), |
513 |
|
Params :: [query_param()], |
514 |
|
FilterMap :: no_filtermap_fun | query_filtermap_fun(), |
515 |
|
Timeout :: default_timeout | timeout(), |
516 |
|
Result :: query_result(). |
517 |
|
execute_helper(Conn, StatementRef, Params, FilterMap, Timeout) -> |
518 |
188 |
case mysql_protocol:valid_params(Params) of |
519 |
|
true -> |
520 |
187 |
query_call(Conn, |
521 |
|
{execute, StatementRef, Params, FilterMap, Timeout}); |
522 |
|
false -> |
523 |
1 |
error(badarg) |
524 |
|
end. |
525 |
|
|
526 |
|
%% @doc Creates a prepared statement from the passed query. |
527 |
|
%% @see prepare/3 |
528 |
|
-spec prepare(Conn, Query) -> {ok, StatementId} | {error, Reason} |
529 |
|
when Conn :: connection(), |
530 |
|
Query :: query(), |
531 |
|
StatementId :: statement_id(), |
532 |
|
Reason :: server_reason(). |
533 |
|
prepare(Conn, Query) -> |
534 |
93 |
gen_server:call(Conn, {prepare, Query}). |
535 |
|
|
536 |
|
%% @doc Creates a prepared statement from the passed query and associates it |
537 |
|
%% with the given name. |
538 |
|
%% @see prepare/2 |
539 |
|
-spec prepare(Conn, Name, Query) -> {ok, Name} | {error, Reason} |
540 |
|
when Conn :: connection(), |
541 |
|
Name :: statement_name(), |
542 |
|
Query :: query(), |
543 |
|
Reason :: server_reason(). |
544 |
|
prepare(Conn, Name, Query) -> |
545 |
7 |
gen_server:call(Conn, {prepare, Name, Query}). |
546 |
|
|
547 |
|
%% @doc Deallocates a prepared statement. |
548 |
|
-spec unprepare(Conn, StatementRef) -> ok | {error, Reason} |
549 |
|
when Conn :: connection(), |
550 |
|
StatementRef :: statement_ref(), |
551 |
|
Reason :: server_reason() | not_prepared. |
552 |
|
unprepare(Conn, StatementRef) -> |
553 |
91 |
gen_server:call(Conn, {unprepare, StatementRef}). |
554 |
|
|
555 |
|
%% @doc Returns the number of warnings generated by the last query/2 or |
556 |
|
%% execute/3 calls. |
557 |
|
-spec warning_count(connection()) -> integer(). |
558 |
|
warning_count(Conn) -> |
559 |
2 |
gen_server:call(Conn, warning_count). |
560 |
|
|
561 |
|
%% @doc Returns the number of inserted, updated and deleted rows of the last |
562 |
|
%% executed query or prepared statement. If found_rows is set on the |
563 |
|
%% connection, for update operation the return value will equal to the number |
564 |
|
%% of rows matched by the query. |
565 |
|
-spec affected_rows(connection()) -> integer(). |
566 |
|
affected_rows(Conn) -> |
567 |
3 |
gen_server:call(Conn, affected_rows). |
568 |
|
|
569 |
|
%% @doc Returns true if auto-commit is enabled and false otherwise. |
570 |
|
-spec autocommit(connection()) -> boolean(). |
571 |
|
autocommit(Conn) -> |
572 |
3 |
gen_server:call(Conn, autocommit). |
573 |
|
|
574 |
|
%% @doc Returns the last insert-id. |
575 |
|
-spec insert_id(connection()) -> integer(). |
576 |
|
insert_id(Conn) -> |
577 |
3 |
gen_server:call(Conn, insert_id). |
578 |
|
|
579 |
|
%% @doc Returns true if the connection is in a transaction and false otherwise. |
580 |
|
%% This works regardless of whether the transaction has been started using |
581 |
|
%% transaction/2,3 or using a plain `mysql:query(Connection, "BEGIN")'. |
582 |
|
%% @see transaction/2 |
583 |
|
%% @see transaction/4 |
584 |
|
-spec in_transaction(connection()) -> boolean(). |
585 |
|
in_transaction(Conn) -> |
586 |
25 |
gen_server:call(Conn, in_transaction). |
587 |
|
|
588 |
|
%% @doc This function executes the functional object Fun as a transaction. |
589 |
|
%% @see transaction/4 |
590 |
|
-spec transaction(Conn, TransactionFun) -> TransactionResult |
591 |
|
when Conn :: connection(), |
592 |
|
TransactionFun :: fun(() -> Result), |
593 |
|
TransactionResult :: transaction_result(Result). |
594 |
|
transaction(Conn, Fun) -> |
595 |
24 |
transaction(Conn, Fun, [], infinity). |
596 |
|
|
597 |
|
%% @doc This function executes the functional object Fun as a transaction. |
598 |
|
%% @see transaction/4 |
599 |
|
-spec transaction(Conn, TransactionFun, Retries) -> TransactionResult |
600 |
|
when Conn :: connection(), |
601 |
|
TransactionFun :: fun(() -> Result), |
602 |
|
Retries :: non_neg_integer() | infinity, |
603 |
|
TransactionResult :: transaction_result(Result). |
604 |
|
transaction(Conn, Fun, Retries) -> |
605 |
4 |
transaction(Conn, Fun, [], Retries). |
606 |
|
|
607 |
|
%% @doc This function executes the functional object Fun with arguments Args as |
608 |
|
%% a transaction. |
609 |
|
%% |
610 |
|
%% The semantics are as close as possible to mnesia's transactions. Transactions |
611 |
|
%% can be nested and are restarted automatically when deadlocks are detected. |
612 |
|
%% MySQL's savepoints are used to implement nested transactions. |
613 |
|
%% |
614 |
|
%% Fun must be a function and Args must be a list of the same length as the |
615 |
|
%% arity of Fun. |
616 |
|
%% |
617 |
|
%% If an exception occurs within Fun, the exception is caught and `{aborted, |
618 |
|
%% Reason}' is returned. The value of `Reason' depends on the class of the |
619 |
|
%% exception. |
620 |
|
%% |
621 |
|
%% Note that an error response from a query does not cause a transaction to be |
622 |
|
%% rollbacked. To force a rollback on a MySQL error you can trigger a `badmatch' |
623 |
|
%% using e.g. `ok = mysql:query(Pid, "SELECT some_non_existent_value")'. An |
624 |
|
%% exception to this is the error 1213 "Deadlock", after the specified number |
625 |
|
%% of retries, all failed. In this case, the transaction is aborted and the |
626 |
|
%% error is retured as the reason for the aborted transaction, along with a |
627 |
|
%% stacktrace pointing to where the last deadlock was detected. (In earlier |
628 |
|
%% versions, up to and including 1.3.2, transactions where automatically |
629 |
|
%% restarted also for the error 1205 "Lock wait timeout". This is no longer the |
630 |
|
%% case.) |
631 |
|
%% |
632 |
|
%% Some queries such as ALTER TABLE cause an *implicit commit* on the server. |
633 |
|
%% If such a query is executed within a transaction, an error on the form |
634 |
|
%% `{implicit_commit, Query}' is raised. This means that the transaction has |
635 |
|
%% been committed prematurely. This also happens if an explicit COMMIT is |
636 |
|
%% executed as a plain query within a managed transaction. (Don't do that!) |
637 |
|
%% |
638 |
|
%% <table> |
639 |
|
%% <thead> |
640 |
|
%% <tr><th>Class of exception</th><th>Return value</th></tr> |
641 |
|
%% </thead> |
642 |
|
%% <tbody> |
643 |
|
%% <tr> |
644 |
|
%% <td>`error' with reason `ErrorReason'</td> |
645 |
|
%% <td>`{aborted, {ErrorReason, Stack}}'</td> |
646 |
|
%% </tr> |
647 |
|
%% <tr><td>`exit(Term)'</td><td>`{aborted, Term}'</td></tr> |
648 |
|
%% <tr><td>`throw(Term)'</td><td>`{aborted, {throw, Term}}'</td></tr> |
649 |
|
%% </tbody> |
650 |
|
%% </table> |
651 |
|
-spec transaction(Conn, TransactionFun, Args, Retries) -> TransactionResult |
652 |
|
when Conn :: connection(), |
653 |
|
TransactionFun :: fun((...) -> Result), |
654 |
|
Args :: list(), |
655 |
|
Retries :: non_neg_integer() | infinity, |
656 |
|
TransactionResult :: transaction_result(Result). |
657 |
|
transaction(Conn, Fun, Args, Retries) when is_list(Args), |
658 |
|
is_function(Fun, length(Args)) -> |
659 |
|
%% The guard makes sure that we can apply Fun to Args. Any error we catch |
660 |
|
%% in the try-catch are actual errors that occurred in Fun. |
661 |
28 |
ok = gen_server:call(Conn, start_transaction, infinity), |
662 |
28 |
execute_transaction(Conn, Fun, Args, Retries). |
663 |
|
|
664 |
|
%% @private |
665 |
|
%% @doc This is a helper for transaction/2,3,4. It performs everything except |
666 |
|
%% executing the BEGIN statement. It is called recursively when a transaction |
667 |
|
%% is retried. |
668 |
|
%% |
669 |
|
%% "When a transaction rollback occurs due to a deadlock or lock wait timeout, |
670 |
|
%% it cancels the effect of the statements within the transaction. But if the |
671 |
|
%% start-transaction statement was START TRANSACTION or BEGIN statement, |
672 |
|
%% rollback does not cancel that statement." |
673 |
|
%% (https://dev.mysql.com/doc/refman/5.6/en/innodb-error-handling.html) |
674 |
|
%% |
675 |
|
%% This seems to have changed in MySQL 5.7.x though (although the MySQL |
676 |
|
%% documentation hasn't been updated). Now, also the BEGIN is cancelled, so a |
677 |
|
%% new BEGIN has to be issued when restarting the transaction. This has no |
678 |
|
%% effect on older versions, not even a warning. |
679 |
|
%% |
680 |
|
%% Lock Wait Timeout: |
681 |
|
%% "InnoDB rolls back only the last statement on a transaction timeout by |
682 |
|
%% default. If --innodb_rollback_on_timeout is specified, a transaction timeout |
683 |
|
%% causes InnoDB to abort and roll back the entire transaction (the same |
684 |
|
%% behavior as in MySQL 4.1)." |
685 |
|
%% (https://dev.mysql.com/doc/refman/5.6/en/innodb-parameters.html) |
686 |
|
execute_transaction(Conn, Fun, Args, Retries) -> |
687 |
30 |
try apply(Fun, Args) of |
688 |
|
ResultOfFun -> |
689 |
16 |
ok = gen_server:call(Conn, commit, infinity), |
690 |
16 |
{atomic, ResultOfFun} |
691 |
|
catch |
692 |
|
%% We are at the top level, try to restart the transaction if there are |
693 |
|
%% retries left |
694 |
|
?EXCEPTION(throw, {implicit_rollback, 1, _}, _Stacktrace) |
695 |
|
when Retries == infinity -> |
696 |
|
%% In MySQL < 5.7 we're not in a transaction here, but in earlier |
697 |
|
%% versions we are, so we can't use `gen_server:call(Conn, |
698 |
|
%% start_transaction, infinity)' here. |
699 |
1 |
ok = query(Conn, <<"BEGIN">>), |
700 |
1 |
execute_transaction(Conn, Fun, Args, infinity); |
701 |
|
?EXCEPTION(throw, {implicit_rollback, 1, _}, _Stacktrace) |
702 |
|
when Retries > 0 -> |
703 |
1 |
ok = query(Conn, <<"BEGIN">>), |
704 |
1 |
execute_transaction(Conn, Fun, Args, Retries - 1); |
705 |
|
?EXCEPTION(throw, {implicit_rollback, 1, Reason}, Stacktrace) |
706 |
|
when Retries == 0 -> |
707 |
|
%% No more retries. Return 'aborted' along with the deadlock error |
708 |
|
%% and a the trace to the line where the deadlock occured. |
709 |
1 |
Trace = ?GET_STACK(Stacktrace), |
710 |
|
%% In MySQL < 5.7, we are still in a transaction here, but in 5.7+ |
711 |
|
%% we're not. The ROLLBACK executed here has no effect if no |
712 |
|
%% transaction is ongoing. |
713 |
1 |
ok = gen_server:call(Conn, rollback, infinity), |
714 |
1 |
{aborted, {Reason, Trace}}; |
715 |
|
?EXCEPTION(throw, {implicit_rollback, N, Reason}, Stacktrace) |
716 |
|
when N > 1 -> |
717 |
|
%% Nested transaction. Bubble out to the outermost level. |
718 |
3 |
erlang:raise(throw, {implicit_rollback, N - 1, Reason}, |
719 |
|
?GET_STACK(Stacktrace)); |
720 |
|
?EXCEPTION(error, {implicit_commit, _Query} = E, Stacktrace) -> |
721 |
|
%% The called did something like ALTER TABLE which resulted in an |
722 |
|
%% implicit commit. The server has already committed. We need to |
723 |
|
%% jump out of N levels of transactions. |
724 |
|
%% |
725 |
|
%% Returning 'atomic' or 'aborted' would both be wrong. Raise an |
726 |
|
%% exception is the best we can do. |
727 |
2 |
erlang:raise(error, E, ?GET_STACK(Stacktrace)); |
728 |
|
?EXCEPTION(error, change_user_in_transaction = E, Stacktrace) -> |
729 |
|
%% The called tried to change user inside the transaction, which |
730 |
|
%% is not allowed and a serious mistake. We roll back and raise |
731 |
|
%% an error. |
732 |
1 |
ok = gen_server:call(Conn, rollback, infinity), |
733 |
1 |
erlang:raise(error, E, ?GET_STACK(Stacktrace)); |
734 |
|
?EXCEPTION(error, reset_connection_in_transaction = E, Stacktrace) -> |
735 |
|
%% The called tried to reset connection inside the transaction, which |
736 |
|
%% is not allowed and a serious mistake. We roll back and raise |
737 |
|
%% an error. |
738 |
:-( |
ok = gen_server:call(Conn, rollback, infinity), |
739 |
:-( |
erlang:raise(error, E, ?GET_STACK(Stacktrace)); |
740 |
|
?EXCEPTION(Class, Reason, Stacktrace) -> |
741 |
|
%% We must be able to rollback. Otherwise let's crash. |
742 |
4 |
ok = gen_server:call(Conn, rollback, infinity), |
743 |
|
%% These forms for throw, error and exit mirror Mnesia's behaviour. |
744 |
4 |
Aborted = case Class of |
745 |
2 |
throw -> {throw, Reason}; |
746 |
1 |
error -> {Reason, ?GET_STACK(Stacktrace)}; |
747 |
1 |
exit -> Reason |
748 |
|
end, |
749 |
4 |
{aborted, Aborted} |
750 |
|
end. |
751 |
|
|
752 |
|
%% @doc Equivalent to `change_user(Conn, Username, Password, [])'. |
753 |
|
%% @see change_user/4 |
754 |
|
-spec change_user(Conn, Username, Password) -> Result |
755 |
|
when Conn :: connection(), |
756 |
|
Username :: iodata(), |
757 |
|
Password :: iodata(), |
758 |
|
Result :: ok. |
759 |
|
change_user(Conn, Username, Password) -> |
760 |
6 |
change_user(Conn, Username, Password, []). |
761 |
|
|
762 |
|
%% @doc Changes the user of the active connection without closing and |
763 |
|
%% and re-opening it. The currently active session will be reset (ie, |
764 |
|
%% user variables, temporary tables, prepared statements, etc will |
765 |
|
%% be lost) independent of whether the operation succeeds or fails. |
766 |
|
%% |
767 |
|
%% If change user is called when a transaction is active (ie, neither |
768 |
|
%% committed nor rolled back), calling `change_user' will fail with |
769 |
|
%% an error exception and `change_user_in_transaction' as the error |
770 |
|
%% message. |
771 |
|
%% |
772 |
|
%% If the change user operation fails, `{error, Reason}' will be |
773 |
|
%% returned. Specifically, if the operation itself fails (eg |
774 |
|
%% authentication failure), `change_user_failed' will be returned as |
775 |
|
%% the reason, while if the operation itself succeeds but one of |
776 |
|
%% the given initial queries or prepares fails, the reason will |
777 |
|
%% reflect the cause for the failure. In any case, the connection |
778 |
|
%% process will exit with the same reason and cannot be used any longer. |
779 |
|
%% |
780 |
|
%% For a description of the `database', `queries' and `prepare' |
781 |
|
%% options, see `start_link/1'. |
782 |
|
%% |
783 |
|
%% @see start_link/1 |
784 |
|
-spec change_user(Conn, Username, Password, Options) -> Result |
785 |
|
when Conn :: connection(), |
786 |
|
Username :: iodata(), |
787 |
|
Password :: iodata(), |
788 |
|
Options :: [Option], |
789 |
|
Result :: ok, |
790 |
|
Option :: {database, iodata()} |
791 |
|
| {queries, [query()]} |
792 |
|
| {prepare, [NamedStatement]}, |
793 |
|
NamedStatement :: {StatementName :: statement_name(), Statement :: query()}. |
794 |
|
change_user(Conn, Username, Password, Options) -> |
795 |
11 |
case in_transaction(Conn) of |
796 |
2 |
true -> error(change_user_in_transaction); |
797 |
9 |
false -> ok |
798 |
|
end, |
799 |
9 |
gen_server:call(Conn, {change_user, Username, Password, Options}). |
800 |
|
|
801 |
|
-spec reset_connection(Conn) -> ok | {error, Reason} |
802 |
|
when Conn :: connection(), |
803 |
|
Reason :: server_reason(). |
804 |
|
reset_connection(Conn) -> |
805 |
1 |
case in_transaction(Conn) of |
806 |
:-( |
true -> error(reset_connection_in_transaction); |
807 |
1 |
false -> ok |
808 |
|
end, |
809 |
1 |
gen_server:call(Conn, reset_connection). |
810 |
|
|
811 |
|
%% @doc Encodes a term as a MySQL literal so that it can be used to inside a |
812 |
|
%% query. If backslash escapes are enabled, backslashes and single quotes in |
813 |
|
%% strings and binaries are escaped. Otherwise only single quotes are escaped. |
814 |
|
%% |
815 |
|
%% Note that the preferred way of sending values is by prepared statements or |
816 |
|
%% parametrized queries with placeholders. |
817 |
|
%% |
818 |
|
%% @see query/3 |
819 |
|
%% @see execute/3 |
820 |
|
-spec encode(connection(), term()) -> iodata(). |
821 |
|
encode(Conn, Term) -> |
822 |
2 |
Term1 = case (is_list(Term) orelse is_binary(Term)) andalso |
823 |
2 |
gen_server:call(Conn, backslash_escapes_enabled) of |
824 |
1 |
true -> mysql_encode:backslash_escape(Term); |
825 |
1 |
false -> Term |
826 |
|
end, |
827 |
2 |
mysql_encode:encode(Term1). |
828 |
|
|
829 |
|
%% --- Helpers --- |
830 |
|
|
831 |
|
%% @doc Makes a gen_server call for a query (plain, parametrized or prepared), |
832 |
|
%% checks the reply and sometimes throws an exception when we need to jump out |
833 |
|
%% of a transaction. |
834 |
|
query_call(Conn, CallReq) -> |
835 |
650 |
case gen_server:call(Conn, CallReq, infinity) of |
836 |
|
{implicit_commit, _NestingLevel, Query} -> |
837 |
1 |
error({implicit_commit, Query}); |
838 |
|
{implicit_rollback, _NestingLevel, _ServerReason} = ImplicitRollback -> |
839 |
3 |
throw(ImplicitRollback); |
840 |
|
Result -> |
841 |
646 |
Result |
842 |
|
end. |