Просмотр исходного кода

Merge pull request #108 from seriyps/map_options

Make it possible to pass connect options as `map()`
Sergey Prokhorov 8 лет назад
Родитель
Сommit
2636198ba3
8 измененных файлов с 111 добавлено и 28 удалено
  1. 3 1
      README.md
  2. 2 0
      rebar.config
  3. 44 9
      src/epgsql.erl
  4. 9 2
      src/epgsqla.erl
  5. 9 2
      src/epgsqli.erl
  6. 15 7
      test/epgsql_cast.erl
  7. 15 7
      test/epgsql_incremental.erl
  8. 14 0
      test/epgsql_tests.erl

+ 3 - 1
README.md

@@ -71,7 +71,7 @@ see `CHANGES` for full list.
     {async,    Receiver   :: pid() | atom()}       | % process to receive LISTEN/NOTIFY msgs
     {async,    Receiver   :: pid() | atom()}       | % process to receive LISTEN/NOTIFY msgs
     {replication, Replication :: string()}. % Pass "database" to connect in replication mode
     {replication, Replication :: string()}. % Pass "database" to connect in replication mode
     
     
--spec connect(host(), string(), string(), [connect_option()])
+-spec connect(host(), string(), string(), [connect_option()] | map())
         -> {ok, Connection :: connection()} | {error, Reason :: connect_error()}.    
         -> {ok, Connection :: connection()} | {error, Reason :: connect_error()}.    
 %% @doc connects to Postgres
 %% @doc connects to Postgres
 %% where
 %% where
@@ -95,6 +95,8 @@ ok = epgsql:close(C).
 The `{timeout, TimeoutMs}` parameter will trigger an `{error, timeout}` result when the
 The `{timeout, TimeoutMs}` parameter will trigger an `{error, timeout}` result when the
 socket fails to connect within `TimeoutMs` milliseconds.
 socket fails to connect within `TimeoutMs` milliseconds.
 
 
+Options may be passed as map with the same key names, if your VM version supports maps.
+
 Asynchronous connect example (applies to **epgsqli** too):
 Asynchronous connect example (applies to **epgsqli** too):
 
 
 ```erlang
 ```erlang

+ 2 - 0
rebar.config

@@ -1,3 +1,5 @@
+{erl_opts, [{platform_define, "^[0-9]+", have_maps}]}.
+
 {eunit_opts, [verbose]}.
 {eunit_opts, [verbose]}.
 
 
 {cover_enabled, true}.
 {cover_enabled, true}.

+ 44 - 9
src/epgsql.erl

@@ -24,9 +24,10 @@
          sync_on_error/2,
          sync_on_error/2,
          standby_status_update/3,
          standby_status_update/3,
          start_replication/5,
          start_replication/5,
-         start_replication/6]).
+         start_replication/6,
+         to_proplist/1]).
 
 
--export_type([connection/0, connect_option/0,
+-export_type([connection/0, connect_option/0, connect_opts/0,
               connect_error/0, query_error/0,
               connect_error/0, query_error/0,
               sql_query/0, bind_param/0, typed_param/0,
               sql_query/0, bind_param/0, typed_param/0,
               squery_row/0, equery_row/0, reply/1]).
               squery_row/0, equery_row/0, reply/1]).
@@ -37,15 +38,39 @@
 -type host() :: inet:ip_address() | inet:hostname().
 -type host() :: inet:ip_address() | inet:hostname().
 -type connection() :: pid().
 -type connection() :: pid().
 -type connect_option() ::
 -type connect_option() ::
+    {host, host()}                                 |
+    {username, string()}                           |
+    {password, string()}                           |
     {database, DBName     :: string()}             |
     {database, DBName     :: string()}             |
     {port,     PortNum    :: inet:port_number()}   |
     {port,     PortNum    :: inet:port_number()}   |
     {ssl,      IsEnabled  :: boolean() | required} |
     {ssl,      IsEnabled  :: boolean() | required} |
     {ssl_opts, SslOptions :: [ssl:ssl_option()]}   | % @see OTP ssl app, ssl_api.hrl
     {ssl_opts, SslOptions :: [ssl:ssl_option()]}   | % @see OTP ssl app, ssl_api.hrl
     {timeout,  TimeoutMs  :: timeout()}            | % default: 5000 ms
     {timeout,  TimeoutMs  :: timeout()}            | % default: 5000 ms
-    {async,    Receiver   :: pid()}                | % process to receive LISTEN/NOTIFY msgs
+    {async,    Receiver   :: pid() | atom()}       | % process to receive LISTEN/NOTIFY msgs
     {replication, Replication :: string()}. % Pass "database" to connect in replication mode
     {replication, Replication :: string()}. % Pass "database" to connect in replication mode
 
 
--type connect_error() :: #error{}.
+-ifdef(have_maps).
+-type connect_opts() ::
+        [connect_option()]
+      | #{host => host(),
+          username => string(),
+          password => string(),
+          database => string(),
+          port => inet:port_number(),
+          ssl => boolean() | required,
+          ssl_opts => [ssl:ssl_option()],
+          timeout => timeout(),
+          async => pid(),
+          replication => string()}.
+-else.
+-type connect_opts() :: [connect_option()].
+-endif.
+
+-type connect_error() ::
+        #error{}
+      | {unsupported_auth_method, atom()}
+      | invalid_authorization_specification
+      | invalid_password.
 -type query_error() :: #error{}.
 -type query_error() :: #error{}.
 
 
 -type bind_param() ::
 -type bind_param() ::
@@ -84,7 +109,10 @@
 %% -------------
 %% -------------
 
 
 %% -- client interface --
 %% -- client interface --
-connect(Settings) ->
+-spec connect(connect_opts())
+        -> {ok, Connection :: connection()} | {error, Reason :: connect_error()}.
+connect(Settings0) ->
+    Settings = to_proplist(Settings0),
 	Host = proplists:get_value(host, Settings, "localhost"),
 	Host = proplists:get_value(host, Settings, "localhost"),
 	Username = proplists:get_value(username, Settings, os:getenv("USER")),
 	Username = proplists:get_value(username, Settings, os:getenv("USER")),
 	Password = proplists:get_value(password, Settings, ""),
 	Password = proplists:get_value(password, Settings, ""),
@@ -96,7 +124,7 @@ connect(Host, Opts) ->
 connect(Host, Username, Opts) ->
 connect(Host, Username, Opts) ->
     connect(Host, Username, "", Opts).
     connect(Host, Username, "", Opts).
 
 
--spec connect(host(), string(), string(), [connect_option()])
+-spec connect(host(), string(), string(), connect_opts())
         -> {ok, Connection :: connection()} | {error, Reason :: connect_error()}.
         -> {ok, Connection :: connection()} | {error, Reason :: connect_error()}.
 %% @doc connects to Postgres
 %% @doc connects to Postgres
 %% where
 %% where
@@ -109,9 +137,10 @@ connect(Host, Username, Password, Opts) ->
     {ok, C} = epgsql_sock:start_link(),
     {ok, C} = epgsql_sock:start_link(),
     connect(C, Host, Username, Password, Opts).
     connect(C, Host, Username, Password, Opts).
 
 
--spec connect(connection(), host(), string(), string(), [connect_option()])
+-spec connect(connection(), host(), string(), string(), connect_opts())
         -> {ok, Connection :: connection()} | {error, Reason :: connect_error()}.
         -> {ok, Connection :: connection()} | {error, Reason :: connect_error()}.
-connect(C, Host, Username, Password, Opts) ->
+connect(C, Host, Username, Password, Opts0) ->
+    Opts = to_proplist(Opts0),
     %% TODO connect timeout
     %% TODO connect timeout
     case gen_server:call(C,
     case gen_server:call(C,
                          {connect, Host, Username, Password, Opts},
                          {connect, Host, Username, Password, Opts},
@@ -315,4 +344,10 @@ standby_status_update(Connection, FlushedLSN, AppliedLSN) ->
 start_replication(Connection, ReplicationSlot, Callback, CbInitState, WALPosition, PluginOpts) ->
 start_replication(Connection, ReplicationSlot, Callback, CbInitState, WALPosition, PluginOpts) ->
     gen_server:call(Connection, {start_replication, ReplicationSlot, Callback, CbInitState, WALPosition, PluginOpts}).
     gen_server:call(Connection, {start_replication, ReplicationSlot, Callback, CbInitState, WALPosition, PluginOpts}).
 start_replication(Connection, ReplicationSlot, Callback, CbInitState, WALPosition) ->
 start_replication(Connection, ReplicationSlot, Callback, CbInitState, WALPosition) ->
-    start_replication(Connection, ReplicationSlot, Callback, CbInitState, WALPosition, []).
+    start_replication(Connection, ReplicationSlot, Callback, CbInitState, WALPosition, []).
+
+%% @private
+to_proplist(List) when is_list(List) ->
+    List;
+to_proplist(Map) ->
+    maps:to_list(Map).

+ 9 - 2
src/epgsqla.erl

@@ -3,7 +3,7 @@
 -module(epgsqla).
 -module(epgsqla).
 
 
 -export([start_link/0,
 -export([start_link/0,
-         connect/2, connect/3, connect/4, connect/5,
+         connect/1, connect/2, connect/3, connect/4, connect/5,
          close/1,
          close/1,
          get_parameter/2,
          get_parameter/2,
          set_notice_receiver/2,
          set_notice_receiver/2,
@@ -27,6 +27,13 @@
 start_link() ->
 start_link() ->
     epgsql_sock:start_link().
     epgsql_sock:start_link().
 
 
+connect(Opts) ->
+    Settings = epgsql:to_proplist(Opts),
+    Host = proplists:get_value(host, Settings, "localhost"),
+    Username = proplists:get_value(username, Settings, os:getenv("USER")),
+    Password = proplists:get_value(password, Settings, ""),
+    connect(Host, Username, Password, Settings).
+
 connect(Host, Opts) ->
 connect(Host, Opts) ->
     connect(Host, os:getenv("USER"), "", Opts).
     connect(Host, os:getenv("USER"), "", Opts).
 
 
@@ -40,7 +47,7 @@ connect(Host, Username, Password, Opts) ->
 -spec connect(epgsql:connection(), inet:ip_address() | inet:hostname(),
 -spec connect(epgsql:connection(), inet:ip_address() | inet:hostname(),
               string(), string(), [epgsql:connect_option()]) -> reference().
               string(), string(), [epgsql:connect_option()]) -> reference().
 connect(C, Host, Username, Password, Opts) ->
 connect(C, Host, Username, Password, Opts) ->
-    complete_connect(C, cast(C, {connect, Host, Username, Password, Opts})).
+    complete_connect(C, cast(C, {connect, Host, Username, Password, epgsql:to_proplist(Opts)})).
 
 
 -spec close(epgsql:connection()) -> ok.
 -spec close(epgsql:connection()) -> ok.
 close(C) ->
 close(C) ->

+ 9 - 2
src/epgsqli.erl

@@ -3,7 +3,7 @@
 -module(epgsqli).
 -module(epgsqli).
 
 
 -export([start_link/0,
 -export([start_link/0,
-         connect/2, connect/3, connect/4, connect/5,
+         connect/1, connect/2, connect/3, connect/4, connect/5,
          close/1,
          close/1,
          get_parameter/2,
          get_parameter/2,
          set_notice_receiver/2,
          set_notice_receiver/2,
@@ -26,6 +26,13 @@
 start_link() ->
 start_link() ->
     epgsql_sock:start_link().
     epgsql_sock:start_link().
 
 
+connect(Opts) ->
+    Settings = epgsql:to_proplist(Opts),
+    Host = proplists:get_value(host, Settings, "localhost"),
+    Username = proplists:get_value(username, Settings, os:getenv("USER")),
+    Password = proplists:get_value(password, Settings, ""),
+    connect(Host, Username, Password, Settings).
+
 connect(Host, Opts) ->
 connect(Host, Opts) ->
     connect(Host, os:getenv("USER"), "", Opts).
     connect(Host, os:getenv("USER"), "", Opts).
 
 
@@ -39,7 +46,7 @@ connect(Host, Username, Password, Opts) ->
 -spec connect(epgsql:connection(), inet:ip_address() | inet:hostname(),
 -spec connect(epgsql:connection(), inet:ip_address() | inet:hostname(),
               string(), string(), [epgsql:connect_option()]) -> reference().
               string(), string(), [epgsql:connect_option()]) -> reference().
 connect(C, Host, Username, Password, Opts) ->
 connect(C, Host, Username, Password, Opts) ->
-    epgsqla:complete_connect(C, incremental(C, {connect, Host, Username, Password, Opts})).
+    epgsqla:complete_connect(C, incremental(C, {connect, Host, Username, Password, epgsql:to_proplist(Opts)})).
 
 
 -spec close(epgsql:connection()) -> ok.
 -spec close(epgsql:connection()) -> ok.
 close(C) ->
 close(C) ->

+ 15 - 7
test/epgsql_cast.erl

@@ -5,7 +5,7 @@
 
 
 -module(epgsql_cast).
 -module(epgsql_cast).
 
 
--export([connect/2, connect/3, connect/4, close/1]).
+-export([connect/1, connect/2, connect/3, connect/4, close/1]).
 -export([get_parameter/2, set_notice_receiver/2, squery/2, equery/2, equery/3]).
 -export([get_parameter/2, set_notice_receiver/2, squery/2, equery/2, equery/3]).
 -export([prepared_query/3]).
 -export([prepared_query/3]).
 -export([parse/2, parse/3, parse/4, describe/2, describe/3]).
 -export([parse/2, parse/3, parse/4, describe/2, describe/3]).
@@ -18,22 +18,30 @@
 
 
 %% -- client interface --
 %% -- client interface --
 
 
+connect(Opts) ->
+    Ref = epgsqla:connect(Opts),
+    await_connect(Ref).
+
 connect(Host, Opts) ->
 connect(Host, Opts) ->
-    connect(Host, os:getenv("USER"), "", Opts).
+    Ref = epgsqla:connect(Host, Opts),
+    await_connect(Ref).
 
 
 connect(Host, Username, Opts) ->
 connect(Host, Username, Opts) ->
-    connect(Host, Username, "", Opts).
+    Ref = epgsqla:connect(Host, Username, Opts),
+    await_connect(Ref).
 
 
 connect(Host, Username, Password, Opts) ->
 connect(Host, Username, Password, Opts) ->
-    {ok, C} = epgsql_sock:start_link(),
-    Ref = epgsqla:connect(C, Host, Username, Password, Opts),
+    Ref = epgsqla:connect(Host, Username, Password, Opts),
     %% TODO connect timeout
     %% TODO connect timeout
+    await_connect(Ref).
+
+await_connect(Ref) ->
     receive
     receive
         {C, Ref, connected} ->
         {C, Ref, connected} ->
             {ok, C};
             {ok, C};
-        {C, Ref, Error = {error, _}} ->
+        {_C, Ref, Error = {error, _}} ->
             Error;
             Error;
-        {'EXIT', C, _Reason} ->
+        {'EXIT', _C, _Reason} ->
             {error, closed}
             {error, closed}
     end.
     end.
 
 

+ 15 - 7
test/epgsql_incremental.erl

@@ -5,7 +5,7 @@
 
 
 -module(epgsql_incremental).
 -module(epgsql_incremental).
 
 
--export([connect/2, connect/3, connect/4, close/1]).
+-export([connect/1, connect/2, connect/3, connect/4, close/1]).
 -export([get_parameter/2, set_notice_receiver/2, squery/2, equery/2, equery/3]).
 -export([get_parameter/2, set_notice_receiver/2, squery/2, equery/2, equery/3]).
 -export([prepared_query/3]).
 -export([prepared_query/3]).
 -export([parse/2, parse/3, parse/4, describe/2, describe/3]).
 -export([parse/2, parse/3, parse/4, describe/2, describe/3]).
@@ -17,21 +17,29 @@
 
 
 %% -- client interface --
 %% -- client interface --
 
 
+connect(Opts) ->
+    Ref = epgsqli:connect(Opts),
+    await_connect(Ref).
+
 connect(Host, Opts) ->
 connect(Host, Opts) ->
-    connect(Host, os:getenv("USER"), "", Opts).
+    Ref = epgsqli:connect(Host, Opts),
+    await_connect(Ref).
 
 
 connect(Host, Username, Opts) ->
 connect(Host, Username, Opts) ->
-    connect(Host, Username, "", Opts).
+    Ref = epgsqli:connect(Host, Username, Opts),
+    await_connect(Ref).
 
 
 connect(Host, Username, Password, Opts) ->
 connect(Host, Username, Password, Opts) ->
-    {ok, C} = epgsql_sock:start_link(),
-    Ref = epgsqli:connect(C, Host, Username, Password, Opts),
+    Ref = epgsqli:connect(Host, Username, Password, Opts),
+    await_connect(Ref).
+
+await_connect(Ref) ->
     receive
     receive
         {C, Ref, connected} ->
         {C, Ref, connected} ->
             {ok, C};
             {ok, C};
-        {C, Ref, Error = {error, _}} ->
+        {_C, Ref, Error = {error, _}} ->
             Error;
             Error;
-        {'EXIT', C, _Reason} ->
+        {'EXIT', _C, _Reason} ->
             {error, closed}
             {error, closed}
     end.
     end.
 
 

+ 14 - 0
test/epgsql_tests.erl

@@ -106,6 +106,20 @@ connect_with_client_cert_test(Module) ->
       "epgsql_test_cert",
       "epgsql_test_cert",
       [{ssl, true}, {keyfile, File("epgsql.key")}, {certfile, File("epgsql.crt")}]).
       [{ssl, true}, {keyfile, File("epgsql.key")}, {certfile, File("epgsql.crt")}]).
 
 
+-ifdef(have_maps).
+connect_map_test(Module) ->
+    Opts = #{host => ?host,
+             port => ?port,
+             database => "epgsql_test_db1",
+             username => "epgsql_test_md5",
+             password => "epgsql_test_md5"
+            },
+    {ok, C} = Module:connect(Opts),
+    Module:close(C),
+    flush(),
+    ok.
+-endif.
+
 prepared_query_test(Module) ->
 prepared_query_test(Module) ->
   with_connection(
   with_connection(
     Module,
     Module,