Browse Source

Add get_cmd_status function. Affects #112

Сергей Прохоров 8 years ago
parent
commit
3a135b3024
8 changed files with 101 additions and 6 deletions
  1. 29 0
      README.md
  2. 11 0
      src/epgsql.erl
  3. 15 4
      src/epgsql_sock.erl
  4. 7 0
      src/epgsqla.erl
  5. 7 0
      src/epgsqli.erl
  6. 4 1
      test/epgsql_cast.erl
  7. 4 1
      test/epgsql_incremental.erl
  8. 24 0
      test/epgsql_tests.erl

+ 29 - 0
README.md

@@ -483,6 +483,35 @@ Message formats:
 - `Error`       - an `#error{}` record, see `epgsql.hrl`
 
 
+## Utility functions
+
+### Command status
+
+`epgsql{a,i}:get_cmd_status(C) -> undefined | atom() | {atom(), integer()}`
+
+This function returns last executed command's status information. It's usualy
+the name of SQL command and, for some of them (like UPDATE or INSERT) the
+number of affected rows. See [libpq PQcmdStatus](https://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQCMDSTATUS).
+But there is one interesting case: if you execute `COMMIT` on failed transaction,
+status will be `rollback`, not `commit`.
+This is how you can detect failed transactions:
+
+```erlang
+{ok, _, _} = epgsql:squery(C, "BEGIN").
+{error, _} = epgsql:equery(C, "SELECT 1 / $1::integer", [0]).
+{ok, _, _} = epgsql:squery(C, "COMMIT").
+{ok, rollback} = epgsql:get_cmd_status(C).
+```
+
+### Server parameters
+
+`epgsql{a,i}:get_parameter(C, Name) -> binary() | undefined`
+
+Retrieve actual value of server-side parameters, such as character endoding,
+date/time format and timezone, server version and so on. See [libpq PQparameterStatus](https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQPARAMETERSTATUS).
+Parameter's value may change during connection's lifetime.
+
+
 ## Mailing list
 
   [Google groups](https://groups.google.com/forum/#!forum/epgsql)

+ 11 - 0
src/epgsql.erl

@@ -7,6 +7,7 @@
          close/1,
          get_parameter/2,
          set_notice_receiver/2,
+         get_cmd_status/1,
          squery/2,
          equery/2, equery/3, equery/4,
          prepared_query/3,
@@ -188,6 +189,16 @@ get_parameter(C, Name) ->
 set_notice_receiver(C, PidOrName) ->
     epgsql_sock:set_notice_receiver(C, PidOrName).
 
+%% @doc Returns last command status message
+%% If multiple queries was executed using `squery/2', separated by semicolon,
+%% only last query's status will be available.
+%% See https://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQCMDSTATUS
+-spec get_cmd_status(connection()) -> {ok, Status}
+                                          when
+      Status :: undefined | atom() | {atom(), integer()}.
+get_cmd_status(C) ->
+    epgsql_sock:get_cmd_status(C).
+
 -spec squery(connection(), sql_query()) -> reply(squery_row()) | [reply(squery_row())].
 %% @doc runs simple `SqlQuery' via given `Connection'
 squery(Connection, SqlQuery) ->

+ 15 - 4
src/epgsql_sock.erl

@@ -9,6 +9,7 @@
          close/1,
          get_parameter/2,
          set_notice_receiver/2,
+         get_cmd_status/1,
          cancel/1]).
 
 -export([handle_call/3, handle_cast/2, handle_info/2]).
@@ -81,6 +82,7 @@
                 batch = [],
                 sync_required,
                 txstatus,
+                complete_status :: undefined | atom() | {atom(), integer()},
                 repl_last_received_lsn,
                 repl_last_flushed_lsn,
                 repl_last_applied_lsn,
@@ -105,6 +107,9 @@ set_notice_receiver(C, PidOrName) when is_pid(PidOrName);
                                        is_atom(PidOrName) ->
     gen_server:call(C, {set_async_receiver, PidOrName}, infinity).
 
+get_cmd_status(C) ->
+    gen_server:call(C, get_cmd_status, infinity).
+
 cancel(S) ->
     gen_server:cast(S, cancel).
 
@@ -127,6 +132,9 @@ handle_call({get_parameter, Name}, _From, State) ->
 handle_call({set_async_receiver, PidOrName}, _From, #state{async = Previous} = State) ->
     {reply, {ok, Previous}, State#state{async = PidOrName}};
 
+handle_call(get_cmd_status, _From, #state{complete_status = Status} = State) ->
+    {reply, {ok, Status}, State};
+
 handle_call({standby_status_update, FlushedLSN, AppliedLSN}, _From,
     #state{repl_last_received_lsn = ReceivedLSN} = State) ->
     send(State, ?COPY_DATA, epgsql_wire:encode_standby_status_update(ReceivedLSN, FlushedLSN, AppliedLSN)),
@@ -135,14 +143,16 @@ handle_call({standby_status_update, FlushedLSN, AppliedLSN}, _From,
 handle_call(Command, From, State) ->
     #state{queue = Q} = State,
     Req = {{call, From}, Command},
-    command(Command, State#state{queue = queue:in(Req, Q)}).
+    command(Command, State#state{queue = queue:in(Req, Q),
+                                 complete_status = undefined}).
 
 handle_cast({{Method, From, Ref}, Command} = Req, State)
   when ((Method == cast) or (Method == incremental)),
        is_pid(From),
        is_reference(Ref)  ->
     #state{queue = Q} = State,
-    command(Command, State#state{queue = queue:in(Req, Q)});
+    command(Command, State#state{queue = queue:in(Req, Q),
+                                 complete_status = undefined});
 
 handle_cast(stop, State) ->
     {stop, normal, flush_queue(State, {error, closed})};
@@ -714,8 +724,9 @@ on_message({?PORTAL_SUSPENDED, <<>>}, State) ->
     {noreply, State2};
 
 %% CommandComplete
-on_message({?COMMAND_COMPLETE, Bin}, State) ->
+on_message({?COMMAND_COMPLETE, Bin}, State0) ->
     Complete = epgsql_wire:decode_complete(Bin),
+    State = State0#state{complete_status = Complete},
     Command = command_tag(State),
     Notice = {complete, Complete},
     Rows = lists:reverse(State#state.rows),
@@ -845,4 +856,4 @@ on_message({?COPY_DATA, <<?X_LOG_DATA, StartLSN:?int64, EndLSN:?int64, _Timestam
         CbModule:handle_x_log_data(StartLSN, EndLSN, WALRecord, CbState),
     {noreply, State#state{repl_feedback_required = true, repl_last_received_lsn = EndLSN,
         repl_last_flushed_lsn = LastFlushedLSN, repl_last_applied_lsn = LastAppliedLSN,
-        repl_cbstate = NewCbState}}.
+        repl_cbstate = NewCbState}}.

+ 7 - 0
src/epgsqla.erl

@@ -7,6 +7,7 @@
          close/1,
          get_parameter/2,
          set_notice_receiver/2,
+         get_cmd_status/1,
          squery/2,
          equery/2, equery/3,
          prepared_query/3,
@@ -62,6 +63,12 @@ get_parameter(C, Name) ->
 set_notice_receiver(C, PidOrName) ->
     epgsql_sock:set_notice_receiver(C, PidOrName).
 
+-spec get_cmd_status(epgsql:connection()) -> {ok, Status}
+                                          when
+      Status :: undefined | atom() | {atom(), integer()}.
+get_cmd_status(C) ->
+    epgsql_sock:get_cmd_status(C).
+
 -spec squery(epgsql:connection(), string()) -> reference().
 squery(C, Sql) ->
     cast(C, {squery, Sql}).

+ 7 - 0
src/epgsqli.erl

@@ -7,6 +7,7 @@
          close/1,
          get_parameter/2,
          set_notice_receiver/2,
+         get_cmd_status/1,
          squery/2,
          equery/2, equery/3,
          prepared_query/3,
@@ -61,6 +62,12 @@ get_parameter(C, Name) ->
 set_notice_receiver(C, PidOrName) ->
     epgsql_sock:set_notice_receiver(C, PidOrName).
 
+-spec get_cmd_status(epgsql:connection()) -> {ok, Status}
+                                          when
+      Status :: undefined | atom() | {atom(), integer()}.
+get_cmd_status(C) ->
+    epgsql_sock:get_cmd_status(C).
+
 -spec squery(epgsql:connection(), string()) -> reference().
 squery(C, Sql) ->
     incremental(C, {squery, Sql}).

+ 4 - 1
test/epgsql_cast.erl

@@ -6,7 +6,7 @@
 -module(epgsql_cast).
 
 -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, get_cmd_status/1, squery/2, equery/2, equery/3]).
 -export([prepared_query/3]).
 -export([parse/2, parse/3, parse/4, describe/2, describe/3]).
 -export([bind/3, bind/4, execute/2, execute/3, execute/4, execute_batch/2]).
@@ -54,6 +54,9 @@ get_parameter(C, Name) ->
 set_notice_receiver(C, PidOrName) ->
     epgsqla:set_notice_receiver(C, PidOrName).
 
+get_cmd_status(C) ->
+    epgsqla:get_cmd_status(C).
+
 squery(C, Sql) ->
     Ref = epgsqla:squery(C, Sql),
     receive_result(C, Ref).

+ 4 - 1
test/epgsql_incremental.erl

@@ -6,7 +6,7 @@
 -module(epgsql_incremental).
 
 -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, get_cmd_status/1, squery/2, equery/2, equery/3]).
 -export([prepared_query/3]).
 -export([parse/2, parse/3, parse/4, describe/2, describe/3]).
 -export([bind/3, bind/4, execute/2, execute/3, execute/4, execute_batch/2]).
@@ -52,6 +52,9 @@ get_parameter(C, Name) ->
 set_notice_receiver(C, PidOrName) ->
     epgsqli:set_notice_receiver(C, PidOrName).
 
+get_cmd_status(C) ->
+    epgsqli:get_cmd_status(C).
+
 squery(C, Sql) ->
     Ref = epgsqli:squery(C, Sql),
     case receive_results(C, Ref, []) of

+ 24 - 0
test/epgsql_tests.erl

@@ -899,6 +899,30 @@ set_notice_receiver_test(Module) ->
       end,
       []).
 
+get_cmd_status_test(Module) ->
+    with_connection(
+      Module,
+      fun(C) ->
+              {ok, [], []} = Module:squery(C, "BEGIN"),
+              ?assertEqual({ok, 'begin'}, Module:get_cmd_status(C)),
+              {ok, [], []} = epgsql:equery(C, "CREATE TEMPORARY TABLE cmd_status_t(col INTEGER)"),
+              ?assertEqual({ok, 'create'}, Module:get_cmd_status(C)),
+              %% Some commands have number of affected rows in status
+              {ok, N} = Module:squery(C, "INSERT INTO cmd_status_t (col) VALUES (1), (2)"),
+              ?assertEqual({ok, {'insert', N}}, Module:get_cmd_status(C)),
+              {ok, 1} = Module:squery(C, "UPDATE cmd_status_t SET col=3 WHERE col=1"),
+              ?assertEqual({ok, {'update', 1}}, Module:get_cmd_status(C)),
+              %% Failed queries have no status
+              {error, _} = Module:squery(C, "UPDATE cmd_status_t SET col='text' WHERE col=2"),
+              ?assertEqual({ok, undefined}, Module:get_cmd_status(C)),
+              %% if COMMIT failed, status will be 'rollback'
+              {ok, [], []} = Module:squery(C, "COMMIT"),
+              ?assertEqual({ok, 'rollback'}, Module:get_cmd_status(C)),
+              %% Only last command's status returned
+              [_, _, _] = Module:squery(C, "BEGIN; SELECT 1; COMMIT"),
+              ?assertEqual({ok, 'commit'}, Module:get_cmd_status(C))
+      end).
+
 application_test(_Module) ->
     lists:foreach(fun application:start/1, ?ssl_apps),
     ok = application:start(epgsql),