Browse Source

Merge pull request #10 from seriyps/typespecs

Typespecs added
David N. Welton 11 years ago
parent
commit
9bffb8fecb
5 changed files with 100 additions and 5 deletions
  1. 1 1
      Makefile
  2. 17 3
      include/pgsql.hrl
  3. 12 1
      src/apgsql.erl
  4. 11 0
      src/ipgsql.erl
  5. 59 0
      src/pgsql.erl

+ 1 - 1
Makefile

@@ -18,6 +18,6 @@ dialyzer: build.plt compile
 	dialyzer --plt $< ebin
 	dialyzer --plt $< ebin
 
 
 build.plt:
 build.plt:
-	dialyzer -q --build_plt --apps kernel stdlib ssl --output_plt $@
+	dialyzer -q --build_plt --apps erts kernel stdlib ssl --output_plt $@
 
 
 .PHONY: all compile release clean create_testdbs test dialyzer build.plt
 .PHONY: all compile release clean create_testdbs test dialyzer build.plt

+ 17 - 3
include/pgsql.hrl

@@ -1,4 +1,18 @@
--record(column,    {name, type, size, modifier, format}).
--record(statement, {name, columns, types}).
+-type epgsql_type() :: atom() | {array, atom()} | {unknown_oid, integer()}.
 
 
--record(error,  {severity, code, message, extra}).
+-record(column,    {name :: binary(),
+                    type :: epgsql_type(),
+                    size :: -1 | pos_integer(),
+                    modifier :: -1 | pos_integer(),
+                    format :: integer()}).
+
+-record(statement, {name :: string(),
+                    columns :: [#column{}],
+                    types :: [epgsql_type()]}).
+
+-record(error,  {severity :: fatal | error | atom(), %TODO: concretize
+                 code :: binary(),
+                 message :: binary(),
+                 extra :: [{detail, binary()}
+                           | {hint, binary()}
+                           | {position, binary()}]}).

+ 12 - 1
src/apgsql.erl

@@ -20,7 +20,7 @@
 -include("pgsql.hrl").
 -include("pgsql.hrl").
 
 
 %% -- client interface --
 %% -- client interface --
-
+-spec start_link() -> {ok, pid()}.
 start_link() ->
 start_link() ->
     pgsql_sock:start_link().
     pgsql_sock:start_link().
 
 
@@ -34,21 +34,27 @@ connect(Host, Username, Password, Opts) ->
     {ok, C} = pgsql_sock:start_link(),
     {ok, C} = pgsql_sock:start_link(),
     connect(C, Host, Username, Password, Opts).
     connect(C, Host, Username, Password, Opts).
 
 
+-spec connect(pgsql:connection(), inet:ip_address() | inet:hostname(),
+              string(), string(), [pgsql:connect_option()]) -> reference().
 connect(C, Host, Username, Password, Opts) ->
 connect(C, Host, Username, Password, Opts) ->
     cast(C, {connect, Host, Username, Password, Opts}).
     cast(C, {connect, Host, Username, Password, Opts}).
 
 
+-spec close(pgsql:connection()) -> ok.
 close(C) ->
 close(C) ->
     pgsql_sock:close(C).
     pgsql_sock:close(C).
 
 
+-spec get_parameter(pgsql:connection(), binary()) -> binary() | undefined.
 get_parameter(C, Name) ->
 get_parameter(C, Name) ->
     pgsql_sock:get_parameter(C, Name).
     pgsql_sock:get_parameter(C, Name).
 
 
+-spec squery(pgsql:connection(), string()) -> reference().
 squery(C, Sql) ->
 squery(C, Sql) ->
     cast(C, {squery, Sql}).
     cast(C, {squery, Sql}).
 
 
 equery(C, Sql) ->
 equery(C, Sql) ->
     equery(C, Sql, []).
     equery(C, Sql, []).
 
 
+-spec equery(pgsql:connection(), #statement{}, [pgsql:bind_param()]) -> reference().
 equery(C, Statement, Parameters) ->
 equery(C, Statement, Parameters) ->
     cast(C, {equery, Statement, Parameters}).
     cast(C, {equery, Statement, Parameters}).
 
 
@@ -58,12 +64,14 @@ parse(C, Sql) ->
 parse(C, Sql, Types) ->
 parse(C, Sql, Types) ->
     parse(C, "", Sql, Types).
     parse(C, "", Sql, Types).
 
 
+-spec parse(pgsql:connection(), iolist(), string(), [epgsql_type()]) -> reference().
 parse(C, Name, Sql, Types) ->
 parse(C, Name, Sql, Types) ->
     cast(C, {parse, Name, Sql, Types}).
     cast(C, {parse, Name, Sql, Types}).
 
 
 bind(C, Statement, Parameters) ->
 bind(C, Statement, Parameters) ->
     bind(C, Statement, "", Parameters).
     bind(C, Statement, "", Parameters).
 
 
+-spec bind(pgsql:connection(), #statement{}, string(), [pgsql:bind_param()]) -> reference().
 bind(C, Statement, PortalName, Parameters) ->
 bind(C, Statement, PortalName, Parameters) ->
     cast(C, {bind, Statement, PortalName, Parameters}).
     cast(C, {bind, Statement, PortalName, Parameters}).
 
 
@@ -73,9 +81,11 @@ execute(C, S) ->
 execute(C, S, N) ->
 execute(C, S, N) ->
     execute(C, S, "", N).
     execute(C, S, "", N).
 
 
+-spec execute(pgsql:connection(), #statement{}, string(), non_neg_integer()) -> reference().
 execute(C, Statement, PortalName, MaxRows) ->
 execute(C, Statement, PortalName, MaxRows) ->
     cast(C, {execute, Statement, PortalName, MaxRows}).
     cast(C, {execute, Statement, PortalName, MaxRows}).
 
 
+-spec execute_batch(pgsql:connection(), [{#statement{}, [pgsql:bind_param()]}]) -> reference().
 execute_batch(C, Batch) ->
 execute_batch(C, Batch) ->
     cast(C, {execute_batch, Batch}).
     cast(C, {execute_batch, Batch}).
 
 
@@ -97,6 +107,7 @@ close(C, Type, Name) ->
 sync(C) ->
 sync(C) ->
     cast(C, sync).
     cast(C, sync).
 
 
+-spec cancel(pgsql:connection()) -> ok.
 cancel(C) ->
 cancel(C) ->
     pgsql_sock:cancel(C).
     pgsql_sock:cancel(C).
 
 

+ 11 - 0
src/ipgsql.erl

@@ -34,21 +34,27 @@ connect(Host, Username, Password, Opts) ->
     {ok, C} = pgsql_sock:start_link(),
     {ok, C} = pgsql_sock:start_link(),
     connect(C, Host, Username, Password, Opts).
     connect(C, Host, Username, Password, Opts).
 
 
+-spec connect(pgsql:connection(), inet:ip_address() | inet:hostname(),
+              string(), string(), [pgsql:connect_option()]) -> reference().
 connect(C, Host, Username, Password, Opts) ->
 connect(C, Host, Username, Password, Opts) ->
     incremental(C, {connect, Host, Username, Password, Opts}).
     incremental(C, {connect, Host, Username, Password, Opts}).
 
 
+-spec close(pgsql:connection()) -> ok.
 close(C) ->
 close(C) ->
     pgsql_sock:close(C).
     pgsql_sock:close(C).
 
 
+-spec get_parameter(pgsql:connection(), binary()) -> binary() | undefined.
 get_parameter(C, Name) ->
 get_parameter(C, Name) ->
     pgsql_sock:get_parameter(C, Name).
     pgsql_sock:get_parameter(C, Name).
 
 
+-spec squery(pgsql:connection(), string()) -> reference().
 squery(C, Sql) ->
 squery(C, Sql) ->
     incremental(C, {squery, Sql}).
     incremental(C, {squery, Sql}).
 
 
 equery(C, Sql) ->
 equery(C, Sql) ->
     equery(C, Sql, []).
     equery(C, Sql, []).
 
 
+-spec equery(pgsql:connection(), #statement{}, [pgsql:bind_param()]) -> reference().
 equery(C, Statement, Parameters) ->
 equery(C, Statement, Parameters) ->
     incremental(C, {equery, Statement, Parameters}).
     incremental(C, {equery, Statement, Parameters}).
 
 
@@ -58,12 +64,14 @@ parse(C, Sql) ->
 parse(C, Sql, Types) ->
 parse(C, Sql, Types) ->
     parse(C, "", Sql, Types).
     parse(C, "", Sql, Types).
 
 
+-spec parse(pgsql:connection(), iolist(), string(), [epgsql_type()]) -> reference().
 parse(C, Name, Sql, Types) ->
 parse(C, Name, Sql, Types) ->
     incremental(C, {parse, Name, Sql, Types}).
     incremental(C, {parse, Name, Sql, Types}).
 
 
 bind(C, Statement, Parameters) ->
 bind(C, Statement, Parameters) ->
     bind(C, Statement, "", Parameters).
     bind(C, Statement, "", Parameters).
 
 
+-spec bind(pgsql:connection(), #statement{}, string(), [pgsql:bind_param()]) -> reference().
 bind(C, Statement, PortalName, Parameters) ->
 bind(C, Statement, PortalName, Parameters) ->
     incremental(C, {bind, Statement, PortalName, Parameters}).
     incremental(C, {bind, Statement, PortalName, Parameters}).
 
 
@@ -73,9 +81,11 @@ execute(C, S) ->
 execute(C, S, N) ->
 execute(C, S, N) ->
     execute(C, S, "", N).
     execute(C, S, "", N).
 
 
+-spec execute(pgsql:connection(), #statement{}, string(), non_neg_integer()) -> reference().
 execute(C, Statement, PortalName, MaxRows) ->
 execute(C, Statement, PortalName, MaxRows) ->
     incremental(C, {execute, Statement, PortalName, MaxRows}).
     incremental(C, {execute, Statement, PortalName, MaxRows}).
 
 
+-spec execute_batch(pgsql:connection(), [{#statement{}, [pgsql:bind_param()]}]) -> reference().
 execute_batch(C, Batch) ->
 execute_batch(C, Batch) ->
     incremental(C, {execute_batch, Batch}).
     incremental(C, {execute_batch, Batch}).
 
 
@@ -97,6 +107,7 @@ close(C, Type, Name) ->
 sync(C) ->
 sync(C) ->
     incremental(C, sync).
     incremental(C, sync).
 
 
+-spec cancel(pgsql:connection()) -> ok.
 cancel(C) ->
 cancel(C) ->
     pgsql_sock:cancel(C).
     pgsql_sock:cancel(C).
 
 

+ 59 - 0
src/pgsql.erl

@@ -19,8 +19,42 @@
          with_transaction/2,
          with_transaction/2,
          sync_on_error/2]).
          sync_on_error/2]).
 
 
+-export_type([connection/0, connect_option/0,
+              connect_error/0, query_error/0,
+              bind_param/0,
+              squery_row/0, equery_row/0, ok_reply/1]).
+
 -include("pgsql.hrl").
 -include("pgsql.hrl").
 
 
+-type connection() :: pid().
+-type connect_option() :: {database, string()}
+                          | {port, inet:port_number()}
+                          | {ssl, boolean() | required}
+                          | {ssl_opts, list()} % ssl:option(), see OTP ssl_api.hrl
+                          | {timeout, timeout()}
+                          | {async, pid()}.
+-type connect_error() :: #error{}.
+-type query_error() :: #error{}.
+
+-type bind_param() ::
+        null
+        | boolean()
+        | string()
+        | binary()
+        | integer()
+        | float()
+        | calendar:date()
+        | calendar:time()                       %actualy, `Seconds' may be float()
+        | calendar:datetime()
+        | {calendar:time(), Days::non_neg_integer(), Months::non_neg_integer()}
+        | [bind_param()].                       %array (maybe nested)
+
+-type squery_row() :: {binary()}.
+-type equery_row() :: {bind_param()}.
+-type ok_reply(RowType) :: {ok, [#column{}], [RowType]}  % SELECT
+                         | {ok, non_neg_integer()}   % UPDATE / INSERT
+                         | {ok, non_neg_integer(), [#column{}], [RowType]}. % UPDATE / INSERT + RETURNING
+
 %% -- client interface --
 %% -- client interface --
 
 
 connect(Host, Opts) ->
 connect(Host, Opts) ->
@@ -33,6 +67,9 @@ connect(Host, Username, Password, Opts) ->
     {ok, C} = pgsql_sock:start_link(),
     {ok, C} = pgsql_sock:start_link(),
     connect(C, Host, Username, Password, Opts).
     connect(C, Host, Username, Password, Opts).
 
 
+-spec connect(connection(), inet:ip_address() | inet:hostname(),
+              string(), string(), [connect_option()]) ->
+                     {ok, pid()} | {error, connect_error()}.
 connect(C, Host, Username, Password, Opts) ->
 connect(C, Host, Username, Password, Opts) ->
     %% TODO connect timeout
     %% TODO connect timeout
     case gen_server:call(C,
     case gen_server:call(C,
@@ -44,12 +81,16 @@ connect(C, Host, Username, Password, Opts) ->
             Error
             Error
     end.
     end.
 
 
+-spec close(connection()) -> ok.
 close(C) ->
 close(C) ->
     pgsql_sock:close(C).
     pgsql_sock:close(C).
 
 
+-spec get_parameter(connection(), binary()) -> binary() | undefined.
 get_parameter(C, Name) ->
 get_parameter(C, Name) ->
     pgsql_sock:get_parameter(C, Name).
     pgsql_sock:get_parameter(C, Name).
 
 
+-spec squery(connection(), string()) ->
+                    ok_reply(squery_row()) | {error, query_error()}.
 squery(C, Sql) ->
 squery(C, Sql) ->
     gen_server:call(C, {squery, Sql}, infinity).
     gen_server:call(C, {squery, Sql}, infinity).
 
 
@@ -57,6 +98,8 @@ equery(C, Sql) ->
     equery(C, Sql, []).
     equery(C, Sql, []).
 
 
 %% TODO add fast_equery command that doesn't need parsed statement
 %% TODO add fast_equery command that doesn't need parsed statement
+-spec equery(connection(), string(), [bind_param()]) ->
+                    ok_reply(equery_row()) | {error, query_error()}.
 equery(C, Sql, Parameters) ->
 equery(C, Sql, Parameters) ->
     Name = ["equery-", atom_to_list(node()), pid_to_list(self())],
     Name = ["equery-", atom_to_list(node()), pid_to_list(self())],
     case parse(C, Name, Sql, []) of
     case parse(C, Name, Sql, []) of
@@ -75,6 +118,8 @@ parse(C, Sql) ->
 parse(C, Sql, Types) ->
 parse(C, Sql, Types) ->
     parse(C, "", Sql, Types).
     parse(C, "", Sql, Types).
 
 
+-spec parse(connection(), iolist(), string(), [epgsql_type()]) ->
+                   {ok, #statement{}} | {error, query_error()}.
 parse(C, Name, Sql, Types) ->
 parse(C, Name, Sql, Types) ->
     sync_on_error(C, gen_server:call(C, {parse, Name, Sql, Types}, infinity)).
     sync_on_error(C, gen_server:call(C, {parse, Name, Sql, Types}, infinity)).
 
 
@@ -83,6 +128,8 @@ parse(C, Name, Sql, Types) ->
 bind(C, Statement, Parameters) ->
 bind(C, Statement, Parameters) ->
     bind(C, Statement, "", Parameters).
     bind(C, Statement, "", Parameters).
 
 
+-spec bind(connection(), #statement{}, string(), [bind_param()]) ->
+                  ok | {error, query_error()}.
 bind(C, Statement, PortalName, Parameters) ->
 bind(C, Statement, PortalName, Parameters) ->
     sync_on_error(
     sync_on_error(
       C,
       C,
@@ -96,9 +143,16 @@ execute(C, S) ->
 execute(C, S, N) ->
 execute(C, S, N) ->
     execute(C, S, "", N).
     execute(C, S, "", N).
 
 
+-spec execute(connection(), #statement{}, string(), non_neg_integer()) -> Reply
+                                                                              when
+      Reply :: {ok | partial, [equery_row()]}
+             | {ok, non_neg_integer()}
+             | {ok, non_neg_integer(), [equery_row()]}.
 execute(C, S, PortalName, N) ->
 execute(C, S, PortalName, N) ->
     gen_server:call(C, {execute, S, PortalName, N}, infinity).
     gen_server:call(C, {execute, S, PortalName, N}, infinity).
 
 
+-spec execute_batch(connection(), [{#statement{}, [bind_param()]}]) ->
+                           [ok_reply(equery_row()) | {error, query_error()}].
 execute_batch(C, Batch) ->
 execute_batch(C, Batch) ->
     gen_server:call(C, {execute_batch, Batch}, infinity).
     gen_server:call(C, {execute_batch, Batch}, infinity).
 
 
@@ -123,10 +177,15 @@ close(C, Type, Name) ->
 sync(C) ->
 sync(C) ->
     gen_server:call(C, sync).
     gen_server:call(C, sync).
 
 
+-spec cancel(connection()) -> ok.
 cancel(C) ->
 cancel(C) ->
     pgsql_sock:cancel(C).
     pgsql_sock:cancel(C).
 
 
 %% misc helper functions
 %% misc helper functions
+-spec with_transaction(connection(), fun((connection()) -> Reply)) ->
+                              Reply | {rollback, any()}
+                                  when
+      Reply :: any().
 with_transaction(C, F) ->
 with_transaction(C, F) ->
     try {ok, [], []} = squery(C, "BEGIN"),
     try {ok, [], []} = squery(C, "BEGIN"),
         R = F(C),
         R = F(C),