1 |
|
%% @private |
2 |
|
%% @doc Functions for encoding a term as an SQL literal. This is not really |
3 |
|
%% part of the protocol; thus the separate module. |
4 |
|
-module(mysql_encode). |
5 |
|
|
6 |
|
-export([encode/1, backslash_escape/1]). |
7 |
|
|
8 |
|
%% @doc Encodes a term as an ANSI SQL literal so that it can be used to inside |
9 |
|
%% a query. In strings only single quotes (') are escaped. If backslash escapes |
10 |
|
%% are enabled for the connection, you should first use backslash_escape/1 to |
11 |
|
%% escape backslashes in strings. |
12 |
|
-spec encode(term()) -> iodata(). |
13 |
1 |
encode(null) -> <<"NULL">>; |
14 |
|
encode(Int) when is_integer(Int) -> |
15 |
1 |
integer_to_binary(Int); |
16 |
|
encode(Float) when is_float(Float) -> |
17 |
|
%% "floats are printed accurately as the shortest, correctly rounded string" |
18 |
1 |
io_lib:format("~w", [Float]); |
19 |
|
encode(Bin) when is_binary(Bin) -> |
20 |
6 |
Escaped = binary:replace(Bin, <<"'">>, <<"''">>, [global]), |
21 |
6 |
[$', Escaped, $']; |
22 |
|
encode(String) when is_list(String) -> |
23 |
4 |
encode(unicode:characters_to_binary(String)); |
24 |
|
encode(Bitstring) when is_bitstring(Bitstring) -> |
25 |
1 |
["b'", [ case B of 0 -> $0; 1 -> $1 end || <<B:1>> <= Bitstring ], $']; |
26 |
|
encode({Y, M, D}) -> |
27 |
2 |
io_lib:format("'~4..0b-~2..0b-~2..0b'", [Y, M, D]); |
28 |
|
encode({{Y, M, D}, {H, Mi, S}}) when is_integer(S) -> |
29 |
3 |
io_lib:format("'~4..0b-~2..0b-~2..0b ~2..0b:~2..0b:~2..0b'", |
30 |
|
[Y, M, D, H, Mi, S]); |
31 |
|
encode({{Y, M, D}, {H, Mi, S}}) when is_float(S) -> |
32 |
1 |
io_lib:format("'~4..0b-~2..0b-~2..0b ~2..0b:~2..0b:~9.6.0f'", |
33 |
|
[Y, M, D, H, Mi, S]); |
34 |
|
encode({D, {H, M, S}}) when D >= 0 -> |
35 |
5 |
Args = [H1 = D * 24 + H, M, S], |
36 |
5 |
if |
37 |
1 |
H1 > 99, is_integer(S) -> io_lib:format("'~b:~2..0b:~2..0b'", Args); |
38 |
1 |
H1 > 99, is_float(S) -> io_lib:format("'~b:~2..0b:~9.6.0f'", Args); |
39 |
2 |
is_integer(S) -> io_lib:format("'~2..0b:~2..0b:~2..0b'", Args); |
40 |
1 |
is_float(S) -> io_lib:format("'~2..0b:~2..0b:~9.6.0f'", Args) |
41 |
|
end; |
42 |
|
encode({D, {H, M, S}}) when D < 0, is_integer(S) -> |
43 |
5 |
Sec = (D * 24 + H) * 3600 + M * 60 + S, |
44 |
5 |
{D1, {H1, M1, S1}} = calendar:seconds_to_daystime(-Sec), |
45 |
5 |
Args = [H2 = D1 * 24 + H1, M1, S1], |
46 |
5 |
if |
47 |
1 |
H2 > 99 -> io_lib:format("'-~b:~2..0b:~2..0b'", Args); |
48 |
4 |
true -> io_lib:format("'-~2..0b:~2..0b:~2..0b'", Args) |
49 |
|
end; |
50 |
|
encode({D, {H, M, S}}) when D < 0, is_float(S) -> |
51 |
3 |
SInt = trunc(S), % trunc(57.654321) = 57 |
52 |
3 |
{SInt1, Frac} = case S - SInt of % 57.6543 - 57 = 0.654321 |
53 |
2 |
0.0 -> {SInt, 0.0}; |
54 |
1 |
Rest -> {SInt + 1, 1 - Rest} % {58, 0.345679} |
55 |
|
end, |
56 |
3 |
Sec = (D * 24 + H) * 3600 + M * 60 + SInt1, |
57 |
3 |
{D1, {H1, M1, S1}} = calendar:seconds_to_daystime(-Sec), |
58 |
3 |
Args = [H2 = D1 * 24 + H1, M1, S1 + Frac], |
59 |
3 |
if |
60 |
1 |
H2 > 99 -> io_lib:format("'-~b:~2..0b:~9.6.0f'", Args); |
61 |
2 |
true -> io_lib:format("'-~2..0b:~2..0b:~9.6.0f'", Args) |
62 |
|
end. |
63 |
|
|
64 |
|
%% @doc Escapes backslashes with an extra backslash. This is necessary if |
65 |
|
%% backslash escapes are enabled in the session. |
66 |
|
backslash_escape(String) -> |
67 |
2 |
Bin = iolist_to_binary(String), |
68 |
2 |
binary:replace(Bin, <<"\\">>, <<"\\\\">>, [global]). |