epgsql.erl 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. %%% Copyright (C) 2008 - Will Glozer. All rights reserved.
  2. %%% Copyright (C) 2011 - Anton Lebedevich. All rights reserved.
  3. -module(epgsql).
  4. -export([connect/1, connect/2, connect/3, connect/4, connect/5,
  5. close/1,
  6. get_parameter/2,
  7. squery/2,
  8. equery/2, equery/3, equery/4,
  9. parse/2, parse/3, parse/4,
  10. describe/2, describe/3,
  11. bind/3, bind/4,
  12. execute/2, execute/3, execute/4,
  13. execute_batch/2,
  14. close/2, close/3,
  15. sync/1,
  16. cancel/1,
  17. update_type_cache/1,
  18. with_transaction/2,
  19. sync_on_error/2]).
  20. -export_type([connection/0, connect_option/0,
  21. connect_error/0, query_error/0,
  22. bind_param/0,
  23. squery_row/0, equery_row/0, ok_reply/1]).
  24. -include("epgsql.hrl").
  25. -type connection() :: pid().
  26. -type connect_option() :: {database, string()}
  27. | {port, inet:port_number()}
  28. | {ssl, boolean() | required}
  29. | {ssl_opts, list()} % ssl:option(), see OTP ssl_api.hrl
  30. | {timeout, timeout()}
  31. | {async, pid()}.
  32. -type connect_error() :: #error{}.
  33. -type query_error() :: #error{}.
  34. -type bind_param() ::
  35. null
  36. | boolean()
  37. | string()
  38. | binary()
  39. | integer()
  40. | float()
  41. | calendar:date()
  42. | calendar:time() %actualy, `Seconds' may be float()
  43. | calendar:datetime()
  44. | {calendar:time(), Days::non_neg_integer(), Months::non_neg_integer()}
  45. | [bind_param()]. %array (maybe nested)
  46. -type squery_row() :: {binary()}.
  47. -type equery_row() :: {bind_param()}.
  48. -type ok_reply(RowType) :: {ok, [#column{}], [RowType]} % SELECT
  49. | {ok, non_neg_integer()} % UPDATE / INSERT
  50. | {ok, non_neg_integer(), [#column{}], [RowType]}. % UPDATE / INSERT + RETURNING
  51. %% -- client interface --
  52. connect(Settings) ->
  53. Host = proplists:get_value(host, Settings, "localhost"),
  54. Username = proplists:get_value(username, Settings, os:getenv("USER")),
  55. Password = proplists:get_value(password, Settings, ""),
  56. connect(Host, Username, Password, Settings).
  57. connect(Host, Opts) ->
  58. connect(Host, os:getenv("USER"), "", Opts).
  59. connect(Host, Username, Opts) ->
  60. connect(Host, Username, "", Opts).
  61. connect(Host, Username, Password, Opts) ->
  62. {ok, C} = epgsql_sock:start_link(),
  63. connect(C, Host, Username, Password, Opts).
  64. -spec connect(connection(), inet:ip_address() | inet:hostname(),
  65. string(), string(), [connect_option()]) ->
  66. {ok, pid()} | {error, connect_error()}.
  67. connect(C, Host, Username, Password, Opts) ->
  68. %% TODO connect timeout
  69. case gen_server:call(C,
  70. {connect, Host, Username, Password, Opts},
  71. infinity) of
  72. connected ->
  73. update_type_cache(C),
  74. {ok, C};
  75. Error = {error, _} ->
  76. Error
  77. end.
  78. -spec update_type_cache(connection()) -> ok.
  79. update_type_cache(C) ->
  80. DynamicTypes = [<<"hstore">>,<<"geometry">>],
  81. Query = "SELECT typname, oid::int4, typarray::int4"
  82. " FROM pg_type"
  83. " WHERE typname = ANY($1::varchar[])",
  84. {ok, _, TypeInfos} = equery(C, Query, [DynamicTypes]),
  85. ok = gen_server:call(C, {update_type_cache, TypeInfos}).
  86. -spec close(connection()) -> ok.
  87. close(C) ->
  88. epgsql_sock:close(C).
  89. -spec get_parameter(connection(), binary()) -> binary() | undefined.
  90. get_parameter(C, Name) ->
  91. epgsql_sock:get_parameter(C, Name).
  92. -spec squery(connection(), string() | iodata()) ->
  93. ok_reply(squery_row()) | {error, query_error()} |
  94. [ok_reply(squery_row()) | {error, query_error()}].
  95. squery(C, Sql) ->
  96. gen_server:call(C, {squery, Sql}, infinity).
  97. equery(C, Sql) ->
  98. equery(C, Sql, []).
  99. %% TODO add fast_equery command that doesn't need parsed statement
  100. equery(C, Sql, Parameters) ->
  101. case parse(C, "", Sql, []) of
  102. {ok, #statement{types = Types} = S} ->
  103. Typed_Parameters = lists:zip(Types, Parameters),
  104. gen_server:call(C, {equery, S, Typed_Parameters}, infinity);
  105. Error ->
  106. Error
  107. end.
  108. -spec equery(connection(), string(), string() | iodata(), [bind_param()]) ->
  109. ok_reply(equery_row()) | {error, query_error()}.
  110. equery(C, Name, Sql, Parameters) ->
  111. case parse(C, Name, Sql, []) of
  112. {ok, #statement{types = Types} = S} ->
  113. Typed_Parameters = lists:zip(Types, Parameters),
  114. gen_server:call(C, {equery, S, Typed_Parameters}, infinity);
  115. Error ->
  116. Error
  117. end.
  118. %% parse
  119. parse(C, Sql) ->
  120. parse(C, Sql, []).
  121. parse(C, Sql, Types) ->
  122. parse(C, "", Sql, Types).
  123. -spec parse(connection(), iolist(), string() | iodata(), [epgsql_type()]) ->
  124. {ok, #statement{}} | {error, query_error()}.
  125. parse(C, Name, Sql, Types) ->
  126. sync_on_error(C, gen_server:call(C, {parse, Name, Sql, Types}, infinity)).
  127. %% bind
  128. bind(C, Statement, Parameters) ->
  129. bind(C, Statement, "", Parameters).
  130. -spec bind(connection(), #statement{}, string(), [bind_param()]) ->
  131. ok | {error, query_error()}.
  132. bind(C, Statement, PortalName, Parameters) ->
  133. sync_on_error(
  134. C,
  135. gen_server:call(C, {bind, Statement, PortalName, Parameters}, infinity)).
  136. %% execute
  137. execute(C, S) ->
  138. execute(C, S, "", 0).
  139. execute(C, S, N) ->
  140. execute(C, S, "", N).
  141. -spec execute(connection(), #statement{}, string(), non_neg_integer()) -> Reply
  142. when
  143. Reply :: {ok | partial, [equery_row()]}
  144. | {ok, non_neg_integer()}
  145. | {ok, non_neg_integer(), [equery_row()]}
  146. | {error, query_error()}.
  147. execute(C, S, PortalName, N) ->
  148. gen_server:call(C, {execute, S, PortalName, N}, infinity).
  149. -spec execute_batch(connection(), [{#statement{}, [bind_param()]}]) ->
  150. [ok_reply(equery_row()) | {error, query_error()}].
  151. execute_batch(C, Batch) ->
  152. gen_server:call(C, {execute_batch, Batch}, infinity).
  153. %% statement/portal functions
  154. describe(C, #statement{name = Name}) ->
  155. describe(C, statement, Name).
  156. describe(C, statement, Name) ->
  157. sync_on_error(C, gen_server:call(C, {describe_statement, Name}, infinity));
  158. %% TODO unknown result format of Describe portal
  159. describe(C, portal, Name) ->
  160. sync_on_error(C, gen_server:call(C, {describe_portal, Name}, infinity)).
  161. close(C, #statement{name = Name}) ->
  162. close(C, statement, Name).
  163. close(C, Type, Name) ->
  164. gen_server:call(C, {close, Type, Name}).
  165. sync(C) ->
  166. gen_server:call(C, sync).
  167. -spec cancel(connection()) -> ok.
  168. cancel(C) ->
  169. epgsql_sock:cancel(C).
  170. %% misc helper functions
  171. -spec with_transaction(connection(), fun((connection()) -> Reply)) ->
  172. Reply | {rollback, any()}
  173. when
  174. Reply :: any().
  175. with_transaction(C, F) ->
  176. try {ok, [], []} = squery(C, "BEGIN"),
  177. R = F(C),
  178. {ok, [], []} = squery(C, "COMMIT"),
  179. R
  180. catch
  181. _:Why ->
  182. squery(C, "ROLLBACK"),
  183. %% TODO hides error stacktrace
  184. {rollback, Why}
  185. end.
  186. sync_on_error(C, Error = {error, _}) ->
  187. ok = sync(C),
  188. Error;
  189. sync_on_error(_C, R) ->
  190. R.