From f9f3af6f62a0db77019789416eebc14079741580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Fri, 31 Aug 2018 23:34:34 +0200 Subject: [PATCH 01/19] Remove Docker dependent test --- test/mariaex/protocol_test.exs | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 test/mariaex/protocol_test.exs diff --git a/test/mariaex/protocol_test.exs b/test/mariaex/protocol_test.exs deleted file mode 100644 index e7201a1..0000000 --- a/test/mariaex/protocol_test.exs +++ /dev/null @@ -1,21 +0,0 @@ -defmodule Mariaex.ProtocolTest do - use ExUnit.Case - import ExUnit.CaptureLog - - describe "Integration test" do - test "Expect to disconnect from the database when it goes down" do - Process.flag(:trap_exit, true) - opts = [database: "information_schema", username: "mariaex_user", password: "mariaex_pass", backoff_type: :stop, sync_connect: true] - - log = capture_log(fn -> - assert {:ok, pid} = Mariaex.start_link(opts) - # Restart the docker container to assure that mariadb will stop - # responding to ping(s) - System.cmd("docker", ["restart", "mariadb"]) - end) - - # Check if we disconnected - assert log =~ "disconnected" - end - end -end From e0bcf00241ea222c8b6d27f479eb191a16eb64f5 Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Fri, 31 Aug 2018 13:59:51 +0200 Subject: [PATCH 02/19] Update decimal --- mix.exs | 2 +- mix.lock | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/mix.exs b/mix.exs index d97635a..fe46ca5 100644 --- a/mix.exs +++ b/mix.exs @@ -24,7 +24,7 @@ defmodule Mariaex.Mixfile do end defp deps do - [{:decimal, "~> 1.0"}, + [{:decimal, "~> 1.2"}, {:db_connection, "~> 1.1"}, {:coverex, "~> 1.4.10", only: :test}, {:ex_doc, ">= 0.0.0", only: :dev}, diff --git a/mix.lock b/mix.lock index 8c88065..c2da630 100644 --- a/mix.lock +++ b/mix.lock @@ -1,8 +1,9 @@ -%{"certifi": {:hex, :certifi, "0.7.0", "861a57f3808f7eb0c2d1802afeaae0fa5de813b0df0979153cbafcd853ababaf", [:rebar3], [], "hexpm"}, +%{ + "certifi": {:hex, :certifi, "0.7.0", "861a57f3808f7eb0c2d1802afeaae0fa5de813b0df0979153cbafcd853ababaf", [:rebar3], [], "hexpm"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, "coverex": {:hex, :coverex, "1.4.10", "f6b68f95b3d51d04571a09dd2071c980e8398a38cf663db22b903ecad1083d51", [:mix], [{:httpoison, "~> 0.9", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "db_connection": {:hex, :db_connection, "1.1.0", "b2b88db6d7d12f99997b584d09fad98e560b817a20dab6a526830e339f54cdb3", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, - "decimal": {:hex, :decimal, "1.1.0", "3333732f17a90ff3057d7ab8c65f0930ca2d67e15cca812a91ead5633ed472fe", [:mix], [], "hexpm"}, + "decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"}, "earmark": {:hex, :earmark, "1.0.3", "89bdbaf2aca8bbb5c97d8b3b55c5dd0cff517ecc78d417e87f1d0982e514557b", [:mix], [], "hexpm"}, "ex_doc": {:hex, :ex_doc, "0.14.5", "c0433c8117e948404d93ca69411dd575ec6be39b47802e81ca8d91017a0cf83c", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, "hackney": {:hex, :hackney, "1.6.3", "d489d7ca2d4323e307bedc4bfe684323a7bf773ecfd77938f3ee8074e488e140", [:mix, :rebar3], [{:certifi, "0.7.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "1.2.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, @@ -11,4 +12,5 @@ "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, "poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], [], "hexpm"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"}} + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"}, +} From e9c575c609340115bc4df3a7dbd363e128c20851 Mon Sep 17 00:00:00 2001 From: James Fish Date: Fri, 11 Aug 2017 12:14:32 +0100 Subject: [PATCH 03/19] Use database transaction status to prevent transaction errors --- lib/mariaex/protocol.ex | 97 +++++++++++++++++++++++++++-------------- mix.exs | 2 +- mix.lock | 2 +- 3 files changed, 66 insertions(+), 35 deletions(-) diff --git a/lib/mariaex/protocol.ex b/lib/mariaex/protocol.ex index 9ab89ea..0851b67 100644 --- a/lib/mariaex/protocol.ex +++ b/lib/mariaex/protocol.ex @@ -35,6 +35,7 @@ defmodule Mariaex.Protocol do @client_ps_multi_results 0x00040000 @client_deprecate_eof 0x01000000 + @server_status_in_trans 0x0001 @server_more_results_exists 0x0008 @server_status_cursor_exists 0x0040 @server_status_last_row_sent 0x0080 @@ -63,6 +64,7 @@ defmodule Mariaex.Protocol do seqnum: 0, datetime: :structs, json_library: Poison, + transaction_status: :idle, ssl_conn_state: :undefined # :undefined | :not_used | :ssl_handshake | :connected @doc """ @@ -383,8 +385,8 @@ defmodule Mariaex.Protocol do defp prepare_recv(state, query) do case prepare_recv(state) do - {:prepared, id, num_params, state} -> - {:ok, prepare_insert(id, num_params, query, state), clean_state(state)} + {:prepared, id, num_params, flags, state} -> + {:ok, prepare_insert(id, num_params, query, state), clean_state(state, flags)} {:ok, packet, state} -> handle_error(packet, query, state) {:error, reason} -> @@ -395,13 +397,13 @@ defmodule Mariaex.Protocol do defp prepare_recv(state) do state = %{state | state: :prepare_send} with {:ok, packet(msg: stmt_prepare_ok(statement_id: id, num_columns: num_cols, num_params: num_params)), state} <- msg_recv(state), - {:eof, state} <- skip_definitions(state, num_params), - {:eof, state} <- skip_definitions(state, num_cols) do - {:prepared, id, num_params, state} + {:eof, _, state} <- skip_definitions(state, num_params), + {:eof, flags, state} <- skip_definitions(state, num_cols) do + {:prepared, id, num_params, flags, state} end end - defp skip_definitions(state, 0), do: {:eof, state} + defp skip_definitions(state, 0), do: {:eof, nil, state} defp skip_definitions(state, count) do do_skip_definitions(%{state | state: :column_definitions}, count) end @@ -415,12 +417,12 @@ defmodule Mariaex.Protocol do end end defp do_skip_definitions(%{deprecated_eof: true} = state, 0) do - {:eof, state} + {:eof, nil, state} end defp do_skip_definitions(%{deprecated_eof: false} = state, 0) do case msg_recv(state) do - {:ok, packet(msg: eof_resp()), state} -> - {:eof, state} + {:ok, packet(msg: eof_resp(status_flags: flags)), state} -> + {:eof, flags, state} other -> other end @@ -507,9 +509,9 @@ defmodule Mariaex.Protocol do defp text_query_recv(state, query) do case text_query_recv(state) do - {:resultset, columns, rows, _flags, state} -> + {:resultset, columns, rows, flags, state} -> result = %Mariaex.Result{rows: rows, connection_id: state.connection_id} - {:ok, {result, columns}, clean_state(state)} + {:ok, {result, columns}, clean_state(state, flags)} {:ok, packet(msg: ok_resp()) = packet, state} -> handle_ok_packet(packet, query, state) {:ok, packet, state} -> @@ -630,16 +632,16 @@ defmodule Mariaex.Protocol do binary_query_more(state, query, columns, rows) true -> result = %Mariaex.Result{rows: rows, connection_id: state.connection_id} - {:ok, {result, columns}, clean_state(state)} + {:ok, {result, columns}, clean_state(state, flags)} end end defp binary_query_more(state, query, columns, rows) do case msg_recv(state) do - {:ok, packet(msg: ok_resp(affected_rows: affected_rows, last_insert_id: last_insert_id)), state} -> + {:ok, packet(msg: ok_resp(affected_rows: affected_rows, last_insert_id: last_insert_id, status_flags: flags)), state} -> result = %Mariaex.Result{rows: rows, num_rows: affected_rows, last_insert_id: last_insert_id, connection_id: state.connection_id} - {:ok, {result, columns}, clean_state(state)} + {:ok, {result, columns}, clean_state(state, flags)} {:ok, packet, state} -> handle_error(packet, query, state) {:error, reason} -> @@ -668,14 +670,27 @@ defmodule Mariaex.Protocol do end end - defp handle_ok_packet(packet(msg: ok_resp(affected_rows: affected_rows, last_insert_id: last_insert_id)), _query, s) do + defp handle_ok_packet(packet(msg: ok_resp(affected_rows: affected_rows, last_insert_id: last_insert_id, status_flags: flags)), _query, s) do result = %Mariaex.Result{columns: [], rows: nil, num_rows: affected_rows, last_insert_id: last_insert_id, connection_id: s.connection_id} - {:ok, {result, nil}, clean_state(s)} + {:ok, {result, nil}, clean_state(s, flags)} end - defp clean_state(state) do - %{state | state: :running, state_data: nil} + defp clean_state(state, flags) do + status = transaction_status(state, flags) + %{state | state: :running, state_data: nil, transaction_status: status} + end + + defp transaction_status(_, flags) when is_integer(flags) do + case flags &&& @server_status_in_trans do + @server_status_in_trans -> + :transaction + 0 -> + :idle + end + end + defp transaction_status(%{transaction_status: status}, nil) do + status end @doc """ @@ -817,10 +832,11 @@ defmodule Mariaex.Protocol do binary_first_more(state, query, columns, rows) (flags &&& @server_status_cursor_exists) == @server_status_cursor_exists -> %{cursors: cursors} = state - state = clean_state(%{state | cursors: Map.put(cursors, ref, columns)}) + state = clean_state(%{state | cursors: Map.put(cursors, ref, columns)}, flags) {:ok, {%Mariaex.Result{rows: rows, connection_id: state.connection_id}, columns}, state} true -> - {:deallocate, {%Mariaex.Result{rows: rows, connection_id: state.connection_id}, columns}, clean_state(state)} + result = %Mariaex.Result{rows: rows, connection_id: state.connection_id} + {:deallocate, {result, columns}, clean_state(state, flags)} end end @@ -853,9 +869,11 @@ defmodule Mariaex.Protocol do defp binary_next_resultset(state, columns, rows, flags) do cond do (flags &&& @server_status_last_row_sent) == @server_status_last_row_sent -> - {:deallocate, {%Mariaex.Result{rows: rows, connection_id: state.connection_id}, columns}, clean_state(state)} + result = %Mariaex.Result{rows: rows, connection_id: state.connection_id} + {:deallocate, {result, columns}, clean_state(state, flags)} (flags &&& @server_status_cursor_exists) == @server_status_cursor_exists -> - {:ok, {%Mariaex.Result{rows: rows, connection_id: state.connection_id}, columns}, clean_state(state)} + result = %Mariaex.Result{rows: rows, connection_id: state.connection_id} + {:ok, {result, columns}, clean_state(state, flags)} end end @@ -916,46 +934,59 @@ defmodule Mariaex.Protocol do @doc """ DBConnection callback """ - def handle_begin(opts, s) do + def handle_begin(opts, %{transaction_status: status} = s) do case Keyword.get(opts, :mode, :transaction) do - :transaction -> + :transaction when status == :idle -> name = @reserved_prefix <> "BEGIN" handle_transaction(name, :begin, opts, s) - :savepoint -> + :savepoint when status == :transaction -> name = @reserved_prefix <> "SAVEPOINT mariaex_savepoint" handle_savepoint([name], [:savepoint], opts, s) + mode when mode in [:transaction, :savepoint] -> + {status, s} end end @doc """ DBConnection callback """ - def handle_commit(opts, s) do + def handle_commit(opts, %{transaction_status: status} = s) do case Keyword.get(opts, :mode, :transaction) do - :transaction -> + :transaction when status == :transaction -> name = @reserved_prefix <> "COMMIT" handle_transaction(name, :commit, opts, s) - :savepoint -> + :savepoint when status == :transaction -> name = @reserved_prefix <> "RELEASE SAVEPOINT mariaex_savepoint" handle_savepoint([name], [:release], opts, s) + mode when mode in [:transaction, :savepoint] -> + {status, s} end end @doc """ DBConnection callback """ - def handle_rollback(opts, s) do + def handle_rollback(opts, %{transaction_status: status} = s) do case Keyword.get(opts, :mode, :transaction) do - :transaction -> + :transaction when status == :transaction -> name = @reserved_prefix <> "ROLLBACK" handle_transaction(name, :rollback, opts, s) - :savepoint -> + :savepoint when status == :transaction -> names = [@reserved_prefix <> "ROLLBACK TO SAVEPOINT mariaex_savepoint", @reserved_prefix <> "RELEASE SAVEPOINT mariaex_savepoint"] handle_savepoint(names, [:rollback, :release], opts, s) + mode when mode in [:transaction, :savepoint] -> + {status, s} end end + @doc """ + DBConnection callback + """ + def handle_status(_, %{transaction_status: status} = state) do + {status, state} + end + defp handle_transaction(name, cmd, opts, state) do query = %Query{type: :text, name: name, statement: to_string(cmd), reserved?: true} handle_execute(query, [], opts, state) @@ -1162,9 +1193,9 @@ defmodule Mariaex.Protocol do case query do %Query{} -> {:ok, nil, s} = handle_close(query, [], s) - {:error, error, clean_state(s)} + {:error, error, clean_state(s, nil)} nil -> - {:error, error, clean_state(s)} + {:error, error, clean_state(s, nil)} end end diff --git a/mix.exs b/mix.exs index fe46ca5..698e90c 100644 --- a/mix.exs +++ b/mix.exs @@ -25,7 +25,7 @@ defmodule Mariaex.Mixfile do defp deps do [{:decimal, "~> 1.2"}, - {:db_connection, "~> 1.1"}, + {:db_connection, "~> 1.1", github: "elixir-ecto/db_connection", ref: "4947966"}, {:coverex, "~> 1.4.10", only: :test}, {:ex_doc, ">= 0.0.0", only: :dev}, {:poison, ">= 0.0.0", optional: true}] diff --git a/mix.lock b/mix.lock index c2da630..8b1958d 100644 --- a/mix.lock +++ b/mix.lock @@ -2,7 +2,7 @@ "certifi": {:hex, :certifi, "0.7.0", "861a57f3808f7eb0c2d1802afeaae0fa5de813b0df0979153cbafcd853ababaf", [:rebar3], [], "hexpm"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, "coverex": {:hex, :coverex, "1.4.10", "f6b68f95b3d51d04571a09dd2071c980e8398a38cf663db22b903ecad1083d51", [:mix], [{:httpoison, "~> 0.9", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, - "db_connection": {:hex, :db_connection, "1.1.0", "b2b88db6d7d12f99997b584d09fad98e560b817a20dab6a526830e339f54cdb3", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, + "db_connection": {:git, "https://github.com/elixir-ecto/db_connection.git", "49479667131329376adf1c2c0e9a16bcf470aa84", [ref: "4947966"]}, "decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"}, "earmark": {:hex, :earmark, "1.0.3", "89bdbaf2aca8bbb5c97d8b3b55c5dd0cff517ecc78d417e87f1d0982e514557b", [:mix], [], "hexpm"}, "ex_doc": {:hex, :ex_doc, "0.14.5", "c0433c8117e948404d93ca69411dd575ec6be39b47802e81ca8d91017a0cf83c", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, From dd2dc21af813fc31b942516db9b9641021a357fe Mon Sep 17 00:00:00 2001 From: James Fish Date: Sat, 12 Aug 2017 12:08:44 +0100 Subject: [PATCH 04/19] Use one text statement for all queries --- lib/mariaex/protocol.ex | 58 ++++++++++++++-------------- test/transaction_test.exs | 79 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 29 deletions(-) create mode 100644 test/transaction_test.exs diff --git a/lib/mariaex/protocol.ex b/lib/mariaex/protocol.ex index 0851b67..1f3ec7e 100644 --- a/lib/mariaex/protocol.ex +++ b/lib/mariaex/protocol.ex @@ -937,11 +937,9 @@ defmodule Mariaex.Protocol do def handle_begin(opts, %{transaction_status: status} = s) do case Keyword.get(opts, :mode, :transaction) do :transaction when status == :idle -> - name = @reserved_prefix <> "BEGIN" - handle_transaction(name, :begin, opts, s) + handle_transaction("BEGIN", s) :savepoint when status == :transaction -> - name = @reserved_prefix <> "SAVEPOINT mariaex_savepoint" - handle_savepoint([name], [:savepoint], opts, s) + handle_transaction("SAVEPOINT mariaex_savepoint", s) mode when mode in [:transaction, :savepoint] -> {status, s} end @@ -953,11 +951,9 @@ defmodule Mariaex.Protocol do def handle_commit(opts, %{transaction_status: status} = s) do case Keyword.get(opts, :mode, :transaction) do :transaction when status == :transaction -> - name = @reserved_prefix <> "COMMIT" - handle_transaction(name, :commit, opts, s) + handle_transaction("COMMIT", s) :savepoint when status == :transaction -> - name = @reserved_prefix <> "RELEASE SAVEPOINT mariaex_savepoint" - handle_savepoint([name], [:release], opts, s) + handle_transaction("RELEASE SAVEPOINT mariaex_savepoint", s) mode when mode in [:transaction, :savepoint] -> {status, s} end @@ -969,12 +965,11 @@ defmodule Mariaex.Protocol do def handle_rollback(opts, %{transaction_status: status} = s) do case Keyword.get(opts, :mode, :transaction) do :transaction when status == :transaction -> - name = @reserved_prefix <> "ROLLBACK" - handle_transaction(name, :rollback, opts, s) + handle_transaction("ROLLBACK", s) :savepoint when status == :transaction -> - names = [@reserved_prefix <> "ROLLBACK TO SAVEPOINT mariaex_savepoint", - @reserved_prefix <> "RELEASE SAVEPOINT mariaex_savepoint"] - handle_savepoint(names, [:rollback, :release], opts, s) + rollback_release = + "ROLLBACK TO SAVEPOINT mariaex_savepoint; RELEASE SAVEPOINT mariaex_savepoint" + handle_transaction(rollback_release, s) mode when mode in [:transaction, :savepoint] -> {status, s} end @@ -987,24 +982,29 @@ defmodule Mariaex.Protocol do {status, state} end - defp handle_transaction(name, cmd, opts, state) do - query = %Query{type: :text, name: name, statement: to_string(cmd), reserved?: true} - handle_execute(query, [], opts, state) + defp handle_transaction(statement, state) do + state + |> send_text_query(statement) + |> transaction_recv() end - defp handle_savepoint(names, cmds, opts, state) do - Enum.zip(names, cmds) |> Enum.reduce({:ok, nil, state}, - fn({@reserved_prefix <> name, _cmd}, {:ok, _, state}) -> - query = %Query{type: :text, name: @reserved_prefix <> name, statement: name} - case handle_execute(query, [], opts, state) do - {:ok, res, state} -> - {:ok, res, state} - other -> - other - end - ({_name, _cmd}, {:error, _, _} = error) -> - error - end) + defp transaction_recv(state) do + case msg_recv(state) do + {:ok, packet(msg: ok_resp(status_flags: flags)), state} + when (flags &&& @server_more_results_exists) == @server_more_results_exists -> + # rollback/release has multiple results + transaction_recv(state) + {:ok, packet(msg: ok_resp(status_flags: flags)), state} -> + result = %Mariaex.Result{columns: [], rows: nil, num_rows: 0, + last_insert_id: 0} + {:ok, result, clean_state(state, flags)} + {:ok, packet(msg: error_resp(error_code: code, error_message: message)), state} -> + err = %Mariaex.Error{mariadb: %{code: code, message: message}} + # connection in bad state and unlikely to recover + {:disconnect, err, state} + {:error, reason} -> + recv_error(reason, state) + end end defp recv_error(reason, %{sock: {sock_mod, _}} = state) do diff --git a/test/transaction_test.exs b/test/transaction_test.exs new file mode 100644 index 0000000..c650784 --- /dev/null +++ b/test/transaction_test.exs @@ -0,0 +1,79 @@ +defmodule TransactionTest do + use ExUnit.Case + import Mariaex.TestHelper + + setup do + opts = [database: "mariaex_test", username: "mariaex_user", password: "mariaex_pass", backoff_type: :stop] + {:ok, pid} = Mariaex.Connection.start_link(opts) + {:ok, [pid: pid]} + end + + test "transaction shows correct transaction status", context do + pid = context[:pid] + opts = [mode: :transaction] + + assert DBConnection.status(pid, opts) == :idle + assert query("SELECT 42", []) == [[42]] + assert DBConnection.status(pid, opts) == :idle + {conn, _} = DBConnection.begin!(pid, opts) + assert DBConnection.status(conn, opts) == :transaction + DBConnection.commit!(conn, opts) + assert DBConnection.status(pid, opts) == :idle + assert query("SELECT 42", []) == [[42]] + assert DBConnection.status(pid) == :idle + end + + test "can not begin transaction if already begun", context do + pid = context[:pid] + opts = [mode: :transaction] + + {conn, _} = DBConnection.begin!(pid, opts) + assert {:error, %DBConnection.TransactionError{status: :transaction}} = + DBConnection.begin(conn, opts) + DBConnection.commit!(conn, opts) + end + + test "can not commit or rollback transaction if not begun", context do + pid = context[:pid] + opts = [mode: :transaction] + + assert {:error, %DBConnection.TransactionError{status: :idle}} = + DBConnection.commit(pid, opts) + assert {:error, %DBConnection.TransactionError{status: :idle}} = + DBConnection.rollback(pid, opts) + end + + test "savepoint transaction shows correct transaction status", context do + pid = context[:pid] + opts = [mode: :savepoint] + + {conn, _} = DBConnection.begin!(pid, [mode: :transaction]) + assert DBConnection.status(conn, opts) == :transaction + + assert {:ok, conn, _} = DBConnection.begin(conn, opts) + assert DBConnection.status(conn, opts) == :transaction + DBConnection.commit!(conn, opts) + assert DBConnection.status(pid, opts) == :transaction + + assert {:ok, conn, _} = DBConnection.begin(pid, opts) + assert DBConnection.status(conn, opts) == :transaction + DBConnection.rollback!(conn, opts) + assert DBConnection.status(pid, opts) == :transaction + + DBConnection.commit!(pid, [mode: :transaction]) + assert DBConnection.status(pid) == :idle + assert query("SELECT 42", []) == [[42]] + end + + test "can not begin, commit or rollback savepoint transaction if not begun", context do + pid = context[:pid] + opts = [mode: :savepoint] + + assert {:error, %DBConnection.TransactionError{status: :idle}} = + DBConnection.begin(pid, opts) + assert {:error, %DBConnection.TransactionError{status: :idle}} = + DBConnection.commit(pid, opts) + assert {:error, %DBConnection.TransactionError{status: :idle}} = + DBConnection.rollback(pid, opts) + end +end From 0491f72a20a687379b517f541b16a227c774041d Mon Sep 17 00:00:00 2001 From: James Fish Date: Sun, 13 Aug 2017 00:29:08 +0100 Subject: [PATCH 05/19] Support explicit cursor API --- lib/mariaex/protocol.ex | 114 +++++++++++++++++++++++++++++----------- lib/mariaex/structs.ex | 2 +- test/stream_test.exs | 79 ++++++++++++++++++++++++++-- 3 files changed, 160 insertions(+), 35 deletions(-) diff --git a/lib/mariaex/protocol.ex b/lib/mariaex/protocol.ex index 1f3ec7e..d4d7ddf 100644 --- a/lib/mariaex/protocol.ex +++ b/lib/mariaex/protocol.ex @@ -678,7 +678,13 @@ defmodule Mariaex.Protocol do defp clean_state(state, flags) do status = transaction_status(state, flags) - %{state | state: :running, state_data: nil, transaction_status: status} + state = %{state | state: :running, state_data: nil, transaction_status: status} + case status do + :idle -> + clean_cursors(state) + :transaction -> + state + end end defp transaction_status(_, flags) when is_integer(flags) do @@ -693,6 +699,13 @@ defmodule Mariaex.Protocol do status end + defp clean_cursors(%{cursors: cursors} = state) do + for {_ref, {_status, id, _info}} <- cursors, is_integer(id) do + msg_send(stmt_close(command: com_stmt_close(), statement_id: id), state, 0) + end + %{state | cursors: %{}} + end + @doc """ DBConnection callback """ @@ -738,8 +751,8 @@ defmodule Mariaex.Protocol do {:close_prepare_declare, id, query} -> prepare_declare(&close_prepare(id, query, &1), params, opts, state) {:text, _} -> - cursor = %Cursor{statement_id: :text, params: params, ref: make_ref()} - {:ok, cursor, state} + cursor = %Cursor{statement_id: :text, ref: make_ref()} + declare(cursor, params, state) end end @@ -767,8 +780,14 @@ defmodule Mariaex.Protocol do defp declare(id, params, opts, state) do max_rows = Keyword.get(opts, :max_rows, @max_rows) - cursor = %Cursor{statement_id: id, params: params, ref: make_ref(), max_rows: max_rows} - {:ok, cursor, state} + cursor = %Cursor{statement_id: id, ref: make_ref(), max_rows: max_rows} + declare(cursor, params, state) + end + + defp declare(%Cursor{ref: ref, statement_id: id} = cursor, params, state) do + state = put_in(state.cursors[ref], {:first, id, params}) + # close cursor if idle + {:ok, cursor, clean_state(state, nil)} end defp prepare_declare(prepare, params, opts, state) do @@ -788,28 +807,59 @@ defmodule Mariaex.Protocol do Cache.take(cache, name) end - def handle_first(query, %Cursor{statement_id: :text, params: params}, opts, state) do + def handle_fetch(query, cursor, opts, state) do + %Cursor{ref: ref, statement_id: id} = cursor + %{cursors: cursors} = state + case cursors do + %{^ref => {:first, _, params}} -> + first(query, cursor, params, opts, state) |> fetch_result(ref, id) + %{^ref => {:cont, _, columns}} -> + next(query, cursor, columns, state) |> fetch_result(ref, id) + %{^ref => {:halt, _, columns}} -> + # cursor finished, empty result + result = %Mariaex.Result{rows: [], num_rows: 0} + {:halt, {result, columns}, state} + %{} -> + msg = "could not find active cursor: #{inspect cursor}" + {:error, Mariaex.Error.exception(msg), state} + end + end + + defp fetch_result({:cont, {_, columns} = res, state}, ref, id) do + {:cont, res, put_in(state.cursors[ref], {:cont, id, columns})} + end + defp fetch_result({:halt, {_, columns} = res, state}, ref, id) do + {:halt, res, put_in(state.cursors[ref], {:halt, id, columns})} + end + defp fetch_result({:error, _, _} = error, _ref, _id) do + error + end + defp fetch_result({:disconnect, _, _} = disconnect, _ref, _id) do + disconnect + end + + defp first(query, %Cursor{statement_id: :text}, params, opts, state) do case handle_execute(query, params, opts, state) do {:ok, result, state} -> - {:deallocate, result, state} + {:halt, result, state} other -> other end end - def handle_first(query, %Cursor{statement_id: id, ref: ref, params: params}, _, state) do + defp first(query, %Cursor{statement_id: id}, params, _, state) do msg_send(stmt_execute(command: com_stmt_execute(), parameters: params, statement_id: id, flags: @cursor_type_read_only, iteration_count: 1), state, 0) - binary_first_recv(state, ref, query) + binary_first_recv(state, query) end - defp binary_first_recv(state, ref, query) do + defp binary_first_recv(state, query) do case binary_first_recv(state) do {:eof, columns, flags, state} -> - binary_first_resultset(state, query, ref, columns, [], flags) + binary_first_resultset(state, query, columns, [], flags) {:resultset, columns, rows, flags, state} -> - binary_first_resultset(state, query, ref, columns, rows, flags) + binary_first_resultset(state, query, columns, rows, flags) {:ok, packet(msg: ok_resp()) = packet, state} -> {:ok, result, state} = handle_ok_packet(packet, query, state) - {:deallocate, result, state} + {:halt, result, state} {:ok, packet, state} -> handle_error(packet, query, state) {:error, reason} -> @@ -826,36 +876,34 @@ defmodule Mariaex.Protocol do end end - defp binary_first_resultset(state, query, ref, columns, rows, flags) do + defp binary_first_resultset(state, query, columns, rows, flags) do cond do (flags &&& @server_more_results_exists) == @server_more_results_exists -> binary_first_more(state, query, columns, rows) (flags &&& @server_status_cursor_exists) == @server_status_cursor_exists -> - %{cursors: cursors} = state - state = clean_state(%{state | cursors: Map.put(cursors, ref, columns)}, flags) - {:ok, {%Mariaex.Result{rows: rows, connection_id: state.connection_id}, columns}, state} + result = %Mariaex.Result{rows: rows, connection_id: state.connection_id} + {:cont, {result, columns}, clean_state(state, flags)} true -> result = %Mariaex.Result{rows: rows, connection_id: state.connection_id} - {:deallocate, {result, columns}, clean_state(state, flags)} + {:halt, {result, columns}, clean_state(state, flags)} end end defp binary_first_more(state, query, columns, rows) do case binary_query_more(state, query, columns, rows) do {:ok, res, state} -> - {:deallocate, res, state} + {:halt, res, state} other -> other end end - def handle_next(query, %Cursor{statement_id: id, ref: ref, max_rows: max_rows}, _, state) do + defp next(query, %Cursor{statement_id: id, max_rows: max_rows}, columns, state) do msg_send(stmt_fetch(command: com_stmt_fetch(), statement_id: id, num_rows: max_rows), state, 0) - binary_next_recv(state, ref, query) + binary_next_recv(state, query, columns) end - defp binary_next_recv(%{cursors: cursors} = state, ref, query) do - columns = Map.fetch!(cursors, ref) + defp binary_next_recv(state, query, columns) do case bin_rows_recv(state, columns) do {:eof, rows, flags, state} -> binary_next_resultset(state, columns, rows, flags) @@ -870,19 +918,23 @@ defmodule Mariaex.Protocol do cond do (flags &&& @server_status_last_row_sent) == @server_status_last_row_sent -> result = %Mariaex.Result{rows: rows, connection_id: state.connection_id} - {:deallocate, {result, columns}, clean_state(state, flags)} + {:halt, {result, columns}, clean_state(state, flags)} (flags &&& @server_status_cursor_exists) == @server_status_cursor_exists -> result = %Mariaex.Result{rows: rows, connection_id: state.connection_id} - {:ok, {result, columns}, clean_state(state, flags)} + {:cont, {result, columns}, clean_state(state, flags)} end end - def handle_deallocate(_, %Cursor{statement_id: :text}, _, state) do - {:ok, nil, state} - end - def handle_deallocate(query, %Cursor{statement_id: id, ref: ref}, _, state) do - %{cursors: cursors} = state - deallocate(id, query, %{state | cursors: Map.delete(cursors, ref)}) + def handle_deallocate(query, cursor, _, state) do + %Cursor{ref: ref, statement_id: id} = cursor + case pop_in(state.cursors[ref]) do + {nil, state} -> + {:ok, nil, state} + {_exists, state} when id == :text -> + {:ok, nil, state} + {_exists, state} -> + deallocate(id, query, state) + end end defp deallocate(id, query, state) do diff --git a/lib/mariaex/structs.ex b/lib/mariaex/structs.ex index d7fe510..e29aec9 100644 --- a/lib/mariaex/structs.ex +++ b/lib/mariaex/structs.ex @@ -35,5 +35,5 @@ end defmodule Mariaex.Cursor do @moduledoc false - defstruct [:ref, :statement_id, :params, max_rows: 0] + defstruct [:ref, :statement_id, max_rows: 0] end diff --git a/test/stream_test.exs b/test/stream_test.exs index 0cc25f7..e89674a 100644 --- a/test/stream_test.exs +++ b/test/stream_test.exs @@ -17,9 +17,8 @@ defmodule StreamTest do test "simple text stream", context do assert Mariaex.transaction(context[:pid], fn(conn) -> - stream = Mariaex.stream(conn, "SELECT * FROM stream", [], []) - assert [%Mariaex.Result{num_rows: 0, rows: []}, - %Mariaex.Result{num_rows: 2, rows: [[1, "foo"], [2, "bar"]]}] = + stream = Mariaex.stream(conn, "SELECT * FROM stream", [], [query_type: :text]) + assert [%Mariaex.Result{num_rows: 2, rows: [[1, "foo"], [2, "bar"]]}] = Enum.to_list(stream) :done end) == {:ok, :done} @@ -147,6 +146,80 @@ defmodule StreamTest do end) == {:ok, :done} end + test "simple text cursor", context do + query = %Mariaex.Query{type: :text, statement: "SELECT * FROM stream", + ref: make_ref(), num_params: 0} + assert {:ok, cursor} = Mariaex.transaction(context[:pid], fn(conn) -> + assert {:ok, cursor} = DBConnection.declare(conn, query, []) + assert {:halt, %Mariaex.Result{num_rows: 2, rows: [[1, "foo"], [2, "bar"]]}} = DBConnection.fetch(conn, query, cursor) + + # no results once halt, don't re-execute + assert {:halt, %Mariaex.Result{num_rows: 0, rows: []}} = DBConnection.fetch(conn, query, cursor) + cursor + end) + + pid = context[:pid] + + # cursor gets removed when transaction ends + assert_raise Mariaex.Error, ~r"could not find active cursor", + fn -> DBConnection.fetch!(pid, query, cursor) end + + # deallocate should never fail + assert {:ok, _} = DBConnection.deallocate(pid, query, cursor) + end + + test "simple unnamed prepared cursor", context do + query = prepare("", "SELECT * FROM stream") + assert {:ok, cursor} = Mariaex.transaction(context[:pid], fn(conn) -> + cursor = DBConnection.declare!(conn, query, []) + assert {:cont, %Mariaex.Result{num_rows: 0, rows: []}} = + DBConnection.fetch(conn, query, cursor) + assert {:halt, %Mariaex.Result{num_rows: 2, rows: [[1, "foo"], [2, "bar"]]}} = + DBConnection.fetch(conn, query, cursor) + + # no results once halt, don't re-execute + assert {:halt, %Mariaex.Result{num_rows: 0, rows: []}} = DBConnection.fetch(conn, query, cursor) + cursor + end) + + pid = context[:pid] + + # cursor gets removed when transaction ends + assert_raise Mariaex.Error, ~r"could not find active cursor", + fn -> DBConnection.fetch!(pid, query, cursor) end + + # deallocate should never fail + assert {:ok, _} = DBConnection.deallocate(pid, query, cursor) + + assert [[1, "foo"], [2, "bar"]] = execute(query, []) + end + + test "simple named prepared cursor", context do + query = prepare("stream", "SELECT * FROM stream") + assert {:ok, cursor} = Mariaex.transaction(context[:pid], fn(conn) -> + cursor = DBConnection.declare!(conn, query, []) + assert {:cont, %Mariaex.Result{num_rows: 0, rows: []}} = + DBConnection.fetch(conn, query, cursor) + assert {:halt, %Mariaex.Result{num_rows: 2, rows: [[1, "foo"], [2, "bar"]]}} = + DBConnection.fetch(conn, query, cursor) + + # no results once halt, don't re-execute + assert {:halt, %Mariaex.Result{num_rows: 0, rows: []}} = DBConnection.fetch(conn, query, cursor) + cursor + end) + + pid = context[:pid] + + # cursor gets removed when transaction ends + assert_raise Mariaex.Error, ~r"could not find active cursor", + fn -> DBConnection.fetch!(pid, query, cursor) end + + # deallocate should never fail + assert {:ok, _} = DBConnection.deallocate(pid, query, cursor) + + assert [[1, "foo"], [2, "bar"]] = execute(query, []) + end + defp connect() do opts = [database: "mariaex_test", username: "mariaex_user", password: "mariaex_pass", backoff_type: :stop] Mariaex.Connection.start_link(opts) From 9f2350d8a3901d4c11dede4d1a4fc11ac611538b Mon Sep 17 00:00:00 2001 From: James Fish Date: Sun, 13 Aug 2017 11:45:58 +0100 Subject: [PATCH 06/19] Remove reserved query name errors --- lib/mariaex/protocol.ex | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/lib/mariaex/protocol.ex b/lib/mariaex/protocol.ex index d4d7ddf..124b175 100644 --- a/lib/mariaex/protocol.ex +++ b/lib/mariaex/protocol.ex @@ -12,7 +12,6 @@ defmodule Mariaex.Protocol do use DBConnection use Bitwise - @reserved_prefix "MARIAEX_" @timeout 5000 @cache_size 100 @max_rows 500 @@ -326,9 +325,6 @@ defmodule Mariaex.Protocol do @doc """ DBConnection callback """ - def handle_prepare(%Query{name: @reserved_prefix <> _} = query, _, s) do - reserved_error(query, s) - end def handle_prepare(%Query{type: nil} = query, opts, s) do case handle_prepare(%Query{query | type: :binary}, opts, s) do {:error, %Mariaex.Error{mariadb: %{code: 1295}}, s} -> @@ -442,9 +438,6 @@ defmodule Mariaex.Protocol do @doc """ DBConnection callback """ - def handle_execute(%Query{name: @reserved_prefix <> _, reserved?: false} = query, _, s) do - reserved_error(query, s) - end def handle_execute(%Query{type: :text, statement: statement} = query, [], _opts, state) do send_text_query(state, statement) |> text_query_recv(query) end @@ -709,9 +702,6 @@ defmodule Mariaex.Protocol do @doc """ DBConnection callback """ - def handle_close(%Query{name: @reserved_prefix <> _ , reserved?: false} = query, _, s) do - reserved_error(query, s) - end def handle_close(%Query{type: :text}, _, s) do {:ok, nil, s} end @@ -1250,9 +1240,4 @@ defmodule Mariaex.Protocol do {:error, error, clean_state(s, nil)} end end - - defp reserved_error(query, s) do - error = ArgumentError.exception("query #{inspect query} uses reserved name") - {:error, error, s} - end end From 869c0b7ff71780479b4d1c14596f97382a713a50 Mon Sep 17 00:00:00 2001 From: James Fish Date: Sun, 13 Aug 2017 11:57:28 +0100 Subject: [PATCH 07/19] Add max rows option to each cursor fetch --- lib/mariaex/protocol.ex | 25 +++++++++++-------------- lib/mariaex/structs.ex | 2 +- test/stream_test.exs | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/lib/mariaex/protocol.ex b/lib/mariaex/protocol.ex index 124b175..4221f78 100644 --- a/lib/mariaex/protocol.ex +++ b/lib/mariaex/protocol.ex @@ -732,14 +732,15 @@ defmodule Mariaex.Protocol do end end - def handle_declare(query, params, opts, state) do + def handle_declare(query, params, _, state) do case declare_lookup(query, state) do {:declare, id} -> - declare(id, params, opts, state) + cursor = %Cursor{statement_id: id, ref: make_ref()} + declare(cursor, params, state) {:prepare_declare, query} -> - prepare_declare(&prepare(query, &1), params, opts, state) + prepare_declare(&prepare(query, &1), params, state) {:close_prepare_declare, id, query} -> - prepare_declare(&close_prepare(id, query, &1), params, opts, state) + prepare_declare(&close_prepare(id, query, &1), params, state) {:text, _} -> cursor = %Cursor{statement_id: :text, ref: make_ref()} declare(cursor, params, state) @@ -768,23 +769,18 @@ defmodule Mariaex.Protocol do end end - defp declare(id, params, opts, state) do - max_rows = Keyword.get(opts, :max_rows, @max_rows) - cursor = %Cursor{statement_id: id, ref: make_ref(), max_rows: max_rows} - declare(cursor, params, state) - end - defp declare(%Cursor{ref: ref, statement_id: id} = cursor, params, state) do state = put_in(state.cursors[ref], {:first, id, params}) # close cursor if idle {:ok, cursor, clean_state(state, nil)} end - defp prepare_declare(prepare, params, opts, state) do + defp prepare_declare(prepare, params, state) do case prepare.(state) do {:ok, query, state} -> id = prepare_declare_lookup(query, state) - declare(id, params, opts, state) + cursor = %Cursor{statement_id: id, ref: make_ref()} + declare(cursor, params, state) {err, _, _} = error when err in [:error, :disconnect] -> error end @@ -804,7 +800,7 @@ defmodule Mariaex.Protocol do %{^ref => {:first, _, params}} -> first(query, cursor, params, opts, state) |> fetch_result(ref, id) %{^ref => {:cont, _, columns}} -> - next(query, cursor, columns, state) |> fetch_result(ref, id) + next(query, cursor, columns, opts, state) |> fetch_result(ref, id) %{^ref => {:halt, _, columns}} -> # cursor finished, empty result result = %Mariaex.Result{rows: [], num_rows: 0} @@ -888,7 +884,8 @@ defmodule Mariaex.Protocol do end end - defp next(query, %Cursor{statement_id: id, max_rows: max_rows}, columns, state) do + defp next(query, %Cursor{statement_id: id}, columns, opts, state) do + max_rows = Keyword.get(opts, :max_rows, @max_rows) msg_send(stmt_fetch(command: com_stmt_fetch(), statement_id: id, num_rows: max_rows), state, 0) binary_next_recv(state, query, columns) end diff --git a/lib/mariaex/structs.ex b/lib/mariaex/structs.ex index e29aec9..cea7583 100644 --- a/lib/mariaex/structs.ex +++ b/lib/mariaex/structs.ex @@ -35,5 +35,5 @@ end defmodule Mariaex.Cursor do @moduledoc false - defstruct [:ref, :statement_id, max_rows: 0] + defstruct [:ref, :statement_id] end diff --git a/test/stream_test.exs b/test/stream_test.exs index e89674a..bbda627 100644 --- a/test/stream_test.exs +++ b/test/stream_test.exs @@ -220,6 +220,24 @@ defmodule StreamTest do assert [[1, "foo"], [2, "bar"]] = execute(query, []) end + test "fetch fetches max_rows", context do + query = prepare("", "SELECT * FROM stream") + assert Mariaex.transaction(context[:pid], fn(conn) -> + cursor = DBConnection.declare!(conn, query, []) + assert {:cont, %Mariaex.Result{num_rows: 0, rows: []}} = + DBConnection.fetch(conn, query, cursor) + assert {:cont, %Mariaex.Result{num_rows: 1, rows: [[1, "foo"]]}} = + DBConnection.fetch(conn, query, cursor, [max_rows: 1]) + assert {:ok, _} = DBConnection.deallocate(conn, query, cursor) + + assert %Mariaex.Result{rows: [[1, "foo"], [2, "bar"]]} = + Mariaex.execute!(conn, query, []) + :done + end) == {:ok, :done} + + assert [[1, "foo"], [2, "bar"]] = execute(query, []) + end + defp connect() do opts = [database: "mariaex_test", username: "mariaex_user", password: "mariaex_pass", backoff_type: :stop] Mariaex.Connection.start_link(opts) From c1c74753684e6528d24532bf198ebf4a8a77e86c Mon Sep 17 00:00:00 2001 From: James Fish Date: Sun, 26 Nov 2017 15:24:44 -0800 Subject: [PATCH 08/19] Raise DBConnection.ConnectionError on connection error --- lib/mariaex.ex | 28 ++++++++++++++++------------ lib/mariaex/protocol.ex | 15 +++++++++++++-- test/query_test.exs | 3 ++- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/lib/mariaex.ex b/lib/mariaex.ex index 8e54222..4be0882 100644 --- a/lib/mariaex.ex +++ b/lib/mariaex.ex @@ -240,10 +240,12 @@ defmodule Mariaex do {:ok, Mariaex.Result.t} | {:error, Mariaex.Error.t} def execute(conn, %Query{} = query, params, opts \\ []) do case DBConnection.execute(conn, query, params, defaults(opts)) do - {:error, %ArgumentError{} = err} -> + {:ok, _} = ok -> + ok + {:error, %Mariaex.Error{}} = error -> + error + {:error, err} -> raise err - other -> - other end end @@ -289,10 +291,10 @@ defmodule Mariaex do case DBConnection.close(conn, query, defaults(opts)) do {:ok, _} -> :ok - {:error, %ArgumentError{} = err} -> + {:error, %Mariaex.Error{}} = error -> + error + {:error, err} -> raise err - other -> - other end end @@ -421,19 +423,21 @@ defmodule Mariaex do case DBConnection.prepare_execute(conn, query, params, defaults(opts)) do {:ok, _, result} -> {:ok, result} - {:error, %ArgumentError{} = err} -> - raise err - {:error, _} = error -> + {:error, %Mariaex.Error{}} = error -> error + {:error, err} -> + raise err end end defp prepare_binary(conn, query, opts) do case DBConnection.prepare(conn, query, defaults(opts)) do - {:error, %ArgumentError{} = err} -> + {:ok, _} = ok -> + ok + {:error, %Mariaex.Error{}} = error -> + error + {:error, err} -> raise err - other -> - other end end diff --git a/lib/mariaex/protocol.ex b/lib/mariaex/protocol.ex index 4221f78..4bec7dc 100644 --- a/lib/mariaex/protocol.ex +++ b/lib/mariaex/protocol.ex @@ -15,6 +15,7 @@ defmodule Mariaex.Protocol do @timeout 5000 @cache_size 100 @max_rows 500 + @nonposix_errors [:closed, :timeout] @maxpacketbytes 50000000 @mysql_native_password "mysql_native_password" @@ -1054,8 +1055,18 @@ defmodule Mariaex.Protocol do Do disconnect """ def do_disconnect(s, {tag, action, reason, buffer}) do - err = Mariaex.Error.exception(tag: tag, action: action, reason: reason) - do_disconnect(s, err, buffer) + msg = "#{tag} #{action}: #{format_error(tag, reason)}" + {:disconnect, DBConnection.ConnectionError.exception(msg), %{s | buffer: buffer}} + end + + defp format_error(_, reason) when reason in @nonposix_errors do + Atom.to_string(reason) + end + defp format_error(:tcp, reason) do + "#{:inet.format_error(reason)} - #{inspect(reason)}" + end + defp format_error(:ssl, reason) do + "#{:ssl.format_error(reason)} - #{inspect(reason)}" end defp do_disconnect(%{connection_id: connection_id} = state, %Mariaex.Error{} = err, buffer) do diff --git a/test/query_test.exs b/test/query_test.exs index 4eda2c0..36f3948 100644 --- a/test/query_test.exs +++ b/test/query_test.exs @@ -40,7 +40,8 @@ defmodule QueryTest do Process.flag(:trap_exit, true) capture_log fn -> - assert %Mariaex.Error{} = query("DO SLEEP(0.1)", [], timeout: 0) + assert_raise DBConnection.ConnectionError, "tcp recv: closed", + fn -> query("DO SLEEP(10)", [], timeout: 50) end assert_receive {:EXIT, ^conn, {:shutdown, %DBConnection.ConnectionError{}}} end end From 02bc0fb030e2da695c43846e39defa89d6021b75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Fri, 31 Aug 2018 23:53:33 +0200 Subject: [PATCH 09/19] Update to DBConnection 2.0 --- mix.exs | 2 +- mix.lock | 2 +- test/query_test.exs | 17 ++++++++++------- test/start_test.exs | 25 ++++++++++++++++++------- test/test_helper.exs | 6 ------ test/transaction_test.exs | 1 - 6 files changed, 30 insertions(+), 23 deletions(-) diff --git a/mix.exs b/mix.exs index 698e90c..f3f7a0b 100644 --- a/mix.exs +++ b/mix.exs @@ -25,7 +25,7 @@ defmodule Mariaex.Mixfile do defp deps do [{:decimal, "~> 1.2"}, - {:db_connection, "~> 1.1", github: "elixir-ecto/db_connection", ref: "4947966"}, + {:db_connection, "~> 2.0.0-dev", github: "elixir-ecto/db_connection", ref: "6d477c"}, {:coverex, "~> 1.4.10", only: :test}, {:ex_doc, ">= 0.0.0", only: :dev}, {:poison, ">= 0.0.0", optional: true}] diff --git a/mix.lock b/mix.lock index 8b1958d..a069ae4 100644 --- a/mix.lock +++ b/mix.lock @@ -2,7 +2,7 @@ "certifi": {:hex, :certifi, "0.7.0", "861a57f3808f7eb0c2d1802afeaae0fa5de813b0df0979153cbafcd853ababaf", [:rebar3], [], "hexpm"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, "coverex": {:hex, :coverex, "1.4.10", "f6b68f95b3d51d04571a09dd2071c980e8398a38cf663db22b903ecad1083d51", [:mix], [{:httpoison, "~> 0.9", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, - "db_connection": {:git, "https://github.com/elixir-ecto/db_connection.git", "49479667131329376adf1c2c0e9a16bcf470aa84", [ref: "4947966"]}, + "db_connection": {:git, "https://github.com/elixir-ecto/db_connection.git", "6d477c1030cd527fb87b01e2ca61b73bc4a9cd21", [ref: "6d477c"]}, "decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"}, "earmark": {:hex, :earmark, "1.0.3", "89bdbaf2aca8bbb5c97d8b3b55c5dd0cff517ecc78d417e87f1d0982e514557b", [:mix], [], "hexpm"}, "ex_doc": {:hex, :ex_doc, "0.14.5", "c0433c8117e948404d93ca69411dd575ec6be39b47802e81ca8d91017a0cf83c", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, diff --git a/test/query_test.exs b/test/query_test.exs index 36f3948..4ff50e3 100644 --- a/test/query_test.exs +++ b/test/query_test.exs @@ -1,8 +1,9 @@ defmodule QueryTest do use ExUnit.Case, async: true import Mariaex.TestHelper + import ExUnit.CaptureLog - @opts [database: "mariaex_test", username: "mariaex_user", password: "mariaex_pass", cache_size: 2, backoff_type: :stop] + @opts [database: "mariaex_test", username: "mariaex_user", password: "mariaex_pass", cache_size: 2, backoff_type: :stop, max_restarts: 0] setup context do connection_opts = context[:connection_opts] || [] @@ -36,14 +37,16 @@ defmodule QueryTest do end test "queries are dequeued after previous query is processed", context do + Process.flag(:trap_exit, true) conn = context[:pid] - Process.flag(:trap_exit, true) - capture_log fn -> - assert_raise DBConnection.ConnectionError, "tcp recv: closed", - fn -> query("DO SLEEP(10)", [], timeout: 50) end - assert_receive {:EXIT, ^conn, {:shutdown, %DBConnection.ConnectionError{}}} - end + assert capture_log(fn -> + assert_raise DBConnection.ConnectionError, "tcp recv: closed", fn -> + query("DO SLEEP(10)", [], timeout: 50) + end + + assert_receive {:EXIT, ^conn, :killed}, 5000 + end) =~ "** (DBConnection.ConnectionError)" end test "support primitive data types using prepared statements", context do diff --git a/test/start_test.exs b/test/start_test.exs index 9b79cf5..8e03c9a 100644 --- a/test/start_test.exs +++ b/test/start_test.exs @@ -1,14 +1,25 @@ defmodule StartTest do use ExUnit.Case, async: true + import ExUnit.CaptureLog test "connection_errors" do - Process.flag :trap_exit, true - assert {:error, {%Mariaex.Error{mariadb: %{message: "Unknown database 'non_existing'"}}, _}} = - Mariaex.Connection.start_link(username: "mariaex_user", password: "mariaex_pass", database: "non_existing", sync_connect: true, backoff_type: :stop) - assert {:error, {%Mariaex.Error{mariadb: %{message: "Access denied for user " <> _}}, _}} = - Mariaex.Connection.start_link(username: "non_existing", database: "mariaex_test", sync_connect: true, backoff_type: :stop) - assert {:error, {%Mariaex.Error{message: "tcp connect: econnrefused"}, _}} = - Mariaex.Connection.start_link(username: "mariaex_user", password: "mariaex_pass", database: "mariaex_test", port: 60999, sync_connect: true, backoff_type: :stop) + Process.flag(:trap_exit, true) + opts = [sync_connect: true, backoff_type: :stop, max_restarts: 0] + + assert capture_log(fn -> + {:ok, pid} = Mariaex.start_link([username: "mariaex_user", password: "mariaex_pass", database: "non_existing"] ++ opts) + assert_receive {:EXIT, ^pid, :killed}, 5000 + end) =~ "** (Mariaex.Error) (1049): Unknown database 'non_existing'" + + assert capture_log(fn -> + {:ok, pid} = Mariaex.start_link([username: "non_existing", database: "mariaex_test"] ++ opts) + assert_receive {:EXIT, ^pid, :killed}, 5000 + end) =~ "** (Mariaex.Error) (1045): Access denied for user 'non_existing'@'localhost'" + + assert capture_log(fn -> + {:ok, pid} = Mariaex.start_link([username: "mariaex_user", password: "mariaex_pass", database: "mariaex_test", port: 60999] ++ opts) + assert_receive {:EXIT, ^pid, :killed}, 5000 + end) =~ "** (Mariaex.Error) tcp connect: econnrefused" end ## Tests tagged with :ssl_tests are excluded from running by default (see test_helper.exs) diff --git a/test/test_helper.exs b/test/test_helper.exs index 0834724..eb192ff 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -158,12 +158,6 @@ defmodule Mariaex.TestHelper do end end - def capture_log(fun) do - Logger.remove_backend(:console) - fun.() - Logger.add_backend(:console, flush: true) - end - def length_encode_row(row) do Enum.map_join(row, &(<> <> &1)) end diff --git a/test/transaction_test.exs b/test/transaction_test.exs index c650784..ffac103 100644 --- a/test/transaction_test.exs +++ b/test/transaction_test.exs @@ -30,7 +30,6 @@ defmodule TransactionTest do {conn, _} = DBConnection.begin!(pid, opts) assert {:error, %DBConnection.TransactionError{status: :transaction}} = DBConnection.begin(conn, opts) - DBConnection.commit!(conn, opts) end test "can not commit or rollback transaction if not begun", context do From e319241538b49ac643d3201db379d0d070304478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 2 Sep 2018 23:25:02 +0200 Subject: [PATCH 10/19] Update to latest DBCOnnection --- mix.exs | 2 +- mix.lock | 2 +- test/start_test.exs | 2 +- test/stream_test.exs | 92 --------------------------------------- test/transaction_test.exs | 59 ++----------------------- 5 files changed, 6 insertions(+), 151 deletions(-) diff --git a/mix.exs b/mix.exs index f3f7a0b..2ecaeb6 100644 --- a/mix.exs +++ b/mix.exs @@ -25,7 +25,7 @@ defmodule Mariaex.Mixfile do defp deps do [{:decimal, "~> 1.2"}, - {:db_connection, "~> 2.0.0-dev", github: "elixir-ecto/db_connection", ref: "6d477c"}, + {:db_connection, "~> 2.0.0-dev", github: "elixir-ecto/db_connection", ref: "854d917"}, {:coverex, "~> 1.4.10", only: :test}, {:ex_doc, ">= 0.0.0", only: :dev}, {:poison, ">= 0.0.0", optional: true}] diff --git a/mix.lock b/mix.lock index a069ae4..007df0b 100644 --- a/mix.lock +++ b/mix.lock @@ -2,7 +2,7 @@ "certifi": {:hex, :certifi, "0.7.0", "861a57f3808f7eb0c2d1802afeaae0fa5de813b0df0979153cbafcd853ababaf", [:rebar3], [], "hexpm"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, "coverex": {:hex, :coverex, "1.4.10", "f6b68f95b3d51d04571a09dd2071c980e8398a38cf663db22b903ecad1083d51", [:mix], [{:httpoison, "~> 0.9", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, - "db_connection": {:git, "https://github.com/elixir-ecto/db_connection.git", "6d477c1030cd527fb87b01e2ca61b73bc4a9cd21", [ref: "6d477c"]}, + "db_connection": {:git, "https://github.com/elixir-ecto/db_connection.git", "854d917721b820576da4b74b3620bf359c61179f", [ref: "854d917"]}, "decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"}, "earmark": {:hex, :earmark, "1.0.3", "89bdbaf2aca8bbb5c97d8b3b55c5dd0cff517ecc78d417e87f1d0982e514557b", [:mix], [], "hexpm"}, "ex_doc": {:hex, :ex_doc, "0.14.5", "c0433c8117e948404d93ca69411dd575ec6be39b47802e81ca8d91017a0cf83c", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, diff --git a/test/start_test.exs b/test/start_test.exs index 8e03c9a..9d26445 100644 --- a/test/start_test.exs +++ b/test/start_test.exs @@ -14,7 +14,7 @@ defmodule StartTest do assert capture_log(fn -> {:ok, pid} = Mariaex.start_link([username: "non_existing", database: "mariaex_test"] ++ opts) assert_receive {:EXIT, ^pid, :killed}, 5000 - end) =~ "** (Mariaex.Error) (1045): Access denied for user 'non_existing'@'localhost'" + end) =~ "** (Mariaex.Error) (1045): Access denied for user 'non_existing'" assert capture_log(fn -> {:ok, pid} = Mariaex.start_link([username: "mariaex_user", password: "mariaex_pass", database: "mariaex_test", port: 60999] ++ opts) diff --git a/test/stream_test.exs b/test/stream_test.exs index bbda627..1ca15da 100644 --- a/test/stream_test.exs +++ b/test/stream_test.exs @@ -146,98 +146,6 @@ defmodule StreamTest do end) == {:ok, :done} end - test "simple text cursor", context do - query = %Mariaex.Query{type: :text, statement: "SELECT * FROM stream", - ref: make_ref(), num_params: 0} - assert {:ok, cursor} = Mariaex.transaction(context[:pid], fn(conn) -> - assert {:ok, cursor} = DBConnection.declare(conn, query, []) - assert {:halt, %Mariaex.Result{num_rows: 2, rows: [[1, "foo"], [2, "bar"]]}} = DBConnection.fetch(conn, query, cursor) - - # no results once halt, don't re-execute - assert {:halt, %Mariaex.Result{num_rows: 0, rows: []}} = DBConnection.fetch(conn, query, cursor) - cursor - end) - - pid = context[:pid] - - # cursor gets removed when transaction ends - assert_raise Mariaex.Error, ~r"could not find active cursor", - fn -> DBConnection.fetch!(pid, query, cursor) end - - # deallocate should never fail - assert {:ok, _} = DBConnection.deallocate(pid, query, cursor) - end - - test "simple unnamed prepared cursor", context do - query = prepare("", "SELECT * FROM stream") - assert {:ok, cursor} = Mariaex.transaction(context[:pid], fn(conn) -> - cursor = DBConnection.declare!(conn, query, []) - assert {:cont, %Mariaex.Result{num_rows: 0, rows: []}} = - DBConnection.fetch(conn, query, cursor) - assert {:halt, %Mariaex.Result{num_rows: 2, rows: [[1, "foo"], [2, "bar"]]}} = - DBConnection.fetch(conn, query, cursor) - - # no results once halt, don't re-execute - assert {:halt, %Mariaex.Result{num_rows: 0, rows: []}} = DBConnection.fetch(conn, query, cursor) - cursor - end) - - pid = context[:pid] - - # cursor gets removed when transaction ends - assert_raise Mariaex.Error, ~r"could not find active cursor", - fn -> DBConnection.fetch!(pid, query, cursor) end - - # deallocate should never fail - assert {:ok, _} = DBConnection.deallocate(pid, query, cursor) - - assert [[1, "foo"], [2, "bar"]] = execute(query, []) - end - - test "simple named prepared cursor", context do - query = prepare("stream", "SELECT * FROM stream") - assert {:ok, cursor} = Mariaex.transaction(context[:pid], fn(conn) -> - cursor = DBConnection.declare!(conn, query, []) - assert {:cont, %Mariaex.Result{num_rows: 0, rows: []}} = - DBConnection.fetch(conn, query, cursor) - assert {:halt, %Mariaex.Result{num_rows: 2, rows: [[1, "foo"], [2, "bar"]]}} = - DBConnection.fetch(conn, query, cursor) - - # no results once halt, don't re-execute - assert {:halt, %Mariaex.Result{num_rows: 0, rows: []}} = DBConnection.fetch(conn, query, cursor) - cursor - end) - - pid = context[:pid] - - # cursor gets removed when transaction ends - assert_raise Mariaex.Error, ~r"could not find active cursor", - fn -> DBConnection.fetch!(pid, query, cursor) end - - # deallocate should never fail - assert {:ok, _} = DBConnection.deallocate(pid, query, cursor) - - assert [[1, "foo"], [2, "bar"]] = execute(query, []) - end - - test "fetch fetches max_rows", context do - query = prepare("", "SELECT * FROM stream") - assert Mariaex.transaction(context[:pid], fn(conn) -> - cursor = DBConnection.declare!(conn, query, []) - assert {:cont, %Mariaex.Result{num_rows: 0, rows: []}} = - DBConnection.fetch(conn, query, cursor) - assert {:cont, %Mariaex.Result{num_rows: 1, rows: [[1, "foo"]]}} = - DBConnection.fetch(conn, query, cursor, [max_rows: 1]) - assert {:ok, _} = DBConnection.deallocate(conn, query, cursor) - - assert %Mariaex.Result{rows: [[1, "foo"], [2, "bar"]]} = - Mariaex.execute!(conn, query, []) - :done - end) == {:ok, :done} - - assert [[1, "foo"], [2, "bar"]] = execute(query, []) - end - defp connect() do opts = [database: "mariaex_test", username: "mariaex_user", password: "mariaex_pass", backoff_type: :stop] Mariaex.Connection.start_link(opts) diff --git a/test/transaction_test.exs b/test/transaction_test.exs index ffac103..d8e01f0 100644 --- a/test/transaction_test.exs +++ b/test/transaction_test.exs @@ -15,64 +15,11 @@ defmodule TransactionTest do assert DBConnection.status(pid, opts) == :idle assert query("SELECT 42", []) == [[42]] assert DBConnection.status(pid, opts) == :idle - {conn, _} = DBConnection.begin!(pid, opts) - assert DBConnection.status(conn, opts) == :transaction - DBConnection.commit!(conn, opts) + DBConnection.transaction(pid, fn conn -> + assert DBConnection.status(conn, opts) == :transaction + end, opts) assert DBConnection.status(pid, opts) == :idle assert query("SELECT 42", []) == [[42]] assert DBConnection.status(pid) == :idle end - - test "can not begin transaction if already begun", context do - pid = context[:pid] - opts = [mode: :transaction] - - {conn, _} = DBConnection.begin!(pid, opts) - assert {:error, %DBConnection.TransactionError{status: :transaction}} = - DBConnection.begin(conn, opts) - end - - test "can not commit or rollback transaction if not begun", context do - pid = context[:pid] - opts = [mode: :transaction] - - assert {:error, %DBConnection.TransactionError{status: :idle}} = - DBConnection.commit(pid, opts) - assert {:error, %DBConnection.TransactionError{status: :idle}} = - DBConnection.rollback(pid, opts) - end - - test "savepoint transaction shows correct transaction status", context do - pid = context[:pid] - opts = [mode: :savepoint] - - {conn, _} = DBConnection.begin!(pid, [mode: :transaction]) - assert DBConnection.status(conn, opts) == :transaction - - assert {:ok, conn, _} = DBConnection.begin(conn, opts) - assert DBConnection.status(conn, opts) == :transaction - DBConnection.commit!(conn, opts) - assert DBConnection.status(pid, opts) == :transaction - - assert {:ok, conn, _} = DBConnection.begin(pid, opts) - assert DBConnection.status(conn, opts) == :transaction - DBConnection.rollback!(conn, opts) - assert DBConnection.status(pid, opts) == :transaction - - DBConnection.commit!(pid, [mode: :transaction]) - assert DBConnection.status(pid) == :idle - assert query("SELECT 42", []) == [[42]] - end - - test "can not begin, commit or rollback savepoint transaction if not begun", context do - pid = context[:pid] - opts = [mode: :savepoint] - - assert {:error, %DBConnection.TransactionError{status: :idle}} = - DBConnection.begin(pid, opts) - assert {:error, %DBConnection.TransactionError{status: :idle}} = - DBConnection.commit(pid, opts) - assert {:error, %DBConnection.TransactionError{status: :idle}} = - DBConnection.rollback(pid, opts) - end end From 6abbee253856d1a6e389c396dae9db1a7123cb0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 13 Sep 2018 13:18:25 +0200 Subject: [PATCH 11/19] Update to latest DBConnection --- lib/mariaex.ex | 41 +++++++++++------------------------------ lib/mariaex/protocol.ex | 28 +++++++++++++++------------- mix.exs | 2 +- mix.lock | 2 +- test/query_test.exs | 6 ++---- test/test_helper.exs | 14 +++++++------- 6 files changed, 37 insertions(+), 56 deletions(-) diff --git a/lib/mariaex.ex b/lib/mariaex.ex index 4be0882..c989f1a 100644 --- a/lib/mariaex.ex +++ b/lib/mariaex.ex @@ -138,12 +138,11 @@ defmodule Mariaex do def query(conn, statement, params, opts) do case Keyword.get(opts, :query_type) do :text -> - query = %Query{type: :text, statement: statement, ref: make_ref(), - num_params: 0} - execute(conn, query, [], opts) + query = %Query{type: :text, statement: statement, ref: make_ref(), num_params: 0} + run_query(:execute, conn, query, [], opts) type when type in [:binary, nil] -> query = %Query{type: type, statement: statement} - prepare_execute(conn, query, params, defaults(opts)) + run_query(:prepare_execute, conn, query, params, defaults(opts)) end end @@ -237,16 +236,9 @@ defmodule Mariaex do Mariaex.execute(conn, query, ["%my%"]) """ @spec execute(conn, Mariaex.Query.t, list, Keyword.t) :: - {:ok, Mariaex.Result.t} | {:error, Mariaex.Error.t} + {:ok, Mariaex.Query.t, Mariaex.Result.t} | {:error, Mariaex.Error.t} def execute(conn, %Query{} = query, params, opts \\ []) do - case DBConnection.execute(conn, query, params, defaults(opts)) do - {:ok, _} = ok -> - ok - {:error, %Mariaex.Error{}} = error -> - error - {:error, err} -> - raise err - end + DBConnection.execute(conn, query, params, defaults(opts)) end @doc """ @@ -255,12 +247,7 @@ defmodule Mariaex do """ @spec execute!(conn, Mariaex.Query.t, list, Keyword.t) :: Mariaex.Result.t def execute!(conn, query, params, opts \\ []) do - case execute(conn, query, params, opts) do - {:ok, res} -> - res - {:error, err} -> - raise err - end + DBConnection.execute!(conn, query, params, defaults(opts)) end @doc """ @@ -291,10 +278,8 @@ defmodule Mariaex do case DBConnection.close(conn, query, defaults(opts)) do {:ok, _} -> :ok - {:error, %Mariaex.Error{}} = error -> - error {:error, err} -> - raise err + err end end @@ -419,14 +404,12 @@ defmodule Mariaex do ## Helpers - defp prepare_execute(conn, query, params, opts) do - case DBConnection.prepare_execute(conn, query, params, defaults(opts)) do + defp run_query(op, conn, query, params, opts) do + case apply(DBConnection, op, [conn, query, params, defaults(opts)]) do {:ok, _, result} -> {:ok, result} - {:error, %Mariaex.Error{}} = error -> + {:error, _} = error -> error - {:error, err} -> - raise err end end @@ -434,10 +417,8 @@ defmodule Mariaex do case DBConnection.prepare(conn, query, defaults(opts)) do {:ok, _} = ok -> ok - {:error, %Mariaex.Error{}} = error -> - error {:error, err} -> - raise err + err end end diff --git a/lib/mariaex/protocol.ex b/lib/mariaex/protocol.ex index 4bec7dc..a1c82b8 100644 --- a/lib/mariaex/protocol.ex +++ b/lib/mariaex/protocol.ex @@ -224,7 +224,7 @@ defmodule Mariaex.Protocol do case send_text_query(state, statement) |> text_query_recv(query) do {:error, error, _} -> {:error, error} - {:ok, _, state} -> + {:ok, _, _, state} -> activate(state, state.buffer) |> connected() end end @@ -505,9 +505,10 @@ defmodule Mariaex.Protocol do case text_query_recv(state) do {:resultset, columns, rows, flags, state} -> result = %Mariaex.Result{rows: rows, connection_id: state.connection_id} - {:ok, {result, columns}, clean_state(state, flags)} + {:ok, query, {result, columns}, clean_state(state, flags)} {:ok, packet(msg: ok_resp()) = packet, state} -> - handle_ok_packet(packet, query, state) + {:ok, result, state} = handle_ok_packet(packet, query, state) + {:ok, query, result, state} {:ok, packet, state} -> handle_error(packet, query, state) {:error, reason} -> @@ -566,7 +567,8 @@ defmodule Mariaex.Protocol do {:resultset, columns, bin_rows, flags, state} -> binary_query_resultset(state, query, columns, bin_rows, flags) {:ok, packet(msg: ok_resp()) = packet, state} -> - handle_ok_packet(packet, query, state) + {:ok, result, state} = handle_ok_packet(packet, query, state) + {:ok, query, result, state} {:ok, packet, state} -> handle_error(packet, query, state) {:error, reason} -> @@ -626,7 +628,7 @@ defmodule Mariaex.Protocol do binary_query_more(state, query, columns, rows) true -> result = %Mariaex.Result{rows: rows, connection_id: state.connection_id} - {:ok, {result, columns}, clean_state(state, flags)} + {:ok, query, {result, columns}, clean_state(state, flags)} end end @@ -635,7 +637,7 @@ defmodule Mariaex.Protocol do {:ok, packet(msg: ok_resp(affected_rows: affected_rows, last_insert_id: last_insert_id, status_flags: flags)), state} -> result = %Mariaex.Result{rows: rows, num_rows: affected_rows, last_insert_id: last_insert_id, connection_id: state.connection_id} - {:ok, {result, columns}, clean_state(state, flags)} + {:ok, query, {result, columns}, clean_state(state, flags)} {:ok, packet, state} -> handle_error(packet, query, state) {:error, reason} -> @@ -737,14 +739,14 @@ defmodule Mariaex.Protocol do case declare_lookup(query, state) do {:declare, id} -> cursor = %Cursor{statement_id: id, ref: make_ref()} - declare(cursor, params, state) + declare(cursor, query, params, state) {:prepare_declare, query} -> prepare_declare(&prepare(query, &1), params, state) {:close_prepare_declare, id, query} -> prepare_declare(&close_prepare(id, query, &1), params, state) {:text, _} -> cursor = %Cursor{statement_id: :text, ref: make_ref()} - declare(cursor, params, state) + declare(cursor, query, params, state) end end @@ -770,10 +772,10 @@ defmodule Mariaex.Protocol do end end - defp declare(%Cursor{ref: ref, statement_id: id} = cursor, params, state) do + defp declare(%Cursor{ref: ref, statement_id: id} = cursor, query, params, state) do state = put_in(state.cursors[ref], {:first, id, params}) # close cursor if idle - {:ok, cursor, clean_state(state, nil)} + {:ok, query, cursor, clean_state(state, nil)} end defp prepare_declare(prepare, params, state) do @@ -781,7 +783,7 @@ defmodule Mariaex.Protocol do {:ok, query, state} -> id = prepare_declare_lookup(query, state) cursor = %Cursor{statement_id: id, ref: make_ref()} - declare(cursor, params, state) + declare(cursor, query, params, state) {err, _, _} = error when err in [:error, :disconnect] -> error end @@ -827,7 +829,7 @@ defmodule Mariaex.Protocol do defp first(query, %Cursor{statement_id: :text}, params, opts, state) do case handle_execute(query, params, opts, state) do - {:ok, result, state} -> + {:ok, _, result, state} -> {:halt, result, state} other -> other @@ -878,7 +880,7 @@ defmodule Mariaex.Protocol do defp binary_first_more(state, query, columns, rows) do case binary_query_more(state, query, columns, rows) do - {:ok, res, state} -> + {:ok, _query, res, state} -> {:halt, res, state} other -> other diff --git a/mix.exs b/mix.exs index 2ecaeb6..7be93cd 100644 --- a/mix.exs +++ b/mix.exs @@ -25,7 +25,7 @@ defmodule Mariaex.Mixfile do defp deps do [{:decimal, "~> 1.2"}, - {:db_connection, "~> 2.0.0-dev", github: "elixir-ecto/db_connection", ref: "854d917"}, + {:db_connection, "~> 2.0.0-dev", github: "elixir-ecto/db_connection", ref: "03b78e1"}, {:coverex, "~> 1.4.10", only: :test}, {:ex_doc, ">= 0.0.0", only: :dev}, {:poison, ">= 0.0.0", optional: true}] diff --git a/mix.lock b/mix.lock index 007df0b..aa0673d 100644 --- a/mix.lock +++ b/mix.lock @@ -2,7 +2,7 @@ "certifi": {:hex, :certifi, "0.7.0", "861a57f3808f7eb0c2d1802afeaae0fa5de813b0df0979153cbafcd853ababaf", [:rebar3], [], "hexpm"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, "coverex": {:hex, :coverex, "1.4.10", "f6b68f95b3d51d04571a09dd2071c980e8398a38cf663db22b903ecad1083d51", [:mix], [{:httpoison, "~> 0.9", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, - "db_connection": {:git, "https://github.com/elixir-ecto/db_connection.git", "854d917721b820576da4b74b3620bf359c61179f", [ref: "854d917"]}, + "db_connection": {:git, "https://github.com/elixir-ecto/db_connection.git", "03b78e12d42764d8c7474a5def232d2343517396", [ref: "03b78e1"]}, "decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"}, "earmark": {:hex, :earmark, "1.0.3", "89bdbaf2aca8bbb5c97d8b3b55c5dd0cff517ecc78d417e87f1d0982e514557b", [:mix], [], "hexpm"}, "ex_doc": {:hex, :ex_doc, "0.14.5", "c0433c8117e948404d93ca69411dd575ec6be39b47802e81ca8d91017a0cf83c", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, diff --git a/test/query_test.exs b/test/query_test.exs index 4ff50e3..c998b2f 100644 --- a/test/query_test.exs +++ b/test/query_test.exs @@ -41,10 +41,8 @@ defmodule QueryTest do conn = context[:pid] assert capture_log(fn -> - assert_raise DBConnection.ConnectionError, "tcp recv: closed", fn -> - query("DO SLEEP(10)", [], timeout: 50) - end - + %DBConnection.ConnectionError{message: message} = query("DO SLEEP(10)", [], timeout: 50) + assert message =~ "tcp recv: closed" assert_receive {:EXIT, ^conn, :killed}, 5000 end) =~ "** (DBConnection.ConnectionError)" end diff --git a/test/test_helper.exs b/test/test_helper.exs index eb192ff..a8878c0 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -101,7 +101,7 @@ defmodule Mariaex.TestHelper do case Mariaex.Connection.query(pid, unquote(stat), unquote(params), unquote(opts)) do {:ok, %Mariaex.Result{rows: nil}} -> :ok {:ok, %Mariaex.Result{rows: rows}} -> rows - {:error, %Mariaex.Error{} = err} -> err + {:error, err} -> err end end end @@ -113,7 +113,7 @@ defmodule Mariaex.TestHelper do case Mariaex.query(var!(context)[:pid], unquote(stat), unquote(params), opts) do {:ok, %Mariaex.Result{rows: nil}} -> :ok {:ok, %Mariaex.Result{rows: rows}} -> rows - {:error, %Mariaex.Error{} = err} -> err + {:error, err} -> err end end end @@ -134,7 +134,7 @@ defmodule Mariaex.TestHelper do quote do case Mariaex.prepare(var!(context)[:pid], unquote(stat), unquote(opts)) do {:ok, %Mariaex.Query{} = query} -> query - {:error, %Mariaex.Error{} = err} -> err + {:error, err} -> err end end end @@ -142,9 +142,9 @@ defmodule Mariaex.TestHelper do defmacro execute(query, params, opts \\ []) do quote do case Mariaex.execute(var!(context)[:pid], unquote(query), unquote(params), unquote(opts)) do - {:ok, %Mariaex.Result{rows: nil}} -> :ok - {:ok, %Mariaex.Result{rows: rows}} -> rows - {:error, %Mariaex.Error{} = err} -> err + {:ok, %Mariaex.Query{}, %Mariaex.Result{rows: nil}} -> :ok + {:ok, %Mariaex.Query{}, %Mariaex.Result{rows: rows}} -> rows + {:error, err} -> err end end end @@ -153,7 +153,7 @@ defmodule Mariaex.TestHelper do quote do case Mariaex.close(var!(context)[:pid], unquote(query), unquote(opts)) do :ok -> :ok - {:error, %Mariaex.Error{} = err} -> err + {:error, err} -> err end end end From 6783f8fa50e2af27d687ac967a35d5a1f3f38f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 13 Sep 2018 13:24:30 +0200 Subject: [PATCH 12/19] Update docs --- lib/mariaex.ex | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/mariaex.ex b/lib/mariaex.ex index c989f1a..62f0f50 100644 --- a/lib/mariaex.ex +++ b/lib/mariaex.ex @@ -210,8 +210,10 @@ defmodule Mariaex do @doc """ Runs an (extended) prepared query and returns the result as - `{:ok, %Mariaex.Result{}}` or `{:error, %Mariaex.Error{}}` if there was an - error. Parameters are given as part of the prepared query, `%Mariaex.Query{}`. + `{:ok, %Mariaex.Query{}, %Mariaex.Result{}}` or `{:error, %Mariaex.Error{}}` + if there was an error. Parameters are given as part of the prepared query, + `%Mariaex.Query{}`. + See the README for information on how Mariaex encodes and decodes Elixir values by default. See `Mariaex.Query` for the query data and `Mariaex.Result` for the result data. From 1f33f8ea8681168752c32937626589d02046f852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 13 Sep 2018 13:26:47 +0200 Subject: [PATCH 13/19] Fix return types --- lib/mariaex.ex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/mariaex.ex b/lib/mariaex.ex index 62f0f50..18d6711 100644 --- a/lib/mariaex.ex +++ b/lib/mariaex.ex @@ -280,8 +280,8 @@ defmodule Mariaex do case DBConnection.close(conn, query, defaults(opts)) do {:ok, _} -> :ok - {:error, err} -> - err + {:error, _} = error -> + error end end @@ -419,8 +419,8 @@ defmodule Mariaex do case DBConnection.prepare(conn, query, defaults(opts)) do {:ok, _} = ok -> ok - {:error, err} -> - err + {:error, _} = error -> + error end end From 31e896a0555a8224c416ec186f5e45096f2ac7d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 13 Sep 2018 13:27:43 +0200 Subject: [PATCH 14/19] Update types --- lib/mariaex.ex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/mariaex.ex b/lib/mariaex.ex index 18d6711..669fabc 100644 --- a/lib/mariaex.ex +++ b/lib/mariaex.ex @@ -132,7 +132,7 @@ defmodule Mariaex do param_types: ["text", "text"], result_types: ["text"]) """ - @spec query(conn, iodata, list, Keyword.t) :: {:ok, Mariaex.Result.t} | {:error, Mariaex.Error.t} + @spec query(conn, iodata, list, Keyword.t) :: {:ok, Mariaex.Result.t} | {:error, Exception.t} def query(conn, statement, params \\ [], opts \\ []) def query(conn, statement, params, opts) do @@ -181,7 +181,7 @@ defmodule Mariaex do Mariaex.prepare(conn, "", "CREATE TABLE posts (id serial, title text)") """ - @spec prepare(conn, iodata, iodata, Keyword.t) :: {:ok, Mariaex.Query.t} | {:error, Mariaex.Error.t} + @spec prepare(conn, iodata, iodata, Keyword.t) :: {:ok, Mariaex.Query.t} | {:error, Exception.t} def prepare(conn, name, statement, opts \\ []) do case Keyword.get(opts, :query_type) do :text -> @@ -238,7 +238,7 @@ defmodule Mariaex do Mariaex.execute(conn, query, ["%my%"]) """ @spec execute(conn, Mariaex.Query.t, list, Keyword.t) :: - {:ok, Mariaex.Query.t, Mariaex.Result.t} | {:error, Mariaex.Error.t} + {:ok, Mariaex.Query.t, Mariaex.Result.t} | {:error, Exception.t} def execute(conn, %Query{} = query, params, opts \\ []) do DBConnection.execute(conn, query, params, defaults(opts)) end @@ -270,7 +270,7 @@ defmodule Mariaex do query = Mariaex.prepare!(conn, "SELECT id FROM posts WHERE title like $1") Mariaex.close(conn, query) """ - @spec close(conn, Mariaex.Query.t, Keyword.t) :: :ok | {:error, Mariaex.Error.t} + @spec close(conn, Mariaex.Query.t, Keyword.t) :: :ok | {:error, Exception.t} def close(conn, query, opts \\ []) def close(_, %Query{type: :text}, _) do From 0483010c695fed5fb83fef761c4949b1f088f0ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 16 Sep 2018 21:54:16 +0200 Subject: [PATCH 15/19] Update dbconnection reference --- mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 7be93cd..9408004 100644 --- a/mix.exs +++ b/mix.exs @@ -25,7 +25,7 @@ defmodule Mariaex.Mixfile do defp deps do [{:decimal, "~> 1.2"}, - {:db_connection, "~> 2.0.0-dev", github: "elixir-ecto/db_connection", ref: "03b78e1"}, + {:db_connection, "~> 2.0.0-dev", github: "elixir-ecto/db_connection", ref: "fbe3775"}, {:coverex, "~> 1.4.10", only: :test}, {:ex_doc, ">= 0.0.0", only: :dev}, {:poison, ">= 0.0.0", optional: true}] diff --git a/mix.lock b/mix.lock index aa0673d..7c6ec60 100644 --- a/mix.lock +++ b/mix.lock @@ -2,7 +2,7 @@ "certifi": {:hex, :certifi, "0.7.0", "861a57f3808f7eb0c2d1802afeaae0fa5de813b0df0979153cbafcd853ababaf", [:rebar3], [], "hexpm"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, "coverex": {:hex, :coverex, "1.4.10", "f6b68f95b3d51d04571a09dd2071c980e8398a38cf663db22b903ecad1083d51", [:mix], [{:httpoison, "~> 0.9", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, - "db_connection": {:git, "https://github.com/elixir-ecto/db_connection.git", "03b78e12d42764d8c7474a5def232d2343517396", [ref: "03b78e1"]}, + "db_connection": {:git, "https://github.com/elixir-ecto/db_connection.git", "fbe3775534864e9ddbcdc4ca98e9ea3ea031704f", [ref: "fbe3775"]}, "decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"}, "earmark": {:hex, :earmark, "1.0.3", "89bdbaf2aca8bbb5c97d8b3b55c5dd0cff517ecc78d417e87f1d0982e514557b", [:mix], [], "hexpm"}, "ex_doc": {:hex, :ex_doc, "0.14.5", "c0433c8117e948404d93ca69411dd575ec6be39b47802e81ca8d91017a0cf83c", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, From f9a48cc9598328f006e88c3b0009d4440cbb3b27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 16 Sep 2018 21:55:57 +0200 Subject: [PATCH 16/19] Remove outdated pool config --- lib/mariaex.ex | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/lib/mariaex.ex b/lib/mariaex.ex index 669fabc..5fea5b5 100644 --- a/lib/mariaex.ex +++ b/lib/mariaex.ex @@ -61,8 +61,7 @@ defmodule Mariaex do * `:idle` - Either `:active` to asynchronously detect TCP disconnects when idle or `:passive` not to (default: `:passive`); * `:pool` - The pool module to use, see `DBConnection` for pool dependent - options, this option must be included with all requests contacting the pool - if not `DBConnection.Connection` (default: `DBConnection.Connection`); + options (default: `DBConnection.ConnectionPool`); * `:name` - A name to register the started process (see the `:name` option in `GenServer.start_link/3`). * `:datetime` - How datetimes should be returned. `:structs` for Elixir v1.3 @@ -172,8 +171,6 @@ defmodule Mariaex do (default: `#{@pool_timeout}`) * `:queue` - Whether to wait for connection in a queue (default: `true`); * `:timeout` - Prepare request timeout (default: `#{@timeout}`); - * `:pool` - The pool module to use, must match that set on - `start_link/1`, see `DBConnection` * `:query_type` - `:binary` to use binary protocol, `:text` to use text protocol or `nil` to try binary but fallback to text (default `nil`) @@ -226,8 +223,6 @@ defmodule Mariaex do * `:timeout` - Execute request timeout (default: `#{@timeout}`); * `:decode_mapper` - Fun to map each row in the result to a term after decoding, (default: `fn x -> x end`); - * `:pool` - The pool module to use, must match that set on - `start_link/1`, see `DBConnection` ## Examples @@ -262,8 +257,6 @@ defmodule Mariaex do (default: `#{@pool_timeout}`) * `:queue` - Whether to wait for connection in a queue (default: `true`); * `:timeout` - Prepare request timeout (default: `#{@timeout}`); - * `:pool` - The pool module to use, must match that set on - `start_link/1`, see `DBConnection` ## Examples @@ -322,16 +315,13 @@ defmodule Mariaex do (default: `#{@pool_timeout}`) * `:queue` - Whether to wait for connection in a queue (default: `true`); * `:timeout` - Transaction timeout (default: `#{@timeout}`); - * `:pool` - The pool module to use, must match that set on - `start_link/1`, see `DBConnection; * `:mode` - Set to `:savepoint` to use savepoints instead of an SQL transaction, otherwise set to `:transaction` (default: `:transaction`); - The `:timeout` is for the duration of the transaction and all nested transactions and requests. This timeout overrides timeouts set by internal - transactions and requests. The `:pool` and `:mode` will be used for all - requests inside the transaction function. + transactions and requests. The `:mode` will be used for all requests inside + the transaction function. ## Example From 884191780dcca85d7eb8bccab51572c64f153280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 16 Sep 2018 22:57:54 +0200 Subject: [PATCH 17/19] Add Mariaex.prepare_execute --- lib/mariaex.ex | 95 +++++++++++++++++++++++++----------- test/prepared_query_test.exs | 9 +++- test/test_helper.exs | 9 ++++ 3 files changed, 84 insertions(+), 29 deletions(-) diff --git a/lib/mariaex.ex b/lib/mariaex.ex index 5fea5b5..9739a7a 100644 --- a/lib/mariaex.ex +++ b/lib/mariaex.ex @@ -16,10 +16,14 @@ defmodule Mariaex do """ @type conn :: DBConnection.conn - @pool_timeout 5000 - @timeout 5000 @idle_timeout 5000 @max_rows 500 + + # Inherited from DBConnection + + @pool_timeout 5000 + @timeout 15000 + ### PUBLIC API ### @doc """ @@ -141,7 +145,7 @@ defmodule Mariaex do run_query(:execute, conn, query, [], opts) type when type in [:binary, nil] -> query = %Query{type: type, statement: statement} - run_query(:prepare_execute, conn, query, params, defaults(opts)) + run_query(:prepare_execute, conn, query, params, opts) end end @@ -187,7 +191,7 @@ defmodule Mariaex do {:ok, query} type when type in [:binary, nil] -> query = %Query{type: type, name: name, statement: statement} - prepare_binary(conn, query, opts) + DBConnection.prepare(conn, query, opts) end end @@ -205,6 +209,58 @@ defmodule Mariaex do end end + @doc """ + Prepares and executes a query and returns the result as + `{:ok, %Mariaex.Query{}, %Mariaex.Result{}}` or `{:error, %Mariaex.Error{}}` + if there was an error. Parameters are given as part of the prepared query, + `%Mariaex.Query{}`. + + See the README for information on how Mariaex encodes and decodes Elixir + values by default. See `Mariaex.Query` for the query data and + `Mariaex.Result` for the result data. + + ## Options + + * `:pool_timeout` - Time to wait in the queue for the connection + (default: `#{@pool_timeout}`) + * `:queue` - Whether to wait for connection in a queue (default: `true`); + * `:timeout` - Execute request timeout (default: `#{@timeout}`); + * `:decode_mapper` - Fun to map each row in the result to a term after + decoding, (default: `fn x -> x end`); + + ## Examples + + Mariaex.prepare_execute(conn, "", "SELECT id FROM posts WHERE title like $1", ["%my%"]) + + """ + @spec prepare_execute(conn, iodata, iodata, list, Keyword.t) :: + {:ok, Mariaex.Query.t, Mariaex.Result.t} | {:error, Exception.t} + def prepare_execute(conn, name, statement, params, opts \\ []) do + case Keyword.get(opts, :query_type) do + :text -> + query = %Query{type: :text, name: name, statement: statement, ref: make_ref(), num_params: 0} + DBConnection.execute(conn, query, params, opts) + type when type in [:binary, nil] -> + query = %Query{type: type, name: name, statement: statement} + DBConnection.prepare_execute(conn, query, params, opts) + end + end + + @doc """ + Prepares and executes a query and returns the result or raises + `Mariaex.Error` if there was an error. See `execute/4`. + """ + @spec prepare_execute!(conn, Mariaex.Query.t, list, Keyword.t) :: + {Mariaex.Query.t, Mariaex.Result.t} + def prepare_execute!(conn, name, statement, params, opts \\ []) do + case prepare_execute(conn, name, statement, params, opts) do + {:ok, query, result} -> + {query, result} + {:error, err} -> + raise err + end + end + @doc """ Runs an (extended) prepared query and returns the result as `{:ok, %Mariaex.Query{}, %Mariaex.Result{}}` or `{:error, %Mariaex.Error{}}` @@ -235,7 +291,7 @@ defmodule Mariaex do @spec execute(conn, Mariaex.Query.t, list, Keyword.t) :: {:ok, Mariaex.Query.t, Mariaex.Result.t} | {:error, Exception.t} def execute(conn, %Query{} = query, params, opts \\ []) do - DBConnection.execute(conn, query, params, defaults(opts)) + DBConnection.execute(conn, query, params, opts) end @doc """ @@ -244,7 +300,7 @@ defmodule Mariaex do """ @spec execute!(conn, Mariaex.Query.t, list, Keyword.t) :: Mariaex.Result.t def execute!(conn, query, params, opts \\ []) do - DBConnection.execute!(conn, query, params, defaults(opts)) + DBConnection.execute!(conn, query, params, opts) end @doc """ @@ -270,7 +326,7 @@ defmodule Mariaex do :ok end def close(conn, query, opts) do - case DBConnection.close(conn, query, defaults(opts)) do + case DBConnection.close(conn, query, opts) do {:ok, _} -> :ok {:error, _} = error -> @@ -332,7 +388,7 @@ defmodule Mariaex do @spec transaction(conn, ((DBConnection.t) -> result), Keyword.t) :: {:ok, result} | {:error, any} when result: var def transaction(conn, fun, opts \\ []) do - DBConnection.transaction(conn, fun, defaults(opts)) + DBConnection.transaction(conn, fun, opts) end @doc """ @@ -372,7 +428,7 @@ defmodule Mariaex do def stream(conn, query, params, opts \\ []) def stream(conn, %Query{} = query, params, opts) do - DBConnection.stream(conn, query, params, defaults(opts)) + DBConnection.stream(conn, query, params, opts) end def stream(conn, statement, params, opts) do case Keyword.get(opts, :query_type) do @@ -382,7 +438,7 @@ defmodule Mariaex do stream(conn, query, [], opts) type when type in [:binary, nil] -> query = %Query{type: type, statement: statement} - prepare_stream(conn, query, params, opts) + DBConnection.prepare_stream(conn, query, params, opts) end end @@ -397,28 +453,11 @@ defmodule Mariaex do ## Helpers defp run_query(op, conn, query, params, opts) do - case apply(DBConnection, op, [conn, query, params, defaults(opts)]) do + case apply(DBConnection, op, [conn, query, params, opts]) do {:ok, _, result} -> {:ok, result} {:error, _} = error -> error end end - - defp prepare_binary(conn, query, opts) do - case DBConnection.prepare(conn, query, defaults(opts)) do - {:ok, _} = ok -> - ok - {:error, _} = error -> - error - end - end - - defp prepare_stream(conn, query, params, opts) do - DBConnection.prepare_stream(conn, query, params, defaults(opts)) - end - - defp defaults(opts) do - Keyword.put_new(opts, :timeout, @timeout) - end end diff --git a/test/prepared_query_test.exs b/test/prepared_query_test.exs index 63b57fb..e9f1b67 100644 --- a/test/prepared_query_test.exs +++ b/test/prepared_query_test.exs @@ -28,7 +28,14 @@ defmodule PreparedQueryTest do assert [[42]] = query("SELECT 42", []) end - test "prepare query and execute different queries with same name", context do + test "prepare_execute, execute and close", context do + assert {%Mariaex.Query{} = query, [[42]]} = prepare_execute("42", "SELECT ?", [42]) + assert [[41]] = execute(query, [41]) + assert :ok = close(query) + assert [[43]] = query("SELECT ?", [43]) + end + + test "prepare and execute different queries with same name", context do query42 = prepare("select", "SELECT 42") assert close(query42) == :ok assert %Mariaex.Query{} = prepare("select", "SELECT 41") diff --git a/test/test_helper.exs b/test/test_helper.exs index a8878c0..934cbe2 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -149,6 +149,15 @@ defmodule Mariaex.TestHelper do end end + defmacro prepare_execute(name, statement, params, opts \\ []) do + quote do + case Mariaex.prepare_execute(var!(context)[:pid], unquote(name), unquote(statement), unquote(params), unquote(opts)) do + {:ok, %Mariaex.Query{} = query, %Mariaex.Result{rows: rows}} -> {query, rows} + {:error, err} -> err + end + end + end + defmacro close(query, opts \\ []) do quote do case Mariaex.close(var!(context)[:pid], unquote(query), unquote(opts)) do From 642d65047f3f1741e55f876eab4ba9d1948923e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 24 Sep 2018 10:34:49 +0200 Subject: [PATCH 18/19] Update mariaex.ex --- lib/mariaex.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mariaex.ex b/lib/mariaex.ex index 9739a7a..938d115 100644 --- a/lib/mariaex.ex +++ b/lib/mariaex.ex @@ -211,7 +211,7 @@ defmodule Mariaex do @doc """ Prepares and executes a query and returns the result as - `{:ok, %Mariaex.Query{}, %Mariaex.Result{}}` or `{:error, %Mariaex.Error{}}` + `{:ok, %Mariaex.Query{}, %Mariaex.Result{}}` or `{:error, exception}` if there was an error. Parameters are given as part of the prepared query, `%Mariaex.Query{}`. From ec1405598beed6beb5814ed325d2bd0f01897b7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 24 Sep 2018 10:35:52 +0200 Subject: [PATCH 19/19] Update DBConnection git ref --- mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 9408004..913a2ee 100644 --- a/mix.exs +++ b/mix.exs @@ -25,7 +25,7 @@ defmodule Mariaex.Mixfile do defp deps do [{:decimal, "~> 1.2"}, - {:db_connection, "~> 2.0.0-dev", github: "elixir-ecto/db_connection", ref: "fbe3775"}, + {:db_connection, "~> 2.0.0-dev", github: "elixir-ecto/db_connection", ref: "bb29b5c"}, {:coverex, "~> 1.4.10", only: :test}, {:ex_doc, ">= 0.0.0", only: :dev}, {:poison, ">= 0.0.0", optional: true}] diff --git a/mix.lock b/mix.lock index 7c6ec60..b5d8118 100644 --- a/mix.lock +++ b/mix.lock @@ -2,7 +2,7 @@ "certifi": {:hex, :certifi, "0.7.0", "861a57f3808f7eb0c2d1802afeaae0fa5de813b0df0979153cbafcd853ababaf", [:rebar3], [], "hexpm"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, "coverex": {:hex, :coverex, "1.4.10", "f6b68f95b3d51d04571a09dd2071c980e8398a38cf663db22b903ecad1083d51", [:mix], [{:httpoison, "~> 0.9", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, - "db_connection": {:git, "https://github.com/elixir-ecto/db_connection.git", "fbe3775534864e9ddbcdc4ca98e9ea3ea031704f", [ref: "fbe3775"]}, + "db_connection": {:git, "https://github.com/elixir-ecto/db_connection.git", "bb29b5cdd2b6759d5253b316fc907aa62077cce1", [ref: "bb29b5c"]}, "decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"}, "earmark": {:hex, :earmark, "1.0.3", "89bdbaf2aca8bbb5c97d8b3b55c5dd0cff517ecc78d417e87f1d0982e514557b", [:mix], [], "hexpm"}, "ex_doc": {:hex, :ex_doc, "0.14.5", "c0433c8117e948404d93ca69411dd575ec6be39b47802e81ca8d91017a0cf83c", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"},