Browse Source

Merge pull request #92 from eshubin/master

add prepared queries function
David N. Welton 9 years ago
parent
commit
654378074d
4 changed files with 61 additions and 9 deletions
  1. 17 0
      README.md
  2. 12 0
      src/epgsql.erl
  3. 20 9
      src/epgsql_sock.erl
  4. 12 0
      test/epgsql_tests.erl

+ 17 - 0
README.md

@@ -273,6 +273,23 @@ end.
 `epgsqli:equery(C, Statement, [Parameters])` sends same set of messages as
 squery including final `{C, Ref, done}`.
 
+## Prepared Query
+```erlang
+{ok, Columns, Rows}        = epgsql:prepared_query(C, StatementName, [Parameters]).
+{ok, Count}                = epgsql:prepared_query(C, StatementName, [Parameters]).
+{ok, Count, Columns, Rows} = epgsql:prepared_query(C, StatementName, [Parameters]).
+{error, Error}             = epgsql:prepared_equery(C, "non_existent_query", [Parameters]).
+```
+`Parameters` - optional list of values to be bound to `$1`, `$2`, `$3`, etc.
+`StatementName` - name of query given with ```erlang epgsql:parse(C, StatementName, "select ...", []).```
+
+With prepared query one can parse a query giving it a name with `epgsql:parse` on start and reuse the name 
+for all further queries with different parameters.
+```erlang
+epgsql:parse(C, "inc", "select $1+1", []).
+epgsql:prepared_query(C, "inc", [4]).
+epgsql:prepared_query(C, "inc", [1]).
+```
 
 ## Parse/Bind/Execute
 

+ 12 - 0
src/epgsql.erl

@@ -8,6 +8,7 @@
          get_parameter/2,
          squery/2,
          equery/2, equery/3, equery/4,
+         prepared_query/3,
          parse/2, parse/3, parse/4,
          describe/2, describe/3,
          bind/3, bind/4,
@@ -159,6 +160,17 @@ equery(C, Name, Sql, Parameters) ->
             Error
     end.
 
+-spec prepared_query(C::connection(), Name::string(), Parameters::[bind_param()]) -> reply(equery_row()).
+prepared_query(C, Name, Parameters) ->
+    case describe(C, statement, Name) of
+        {ok, #statement{types = Types} = S} ->
+            Typed_Parameters = lists:zip(Types, Parameters),
+            gen_server:call(C, {prepared_query, S, Typed_Parameters}, infinity);
+        Error ->
+            Error
+    end.
+
+
 %% parse
 
 parse(C, Sql) ->

+ 20 - 9
src/epgsql_sock.erl

@@ -225,6 +225,17 @@ command({equery, Statement, Parameters}, #state{codec = Codec} = State) ->
     ]),
     {noreply, State};
 
+command({prepared_query, Statement, Parameters}, #state{codec = Codec} = State) ->
+    #statement{name = StatementName, columns = Columns} = Statement,
+    Bin1 = epgsql_wire:encode_parameters(Parameters, Codec),
+    Bin2 = epgsql_wire:encode_formats(Columns),
+    send_multi(State, [
+        {?BIND, ["", 0, StatementName, 0, Bin1, Bin2]},
+        {?EXECUTE, ["", 0, <<0:?int32>>]},
+        {?SYNC, []}
+    ]),
+    {noreply, State};
+
 command({parse, Name, Sql, Types}, State) ->
     Bin = epgsql_wire:encode_types(Types, State#state.codec),
     send_multi(State, [
@@ -433,7 +444,7 @@ command_tag(#state{queue = Q}) ->
 get_columns(State) ->
     #state{queue = Q, columns = Columns, batch = Batch} = State,
     case queue:get(Q) of
-        {_, {equery, #statement{columns = C}, _}} ->
+        {_, {Command, #statement{columns = C}, _}}  when Command == equery; Command == prepared_query ->
             C;
         {_, {execute, #statement{columns = C}, _, _}} ->
             C;
@@ -595,7 +606,7 @@ on_message({?NO_DATA, <<>>}, State) ->
 %% BindComplete
 on_message({?BIND_COMPLETE, <<>>}, State) ->
     State2 = case command_tag(State) of
-                 equery ->
+                 Command when Command == equery; Command == prepared_query ->
                      %% TODO send Describe as a part of equery, needs text format support
                      notify(State, {columns, get_columns(State)});
                  bind ->
@@ -615,7 +626,7 @@ on_message({?BIND_COMPLETE, <<>>}, State) ->
 %% CloseComplete
 on_message({?CLOSE_COMPLETE, <<>>}, State) ->
     State2 = case command_tag(State) of
-                 equery ->
+                 Command when Command == equery; Command == prepared_query ->
                      State;
                  close ->
                      finish(State, ok)
@@ -653,11 +664,11 @@ on_message({?COMMAND_COMPLETE, Bin}, State) ->
                      add_result(State, Notice, {ok, Count, Rows});
                  {execute_batch, _, _} ->
                      add_result(State, Notice, {ok, Rows});
-                 {C, {_, Count}, []} when C == squery; C == equery ->
+                 {C, {_, Count}, []} when C == squery; C == equery; C == prepared_query ->
                      add_result(State, Notice, {ok, Count});
-                 {C, {_, Count}, _} when C == squery; C == equery ->
+                 {C, {_, Count}, _} when C == squery; C == equery; C == prepared_query ->
                      add_result(State, Notice, {ok, Count, get_columns(State), Rows});
-                 {C, _, _} when C == squery; C == equery ->
+                 {C, _, _} when C == squery; C == equery; C == prepared_query ->
                      add_result(State, Notice, {ok, get_columns(State), Rows})
              end,
     {noreply, State2};
@@ -668,7 +679,7 @@ on_message({?EMPTY_QUERY, _Bin}, State) ->
     State2 = case command_tag(State) of
                  execute ->
                      finish(State, Notice, {ok, [], []});
-                 C when C == squery; C == equery ->
+                 C when C == squery; C == equery; C == prepared_query ->
                      add_result(State, Notice, {ok, [], []})
              end,
     {noreply, State2};
@@ -685,7 +696,7 @@ on_message({?READY_FOR_QUERY, <<Status:8>>}, State) ->
                      end;
                  execute_batch ->
                      finish(State, done, lists:reverse(State#state.results));
-                 equery ->
+                 Command when Command == equery; Command == prepared_query ->
                      case State#state.results of
                          [Result] ->
                              finish(State, done, Result);
@@ -703,7 +714,7 @@ on_message(Error = {error, Reason}, State) ->
             {stop, {shutdown, Reason}, State};
         false ->
             State2 = case command_tag(State) of
-                C when C == squery; C == equery; C == execute_batch ->
+                C when C == squery; C == equery; C == execute_batch; C == prepared_query ->
                     add_result(State, Error, Error);
                 _ ->
                     sync_required(finish(State, Error))

+ 12 - 0
test/epgsql_tests.erl

@@ -106,6 +106,18 @@ connect_with_client_cert_test(Module) ->
       "epgsql_test_cert",
       [{ssl, true}, {keyfile, File("epgsql.key")}, {certfile, File("epgsql.crt")}]).
 
+prepared_query_test(Module) ->
+  with_connection(
+    Module,
+    fun(C) ->
+      {ok, _} = epgsql:parse(C, "inc", "select $1+1", []),
+      {ok, Cols, [{5}]} = epgsql:prepared_query(C, "inc", [4]),
+      {ok, Cols, [{2}]} = epgsql:prepared_query(C, "inc", [1]),
+      {ok, Cols, [{23}]} = epgsql:prepared_query(C, "inc", [22]),
+      {error, _} = epgsql:prepared_query(C, "non_existent_query", [4])
+    end).
+
+
 select_test(Module) ->
     with_connection(
       Module,