Current section
Files
Jump to
Current section
Files
src/pog_ffi.erl
-module(pog_ffi).
-export([query/4, query_extended/2, start/1, coerce/1, null/0, checkout/1]).
-include_lib("pog/include/pog_Config.hrl").
-include_lib("pg_types/include/pg_types.hrl").
null() ->
null.
coerce(Value) ->
Value.
%% Use correct defaults for SSL connections when SSL is enabled.
%% Peers have to be verified & cacerts are fetched directly from the system.
%%
%% `server_name_indication` should be set to the value of the Host, because the
%% connection to Postgres uses a TCP connection that get upgraded to TLS, and
%% the TLS socket is sent as is, meaning the Hostname is lost when ssl module
%% get the socket. server_name_indication overrides that behaviour and send
%% the correct Hostname to the ssl module.
%% `customize_hostname_check` should be set to with the verify hostname match
%% with HTTPS, because otherwise wildcards certificaties (i.e. *.example.com)
%% will not be handled correctly.
default_ssl_options(Host, Ssl) ->
case Ssl of
ssl_disabled -> {false, []};
ssl_unverified -> {true, [{verify, verify_none}]};
ssl_verified -> {true, [
{verify, verify_peer},
{cacerts, public_key:cacerts_get()},
{server_name_indication, binary_to_list(Host)},
{customize_hostname_check, [
{match_fun, public_key:pkix_verify_hostname_match_fun(https)}
]}
]}
end.
start(Config) ->
% Unfortunately this has to be supplied via global mutable state currently.
application:set_env(pg_types, timestamp_config, integer_system_time_microseconds),
#config{
pool_name = PoolName,
host = Host,
port = Port,
database = Database,
user = User,
password = Password,
ssl = Ssl,
connection_parameters = ConnectionParameters,
pool_size = PoolSize,
queue_target = QueueTarget,
queue_interval = QueueInterval,
idle_interval = IdleInterval,
trace = Trace,
ip_version = IpVersion,
rows_as_map = RowsAsMap
} = Config,
{SslActivated, SslOptions} = default_ssl_options(Host, Ssl),
Options1 = #{
host => Host,
port => Port,
database => Database,
user => User,
ssl => SslActivated,
ssl_options => SslOptions,
connection_parameters => ConnectionParameters,
pool_size => PoolSize,
queue_target => QueueTarget,
queue_interval => QueueInterval,
idle_interval => IdleInterval,
trace => Trace,
decode_opts => [{return_rows_as_maps, RowsAsMap}],
socket_options => case IpVersion of
ipv4 -> [];
ipv6 -> [inet6]
end
},
Options2 = case Password of
{some, Pw} -> maps:put(password, Pw, Options1);
none -> Options1
end,
pgo_pool:start_link(PoolName, Options2).
query(Pool, Sql, Arguments, Timeout) ->
Res = case Pool of
{single_connection, Conn} ->
pgo_handler:extended_query(Conn, Sql, Arguments, #{});
{pool, Name} ->
Options = #{
pool => Name,
pool_options => [{timeout, Timeout}]
},
pgo:query(Sql, Arguments, Options)
end,
case Res of
#{rows := Rows, num_rows := NumRows} ->
{ok, {NumRows, Rows}};
{error, Error} ->
{error, convert_error(Error)}
end.
query_extended(Conn, Sql) ->
case pgo_handler:extended_query(Conn, Sql, [], #{queue_time => undefined}) of
#{rows := Rows, num_rows := NumRows} ->
{ok, {NumRows, Rows}};
{error, Error} ->
{error, convert_error(Error)}
end.
checkout(Name) when is_atom(Name) ->
case pgo:checkout(Name) of
{ok, Ref, Conn} -> {ok, {Ref, Conn}};
{error, Error} -> {error, convert_error(Error)}
end.
convert_error(none_available) ->
connection_unavailable;
convert_error({pgo_protocol, {parameters, Expected, Got}}) ->
{unexpected_argument_count, Expected, Got};
convert_error({pgsql_error, #{
message := Message,
constraint := Constraint,
detail := Detail
}}) ->
{constraint_violated, Message, Constraint, Detail};
convert_error({pgsql_error, #{code := Code, message := Message}}) ->
Constant = case pog:error_code_name(Code) of
{ok, X} -> X;
{error, nil} -> <<"unknown">>
end,
{postgresql_error, Code, Constant, Message};
convert_error(#{
error := badarg_encoding,
type_info := #type_info{name = Expected},
value := Value
}) ->
Got = list_to_binary(io_lib:format("~p", [Value])),
{unexpected_argument_type, Expected, Got};
convert_error(closed) ->
query_timeout.