Current section
18 Versions
Jump to
Current section
18 Versions
Compare versions
13
files changed
+256
additions
-177
deletions
| @@ -57,52 +57,60 @@ see `CHANGES` for full list. | |
| 57 57 | (thousands of messages). |
| 58 58 | |
| 59 59 | ## Usage |
| 60 | + |
| 60 61 | ### Connect |
| 61 62 | |
| 62 63 | ```erlang |
| 63 | - -type host() :: inet:ip_address() | inet:hostname(). |
| 64 | + connect(Opts) -> {ok, Connection :: epgsql:connection()} | {error, Reason :: epgsql:connect_error()} |
| 65 | + when |
| 66 | + Opts :: |
| 67 | + #{host := inet:ip_address() | inet:hostname(), |
| 68 | + username := iodata(), |
| 69 | + password => iodata() | fun( () -> iodata() ), |
| 70 | + database => iodata(), |
| 71 | + port => inet:port_number(), |
| 72 | + ssl => boolean() | required, |
| 73 | + ssl_opts => [ssl:ssl_option()], % @see OTP ssl app, ssl_api.hrl |
| 74 | + timeout => timeout(), % socket connect timeout, default: 5000 ms |
| 75 | + async => pid() | atom(), % process to receive LISTEN/NOTIFY msgs |
| 76 | + codecs => [{epgsql_codec:codec_mod(), any()}]} |
| 77 | + replication => Replication :: string()} % Pass "database" to connect in replication mode |
| 78 | + | list(). |
| 64 79 | |
| 65 | - -type connect_option() :: |
| 66 | - {database, DBName :: string()} | |
| 67 | - {port, PortNum :: inet:port_number()} | |
| 68 | - {ssl, IsEnabled :: boolean() | required} | |
| 69 | - {ssl_opts, SslOptions :: [ssl:ssl_option()]} | % @see OTP ssl app, ssl_api.hrl |
| 70 | - {timeout, TimeoutMs :: timeout()} | % default: 5000 ms |
| 71 | - {async, Receiver :: pid() | atom()} | % process to receive LISTEN/NOTIFY msgs |
| 72 | - {codecs, Codecs :: [{epgsql_codec:codec_mod(), any()}]} | |
| 73 | - {replication, Replication :: string()}. % Pass "database" to connect in replication mode |
| 74 | - |
| 75 | - -spec connect(host(), string(), string(), [connect_option()] | map()) |
| 76 | - -> {ok, Connection :: connection()} | {error, Reason :: connect_error()}. |
| 77 | - %% @doc connects to Postgres |
| 78 | - %% where |
| 79 | - %% `Host' - host to connect to |
| 80 | - %% `Username' - username to connect as, defaults to `$USER' |
| 81 | - %% `Password' - optional password to authenticate with |
| 82 | - %% `Opts' - proplist or map of extra options |
| 83 | - %% returns `{ok, Connection}' otherwise `{error, Reason}' |
| 84 | - connect(Host, Username, Password, Opts) -> ... |
| 80 | + connect(Host, Username, Password, Opts) -> {ok, C} | {error, Reason}. |
| 85 81 | ``` |
| 86 82 | example: |
| 87 83 | ```erlang |
| 88 | - {ok, C} = epgsql:connect("localhost", "username", "psss", [ |
| 89 | - {database, "test_db"}, |
| 90 | - {timeout, 4000} |
| 91 | - ]), |
| 84 | + {ok, C} = epgsql:connect("localhost", "username", "psss", #{ |
| 85 | + database => "test_db", |
| 86 | + timeout => 4000 |
| 87 | + }), |
| 92 88 | ... |
| 93 89 | ok = epgsql:close(C). |
| 94 90 | ``` |
| 95 91 | |
| 96 | - The `{timeout, TimeoutMs}` parameter will trigger an `{error, timeout}` result when the |
| 97 | - socket fails to connect within `TimeoutMs` milliseconds. |
| 92 | + Only `host` and `username` are mandatory, but most likely you would need `database` and `password`. |
| 98 93 | |
| 99 | - Options may be passed as map with the same key names, if your VM version supports maps. |
| 94 | + - `password` - DB user password. It might be provided as string / binary or as a fun that returns |
| 95 | + string / binary. Internally, plain password is wrapped to anonymous fun before it is sent to connection |
| 96 | + process, so, if `connect` command crashes, plain password will not appear in crash logs. |
| 97 | + - `{timeout, TimeoutMs}` parameter will trigger an `{error, timeout}` result when the |
| 98 | + socket fails to connect within `TimeoutMs` milliseconds. |
| 99 | + - `ssl` if set to `true`, perform an attempt to connect in ssl mode, but continue unencrypted |
| 100 | + if encryption isn't supported by server. if set to `required` connection will fail if encryption |
| 101 | + is not available. |
| 102 | + - `ssl_opts` will be passed as is to `ssl:connect/3` |
| 103 | + - `async` see [Server notifications](#server-notifications) |
| 104 | + - `codecs` see [Pluggable datatype codecs](#pluggable-datatype-codecs) |
| 105 | + - `replication` see [Streaming replication protocol](#streaming-replication-protocol) |
| 106 | + |
| 107 | + Options may be passed as proplist or as map with the same key names. |
| 100 108 | |
| 101 109 | Asynchronous connect example (applies to **epgsqli** too): |
| 102 110 | |
| 103 111 | ```erlang |
| 104 112 | {ok, C} = epgsqla:start_link(), |
| 105 | - Ref = epgsqla:connect(C, "localhost", "username", "psss", [{database, "test_db"}]), |
| 113 | + Ref = epgsqla:connect(C, "localhost", "username", "psss", #{database => "test_db"}), |
| 106 114 | receive |
| 107 115 | {C, Ref, connected} -> |
| 108 116 | {ok, C}; |
| @@ -221,7 +229,7 @@ receive | |
| 221 229 | end. |
| 222 230 | ``` |
| 223 231 | |
| 224 | - ## Extended Query |
| 232 | + ### Extended Query |
| 225 233 | |
| 226 234 | ```erlang |
| 227 235 | {ok, Columns, Rows} = epgsql:equery(C, "select ...", [Parameters]). |
| @@ -267,17 +275,17 @@ end. | |
| 267 275 | `epgsqli:equery(C, Statement, [TypedParameters])` sends same set of messages as |
| 268 276 | squery including final `{C, Ref, done}`. |
| 269 277 | |
| 270 | - ## Prepared Query |
| 278 | + ### Prepared Query |
| 271 279 | ```erlang |
| 272 280 | {ok, Columns, Rows} = epgsql:prepared_query(C, StatementName, [Parameters]). |
| 273 281 | {ok, Count} = epgsql:prepared_query(C, StatementName, [Parameters]). |
| 274 282 | {ok, Count, Columns, Rows} = epgsql:prepared_query(C, StatementName, [Parameters]). |
| 275 | - {error, Error} = epgsql:prepared_equery(C, "non_existent_query", [Parameters]). |
| 283 | + {error, Error} = epgsql:prepared_query(C, "non_existent_query", [Parameters]). |
| 276 284 | ``` |
| 277 285 | `Parameters` - optional list of values to be bound to `$1`, `$2`, `$3`, etc. |
| 278 286 | `StatementName` - name of query given with ```erlang epgsql:parse(C, StatementName, "select ...", []).``` |
| 279 287 | |
| 280 | - With prepared query one can parse a query giving it a name with `epgsql:parse` on start and reuse the name |
| 288 | + With prepared query one can parse a query giving it a name with `epgsql:parse` on start and reuse the name |
| 281 289 | for all further queries with different parameters. |
| 282 290 | ```erlang |
| 283 291 | epgsql:parse(C, "inc", "select $1+1", []). |
| @@ -302,7 +310,7 @@ end. | |
| 302 310 | `epgsqli:prepared_query(C, Statement, [TypedParameters])` sends same set of messages as |
| 303 311 | squery including final `{C, Ref, done}`. |
| 304 312 | |
| 305 | - ## Parse/Bind/Execute |
| 313 | + ### Parse/Bind/Execute |
| 306 314 | |
| 307 315 | ```erlang |
| 308 316 | {ok, Statement} = epgsql:parse(C, [StatementName], Sql, [ParameterTypes]). |
| @@ -360,7 +368,7 @@ All epgsql functions return `{error, Error}` when an error occurs. | |
| 360 368 | `epgsqla`/`epgsqli` modules' `close` and `sync` functions send `{C, Ref, ok}`. |
| 361 369 | |
| 362 370 | |
| 363 | - ## Batch execution |
| 371 | + ### Batch execution |
| 364 372 | |
| 365 373 | Batch execution is `bind` + `execute` for several prepared statements. |
| 366 374 | It uses unnamed portals and `MaxRows = 0`. |
| @@ -420,7 +428,9 @@ PG type | Representation | |
| 420 428 | cidr | `{ip_address(), Mask :: 0..32}` |
| 421 429 | macaddr(8) | tuple of 6 or 8 `byte()` |
| 422 430 | geometry | `ewkb:geometry()` |
| 423 | - |
| 431 | + tsrange | `{{Hour, Minute, Second.Microsecond}, {Hour, Minute, Second.Microsecond}}` |
| 432 | + tstzrange | `{{Hour, Minute, Second.Microsecond}, {Hour, Minute, Second.Microsecond}}` |
| 433 | + daterange | `{{Year, Month, Day}, {Year, Month, Day}}` |
| 424 434 | |
| 425 435 | `timestamp` and `timestamptz` parameters can take `erlang:now()` format: `{MegaSeconds, Seconds, MicroSeconds}` |
| 426 436 | |
| @@ -428,6 +438,9 @@ PG type | Representation | |
| 428 438 | bracket and parentheses respectively. Additionally, infinities are represented by the atoms `minus_infinity` |
| 429 439 | and `plus_infinity` |
| 430 440 | |
| 441 | + `tsrange`, `tstzrange`, `daterange` are range types for `timestamp`, `timestamptz` and `date` |
| 442 | + respectively. They can return `empty` atom as the result from a database if bounds are equal |
| 443 | + |
| 431 444 | ## Errors |
| 432 445 | |
| 433 446 | Errors originating from the PostgreSQL backend are returned as `{error, #error{}}`, |
| @@ -1,5 +1,5 @@ | |
| 1 1 | {<<"name">>,<<"epgsql">>}. |
| 2 | - {<<"version">>,<<"4.1.0">>}. |
| 2 | + {<<"version">>,<<"4.2.0">>}. |
| 3 3 | {<<"requirements">>,#{}}. |
| 4 4 | {<<"app">>,<<"epgsql">>}. |
| 5 5 | {<<"precompiled">>,false}. |
| @@ -7,8 +7,8 @@ | |
| 7 7 | {<<"files">>, |
| 8 8 | [<<"src/epgsql.app.src">>,<<"LICENSE">>,<<"README.md">>, |
| 9 9 | <<"include/epgsql.hrl">>,<<"include/epgsql_geometry.hrl">>, |
| 10 | - <<"include/protocol.hrl">>,<<"rebar.config">>,<<"rebar.config.script">>, |
| 11 | - <<"rebar.lock">>,<<"src/commands/epgsql_cmd_batch.erl">>, |
| 10 | + <<"include/protocol.hrl">>,<<"rebar.config">>,<<"rebar.lock">>, |
| 11 | + <<"src/commands/epgsql_cmd_batch.erl">>, |
| 12 12 | <<"src/commands/epgsql_cmd_bind.erl">>, |
| 13 13 | <<"src/commands/epgsql_cmd_close.erl">>, |
| 14 14 | <<"src/commands/epgsql_cmd_connect.erl">>, |
| @@ -35,6 +35,7 @@ | |
| 35 35 | <<"src/datatypes/epgsql_codec_noop.erl">>, |
| 36 36 | <<"src/datatypes/epgsql_codec_postgis.erl">>, |
| 37 37 | <<"src/datatypes/epgsql_codec_text.erl">>, |
| 38 | + <<"src/datatypes/epgsql_codec_timerange.erl">>, |
| 38 39 | <<"src/datatypes/epgsql_codec_uuid.erl">>,<<"src/epgsql.erl">>, |
| 39 40 | <<"src/epgsql_binary.erl">>,<<"src/epgsql_codec.erl">>, |
| 40 41 | <<"src/epgsql_command.erl">>,<<"src/epgsql_errcodes.erl">>, |
| @@ -1,6 +1,4 @@ | |
| 1 | - {erl_opts, [{platform_define, "^[0-9]+", have_maps}, |
| 2 | - {platform_define, "^(1[89])|^([2-9][0-9])", 'FAST_MAPS'}, % Erlang >=18 |
| 3 | - {platform_define, "^(R|1|20)", 'FUN_STACKTRACE'}]}. % Erlang < 21 |
| 1 | + {erl_opts, [{platform_define, "^17", 'SLOW_MAPS'}]}. % Erlang 17 |
| 4 2 | |
| 5 3 | {eunit_opts, [verbose]}. |
| 6 4 | |
| @@ -22,16 +20,15 @@ | |
| 22 20 | {ct_hooks, [epgsql_cth]} |
| 23 21 | ]}. |
| 24 22 | |
| 25 | - %% See rebar.config.script |
| 26 | - %% {elvis, |
| 27 | - %% [#{dirs => ["src", "src/*"], |
| 28 | - %% include_dirs => ["include"], |
| 29 | - %% filter => "*.erl", |
| 30 | - %% ruleset => erl_files, |
| 31 | - %% rules => |
| 32 | - %% [{elvis_style, line_length, #{limit => 120}}, |
| 33 | - %% {elvis_style, god_modules, #{limit => 40}}, |
| 34 | - %% {elvis_style, state_record_and_type, disable} % epgsql_sock |
| 35 | - %% ]} |
| 36 | - %% ] |
| 37 | - %% }. |
| 23 | + {elvis, |
| 24 | + [#{dirs => ["src", "src/*"], |
| 25 | + include_dirs => ["include"], |
| 26 | + filter => "*.erl", |
| 27 | + ruleset => erl_files, |
| 28 | + rules => |
| 29 | + [{elvis_style, line_length, #{limit => 120}}, |
| 30 | + {elvis_style, god_modules, #{limit => 40}}, |
| 31 | + {elvis_style, state_record_and_type, disable} % epgsql_sock |
| 32 | + ]} |
| 33 | + ] |
| 34 | + }. |
| @@ -1,19 +0,0 @@ | |
| 1 | - %% Elvis config uses maps, but they are not available in Erlang < 17 |
| 2 | - case erlang:is_builtin(erlang, is_map, 1) of |
| 3 | - true -> |
| 4 | - [{elvis, |
| 5 | - [maps:from_list( |
| 6 | - [{dirs, ["src", "src/*"]}, |
| 7 | - {include_dirs, ["include"]}, |
| 8 | - {filter, "*.erl"}, |
| 9 | - {ruleset, erl_files}, |
| 10 | - {rules, |
| 11 | - [{elvis_style, line_length, maps:from_list([{limit, 120}])}, |
| 12 | - {elvis_style, god_modules, maps:from_list([{limit, 40}])}, |
| 13 | - {elvis_style, state_record_and_type, disable} % epgsql_sock |
| 14 | - ]} |
| 15 | - ]) |
| 16 | - ] |
| 17 | - } | CONFIG]; |
| 18 | - false -> CONFIG |
| 19 | - end. |
| @@ -4,6 +4,7 @@ | |
| 4 4 | %%% |
| 5 5 | -module(epgsql_cmd_connect). |
| 6 6 | -behaviour(epgsql_command). |
| 7 | + -export([hide_password/1, opts_hide_password/1]). |
| 7 8 | -export([init/1, execute/2, handle_message/4]). |
| 8 9 | -export_type([response/0, connect_error/0]). |
| 9 10 | |
| @@ -27,7 +28,7 @@ | |
| 27 28 | | unknown). |
| 28 29 | |
| 29 30 | -record(connect, |
| 30 | - {opts :: list(), |
| 31 | + {opts :: map(), |
| 31 32 | auth_fun :: auth_fun() | undefined, |
| 32 33 | auth_state :: any() | undefined, |
| 33 34 | auth_send :: {integer(), iodata()} | undefined, |
| @@ -41,19 +42,14 @@ | |
| 41 42 | -define(AUTH_SASL_CONTINUE, 11). |
| 42 43 | -define(AUTH_SASL_FINAL, 12). |
| 43 44 | |
| 44 | - init({Host, Username, Password, Opts}) -> |
| 45 | - Opts1 = [{host, Host}, |
| 46 | - {username, Username}, |
| 47 | - {password, Password} |
| 48 | - | Opts], |
| 49 | - #connect{opts = Opts1}. |
| 45 | + init(#{host := _, username := _} = Opts) -> |
| 46 | + #connect{opts = Opts}. |
| 50 47 | |
| 51 48 | execute(PgSock, #connect{opts = Opts, stage = connect} = State) -> |
| 52 | - Host = get_val(host, Opts), |
| 53 | - Username = get_val(username, Opts), |
| 54 | - %% _ = get_val(password, Opts), |
| 55 | - Timeout = proplists:get_value(timeout, Opts, 5000), |
| 56 | - Port = proplists:get_value(port, Opts, 5432), |
| 49 | + #{host := Host, |
| 50 | + username := Username} = Opts, |
| 51 | + Timeout = maps:get(timeout, Opts, 5000), |
| 52 | + Port = maps:get(port, Opts, 5432), |
| 57 53 | SockOpts = [{active, false}, {packet, raw}, binary, {nodelay, true}, {keepalive, true}], |
| 58 54 | case gen_tcp:connect(Host, Port, SockOpts, Timeout) of |
| 59 55 | {ok, Sock} -> |
| @@ -67,29 +63,27 @@ execute(PgSock, #connect{opts = Opts, stage = connect} = State) -> | |
| 67 63 | inet:getopts(Sock, [recbuf, sndbuf]), |
| 68 64 | inet:setopts(Sock, [{buffer, max(RecBufSize, SndBufSize)}]), |
| 69 65 | |
| 70 | - PgSock1 = maybe_ssl(Sock, proplists:get_value(ssl, Opts, false), Opts, PgSock), |
| 66 | + PgSock1 = maybe_ssl(Sock, maps:get(ssl, Opts, false), Opts, PgSock), |
| 71 67 | |
| 72 68 | Opts2 = ["user", 0, Username, 0], |
| 73 | - Opts3 = case proplists:get_value(database, Opts, undefined) of |
| 74 | - undefined -> Opts2; |
| 75 | - Database -> [Opts2 | ["database", 0, Database, 0]] |
| 69 | + Opts3 = case maps:find(database, Opts) of |
| 70 | + error -> Opts2; |
| 71 | + {ok, Database} -> [Opts2 | ["database", 0, Database, 0]] |
| 76 72 | end, |
| 77 73 | |
| 78 | - Replication = proplists:get_value(replication, Opts, undefined), |
| 79 | - Opts4 = case Replication of |
| 80 | - undefined -> Opts3; |
| 81 | - Replication -> |
| 82 | - [Opts3 | ["replication", 0, Replication, 0]] |
| 74 | + {Opts4, PgSock2} = |
| 75 | + case Opts of |
| 76 | + #{replication := Replication} -> |
| 77 | + {[Opts3 | ["replication", 0, Replication, 0]], |
| 78 | + epgsql_sock:init_replication_state(PgSock1)}; |
| 79 | + _ -> {Opts3, PgSock1} |
| 83 80 | end, |
| 84 | - PgSock2 = case Replication of |
| 85 | - undefined -> PgSock1; |
| 86 | - _ -> epgsql_sock:init_replication_state(PgSock1) |
| 87 | - end, |
| 88 81 | |
| 89 82 | epgsql_sock:send(PgSock2, [<<196608:?int32>>, Opts4, 0]), |
| 90 | - PgSock3 = case proplists:get_value(async, Opts, undefined) of |
| 91 | - undefined -> PgSock2; |
| 92 | - Async -> epgsql_sock:set_attr(async, Async, PgSock2) |
| 83 | + PgSock3 = case Opts of |
| 84 | + #{async := Async} -> |
| 85 | + epgsql_sock:set_attr(async, Async, PgSock2); |
| 86 | + _ -> PgSock2 |
| 93 87 | end, |
| 94 88 | {ok, PgSock3, State#connect{stage = maybe_auth}}; |
| 95 89 | {error, Reason} = Error -> |
| @@ -100,15 +94,35 @@ execute(PgSock, #connect{stage = auth, auth_send = {PacketId, Data}} = St) -> | |
| 100 94 | {ok, PgSock, St#connect{auth_send = undefined}}. |
| 101 95 | |
| 102 96 | |
| 97 | + %% @doc Replace `password' in Opts map with obfuscated one |
| 98 | + opts_hide_password(#{password := Password} = Opts) -> |
| 99 | + HiddenPassword = hide_password(Password), |
| 100 | + Opts#{password => HiddenPassword}; |
| 101 | + opts_hide_password(Opts) -> Opts. |
| 102 | + |
| 103 | + |
| 104 | + %% @doc this function wraps plaintext password to a lambda function, so, if |
| 105 | + %% epgsql_sock process crashes when executing `connect` command, password will |
| 106 | + %% not appear in a crash log |
| 107 | + -spec hide_password(iodata()) -> fun( () -> iodata() ). |
| 108 | + hide_password(Password) when is_list(Password); |
| 109 | + is_binary(Password) -> |
| 110 | + fun() -> |
| 111 | + Password |
| 112 | + end; |
| 113 | + hide_password(PasswordFun) when is_function(PasswordFun, 0) -> |
| 114 | + PasswordFun. |
| 115 | + |
| 116 | + |
| 103 117 | maybe_ssl(S, false, _, PgSock) -> |
| 104 118 | epgsql_sock:set_net_socket(gen_tcp, S, PgSock); |
| 105 119 | maybe_ssl(S, Flag, Opts, PgSock) -> |
| 106 120 | ok = gen_tcp:send(S, <<8:?int32, 80877103:?int32>>), |
| 107 | - Timeout = proplists:get_value(timeout, Opts, 5000), |
| 121 | + Timeout = maps:get(timeout, Opts, 5000), |
| 108 122 | {ok, <<Code>>} = gen_tcp:recv(S, 1, Timeout), |
| 109 123 | case Code of |
| 110 124 | $S -> |
| 111 | - SslOpts = proplists:get_value(ssl_opts, Opts, []), |
| 125 | + SslOpts = maps:get(ssl_opts, Opts, []), |
| 112 126 | case ssl:connect(S, SslOpts, Timeout) of |
| 113 127 | {ok, S2} -> |
| 114 128 | epgsql_sock:set_net_socket(ssl, S2, PgSock); |
| @@ -167,14 +181,14 @@ auth_handle(Data, PgSock, #connect{auth_fun = Fun, auth_state = AuthSt} = St) -> | |
| 167 181 | |
| 168 182 | %% AuthenticationCleartextPassword |
| 169 183 | auth_cleartext(init, _AuthState, #connect{opts = Opts}) -> |
| 170 | - Password = get_val(password, Opts), |
| 184 | + Password = get_password(Opts), |
| 171 185 | {send, ?PASSWORD, [Password, 0], undefined}; |
| 172 186 | auth_cleartext(_, _, _) -> unknown. |
| 173 187 | |
| 174 188 | %% AuthenticationMD5Password |
| 175 189 | auth_md5(init, Salt, #connect{opts = Opts}) -> |
| 176 | - User = get_val(username, Opts), |
| 177 | - Password = get_val(password, Opts), |
| 190 | + User = maps:get(username, Opts), |
| 191 | + Password = get_password(Opts), |
| 178 192 | Digest1 = hex(erlang:md5([Password, User])), |
| 179 193 | Str = ["md5", hex(erlang:md5([Digest1, Salt])), 0], |
| 180 194 | {send, ?PASSWORD, Str, undefined}; |
| @@ -182,14 +196,14 @@ auth_md5(_, _, _) -> unknown. | |
| 182 196 | |
| 183 197 | %% AuthenticationSASL |
| 184 198 | auth_scram(init, undefined, #connect{opts = Opts}) -> |
| 185 | - User = get_val(username, Opts), |
| 199 | + User = maps:get(username, Opts), |
| 186 200 | Nonce = epgsql_scram:get_nonce(16), |
| 187 201 | ClientFirst = epgsql_scram:get_client_first(User, Nonce), |
| 188 202 | SaslInitialResponse = [?SCRAM_AUTH_METHOD, 0, <<(iolist_size(ClientFirst)):?int32>>, ClientFirst], |
| 189 203 | {send, ?SASL_ANY_RESPONSE, SaslInitialResponse, {auth_request, Nonce}}; |
| 190 204 | auth_scram(<<?AUTH_SASL_CONTINUE:?int32, ServerFirst/binary>>, {auth_request, Nonce}, #connect{opts = Opts}) -> |
| 191 | - User = get_val(username, Opts), |
| 192 | - Password = get_val(password, Opts), |
| 205 | + User = maps:get(username, Opts), |
| 206 | + Password = get_password(Opts), |
| 193 207 | ServerFirstParts = epgsql_scram:parse_server_first(ServerFirst, Nonce), |
| 194 208 | {ClientFinalMessage, ServerProof} = epgsql_scram:get_client_final(ServerFirstParts, Nonce, User, Password), |
| 195 209 | {send, ?SASL_ANY_RESPONSE, ClientFinalMessage, {server_final, ServerProof}}; |
| @@ -243,10 +257,10 @@ handle_message(_, _, _, _) -> | |
| 243 257 | unknown. |
| 244 258 | |
| 245 259 | |
| 246 | - get_val(Key, Proplist) -> |
| 247 | - Val = proplists:get_value(Key, Proplist), |
| 248 | - (Val =/= undefined) orelse error({required_option, Key}), |
| 249 | - Val. |
| 260 | + get_password(Opts) -> |
| 261 | + PasswordFun = maps:get(password, Opts), |
| 262 | + PasswordFun(). |
| 263 | + |
| 250 264 | |
| 251 265 | hex(Bin) -> |
| 252 266 | HChar = fun(N) when N < 10 -> $0 + N; |
Loading more files…