./ct_report/coverage/mongoose_rdbms_pgsql.COVER.html

1 %%==============================================================================
2 %% Copyright 2016 Erlang Solutions Ltd.
3 %%
4 %% Licensed under the Apache License, Version 2.0 (the "License");
5 %% you may not use this file except in compliance with the License.
6 %% You may obtain a copy of the License at
7 %%
8 %% http://www.apache.org/licenses/LICENSE-2.0
9 %%
10 %% Unless required by applicable law or agreed to in writing, software
11 %% distributed under the License is distributed on an "AS IS" BASIS,
12 %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 %% See the License for the specific language governing permissions and
14 %% limitations under the License.
15 %%==============================================================================
16
17 -module(mongoose_rdbms_pgsql).
18 -author('konrad.zemek@erlang-solutions.com').
19 -behaviour(mongoose_rdbms_backend).
20
21 -include_lib("epgsql/include/epgsql.hrl").
22
23 -define(PGSQL_PORT, 5432).
24
25 -export([escape_binary/1, unescape_binary/1, connect/2, disconnect/1,
26 query/3, prepare/5, execute/4]).
27
28 %% API
29
30 -spec escape_binary(binary()) -> iodata().
31 escape_binary(Bin) when is_binary(Bin) ->
32 87 [<<"decode('">>, base64:encode(Bin), <<"','base64')">>].
33
34 -spec unescape_binary(binary()) -> binary().
35 unescape_binary(<<"\\x", Bin/binary>>) ->
36 174 base16:decode(Bin);
37 unescape_binary(Bin) when is_binary(Bin) ->
38 5902 Bin.
39
40 -spec connect(Args :: any(), QueryTimeout :: non_neg_integer()) ->
41 {ok, Connection :: term()} | {error, Reason :: any()}.
42 connect(Settings, QueryTimeout) ->
43 380 case epgsql:connect(db_opts(Settings)) of
44 {ok, Pid} ->
45 380 epgsql:squery(Pid, [<<"SET statement_timeout=">>, integer_to_binary(QueryTimeout)]),
46 380 epgsql:squery(Pid, <<"SET standard_conforming_strings=off">>),
47 380 {ok, Pid};
48 Error ->
49
:-(
Error
50 end.
51
52 -spec disconnect(Connection :: epgsql:connection()) -> ok.
53 disconnect(Connection) ->
54 365 epgsql:close(Connection).
55
56 -spec query(Connection :: term(), Query :: any(),
57 Timeout :: infinity | non_neg_integer()) -> mongoose_rdbms:query_result().
58 query(Connection, Query, _Timeout) ->
59 24453 pgsql_to_rdbms(epgsql:squery(Connection, Query)).
60
61 -spec prepare(Connection :: term(), Name :: atom(), Table :: binary(),
62 Fields :: [binary()], Statement :: iodata()) ->
63 {ok, term()} | {error, any()}.
64 prepare(Connection, Name, _Table, _Fields, Statement) ->
65 1347 BinName = [atom_to_binary(Name, latin1)],
66 1347 ReplacedStatement = replace_question_marks(Statement),
67 1347 case epgsql:parse(Connection, BinName, ReplacedStatement, []) of
68 1347 {ok, _} -> epgsql:describe(Connection, statement, BinName);
69
:-(
Error -> Error
70 end.
71
72 -spec execute(Connection :: term(), StatementRef :: term(), Params :: [term()],
73 Timeout :: infinity | non_neg_integer()) -> mongoose_rdbms:query_result().
74 execute(Connection, StatementRef, Params, _Timeout) ->
75 94316 pgsql_to_rdbms(epgsql:prepared_query(Connection, StatementRef, Params)).
76
77 %% Helpers
78
79 -spec db_opts(Settings :: term()) -> [term()].
80 db_opts({pgsql, Server, DB, User, Pass}) ->
81
:-(
db_opts({pgsql, Server, ?PGSQL_PORT, DB, User, Pass});
82 db_opts({pgsql, Server, Port, DB, User, Pass}) when is_integer(Port) ->
83
:-(
get_db_basic_opts({Server, Port, DB, User, Pass});
84 db_opts({pgsql, Server, DB, User, Pass, SSLConnOpts}) ->
85 380 db_opts({pgsql, Server, ?PGSQL_PORT, DB, User, Pass, SSLConnOpts});
86 db_opts({pgsql, Server, Port, DB, User, Pass, SSLConnOpts}) when is_integer(Port) ->
87 380 DBBasicOpts = get_db_basic_opts({Server, Port, DB, User, Pass}),
88 380 extend_db_opts_with_ssl(DBBasicOpts, SSLConnOpts).
89
90 -spec get_db_basic_opts(Settings :: term()) -> [term()].
91 get_db_basic_opts({Server, Port, DB, User, Pass}) ->
92 380 [
93 {host, Server},
94 {port, Port},
95 {database, DB},
96 {username, User},
97 {password, Pass},
98 %% Encode 0 and 1 as booleans, as well as true and false
99 {codecs, [{mongoose_rdbms_pgsql_codec_boolean, []}]}
100 ].
101
102 -spec extend_db_opts_with_ssl(Opts :: [term()], SSLConnOpts :: [term()]) -> [term()].
103 extend_db_opts_with_ssl(Opts, SSLConnOpts) ->
104 380 Opts ++ SSLConnOpts.
105
106 -spec pgsql_to_rdbms(epgsql:reply(term())) -> mongoose_rdbms:query_result().
107 pgsql_to_rdbms(Items) when is_list(Items) ->
108
:-(
lists:reverse([pgsql_to_rdbms(Item) || Item <- Items]);
109 pgsql_to_rdbms({error, #error{codename = unique_violation}}) ->
110 104 {error, duplicate_key};
111 pgsql_to_rdbms({error, #error{message = Message}}) ->
112
:-(
{error, unicode:characters_to_list(Message)};
113 pgsql_to_rdbms({ok, Count}) ->
114 43167 {updated, Count};
115 pgsql_to_rdbms({ok, Count, _Column, Value}) ->
116
:-(
{updated, Count, Value};
117 pgsql_to_rdbms({ok, _Columns, Rows}) ->
118 75498 {selected, Rows}.
119
120 -spec replace_question_marks(Statement :: iodata()) -> iodata().
121 replace_question_marks(Statement) when is_list(Statement) ->
122
:-(
replace_question_marks(iolist_to_binary(Statement));
123 replace_question_marks(Statement) when is_binary(Statement) ->
124 1347 [Head | Parts] = binary:split(Statement, <<"?">>, [global]),
125 1347 Placeholders = [<<"$", (integer_to_binary(I))/binary>> || I <- lists:seq(1, length(Parts))],
126 1347 PartsWithPlaceholders = lists:zipwith(fun(A, B) -> [A, B] end, Placeholders, Parts),
127 1347 [Head | PartsWithPlaceholders].
Line Hits Source